Skip to content

Commit

Permalink
Add Model.raw method to support the raw sql query (tortoise#896)
Browse files Browse the repository at this point in the history
* Add `Model.raw` method to support the raw sql query

* Fix test

* Fix test
  • Loading branch information
long2ice authored Sep 2, 2021
1 parent af5d87e commit 2dd397d
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 6 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ Changelog

0.17
====

0.17.8
------
- Add `Model.raw` method to support the raw sql query.

0.17.7
------
- Fix `select_related` behaviour for forward relation. (#825)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "tortoise-orm"
version = "0.17.7"
version = "0.17.8"
description = "Easy async ORM for python, built with relations in mind"
authors = ["Andrey Bondar <[email protected]>", "Nickolas Grigoriadis <[email protected]>", "long2ice <[email protected]>"]
license = "Apache-2.0"
Expand Down
8 changes: 8 additions & 0 deletions tests/test_model_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
Dest_null,
Event,
IntFields,
Node,
NoID,
O2O_null,
RequiredPKModel,
Expand Down Expand Up @@ -288,6 +289,13 @@ async def test_force_update_raise(self):
with self.assertRaises(IntegrityError):
await obj.save(force_update=True)

async def test_raw(self):
await Node.create(name="TestRaw")
ret = await Node.raw("select * from node where name='TestRaw'")
self.assertEqual(len(ret), 1)
ret = await Node.raw("select * from node where name='111'")
self.assertEqual(len(ret), 0)


class TestModelMethodsNoID(TestModelMethods):
async def setUp(self):
Expand Down
2 changes: 1 addition & 1 deletion tortoise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,4 +709,4 @@ async def do_stuff():
loop.run_until_complete(Tortoise.close_connections())


__version__ = "0.17.7"
__version__ = "0.17.8"
6 changes: 4 additions & 2 deletions tortoise/backends/base/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from pypika.terms import ArithmeticExpression, Function

from tortoise.exceptions import OperationalError
from tortoise.expressions import F
from tortoise.expressions import F, RawSQL
from tortoise.fields.base import Field
from tortoise.fields.relational import (
BackwardFKRelation,
Expand Down Expand Up @@ -122,7 +122,9 @@ async def execute_explain(self, query: Query) -> Any:
sql = " ".join((self.EXPLAIN_PREFIX, query.get_sql()))
return (await self.db.execute_query(sql))[1]

async def execute_select(self, query: Query, custom_fields: Optional[list] = None) -> list:
async def execute_select(
self, query: Union[Query, RawSQL], custom_fields: Optional[list] = None
) -> list:
_, raw_results = await self.db.execute_query(query.get_sql())
instance_list = []
for row in raw_results:
Expand Down
15 changes: 14 additions & 1 deletion tortoise/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from tortoise.functions import Function
from tortoise.indexes import Index
from tortoise.manager import Manager
from tortoise.queryset import ExistsQuery, Q, QuerySet, QuerySetSingle
from tortoise.queryset import ExistsQuery, Q, QuerySet, QuerySetSingle, RawSQLQuery
from tortoise.router import router
from tortoise.signals import Signals
from tortoise.transactions import current_transaction_map, in_transaction
Expand Down Expand Up @@ -1203,6 +1203,19 @@ def get(cls: Type[MODEL], *args: Q, **kwargs: Any) -> QuerySetSingle[MODEL]:
"""
return cls._meta.manager.get_queryset().get(*args, **kwargs)

@classmethod
def raw(cls, sql: str) -> "RawSQLQuery":
"""
Executes a RAW SQL and returns the result
.. code-block:: python3
result = await User.raw("select * from users where name like '%test%'")
:param sql: The raw sql.
"""
return cls._meta.manager.get_queryset().raw(sql)

@classmethod
def exists(cls: Type[MODEL], *args: Q, **kwargs: Any) -> ExistsQuery:
"""
Expand Down
34 changes: 33 additions & 1 deletion tortoise/queryset.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
MultipleObjectsReturned,
ParamsError,
)
from tortoise.expressions import F
from tortoise.expressions import F, RawSQL
from tortoise.fields.relational import (
ForeignKeyFieldInstance,
OneToOneFieldInstance,
Expand Down Expand Up @@ -649,6 +649,12 @@ def all(self) -> "QuerySet[MODEL]":
"""
return self._clone()

def raw(self, sql: str) -> "RawSQLQuery":
"""
Return the QuerySet from raw SQL
"""
return RawSQLQuery(model=self.model, db=self._db, sql=sql)

def first(self) -> QuerySetSingle[Optional[MODEL]]:
"""
Limit queryset to one object and return one object instead of list.
Expand Down Expand Up @@ -1497,3 +1503,29 @@ async def _execute(self) -> List[dict]:
row[col] = func(row[col])

return result


class RawSQLQuery(AwaitableQuery):

__slots__ = ("_sql", "_db")

def __init__(self, model: Type[MODEL], db: BaseDBAsyncClient, sql: str):
super().__init__(model)
self._sql = sql
self._db = db

def _make_query(self) -> None:
self.query = RawSQL(self._sql)

async def _execute(self) -> Any:
instance_list = await self._db.executor_class(
model=self.model,
db=self._db,
).execute_select(self.query)
return instance_list

def __await__(self) -> Generator[Any, None, List[MODEL]]:
if self._db is None:
self._db = self._choose_db() # type: ignore
self._make_query()
return self._execute().__await__()

0 comments on commit 2dd397d

Please sign in to comment.