// Copyright (C) 2024 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Database interface for relational database operations.
//!
//! This module provides high-level abstractions for working with relational
//! databases, including opening database connections, executing queries, and
//! handling results.
use std::pin::Pin;
use cxx::SharedPtr;
use crate::config::OpenConfig;
use crate::params::{FromSql, Params};
use crate::wrapper::ffi::{self, Execute, ExecuteSql, NewRowEntity, Query};
use crate::wrapper::open_rdb_store;
/// Success error code constant.
const E_OK: i32 = 0;
/// Database connection and operation interface.
///
/// Provides methods for executing SQL statements and queries on a relational
/// database. Wraps the underlying FFI implementation with safe, idiomatic Rust.
pub struct RdbStore<'a> {
/// Internal representation of the database store
inner: RdbStoreInner<'a>,
}
impl<'a> RdbStore<'a> {
/// Opens a database connection using the provided configuration.
///
/// # Arguments
///
/// * `config` - Configuration options for opening the database
///
/// # Returns
///
/// Returns `Ok` with a new `RdbStore` instance on success, or `Err` with an
/// error code on failure
pub fn open(config: OpenConfig) -> Result<Self, i32> {
let rdb = open_rdb_store(config)?;
if rdb.is_null() {
return Err(-1);
}
Ok(Self {
inner: RdbStoreInner::Shared(rdb),
})
}
/// Creates a `RdbStore` from an FFI reference.
///
/// Used internally to wrap FFI pointers with a safe Rust interface.
///
/// # Arguments
///
/// * `ffi` - FFI pointer to an existing database store
pub fn from_ffi(ffi: Pin<&'a mut ffi::RdbStore>) -> Self {
Self {
inner: RdbStoreInner::Ref(ffi),
}
}
/// Executes an SQL statement with optional parameters.
///
/// Use for statements that modify the database like INSERT, UPDATE, DELETE,
/// etc.
///
/// # Arguments
///
/// * `sql` - The SQL statement to execute
/// * `values` - Parameters to bind to the statement
///
/// # Returns
///
/// Returns `Ok(())` on success, or `Err` with an error code on failure
pub fn execute<P: Params>(&self, sql: &str, values: P) -> Result<(), i32> {
match Execute(self.inner.pin_mut(), sql, values.into_values_object()) {
0 => Ok(()),
err => Err(err),
}
}
/// Executes an SQL statement via `ExecuteSql`, which bypasses `Execute`'s column-count
/// check. Use this for statements `Execute` rejects, such as multi-column PRAGMAs
/// (e.g. `PRAGMA wal_checkpoint(RESTART)`). Result rows are not returned.
pub fn execute_sql(&self, sql: &str) -> Result<(), i32> {
match ExecuteSql(self.inner.pin_mut(), sql) {
0 => Ok(()),
err => Err(err),
}
}
/// Executes an SQL query and returns results as a typed iterator.
///
/// The return type `T` must implement the `FromSql` trait to convert from
/// database rows.
///
/// # Arguments
///
/// * `sql` - The SQL query statement
/// * `values` - Parameters to bind to the query
///
/// # Returns
///
/// Returns `Ok` with a `QuerySet` iterator on success, or `Err` with an
/// error code on failure
///
/// # Safety
///
/// This method uses unsafe code to handle FFI pointers to the underlying
/// result set.
pub fn query<T>(&self, sql: &str, values: impl Params) -> Result<QuerySet<T>, i32> {
let result = Query(self.inner.pin_mut(), sql, values.into_values_object());
if result.is_null() {
return Err(-1);
}
let ptr = result.as_ref().unwrap() as *const ffi::ResultSet as *mut ffi::ResultSet;
let mut column_count = 0;
match unsafe { Pin::new_unchecked(ptr.as_mut().unwrap()).GetColumnCount(&mut column_count) }
{
0 => {}
err => return Err(err),
};
Ok(QuerySet {
inner: result,
column_count,
phantom: std::marker::PhantomData,
})
}
}
/// Internal representation of a database store.
///
/// Provides a unified interface for different ownership models of the
/// underlying FFI store.
enum RdbStoreInner<'a> {
/// Shared ownership model using a reference-counted pointer
Shared(SharedPtr<ffi::RdbStore>),
/// Borrowed reference model using a pinned mutable reference
Ref(Pin<&'a mut ffi::RdbStore>),
}
impl RdbStoreInner<'_> {
/// Converts the inner store into a pinned mutable reference.
///
/// # Safety
///
/// This method uses unsafe code to convert between pointer types and create
/// mutable references.
fn pin_mut(&self) -> Pin<&mut ffi::RdbStore> {
match self {
Self::Shared(ffi) => {
let ptr = ffi.as_ref().unwrap() as *const ffi::RdbStore as *mut ffi::RdbStore;
unsafe { Pin::new_unchecked(ptr.as_mut().unwrap()) }
}
Self::Ref(ffi) => {
let ptr = ffi.as_ref().get_ref() as *const ffi::RdbStore as *mut ffi::RdbStore;
unsafe { Pin::new_unchecked(ptr.as_mut().unwrap()) }
}
}
}
}
/// Iterator over database query results.
///
/// Provides methods to access metadata and iterate over rows, converting each
/// row to type `T`.
///
/// # Type Parameters
///
/// * `T` - The target type that each row will be converted to
pub struct QuerySet<T> {
/// Internal FFI result set pointer
inner: SharedPtr<ffi::ResultSet>,
/// Number of columns in the result set
column_count: i32,
/// Type marker for the target row conversion type
phantom: std::marker::PhantomData<T>,
}
impl<T> QuerySet<T> {
/// Gets the number of rows in the query result.
///
/// Returns 0 if an error occurs while retrieving the row count.
pub fn row_count(&mut self) -> i32 {
let mut row_count = 0;
match self.pin_mut().GetRowCount(&mut row_count) {
0 => row_count,
_err => 0,
}
}
/// Gets the number of columns in the query result.
pub fn column_count(&self) -> i32 {
self.column_count
}
/// Converts the inner result set into a pinned mutable reference.
///
/// # Safety
///
/// This method uses unsafe code to convert between pointer types and create
/// mutable references.
fn pin_mut(&mut self) -> Pin<&mut ffi::ResultSet> {
let ptr = self.inner.as_ref().unwrap() as *const ffi::ResultSet as *mut ffi::ResultSet;
unsafe { Pin::new_unchecked(ptr.as_mut().unwrap()) }
}
}
impl<T> Iterator for QuerySet<T>
where
T: FromSql,
{
type Item = T;
/// Advances to the next row and converts it to type `T`.
///
/// Returns `None` when there are no more rows or when an error occurs.
fn next(&mut self) -> Option<Self::Item> {
let mut row = NewRowEntity();
if self.pin_mut().GoToNextRow() != E_OK {
return None;
};
if self.pin_mut().GetRow(row.pin_mut()) != E_OK {
return None;
}
Some(T::from_sql(0, row.pin_mut()))
}
}
macro_rules! single_tuple_impl {
($(($field:tt $ftype:ident)),* $(,)?) => {
impl <$($ftype,) *> Iterator for QuerySet<($($ftype,) *)> where $($ftype: FromSql,)* {
type Item = ($($ftype,) *);
fn next(&mut self) -> Option<Self::Item> {
let mut row = NewRowEntity();
if self.pin_mut().GoToNextRow() != E_OK {
return None;
};
if (self.pin_mut().GetRow(row.pin_mut()) != E_OK) {
return None;
}
Some(($({
$ftype::from_sql($field,row.pin_mut())
}), *))
}
}
};
}
single_tuple_impl!((0 A), (1 B));
single_tuple_impl!((0 A), (1 B), (2 C));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O));
single_tuple_impl!((0 A), (1 B), (2 C), (3 D), (4 E), (5 F), (6 G), (7 H), (8 I), (9 J), (10 K), (11 L), (12 M), (13 N), (14 O), (15 P));
// Unit tests for the database module
#[cfg(test)]
mod ut_database {
include!("../tests/ut/ut_database.rs");
}