Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: first poc implementation #2

Merged
merged 37 commits into from
Apr 7, 2024
Merged

feat: first poc implementation #2

merged 37 commits into from
Apr 7, 2024

Conversation

gersmann
Copy link
Contributor

@gersmann gersmann commented Jan 21, 2024

Still pretty basic implementation of Django database datapter for MongoDB.

Reference material on the Django ORM architecture:
https://simpleisbetterthancomplex.com/media/2016/11/db.pdf

This is working:

  • Select/Insert/Update/Delete type queries.
  • First set of 'lookup' operations (easily extensible)
  • MongoDB search support (for lookups and wildcard queries)
  • 'Aliasing' columns / mapping fields to nested mongo paths
  • Projections of queried fields and selective updates
  • Count Aggregation and Distinct Query
  • 'Single Table Inheritance'

Notable omissions

  • Almost all aggregation queries
  • Joins (did some prototyping, pretty doable, but performance could be bad depending on database setup and schema)
  • Nested sub-queries (doable, needs some research on how it would perform on large collections).
  • Migrations are skipped, we are using a schema-less database, but could implement operations such as field renames, at the moment due to the size of our collections not really useful though
  • Plenty of other topics which need to be implemented / checked to achive full coverage of the ORM functionality

@@ -0,0 +1,9 @@
setup_local_db:
@echo "Creating local database..."
@atlas deployments setup local --type local --port 3307 --force
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

atlas cli local deployments support mongo search

return self.settings_dict

def get_new_connection(self, conn_params):
name = conn_params.get("NAME") or "test"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Django instantiates the connection in db-less mode (without db name) for operations such as create / drop database.



class DatabaseClient(BaseDatabaseClient):
executable_name = "django_mongodb"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this class implements the django sqlshell command, which I personally don't use, so this is optional, but should be fairly easy

self.extr = None

def build_mongo_filter(self, filter_expr):
referenced_tables = set()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bare predicate filter, which is used as $match in query pipelines or as filter in update/delete.

{"$replaceRoot": {"newRoot": "$_id"}},
]

def as_operation(self, with_limits=True, with_col_aliases=False):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MongoDB implementation of as_sql of the base sql compiler

@gersmann gersmann changed the title feat: first poc implementaiton feat: first poc implementation Jan 21, 2024

class DatabaseWrapper(BaseDatabaseWrapper):
vendor = "django_mongodb"
data_types = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the base configuration Django, but adapted where needed (notably objectid). Those are currently logical names only, used in prep operations, as we are not generating / updating schematas..

with self.wrap_database_errors:
self.mongo_client.close()

def _set_autocommit(self, autocommit):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not currently implement transactions, we are also not using any in the backend as of now. But should be possible actually without too much effort to do so, using client_session, and doesn't look that complicated at a first glance, but would need some investigation. Biggest benefit at the moment would be in testing, but for the few models we are using a db cleaning auto-fixture would do.


@cached_property
def mongo_meta(self):
if hasattr(self.query.model, "MongoMeta"):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some custom meta class / props, at the moment only for defining what search indexes are set up on the collections, so that when doing queries via search, we can push the right lookups to the search index.


def execute_sql(
self, result_type=MULTI, chunked_fetch=False, chunk_size=GET_ITERATOR_CHUNK_SIZE
):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the entry point into the compiler for QuerySet methods such as fetch_all, udpate, delete.

The implementation is analoguous, but adapted where needed, from the implementation in the base SQLCompiler.

cursor.close()
raise

if result_type == CURSOR:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is used by raw queries, which rely on cursor methods such as rowcount for info about query results


class MongoNothingNode(Node):
def get_mongo_query(self, compiler, connection, is_search=...) -> dict:
return {"$expr": {"$eq": [True, False]}}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed for Queryset#none

self.alias = alias

def get_mongo(self):
return {"$project": {(self.alias or self.col.target.attname): f"${self.col.target.column}"}}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This maps the MongoDB field path to the attribute names we are defining in the MongoDB model.

self.query = query
self.order = query.order_by

def get_mongo_order(self, attname_as_key=False):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in aggregation queries, we already have the document with Django attribute names as keys and are setting attname_as_key=True

from django.db.backends.base.schema import BaseDatabaseSchemaEditor


class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not at the moment implement and schema migrations. We could add support for field renames and type conversions here, if we'd have use for that.

extended = models.CharField(max_length=100)

class Meta:
db_table = "testapp_foomodel"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This child model is persistent with it's and it's parents fields in the same collection.

@gersmann gersmann marked this pull request as ready for review January 23, 2024 12:58
@gersmann gersmann merged commit 55c3faf into main-empty Apr 7, 2024
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant