#![allow(non_snake_case)]
use crate::{
Variant, array::Array, some_function1, some_function2, some_function3, some_function4,
some_polymorphic_function1, some_polymorphic_function2, some_polymorphic_null_function1,
string_interner::*,
};
use arcstr::ArcStr;
use core::fmt::Error;
use feldera_macros::IsNone;
use feldera_types::{deserialize_without_context, serialize_without_context};
use itertools::Itertools;
use like::{Escape, Like};
use md5::{Digest, Md5};
use regex::Regex;
use rkyv::{
DeserializeUnsized, Fallible, SerializeUnsized,
string::{ArchivedString, StringResolver},
};
use serde::{Deserialize, Serialize};
use size_of::{Context, SizeOf};
use std::{
cmp::max,
fmt::{Display, Formatter},
mem::{MaybeUninit, transmute},
sync::Arc,
};
use tracing::warn;
use xxhash_rust::xxh64::xxh64;
type StringRef = ArcStr;
pub type InternedString = InternedStringId;
#[derive(
Clone, Default, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize, IsNone,
)]
#[serde(transparent)]
pub struct SqlString(StringRef);
impl SqlString {
pub fn new() -> Self {
Self::default()
}
pub fn from_ref(value: &str) -> Self {
SqlString(StringRef::from(value.to_string()))
}
pub fn maybe_reuse(value: &str, candidate: &SqlString) -> Self {
if value.as_ptr() == candidate.str().as_ptr() && value.len() == candidate.len() {
candidate.clone()
} else {
SqlString(StringRef::from(value.to_string()))
}
}
pub fn from_concat(strings: &[&str]) -> Self {
Self::from_concat_iterator(strings.iter().copied())
}
pub fn from_concat_iterator<'a, I>(strings: I) -> Self
where
I: IntoIterator<Item = &'a str>,
I::IntoIter: Clone,
{
fn copy_bytes_uninit(dst: &mut [MaybeUninit<u8>], src: &[u8]) {
let src: &[MaybeUninit<u8>] = unsafe { transmute(src) };
dst.copy_from_slice(src);
}
let strings = strings.into_iter();
let len = strings.clone().map(|s| s.len()).sum::<usize>();
if len > 0 {
Self(unsafe {
ArcStr::init_with_unchecked(len, |buf| {
let mut offset = 0;
for s in strings {
let end = offset + s.len();
copy_bytes_uninit(&mut buf[offset..end], s.as_bytes());
offset = end;
}
debug_assert_eq!(offset, len);
})
})
} else {
Self(ArcStr::new())
}
}
pub fn str(&self) -> &str {
&self.0
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
serialize_without_context!(SqlString);
deserialize_without_context!(SqlString);
impl Display for SqlString {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
self.str().fmt(f)
}
}
impl From<ArcStr> for SqlString {
fn from(value: ArcStr) -> Self {
SqlString(value)
}
}
impl From<String> for SqlString {
fn from(value: String) -> Self {
SqlString(StringRef::from(value))
}
}
impl From<char> for SqlString {
fn from(value: char) -> Self {
SqlString(StringRef::from(value.encode_utf8(&mut [0; 4])))
}
}
impl From<&str> for SqlString {
fn from(value: &str) -> Self {
SqlString::from_ref(value)
}
}
impl AsRef<ArcStr> for SqlString {
fn as_ref(&self) -> &ArcStr {
&self.0
}
}
impl SizeOf for SqlString {
fn size_of_children(&self, context: &mut Context) {
self.0.size_of_children(context);
}
}
impl rkyv::Archive for SqlString {
type Archived = ArchivedString;
type Resolver = StringResolver;
#[inline]
unsafe fn resolve(&self, pos: usize, resolver: Self::Resolver, out: *mut Self::Archived) {
unsafe {
ArchivedString::resolve_from_str(self.str(), pos, resolver, out);
}
}
}
impl<S: Fallible + ?Sized> rkyv::Serialize<S> for SqlString
where
str: SerializeUnsized<S>,
{
#[inline]
fn serialize(&self, serializer: &mut S) -> Result<Self::Resolver, S::Error> {
ArchivedString::serialize_from_str(self.str(), serializer)
}
}
impl<D: Fallible + ?Sized> rkyv::Deserialize<SqlString, D> for ArchivedString
where
str: DeserializeUnsized<str, D>,
{
#[inline]
fn deserialize(&self, _: &mut D) -> Result<SqlString, D::Error> {
Ok(SqlString::from_ref(self.as_str()))
}
}
#[doc(hidden)]
pub fn concat_s_s(left: SqlString, right: SqlString) -> SqlString {
SqlString::from_concat(&[left.str(), right.str()])
}
some_polymorphic_function2!(concat, s, SqlString, s, SqlString, SqlString);
#[doc(hidden)]
fn substring3_impl(s: &str, left: i32, count: i32) -> &str {
if count <= 0 {
""
} else {
let (start_char, char_count) = if left < 1 {
(0_usize, max(count + left - 1, 0) as usize)
} else {
((left - 1) as usize, count as usize)
};
let start_byte = byte_index(s, start_char);
let end_byte = byte_index(&s[start_byte..], char_count) + start_byte;
&s[start_byte..end_byte]
}
}
#[doc(hidden)]
pub fn substring3___(value: SqlString, left: i32, count: i32) -> SqlString {
SqlString::maybe_reuse(substring3_impl(value.str(), left, count), &value)
}
some_function3!(substring3, SqlString, i32, i32, SqlString);
pub(crate) fn byte_index(s: &str, char_index: usize) -> usize {
if char_index < s.len() {
for (char_count, (byte_index, _)) in s.char_indices().enumerate() {
if char_index == char_count {
return byte_index;
}
}
}
s.len()
}
pub(crate) fn byte_index_rev(s: &str, char_index: usize) -> usize {
if char_index < s.len() {
for (char_count, (byte_index, _)) in s.char_indices().rev().enumerate() {
if char_index == char_count {
return byte_index;
}
}
}
0
}
fn substring2_impl(s: &str, left: i32) -> &str {
let char_index = left.max(1) - 1;
let byte_index = byte_index(s, char_index as usize);
&s[byte_index..]
}
#[doc(hidden)]
pub fn substring2__(value: SqlString, left: i32) -> SqlString {
let s = value.str();
let char_index = left.max(1) - 1;
let byte_index = byte_index(s, char_index as usize);
SqlString::maybe_reuse(&s[byte_index..], &value)
}
some_function2!(substring2, SqlString, i32, SqlString);
#[doc(hidden)]
pub fn trim_both_s_s(remove: SqlString, value: SqlString) -> SqlString {
SqlString::maybe_reuse(
value.str().trim_matches(|c| remove.str().contains(c)),
&value,
)
}
some_polymorphic_function2!(trim_both, s, SqlString, s, SqlString, SqlString);
#[doc(hidden)]
pub fn trim_leading_s_s(remove: SqlString, value: SqlString) -> SqlString {
SqlString::maybe_reuse(
value.str().trim_start_matches(|c| remove.str().contains(c)),
&value,
)
}
some_polymorphic_function2!(trim_leading, s, SqlString, s, SqlString, SqlString);
#[doc(hidden)]
pub fn trim_trailing_s_s(remove: SqlString, value: SqlString) -> SqlString {
SqlString::maybe_reuse(
value.str().trim_end_matches(|c| remove.str().contains(c)),
&value,
)
}
some_polymorphic_function2!(trim_trailing, s, SqlString, s, SqlString, SqlString);
#[doc(hidden)]
pub fn like2__(value: SqlString, pattern: SqlString) -> bool {
Like::<false>::like(value.str(), pattern.str()).unwrap()
}
some_function2!(like2, SqlString, SqlString, bool);
#[doc(hidden)]
pub fn rlike__(value: SqlString, re: ParsedRegex) -> bool {
match re {
ParsedRegex::Invalid(r, e) => {
panic!("Invalid regular expression in RLIKE '{}': {}", r, e);
}
ParsedRegex::Regex(re) => re.is_match(value.str()),
}
}
some_function2!(rlike, SqlString, ParsedRegex, bool);
#[doc(hidden)]
pub fn like3___(value: SqlString, pattern: SqlString, escape: SqlString) -> bool {
let escaped = pattern.str().escape(escape.str()).unwrap();
Like::<true>::like(value.str(), &escaped).unwrap()
}
some_function3!(like3, SqlString, SqlString, SqlString, bool);
#[doc(hidden)]
pub fn ilike2__(value: SqlString, pattern: SqlString) -> bool {
Like::<false>::like(
value.str().to_lowercase().as_str(),
pattern.str().to_lowercase().as_str(),
)
.unwrap()
}
some_function2!(ilike2, SqlString, SqlString, bool);
#[doc(hidden)]
pub fn position__(needle: SqlString, haystack: SqlString) -> i32 {
let s = haystack.str();
let pos = s.find(needle.str());
match pos {
None => 0,
Some(i) => (s[..i].chars().count() + 1) as i32,
}
}
some_function2!(position, SqlString, SqlString, i32);
#[doc(hidden)]
pub fn char_length_(value: SqlString) -> i32 {
value.str().chars().count() as i32
}
some_function1!(char_length, SqlString, i32);
#[doc(hidden)]
pub fn char_length_ref(value: &str) -> i32 {
value.chars().count() as i32
}
#[doc(hidden)]
pub fn ascii_(value: SqlString) -> i32 {
if value.str().is_empty() {
0
} else {
value.str().chars().next().unwrap() as u32 as i32
}
}
some_function1!(ascii, SqlString, i32);
#[doc(hidden)]
pub fn chr_(code: i32) -> SqlString {
if code < 0 {
SqlString::default()
} else {
let c = char::from_u32(code as u32);
match c {
None => SqlString::default(),
Some(v) => SqlString::from(v),
}
}
}
some_function1!(chr, i32, SqlString);
#[doc(hidden)]
pub fn repeat__(value: SqlString, count: i32) -> SqlString {
if count <= 0 {
SqlString::default()
} else {
SqlString(ArcStr::repeat(value.str(), count as usize))
}
}
some_function2!(repeat, SqlString, i32, SqlString);
#[doc(hidden)]
pub fn overlay3___(source: SqlString, replacement: SqlString, position: i32) -> SqlString {
let len = char_length_ref(replacement.str());
overlay4____(source, replacement, position, len)
}
some_function3!(overlay3, SqlString, SqlString, i32, SqlString);
#[doc(hidden)]
pub fn overlay4____(
source: SqlString,
replacement: SqlString,
position: i32,
remove: i32,
) -> SqlString {
let mut remove = remove;
if remove < 0 {
remove = 0;
}
if position <= 0 {
source
} else if position > char_length_ref(source.str()) {
concat_s_s(source, replacement)
} else {
let start = substring3_impl(source.str(), 1, position - 1);
let middle = replacement.str();
let end = substring2_impl(source.str(), position + remove);
SqlString::from_concat(&[start, middle, end])
}
}
some_function4!(overlay4, SqlString, SqlString, i32, i32, SqlString);
#[doc(hidden)]
pub fn lower_(source: SqlString) -> SqlString {
SqlString::from(source.str().to_lowercase())
}
some_function1!(lower, SqlString, SqlString);
#[doc(hidden)]
pub fn upper_(source: SqlString) -> SqlString {
SqlString::from(source.str().to_uppercase())
}
some_function1!(upper, SqlString, SqlString);
#[doc(hidden)]
pub fn initcap_(source: SqlString) -> SqlString {
let mut result = String::with_capacity(source.str().len());
let mut capitalize_next = true;
for c in source.str().chars() {
if c.is_alphanumeric() {
if capitalize_next {
for r in c.to_uppercase() {
result.push(r);
}
capitalize_next = false;
} else {
for r in c.to_lowercase() {
result.push(r);
}
capitalize_next = false;
}
} else {
capitalize_next = true;
result.push(c);
}
}
SqlString::from(result)
}
some_function1!(initcap, SqlString, SqlString);
#[doc(hidden)]
pub fn initcap_spaces_(source: SqlString) -> SqlString {
let mut result = String::with_capacity(source.str().len());
let mut capitalize_next = true;
for c in source.str().chars() {
if !c.is_whitespace() {
if capitalize_next {
for r in c.to_uppercase() {
result.push(r);
}
capitalize_next = false;
} else {
for r in c.to_lowercase() {
result.push(r);
}
capitalize_next = false;
}
} else {
capitalize_next = true;
result.push(c);
}
}
SqlString::from(result)
}
some_function1!(initcap_spaces, SqlString, SqlString);
#[doc(hidden)]
pub fn replace___(haystack: SqlString, needle: SqlString, replacement: SqlString) -> SqlString {
SqlString::from(haystack.str().replace(needle.str(), replacement.str()))
}
some_function3!(replace, SqlString, SqlString, SqlString, SqlString);
#[doc(hidden)]
pub fn left_s_i32(value: SqlString, n_chars: i32) -> SqlString {
if n_chars <= 0 {
return SqlString::new();
}
let str = value.str();
let n_bytes = byte_index(str, n_chars as usize);
if n_bytes < str.len() {
SqlString::from_ref(&str[..n_bytes])
} else {
value
}
}
some_polymorphic_function2!(left, s, SqlString, i32, i32, SqlString);
#[doc(hidden)]
pub fn right_s_i32(value: SqlString, n_chars: i32) -> SqlString {
if n_chars <= 0 {
return SqlString::new();
}
let str = value.str();
let byte_index = byte_index_rev(str, (n_chars - 1) as usize);
if byte_index == 0 {
value
} else {
SqlString::from_ref(&str[byte_index..])
}
}
some_polymorphic_function2!(right, s, SqlString, i32, i32, SqlString);
#[doc(hidden)]
pub fn split2__(source: SqlString, separators: SqlString) -> Array<SqlString> {
if separators.str().is_empty() {
return Arc::new(vec![source]);
}
if source.str().is_empty() {
return Arc::new(vec![]);
}
source
.str()
.split(separators.str())
.map(SqlString::from)
.collect::<Vec<SqlString>>()
.into()
}
some_function2!(split2, SqlString, SqlString, Array<SqlString>);
#[doc(hidden)]
pub fn split1_(source: SqlString) -> Array<SqlString> {
split2__(source, SqlString(arcstr::literal!(",")))
}
some_function1!(split1, SqlString, Array<SqlString>);
#[doc(hidden)]
pub fn reverse_(s: SqlString) -> SqlString {
let r: String = s.str().chars().rev().collect();
SqlString::from(r)
}
some_function1!(reverse, SqlString, SqlString);
#[doc(hidden)]
pub fn split_part___(s: SqlString, delimiter: SqlString, n: i32) -> SqlString {
let parts: Array<SqlString> = split2__(s, delimiter);
let part_count = parts.len() as i32;
let n = if n < 0 { part_count + n + 1 } else { n };
if n <= 0 || n > part_count {
return SqlString::new();
}
parts[(n - 1) as usize].clone()
}
some_function3!(split_part, SqlString, SqlString, i32, SqlString);
#[doc(hidden)]
pub fn array_to_string2_vec__(value: Array<SqlString>, separator: SqlString) -> SqlString {
SqlString::from_concat_iterator(Itertools::intersperse(
value.iter().map(|s| s.str()),
separator.str(),
))
}
some_function2!(array_to_string2_vec, Array<SqlString>, SqlString, SqlString);
#[doc(hidden)]
pub fn array_to_string2Nvec__(value: Array<Option<SqlString>>, separator: SqlString) -> SqlString {
SqlString::from_concat_iterator(Itertools::intersperse(
value.iter().flatten().map(|s| s.str()),
separator.str(),
))
}
some_function2!(
array_to_string2Nvec,
Array<Option<SqlString>>,
SqlString,
SqlString
);
#[doc(hidden)]
pub fn array_to_string3_vec___(
value: Array<SqlString>,
separator: SqlString,
_null_value: SqlString,
) -> SqlString {
array_to_string2_vec__(value, separator)
}
some_function3!(
array_to_string3_vec,
Array<SqlString>,
SqlString,
SqlString,
SqlString
);
#[doc(hidden)]
pub fn array_to_string3Nvec___(
value: Array<Option<SqlString>>,
separator: SqlString,
null_value: SqlString,
) -> SqlString {
SqlString::from_concat_iterator(Itertools::intersperse(
value
.iter()
.map(|s| s.as_ref().unwrap_or(&null_value).str()),
separator.str(),
))
}
some_function3!(
array_to_string3Nvec,
Array<Option<SqlString>>,
SqlString,
SqlString,
SqlString
);
#[doc(hidden)]
pub fn writelog<T: std::fmt::Display>(format: SqlString, argument: T) -> T {
let format_arg = format!("{}", argument);
let formatted = format.str().replace("%%", &format_arg);
print!("{}", formatted);
argument
}
#[doc(hidden)]
pub fn parse_json_s(value: SqlString) -> Variant {
let var = serde_json::from_str::<Variant>(value.str());
match var {
Ok(v) => v,
Err(_) => Variant::SqlNull,
}
}
#[doc(hidden)]
pub fn parse_json_nullN(_value: Option<()>) -> Option<Variant> {
None
}
some_polymorphic_function1!(parse_json, s, SqlString, Variant);
#[doc(hidden)]
pub fn to_json_V(value: Variant) -> Option<SqlString> {
match value.to_json_string() {
Ok(s) => Some(SqlString::from(s)),
_ => None,
}
}
some_polymorphic_null_function1!(to_json, V, Variant, SqlString);
#[doc(hidden)]
pub fn to_json_nullN(_value: Option<()>) -> Option<SqlString> {
None
}
#[doc(hidden)]
#[derive(Debug, Clone)]
pub enum ParsedRegex {
Regex(Regex),
Invalid(SqlString, String),
}
#[doc(hidden)]
pub fn make_regex_(re: SqlString) -> ParsedRegex {
match Regex::new(re.str()) {
Ok(r) => ParsedRegex::Regex(r),
Err(e) => {
warn!(
"failed to parse '{}' as a regular expression: {}",
re,
e.to_string()
);
ParsedRegex::Invalid(re, e.to_string())
}
}
}
some_function1!(make_regex, SqlString, ParsedRegex);
#[doc(hidden)]
pub fn regexp_replace3___(str: SqlString, re: ParsedRegex, repl: SqlString) -> SqlString {
match re {
ParsedRegex::Invalid(r, e) => {
panic!("Invalid regular expression in REPLACE '{}': {}", r, e);
}
ParsedRegex::Regex(re) => {
SqlString::maybe_reuse(re.replace_all(str.str(), repl.str()).as_ref(), &str)
}
}
}
some_function3!(
regexp_replace3,
SqlString,
ParsedRegex,
SqlString,
SqlString
);
#[doc(hidden)]
pub fn regexp_replace2__(str: SqlString, re: ParsedRegex) -> SqlString {
regexp_replace3___(str, re, SqlString::new())
}
some_function2!(regexp_replace2, SqlString, ParsedRegex, SqlString);
#[doc(hidden)]
pub fn concat_ws___(sep: SqlString, left: SqlString, right: SqlString) -> SqlString {
SqlString::from_concat(&[left.str(), sep.str(), right.str()])
}
#[doc(hidden)]
pub fn concat_wsN__(
sep: Option<SqlString>,
left: SqlString,
right: SqlString,
) -> Option<SqlString> {
let sep = sep?;
Some(concat_ws___(sep, left, right))
}
#[doc(hidden)]
pub fn concat_ws_N_(sep: SqlString, left: Option<SqlString>, right: SqlString) -> SqlString {
match left {
None => right,
Some(left) => concat_ws___(sep, left, right),
}
}
#[doc(hidden)]
pub fn concat_ws_NN(
sep: SqlString,
left: Option<SqlString>,
right: Option<SqlString>,
) -> SqlString {
match (left, right) {
(None, None) => SqlString::new(),
(None, Some(right)) => right,
(Some(left), None) => left,
(Some(left), Some(right)) => concat_ws___(sep, left, right),
}
}
#[doc(hidden)]
pub fn concat_wsNN_(
sep: Option<SqlString>,
left: Option<SqlString>,
right: SqlString,
) -> Option<SqlString> {
let sep = sep?;
Some(concat_ws_N_(sep, left, right))
}
#[doc(hidden)]
pub fn concat_ws__N(sep: SqlString, left: SqlString, right: Option<SqlString>) -> SqlString {
match right {
None => left,
Some(right) => concat_ws___(sep, left, right),
}
}
#[doc(hidden)]
pub fn concat_wsN_N(
sep: Option<SqlString>,
left: SqlString,
right: Option<SqlString>,
) -> Option<SqlString> {
let sep = sep?;
Some(concat_ws__N(sep, left, right))
}
#[doc(hidden)]
pub fn concat_wsNNN(
sep: Option<SqlString>,
left: Option<SqlString>,
right: Option<SqlString>,
) -> Option<SqlString> {
let sep = sep?;
Some(concat_ws_NN(sep, left, right))
}
#[doc(hidden)]
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::{
SqlString, array_to_string2_vec__, array_to_string2Nvec__, array_to_string3Nvec___,
byte_index, byte_index_rev, left_s_i32, right_s_i32, substring2__, substring3___,
};
use dbsp::storage::file::to_bytes;
use rkyv::from_bytes;
use size_of::SizeOf;
#[test]
fn rkyv_serialize_deserialize_sqlstring() {
let s = SqlString::from_ref("abc1✅");
let archived = to_bytes(&s).unwrap();
let deserialized: SqlString = from_bytes(&archived).unwrap();
assert_eq!(s.str(), deserialized.str());
}
#[test]
fn sizeof_sqlstring() {
let s = SqlString::from_ref("abcdefghijklmnopqrstuvwxyz");
let total_size = SizeOf::size_of(&s);
dbg!(&total_size);
assert!(total_size.total_bytes() > 26);
assert!(total_size.shared_bytes() >= 26);
}
#[test]
fn test_byte_index() {
for (s, byte_offsets) in [
("abcdéfg", vec![0, 1, 2, 3, 4, 6, 7, 8]),
("アイウエオ", vec![0, 3, 6, 9, 12, 15]),
] {
for (char_offset, byte_offset) in byte_offsets.iter().copied().enumerate() {
assert_eq!(byte_index(s, char_offset), byte_offset);
assert_eq!(
left_s_i32(SqlString::from_ref(s), char_offset as i32),
SqlString::from_ref(&s[..byte_offset])
);
assert_eq!(
substring2__(SqlString::from_ref(s), (char_offset + 1) as i32),
SqlString::from_ref(&s[byte_offset..])
);
for (char_count, end_byte_offset) in
byte_offsets[char_offset..].iter().copied().enumerate()
{
assert_eq!(
substring3___(
SqlString::from_ref(s),
(char_offset + 1) as i32,
char_count as i32
),
SqlString::from_ref(&s[byte_offset..end_byte_offset])
);
}
}
for (char_offset, byte_offset) in byte_offsets.iter().copied().rev().skip(1).enumerate()
{
assert_eq!(byte_index_rev(s, char_offset), byte_offset);
assert_eq!(
right_s_i32(SqlString::from_ref(s), (char_offset + 1) as i32),
SqlString::from_ref(&s[byte_offset..])
)
}
assert_eq!(left_s_i32(SqlString::from_ref(s), 0), SqlString::new());
assert_eq!(right_s_i32(SqlString::from_ref(s), 0), SqlString::new());
}
}
#[test]
fn empty() {
assert_eq!(
SqlString::from_concat(&["", "", "", ""]),
SqlString::from("")
);
}
#[test]
fn concat() {
assert_eq!(
SqlString::from_concat(&["a", "xyzzy", "✨", "", "c", "plop"]),
SqlString::from("axyzzy✨cplop")
);
}
#[test]
fn array_to_string_vec() {
assert_eq!(
array_to_string2_vec__(
Arc::new(vec![
SqlString::from("xyzzy"),
SqlString::from("quux"),
SqlString::from("カキクケコ"),
SqlString::from("foo"),
]),
SqlString::from("_"),
),
SqlString::from("xyzzy_quux_カキクケコ_foo")
);
assert_eq!(
array_to_string2Nvec__(
Arc::new(vec![
None,
Some(SqlString::from("xyzzy")),
Some(SqlString::from("quux")),
None,
Some(SqlString::from("foo")),
None,
]),
SqlString::from("_"),
),
SqlString::from("xyzzy_quux_foo")
);
assert_eq!(
array_to_string3Nvec___(
Arc::new(vec![
None,
Some(SqlString::from("Tokyo(東京)")),
Some(SqlString::from("quux")),
None,
Some(SqlString::from("foo")),
None,
]),
SqlString::from("_"),
SqlString::from("#"),
),
SqlString::from("#_Tokyo(東京)_quux_#_foo_#")
);
}
}
#[doc(hidden)]
pub fn md5_s(source: SqlString) -> SqlString {
let mut hasher = Md5::new();
hasher.update(source.str());
let result = hasher.finalize();
SqlString::from(format!("{:x}", result))
}
some_polymorphic_function1!(md5, s, SqlString, SqlString);
#[doc(hidden)]
pub fn xxhash_s_i64(source: SqlString, seed: i64) -> i64 {
let hash = xxh64(source.str().as_bytes(), seed as u64);
hash as i64
}
some_polymorphic_function2!(xxhash, s, SqlString, i64, i64, i64);
#[doc(hidden)]
pub fn intern(s: Option<SqlString>) -> Option<InternedString> {
s.map(|s| intern_string(&s))
}
#[doc(hidden)]
pub fn unintern(id: Option<InternedString>) -> Option<SqlString> {
match id {
None => None,
Some(id) => match unintern_string(&id) {
Some(s) => Some(s),
None => panic!("Interning library has not returned a string for id='{id}'"),
},
}
}
#[cfg(test)]
mod test {
use crate::SqlString;
use rkyv::{option::ArchivedOption, string::ArchivedString};
#[test]
fn sql_string_size() {
assert_eq!(
std::mem::size_of::<SqlString>(),
std::mem::size_of::<Option<SqlString>>()
);
assert!(
std::mem::size_of::<ArchivedString>()
< std::mem::size_of::<ArchivedOption<ArchivedString>>()
)
}
}