mirror of
https://github.com/Squareville/Assembly.git
synced 2025-12-30 16:26:37 -06:00
442 lines
12 KiB
Rust
442 lines
12 KiB
Rust
//! # Types that are common to most FDB-APIs
|
|
//!
|
|
//! This crate module contains rustic representations/types for values that
|
|
//! necessarily appear in most of the APIs in this crate.
|
|
|
|
use std::{
|
|
borrow::{Borrow, Cow},
|
|
convert::TryFrom,
|
|
error::Error,
|
|
fmt,
|
|
ops::Deref,
|
|
};
|
|
|
|
use encoding_rs::WINDOWS_1252;
|
|
use memchr::memchr;
|
|
|
|
#[repr(transparent)]
|
|
#[derive(Ord, PartialOrd, Eq, PartialEq)]
|
|
/// An owned latin-1 encoded string
|
|
pub struct Latin1String {
|
|
inner: Box<[u8]>,
|
|
}
|
|
|
|
impl Latin1String {
|
|
/// Create a new string
|
|
///
|
|
/// ## Safety
|
|
///
|
|
/// Must not contain null bytes
|
|
pub unsafe fn new(inner: Box<[u8]>) -> Self {
|
|
Self { inner }
|
|
}
|
|
|
|
/// Create a new instance from a rust string.
|
|
///
|
|
/// **Note**: This encodes any unavailable unicode codepoints as their equivalent HTML-Entity.
|
|
/// This is an implementation detail of the `encoding_rs` crate and not really useful for this crate.
|
|
pub fn encode(string: &str) -> Cow<Latin1Str> {
|
|
let (res, _enc, _has_replaced_chars) = WINDOWS_1252.encode(string);
|
|
match res {
|
|
Cow::Owned(o) => Cow::Owned(Self {
|
|
inner: o.into_boxed_slice(),
|
|
}),
|
|
Cow::Borrowed(b) => Cow::Borrowed(unsafe { Latin1Str::from_bytes_unchecked(b) }),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Borrow<Latin1Str> for Latin1String {
|
|
fn borrow(&self) -> &Latin1Str {
|
|
unsafe { Latin1Str::from_bytes_unchecked(&self.inner) }
|
|
}
|
|
}
|
|
|
|
impl Deref for Latin1String {
|
|
type Target = Latin1Str;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
self.borrow()
|
|
}
|
|
}
|
|
|
|
impl From<Cow<'_, Latin1Str>> for Latin1String {
|
|
fn from(cow: Cow<'_, Latin1Str>) -> Self {
|
|
cow.into_owned()
|
|
}
|
|
}
|
|
|
|
impl From<&Latin1Str> for Latin1String {
|
|
fn from(src: &Latin1Str) -> Latin1String {
|
|
src.to_owned()
|
|
}
|
|
}
|
|
|
|
#[repr(transparent)]
|
|
#[derive(PartialEq, PartialOrd, Eq, Ord)]
|
|
/// A borrowed latin-1 encoded string (like `&str`)
|
|
pub struct Latin1Str {
|
|
#[allow(dead_code)]
|
|
inner: [u8],
|
|
}
|
|
|
|
#[cfg(feature = "serde-derives")]
|
|
impl serde::Serialize for Latin1Str {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_str(self.decode().as_ref())
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for &'_ Latin1Str {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
self.decode().fmt(f)
|
|
}
|
|
}
|
|
|
|
impl ToOwned for Latin1Str {
|
|
type Owned = Latin1String;
|
|
|
|
fn to_owned(&self) -> Self::Owned {
|
|
Latin1String {
|
|
inner: self.as_bytes().into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Latin1Str {
|
|
/// Takes all bytes until before the first null byte or end of slice.
|
|
pub(super) fn new(bytes: &[u8]) -> &Self {
|
|
let text = if let Some(index) = memchr(0x00, bytes) {
|
|
bytes.split_at(index).0
|
|
} else {
|
|
bytes
|
|
};
|
|
unsafe { Self::from_bytes_unchecked(text) }
|
|
}
|
|
|
|
/// Turns some bytes into a Latin1Str slice
|
|
///
|
|
/// ## Safety
|
|
///
|
|
/// The byte slice may not contain any null bytes
|
|
pub unsafe fn from_bytes_unchecked(text: &[u8]) -> &Self {
|
|
&*(text as *const [u8] as *const Latin1Str)
|
|
}
|
|
|
|
/// Get the bytes of the string
|
|
pub fn as_bytes(&self) -> &[u8] {
|
|
&self.inner
|
|
}
|
|
|
|
/// Get the bytes of the string
|
|
pub fn len(&self) -> usize {
|
|
self.inner.len()
|
|
}
|
|
|
|
/// Check whether the str is empty
|
|
pub fn is_empty(&self) -> bool {
|
|
self.inner.is_empty()
|
|
}
|
|
|
|
/// Calculates the number of 4-byte units that are needed to store
|
|
/// this string with at least one null terminator.
|
|
pub fn req_buf_len(&self) -> usize {
|
|
self.inner.len() / 4 + 1
|
|
}
|
|
|
|
/// Decode the string
|
|
pub fn decode(&self) -> Cow<str> {
|
|
WINDOWS_1252.decode(self.as_bytes()).0
|
|
}
|
|
|
|
/// Hash the string using `SuperFashHash` / [`hsieh_hash`]
|
|
pub fn hash(&self) -> u32 {
|
|
hsieh_hash::digest(self.as_bytes())
|
|
}
|
|
}
|
|
|
|
/// Type-Parameters to [`Value`]
|
|
///
|
|
/// This trait is used to parameterize `Value` to produce the concrete types
|
|
/// that are used elsewhere in this crate.
|
|
pub trait Context {
|
|
/// The type that holds a `ValueType::String`
|
|
type String;
|
|
/// The type that holds a `ValueType::BigInt`
|
|
type I64;
|
|
/// The type that holds a `ValueType::VarChar`
|
|
type XML;
|
|
}
|
|
|
|
/// Trait for mapping value from one context to another
|
|
///
|
|
/// This traits allows us to implement a generic [`Value::map`] function
|
|
/// that works similar to three [`FnMut`] closures but can guarantee that
|
|
/// only one of them ever borrows `Self` mutably at the same time.
|
|
pub trait ValueMapperMut<TI, TO>
|
|
where
|
|
TI: Context,
|
|
TO: Context,
|
|
{
|
|
/// Called when mapping a string
|
|
fn map_string(&mut self, from: &TI::String) -> TO::String;
|
|
/// Called when mapping an i64
|
|
fn map_i64(&mut self, from: &TI::I64) -> TO::I64;
|
|
/// Called when mapping an XML value
|
|
fn map_xml(&mut self, from: &TI::XML) -> TO::XML;
|
|
}
|
|
|
|
/// A single field value in the database
|
|
///
|
|
/// This is a generic enum that is the template for all
|
|
/// other `Field` types in this crate.
|
|
#[derive(Debug, PartialEq)]
|
|
#[cfg_attr(feature = "serde-derives", derive(serde::Serialize))]
|
|
#[cfg_attr(feature = "serde-derives", serde(untagged))]
|
|
pub enum Value<T: Context> {
|
|
/// The NULL value
|
|
Nothing,
|
|
/// A 32 bit integer
|
|
Integer(i32),
|
|
/// A 32 bit IEEE floating point number
|
|
Float(f32),
|
|
/// A string
|
|
Text(T::String),
|
|
/// A boolean
|
|
Boolean(bool),
|
|
/// A 64 bit integer
|
|
BigInt(T::I64),
|
|
/// A (XML?) string
|
|
VarChar(T::XML),
|
|
}
|
|
|
|
impl<T: Context> Clone for Value<T>
|
|
where
|
|
T::String: Clone,
|
|
T::XML: Clone,
|
|
T::I64: Clone,
|
|
{
|
|
fn clone(&self) -> Self {
|
|
match self {
|
|
Value::Nothing => Value::Nothing,
|
|
Value::Integer(v) => Value::Integer(*v),
|
|
Value::Float(v) => Value::Float(*v),
|
|
Value::Text(v) => Value::Text(v.clone()),
|
|
Value::Boolean(v) => Value::Boolean(*v),
|
|
Value::BigInt(v) => Value::BigInt(v.clone()),
|
|
Value::VarChar(v) => Value::VarChar(v.clone()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Context> Copy for Value<T>
|
|
where
|
|
T::String: Copy,
|
|
T::XML: Copy,
|
|
T::I64: Copy,
|
|
{
|
|
}
|
|
|
|
impl<T: Context> Value<T> {
|
|
/// Creates a value of a different context using the given mapper
|
|
pub fn map<O, M>(&self, mapper: &mut M) -> Value<O>
|
|
where
|
|
O: Context,
|
|
M: ValueMapperMut<T, O>,
|
|
{
|
|
match self {
|
|
Value::Nothing => Value::Nothing,
|
|
Value::Integer(v) => Value::Integer(*v),
|
|
Value::Float(v) => Value::Float(*v),
|
|
Value::Text(v) => Value::Text(mapper.map_string(v)),
|
|
Value::Boolean(v) => Value::Boolean(*v),
|
|
Value::BigInt(v) => Value::BigInt(mapper.map_i64(v)),
|
|
Value::VarChar(v) => Value::VarChar(mapper.map_xml(v)),
|
|
}
|
|
}
|
|
|
|
/// Returns `Some` with the value if the field contains an [`Value::Integer`].
|
|
pub fn into_opt_integer(self) -> Option<i32> {
|
|
if let Self::Integer(value) = self {
|
|
Some(value)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Returns `Some` with the value if the field contains a [`Value::Float`].
|
|
pub fn into_opt_float(self) -> Option<f32> {
|
|
if let Self::Float(value) = self {
|
|
Some(value)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Returns `Some` with the value if the field contains a [`Value::Text`].
|
|
pub fn into_opt_text(self) -> Option<T::String> {
|
|
if let Self::Text(value) = self {
|
|
Some(value)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Returns `Some` with the value if the field contains a [`Value::Boolean`].
|
|
pub fn into_opt_boolean(self) -> Option<bool> {
|
|
if let Self::Boolean(value) = self {
|
|
Some(value)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Returns `Some` with the value if the field contains a [`Value::BigInt`].
|
|
pub fn into_opt_big_int(self) -> Option<T::I64> {
|
|
if let Self::BigInt(value) = self {
|
|
Some(value)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Returns `Some` with the value if the field contains a [`Value::VarChar`].
|
|
pub fn into_opt_varchar(self) -> Option<T::XML> {
|
|
if let Self::VarChar(value) = self {
|
|
Some(value)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Context> From<&Value<T>> for ValueType {
|
|
fn from(val: &Value<T>) -> Self {
|
|
match val {
|
|
Value::Nothing => ValueType::Nothing,
|
|
Value::Integer(_) => ValueType::Integer,
|
|
Value::Float(_) => ValueType::Float,
|
|
Value::Text(_) => ValueType::Text,
|
|
Value::Boolean(_) => ValueType::Boolean,
|
|
Value::BigInt(_) => ValueType::BigInt,
|
|
Value::VarChar(_) => ValueType::VarChar,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Value datatypes used in the database
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "serde-derives", derive(serde::Serialize))]
|
|
pub enum ValueType {
|
|
/// The NULL value
|
|
Nothing,
|
|
/// A 32-bit signed integer
|
|
Integer,
|
|
/// A 32-bit IEEE floating point number
|
|
Float,
|
|
/// A long string
|
|
Text,
|
|
/// A boolean
|
|
Boolean,
|
|
/// A 64 bit integer
|
|
BigInt,
|
|
/// An (XML?) string
|
|
VarChar,
|
|
}
|
|
|
|
impl ValueType {
|
|
/// Get a static name for the type
|
|
pub fn static_name(&self) -> &'static str {
|
|
match self {
|
|
ValueType::Nothing => "NULL",
|
|
ValueType::Integer => "INTEGER",
|
|
ValueType::Float => "FLOAT",
|
|
ValueType::Text => "TEXT",
|
|
ValueType::Boolean => "BOOLEAN",
|
|
ValueType::BigInt => "BIGINT",
|
|
ValueType::VarChar => "VARCHAR",
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ValueType {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.static_name())
|
|
}
|
|
}
|
|
|
|
impl From<ValueType> for u8 {
|
|
fn from(value_type: ValueType) -> u8 {
|
|
match value_type {
|
|
ValueType::Nothing => 0,
|
|
ValueType::Integer => 1,
|
|
ValueType::Float => 3,
|
|
ValueType::Text => 4,
|
|
ValueType::Boolean => 5,
|
|
ValueType::BigInt => 6,
|
|
ValueType::VarChar => 8,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ValueType> for u32 {
|
|
fn from(value_type: ValueType) -> u32 {
|
|
u8::from(value_type).into()
|
|
}
|
|
}
|
|
|
|
/// This represents a value type that could not be parsed
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct UnknownValueType(u32);
|
|
|
|
impl UnknownValueType {
|
|
/// Get the value that could not be interpreted
|
|
pub fn value(&self) -> u32 {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl Error for UnknownValueType {}
|
|
impl fmt::Display for UnknownValueType {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "Unknown FDB value type {}", self.0)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<u32> for ValueType {
|
|
type Error = UnknownValueType;
|
|
|
|
fn try_from(value_type: u32) -> Result<ValueType, Self::Error> {
|
|
match value_type {
|
|
0 => Ok(ValueType::Nothing),
|
|
1 => Ok(ValueType::Integer),
|
|
3 => Ok(ValueType::Float),
|
|
4 => Ok(ValueType::Text),
|
|
5 => Ok(ValueType::Boolean),
|
|
6 => Ok(ValueType::BigInt),
|
|
8 => Ok(ValueType::VarChar),
|
|
_ => Err(UnknownValueType(value_type)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::Latin1Str;
|
|
|
|
#[test]
|
|
fn test_latin1_req_bytes() {
|
|
assert_eq!(1, Latin1Str::new(b"a").req_buf_len());
|
|
assert_eq!(1, Latin1Str::new(b"ab").req_buf_len());
|
|
assert_eq!(1, Latin1Str::new(b"abc").req_buf_len());
|
|
assert_eq!(2, Latin1Str::new(b"abcd").req_buf_len());
|
|
assert_eq!(2, Latin1Str::new(b"abcde").req_buf_len());
|
|
assert_eq!(2, Latin1Str::new(b"abcdef").req_buf_len());
|
|
assert_eq!(2, Latin1Str::new(b"abcdefg").req_buf_len());
|
|
assert_eq!(3, Latin1Str::new(b"abcdefgh").req_buf_len());
|
|
}
|
|
}
|