sqlqb 0.1.1__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlqb-0.1.1 → sqlqb-0.1.3}/PKG-INFO +1 -1
- {sqlqb-0.1.1 → sqlqb-0.1.3}/pyproject.toml +7 -1
- sqlqb-0.1.3/src/sqlqb/__init__.py +6 -0
- sqlqb-0.1.3/src/sqlqb/delete.py +45 -0
- sqlqb-0.1.3/src/sqlqb/insert.py +45 -0
- sqlqb-0.1.3/src/sqlqb/pymysql/__init__.py +85 -0
- {sqlqb-0.1.1 → sqlqb-0.1.3}/src/sqlqb/select.py +19 -13
- sqlqb-0.1.3/src/sqlqb/sqlalchemy/__init__.py +95 -0
- sqlqb-0.1.3/src/sqlqb/sqlite/__init__.py +72 -0
- sqlqb-0.1.3/src/sqlqb/update.py +52 -0
- sqlqb-0.1.3/tests/test_delete.py +81 -0
- sqlqb-0.1.3/tests/test_insert.py +60 -0
- sqlqb-0.1.3/tests/test_pymysql.py +223 -0
- {sqlqb-0.1.1 → sqlqb-0.1.3}/tests/test_select.py +28 -10
- sqlqb-0.1.3/tests/test_sqlalchemy.py +226 -0
- sqlqb-0.1.3/tests/test_sqlite.py +107 -0
- sqlqb-0.1.3/tests/test_update.py +100 -0
- {sqlqb-0.1.1 → sqlqb-0.1.3}/uv.lock +123 -1
- sqlqb-0.1.1/htmlcov/.gitignore +0 -2
- sqlqb-0.1.1/htmlcov/class_index.html +0 -201
- sqlqb-0.1.1/htmlcov/coverage_html_cb_dd2e7eb5.js +0 -735
- sqlqb-0.1.1/htmlcov/favicon_32_cb_c827f16f.png +0 -0
- sqlqb-0.1.1/htmlcov/function_index.html +0 -391
- sqlqb-0.1.1/htmlcov/index.html +0 -144
- sqlqb-0.1.1/htmlcov/keybd_closed_cb_900cfef5.png +0 -0
- sqlqb-0.1.1/htmlcov/status.json +0 -1
- sqlqb-0.1.1/htmlcov/style_cb_4667309f.css +0 -391
- sqlqb-0.1.1/htmlcov/z_6088b58928f3e76c___init___py.html +0 -131
- sqlqb-0.1.1/htmlcov/z_96458094e7923210___init___py.html +0 -128
- sqlqb-0.1.1/htmlcov/z_c812cfdabd36609f___init___py.html +0 -100
- sqlqb-0.1.1/htmlcov/z_c812cfdabd36609f_select_py.html +0 -177
- sqlqb-0.1.1/src/sqlqb/__init__.py +0 -3
- sqlqb-0.1.1/src/sqlqb/mysql/__init__.py +0 -34
- sqlqb-0.1.1/src/sqlqb/sqlite/__init__.py +0 -31
- sqlqb-0.1.1/tests/test_sqlite.py +0 -52
- {sqlqb-0.1.1 → sqlqb-0.1.3}/.gitignore +0 -0
- {sqlqb-0.1.1 → sqlqb-0.1.3}/Makefile +0 -0
- {sqlqb-0.1.1 → sqlqb-0.1.3}/README.md +0 -0
- /sqlqb-0.1.1/test.db → /sqlqb-0.1.3/data.db +0 -0
- {sqlqb-0.1.1 → sqlqb-0.1.3}/tests/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlqb"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
4
4
|
description = "A simple sql query builder"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = []
|
|
@@ -13,6 +13,12 @@ dev = [
|
|
|
13
13
|
"pytest-cov>=7.1.0",
|
|
14
14
|
"ruff>=0.15.17",
|
|
15
15
|
]
|
|
16
|
+
pymysql = [
|
|
17
|
+
"pymysql>=1.2.0",
|
|
18
|
+
]
|
|
19
|
+
sqlalchemy = [
|
|
20
|
+
"sqlalchemy>=2.0.51",
|
|
21
|
+
]
|
|
16
22
|
|
|
17
23
|
[build-system]
|
|
18
24
|
requires = ["hatchling", "hatch-vcs"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import io
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Delete:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.__table: str | None = None
|
|
7
|
+
self.__wheres: list[str] = []
|
|
8
|
+
self.__params: list = []
|
|
9
|
+
self.__limit: int | None = None
|
|
10
|
+
|
|
11
|
+
def From(self, table: str) -> "Delete":
|
|
12
|
+
if not table or not table.strip():
|
|
13
|
+
raise ValueError("error: table name must not be empty")
|
|
14
|
+
self.__table = table
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def Where(self, condition: str, *args) -> "Delete":
|
|
18
|
+
self.__wheres.append(condition)
|
|
19
|
+
self.__params += args
|
|
20
|
+
return self
|
|
21
|
+
|
|
22
|
+
def Limit(self, limit: int) -> "Delete":
|
|
23
|
+
self.__limit = limit
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def params(self) -> list:
|
|
28
|
+
return self.__params
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def sql(self) -> str:
|
|
32
|
+
if not self.__table:
|
|
33
|
+
raise ValueError("error: no table specified")
|
|
34
|
+
sql = io.StringIO()
|
|
35
|
+
sql.write("DELETE FROM ")
|
|
36
|
+
sql.write(self.__table)
|
|
37
|
+
if self.__wheres:
|
|
38
|
+
sql.write(" WHERE ")
|
|
39
|
+
sql.write(" AND ".join(self.__wheres))
|
|
40
|
+
if self.__limit is not None:
|
|
41
|
+
sql.write(f" LIMIT {self.__limit}")
|
|
42
|
+
return sql.getvalue()
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
return self.sql
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import io
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Insert:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.__table: str | None = None
|
|
7
|
+
self.__rows: list[dict] = []
|
|
8
|
+
|
|
9
|
+
def Into(self, table: str) -> "Insert":
|
|
10
|
+
if not table or not table.strip():
|
|
11
|
+
raise ValueError("error: table name must not be empty")
|
|
12
|
+
self.__table = table
|
|
13
|
+
return self
|
|
14
|
+
|
|
15
|
+
def Values(self, **kwargs) -> "Insert":
|
|
16
|
+
if not kwargs:
|
|
17
|
+
raise ValueError("error: specify at least one column value")
|
|
18
|
+
self.__rows.append(kwargs)
|
|
19
|
+
return self
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def params(self) -> list:
|
|
23
|
+
if not self.__rows:
|
|
24
|
+
return []
|
|
25
|
+
return [v for row in self.__rows for v in row.values()]
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def sql(self) -> str:
|
|
29
|
+
if not self.__table:
|
|
30
|
+
raise ValueError("error: no table specified")
|
|
31
|
+
if not self.__rows:
|
|
32
|
+
raise ValueError("error: no values specified")
|
|
33
|
+
columns = list(self.__rows[0].keys())
|
|
34
|
+
sql = io.StringIO()
|
|
35
|
+
sql.write("INSERT INTO ")
|
|
36
|
+
sql.write(self.__table)
|
|
37
|
+
sql.write(" (")
|
|
38
|
+
sql.write(", ".join(columns))
|
|
39
|
+
sql.write(") VALUES ")
|
|
40
|
+
placeholder = "(" + ", ".join(["?"] * len(columns)) + ")"
|
|
41
|
+
sql.write(", ".join([placeholder] * len(self.__rows)))
|
|
42
|
+
return sql.getvalue()
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
return self.sql
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from pymysql.connections import Connection as _Connection
|
|
2
|
+
from pymysql.cursors import DictCursor
|
|
3
|
+
|
|
4
|
+
from sqlqb import Select as _Select
|
|
5
|
+
from sqlqb import Insert as _Insert
|
|
6
|
+
from sqlqb import Update as _Update
|
|
7
|
+
from sqlqb import Delete as _Delete
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Select(_Select):
|
|
11
|
+
def __init__(self, connection: _Connection, *args):
|
|
12
|
+
super().__init__(*args)
|
|
13
|
+
self.__connection = connection
|
|
14
|
+
|
|
15
|
+
def execute(self):
|
|
16
|
+
cursor = self.__connection.cursor()
|
|
17
|
+
cursor.execute(self.sql, self.params)
|
|
18
|
+
return cursor
|
|
19
|
+
|
|
20
|
+
def fetchone(self):
|
|
21
|
+
with self.__connection.cursor() as cursor:
|
|
22
|
+
cursor.execute(self.sql, self.params)
|
|
23
|
+
result = cursor.fetchone()
|
|
24
|
+
return result
|
|
25
|
+
|
|
26
|
+
def fetchall(self):
|
|
27
|
+
with self.__connection.cursor() as cursor:
|
|
28
|
+
cursor.execute(self.sql, self.params)
|
|
29
|
+
result = cursor.fetchall()
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Insert(_Insert):
|
|
34
|
+
def __init__(self, connection: _Connection):
|
|
35
|
+
super().__init__()
|
|
36
|
+
self.__connection = connection
|
|
37
|
+
|
|
38
|
+
def execute(self) -> int:
|
|
39
|
+
with self.__connection.cursor() as cursor:
|
|
40
|
+
cursor.execute(self.sql, self.params)
|
|
41
|
+
return cursor.rowcount
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Update(_Update):
|
|
45
|
+
def __init__(self, connection: _Connection, table: str):
|
|
46
|
+
super().__init__(table)
|
|
47
|
+
self.__connection = connection
|
|
48
|
+
|
|
49
|
+
def execute(self) -> int:
|
|
50
|
+
with self.__connection.cursor() as cursor:
|
|
51
|
+
cursor.execute(self.sql, self.params)
|
|
52
|
+
return cursor.rowcount
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Delete(_Delete):
|
|
56
|
+
def __init__(self, connection: _Connection):
|
|
57
|
+
super().__init__()
|
|
58
|
+
self.__connection = connection
|
|
59
|
+
|
|
60
|
+
def execute(self) -> int:
|
|
61
|
+
with self.__connection.cursor() as cursor:
|
|
62
|
+
cursor.execute(self.sql, self.params)
|
|
63
|
+
return cursor.rowcount
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Connection(_Connection):
|
|
67
|
+
def __init__(self, *args, **kwargs):
|
|
68
|
+
if "cursorclass" not in kwargs:
|
|
69
|
+
kwargs["cursorclass"] = DictCursor
|
|
70
|
+
super().__init__(*args, **kwargs)
|
|
71
|
+
|
|
72
|
+
def Select(self, *args, **kwargs) -> Select:
|
|
73
|
+
return Select(self, *args, **kwargs)
|
|
74
|
+
|
|
75
|
+
def Insert(self) -> Insert:
|
|
76
|
+
return Insert(self)
|
|
77
|
+
|
|
78
|
+
def Update(self, table: str) -> Update:
|
|
79
|
+
return Update(self, table)
|
|
80
|
+
|
|
81
|
+
def Delete(self) -> Delete:
|
|
82
|
+
return Delete(self)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
connect = Connection
|
|
@@ -2,22 +2,26 @@ import io
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class Select:
|
|
5
|
-
def __init__(self, *args):
|
|
6
|
-
self.__columns = list(args)
|
|
7
|
-
self.__table = None
|
|
8
|
-
self.__wheres = []
|
|
9
|
-
self.__params = []
|
|
10
|
-
self.__joins = []
|
|
11
|
-
self.__orderby = []
|
|
12
|
-
self.__groupby = []
|
|
13
|
-
self.__limit = None
|
|
14
|
-
self.__offset = None
|
|
5
|
+
def __init__(self, *args: str):
|
|
6
|
+
self.__columns: list[str] = list(args)
|
|
7
|
+
self.__table: str | None = None
|
|
8
|
+
self.__wheres: list[str] = []
|
|
9
|
+
self.__params: list = []
|
|
10
|
+
self.__joins: list[str] = []
|
|
11
|
+
self.__orderby: list[tuple[str, str]] = []
|
|
12
|
+
self.__groupby: list[str] = []
|
|
13
|
+
self.__limit: int | None = None
|
|
14
|
+
self.__offset: int | None = None
|
|
15
15
|
|
|
16
|
-
def Columns(self, *columns) -> "Select":
|
|
16
|
+
def Columns(self, *columns: str) -> "Select":
|
|
17
|
+
if not columns:
|
|
18
|
+
raise ValueError("error: specify at least one column")
|
|
17
19
|
self.__columns = list(columns)
|
|
18
20
|
return self
|
|
19
21
|
|
|
20
22
|
def From(self, table: str) -> "Select":
|
|
23
|
+
if not table or not table.strip():
|
|
24
|
+
raise ValueError("error: table name must not be empty")
|
|
21
25
|
self.__table = table
|
|
22
26
|
return self
|
|
23
27
|
|
|
@@ -31,11 +35,13 @@ class Select:
|
|
|
31
35
|
self.__params += args
|
|
32
36
|
return self
|
|
33
37
|
|
|
34
|
-
def OrderBy(self, column: str, direction="ASC") -> "Select":
|
|
38
|
+
def OrderBy(self, column: str, direction: str = "ASC") -> "Select":
|
|
39
|
+
if direction not in ("ASC", "DESC"):
|
|
40
|
+
raise ValueError("error: invalid order by direction")
|
|
35
41
|
self.__orderby.append((column, direction))
|
|
36
42
|
return self
|
|
37
43
|
|
|
38
|
-
def GroupBy(self, *columns) -> "Select":
|
|
44
|
+
def GroupBy(self, *columns: str) -> "Select":
|
|
39
45
|
self.__groupby += columns
|
|
40
46
|
return self
|
|
41
47
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from sqlalchemy import create_engine as _create_engine
|
|
2
|
+
from sqlalchemy import text
|
|
3
|
+
from sqlalchemy.engine import Connection as _Connection
|
|
4
|
+
from sqlalchemy.engine import Engine as _Engine
|
|
5
|
+
|
|
6
|
+
from sqlqb import Select as _Select
|
|
7
|
+
from sqlqb import Insert as _Insert
|
|
8
|
+
from sqlqb import Update as _Update
|
|
9
|
+
from sqlqb import Delete as _Delete
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _bind(sql: str, params: list) -> tuple[str, dict]:
|
|
13
|
+
"""Replace ? placeholders with :p0, :p1, ... for SQLAlchemy text()."""
|
|
14
|
+
named: dict = {}
|
|
15
|
+
for i, val in enumerate(params):
|
|
16
|
+
key = f"p{i}"
|
|
17
|
+
sql = sql.replace("?", f":{key}", 1)
|
|
18
|
+
named[key] = val
|
|
19
|
+
return sql, named
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Select(_Select):
|
|
23
|
+
def __init__(self, connection: _Connection, *args):
|
|
24
|
+
super().__init__(*args)
|
|
25
|
+
self.__connection = connection
|
|
26
|
+
|
|
27
|
+
def execute(self):
|
|
28
|
+
sql, params = _bind(self.sql, self.params)
|
|
29
|
+
return self.__connection.execute(text(sql), params)
|
|
30
|
+
|
|
31
|
+
def fetchone(self):
|
|
32
|
+
sql, params = _bind(self.sql, self.params)
|
|
33
|
+
row = self.execute().mappings().fetchone()
|
|
34
|
+
return dict(row) if row is not None else None
|
|
35
|
+
|
|
36
|
+
def fetchall(self) -> list:
|
|
37
|
+
sql, params = _bind(self.sql, self.params)
|
|
38
|
+
return [dict(row) for row in self.execute().mappings()]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Insert(_Insert):
|
|
42
|
+
def __init__(self, connection: _Connection):
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.__connection = connection
|
|
45
|
+
|
|
46
|
+
def execute(self) -> int:
|
|
47
|
+
sql, params = _bind(self.sql, self.params)
|
|
48
|
+
return self.__connection.execute(text(sql), params).rowcount
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Update(_Update):
|
|
52
|
+
def __init__(self, connection: _Connection, table: str):
|
|
53
|
+
super().__init__(table)
|
|
54
|
+
self.__connection = connection
|
|
55
|
+
|
|
56
|
+
def execute(self) -> int:
|
|
57
|
+
sql, params = _bind(self.sql, self.params)
|
|
58
|
+
return self.__connection.execute(text(sql), params).rowcount
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Delete(_Delete):
|
|
62
|
+
def __init__(self, connection: _Connection):
|
|
63
|
+
super().__init__()
|
|
64
|
+
self.__connection = connection
|
|
65
|
+
|
|
66
|
+
def execute(self) -> int:
|
|
67
|
+
sql, params = _bind(self.sql, self.params)
|
|
68
|
+
return self.__connection.execute(text(sql), params).rowcount
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Connection(_Connection):
|
|
72
|
+
def Select(self, *args) -> Select:
|
|
73
|
+
return Select(self, *args)
|
|
74
|
+
|
|
75
|
+
def Insert(self) -> Insert:
|
|
76
|
+
return Insert(self)
|
|
77
|
+
|
|
78
|
+
def Update(self, table: str) -> Update:
|
|
79
|
+
return Update(self, table)
|
|
80
|
+
|
|
81
|
+
def Delete(self) -> Delete:
|
|
82
|
+
return Delete(self)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Engine(_Engine):
|
|
86
|
+
def connect(self) -> Connection:
|
|
87
|
+
conn = super().connect()
|
|
88
|
+
conn.__class__ = Connection
|
|
89
|
+
return conn
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def create_engine(url: str, **kwargs) -> Engine:
|
|
93
|
+
engine = _create_engine(url, **kwargs)
|
|
94
|
+
engine.__class__ = Engine
|
|
95
|
+
return engine
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from sqlite3 import Connection as _Connection
|
|
2
|
+
from sqlite3 import Cursor as _Cursor
|
|
3
|
+
from sqlite3 import connect as _connect
|
|
4
|
+
|
|
5
|
+
from sqlqb import Select as _Select
|
|
6
|
+
from sqlqb import Insert as _Insert
|
|
7
|
+
from sqlqb import Update as _Update
|
|
8
|
+
from sqlqb import Delete as _Delete
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Select(_Select):
|
|
12
|
+
def __init__(self, connection: _Connection, *args):
|
|
13
|
+
super().__init__(*args)
|
|
14
|
+
self.__connection = connection
|
|
15
|
+
|
|
16
|
+
def execute(self) -> _Cursor:
|
|
17
|
+
return self.__connection.execute(self.sql, self.params)
|
|
18
|
+
|
|
19
|
+
def fetchone(self):
|
|
20
|
+
return self.execute().fetchone()
|
|
21
|
+
|
|
22
|
+
def fetchall(self) -> list:
|
|
23
|
+
return self.execute().fetchall()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Insert(_Insert):
|
|
27
|
+
def __init__(self, connection: _Connection):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.__connection = connection
|
|
30
|
+
|
|
31
|
+
def execute(self) -> int:
|
|
32
|
+
return self.__connection.execute(self.sql, self.params).rowcount
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Update(_Update):
|
|
36
|
+
def __init__(self, connection: _Connection, table: str):
|
|
37
|
+
super().__init__(table)
|
|
38
|
+
self.__connection = connection
|
|
39
|
+
|
|
40
|
+
def execute(self) -> int:
|
|
41
|
+
return self.__connection.execute(self.sql, self.params).rowcount
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Delete(_Delete):
|
|
45
|
+
def __init__(self, connection: _Connection):
|
|
46
|
+
super().__init__()
|
|
47
|
+
self.__connection = connection
|
|
48
|
+
|
|
49
|
+
def execute(self) -> int:
|
|
50
|
+
return self.__connection.execute(self.sql, self.params).rowcount
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Connection(_Connection):
|
|
54
|
+
def __init__(self, *args, **kwargs):
|
|
55
|
+
super().__init__(*args, **kwargs)
|
|
56
|
+
self.row_factory = lambda cursor, row: dict(zip([col[0] for col in cursor.description], row))
|
|
57
|
+
|
|
58
|
+
def Select(self, *args, **kwargs) -> Select:
|
|
59
|
+
return Select(self, *args, **kwargs)
|
|
60
|
+
|
|
61
|
+
def Insert(self) -> Insert:
|
|
62
|
+
return Insert(self)
|
|
63
|
+
|
|
64
|
+
def Update(self, table: str) -> Update:
|
|
65
|
+
return Update(self, table)
|
|
66
|
+
|
|
67
|
+
def Delete(self) -> Delete:
|
|
68
|
+
return Delete(self)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def connect(database, **kwargs) -> Connection:
|
|
72
|
+
return _connect(database, factory=Connection, **kwargs)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import io
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Update:
|
|
5
|
+
def __init__(self, table: str):
|
|
6
|
+
if not table or not table.strip():
|
|
7
|
+
raise ValueError("error: table name must not be empty")
|
|
8
|
+
self.__table = table
|
|
9
|
+
self.__sets: dict[str, object] = {}
|
|
10
|
+
self.__wheres: list[str] = []
|
|
11
|
+
self.__where_params: list = []
|
|
12
|
+
self.__limit: int | None = None
|
|
13
|
+
|
|
14
|
+
def Set(self, **kwargs) -> "Update":
|
|
15
|
+
if not kwargs:
|
|
16
|
+
raise ValueError("error: specify at least one column value")
|
|
17
|
+
self.__sets.update(kwargs)
|
|
18
|
+
return self
|
|
19
|
+
|
|
20
|
+
def Where(self, condition: str, *args) -> "Update":
|
|
21
|
+
self.__wheres.append(condition)
|
|
22
|
+
self.__where_params += args
|
|
23
|
+
return self
|
|
24
|
+
|
|
25
|
+
def Limit(self, limit: int) -> "Update":
|
|
26
|
+
self.__limit = limit
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def params(self) -> list:
|
|
31
|
+
if not self.__sets:
|
|
32
|
+
return []
|
|
33
|
+
return list(self.__sets.values()) + self.__where_params
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def sql(self) -> str:
|
|
37
|
+
if not self.__sets:
|
|
38
|
+
raise ValueError("error: no SET values specified")
|
|
39
|
+
sql = io.StringIO()
|
|
40
|
+
sql.write("UPDATE ")
|
|
41
|
+
sql.write(self.__table)
|
|
42
|
+
sql.write(" SET ")
|
|
43
|
+
sql.write(", ".join([f"{col} = ?" for col in self.__sets]))
|
|
44
|
+
if self.__wheres:
|
|
45
|
+
sql.write(" WHERE ")
|
|
46
|
+
sql.write(" AND ".join(self.__wheres))
|
|
47
|
+
if self.__limit is not None:
|
|
48
|
+
sql.write(f" LIMIT {self.__limit}")
|
|
49
|
+
return sql.getvalue()
|
|
50
|
+
|
|
51
|
+
def __str__(self) -> str:
|
|
52
|
+
return self.sql
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from sqlqb import Delete
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestDeleteBasic:
|
|
7
|
+
def test_delete_all(self):
|
|
8
|
+
q = Delete().From("users")
|
|
9
|
+
assert q.sql == "DELETE FROM users"
|
|
10
|
+
|
|
11
|
+
def test_str_equals_sql(self):
|
|
12
|
+
q = Delete().From("users")
|
|
13
|
+
assert str(q) == q.sql
|
|
14
|
+
|
|
15
|
+
def test_no_table_raises(self):
|
|
16
|
+
with pytest.raises(ValueError):
|
|
17
|
+
Delete().sql
|
|
18
|
+
|
|
19
|
+
def test_empty_table_raises(self):
|
|
20
|
+
with pytest.raises(ValueError):
|
|
21
|
+
Delete().From("")
|
|
22
|
+
|
|
23
|
+
def test_blank_table_raises(self):
|
|
24
|
+
with pytest.raises(ValueError):
|
|
25
|
+
Delete().From(" ")
|
|
26
|
+
|
|
27
|
+
def test_params_empty_by_default(self):
|
|
28
|
+
q = Delete().From("users")
|
|
29
|
+
assert q.params == []
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestDeleteWhere:
|
|
33
|
+
def test_single_where(self):
|
|
34
|
+
q = Delete().From("users").Where("id = ?", 1)
|
|
35
|
+
assert q.sql == "DELETE FROM users WHERE id = ?"
|
|
36
|
+
assert q.params == [1]
|
|
37
|
+
|
|
38
|
+
def test_multiple_wheres_joined_with_and(self):
|
|
39
|
+
q = Delete().From("users").Where("active = ?", False).Where("role = ?", "guest")
|
|
40
|
+
assert q.sql == "DELETE FROM users WHERE active = ? AND role = ?"
|
|
41
|
+
assert q.params == [False, "guest"]
|
|
42
|
+
|
|
43
|
+
def test_where_no_params(self):
|
|
44
|
+
q = Delete().From("sessions").Where("expired_at IS NOT NULL")
|
|
45
|
+
assert q.sql == "DELETE FROM sessions WHERE expired_at IS NOT NULL"
|
|
46
|
+
assert q.params == []
|
|
47
|
+
|
|
48
|
+
def test_where_multiple_params(self):
|
|
49
|
+
q = Delete().From("t").Where("a = ? AND b = ?", 1, 2)
|
|
50
|
+
assert q.params == [1, 2]
|
|
51
|
+
|
|
52
|
+
def test_no_where_absent(self):
|
|
53
|
+
q = Delete().From("users")
|
|
54
|
+
assert "WHERE" not in q.sql
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TestDeleteLimit:
|
|
58
|
+
def test_limit(self):
|
|
59
|
+
q = Delete().From("logs").Limit(100)
|
|
60
|
+
assert "LIMIT 100" in q.sql
|
|
61
|
+
|
|
62
|
+
def test_no_order_by_absent(self):
|
|
63
|
+
q = Delete().From("users")
|
|
64
|
+
assert "ORDER BY" not in q.sql
|
|
65
|
+
|
|
66
|
+
def test_no_limit_absent(self):
|
|
67
|
+
q = Delete().From("users")
|
|
68
|
+
assert "LIMIT" not in q.sql
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TestDeleteChaining:
|
|
72
|
+
def test_method_chaining_returns_delete(self):
|
|
73
|
+
q = Delete()
|
|
74
|
+
assert q.From("t") is q
|
|
75
|
+
assert q.Where("1=1") is q
|
|
76
|
+
assert q.Limit(5) is q
|
|
77
|
+
|
|
78
|
+
def test_full_query(self):
|
|
79
|
+
q = Delete().From("logs").Where("level = ?", "debug").Limit(500)
|
|
80
|
+
assert q.sql == "DELETE FROM logs WHERE level = ? LIMIT 500"
|
|
81
|
+
assert q.params == ["debug"]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from sqlqb import Insert
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestInsertBasic:
|
|
7
|
+
def test_single_row(self):
|
|
8
|
+
q = Insert().Into("users").Values(name="Alice", email="alice@example.com")
|
|
9
|
+
assert q.sql == "INSERT INTO users (name, email) VALUES (?, ?)"
|
|
10
|
+
|
|
11
|
+
def test_str_equals_sql(self):
|
|
12
|
+
q = Insert().Into("users").Values(name="Alice")
|
|
13
|
+
assert str(q) == q.sql
|
|
14
|
+
|
|
15
|
+
def test_params_single_row(self):
|
|
16
|
+
q = Insert().Into("users").Values(name="Alice", email="alice@example.com")
|
|
17
|
+
assert q.params == ["Alice", "alice@example.com"]
|
|
18
|
+
|
|
19
|
+
def test_multiple_rows(self):
|
|
20
|
+
q = Insert().Into("users").Values(name="Alice").Values(name="Bob")
|
|
21
|
+
assert q.sql == "INSERT INTO users (name) VALUES (?), (?)"
|
|
22
|
+
assert q.params == ["Alice", "Bob"]
|
|
23
|
+
|
|
24
|
+
def test_multiple_columns_multiple_rows(self):
|
|
25
|
+
q = Insert().Into("orders").Values(user_id=1, total=99).Values(user_id=2, total=50)
|
|
26
|
+
assert q.sql == "INSERT INTO orders (user_id, total) VALUES (?, ?), (?, ?)"
|
|
27
|
+
assert q.params == [1, 99, 2, 50]
|
|
28
|
+
|
|
29
|
+
def test_method_chaining_returns_insert(self):
|
|
30
|
+
q = Insert()
|
|
31
|
+
assert q.Into("t") is q
|
|
32
|
+
assert q.Values(x=1) is q
|
|
33
|
+
|
|
34
|
+
def test_no_table_raises(self):
|
|
35
|
+
with pytest.raises(ValueError):
|
|
36
|
+
Insert().Values(name="Alice").sql
|
|
37
|
+
|
|
38
|
+
def test_no_values_raises(self):
|
|
39
|
+
with pytest.raises(ValueError):
|
|
40
|
+
Insert().Into("users").sql
|
|
41
|
+
|
|
42
|
+
def test_empty_table_raises(self):
|
|
43
|
+
with pytest.raises(ValueError):
|
|
44
|
+
Insert().Into("")
|
|
45
|
+
|
|
46
|
+
def test_blank_table_raises(self):
|
|
47
|
+
with pytest.raises(ValueError):
|
|
48
|
+
Insert().Into(" ")
|
|
49
|
+
|
|
50
|
+
def test_empty_values_raises(self):
|
|
51
|
+
with pytest.raises(ValueError):
|
|
52
|
+
Insert().Into("users").Values()
|
|
53
|
+
|
|
54
|
+
def test_params_empty_before_values(self):
|
|
55
|
+
q = Insert().Into("users")
|
|
56
|
+
assert q.params == []
|
|
57
|
+
|
|
58
|
+
def test_various_value_types(self):
|
|
59
|
+
q = Insert().Into("events").Values(user_id=42, active=True, score=3.14, label=None)
|
|
60
|
+
assert q.params == [42, True, 3.14, None]
|