Skip to content

Commit 7f784bd

Browse files
committed
query builder and the doccumentation
1 parent 081d1d2 commit 7f784bd

File tree

12 files changed

+415
-30
lines changed

12 files changed

+415
-30
lines changed

Changelog.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,44 @@
1+
===
2+
Dataloom **`2.3.0`**
3+
===
4+
5+
### Release Notes - `dataloom`
6+
7+
We have release the new `dataloom` Version `2.3.0` (`2024-02-26`)
8+
9+
##### Features
10+
11+
- updated documentation.
12+
- Query Builder in executing queries and SQL Scripts.
13+
14+
```py
15+
qb = loom.getQueryBuilder()
16+
res = qb.run("select id from posts;", fetchall=True)
17+
print(res)
18+
```
19+
20+
We can use the query builder to execute the SQL as follows:
21+
22+
```py
23+
with open("qb.sql", "r") as reader:
24+
sql = reader.read()
25+
res = qb.run(
26+
sql,
27+
fetchall=True,
28+
is_script=True,
29+
)
30+
print(res)
31+
```
32+
33+
> 👍 **Pro Tip:** Executing a script using query builder does not return a result. The result value is always `None`.
34+
135
===
236
Dataloom **`2.2.0`**
337
===
438

539
### Release Notes - `dataloom`
640

7-
We have release the new `dataloom` Version `2.2.0` (`2024-02-24`)
41+
We have release the new `dataloom` Version `2.2.0` (`2024-02-25`)
842

943
##### Features
1044

README.md

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@
105105
- [4. What about bidirectional queries?](#4-what-about-bidirectional-queries)
106106
- [1. Child to Parent](#1-child-to-parent)
107107
- [2. Parent to Child](#2-parent-to-child)
108+
- [Query Builder.](#query-builder)
109+
- [Why Use Query Builder?](#why-use-query-builder)
108110
- [What is coming next?](#what-is-coming-next)
109111
- [Contributing](#contributing)
110112
- [License](#license)
@@ -2112,10 +2114,81 @@ print(user_post) """ ? =
21122114

21132115
```
21142116

2117+
### Query Builder.
2118+
2119+
Dataloom exposes a method called `getQueryBuilder`, which allows you to obtain a `qb` object. This object enables you to execute SQL queries directly from SQL scripts.
2120+
2121+
```py
2122+
qb = loom.getQueryBuilder()
2123+
2124+
print(qb) # ? = Loom QB<mysql>
2125+
```
2126+
2127+
The `qb` object contains the method called `run`, which is used to execute SQL scripts or SQL queries.
2128+
2129+
```py
2130+
ids = qb.run("select id from posts;", fetchall=True)
2131+
print(ids) # ? = [(1,), (2,), (3,), (4,)]
2132+
```
2133+
2134+
You can also execute SQL files. In the following example, we will demonstrate how you can execute SQL scripts using the `qb`. Let's say we have an SQL file called `qb.sql` which contains the following SQL code:
2135+
2136+
```SQL
2137+
SELECT id, title FROM posts WHERE id IN (1, 3, 2, 4) LIMIT 4 OFFSET 1;
2138+
SELECT COUNT(*) FROM (
2139+
SELECT DISTINCT `id`
2140+
FROM `posts`
2141+
WHERE `id` < 5
2142+
LIMIT 3 OFFSET 2
2143+
) AS subquery;
2144+
```
2145+
2146+
We can use the query builder to execute the SQL as follows:
2147+
2148+
```py
2149+
with open("qb.sql", "r") as reader:
2150+
sql = reader.read()
2151+
res = qb.run(
2152+
sql,
2153+
fetchall=True,
2154+
is_script=True,
2155+
)
2156+
print(res)
2157+
```
2158+
2159+
> 👍 **Pro Tip:** Executing a script using query builder does not return a result. The result value is always `None`.
2160+
2161+
The `run` method takes the following as arguments:
2162+
2163+
| Argument | Description | Type | Required | Default |
2164+
| --------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------- | -------- | ------- |
2165+
| `sql` | SQL query to execute. | `str` | Yes | |
2166+
| `args` | Parameters for the SQL query. | `Any \| None` | No | `None` |
2167+
| `fetchone` | Whether to fetch only one result. | `bool` | No | `False` |
2168+
| `fetchmany` | Whether to fetch multiple results. | `bool` | No | `False` |
2169+
| `fetchall` | Whether to fetch all results. | `bool` | No | `False` |
2170+
| `mutation` | Whether the query is a mutation (insert, update, delete). | `bool` | No | `True` |
2171+
| `bulk` | Whether the query is a bulk operation. | `bool` | No | `False` |
2172+
| `affected_rows` | Whether to return affected rows. | `bool` | No | `False` |
2173+
| `operation` | Type of operation being performed. | `'insert', 'update', 'delete', 'read' \| None` | No | `None` |
2174+
| `verbose` | Verbosity level for logging . Set this option to `0` if you don't want logging at all. | `int` | No | `1` |
2175+
| `is_script` | Whether the SQL is a script. | `bool` | No | `False` |
2176+
2177+
#### Why Use Query Builder?
2178+
2179+
- The query builder empowers developers to seamlessly execute `SQL` queries directly.
2180+
- While Dataloom primarily utilizes `subqueries` for eager data fetching on models, developers may prefer to employ JOIN operations, which are achievable through the `qb` object.
2181+
2182+
```python
2183+
qb = loom.getQueryBuilder()
2184+
result = qb.run("SELECT * FROM table1 INNER JOIN table2 ON table1.id = table2.table1_id;")
2185+
print(result)
2186+
```
2187+
21152188
### What is coming next?
21162189

21172190
1. N-N associations
2118-
2. Query Builder
2191+
2. Self relations
21192192

21202193
### Contributing
21212194

dataloom/loom/__init__.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from dataloom.model import Model
1313
from dataloom.statements import GetStatement
1414
from dataloom.conn import ConnectionOptionsFactory
15-
from typing import Optional, Any
15+
from typing import Optional, Any, Literal
1616
from mysql.connector.pooling import PooledMySQLConnection
1717
from sqlite3 import Connection
1818
from mysql.connector.connection import MySQLConnectionAbstract
@@ -27,6 +27,7 @@
2727
)
2828
from dataloom.loom.interfaces import ILoom
2929
from dataloom.loom.math import math
30+
from dataloom.loom.qb import qb
3031

3132

3233
class Loom(ILoom):
@@ -1165,7 +1166,7 @@ def _execute_sql(
11651166
mutation=True,
11661167
bulk: bool = False,
11671168
affected_rows: bool = False,
1168-
operation: Optional[str] = None,
1169+
operation: Optional[Literal["insert", "update", "delete", "read"]] = None,
11691170
_verbose: int = 1,
11701171
_is_script: bool = False,
11711172
) -> Any:
@@ -1785,3 +1786,28 @@ def count(
17851786
distinct=distinct,
17861787
filters=filters,
17871788
)
1789+
1790+
# qb
1791+
1792+
def getQueryBuilder(self):
1793+
"""
1794+
getQueryBuilder
1795+
---------------
1796+
Retrieves a query builder instance.
1797+
1798+
Parameters
1799+
----------
1800+
No parameters
1801+
1802+
Returns
1803+
-------
1804+
qb
1805+
Query builder instance.
1806+
1807+
Examples
1808+
--------
1809+
>>> qb = loom.getQueryBuilder()
1810+
... print(qb)
1811+
"""
1812+
builder = qb(_execute_sql=self._execute_sql, dialect=self.dialect)
1813+
return builder

dataloom/loom/interfaces.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -218,21 +218,6 @@ def delete_bulk(
218218
def tables(self):
219219
raise NotImplementedError("The tables property was not implemented")
220220

221-
@abstractclassmethod
222-
def _execute_sql(
223-
self,
224-
sql: str,
225-
args=None,
226-
fetchone=False,
227-
fetchmany=False,
228-
fetchall=False,
229-
mutation=True,
230-
bulk: bool = False,
231-
affected_rows: bool = False,
232-
operation: Optional[str] = None,
233-
) -> Any:
234-
raise NotImplementedError("The _execute_sql method was not implemented.")
235-
236221
def connect(
237222
self,
238223
) -> Any | PooledMySQLConnection | MySQLConnectionAbstract | Connection:

dataloom/loom/qb.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from typing import Callable, Any, Literal, Optional
2+
3+
from dataloom.types import DIALECT_LITERAL
4+
5+
6+
class qb:
7+
def __repr__(self) -> str:
8+
return f"Loom QB<{self.dialect}>"
9+
10+
def __str__(self) -> str:
11+
return f"Loom QB<{self.dialect}>"
12+
13+
def __init__(
14+
self, _execute_sql: Callable[..., Any], dialect: DIALECT_LITERAL
15+
) -> None:
16+
self.__exc = _execute_sql
17+
self.dialect = dialect
18+
19+
def run(
20+
self,
21+
sql: str,
22+
args: Any | None = None,
23+
fetchone: bool = False,
24+
fetchmany: bool = False,
25+
fetchall: bool = False,
26+
mutation: bool = True,
27+
bulk: bool = False,
28+
affected_rows: bool = False,
29+
operation: Optional[Literal["insert", "update", "delete", "read"]] = None,
30+
verbose: int = 1,
31+
is_script: bool = False,
32+
):
33+
"""
34+
run
35+
-----------
36+
37+
Execute SQL query with optional parameters.
38+
39+
Parameters
40+
----------
41+
sql : str
42+
SQL query to execute.
43+
args : Any | None, optional
44+
Parameters for the SQL query. Defaults to None.
45+
fetchone : bool, optional
46+
Whether to fetch only one result. Defaults to False.
47+
fetchmany : bool, optional
48+
Whether to fetch multiple results. Defaults to False.
49+
fetchall : bool, optional
50+
Whether to fetch all results. Defaults to False.
51+
mutation : bool, optional
52+
Whether the query is a mutation (insert, update, delete). Defaults to True.
53+
bulk : bool, optional
54+
Whether the query is a bulk operation. Defaults to False.
55+
affected_rows : bool, optional
56+
Whether to return affected rows. Defaults to False.
57+
operation : Literal['insert', 'update', 'delete', 'read'] | None, optional
58+
Type of operation being performed. Defaults to None.
59+
verbose : int, optional
60+
Verbosity level for logging. Defaults to 1.
61+
is_script : bool, optional
62+
Whether the SQL is a script. Defaults to False.
63+
64+
Returns
65+
-------
66+
Any
67+
Query result.
68+
69+
Examples
70+
--------
71+
>>> qb = loom.getQueryBuilder()
72+
... ids = qb.run("select id from posts;", fetchall=True)
73+
... print(ids)
74+
...
75+
"""
76+
return self.__exc(
77+
sql,
78+
args=args,
79+
fetchall=fetchall,
80+
fetchmany=fetchmany,
81+
fetchone=fetchone,
82+
mutation=mutation,
83+
bulk=bulk,
84+
affected_rows=affected_rows,
85+
operation=operation,
86+
_verbose=verbose,
87+
_is_script=is_script,
88+
)

dataloom/loom/sql.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Optional
1+
from typing import Any, Optional, Literal
22
from dataloom.types import DIALECT_LITERAL, SQL_LOGGER_LITERAL
33
from dataloom.exceptions import UnsupportedDialectException
44
from sqlite3 import Connection
@@ -59,7 +59,7 @@ def execute_sql(
5959
mutation=True,
6060
bulk: bool = False,
6161
affected_rows: bool = False,
62-
operation: Optional[str] = None,
62+
operation: Optional[Literal["insert", "update", "delete", "read"]] = None,
6363
_verbose: int = 1,
6464
_is_script: bool = False,
6565
) -> Any:
@@ -110,7 +110,6 @@ def execute_sql(
110110
except Exception:
111111
pass
112112
return None
113-
114113
if args is None:
115114
cursor.execute(sql)
116115
else:
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
class TestQBMySQL:
2+
def test_qb(self):
3+
from dataloom import (
4+
Loom,
5+
Model,
6+
Column,
7+
PrimaryKeyColumn,
8+
CreatedAtColumn,
9+
UpdatedAtColumn,
10+
TableColumn,
11+
ForeignKeyColumn,
12+
ColumnValue,
13+
)
14+
from dataloom.keys import MySQLConfig
15+
from typing import Optional
16+
17+
mysql_loom = Loom(
18+
dialect="mysql",
19+
database=MySQLConfig.database,
20+
password=MySQLConfig.password,
21+
user=MySQLConfig.user,
22+
)
23+
24+
class User(Model):
25+
__tablename__: Optional[TableColumn] = TableColumn(name="users")
26+
id = PrimaryKeyColumn(type="int", auto_increment=True)
27+
name = Column(type="text", nullable=False, default="Bob")
28+
username = Column(type="varchar", unique=True, length=255)
29+
30+
class Post(Model):
31+
__tablename__: Optional[TableColumn] = TableColumn(name="posts")
32+
id = PrimaryKeyColumn(
33+
type="int", auto_increment=True, nullable=False, unique=True
34+
)
35+
completed = Column(type="boolean", default=False)
36+
title = Column(type="varchar", length=255, nullable=False)
37+
# timestamps
38+
createdAt = CreatedAtColumn()
39+
updatedAt = UpdatedAtColumn()
40+
# relations
41+
userId = ForeignKeyColumn(
42+
User, type="int", required=True, onDelete="CASCADE", onUpdate="CASCADE"
43+
)
44+
45+
conn, _ = mysql_loom.connect_and_sync([Post, User], drop=True, force=True)
46+
userId = mysql_loom.insert_one(
47+
instance=User,
48+
values=ColumnValue(name="username", value="@miller"),
49+
)
50+
51+
for title in ["Hey", "Hello", "What are you doing", "Coding"]:
52+
mysql_loom.insert_one(
53+
instance=Post,
54+
values=[
55+
ColumnValue(name="userId", value=userId),
56+
ColumnValue(name="title", value=title),
57+
],
58+
)
59+
qb = mysql_loom.getQueryBuilder()
60+
res = qb.run("select id from posts;", fetchall=True)
61+
assert str(qb) == "Loom QB<mysql>"
62+
assert len(res) == 4
63+
conn.close()

0 commit comments

Comments
 (0)