Skip to content

Commit aff472b

Browse files
authored
0.3.0 - Added DML statement support (#8)
* Refactor the codebase to support DML statements * Added basic insert support * Restrict the superset mode * Added delete support * Added basic update support * Update README to reflect new changes * Refine README --------- Co-authored-by: Peng Ren
1 parent 74df878 commit aff472b

27 files changed

+3103
-497
lines changed

README.md

Lines changed: 153 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ PyMongoSQL is a Python [DB API 2.0 (PEP 249)](https://www.python.org/dev/peps/pe
1616

1717
PyMongoSQL implements the DB API 2.0 interfaces to provide SQL-like access to MongoDB, built on PartiQL syntax for querying semi-structured data. The project aims to:
1818

19-
- Bridge the gap between SQL and NoSQL by providing SQL capabilities for MongoDB's nested document structures
20-
- Support standard SQL DQL (Data Query Language) operations including SELECT statements with WHERE, ORDER BY, and LIMIT clauses on nested and hierarchical data
21-
- Provide seamless integration with existing Python applications that expect DB API 2.0 compliance
22-
- Enable easy migration from traditional SQL databases to MongoDB without rewriting queries for document traversal
19+
- **Bridge SQL and NoSQL**: Provide SQL capabilities for MongoDB's nested document structures
20+
- **Standard SQL Operations**: Support DQL (SELECT) and DML (INSERT, UPDATE, DELETE) operations with WHERE, ORDER BY, and LIMIT clauses
21+
- **Seamless Integration**: Full compatibility with Python applications expecting DB API 2.0 compliance
22+
- **Easy Migration**: Enable migration from traditional SQL databases to MongoDB without rewriting application code
2323

2424
## Features
2525

@@ -28,6 +28,7 @@ PyMongoSQL implements the DB API 2.0 interfaces to provide SQL-like access to Mo
2828
- **Nested Structure Support**: Query and filter deeply nested fields and arrays within MongoDB documents using standard SQL syntax
2929
- **SQLAlchemy Integration**: Complete ORM and Core support with dedicated MongoDB dialect
3030
- **SQL Query Support**: SELECT statements with WHERE conditions, field selection, and aliases
31+
- **DML Support**: Full support for INSERT, UPDATE, and DELETE operations using PartiQL syntax
3132
- **Connection String Support**: MongoDB URI format for easy configuration
3233

3334
## Requirements
@@ -184,16 +185,18 @@ Parameters are substituted into the MongoDB filter during execution, providing p
184185
## Supported SQL Features
185186

186187
### SELECT Statements
187-
- Field selection: `SELECT name, age FROM users`
188-
- Wildcards: `SELECT * FROM products`
189-
- **Field aliases**: `SELECT name as user_name, age as user_age FROM users`
188+
189+
- **Field selection**: `SELECT name, age FROM users`
190+
- **Wildcards**: `SELECT * FROM products`
191+
- **Field aliases**: `SELECT name AS user_name, age AS user_age FROM users`
190192
- **Nested fields**: `SELECT profile.name, profile.age FROM users`
191193
- **Array access**: `SELECT items[0], items[1].name FROM orders`
192194

193195
### WHERE Clauses
194-
- Equality: `WHERE name = 'John'`
195-
- Comparisons: `WHERE age > 25`, `WHERE price <= 100.0`
196-
- Logical operators: `WHERE age > 18 AND status = 'active'`
196+
197+
- **Equality**: `WHERE name = 'John'`
198+
- **Comparisons**: `WHERE age > 25`, `WHERE price <= 100.0`
199+
- **Logical operators**: `WHERE age > 18 AND status = 'active'`, `WHERE age < 30 OR role = 'admin'`
197200
- **Nested field filtering**: `WHERE profile.status = 'active'`
198201
- **Array filtering**: `WHERE items[0].price > 100`
199202

@@ -206,9 +209,140 @@ Parameters are substituted into the MongoDB filter during execution, providing p
206209
> **Note**: Avoid SQL reserved words (`user`, `data`, `value`, `count`, etc.) as unquoted field names. Use alternatives or bracket notation for arrays.
207210
208211
### Sorting and Limiting
209-
- ORDER BY: `ORDER BY name ASC, age DESC`
210-
- LIMIT: `LIMIT 10`
211-
- Combined: `ORDER BY created_at DESC LIMIT 5`
212+
213+
- **ORDER BY**: `ORDER BY name ASC, age DESC`
214+
- **LIMIT**: `LIMIT 10`
215+
- **Combined**: `ORDER BY created_at DESC LIMIT 5`
216+
217+
### INSERT Statements
218+
219+
PyMongoSQL supports inserting documents into MongoDB collections using PartiQL-style object and bag literals.
220+
221+
**Single Document**
222+
223+
```python
224+
cursor.execute(
225+
"INSERT INTO Music {'title': 'Song A', 'artist': 'Alice', 'year': 2021}"
226+
)
227+
```
228+
229+
**Multiple Documents (Bag Syntax)**
230+
231+
```python
232+
cursor.execute(
233+
"INSERT INTO Music << {'title': 'Song B', 'artist': 'Bob'}, {'title': 'Song C', 'artist': 'Charlie'} >>"
234+
)
235+
```
236+
237+
**Parameterized INSERT**
238+
239+
```python
240+
# Positional parameters using ? placeholders
241+
cursor.execute(
242+
"INSERT INTO Music {'title': ?, 'artist': ?, 'year': ?}",
243+
["Song D", "Diana", 2020]
244+
)
245+
```
246+
247+
> **Note**: For parameterized INSERT, use positional parameters (`?`). Named placeholders (`:name`) are supported for SELECT, UPDATE, and DELETE queries.
248+
249+
### UPDATE Statements
250+
251+
PyMongoSQL supports updating documents in MongoDB collections using standard SQL UPDATE syntax.
252+
253+
**Update All Documents**
254+
255+
```python
256+
cursor.execute("UPDATE Music SET available = false")
257+
```
258+
259+
**Update with WHERE Clause**
260+
261+
```python
262+
cursor.execute("UPDATE Music SET price = 14.99 WHERE year < 2020")
263+
```
264+
265+
**Update Multiple Fields**
266+
267+
```python
268+
cursor.execute(
269+
"UPDATE Music SET price = 19.99, available = true WHERE artist = 'Alice'"
270+
)
271+
```
272+
273+
**Update with Logical Operators**
274+
275+
```python
276+
cursor.execute(
277+
"UPDATE Music SET price = 9.99 WHERE year = 2020 AND stock > 5"
278+
)
279+
```
280+
281+
**Parameterized UPDATE**
282+
283+
```python
284+
# Positional parameters using ? placeholders
285+
cursor.execute(
286+
"UPDATE Music SET price = ?, stock = ? WHERE artist = ?",
287+
[24.99, 50, "Bob"]
288+
)
289+
```
290+
291+
**Update Nested Fields**
292+
293+
```python
294+
cursor.execute(
295+
"UPDATE Music SET details.publisher = 'XYZ Records' WHERE title = 'Song A'"
296+
)
297+
```
298+
299+
**Check Updated Row Count**
300+
301+
```python
302+
cursor.execute("UPDATE Music SET available = false WHERE year = 2020")
303+
print(f"Updated {cursor.rowcount} documents")
304+
```
305+
306+
### DELETE Statements
307+
308+
PyMongoSQL supports deleting documents from MongoDB collections using standard SQL DELETE syntax.
309+
310+
**Delete All Documents**
311+
312+
```python
313+
cursor.execute("DELETE FROM Music")
314+
```
315+
316+
**Delete with WHERE Clause**
317+
318+
```python
319+
cursor.execute("DELETE FROM Music WHERE year < 2020")
320+
```
321+
322+
**Delete with Logical Operators**
323+
324+
```python
325+
cursor.execute(
326+
"DELETE FROM Music WHERE year = 2019 AND available = false"
327+
)
328+
```
329+
330+
**Parameterized DELETE**
331+
332+
```python
333+
# Positional parameters using ? placeholders
334+
cursor.execute(
335+
"DELETE FROM Music WHERE artist = ? AND year < ?",
336+
["Charlie", 2021]
337+
)
338+
```
339+
340+
**Check Deleted Row Count**
341+
342+
```python
343+
cursor.execute("DELETE FROM Music WHERE available = false")
344+
print(f"Deleted {cursor.rowcount} documents")
345+
```
212346

213347
## Apache Superset Integration
214348

@@ -231,16 +365,18 @@ PyMongoSQL can be used as a database driver in Apache Superset for querying and
231365

232366
This allows seamless integration between MongoDB data and Superset's BI capabilities without requiring data migration to traditional SQL databases.
233367

234-
<h2 style="color: red;">Limitations & Roadmap</h2>
368+
## Limitations & Roadmap
235369

236-
**Note**: Currently PyMongoSQL focuses on Data Query Language (DQL) operations. The following SQL features are **not yet supported** but are planned for future releases:
370+
**Note**: PyMongoSQL currently supports DQL (Data Query Language) and DML (Data Manipulation Language) operations. The following SQL features are **not yet supported** but are planned for future releases:
237371

238-
- **DML Operations** (Data Manipulation Language)
239-
- `INSERT`, `UPDATE`, `DELETE`
240372
- **DDL Operations** (Data Definition Language)
241373
- `CREATE TABLE/COLLECTION`, `DROP TABLE/COLLECTION`
242374
- `CREATE INDEX`, `DROP INDEX`
243375
- `LIST TABLES/COLLECTIONS`
376+
- `ALTER TABLE/COLLECTION`
377+
- **Advanced DML Operations**
378+
- `MERGE`, `UPSERT`
379+
- Transactions and multi-document operations
244380

245381
These features are on our development roadmap and contributions are welcome!
246382

pymongosql/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
if TYPE_CHECKING:
77
from .connection import Connection
88

9-
__version__: str = "0.2.5"
9+
__version__: str = "0.3.0"
1010

1111
# Globals https://www.python.org/dev/peps/pep-0249/#globals
1212
apilevel: str = "2.0"

pymongosql/cursor.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .error import DatabaseError, OperationalError, ProgrammingError, SqlSyntaxError
77
from .executor import ExecutionContext, ExecutionPlanFactory
88
from .result_set import DictResultSet, ResultSet
9-
from .sql.builder import ExecutionPlan
9+
from .sql.query_builder import QueryExecutionPlan
1010

1111
if TYPE_CHECKING:
1212
from .connection import Connection
@@ -29,7 +29,7 @@ def __init__(self, connection: "Connection", mode: str = "standard", **kwargs) -
2929
self._kwargs = kwargs
3030
self._result_set: Optional[ResultSet] = None
3131
self._result_set_class = ResultSet
32-
self._current_execution_plan: Optional[ExecutionPlan] = None
32+
self._current_execution_plan: Optional[Any] = None
3333
self._is_closed = False
3434

3535
@property
@@ -103,12 +103,32 @@ def execute(self: _T, operation: str, parameters: Optional[Any] = None) -> _T:
103103
self._current_execution_plan = strategy.execution_plan
104104

105105
# Create result set from command result
106-
self._result_set = self._result_set_class(
107-
command_result=result,
108-
execution_plan=self._current_execution_plan,
109-
database=self.connection.database,
110-
**self._kwargs,
111-
)
106+
# For SELECT/QUERY operations, use the execution plan directly
107+
if isinstance(self._current_execution_plan, QueryExecutionPlan):
108+
execution_plan_for_rs = self._current_execution_plan
109+
self._result_set = self._result_set_class(
110+
command_result=result,
111+
execution_plan=execution_plan_for_rs,
112+
database=self.connection.database,
113+
**self._kwargs,
114+
)
115+
else:
116+
# For INSERT and other non-query operations, create a minimal synthetic result
117+
# since INSERT commands don't return a cursor structure
118+
stub_plan = QueryExecutionPlan(collection=self._current_execution_plan.collection)
119+
self._result_set = self._result_set_class(
120+
command_result={
121+
"cursor": {
122+
"id": 0,
123+
"firstBatch": [],
124+
}
125+
},
126+
execution_plan=stub_plan,
127+
database=self.connection.database,
128+
**self._kwargs,
129+
)
130+
# Store the actual insert result for reference
131+
self._result_set._insert_result = result
112132

113133
return self
114134

0 commit comments

Comments
 (0)