Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added fetch method, changed parameters annotations #38

Merged
merged 2 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading