Skip to content

Commit

Permalink
Merge pull request #38 from qaspen-python/feature/fetch_method
Browse files Browse the repository at this point in the history
Added fetch method, changed parameters annotations
  • Loading branch information
chandr-andr authored May 3, 2024
2 parents f4f1d8d + 63ce723 commit 891d975
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 11 deletions.
80 changes: 70 additions & 10 deletions python/psqlpy/_internal/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import types
from enum import Enum
from typing import Any, Callable, List, Optional, TypeVar
from typing import Any, Callable, List, Optional, Sequence, TypeVar

from typing_extensions import Self

Expand Down Expand Up @@ -337,7 +337,7 @@ class Transaction:
async def execute(
self: Self,
querystring: str,
parameters: list[Any] | None = None,
parameters: Sequence[Any] | None = None,
prepared: bool = True,
) -> QueryResult:
"""Execute the query.
Expand Down Expand Up @@ -375,7 +375,7 @@ class Transaction:
async def execute_many(
self: Self,
querystring: str,
parameters: list[list[Any]] | None = None,
parameters: Sequence[Sequence[Any]] | None = None,
prepared: bool = True,
) -> None: ...
"""Execute query multiple times with different parameters.
Expand Down Expand Up @@ -410,10 +410,30 @@ class Transaction:
await transaction.commit()
```
"""
async def fetch(
self: Self,
querystring: str,
parameters: Sequence[Any] | None = None,
prepared: bool = True,
) -> QueryResult:
"""Fetch the result from database.
It's the same as `execute` method, we made it because people are used
to `fetch` method name.
Querystring can contain `$<number>` parameters
for converting them in the driver side.
### Parameters:
- `querystring`: querystring to execute.
- `parameters`: list of parameters to pass in the query.
- `prepared`: should the querystring be prepared before the request.
By default any querystring will be prepared.
"""
async def fetch_row(
self: Self,
querystring: str,
parameters: list[Any] | None = None,
parameters: Sequence[Any] | None = None,
prepared: bool = True,
) -> SingleQueryResult:
"""Fetch exaclty single row from query.
Expand Down Expand Up @@ -453,7 +473,7 @@ class Transaction:
async def fetch_val(
self: Self,
querystring: str,
parameters: list[Any] | None = None,
parameters: Sequence[Any] | None = None,
prepared: bool = True,
) -> Any | None:
"""Execute the query and return first value of the first row.
Expand Down Expand Up @@ -661,7 +681,7 @@ class Transaction:
def cursor(
self: Self,
querystring: str,
parameters: list[Any] | None = None,
parameters: Sequence[Any] | None = None,
fetch_number: int | None = None,
scroll: bool | None = None,
prepared: bool = True,
Expand Down Expand Up @@ -717,7 +737,7 @@ class Connection:
async def execute(
self: Self,
querystring: str,
parameters: list[Any] | None = None,
parameters: Sequence[Any] | None = None,
prepared: bool = True,
) -> QueryResult:
"""Execute the query.
Expand Down Expand Up @@ -784,10 +804,30 @@ class Connection:
)
```
"""
async def fetch(
self: Self,
querystring: str,
parameters: Sequence[Any] | None = None,
prepared: bool = True,
) -> QueryResult:
"""Fetch the result from database.
It's the same as `execute` method, we made it because people are used
to `fetch` method name.
Querystring can contain `$<number>` parameters
for converting them in the driver side.
### Parameters:
- `querystring`: querystring to execute.
- `parameters`: list of parameters to pass in the query.
- `prepared`: should the querystring be prepared before the request.
By default any querystring will be prepared.
"""
async def fetch_row(
self: Self,
querystring: str,
parameters: list[Any] | None = None,
parameters: Sequence[Any] | None = None,
prepared: bool = True,
) -> SingleQueryResult:
"""Fetch exaclty single row from query.
Expand Down Expand Up @@ -824,7 +864,7 @@ class Connection:
async def fetch_val(
self: Self,
querystring: str,
parameters: list[Any] | None = None,
parameters: Sequence[Any] | None = None,
prepared: bool = True,
) -> Any:
"""Execute the query and return first value of the first row.
Expand Down Expand Up @@ -979,7 +1019,7 @@ class ConnectionPool:
async def execute(
self: Self,
querystring: str,
parameters: list[Any] | None = None,
parameters: Sequence[Any] | None = None,
prepared: bool = True,
) -> QueryResult:
"""Execute the query.
Expand Down Expand Up @@ -1011,6 +1051,26 @@ class ConnectionPool:
# it will be dropped on Rust side.
```
"""
async def fetch(
self: Self,
querystring: str,
parameters: Sequence[Any] | None = None,
prepared: bool = True,
) -> QueryResult:
"""Fetch the result from database.
It's the same as `execute` method, we made it because people are used
to `fetch` method name.
Querystring can contain `$<number>` parameters
for converting them in the driver side.
### Parameters:
- `querystring`: querystring to execute.
- `parameters`: list of parameters to pass in the query.
- `prepared`: should the querystring be prepared before the request.
By default any querystring will be prepared.
"""
async def connection(self: Self) -> Connection:
"""Create new connection.
Expand Down
15 changes: 15 additions & 0 deletions python/tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ async def test_connection_execute(
assert len(conn_result.result()) == number_database_records


async def test_connection_fetch(
psql_pool: ConnectionPool,
table_name: str,
number_database_records: int,
) -> None:
"""Test that single connection can fetch queries."""
connection = await psql_pool.connection()

conn_result = await connection.fetch(
querystring=f"SELECT * FROM {table_name}",
)
assert isinstance(conn_result, QueryResult)
assert len(conn_result.result()) == number_database_records


async def test_connection_connection(
psql_pool: ConnectionPool,
) -> None:
Expand Down
17 changes: 17 additions & 0 deletions python/tests/test_connection_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ async def test_pool_execute(
assert len(inner_result) == number_database_records


async def test_pool_fetch(
psql_pool: ConnectionPool,
table_name: str,
number_database_records: int,
) -> None:
"""Test that ConnectionPool can fetch queries."""
select_result = await psql_pool.fetch(
f"SELECT * FROM {table_name}",
)

assert type(select_result) == QueryResult

inner_result = select_result.result()
assert isinstance(inner_result, list)
assert len(inner_result) == number_database_records


async def test_pool_connection(
psql_pool: ConnectionPool,
) -> None:
Expand Down
15 changes: 15 additions & 0 deletions python/tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,21 @@ async def test_transaction_cursor(
assert isinstance(cursor, Cursor)


async def test_transaction_fetch(
psql_pool: ConnectionPool,
table_name: str,
number_database_records: int,
) -> None:
"""Test that single connection can fetch queries."""
connection = await psql_pool.connection()

async with connection.transaction() as transaction:
conn_result = await transaction.fetch(
querystring=f"SELECT * FROM {table_name}",
)
assert len(conn_result.result()) == number_database_records


@pytest.mark.parametrize(
("insert_values"),
[
Expand Down
49 changes: 49 additions & 0 deletions src/driver/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,55 @@ impl Connection {
Ok(())
}

/// Fetch result from the database.
///
/// # Errors
///
/// May return Err Result if
/// 1) Cannot convert incoming parameters
/// 2) Cannot prepare statement
/// 3) Cannot execute query
pub async fn fetch(
self_: pyo3::Py<Self>,
querystring: String,
parameters: Option<pyo3::Py<PyAny>>,
prepared: Option<bool>,
) -> RustPSQLDriverPyResult<PSQLDriverPyQueryResult> {
let db_client = pyo3::Python::with_gil(|gil| self_.borrow(gil).db_client.clone());

let mut params: Vec<PythonDTO> = vec![];
if let Some(parameters) = parameters {
params = convert_parameters(parameters)?;
}
let prepared = prepared.unwrap_or(true);

let result = if prepared {
db_client
.query(
&db_client.prepare_cached(&querystring).await?,
&params
.iter()
.map(|param| param as &QueryParameter)
.collect::<Vec<&QueryParameter>>()
.into_boxed_slice(),
)
.await?
} else {
db_client
.query(
&querystring,
&params
.iter()
.map(|param| param as &QueryParameter)
.collect::<Vec<&QueryParameter>>()
.into_boxed_slice(),
)
.await?
};

Ok(PSQLDriverPyQueryResult::new(result))
}

/// Fetch exaclty single row from query.
///
/// Method doesn't acquire lock on any structure fields.
Expand Down
64 changes: 64 additions & 0 deletions src/driver/connection_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,70 @@ impl ConnectionPool {
Ok(PSQLDriverPyQueryResult::new(result))
}

/// Fetch result from the database.
///
/// It's the same as `execute`, we made it for people who prefer
/// `fetch()`.
///
/// Prepare statement and cache it, then execute.
///
/// # Errors
/// May return Err Result if cannot retrieve new connection
/// or prepare statement or execute statement.
pub async fn fetch<'a>(
self_: pyo3::Py<Self>,
querystring: String,
prepared: Option<bool>,
parameters: Option<pyo3::Py<PyAny>>,
) -> RustPSQLDriverPyResult<PSQLDriverPyQueryResult> {
let db_pool = pyo3::Python::with_gil(|gil| self_.borrow(gil).0.clone());

let db_pool_manager = tokio_runtime()
.spawn(async move { Ok::<Object, RustPSQLDriverError>(db_pool.get().await?) })
.await??;
let mut params: Vec<PythonDTO> = vec![];
if let Some(parameters) = parameters {
params = convert_parameters(parameters)?;
}
let prepared = prepared.unwrap_or(true);
let result = if prepared {
tokio_runtime()
.spawn(async move {
Ok::<Vec<Row>, RustPSQLDriverError>(
db_pool_manager
.query(
&db_pool_manager.prepare_cached(&querystring).await?,
&params
.iter()
.map(|param| param as &QueryParameter)
.collect::<Vec<&QueryParameter>>()
.into_boxed_slice(),
)
.await?,
)
})
.await??
} else {
tokio_runtime()
.spawn(async move {
Ok::<Vec<Row>, RustPSQLDriverError>(
db_pool_manager
.query(
&querystring,
&params
.iter()
.map(|param| param as &QueryParameter)
.collect::<Vec<&QueryParameter>>()
.into_boxed_slice(),
)
.await?,
)
})
.await??
};
Ok(PSQLDriverPyQueryResult::new(result))
}

/// Return new single connection.
///
/// # Errors
Expand Down
27 changes: 27 additions & 0 deletions src/driver/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,33 @@ impl Transaction {
.psqlpy_query(querystring, parameters, prepared)
.await
}

/// Fetch result from the database.
///
/// It converts incoming parameters to rust readable
/// and then execute the query with them.
///
/// # Errors
///
/// May return Err Result if:
/// 1) Cannot convert python parameters
/// 2) Cannot execute querystring.
pub async fn fetch(
self_: Py<Self>,
querystring: String,
parameters: Option<pyo3::Py<PyAny>>,
prepared: Option<bool>,
) -> RustPSQLDriverPyResult<PSQLDriverPyQueryResult> {
let (is_transaction_ready, db_client) = pyo3::Python::with_gil(|gil| {
let self_ = self_.borrow(gil);
(self_.check_is_transaction_ready(), self_.db_client.clone())
});
is_transaction_ready?;
db_client
.psqlpy_query(querystring, parameters, prepared)
.await
}

/// Fetch exaclty single row from query.
///
/// Method doesn't acquire lock on any structure fields.
Expand Down
Loading

0 comments on commit 891d975

Please sign in to comment.