From 8590da10630f7c03f6c7b28a1fb1e41081270c0e Mon Sep 17 00:00:00 2001 From: Crispen Gari Date: Fri, 16 Feb 2024 12:43:02 +0200 Subject: [PATCH] tests and bug fixing --- README.md | 5 + dataloom/keys.py | 2 +- dataloom/loom/subqueries.py | 29 ++- .../tests/mysql/test_eager_loading_mysql.py | 239 ++++++++++++++++++ .../tests/postgres/test_eager_loading_pg.py | 239 ++++++++++++++++++ .../sqlite3/test_eager_loading_sqlite.py | 233 +++++++++++++++++ hi.db | Bin 40960 -> 49152 bytes playground.py | 26 ++ 8 files changed, 764 insertions(+), 9 deletions(-) create mode 100644 dataloom/tests/mysql/test_eager_loading_mysql.py create mode 100644 dataloom/tests/postgres/test_eager_loading_pg.py create mode 100644 dataloom/tests/sqlite3/test_eager_loading_sqlite.py diff --git a/README.md b/README.md index a243c8c..5607be0 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ - [Filters](#filters) - [Utilities](#utilities) - [`inspect`](#inspect) +- [Associations](#associations) - [What is coming next?](#what-is-coming-next) - [Contributing](#contributing) - [License](#license) @@ -1167,6 +1168,10 @@ Output: The `inspect` function take the following arguments. +### Associations + +Eager + ### What is coming next? 1. Associations diff --git a/dataloom/keys.py b/dataloom/keys.py index a519747..6129f81 100644 --- a/dataloom/keys.py +++ b/dataloom/keys.py @@ -1,4 +1,4 @@ -push = False +push = True class PgConfig: diff --git a/dataloom/loom/subqueries.py b/dataloom/loom/subqueries.py index 963a9b6..5c6d3c9 100644 --- a/dataloom/loom/subqueries.py +++ b/dataloom/loom/subqueries.py @@ -37,15 +37,28 @@ def get_find_by_pk_relations(self, parent: Model, pk, includes: list[Include] = _, parent_pk_name, parent_fks, _ = get_table_fields( parent, dialect=self.dialect ) - _pk = relations[key][re.sub(r'`|"', "", parent_pk_name)] - - relations[key] = { - **relations[key], - **self.get_find_by_pk_relations( - include.model, _pk, includes=include.include - ), - } + if isinstance(relations[key], dict): + _pk = relations[key][re.sub(r'`|"', "", parent_pk_name)] + relations[key] = { + **relations[key], + **self.get_find_by_pk_relations( + include.model, _pk, includes=include.include + ), + } + else: + _pk = ( + relations[key][0][re.sub(r'`|"', "", parent_pk_name)] + if len(relations[key]) != 0 + else None + ) + if _pk is not None: + relations[key] = { + **relations[key], + **self.get_find_by_pk_relations( + include.model, _pk, includes=include.include + ), + } return relations def get_one( diff --git a/dataloom/tests/mysql/test_eager_loading_mysql.py b/dataloom/tests/mysql/test_eager_loading_mysql.py new file mode 100644 index 0000000..5505b5b --- /dev/null +++ b/dataloom/tests/mysql/test_eager_loading_mysql.py @@ -0,0 +1,239 @@ +class TestEagerLoadingOnMySQL: + def test_find_by_pk(self): + from dataloom import ( + Dataloom, + Model, + Column, + PrimaryKeyColumn, + CreatedAtColumn, + TableColumn, + ForeignKeyColumn, + ColumnValue, + Include, + Order, + ) + from dataloom.keys import MySQLConfig + + mysql_loom = Dataloom( + dialect="mysql", + database=MySQLConfig.database, + password=MySQLConfig.password, + user=MySQLConfig.user, + ) + + class User(Model): + __tablename__: TableColumn = TableColumn(name="users") + id = PrimaryKeyColumn(type="int", auto_increment=True) + name = Column(type="text", nullable=False, default="Bob") + username = Column(type="varchar", unique=True, length=255) + tokenVersion = Column(type="int", default=0) + + class Profile(Model): + __tablename__: TableColumn = TableColumn(name="profiles") + id = PrimaryKeyColumn(type="int", auto_increment=True) + avatar = Column(type="text", nullable=False) + userId = ForeignKeyColumn( + User, + maps_to="1-1", + type="int", + required=True, + onDelete="CASCADE", + onUpdate="CASCADE", + ) + + class Post(Model): + __tablename__: TableColumn = TableColumn(name="posts") + id = PrimaryKeyColumn( + type="int", auto_increment=True, nullable=False, unique=True + ) + completed = Column(type="boolean", default=False) + title = Column(type="varchar", length=255, nullable=False) + # timestamps + createdAt = CreatedAtColumn() + # relations + userId = ForeignKeyColumn( + User, + maps_to="1-N", + type="int", + required=True, + onDelete="CASCADE", + onUpdate="CASCADE", + ) + + class Category(Model): + __tablename__: TableColumn = TableColumn(name="categories") + id = PrimaryKeyColumn( + type="int", auto_increment=True, nullable=False, unique=True + ) + type = Column(type="varchar", length=255, nullable=False) + + postId = ForeignKeyColumn( + Post, + maps_to="N-1", + type="int", + required=True, + onDelete="CASCADE", + onUpdate="CASCADE", + ) + + conn, tables = mysql_loom.connect_and_sync( + [User, Profile, Post, Category], drop=True, force=True + ) + + userId = mysql_loom.insert_one( + instance=User, + values=ColumnValue(name="username", value="@miller"), + ) + + userId2 = mysql_loom.insert_one( + instance=User, + values=ColumnValue(name="username", value="bob"), + ) + + profileId = mysql_loom.insert_one( + instance=Profile, + values=[ + ColumnValue(name="userId", value=userId), + ColumnValue(name="avatar", value="hello.jpg"), + ], + ) + for title in ["Hey", "Hello", "What are you doing", "Coding"]: + mysql_loom.insert_one( + instance=Post, + values=[ + ColumnValue(name="userId", value=userId), + ColumnValue(name="title", value=title), + ], + ) + + for cat in ["general", "education", "tech", "sport"]: + mysql_loom.insert_one( + instance=Category, + values=[ + ColumnValue(name="postId", value=1), + ColumnValue(name="type", value=cat), + ], + ) + + profile = mysql_loom.find_by_pk( + instance=Profile, + pk=profileId, + include=[ + Include( + model=User, select=["id", "username", "tokenVersion"], has="one" + ) + ], + ) + assert profile == { + "avatar": "hello.jpg", + "id": 1, + "userId": 1, + "user": {"id": 1, "username": "@miller", "tokenVersion": 0}, + } + + user = mysql_loom.find_by_pk( + instance=User, + pk=userId, + include=[Include(model=Profile, select=["id", "avatar"], has="one")], + ) + assert user == { + "id": 1, + "name": "Bob", + "tokenVersion": 0, + "username": "@miller", + "profile": {"id": 1, "avatar": "hello.jpg"}, + } + + user = mysql_loom.find_by_pk( + instance=User, + pk=userId, + include=[ + Include( + model=Post, + select=["id", "title"], + has="many", + offset=0, + limit=2, + order=[ + Order(column="createdAt", order="DESC"), + Order(column="id", order="DESC"), + ], + ), + Include(model=Profile, select=["id", "avatar"], has="one"), + ], + ) + assert user == { + "id": 1, + "name": "Bob", + "tokenVersion": 0, + "username": "@miller", + "posts": [ + {"id": 4, "title": "Coding"}, + {"id": 3, "title": "What are you doing"}, + ], + "profile": {"id": 1, "avatar": "hello.jpg"}, + } + + post = mysql_loom.find_by_pk( + instance=Post, + pk=1, + select=["title", "id"], + include=[ + Include( + model=User, + select=["id", "username"], + has="one", + include=[ + Include(model=Profile, select=["avatar", "id"], has="one") + ], + ), + Include( + model=Category, + select=["id", "type"], + has="many", + order=[Order(column="id", order="DESC")], + ), + ], + ) + + assert post == { + "title": "Hey", + "id": 1, + "user": { + "id": 1, + "username": "@miller", + "profile": {"avatar": "hello.jpg", "id": 1}, + }, + "categories": [ + {"id": 4, "type": "sport"}, + {"id": 3, "type": "tech"}, + {"id": 2, "type": "education"}, + {"id": 1, "type": "general"}, + ], + } + + user = mysql_loom.find_by_pk( + instance=User, + pk=userId2, + select=["username", "id"], + include=[ + Include( + model=Post, + select=["id", "title"], + has="many", + include=[ + Include( + model=Category, + select=["type", "id"], + has="many", + order=[Order(column="id", order="DESC")], + limit=2, + offset=0, + ) + ], + ), + ], + ) + assert user == {"username": "bob", "id": 2, "posts": []} + + conn.close() diff --git a/dataloom/tests/postgres/test_eager_loading_pg.py b/dataloom/tests/postgres/test_eager_loading_pg.py new file mode 100644 index 0000000..4ed4609 --- /dev/null +++ b/dataloom/tests/postgres/test_eager_loading_pg.py @@ -0,0 +1,239 @@ +class TestEagerLoadingOnPG: + def test_find_by_pk(self): + from dataloom import ( + Dataloom, + Model, + Column, + PrimaryKeyColumn, + CreatedAtColumn, + TableColumn, + ForeignKeyColumn, + ColumnValue, + Include, + Order, + ) + from dataloom.keys import PgConfig + + pg_loom = Dataloom( + dialect="postgres", + database=PgConfig.database, + password=PgConfig.password, + user=PgConfig.user, + ) + + class User(Model): + __tablename__: TableColumn = TableColumn(name="users") + id = PrimaryKeyColumn(type="int", auto_increment=True) + name = Column(type="text", nullable=False, default="Bob") + username = Column(type="varchar", unique=True, length=255) + tokenVersion = Column(type="int", default=0) + + class Profile(Model): + __tablename__: TableColumn = TableColumn(name="profiles") + id = PrimaryKeyColumn(type="int", auto_increment=True) + avatar = Column(type="text", nullable=False) + userId = ForeignKeyColumn( + User, + maps_to="1-1", + type="int", + required=True, + onDelete="CASCADE", + onUpdate="CASCADE", + ) + + class Post(Model): + __tablename__: TableColumn = TableColumn(name="posts") + id = PrimaryKeyColumn( + type="int", auto_increment=True, nullable=False, unique=True + ) + completed = Column(type="boolean", default=False) + title = Column(type="varchar", length=255, nullable=False) + # timestamps + createdAt = CreatedAtColumn() + # relations + userId = ForeignKeyColumn( + User, + maps_to="1-N", + type="int", + required=True, + onDelete="CASCADE", + onUpdate="CASCADE", + ) + + class Category(Model): + __tablename__: TableColumn = TableColumn(name="categories") + id = PrimaryKeyColumn( + type="int", auto_increment=True, nullable=False, unique=True + ) + type = Column(type="varchar", length=255, nullable=False) + + postId = ForeignKeyColumn( + Post, + maps_to="N-1", + type="int", + required=True, + onDelete="CASCADE", + onUpdate="CASCADE", + ) + + conn, tables = pg_loom.connect_and_sync( + [User, Profile, Post, Category], drop=True, force=True + ) + + userId = pg_loom.insert_one( + instance=User, + values=ColumnValue(name="username", value="@miller"), + ) + + userId2 = pg_loom.insert_one( + instance=User, + values=ColumnValue(name="username", value="bob"), + ) + + profileId = pg_loom.insert_one( + instance=Profile, + values=[ + ColumnValue(name="userId", value=userId), + ColumnValue(name="avatar", value="hello.jpg"), + ], + ) + for title in ["Hey", "Hello", "What are you doing", "Coding"]: + pg_loom.insert_one( + instance=Post, + values=[ + ColumnValue(name="userId", value=userId), + ColumnValue(name="title", value=title), + ], + ) + + for cat in ["general", "education", "tech", "sport"]: + pg_loom.insert_one( + instance=Category, + values=[ + ColumnValue(name="postId", value=1), + ColumnValue(name="type", value=cat), + ], + ) + + profile = pg_loom.find_by_pk( + instance=Profile, + pk=profileId, + include=[ + Include( + model=User, select=["id", "username", "tokenVersion"], has="one" + ) + ], + ) + assert profile == { + "avatar": "hello.jpg", + "id": 1, + "userId": 1, + "user": {"id": 1, "username": "@miller", "tokenVersion": 0}, + } + + user = pg_loom.find_by_pk( + instance=User, + pk=userId, + include=[Include(model=Profile, select=["id", "avatar"], has="one")], + ) + assert user == { + "id": 1, + "name": "Bob", + "tokenVersion": 0, + "username": "@miller", + "profile": {"id": 1, "avatar": "hello.jpg"}, + } + + user = pg_loom.find_by_pk( + instance=User, + pk=userId, + include=[ + Include( + model=Post, + select=["id", "title"], + has="many", + offset=0, + limit=2, + order=[ + Order(column="createdAt", order="DESC"), + Order(column="id", order="DESC"), + ], + ), + Include(model=Profile, select=["id", "avatar"], has="one"), + ], + ) + assert user == { + "id": 1, + "name": "Bob", + "tokenVersion": 0, + "username": "@miller", + "posts": [ + {"id": 4, "title": "Coding"}, + {"id": 3, "title": "What are you doing"}, + ], + "profile": {"id": 1, "avatar": "hello.jpg"}, + } + + post = pg_loom.find_by_pk( + instance=Post, + pk=1, + select=["title", "id"], + include=[ + Include( + model=User, + select=["id", "username"], + has="one", + include=[ + Include(model=Profile, select=["avatar", "id"], has="one") + ], + ), + Include( + model=Category, + select=["id", "type"], + has="many", + order=[Order(column="id", order="DESC")], + ), + ], + ) + + assert post == { + "title": "Hey", + "id": 1, + "user": { + "id": 1, + "username": "@miller", + "profile": {"avatar": "hello.jpg", "id": 1}, + }, + "categories": [ + {"id": 4, "type": "sport"}, + {"id": 3, "type": "tech"}, + {"id": 2, "type": "education"}, + {"id": 1, "type": "general"}, + ], + } + + user = pg_loom.find_by_pk( + instance=User, + pk=userId2, + select=["username", "id"], + include=[ + Include( + model=Post, + select=["id", "title"], + has="many", + include=[ + Include( + model=Category, + select=["type", "id"], + has="many", + order=[Order(column="id", order="DESC")], + limit=2, + offset=0, + ) + ], + ), + ], + ) + assert user == {"username": "bob", "id": 2, "posts": []} + + conn.close() diff --git a/dataloom/tests/sqlite3/test_eager_loading_sqlite.py b/dataloom/tests/sqlite3/test_eager_loading_sqlite.py new file mode 100644 index 0000000..b47ef0d --- /dev/null +++ b/dataloom/tests/sqlite3/test_eager_loading_sqlite.py @@ -0,0 +1,233 @@ +class TestEagerLoadingOnSQLite: + def test_find_by_pk(self): + from dataloom import ( + Dataloom, + Model, + Column, + PrimaryKeyColumn, + CreatedAtColumn, + TableColumn, + ForeignKeyColumn, + ColumnValue, + Include, + Order, + ) + + sqlite_loom = Dataloom(dialect="sqlite", database="hi.db") + + class User(Model): + __tablename__: TableColumn = TableColumn(name="users") + id = PrimaryKeyColumn(type="int", auto_increment=True) + name = Column(type="text", nullable=False, default="Bob") + username = Column(type="varchar", unique=True, length=255) + tokenVersion = Column(type="int", default=0) + + class Profile(Model): + __tablename__: TableColumn = TableColumn(name="profiles") + id = PrimaryKeyColumn(type="int", auto_increment=True) + avatar = Column(type="text", nullable=False) + userId = ForeignKeyColumn( + User, + maps_to="1-1", + type="int", + required=True, + onDelete="CASCADE", + onUpdate="CASCADE", + ) + + class Post(Model): + __tablename__: TableColumn = TableColumn(name="posts") + id = PrimaryKeyColumn( + type="int", auto_increment=True, nullable=False, unique=True + ) + completed = Column(type="boolean", default=False) + title = Column(type="varchar", length=255, nullable=False) + # timestamps + createdAt = CreatedAtColumn() + # relations + userId = ForeignKeyColumn( + User, + maps_to="1-N", + type="int", + required=True, + onDelete="CASCADE", + onUpdate="CASCADE", + ) + + class Category(Model): + __tablename__: TableColumn = TableColumn(name="categories") + id = PrimaryKeyColumn( + type="int", auto_increment=True, nullable=False, unique=True + ) + type = Column(type="varchar", length=255, nullable=False) + + postId = ForeignKeyColumn( + Post, + maps_to="N-1", + type="int", + required=True, + onDelete="CASCADE", + onUpdate="CASCADE", + ) + + conn, tables = sqlite_loom.connect_and_sync( + [User, Profile, Post, Category], drop=True, force=True + ) + + userId = sqlite_loom.insert_one( + instance=User, + values=ColumnValue(name="username", value="@miller"), + ) + + userId2 = sqlite_loom.insert_one( + instance=User, + values=ColumnValue(name="username", value="bob"), + ) + + profileId = sqlite_loom.insert_one( + instance=Profile, + values=[ + ColumnValue(name="userId", value=userId), + ColumnValue(name="avatar", value="hello.jpg"), + ], + ) + for title in ["Hey", "Hello", "What are you doing", "Coding"]: + sqlite_loom.insert_one( + instance=Post, + values=[ + ColumnValue(name="userId", value=userId), + ColumnValue(name="title", value=title), + ], + ) + + for cat in ["general", "education", "tech", "sport"]: + sqlite_loom.insert_one( + instance=Category, + values=[ + ColumnValue(name="postId", value=1), + ColumnValue(name="type", value=cat), + ], + ) + + profile = sqlite_loom.find_by_pk( + instance=Profile, + pk=profileId, + include=[ + Include( + model=User, select=["id", "username", "tokenVersion"], has="one" + ) + ], + ) + assert profile == { + "avatar": "hello.jpg", + "id": 1, + "userId": 1, + "user": {"id": 1, "username": "@miller", "tokenVersion": 0}, + } + + user = sqlite_loom.find_by_pk( + instance=User, + pk=userId, + include=[Include(model=Profile, select=["id", "avatar"], has="one")], + ) + assert user == { + "id": 1, + "name": "Bob", + "tokenVersion": 0, + "username": "@miller", + "profile": {"id": 1, "avatar": "hello.jpg"}, + } + + user = sqlite_loom.find_by_pk( + instance=User, + pk=userId, + include=[ + Include( + model=Post, + select=["id", "title"], + has="many", + offset=0, + limit=2, + order=[ + Order(column="createdAt", order="DESC"), + Order(column="id", order="DESC"), + ], + ), + Include(model=Profile, select=["id", "avatar"], has="one"), + ], + ) + assert user == { + "id": 1, + "name": "Bob", + "tokenVersion": 0, + "username": "@miller", + "posts": [ + {"id": 4, "title": "Coding"}, + {"id": 3, "title": "What are you doing"}, + ], + "profile": {"id": 1, "avatar": "hello.jpg"}, + } + + post = sqlite_loom.find_by_pk( + instance=Post, + pk=1, + select=["title", "id"], + include=[ + Include( + model=User, + select=["id", "username"], + has="one", + include=[ + Include(model=Profile, select=["avatar", "id"], has="one") + ], + ), + Include( + model=Category, + select=["id", "type"], + has="many", + order=[Order(column="id", order="DESC")], + ), + ], + ) + + assert post == { + "title": "Hey", + "id": 1, + "user": { + "id": 1, + "username": "@miller", + "profile": {"avatar": "hello.jpg", "id": 1}, + }, + "categories": [ + {"id": 4, "type": "sport"}, + {"id": 3, "type": "tech"}, + {"id": 2, "type": "education"}, + {"id": 1, "type": "general"}, + ], + } + + user = sqlite_loom.find_by_pk( + instance=User, + pk=userId2, + select=["username", "id"], + include=[ + Include( + model=Post, + select=["id", "title"], + has="many", + include=[ + Include( + model=Category, + select=["type", "id"], + has="many", + order=[Order(column="id", order="DESC")], + limit=2, + offset=0, + ) + ], + ), + ], + ) + assert user == {"username": "bob", "id": 2, "posts": []} + + conn.close() diff --git a/hi.db b/hi.db index 887375c62e31414a01dfe9d5170fe4840e4b7a21..bcfadd7d16de37355212f16a374cbc2830882134 100644 GIT binary patch delta 911 zcmaJ-O-K|`9Di@-?ascPkEf}PsEe~pfzxX4hZUM;t1U%{#-c$WsOwI;vF@xpBM}Js zd9R)8M2Ak%DfiN$FhmH1FzO~0344gW6og(X=5fR z{1pHo(0g#C#N*b;85%u*?}O;w2fSwwfFCP~UjCv@sSw!opI}o-K}u zL5LPpcBl0nmx^Vn38F1Pwu;qNZ%F4Q$aO#t)WF7Quuo6^xjK{1WVC!F6zOOSMcTp_ zm2jx5BhnR)q^NafdPZvrWcB+2bxna)gzN!CHW>1ayeAvvGi|v>Zeceo!-_42OG2z4 z9_@oX94ljId&D!W59jjw-E>AXs1aW5lrSR2E}{XlOFKRzD@3DRJ;dGWa(h6uFjU*n zd(3jQsM}#5Qr1^Vt&8HD`gHb~Tpv1_W>2P>6KQLANN)INnb*-$0^~Q@CqGD;l&F@E zq*xlmRn&?anADR@aZl$%g%A=D*#_h%DUltrP4m`C3?l?&Y~=L3>0uG5!=^SlEiwo+ vV=XmDeNF3G9wN|)r?jk=pU6}OB2qpaNZag?(=HZ5geot~R$hi-S&aVzBgX3M delta 875 zcmaKqKWx)L6voeS;`nUey@a$ff1K7^6imTM+7v~IRxMIOp+roK2vkFYlDJCBz(iV* z7%6y)kXTq*z-57fih+rtf&o|}RZ4}FAhin&h=FtEqDG`x@~1ohzW3gzJ2#trb3L$n z^w6+|5R%yIKgx?oC(=6mdh*r=k)I-rdx&<)W!jWJ5lz{_J>`{h&y7Z^IJmsfnsWr& zk(cwWd{|XgZl&rx5Z{K9$~Uyd$Y%Id>!P5}#vLjjb{;~E?Jw-xW9PdhDWrh#?(}5q zyfYg`w2#hE;Fy^tta_Fztp)1VQqM5Bj~|TXfxvmoRfom952@G8|8}C zzyV(67VXATgNQt@;@xCk6`}J8HsKvSgZp4X9uo8y{Yu}^C$vWA*?Ac>F-imx9m(KX zd!byrnKskoV`h3RmDSD3oSDf@j2j9wq#V6Cbawz9kr`DpxL{Q(_5iL)jK@5FX8;|= z2My2WCQj#4lZNOG&)_SiYfHM5D3ZPmzG)}(d* s!3^;nM~J72;;AfqDoLJV?5QYv8V-9Z2yW*pj)-MTVEu8){b9@IA7E6zYybcN diff --git a/playground.py b/playground.py index 79d7b55..2de66f6 100644 --- a/playground.py +++ b/playground.py @@ -194,3 +194,29 @@ class Category(Model): ) print(post) + + +user = mysql_loom.find_by_pk( + instance=User, + pk=userId2, + select=["username", "id"], + include=[ + Include( + model=Post, + select=["id", "title"], + has="many", + include=[ + Include( + model=Category, + select=["type", "id"], + has="many", + order=[Order(column="id", order="DESC")], + limit=2, + offset=0, + ) + ], + ), + ], +) + +print(user)