Skip to content

Commit 891d975

Browse files
authored
Merge pull request #38 from qaspen-python/feature/fetch_method
Added fetch method, changed parameters annotations
2 parents f4f1d8d + 63ce723 commit 891d975

File tree

8 files changed

+259
-11
lines changed

8 files changed

+259
-11
lines changed

python/psqlpy/_internal/__init__.pyi

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import types
22
from enum import Enum
3-
from typing import Any, Callable, List, Optional, TypeVar
3+
from typing import Any, Callable, List, Optional, Sequence, TypeVar
44

55
from typing_extensions import Self
66

@@ -337,7 +337,7 @@ class Transaction:
337337
async def execute(
338338
self: Self,
339339
querystring: str,
340-
parameters: list[Any] | None = None,
340+
parameters: Sequence[Any] | None = None,
341341
prepared: bool = True,
342342
) -> QueryResult:
343343
"""Execute the query.
@@ -375,7 +375,7 @@ class Transaction:
375375
async def execute_many(
376376
self: Self,
377377
querystring: str,
378-
parameters: list[list[Any]] | None = None,
378+
parameters: Sequence[Sequence[Any]] | None = None,
379379
prepared: bool = True,
380380
) -> None: ...
381381
"""Execute query multiple times with different parameters.
@@ -410,10 +410,30 @@ class Transaction:
410410
await transaction.commit()
411411
```
412412
"""
413+
async def fetch(
414+
self: Self,
415+
querystring: str,
416+
parameters: Sequence[Any] | None = None,
417+
prepared: bool = True,
418+
) -> QueryResult:
419+
"""Fetch the result from database.
420+
421+
It's the same as `execute` method, we made it because people are used
422+
to `fetch` method name.
423+
424+
Querystring can contain `$<number>` parameters
425+
for converting them in the driver side.
426+
427+
### Parameters:
428+
- `querystring`: querystring to execute.
429+
- `parameters`: list of parameters to pass in the query.
430+
- `prepared`: should the querystring be prepared before the request.
431+
By default any querystring will be prepared.
432+
"""
413433
async def fetch_row(
414434
self: Self,
415435
querystring: str,
416-
parameters: list[Any] | None = None,
436+
parameters: Sequence[Any] | None = None,
417437
prepared: bool = True,
418438
) -> SingleQueryResult:
419439
"""Fetch exaclty single row from query.
@@ -453,7 +473,7 @@ class Transaction:
453473
async def fetch_val(
454474
self: Self,
455475
querystring: str,
456-
parameters: list[Any] | None = None,
476+
parameters: Sequence[Any] | None = None,
457477
prepared: bool = True,
458478
) -> Any | None:
459479
"""Execute the query and return first value of the first row.
@@ -661,7 +681,7 @@ class Transaction:
661681
def cursor(
662682
self: Self,
663683
querystring: str,
664-
parameters: list[Any] | None = None,
684+
parameters: Sequence[Any] | None = None,
665685
fetch_number: int | None = None,
666686
scroll: bool | None = None,
667687
prepared: bool = True,
@@ -717,7 +737,7 @@ class Connection:
717737
async def execute(
718738
self: Self,
719739
querystring: str,
720-
parameters: list[Any] | None = None,
740+
parameters: Sequence[Any] | None = None,
721741
prepared: bool = True,
722742
) -> QueryResult:
723743
"""Execute the query.
@@ -784,10 +804,30 @@ class Connection:
784804
)
785805
```
786806
"""
807+
async def fetch(
808+
self: Self,
809+
querystring: str,
810+
parameters: Sequence[Any] | None = None,
811+
prepared: bool = True,
812+
) -> QueryResult:
813+
"""Fetch the result from database.
814+
815+
It's the same as `execute` method, we made it because people are used
816+
to `fetch` method name.
817+
818+
Querystring can contain `$<number>` parameters
819+
for converting them in the driver side.
820+
821+
### Parameters:
822+
- `querystring`: querystring to execute.
823+
- `parameters`: list of parameters to pass in the query.
824+
- `prepared`: should the querystring be prepared before the request.
825+
By default any querystring will be prepared.
826+
"""
787827
async def fetch_row(
788828
self: Self,
789829
querystring: str,
790-
parameters: list[Any] | None = None,
830+
parameters: Sequence[Any] | None = None,
791831
prepared: bool = True,
792832
) -> SingleQueryResult:
793833
"""Fetch exaclty single row from query.
@@ -824,7 +864,7 @@ class Connection:
824864
async def fetch_val(
825865
self: Self,
826866
querystring: str,
827-
parameters: list[Any] | None = None,
867+
parameters: Sequence[Any] | None = None,
828868
prepared: bool = True,
829869
) -> Any:
830870
"""Execute the query and return first value of the first row.
@@ -979,7 +1019,7 @@ class ConnectionPool:
9791019
async def execute(
9801020
self: Self,
9811021
querystring: str,
982-
parameters: list[Any] | None = None,
1022+
parameters: Sequence[Any] | None = None,
9831023
prepared: bool = True,
9841024
) -> QueryResult:
9851025
"""Execute the query.
@@ -1011,6 +1051,26 @@ class ConnectionPool:
10111051
# it will be dropped on Rust side.
10121052
```
10131053
"""
1054+
async def fetch(
1055+
self: Self,
1056+
querystring: str,
1057+
parameters: Sequence[Any] | None = None,
1058+
prepared: bool = True,
1059+
) -> QueryResult:
1060+
"""Fetch the result from database.
1061+
1062+
It's the same as `execute` method, we made it because people are used
1063+
to `fetch` method name.
1064+
1065+
Querystring can contain `$<number>` parameters
1066+
for converting them in the driver side.
1067+
1068+
### Parameters:
1069+
- `querystring`: querystring to execute.
1070+
- `parameters`: list of parameters to pass in the query.
1071+
- `prepared`: should the querystring be prepared before the request.
1072+
By default any querystring will be prepared.
1073+
"""
10141074
async def connection(self: Self) -> Connection:
10151075
"""Create new connection.
10161076

python/tests/test_connection.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@ async def test_connection_execute(
2626
assert len(conn_result.result()) == number_database_records
2727

2828

29+
async def test_connection_fetch(
30+
psql_pool: ConnectionPool,
31+
table_name: str,
32+
number_database_records: int,
33+
) -> None:
34+
"""Test that single connection can fetch queries."""
35+
connection = await psql_pool.connection()
36+
37+
conn_result = await connection.fetch(
38+
querystring=f"SELECT * FROM {table_name}",
39+
)
40+
assert isinstance(conn_result, QueryResult)
41+
assert len(conn_result.result()) == number_database_records
42+
43+
2944
async def test_connection_connection(
3045
psql_pool: ConnectionPool,
3146
) -> None:

python/tests/test_connection_pool.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,23 @@ async def test_pool_execute(
4949
assert len(inner_result) == number_database_records
5050

5151

52+
async def test_pool_fetch(
53+
psql_pool: ConnectionPool,
54+
table_name: str,
55+
number_database_records: int,
56+
) -> None:
57+
"""Test that ConnectionPool can fetch queries."""
58+
select_result = await psql_pool.fetch(
59+
f"SELECT * FROM {table_name}",
60+
)
61+
62+
assert type(select_result) == QueryResult
63+
64+
inner_result = select_result.result()
65+
assert isinstance(inner_result, list)
66+
assert len(inner_result) == number_database_records
67+
68+
5269
async def test_pool_connection(
5370
psql_pool: ConnectionPool,
5471
) -> None:

python/tests/test_transaction.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,21 @@ async def test_transaction_cursor(
205205
assert isinstance(cursor, Cursor)
206206

207207

208+
async def test_transaction_fetch(
209+
psql_pool: ConnectionPool,
210+
table_name: str,
211+
number_database_records: int,
212+
) -> None:
213+
"""Test that single connection can fetch queries."""
214+
connection = await psql_pool.connection()
215+
216+
async with connection.transaction() as transaction:
217+
conn_result = await transaction.fetch(
218+
querystring=f"SELECT * FROM {table_name}",
219+
)
220+
assert len(conn_result.result()) == number_database_records
221+
222+
208223
@pytest.mark.parametrize(
209224
("insert_values"),
210225
[

src/driver/connection.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,55 @@ impl Connection {
152152
Ok(())
153153
}
154154

155+
/// Fetch result from the database.
156+
///
157+
/// # Errors
158+
///
159+
/// May return Err Result if
160+
/// 1) Cannot convert incoming parameters
161+
/// 2) Cannot prepare statement
162+
/// 3) Cannot execute query
163+
pub async fn fetch(
164+
self_: pyo3::Py<Self>,
165+
querystring: String,
166+
parameters: Option<pyo3::Py<PyAny>>,
167+
prepared: Option<bool>,
168+
) -> RustPSQLDriverPyResult<PSQLDriverPyQueryResult> {
169+
let db_client = pyo3::Python::with_gil(|gil| self_.borrow(gil).db_client.clone());
170+
171+
let mut params: Vec<PythonDTO> = vec![];
172+
if let Some(parameters) = parameters {
173+
params = convert_parameters(parameters)?;
174+
}
175+
let prepared = prepared.unwrap_or(true);
176+
177+
let result = if prepared {
178+
db_client
179+
.query(
180+
&db_client.prepare_cached(&querystring).await?,
181+
&params
182+
.iter()
183+
.map(|param| param as &QueryParameter)
184+
.collect::<Vec<&QueryParameter>>()
185+
.into_boxed_slice(),
186+
)
187+
.await?
188+
} else {
189+
db_client
190+
.query(
191+
&querystring,
192+
&params
193+
.iter()
194+
.map(|param| param as &QueryParameter)
195+
.collect::<Vec<&QueryParameter>>()
196+
.into_boxed_slice(),
197+
)
198+
.await?
199+
};
200+
201+
Ok(PSQLDriverPyQueryResult::new(result))
202+
}
203+
155204
/// Fetch exaclty single row from query.
156205
///
157206
/// Method doesn't acquire lock on any structure fields.

src/driver/connection_pool.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,70 @@ impl ConnectionPool {
230230
Ok(PSQLDriverPyQueryResult::new(result))
231231
}
232232

233+
/// Fetch result from the database.
234+
///
235+
/// It's the same as `execute`, we made it for people who prefer
236+
/// `fetch()`.
237+
///
238+
/// Prepare statement and cache it, then execute.
239+
///
240+
/// # Errors
241+
/// May return Err Result if cannot retrieve new connection
242+
/// or prepare statement or execute statement.
243+
pub async fn fetch<'a>(
244+
self_: pyo3::Py<Self>,
245+
querystring: String,
246+
prepared: Option<bool>,
247+
parameters: Option<pyo3::Py<PyAny>>,
248+
) -> RustPSQLDriverPyResult<PSQLDriverPyQueryResult> {
249+
let db_pool = pyo3::Python::with_gil(|gil| self_.borrow(gil).0.clone());
250+
251+
let db_pool_manager = tokio_runtime()
252+
.spawn(async move { Ok::<Object, RustPSQLDriverError>(db_pool.get().await?) })
253+
.await??;
254+
let mut params: Vec<PythonDTO> = vec![];
255+
if let Some(parameters) = parameters {
256+
params = convert_parameters(parameters)?;
257+
}
258+
let prepared = prepared.unwrap_or(true);
259+
let result = if prepared {
260+
tokio_runtime()
261+
.spawn(async move {
262+
Ok::<Vec<Row>, RustPSQLDriverError>(
263+
db_pool_manager
264+
.query(
265+
&db_pool_manager.prepare_cached(&querystring).await?,
266+
&params
267+
.iter()
268+
.map(|param| param as &QueryParameter)
269+
.collect::<Vec<&QueryParameter>>()
270+
.into_boxed_slice(),
271+
)
272+
.await?,
273+
)
274+
})
275+
.await??
276+
} else {
277+
tokio_runtime()
278+
.spawn(async move {
279+
Ok::<Vec<Row>, RustPSQLDriverError>(
280+
db_pool_manager
281+
.query(
282+
&querystring,
283+
&params
284+
.iter()
285+
.map(|param| param as &QueryParameter)
286+
.collect::<Vec<&QueryParameter>>()
287+
.into_boxed_slice(),
288+
)
289+
.await?,
290+
)
291+
})
292+
.await??
293+
};
294+
Ok(PSQLDriverPyQueryResult::new(result))
295+
}
296+
233297
/// Return new single connection.
234298
///
235299
/// # Errors

src/driver/transaction.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,33 @@ impl Transaction {
221221
.psqlpy_query(querystring, parameters, prepared)
222222
.await
223223
}
224+
225+
/// Fetch result from the database.
226+
///
227+
/// It converts incoming parameters to rust readable
228+
/// and then execute the query with them.
229+
///
230+
/// # Errors
231+
///
232+
/// May return Err Result if:
233+
/// 1) Cannot convert python parameters
234+
/// 2) Cannot execute querystring.
235+
pub async fn fetch(
236+
self_: Py<Self>,
237+
querystring: String,
238+
parameters: Option<pyo3::Py<PyAny>>,
239+
prepared: Option<bool>,
240+
) -> RustPSQLDriverPyResult<PSQLDriverPyQueryResult> {
241+
let (is_transaction_ready, db_client) = pyo3::Python::with_gil(|gil| {
242+
let self_ = self_.borrow(gil);
243+
(self_.check_is_transaction_ready(), self_.db_client.clone())
244+
});
245+
is_transaction_ready?;
246+
db_client
247+
.psqlpy_query(querystring, parameters, prepared)
248+
.await
249+
}
250+
224251
/// Fetch exaclty single row from query.
225252
///
226253
/// Method doesn't acquire lock on any structure fields.

0 commit comments

Comments
 (0)