From 4c457e73e8a374ca37915749164770435a7526d8 Mon Sep 17 00:00:00 2001 From: Crispen Gari Date: Wed, 31 Jan 2024 10:36:00 +0200 Subject: [PATCH 1/4] eee --- orm/db/__init__.py | 6 ++++++ orm/keys.py | 2 +- orm/model/column.py | 2 -- playground.py | 4 ++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/orm/db/__init__.py b/orm/db/__init__.py index 9c2a0ab..ade492d 100644 --- a/orm/db/__init__.py +++ b/orm/db/__init__.py @@ -202,3 +202,9 @@ def find_one(self, instance: Model, filters: dict = {}): sql, _, params = instance._get_select_where_stm(fields, filters) row = self._execute_sql(sql, args=params, fetchone=True) return None if row is None else instance(**dict(zip(fields, row))) + + def delete_by_pk(self, instance: Model): + pass + + def delete_one(self, instance: Model, filters: dict = {}): + pass diff --git a/orm/keys.py b/orm/keys.py index 031a679..75f0def 100644 --- a/orm/keys.py +++ b/orm/keys.py @@ -1,4 +1,4 @@ -push = True +push = False if push: diff --git a/orm/model/column.py b/orm/model/column.py index dc40440..e6a16ef 100644 --- a/orm/model/column.py +++ b/orm/model/column.py @@ -50,7 +50,6 @@ class Column: def __init__( self, type, - primary_key: bool = False, nullable: bool = True, unique: bool = False, length: int | None = None, @@ -58,7 +57,6 @@ def __init__( default=None, ): self.type = type - self.primary_key = primary_key self.nullable = nullable self.unique = unique self.length = length diff --git a/playground.py b/playground.py index 60c5087..0f7906d 100644 --- a/playground.py +++ b/playground.py @@ -72,8 +72,8 @@ def to_dict(self): # posts = db.find_all(Post) # print([u.to_dict() for u in posts]) -# me = db.find_by_pk(User, 1) -# print(me.to_dict()) +me = db.find_by_pk(User, 1) +print(me.to_dict()) # him = db.find_one(User, filters={"id": 1}) # print(him.to_dict()) From cd26b49969a61cf53d3fb67b83dd0723a9d5742d Mon Sep 17 00:00:00 2001 From: Crispen Gari Date: Wed, 31 Jan 2024 14:37:50 +0200 Subject: [PATCH 2/4] delete one and delete bulk fns --- README.md | 8 +++ orm/db/__init__.py | 42 ++++++++-------- orm/model/model.py | 51 ++++++++++++++++++- orm/model/statements.py | 5 ++ orm/tests/test_create_table.py | 16 +++--- orm/tests/test_delete.py | 91 ++++++++++++++++++++++++++++++++++ orm/tests/test_insert.py | 2 +- playground.py | 26 ++++++---- 8 files changed, 203 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index a156505..15c15c8 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,14 @@ him = db.find_one(User, filters={"id": 1}) print(him.to_dict()) ``` +4. Deleting a record + +With the `delete_by_pk` method you can delete a record in a database based on the primary-key value: + +```py +affected_rows = db.delete_by_pk(User, userId) +``` + ### Associations With `dataloom` you can define models that have relationships. Let's say we have a model called `Post` and every post should belong to a single `User`. Here is how you can define model mappings between a `Post` and a `User` using the `ForeignKeyColumn()` diff --git a/orm/db/__init__.py b/orm/db/__init__.py index 64290e5..6a0a3f0 100644 --- a/orm/db/__init__.py +++ b/orm/db/__init__.py @@ -10,7 +10,6 @@ CreatedAtColumn, ForeignKeyColumn, ) -from functools import partial class Database: @@ -49,6 +48,7 @@ def _execute_sql( fetchall=False, mutation=True, bulk: bool = False, + affected_rows: bool = False, ): # do we need to log the executed SQL? if self.logs: @@ -62,7 +62,7 @@ def _execute_sql( sql, vars=args ) # options - if bulk: + if bulk or affected_rows: result = cursor.rowcount else: if fetchmany: @@ -163,7 +163,6 @@ def find_all(self, instance: Model): sql, _, __ = instance._get_select_where_stm(fields) data = list() rows = self._execute_sql(sql, fetchall=True) - print(len(rows)) for row in rows: res = dict(zip(fields, row)) data.append(instance(**res)) @@ -197,8 +196,6 @@ def find_by_pk(self, instance: Model, pk): elif isinstance(field, PrimaryKeyColumn): pk_name = name fields.append(name) - - print(fields) sql, fields = instance._get_select_by_pk_stm(pk, pk_name, fields=fields) row = self._execute_sql(sql, fetchone=True) return None if row is None else instance(**dict(zip(fields, row))) @@ -212,24 +209,29 @@ def find_one(self, instance: Model, filters: dict = {}): row = self._execute_sql(sql, args=params, fetchone=True) return None if row is None else instance(**dict(zip(fields, row))) + def delete_bulk(self, instance: Model, filters: dict = {}): + sql, params = instance._get_delete_bulk_where_stm(filters) + affected_rows = self._execute_sql( + sql, args=params, affected_rows=True, fetchall=True + ) + return affected_rows + def delete_one(self, instance: Model, filters: dict = {}): - fields = list() - for name, field in inspect.getmembers(instance): - if isinstance(field, Column): - fields.append(name) - sql, _, params = instance._get_select_where_stm(fields, filters) - row = self._execute_sql(sql, args=params, fetchone=True) - return None if row is None else instance(**dict(zip(fields, row))) + sql, params = instance._get_delete_where_stm(filters) + affected_rows = self._execute_sql( + sql, args=params, affected_rows=True, fetchall=True + ) + return affected_rows def delete_by_pk(self, instance: Model, pk): # what is the name of the primary key column? pk_name = "id" - fields = list() for name, field in inspect.getmembers(instance): - if isinstance(field, Column): - if field.primary_key: - pk_name = name - fields.append(name) - sql, fields = instance._get_select_by_pk_stm(pk, pk_name, fields=fields) - row = self._execute_sql(sql, fetchone=True) - return None if row is None else instance(**dict(zip(fields, row))) + if isinstance(field, PrimaryKeyColumn): + pk_name = name + + sql, pk = instance._get_delete_by_pk_stm(pk, pk_name) + affected_rows = self._execute_sql( + sql, args=(pk,), affected_rows=True, fetchall=True + ) + return affected_rows diff --git a/orm/model/model.py b/orm/model/model.py index 5dcf254..a7ccc5b 100644 --- a/orm/model/model.py +++ b/orm/model/model.py @@ -29,7 +29,11 @@ def _get_name(cls): for name, _ in inspect.getmembers(cls): if name == "__tablename__": __tablename__ = cls.__tablename__ - return cls.__name__.lower() if __tablename__ is None else __tablename__ + return ( + f'"{cls.__name__.lower()}"' + if __tablename__ is None + else f'"{__tablename__}"' + ) @classmethod def _get_pk_attributes(cls): @@ -190,6 +194,15 @@ def _get_select_by_pk_stm(cls, pk, pk_name: str = "id", fields: list = []): ) return sql, fields + @classmethod + def _get_delete_by_pk_stm(cls, pk, pk_name: str = "id"): + sql = Statements.DELETE_BY_PK.format( + table_name=cls._get_name(), + pk="%s", # mask it to avoid SQL Injection + pk_name=pk_name, + ) + return sql, pk + @classmethod def _get_insert_bulk_smt(cls, placeholders, columns, data): column_names = columns @@ -249,3 +262,39 @@ def _get_select_where_stm(cls, fields: list = [], args: dict = {}): filters=" AND ".join(filters), ) return sql, fields, params + + @classmethod + def _get_delete_where_stm(cls, args: dict = {}): + params = [] + filters = [] + for key, value in args.items(): + filters.append(f"{key} = %s") + params.append(value) + if len(filters) == 0: + sql = Statements.DELETE_ALL_COMMAND.format( + table_name=cls._get_name(), + ) + else: + sql = Statements.DELETE_ONE_WHERE_COMMAND.format( + table_name=cls._get_name(), + filters=" AND ".join(filters), + ) + return sql, params + + @classmethod + def _get_delete_bulk_where_stm(cls, args: dict = {}): + params = [] + filters = [] + for key, value in args.items(): + filters.append(f"{key} = %s") + params.append(value) + if len(filters) == 0: + sql = Statements.DELETE_ALL_COMMAND.format( + table_name=cls._get_name(), + ) + else: + sql = Statements.DELETE_BULK_WHERE_COMMAND.format( + table_name=cls._get_name(), + filters=" AND ".join(filters), + ) + return sql, params diff --git a/orm/model/statements.py b/orm/model/statements.py index e331aab..b937b2a 100644 --- a/orm/model/statements.py +++ b/orm/model/statements.py @@ -1,4 +1,9 @@ class Statements: + # delete + DELETE_BY_PK = "DELETE FROM {table_name} WHERE {pk_name} = {pk};" + DELETE_ONE_WHERE_COMMAND = "DELETE FROM {table_name} WHERE {filters};" + DELETE_BULK_WHERE_COMMAND = "DELETE FROM {table_name} WHERE {filters};" + DELETE_ALL_COMMAND = "DELETE FROM {table_name};" # select SELECT_COMMAND = "SELECT {column_names} FROM {table_name};" SELECT_BY_PK = "SELECT {column_names} FROM {table_name} WHERE {pk_name}={pk};" diff --git a/orm/tests/test_create_table.py b/orm/tests/test_create_table.py index bd953b7..b7559ee 100644 --- a/orm/tests/test_create_table.py +++ b/orm/tests/test_create_table.py @@ -9,14 +9,15 @@ def test_2_pk_error(self): db = Database(database, password=password, user=user) conn = db.connect() - class Users(Model): + class User(Model): + __tablename__ = "users" _id = PrimaryKeyColumn(type="bigint", auto_increment=True) id = PrimaryKeyColumn(type="bigint", auto_increment=True) username = Column(type="text", nullable=False, default="Hello there!!") name = Column(type="varchar", unique=True, length=255) with pytest.raises(Exception) as exc_info: - db.sync([Users], drop=True, force=True) + db.sync([User], drop=True, force=True) assert ( str(exc_info.value) @@ -34,12 +35,13 @@ def test_no_pk_error(self): db = Database(database, password=password, user=user) conn = db.connect() - class Users(Model): + class User(Model): + __tablename__ = "users" username = Column(type="text", nullable=False, default="Hello there!!") name = Column(type="varchar", unique=True, length=255) with pytest.raises(Exception) as exc_info: - db.sync([Users], drop=True, force=True) + db.sync([User], drop=True, force=True) assert str(exc_info.value) == "Your table does not have a primary key column." conn.close() @@ -63,8 +65,8 @@ class User(Model): username = Column(type="text", nullable=False, default="Hello there!!") name = Column(type="varchar", unique=True, length=255) - assert User._get_name() == "users" - assert Todos._get_name() == "todos" + assert User._get_name() == '"users"' + assert Todos._get_name() == '"todos"' conn.close() def test_connect_sync(self): @@ -90,7 +92,7 @@ class Post(Model): assert len(tables) == 2 assert conn.status == 1 - assert tables == ["users", "posts"] + assert sorted(tables) == sorted(["users", "posts"]) conn.close() diff --git a/orm/tests/test_delete.py b/orm/tests/test_delete.py index e69de29..863991e 100644 --- a/orm/tests/test_delete.py +++ b/orm/tests/test_delete.py @@ -0,0 +1,91 @@ +class TestDeletingOnPG: + def test_delete_by_pk_single_fn(self): + from orm.db import Database + from orm.model.column import Column + from orm.model.model import Model, PrimaryKeyColumn + from orm.keys import password, database, user + + db = Database(database, password=password, user=user) + conn = db.connect() + + class User(Model): + __tablename__ = "users" + id = PrimaryKeyColumn(type="bigint", auto_increment=True) + username = Column(type="text", nullable=False, default="Hello there!!") + name = Column(type="varchar", unique=True, length=255) + + db.sync([User], drop=True, force=True) + + user = User(name="Crispen", username="heyy") + userId = db.commit(user) + affected_rows_1 = db.delete_by_pk(User, userId) + affected_rows_2 = db.delete_by_pk(User, 89) + assert affected_rows_1 == 1 + assert affected_rows_2 == 0 + conn.close() + + def test_delete_one_fn(self): + from orm.db import Database + from orm.model.column import Column + from orm.model.model import Model, PrimaryKeyColumn + from orm.keys import password, database, user + + db = Database(database, password=password, user=user) + conn = db.connect() + + class User(Model): + __tablename__ = "users" + id = PrimaryKeyColumn(type="bigint", auto_increment=True) + username = Column(type="text", nullable=False, default="Hello there!!") + name = Column(type="varchar", unique=False, length=255) + + db.sync([User], drop=True, force=True) + + db.commit_bulk( + [ + User(name="Crispen", username="heyy"), + User(name="Crispen", username="heyy"), + User(name="Crispen", username="heyy"), + ] + ) + affected_rows_1 = db.delete_one(User, {"name": "Crispen"}) + affected_rows_2 = db.delete_one(User, {"name": "Crispen", "id": 9}) + affected_rows_3 = db.delete_one(User, {"name": "Crispen", "id": 2}) + + assert affected_rows_1 == 3 + assert affected_rows_3 == 0 + assert affected_rows_2 == 0 + conn.close() + + def test_delete_bulk_fn(self): + from orm.db import Database + from orm.model.column import Column + from orm.model.model import Model, PrimaryKeyColumn + from orm.keys import password, database, user + + db = Database(database, password=password, user=user) + conn = db.connect() + + class User(Model): + __tablename__ = "users" + id = PrimaryKeyColumn(type="bigint", auto_increment=True) + username = Column(type="text", nullable=False, default="Hello there!!") + name = Column(type="varchar", unique=False, length=255) + + db.sync([User], drop=True, force=True) + + db.commit_bulk( + [ + User(name="Crispen", username="heyy"), + User(name="Crispen", username="heyy"), + User(name="Crispen", username="heyy"), + ] + ) + affected_rows_1 = db.delete_one(User, {"name": "Crispen"}) + affected_rows_2 = db.delete_one(User, {"name": "Crispen", "id": 9}) + affected_rows_3 = db.delete_one(User, {"name": "Crispen", "id": 2}) + + assert affected_rows_1 == 3 + assert affected_rows_3 == 0 + assert affected_rows_2 == 0 + conn.close() diff --git a/orm/tests/test_insert.py b/orm/tests/test_insert.py index 22af401..55d2106 100644 --- a/orm/tests/test_insert.py +++ b/orm/tests/test_insert.py @@ -1,4 +1,4 @@ -class TestInsertingOnePG: +class TestInsertingOnPG: def test_insetting_single_document(self): from orm.db import Database from orm.model.column import Column diff --git a/playground.py b/playground.py index 32dbb4f..127971c 100644 --- a/playground.py +++ b/playground.py @@ -53,15 +53,23 @@ def to_dict(self): db = Database("hi", password="root", user="postgres") conn, tables = db.connect_and_sync([User, Post], drop=True, force=True) -user = User(name="Crispen", username="heyy") -userId = db.commit(user) -postId = db.commit( - Post(userId=1, title="What are you thinking"), -) - -now = db.find_by_pk(Post, 1) -print(now.userId) -print(postId) +user = [ + User(name="Crispen", username="heyy"), + User(name="Crispen", username="heyy"), + User(name="Crispen", username="heyy"), +] +userId = db.commit_bulk(user) +# postId = db.commit( +# Post(userId=1, title="What are you thinking"), +# ) + +now = db.delete_bulk(User, {"name": "Crispen"}) + + +# print(f"now: {now}") +# now = db.delete_one(Post, {"id": 8}) +# print(now.userId) +# print(postId) # post = Post(userId=userId, title="What are you thinking") From fb7abe42fde9673e79e6ece465ef1b9be7143ee9 Mon Sep 17 00:00:00 2001 From: Crispen Gari Date: Wed, 31 Jan 2024 14:44:39 +0200 Subject: [PATCH 3/4] docs --- README.md | 12 ++++++++++++ todo.txt | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15c15c8..59bbf33 100644 --- a/README.md +++ b/README.md @@ -348,6 +348,18 @@ With the `delete_by_pk` method you can delete a record in a database based on th affected_rows = db.delete_by_pk(User, userId) ``` +You cal also use `filters` to delete a record in a database. The `delete_one` function allows you to delete a single record in a database that matches a filter. + +```py +affected_rows = db.delete_one(User, {"name": "Crispen"}) +``` + +You can also the `delete_bulk` which delete a lot of records that matches a filter: + +```py +affected_rows = db.delete_bulk(User, {"name": "Crispen"}) +``` + ### Associations With `dataloom` you can define models that have relationships. Let's say we have a model called `Post` and every post should belong to a single `User`. Here is how you can define model mappings between a `Post` and a `User` using the `ForeignKeyColumn()` diff --git a/todo.txt b/todo.txt index 6b24843..118950c 100644 --- a/todo.txt +++ b/todo.txt @@ -2,7 +2,7 @@ 2. inserting data ✅ 3. created at and updated at field ✅ 4. foreign key and primary key ✅ -5. Foreign key Column -6. Delete table +5. Foreign key Column ✅ +6. Delete table data ✅ From 64b6f3a8f59d008e920b0d16f4059e28cd7471b7 Mon Sep 17 00:00:00 2001 From: Crispen Gari Date: Wed, 31 Jan 2024 15:37:52 +0200 Subject: [PATCH 4/4] testing-delete one and delete bulk --- orm/db/__init__.py | 10 ++++++---- orm/model/model.py | 5 ++--- orm/model/statements.py | 6 +++++- orm/tests/test_delete.py | 39 +++++++++++++++++++++++++-------------- playground.py | 3 ++- 5 files changed, 40 insertions(+), 23 deletions(-) diff --git a/orm/db/__init__.py b/orm/db/__init__.py index 6a0a3f0..839f00b 100644 --- a/orm/db/__init__.py +++ b/orm/db/__init__.py @@ -217,10 +217,12 @@ def delete_bulk(self, instance: Model, filters: dict = {}): return affected_rows def delete_one(self, instance: Model, filters: dict = {}): - sql, params = instance._get_delete_where_stm(filters) - affected_rows = self._execute_sql( - sql, args=params, affected_rows=True, fetchall=True - ) + pk = None + for name, field in inspect.getmembers(instance): + if isinstance(field, PrimaryKeyColumn): + pk = name + sql, params = instance._get_delete_where_stm(pk=pk, args=filters) + affected_rows = self._execute_sql(sql, args=params, affected_rows=True) return affected_rows def delete_by_pk(self, instance: Model, pk): diff --git a/orm/model/model.py b/orm/model/model.py index a7ccc5b..31c8f30 100644 --- a/orm/model/model.py +++ b/orm/model/model.py @@ -264,7 +264,7 @@ def _get_select_where_stm(cls, fields: list = [], args: dict = {}): return sql, fields, params @classmethod - def _get_delete_where_stm(cls, args: dict = {}): + def _get_delete_where_stm(cls, pk: str = "id", args: dict = {}): params = [] filters = [] for key, value in args.items(): @@ -276,8 +276,7 @@ def _get_delete_where_stm(cls, args: dict = {}): ) else: sql = Statements.DELETE_ONE_WHERE_COMMAND.format( - table_name=cls._get_name(), - filters=" AND ".join(filters), + table_name=cls._get_name(), filters=" AND ".join(filters), pk=pk ) return sql, params diff --git a/orm/model/statements.py b/orm/model/statements.py index b937b2a..6df9e5b 100644 --- a/orm/model/statements.py +++ b/orm/model/statements.py @@ -1,7 +1,11 @@ class Statements: # delete DELETE_BY_PK = "DELETE FROM {table_name} WHERE {pk_name} = {pk};" - DELETE_ONE_WHERE_COMMAND = "DELETE FROM {table_name} WHERE {filters};" + DELETE_ONE_WHERE_COMMAND = """ + DELETE FROM {table_name} WHERE {pk} = ( + SELECT {pk} FROM {table_name} WHERE {filters} LIMIT 1 + ); + """ DELETE_BULK_WHERE_COMMAND = "DELETE FROM {table_name} WHERE {filters};" DELETE_ALL_COMMAND = "DELETE FROM {table_name};" # select diff --git a/orm/tests/test_delete.py b/orm/tests/test_delete.py index 863991e..b18e1ea 100644 --- a/orm/tests/test_delete.py +++ b/orm/tests/test_delete.py @@ -48,13 +48,15 @@ class User(Model): User(name="Crispen", username="heyy"), ] ) - affected_rows_1 = db.delete_one(User, {"name": "Crispen"}) - affected_rows_2 = db.delete_one(User, {"name": "Crispen", "id": 9}) - affected_rows_3 = db.delete_one(User, {"name": "Crispen", "id": 2}) - - assert affected_rows_1 == 3 - assert affected_rows_3 == 0 - assert affected_rows_2 == 0 + db.delete_one(User, {"name": "Crispen"}) + rows_1 = db.find_many(User, {"name": "Crispen"}) + db.delete_one(User, {"name": "Crispen", "id": 9}) + rows_2 = db.find_many(User, {"name": "Crispen"}) + db.delete_one(User, {"name": "Crispen", "id": 2}) + rows_3 = db.find_many(User, {"name": "Crispen"}) + assert len(rows_1) == 2 + assert len(rows_2) == 2 + assert len(rows_3) == 1 conn.close() def test_delete_bulk_fn(self): @@ -81,11 +83,20 @@ class User(Model): User(name="Crispen", username="heyy"), ] ) - affected_rows_1 = db.delete_one(User, {"name": "Crispen"}) - affected_rows_2 = db.delete_one(User, {"name": "Crispen", "id": 9}) - affected_rows_3 = db.delete_one(User, {"name": "Crispen", "id": 2}) - - assert affected_rows_1 == 3 - assert affected_rows_3 == 0 - assert affected_rows_2 == 0 + db.delete_bulk(User, {"name": "Crispen"}) + rows_1 = db.find_many(User, {"name": "Crispen"}) + db.commit_bulk( + [ + User(name="Crispen", username="heyy"), + User(name="Crispen", username="heyy"), + User(name="Crispen", username="heyy"), + ] + ) + db.delete_bulk(User, {"name": "Crispen", "id": 99}) + rows_2 = db.find_many(User, {"name": "Crispen"}) + db.delete_bulk(User, {"name": "Crispen", "id": 5}) + rows_3 = db.find_many(User, {"name": "Crispen"}) + assert len(rows_1) == 0 + assert len(rows_2) == 3 + assert len(rows_3) == 2 conn.close() diff --git a/playground.py b/playground.py index 127971c..3043cbd 100644 --- a/playground.py +++ b/playground.py @@ -63,7 +63,8 @@ def to_dict(self): # Post(userId=1, title="What are you thinking"), # ) -now = db.delete_bulk(User, {"name": "Crispen"}) +now = db.delete_one(User, {"name": "Crispen"}) +print(now) # print(f"now: {now}")