-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
MongoDB/PyMongo: Add querying capabilities using JessiQL
With corresponding improvements, the amalgamated PyMongo driver can now run 95% of the MongoDB "getting started" tutorial successfully.
- Loading branch information
Showing
13 changed files
with
548 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# Amalgamated PyMongo driver using CrateDB as backend | ||
|
||
|
||
## Setup | ||
|
||
```shell | ||
pip install 'cratedb-toolkit[pymongo]' | ||
``` | ||
|
||
```shell | ||
docker run --rm -it --name=cratedb \ | ||
--publish=4200:4200 --publish=5432:5432 \ | ||
--env=CRATE_HEAP_SIZE=4g crate/crate:nightly \ | ||
-Cdiscovery.type=single-node | ||
``` | ||
|
||
|
||
## Synopsis | ||
```python | ||
import pymongo | ||
from cratedb_toolkit.adapter.pymongo import PyMongoCrateDbAdapter | ||
|
||
with PyMongoCrateDbAdapter(dburi="crate://crate@localhost:4200"): | ||
client = pymongo.MongoClient("localhost", 27017) | ||
collection = client.foo.bar | ||
|
||
inserted_id = collection.insert_one({"author": "Mike", "text": "My first blog post!"}).inserted_id | ||
print(inserted_id) | ||
|
||
document = collection.find({"author": "Mike"}) | ||
print(document) | ||
``` | ||
|
||
|
||
## Examples | ||
|
||
To inspect the capabilities of the driver adapter, there is a basic program at | ||
[pymongo_adapter.py], and test cases at [test_pymongo.py]. | ||
|
||
|
||
[pymongo_adapter.py]: https://github.com/crate-workbench/cratedb-toolkit/blob/main/examples/pymongo_adapter.py | ||
[test_pymongo.py]: https://github.com/crate-workbench/cratedb-toolkit/blob/main/tests/adapter/test_pymongo.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import typing as t | ||
from collections import abc | ||
|
||
import sqlalchemy as sa | ||
from jessiql import Query, QueryObject, QueryObjectDict | ||
from jessiql.exc import InvalidColumnError | ||
from jessiql.typing import SARowDict | ||
|
||
|
||
def table_to_model(table: sa.Table) -> t.Type[sa.orm.Mapper]: | ||
""" | ||
Create SQLAlchemy model class from Table object. | ||
- https://docs.sqlalchemy.org/en/14/orm/mapping_styles.html#imperative-mapping | ||
- https://sparrigan.github.io/sql/sqla/2016/01/03/dynamic-tables.html | ||
""" | ||
mapper_registry = sa.orm.registry(metadata=table.metadata) | ||
Surrogate = type("Surrogate", (), {}) | ||
mapper_registry.map_imperatively(Surrogate, table) | ||
return Surrogate | ||
|
||
|
||
def reflect_model(engine: t.Any, metadata: sa.MetaData, table_name: str) -> t.Type[sa.orm.Mapper]: | ||
""" | ||
Create SQLAlchemy model class by reflecting a database table. | ||
""" | ||
table = sa.Table(table_name, metadata, autoload_with=engine) | ||
return table_to_model(table) | ||
|
||
|
||
def mongodb_query( | ||
model: t.Type[sa.orm.Mapper], | ||
select: t.Union[t.List, None] = None, | ||
filter: t.Union[t.Dict[str, t.Any], None] = None, # noqa: A002 | ||
sort: t.Union[t.List[str], None] = None, | ||
) -> Query: | ||
""" | ||
Create a JessiQL Query object from an SQLAlchemy model class and typical MongoDB query parameters. | ||
""" | ||
|
||
select = select or list(model._sa_class_manager.keys()) # type: ignore[attr-defined] | ||
|
||
filter = filter or {} # noqa: A001 | ||
sort = sort or [] | ||
|
||
# TODO: select, filter, sort, skip, limit | ||
if "_id" in filter: | ||
filter["_id"] = str(filter["_id"]) | ||
query_dict = QueryObjectDict({"select": select, "filter": filter, "sort": sort}) | ||
query_object = QueryObject.from_query_object(query_dict) | ||
|
||
try: | ||
return Query(query=query_object, Model=model) | ||
except InvalidColumnError as ex: | ||
msg = str(ex) | ||
if "Invalid column" in msg and "specified in filter" in msg: | ||
return EmptyQuery() | ||
else: | ||
raise | ||
|
||
|
||
class EmptyQuery(Query): | ||
""" | ||
A surrogate QueryExecutor for propagating back empty results. | ||
""" | ||
|
||
def __init__(self, *args, **kwargs): | ||
self.related_executors = {} | ||
|
||
def _load_results(self, *args, **kwargs) -> abc.Iterator[SARowDict]: | ||
raise StopIteration() | ||
|
||
def _apply_operations_to_results(self, *args, **kwargs) -> t.List[SARowDict]: | ||
return [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.