use core::{
ffi::{CStr, c_char, c_int, c_void},
fmt,
iter::Extend,
mem,
ops::Deref,
ptr, slice,
};
use std::io;
#[cfg(target_pointer_width = "32")]
use sqlite::sqlite3_bind_text;
#[cfg(all(feature = "functions", target_pointer_width = "32"))]
use sqlite::sqlite3_result_text;
#[cfg(all(feature = "functions", target_pointer_width = "64"))]
use sqlite::sqlite3_result_text64;
#[cfg(target_pointer_width = "64")]
use sqlite::{SQLITE_UTF8, sqlite3_bind_text64, sqlite3_uint64};
use sqlite::{
sqlite3_destructor_type, sqlite3_free, sqlite3_str, sqlite3_str_append, sqlite3_str_appendall,
sqlite3_str_appendchar, sqlite3_str_errcode, sqlite3_str_finish, sqlite3_str_length,
sqlite3_str_new,
};
#[cfg(feature = "functions")]
use super::{bind::result, func::ContextRef};
use super::{
bind::{Bind, bind},
bytes::Bytes,
connection::Connected,
statement::Statement,
};
use crate::{
error::{Error, ParameterError, Result},
types::BindIndex,
};
pub struct String {
text: *const c_char,
len: usize,
}
impl String {
pub fn new(value: impl AsRef<str>) -> Result<Self> {
let value = value.as_ref();
debug_assert!(value.len() <= c_int::MAX as usize);
let bytes = Bytes::allocate(value.len() + 1, |buf| {
buf.copy_from_slice(value.as_bytes());
buf[value.len() - 1] = 0;
let _ = CStr::from_bytes_with_nul(buf)?;
Ok(())
})?;
Ok(unsafe { Self::from_bytes_unchecked(bytes) })
}
pub fn display<D: fmt::Display + ?Sized>(value: &D) -> Result<Self> {
use fmt::Write as _;
let mut builder = StringBuilder::new();
write!(&mut builder, "{value}").map_err(|_| ParameterError::Bind)?;
builder.finish()
}
#[inline]
pub const unsafe fn from_raw_parts(ptr: *const c_char, len: usize) -> Self {
match ptr {
_ if ptr.is_null() => panic!("expected non-null String pointer"),
text => Self { text, len },
}
}
pub fn from_bytes(bytes: Bytes) -> Result<Self> {
let s = CStr::from_bytes_with_nul(bytes.data())?;
let s = s.to_str()?;
Ok(unsafe { Self::from_raw_parts(s.as_ptr() as *const c_char, s.len()) })
}
pub const unsafe fn from_bytes_unchecked(bytes: Bytes) -> Self {
let (ptr, len) = bytes.into_raw_parts();
unsafe { Self::from_raw_parts(ptr as *const c_char, len.unchecked_sub(1)) }
}
pub fn empty() -> Result<Self> {
let bytes = Bytes::zeroed(1)?;
Ok(unsafe { Self::from_bytes_unchecked(bytes) })
}
#[inline]
pub const fn into_raw_parts(self) -> (*const c_char, usize) {
let (ptr, len) = (self.as_ptr(), self.len);
mem::forget(self);
(ptr, len)
}
#[inline]
pub const fn as_ptr(&self) -> *const c_char {
self.text
}
#[inline]
pub const fn len(&self) -> usize {
self.len
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub const fn bytes_until_nul(&self) -> &[u8] {
unsafe { slice::from_raw_parts::<'static, u8>(self.text as *const u8, self.len) }
}
#[inline]
pub const fn bytes_with_nul(&self) -> &[u8] {
unsafe { slice::from_raw_parts::<'static, u8>(self.text as *const u8, self.len + 1) }
}
#[inline]
pub const fn as_c_str(&self) -> &CStr {
unsafe { CStr::from_bytes_with_nul_unchecked(self.bytes_with_nul()) }
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(self.bytes_until_nul()) }
}
}
impl fmt::Debug for String {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("String").field(&self.as_c_str()).finish()
}
}
impl fmt::Display for String {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl Deref for String {
type Target = CStr;
#[inline]
fn deref(&self) -> &Self::Target {
self.as_c_str()
}
}
impl Drop for String {
fn drop(&mut self) {
unsafe { sqlite3_free(self.as_ptr() as *mut c_void) }
}
}
#[cfg(target_pointer_width = "64")]
const ENCODING_UTF8: core::ffi::c_uchar = SQLITE_UTF8 as core::ffi::c_uchar;
#[cfg_attr(
target_pointer_width = "32",
doc = "[Binds](Bind) a [`ffi::String`](String) via [`sqlite3_bind_text`]."
)]
#[cfg_attr(
target_pointer_width = "64",
doc = "[Binds](Bind) a [`ffi::String`](String) via [`sqlite3_bind_text64`]."
)]
impl<'b> Bind<'b> for String {
unsafe fn bind_parameter<'s>(self, statement: &Statement<'s>, index: BindIndex) -> Result<()>
where
's: 'b,
{
let (ptr, len) = self.into_raw_parts();
let destructor = sqlite3_destructor_type::new(sqlite3_free);
#[cfg(target_pointer_width = "32")]
bind! { sqlite3_bind_text(statement, index, ptr, len as c_int, destructor) }?;
#[cfg(target_pointer_width = "64")]
bind! { sqlite3_bind_text64(statement, index, ptr, len as sqlite3_uint64, destructor, ENCODING_UTF8) }?;
Ok(())
}
#[cfg(feature = "functions")]
unsafe fn bind_return<'c>(self, context: &ContextRef<'c>)
where
'b: 'c,
{
let (ptr, len) = self.into_raw_parts();
let destructor = sqlite3_destructor_type::new(sqlite3_free);
#[cfg(target_pointer_width = "32")]
result! { sqlite3_result_text(context, ptr, len as c_int, destructor) }
#[cfg(target_pointer_width = "64")]
result! { sqlite3_result_text64(context, ptr, len as sqlite3_uint64, destructor, ENCODING_UTF8) }
}
}
#[derive(Debug)]
pub struct StringBuilder {
ptr: ptr::NonNull<sqlite3_str>,
}
#[allow(clippy::new_without_default)]
impl StringBuilder {
#[doc(alias = "sqlite3_str_new")]
pub fn new() -> Self {
let ptr = unsafe { sqlite3_str_new(ptr::null_mut()) };
Self {
ptr: unsafe { ptr::NonNull::new_unchecked(ptr) },
}
}
pub fn with_limit(conn: impl Connected) -> Self {
let ptr = unsafe { sqlite3_str_new(conn.as_connection_ptr()) };
Self {
ptr: unsafe { ptr::NonNull::new_unchecked(ptr) },
}
}
#[doc(alias = "sqlite3_str_errcode")]
pub fn error(&self) -> Option<Error> {
let code = unsafe { sqlite3_str_errcode(self.as_ptr()) };
Error::from_code(code)
}
#[doc(alias = "sqlite3_str_length")]
pub fn len(&self) -> usize {
unsafe { sqlite3_str_length(self.as_ptr()) as usize }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[doc(alias = "sqlite3_str_append")]
pub fn append<T: Append>(&mut self, text: &T) {
unsafe { text.append(self.ptr) };
}
pub const fn as_ptr(&self) -> *mut sqlite3_str {
self.ptr.as_ptr()
}
#[doc(alias = "sqlite3_str_truncate")]
#[cfg(sqlite_has_truncate_string)]
pub fn truncate(&mut self, n: usize) {
debug_assert!(n <= c_int::MAX as usize);
unsafe { sqlite::sqlite3_str_truncate(self.as_ptr(), n as c_int) }
}
#[doc(alias = "sqlite3_str_finish")]
pub fn finish(self) -> Result<String> {
let len = self.len();
let error = self.error();
let ptr = unsafe { sqlite3_str_finish(self.as_ptr()) };
mem::forget(self);
if let Some(error) = error {
unsafe { sqlite3_free(ptr as *mut c_void) };
return Err(error);
}
if ptr.is_null() {
return String::empty();
}
Ok(unsafe { String::from_raw_parts(ptr, len) })
}
}
pub trait Append {
unsafe fn append(&self, string: ptr::NonNull<sqlite3_str>);
}
impl Append for str {
unsafe fn append(&self, string: ptr::NonNull<sqlite3_str>) {
debug_assert!(self.len() <= c_int::MAX as usize);
unsafe {
sqlite3_str_append(
string.as_ptr(),
self.as_ptr() as *const c_char,
self.len() as c_int,
);
}
}
}
impl Append for CStr {
unsafe fn append(&self, string: ptr::NonNull<sqlite3_str>) {
unsafe {
sqlite3_str_appendall(string.as_ptr(), self.as_ptr() as *const c_char);
}
}
}
impl Append for u8 {
unsafe fn append(&self, string: ptr::NonNull<sqlite3_str>) {
unsafe {
sqlite3_str_appendchar(string.as_ptr(), 1, *self as c_char);
}
}
}
impl<A: Append> Extend<A> for StringBuilder {
fn extend<T: IntoIterator<Item = A>>(&mut self, iter: T) {
for item in iter {
self.append(&item);
}
}
}
impl fmt::Write for StringBuilder {
fn write_str(&mut self, s: &str) -> fmt::Result {
debug_assert!(self.len() + s.len() <= c_int::MAX as usize);
unsafe {
sqlite3_str_append(self.as_ptr(), s.as_ptr() as *const c_char, s.len() as c_int);
}
Ok(())
}
}
impl io::Write for StringBuilder {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
debug_assert!(self.len() + buf.len() <= c_int::MAX as usize);
unsafe {
sqlite3_str_append(
self.as_ptr(),
buf.as_ptr() as *const c_char,
buf.len() as c_int,
);
}
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Drop for StringBuilder {
fn drop(&mut self) {
let ptr = unsafe { sqlite3_str_finish(self.as_ptr()) };
unsafe { sqlite3_free(ptr as *mut c_void) };
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum StringRepresentation {
#[cfg(sqlite_has_utf8_zt)]
NullTerminated = 0x43,
}
#[cfg(test)]
mod tests {
use core::fmt::Write;
use sqlite::{SQLITE_OPEN_CREATE, SQLITE_OPEN_READWRITE};
use super::*;
use crate::ffi::Connection;
#[test]
fn test_detached_builder() {
let mut builder = StringBuilder::new();
write!(builder, "hello").unwrap();
write!(builder, ", world!").unwrap();
let string = builder.finish().expect("finish");
assert_eq!(string.len(), 13);
assert_eq!(string.as_str(), "hello, world!");
}
#[test]
fn test_connected_builder() {
let conn = Connection::open(
c":memory:",
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
None,
)
.expect("open");
{
let mut builder = StringBuilder::with_limit(&conn);
write!(builder, "test string").unwrap();
let string = builder.finish().expect("finish");
assert_eq!(string.len(), 11);
assert_eq!(string.as_str(), "test string");
}
conn.close().expect("close");
}
#[test]
fn test_display() {
let string = String::display("hello, world!").expect("string");
assert_eq!(format!("{string}"), string.as_str());
}
#[test]
fn test_debug() {
let string = String::display("hello, world!").expect("string");
assert_eq!(format!("{string:?}"), r#"String("hello, world!")"#);
}
#[test]
fn test_display_formatting() {
let mut builder = StringBuilder::new();
let value = 42u32;
write!(builder, "value = {}", value).unwrap();
let string = builder.finish().expect("finish");
assert_eq!(string.len(), 10);
assert_eq!(string.as_str(), "value = 42");
}
#[test]
fn test_empty_string() {
let builder = StringBuilder::new();
let string = builder.finish().expect("finish");
assert!(string.is_empty());
assert_eq!(string.as_str(), "");
}
#[test]
fn test_bind_string() {
use crate::Borrowed;
use crate::ffi::Statement;
let conn = Connection::open(
c":memory:",
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
None,
)
.expect("open");
let (stmt, _) = Statement::prepare(&conn, "SELECT ?", 0).expect("prepare");
{
let mut builder = StringBuilder::with_limit(&conn);
write!(builder, "hello from sqlite3_str").unwrap();
let string = builder.finish().expect("finish");
let index = BindIndex::new(1).expect("valid index");
unsafe { stmt.bind(index, string) }.expect("bind");
let has_row = unsafe { stmt.row() }.expect("row");
assert!(has_row);
let col = crate::types::ColumnIndex::new(0);
let value: Borrowed<'_, str> = unsafe { stmt.fetch(col) };
assert_eq!(&*value, "hello from sqlite3_str");
}
stmt.close().expect("close stmt");
conn.close().expect("close conn");
}
}