dbapi-mongodb 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Naruhide KITADA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: dbapi-mongodb
3
+ Version: 0.1.0
4
+ Summary: DBAPI-style adapter that translates SQL (JOIN/DDL/aggregations) into MongoDB operations
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: pymongo<4.0,>=3.13
9
+ Requires-Dist: sqlglot>=25.7.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.3.0; extra == "dev"
12
+ Requires-Dist: sqlalchemy>=2.0.0; extra == "dev"
13
+ Dynamic: license-file
14
+
15
+ # dbapi-mongodb
16
+
17
+ DBAPI-style adapter that lets you execute a limited subset of SQL against MongoDB by translating SQL to Mongo queries. Built on `pymongo` (3.13.x for MongoDB 3.6 compatibility) and `SQLGlot`.
18
+
19
+ Purpose: let existing DB-API / SQLAlchemy Core / FastAPI code treat MongoDB as “just another dialect.”
20
+
21
+ - PyPI package name: `dbapi-mongodb`
22
+ - Module import name: `mongo_dbapi`
23
+
24
+ ## Features
25
+ - DBAPI-like `Connection`/`Cursor`
26
+ - SQL → Mongo: `SELECT/INSERT/UPDATE/DELETE`, `CREATE/DROP TABLE/INDEX` (ASC/DESC, UNIQUE, composite), `WHERE` (comparisons/`AND`/`OR`/`IN`/`BETWEEN`/`LIKE`→`$regex`/`ILIKE`/regex literal), `ORDER BY`, `LIMIT/OFFSET`, INNER/LEFT JOIN (equijoin, composite keys up to 3 hops), `GROUP BY` + aggregates (COUNT/SUM/AVG/MIN/MAX) + `HAVING`, `UNION ALL`, subqueries (`WHERE IN/EXISTS`, `FROM (SELECT ...)`), `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)` on MongoDB 5.x+
27
+ - `%s` positional and `%(name)s` named parameters; unsupported constructs raise Error IDs (e.g. `[mdb][E2]`)
28
+ - Error IDs for common failures: invalid URI, unsupported SQL, unsafe DML without WHERE, parse errors, connection/auth failures
29
+ - DBAPI fields: `rowcount`, `lastrowid`, `description` (column order: explicit order, or alpha for `SELECT *`; JOIN uses left→right)
30
+ - Transactions: `begin/commit/rollback` wrap Mongo sessions; MongoDB 3.6 and other unsupported envs are treated as no-op success
31
+ - Async dialect (thread-pool backed) for Core CRUD/DDL/Index with FastAPI-friendly usage; minimal ORM CRUD for single-table entities (relationships out of scope)
32
+
33
+ - Use cases
34
+ - Swap in Mongo as “another dialect” for existing SQLAlchemy Core–based infra (Engine/Connection + Table/Column)
35
+ - Point existing Core-based batch/report jobs at Mongo data with minimal changes
36
+ - Minimal ORM CRUD for single-table entities (PK → `_id`)
37
+ - Async dialect for FastAPI/async stacks (thread-pool implementation; native async later)
38
+
39
+ ## Requirements
40
+ - Python 3.10+
41
+ - MongoDB 3.6 (bundled `mongodb-3.6` binary) or later (note: bundled binary is 3.6, so transactions are unsupported)
42
+ - Virtualenv at `.venv` (already present); dependencies are managed via `pyproject.toml`
43
+
44
+ ## Installation
45
+ ```bash
46
+ pip install dbapi-mongodb
47
+ # (optional) with a virtualenv: python -m venv .venv && . .venv/bin/activate && pip install dbapi-mongodb
48
+ ```
49
+
50
+ ## Start local MongoDB (bundled 3.6)
51
+ ```bash
52
+ # Default port 27017; override with PORT
53
+ PORT=27018 ./startdb.sh
54
+ ```
55
+
56
+ ## Start local MongoDB 4.4 (replica set, bundled)
57
+ ```bash
58
+ # Default port 27019; uses bundled libssl1.1. LD_LIBRARY_PATH is set inside the script for mongod.
59
+ PORT=27019 ./start4xdb.sh
60
+ # Run tests against 4.x
61
+ MONGODB_URI=mongodb://127.0.0.1:27019 MONGODB_DB=mongo_dbapi_test .venv/bin/pytest -q
62
+ ```
63
+
64
+ ## Usage example
65
+ ```python
66
+ from mongo_dbapi import connect
67
+
68
+ conn = connect("mongodb://127.0.0.1:27018", "mongo_dbapi_test")
69
+ cur = conn.cursor()
70
+ cur.execute("INSERT INTO users (id, name) VALUES (%s, %s)", (1, "Alice"))
71
+
72
+ cur.execute("SELECT id, name FROM users WHERE id = %s", (1,))
73
+ print(cur.fetchall()) # [(1, 'Alice')]
74
+ print(cur.rowcount) # 1
75
+ ```
76
+
77
+ ## Supported SQL
78
+ - Statements: `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `CREATE/DROP TABLE`, `CREATE/DROP INDEX`
79
+ - WHERE: comparisons (`=`, `<>`, `>`, `<`, `<=`, `>=`), `AND`, `OR`, `IN`, `BETWEEN`, `LIKE` (`%`/`_` → `$regex`), `ILIKE`, regex literal `/.../`
80
+ - JOIN: INNER/LEFT equijoin (composite keys, up to 3 joins)
81
+ - Aggregation: `GROUP BY` with COUNT/SUM/AVG/MIN/MAX and `HAVING`
82
+ - Subqueries: `WHERE IN/EXISTS` and `FROM (SELECT ...)` (non-correlated; executed first)
83
+ - Set ops: `UNION ALL`
84
+ - Window: `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)` on MongoDB 5.x+ (`[mdb][E2]` on earlier versions)
85
+ - ORDER/LIMIT/OFFSET
86
+ - Unsupported: non-equi joins, FULL/RIGHT OUTER, `UNION` (distinct), window functions other than `ROW_NUMBER`, correlated subqueries, ORM relationships
87
+
88
+ ## SQLAlchemy
89
+ - DBAPI module attributes: `apilevel="2.0"`, `threadsafety=1`, `paramstyle="pyformat"`.
90
+ - Scheme: `mongodb+dbapi://...` dialect provided (sync + async/thread-pool).
91
+ - Scope: Core text()/Table/Column CRUD/DDL/Index、ORM 最小 CRUD(単一テーブル)、JOIN/UNION ALL/HAVING/subquery/ROW_NUMBER を実通信で確認済み。async dialect は Core CRUD/DDL/Index のラップで、ネイティブ async は今後検討。
92
+
93
+ ## Async (FastAPI/Core) - beta
94
+ - **Current implementation wraps the sync driver in a thread pool** (native async driver is planned). Provided via `mongo_dbapi.async_dbapi.connect_async`. API mirrors sync Core: awaitable CRUD/DDL/Index, JOIN/UNION ALL/HAVING/IN/EXISTS/FROM subquery.
95
+ - Transactions: effective on MongoDB 4.x+ only; 3.6 is no-op. Be mindful that MongoDB transactions differ from RDBMS in locking/perf; avoid heavy transactional workloads.
96
+ - Window: `ROW_NUMBER` is available on MongoDB 5.x+; earlier versions return `[mdb][E2] Unsupported SQL construct: WINDOW_FUNCTION`.
97
+ - FastAPI example:
98
+ ```python
99
+ from fastapi import FastAPI, Depends
100
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncConnection
101
+ from sqlalchemy import text
102
+
103
+ engine = create_async_engine("mongodb+dbapi://127.0.0.1:27019/mongo_dbapi_test")
104
+ app = FastAPI()
105
+
106
+ async def get_conn() -> AsyncConnection:
107
+ async with engine.connect() as conn:
108
+ yield conn
109
+
110
+ @app.get("/users/{user_id}")
111
+ async def get_user(user_id: str, conn: AsyncConnection = Depends(get_conn)):
112
+ rows = await conn.execute(text("SELECT id, name FROM users WHERE id = :id"), {"id": user_id})
113
+ row = rows.fetchone()
114
+ return dict(row) if row else {}
115
+ ```
116
+ - Limitations: async ORM/relationship and statement cache are out of scope; heavy concurrency uses a thread pool under the hood.
117
+
118
+ ## Support levels
119
+ - Tested/stable (real Mongo runs): single-collection CRUD, WHERE/ORDER/LIMIT/OFFSET, INNER/LEFT equijoin (up to 3 hops), GROUP BY + aggregates + HAVING, subqueries (WHERE IN/EXISTS, FROM (SELECT ...)), UNION ALL, `ROW_NUMBER()` (MongoDB 5.x+).
120
+ - Not supported / constraints: non-equi JOIN, FULL/RIGHT OUTER, distinct `UNION`, window functions other than `ROW_NUMBER`, correlated subqueries, ORM relationships; async is thread-pool based.
121
+
122
+ ## Running tests
123
+ ```bash
124
+ PORT=27018 ./startdb.sh # if 27017 is taken
125
+ MONGODB_URI=mongodb://127.0.0.1:27018 MONGODB_DB=mongo_dbapi_test .venv/bin/pytest -q
126
+ ```
127
+
128
+ ## Notes
129
+ - Transactions on MongoDB 3.6 are treated as no-op; 4.x+ (replica set) uses real sessions and the bundled 4.4 binary passes all tests.
130
+ - Error messages are fixed strings per `docs/spec.md`. Keep logs at DEBUG only (default INFO is silent).
131
+
132
+ ## License
133
+ MIT License (see `LICENSE`). Provided as-is without warranty; commercial use permitted.
134
+
135
+ ## GitHub Sponsors
136
+ Maintained in personal time. If this helps you run MongoDB from DB-API/SQLAlchemy stacks, consider supporting via GitHub Sponsors to keep fixes and version updates coming.
@@ -0,0 +1,122 @@
1
+ # dbapi-mongodb
2
+
3
+ DBAPI-style adapter that lets you execute a limited subset of SQL against MongoDB by translating SQL to Mongo queries. Built on `pymongo` (3.13.x for MongoDB 3.6 compatibility) and `SQLGlot`.
4
+
5
+ Purpose: let existing DB-API / SQLAlchemy Core / FastAPI code treat MongoDB as “just another dialect.”
6
+
7
+ - PyPI package name: `dbapi-mongodb`
8
+ - Module import name: `mongo_dbapi`
9
+
10
+ ## Features
11
+ - DBAPI-like `Connection`/`Cursor`
12
+ - SQL → Mongo: `SELECT/INSERT/UPDATE/DELETE`, `CREATE/DROP TABLE/INDEX` (ASC/DESC, UNIQUE, composite), `WHERE` (comparisons/`AND`/`OR`/`IN`/`BETWEEN`/`LIKE`→`$regex`/`ILIKE`/regex literal), `ORDER BY`, `LIMIT/OFFSET`, INNER/LEFT JOIN (equijoin, composite keys up to 3 hops), `GROUP BY` + aggregates (COUNT/SUM/AVG/MIN/MAX) + `HAVING`, `UNION ALL`, subqueries (`WHERE IN/EXISTS`, `FROM (SELECT ...)`), `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)` on MongoDB 5.x+
13
+ - `%s` positional and `%(name)s` named parameters; unsupported constructs raise Error IDs (e.g. `[mdb][E2]`)
14
+ - Error IDs for common failures: invalid URI, unsupported SQL, unsafe DML without WHERE, parse errors, connection/auth failures
15
+ - DBAPI fields: `rowcount`, `lastrowid`, `description` (column order: explicit order, or alpha for `SELECT *`; JOIN uses left→right)
16
+ - Transactions: `begin/commit/rollback` wrap Mongo sessions; MongoDB 3.6 and other unsupported envs are treated as no-op success
17
+ - Async dialect (thread-pool backed) for Core CRUD/DDL/Index with FastAPI-friendly usage; minimal ORM CRUD for single-table entities (relationships out of scope)
18
+
19
+ - Use cases
20
+ - Swap in Mongo as “another dialect” for existing SQLAlchemy Core–based infra (Engine/Connection + Table/Column)
21
+ - Point existing Core-based batch/report jobs at Mongo data with minimal changes
22
+ - Minimal ORM CRUD for single-table entities (PK → `_id`)
23
+ - Async dialect for FastAPI/async stacks (thread-pool implementation; native async later)
24
+
25
+ ## Requirements
26
+ - Python 3.10+
27
+ - MongoDB 3.6 (bundled `mongodb-3.6` binary) or later (note: bundled binary is 3.6, so transactions are unsupported)
28
+ - Virtualenv at `.venv` (already present); dependencies are managed via `pyproject.toml`
29
+
30
+ ## Installation
31
+ ```bash
32
+ pip install dbapi-mongodb
33
+ # (optional) with a virtualenv: python -m venv .venv && . .venv/bin/activate && pip install dbapi-mongodb
34
+ ```
35
+
36
+ ## Start local MongoDB (bundled 3.6)
37
+ ```bash
38
+ # Default port 27017; override with PORT
39
+ PORT=27018 ./startdb.sh
40
+ ```
41
+
42
+ ## Start local MongoDB 4.4 (replica set, bundled)
43
+ ```bash
44
+ # Default port 27019; uses bundled libssl1.1. LD_LIBRARY_PATH is set inside the script for mongod.
45
+ PORT=27019 ./start4xdb.sh
46
+ # Run tests against 4.x
47
+ MONGODB_URI=mongodb://127.0.0.1:27019 MONGODB_DB=mongo_dbapi_test .venv/bin/pytest -q
48
+ ```
49
+
50
+ ## Usage example
51
+ ```python
52
+ from mongo_dbapi import connect
53
+
54
+ conn = connect("mongodb://127.0.0.1:27018", "mongo_dbapi_test")
55
+ cur = conn.cursor()
56
+ cur.execute("INSERT INTO users (id, name) VALUES (%s, %s)", (1, "Alice"))
57
+
58
+ cur.execute("SELECT id, name FROM users WHERE id = %s", (1,))
59
+ print(cur.fetchall()) # [(1, 'Alice')]
60
+ print(cur.rowcount) # 1
61
+ ```
62
+
63
+ ## Supported SQL
64
+ - Statements: `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `CREATE/DROP TABLE`, `CREATE/DROP INDEX`
65
+ - WHERE: comparisons (`=`, `<>`, `>`, `<`, `<=`, `>=`), `AND`, `OR`, `IN`, `BETWEEN`, `LIKE` (`%`/`_` → `$regex`), `ILIKE`, regex literal `/.../`
66
+ - JOIN: INNER/LEFT equijoin (composite keys, up to 3 joins)
67
+ - Aggregation: `GROUP BY` with COUNT/SUM/AVG/MIN/MAX and `HAVING`
68
+ - Subqueries: `WHERE IN/EXISTS` and `FROM (SELECT ...)` (non-correlated; executed first)
69
+ - Set ops: `UNION ALL`
70
+ - Window: `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)` on MongoDB 5.x+ (`[mdb][E2]` on earlier versions)
71
+ - ORDER/LIMIT/OFFSET
72
+ - Unsupported: non-equi joins, FULL/RIGHT OUTER, `UNION` (distinct), window functions other than `ROW_NUMBER`, correlated subqueries, ORM relationships
73
+
74
+ ## SQLAlchemy
75
+ - DBAPI module attributes: `apilevel="2.0"`, `threadsafety=1`, `paramstyle="pyformat"`.
76
+ - Scheme: `mongodb+dbapi://...` dialect provided (sync + async/thread-pool).
77
+ - Scope: Core text()/Table/Column CRUD/DDL/Index、ORM 最小 CRUD(単一テーブル)、JOIN/UNION ALL/HAVING/subquery/ROW_NUMBER を実通信で確認済み。async dialect は Core CRUD/DDL/Index のラップで、ネイティブ async は今後検討。
78
+
79
+ ## Async (FastAPI/Core) - beta
80
+ - **Current implementation wraps the sync driver in a thread pool** (native async driver is planned). Provided via `mongo_dbapi.async_dbapi.connect_async`. API mirrors sync Core: awaitable CRUD/DDL/Index, JOIN/UNION ALL/HAVING/IN/EXISTS/FROM subquery.
81
+ - Transactions: effective on MongoDB 4.x+ only; 3.6 is no-op. Be mindful that MongoDB transactions differ from RDBMS in locking/perf; avoid heavy transactional workloads.
82
+ - Window: `ROW_NUMBER` is available on MongoDB 5.x+; earlier versions return `[mdb][E2] Unsupported SQL construct: WINDOW_FUNCTION`.
83
+ - FastAPI example:
84
+ ```python
85
+ from fastapi import FastAPI, Depends
86
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncConnection
87
+ from sqlalchemy import text
88
+
89
+ engine = create_async_engine("mongodb+dbapi://127.0.0.1:27019/mongo_dbapi_test")
90
+ app = FastAPI()
91
+
92
+ async def get_conn() -> AsyncConnection:
93
+ async with engine.connect() as conn:
94
+ yield conn
95
+
96
+ @app.get("/users/{user_id}")
97
+ async def get_user(user_id: str, conn: AsyncConnection = Depends(get_conn)):
98
+ rows = await conn.execute(text("SELECT id, name FROM users WHERE id = :id"), {"id": user_id})
99
+ row = rows.fetchone()
100
+ return dict(row) if row else {}
101
+ ```
102
+ - Limitations: async ORM/relationship and statement cache are out of scope; heavy concurrency uses a thread pool under the hood.
103
+
104
+ ## Support levels
105
+ - Tested/stable (real Mongo runs): single-collection CRUD, WHERE/ORDER/LIMIT/OFFSET, INNER/LEFT equijoin (up to 3 hops), GROUP BY + aggregates + HAVING, subqueries (WHERE IN/EXISTS, FROM (SELECT ...)), UNION ALL, `ROW_NUMBER()` (MongoDB 5.x+).
106
+ - Not supported / constraints: non-equi JOIN, FULL/RIGHT OUTER, distinct `UNION`, window functions other than `ROW_NUMBER`, correlated subqueries, ORM relationships; async is thread-pool based.
107
+
108
+ ## Running tests
109
+ ```bash
110
+ PORT=27018 ./startdb.sh # if 27017 is taken
111
+ MONGODB_URI=mongodb://127.0.0.1:27018 MONGODB_DB=mongo_dbapi_test .venv/bin/pytest -q
112
+ ```
113
+
114
+ ## Notes
115
+ - Transactions on MongoDB 3.6 are treated as no-op; 4.x+ (replica set) uses real sessions and the bundled 4.4 binary passes all tests.
116
+ - Error messages are fixed strings per `docs/spec.md`. Keep logs at DEBUG only (default INFO is silent).
117
+
118
+ ## License
119
+ MIT License (see `LICENSE`). Provided as-is without warranty; commercial use permitted.
120
+
121
+ ## GitHub Sponsors
122
+ Maintained in personal time. If this helps you run MongoDB from DB-API/SQLAlchemy stacks, consider supporting via GitHub Sponsors to keep fixes and version updates coming.
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: dbapi-mongodb
3
+ Version: 0.1.0
4
+ Summary: DBAPI-style adapter that translates SQL (JOIN/DDL/aggregations) into MongoDB operations
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: pymongo<4.0,>=3.13
9
+ Requires-Dist: sqlglot>=25.7.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.3.0; extra == "dev"
12
+ Requires-Dist: sqlalchemy>=2.0.0; extra == "dev"
13
+ Dynamic: license-file
14
+
15
+ # dbapi-mongodb
16
+
17
+ DBAPI-style adapter that lets you execute a limited subset of SQL against MongoDB by translating SQL to Mongo queries. Built on `pymongo` (3.13.x for MongoDB 3.6 compatibility) and `SQLGlot`.
18
+
19
+ Purpose: let existing DB-API / SQLAlchemy Core / FastAPI code treat MongoDB as “just another dialect.”
20
+
21
+ - PyPI package name: `dbapi-mongodb`
22
+ - Module import name: `mongo_dbapi`
23
+
24
+ ## Features
25
+ - DBAPI-like `Connection`/`Cursor`
26
+ - SQL → Mongo: `SELECT/INSERT/UPDATE/DELETE`, `CREATE/DROP TABLE/INDEX` (ASC/DESC, UNIQUE, composite), `WHERE` (comparisons/`AND`/`OR`/`IN`/`BETWEEN`/`LIKE`→`$regex`/`ILIKE`/regex literal), `ORDER BY`, `LIMIT/OFFSET`, INNER/LEFT JOIN (equijoin, composite keys up to 3 hops), `GROUP BY` + aggregates (COUNT/SUM/AVG/MIN/MAX) + `HAVING`, `UNION ALL`, subqueries (`WHERE IN/EXISTS`, `FROM (SELECT ...)`), `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)` on MongoDB 5.x+
27
+ - `%s` positional and `%(name)s` named parameters; unsupported constructs raise Error IDs (e.g. `[mdb][E2]`)
28
+ - Error IDs for common failures: invalid URI, unsupported SQL, unsafe DML without WHERE, parse errors, connection/auth failures
29
+ - DBAPI fields: `rowcount`, `lastrowid`, `description` (column order: explicit order, or alpha for `SELECT *`; JOIN uses left→right)
30
+ - Transactions: `begin/commit/rollback` wrap Mongo sessions; MongoDB 3.6 and other unsupported envs are treated as no-op success
31
+ - Async dialect (thread-pool backed) for Core CRUD/DDL/Index with FastAPI-friendly usage; minimal ORM CRUD for single-table entities (relationships out of scope)
32
+
33
+ - Use cases
34
+ - Swap in Mongo as “another dialect” for existing SQLAlchemy Core–based infra (Engine/Connection + Table/Column)
35
+ - Point existing Core-based batch/report jobs at Mongo data with minimal changes
36
+ - Minimal ORM CRUD for single-table entities (PK → `_id`)
37
+ - Async dialect for FastAPI/async stacks (thread-pool implementation; native async later)
38
+
39
+ ## Requirements
40
+ - Python 3.10+
41
+ - MongoDB 3.6 (bundled `mongodb-3.6` binary) or later (note: bundled binary is 3.6, so transactions are unsupported)
42
+ - Virtualenv at `.venv` (already present); dependencies are managed via `pyproject.toml`
43
+
44
+ ## Installation
45
+ ```bash
46
+ pip install dbapi-mongodb
47
+ # (optional) with a virtualenv: python -m venv .venv && . .venv/bin/activate && pip install dbapi-mongodb
48
+ ```
49
+
50
+ ## Start local MongoDB (bundled 3.6)
51
+ ```bash
52
+ # Default port 27017; override with PORT
53
+ PORT=27018 ./startdb.sh
54
+ ```
55
+
56
+ ## Start local MongoDB 4.4 (replica set, bundled)
57
+ ```bash
58
+ # Default port 27019; uses bundled libssl1.1. LD_LIBRARY_PATH is set inside the script for mongod.
59
+ PORT=27019 ./start4xdb.sh
60
+ # Run tests against 4.x
61
+ MONGODB_URI=mongodb://127.0.0.1:27019 MONGODB_DB=mongo_dbapi_test .venv/bin/pytest -q
62
+ ```
63
+
64
+ ## Usage example
65
+ ```python
66
+ from mongo_dbapi import connect
67
+
68
+ conn = connect("mongodb://127.0.0.1:27018", "mongo_dbapi_test")
69
+ cur = conn.cursor()
70
+ cur.execute("INSERT INTO users (id, name) VALUES (%s, %s)", (1, "Alice"))
71
+
72
+ cur.execute("SELECT id, name FROM users WHERE id = %s", (1,))
73
+ print(cur.fetchall()) # [(1, 'Alice')]
74
+ print(cur.rowcount) # 1
75
+ ```
76
+
77
+ ## Supported SQL
78
+ - Statements: `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `CREATE/DROP TABLE`, `CREATE/DROP INDEX`
79
+ - WHERE: comparisons (`=`, `<>`, `>`, `<`, `<=`, `>=`), `AND`, `OR`, `IN`, `BETWEEN`, `LIKE` (`%`/`_` → `$regex`), `ILIKE`, regex literal `/.../`
80
+ - JOIN: INNER/LEFT equijoin (composite keys, up to 3 joins)
81
+ - Aggregation: `GROUP BY` with COUNT/SUM/AVG/MIN/MAX and `HAVING`
82
+ - Subqueries: `WHERE IN/EXISTS` and `FROM (SELECT ...)` (non-correlated; executed first)
83
+ - Set ops: `UNION ALL`
84
+ - Window: `ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)` on MongoDB 5.x+ (`[mdb][E2]` on earlier versions)
85
+ - ORDER/LIMIT/OFFSET
86
+ - Unsupported: non-equi joins, FULL/RIGHT OUTER, `UNION` (distinct), window functions other than `ROW_NUMBER`, correlated subqueries, ORM relationships
87
+
88
+ ## SQLAlchemy
89
+ - DBAPI module attributes: `apilevel="2.0"`, `threadsafety=1`, `paramstyle="pyformat"`.
90
+ - Scheme: `mongodb+dbapi://...` dialect provided (sync + async/thread-pool).
91
+ - Scope: Core text()/Table/Column CRUD/DDL/Index、ORM 最小 CRUD(単一テーブル)、JOIN/UNION ALL/HAVING/subquery/ROW_NUMBER を実通信で確認済み。async dialect は Core CRUD/DDL/Index のラップで、ネイティブ async は今後検討。
92
+
93
+ ## Async (FastAPI/Core) - beta
94
+ - **Current implementation wraps the sync driver in a thread pool** (native async driver is planned). Provided via `mongo_dbapi.async_dbapi.connect_async`. API mirrors sync Core: awaitable CRUD/DDL/Index, JOIN/UNION ALL/HAVING/IN/EXISTS/FROM subquery.
95
+ - Transactions: effective on MongoDB 4.x+ only; 3.6 is no-op. Be mindful that MongoDB transactions differ from RDBMS in locking/perf; avoid heavy transactional workloads.
96
+ - Window: `ROW_NUMBER` is available on MongoDB 5.x+; earlier versions return `[mdb][E2] Unsupported SQL construct: WINDOW_FUNCTION`.
97
+ - FastAPI example:
98
+ ```python
99
+ from fastapi import FastAPI, Depends
100
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncConnection
101
+ from sqlalchemy import text
102
+
103
+ engine = create_async_engine("mongodb+dbapi://127.0.0.1:27019/mongo_dbapi_test")
104
+ app = FastAPI()
105
+
106
+ async def get_conn() -> AsyncConnection:
107
+ async with engine.connect() as conn:
108
+ yield conn
109
+
110
+ @app.get("/users/{user_id}")
111
+ async def get_user(user_id: str, conn: AsyncConnection = Depends(get_conn)):
112
+ rows = await conn.execute(text("SELECT id, name FROM users WHERE id = :id"), {"id": user_id})
113
+ row = rows.fetchone()
114
+ return dict(row) if row else {}
115
+ ```
116
+ - Limitations: async ORM/relationship and statement cache are out of scope; heavy concurrency uses a thread pool under the hood.
117
+
118
+ ## Support levels
119
+ - Tested/stable (real Mongo runs): single-collection CRUD, WHERE/ORDER/LIMIT/OFFSET, INNER/LEFT equijoin (up to 3 hops), GROUP BY + aggregates + HAVING, subqueries (WHERE IN/EXISTS, FROM (SELECT ...)), UNION ALL, `ROW_NUMBER()` (MongoDB 5.x+).
120
+ - Not supported / constraints: non-equi JOIN, FULL/RIGHT OUTER, distinct `UNION`, window functions other than `ROW_NUMBER`, correlated subqueries, ORM relationships; async is thread-pool based.
121
+
122
+ ## Running tests
123
+ ```bash
124
+ PORT=27018 ./startdb.sh # if 27017 is taken
125
+ MONGODB_URI=mongodb://127.0.0.1:27018 MONGODB_DB=mongo_dbapi_test .venv/bin/pytest -q
126
+ ```
127
+
128
+ ## Notes
129
+ - Transactions on MongoDB 3.6 are treated as no-op; 4.x+ (replica set) uses real sessions and the bundled 4.4 binary passes all tests.
130
+ - Error messages are fixed strings per `docs/spec.md`. Keep logs at DEBUG only (default INFO is silent).
131
+
132
+ ## License
133
+ MIT License (see `LICENSE`). Provided as-is without warranty; commercial use permitted.
134
+
135
+ ## GitHub Sponsors
136
+ Maintained in personal time. If this helps you run MongoDB from DB-API/SQLAlchemy stacks, consider supporting via GitHub Sponsors to keep fixes and version updates coming.
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ dbapi_mongodb.egg-info/PKG-INFO
5
+ dbapi_mongodb.egg-info/SOURCES.txt
6
+ dbapi_mongodb.egg-info/dependency_links.txt
7
+ dbapi_mongodb.egg-info/requires.txt
8
+ dbapi_mongodb.egg-info/top_level.txt
9
+ mongo_dbapi/__init__.py
10
+ mongo_dbapi/async_dbapi.py
11
+ mongo_dbapi/dbapi.py
12
+ mongo_dbapi/errors.py
13
+ mongo_dbapi/sqlalchemy_dialect.py
14
+ mongo_dbapi/translation.py
15
+ tests/test_async_dbapi.py
16
+ tests/test_dbapi.py
17
+ tests/test_transactions.py
18
+ tests/test_translation.py
@@ -0,0 +1,6 @@
1
+ pymongo<4.0,>=3.13
2
+ sqlglot>=25.7.0
3
+
4
+ [dev]
5
+ pytest>=8.3.0
6
+ sqlalchemy>=2.0.0
@@ -0,0 +1 @@
1
+ mongo_dbapi
@@ -0,0 +1,13 @@
1
+ from .dbapi import Connection, Cursor, connect
2
+ from .errors import MongoDbApiError
3
+
4
+ # DB-API style exception hook for SQLAlchemy / DB-API 仕様の Error エイリアス
5
+ Error = MongoDbApiError
6
+
7
+ apilevel = "2.0"
8
+ threadsafety = 1
9
+ paramstyle = "pyformat"
10
+
11
+ from . import sqlalchemy_dialect # noqa: E402,F401 register dialect
12
+
13
+ __all__ = ["connect", "Connection", "Cursor", "MongoDbApiError", "Error", "apilevel", "threadsafety", "paramstyle"]
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import Any, Mapping, Sequence
5
+
6
+ from .dbapi import Connection, Cursor, connect
7
+
8
+
9
+ class AsyncCursor:
10
+ """Async wrapper for Cursor / Cursor の非同期ラッパー"""
11
+
12
+ def __init__(self, sync_cursor: Cursor):
13
+ self._sync_cursor = sync_cursor
14
+
15
+ async def execute(self, sql: str, params: Sequence | Mapping | None = None) -> "AsyncCursor":
16
+ await asyncio.to_thread(self._sync_cursor.execute, sql, params)
17
+ return self
18
+
19
+ async def executemany(self, sql: str, seq_of_params: Sequence[Sequence | Mapping]) -> "AsyncCursor":
20
+ await asyncio.to_thread(self._sync_cursor.executemany, sql, seq_of_params)
21
+ return self
22
+
23
+ async def fetchone(self) -> tuple | None:
24
+ return await asyncio.to_thread(self._sync_cursor.fetchone)
25
+
26
+ async def fetchall(self) -> list[tuple]:
27
+ return await asyncio.to_thread(self._sync_cursor.fetchall)
28
+
29
+ @property
30
+ def rowcount(self) -> int:
31
+ return self._sync_cursor.rowcount
32
+
33
+ @property
34
+ def lastrowid(self) -> Any:
35
+ return self._sync_cursor.lastrowid
36
+
37
+ @property
38
+ def description(self):
39
+ return self._sync_cursor.description
40
+
41
+ async def close(self) -> None:
42
+ await asyncio.to_thread(self._sync_cursor.close)
43
+
44
+
45
+ class AsyncConnection:
46
+ """Async wrapper for Connection / Connection の非同期ラッパー"""
47
+
48
+ def __init__(self, sync_conn: Connection):
49
+ self._sync_conn = sync_conn
50
+
51
+ async def cursor(self) -> AsyncCursor:
52
+ cur = await asyncio.to_thread(self._sync_conn.cursor)
53
+ return AsyncCursor(cur)
54
+
55
+ async def begin(self) -> None:
56
+ await asyncio.to_thread(self._sync_conn.begin)
57
+
58
+ async def commit(self) -> None:
59
+ await asyncio.to_thread(self._sync_conn.commit)
60
+
61
+ async def rollback(self) -> None:
62
+ await asyncio.to_thread(self._sync_conn.rollback)
63
+
64
+ async def list_tables(self) -> list[str]:
65
+ return await asyncio.to_thread(self._sync_conn.list_tables)
66
+
67
+ async def close(self) -> None:
68
+ await asyncio.to_thread(self._sync_conn.close)
69
+
70
+
71
+ async def connect_async(uri: str, db_name: str) -> AsyncConnection:
72
+ """Async factory for Connection / Connection の非同期ファクトリ"""
73
+ sync_conn = await asyncio.to_thread(connect, uri, db_name)
74
+ return AsyncConnection(sync_conn)