use smallvec::SmallVec;
use smol_str::SmolStr;
use std::borrow::Cow;
use std::fmt;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Identifier(SmolStr);
impl Identifier {
#[inline]
pub fn new(s: impl AsRef<str>) -> Self {
Self(SmolStr::new(s.as_ref()))
}
#[inline]
pub const fn from_static(s: &'static str) -> Self {
Self(SmolStr::new_static(s))
}
#[inline]
pub fn as_str(&self) -> &str {
self.0.as_str()
}
#[inline]
pub fn is_inline(&self) -> bool {
self.0.is_heap_allocated() == false
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl fmt::Debug for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Identifier({:?})", self.0.as_str())
}
}
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<&str> for Identifier {
#[inline]
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for Identifier {
#[inline]
fn from(s: String) -> Self {
Self(SmolStr::new(&s))
}
}
impl From<&String> for Identifier {
#[inline]
fn from(s: &String) -> Self {
Self(SmolStr::new(s))
}
}
impl From<Cow<'_, str>> for Identifier {
#[inline]
fn from(s: Cow<'_, str>) -> Self {
Self(SmolStr::new(&s))
}
}
impl AsRef<str> for Identifier {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Default for Identifier {
fn default() -> Self {
Self(SmolStr::default())
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct CowIdentifier<'a>(Cow<'a, str>);
impl<'a> CowIdentifier<'a> {
#[inline]
pub const fn borrowed(s: &'a str) -> Self {
Self(Cow::Borrowed(s))
}
#[inline]
pub fn owned(s: String) -> Self {
Self(Cow::Owned(s))
}
#[inline]
pub fn new(s: impl Into<Cow<'a, str>>) -> Self {
Self(s.into())
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline]
pub fn is_borrowed(&self) -> bool {
matches!(self.0, Cow::Borrowed(_))
}
#[inline]
pub fn into_owned(self) -> String {
self.0.into_owned()
}
}
impl<'a> fmt::Debug for CowIdentifier<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"CowIdentifier({:?}, borrowed={})",
self.0.as_ref(),
self.is_borrowed()
)
}
}
impl<'a> fmt::Display for CowIdentifier<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<'a> From<&'a str> for CowIdentifier<'a> {
#[inline]
fn from(s: &'a str) -> Self {
Self::borrowed(s)
}
}
impl From<String> for CowIdentifier<'static> {
#[inline]
fn from(s: String) -> Self {
Self::owned(s)
}
}
impl<'a> AsRef<str> for CowIdentifier<'a> {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> Default for CowIdentifier<'a> {
fn default() -> Self {
Self::borrowed("")
}
}
pub type ColumnList = SmallVec<[Identifier; 8]>;
pub type ColumnNameList = SmallVec<[String; 8]>;
pub type CowColumnList<'a> = SmallVec<[Cow<'a, str>; 8]>;
pub type OrderByList = SmallVec<[(Identifier, crate::types::SortOrder); 4]>;
pub type PartitionByList = SmallVec<[Identifier; 4]>;
pub type ExprList = SmallVec<[String; 8]>;
pub type ValueList<T> = SmallVec<[T; 16]>;
#[derive(Debug, Clone)]
pub struct ReusableBuilder {
buffer: String,
initial_capacity: usize,
}
impl ReusableBuilder {
#[inline]
pub fn new() -> Self {
Self {
buffer: String::new(),
initial_capacity: 0,
}
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self {
buffer: String::with_capacity(capacity),
initial_capacity: capacity,
}
}
#[inline]
pub fn push(&mut self, s: &str) -> &mut Self {
self.buffer.push_str(s);
self
}
#[inline]
pub fn push_char(&mut self, c: char) -> &mut Self {
self.buffer.push(c);
self
}
#[inline]
pub fn push_fmt(&mut self, args: fmt::Arguments<'_>) -> &mut Self {
use std::fmt::Write;
let _ = self.buffer.write_fmt(args);
self
}
#[inline]
pub fn space(&mut self) -> &mut Self {
self.buffer.push(' ');
self
}
#[inline]
pub fn comma(&mut self) -> &mut Self {
self.buffer.push_str(", ");
self
}
#[inline]
pub fn as_str(&self) -> &str {
&self.buffer
}
#[inline]
pub fn len(&self) -> usize {
self.buffer.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.buffer.is_empty()
}
#[inline]
pub fn build(&self) -> String {
self.buffer.clone()
}
#[inline]
pub fn take(&mut self) -> String {
std::mem::take(&mut self.buffer)
}
#[inline]
pub fn reset(&mut self) {
self.buffer.clear();
}
#[inline]
pub fn reset_shrink(&mut self) {
self.buffer.clear();
if self.buffer.capacity() > self.initial_capacity * 2 {
self.buffer.shrink_to(self.initial_capacity);
}
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.buffer.reserve(additional);
}
#[inline]
pub fn capacity(&self) -> usize {
self.buffer.capacity()
}
}
impl Default for ReusableBuilder {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for ReusableBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.buffer)
}
}
impl From<ReusableBuilder> for String {
fn from(builder: ReusableBuilder) -> String {
builder.buffer
}
}
pub struct BuilderPool {
builders: parking_lot::Mutex<Vec<ReusableBuilder>>,
capacity: usize,
}
impl BuilderPool {
pub fn new(pool_size: usize, builder_capacity: usize) -> Self {
let builders: Vec<_> = (0..pool_size)
.map(|_| ReusableBuilder::with_capacity(builder_capacity))
.collect();
Self {
builders: parking_lot::Mutex::new(builders),
capacity: builder_capacity,
}
}
#[inline]
pub fn get(&self) -> ReusableBuilder {
self.builders
.lock()
.pop()
.unwrap_or_else(|| ReusableBuilder::with_capacity(self.capacity))
}
#[inline]
pub fn put(&self, mut builder: ReusableBuilder) {
builder.reset_shrink();
self.builders.lock().push(builder);
}
pub fn len(&self) -> usize {
self.builders.lock().len()
}
pub fn is_empty(&self) -> bool {
self.builders.lock().is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct OptimizedWindowSpec {
pub partition_by: PartitionByList,
pub order_by: SmallVec<[(Identifier, crate::types::SortOrder); 4]>,
pub frame: Option<WindowFrame>,
pub window_ref: Option<Identifier>,
}
#[derive(Debug, Clone)]
pub struct WindowFrame {
pub frame_type: FrameType,
pub start: FrameBound,
pub end: Option<FrameBound>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameType {
Rows,
Range,
Groups,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FrameBound {
UnboundedPreceding,
Preceding(u32),
CurrentRow,
Following(u32),
UnboundedFollowing,
}
impl OptimizedWindowSpec {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn partition_by<I, S>(mut self, columns: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<Identifier>,
{
self.partition_by
.extend(columns.into_iter().map(Into::into));
self
}
#[inline]
pub fn partition_by_col(mut self, column: impl Into<Identifier>) -> Self {
self.partition_by.push(column.into());
self
}
#[inline]
pub fn order_by(
mut self,
column: impl Into<Identifier>,
order: crate::types::SortOrder,
) -> Self {
self.order_by.push((column.into(), order));
self
}
#[inline]
pub fn rows(mut self, start: FrameBound, end: Option<FrameBound>) -> Self {
self.frame = Some(WindowFrame {
frame_type: FrameType::Rows,
start,
end,
});
self
}
#[inline]
pub fn rows_unbounded_preceding(self) -> Self {
self.rows(FrameBound::UnboundedPreceding, Some(FrameBound::CurrentRow))
}
#[inline]
pub fn window_ref(mut self, name: impl Into<Identifier>) -> Self {
self.window_ref = Some(name.into());
self
}
pub fn to_sql(&self, _db_type: crate::sql::DatabaseType) -> String {
let mut parts: SmallVec<[String; 4]> = SmallVec::new();
if let Some(ref name) = self.window_ref {
return format!("OVER {}", name);
}
if !self.partition_by.is_empty() {
let cols: Vec<_> = self.partition_by.iter().map(|c| c.as_str()).collect();
parts.push(format!("PARTITION BY {}", cols.join(", ")));
}
if !self.order_by.is_empty() {
let cols: Vec<_> = self
.order_by
.iter()
.map(|(col, order)| {
format!(
"{} {}",
col.as_str(),
match order {
crate::types::SortOrder::Asc => "ASC",
crate::types::SortOrder::Desc => "DESC",
}
)
})
.collect();
parts.push(format!("ORDER BY {}", cols.join(", ")));
}
if let Some(ref frame) = self.frame {
let frame_type = match frame.frame_type {
FrameType::Rows => "ROWS",
FrameType::Range => "RANGE",
FrameType::Groups => "GROUPS",
};
let start = frame_bound_to_sql(&frame.start);
if let Some(ref end) = frame.end {
let end_sql = frame_bound_to_sql(end);
parts.push(format!("{} BETWEEN {} AND {}", frame_type, start, end_sql));
} else {
parts.push(format!("{} {}", frame_type, start));
}
}
if parts.is_empty() {
"OVER ()".to_string()
} else {
format!("OVER ({})", parts.join(" "))
}
}
}
fn frame_bound_to_sql(bound: &FrameBound) -> &'static str {
match bound {
FrameBound::UnboundedPreceding => "UNBOUNDED PRECEDING",
FrameBound::Preceding(_) => "PRECEDING", FrameBound::CurrentRow => "CURRENT ROW",
FrameBound::Following(_) => "FOLLOWING", FrameBound::UnboundedFollowing => "UNBOUNDED FOLLOWING",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_identifier_inline() {
let id = Identifier::new("user_id");
assert_eq!(id.as_str(), "user_id");
assert!(id.is_inline()); }
#[test]
fn test_identifier_from_static() {
let id = Identifier::from_static("email");
assert_eq!(id.as_str(), "email");
}
#[test]
fn test_cow_identifier_borrowed() {
let id = CowIdentifier::borrowed("user_id");
assert!(id.is_borrowed());
assert_eq!(id.as_str(), "user_id");
}
#[test]
fn test_cow_identifier_owned() {
let id = CowIdentifier::owned("dynamic".to_string());
assert!(!id.is_borrowed());
assert_eq!(id.as_str(), "dynamic");
}
#[test]
fn test_column_list_stack_allocation() {
let mut cols: ColumnList = SmallVec::new();
cols.push(Identifier::new("id"));
cols.push(Identifier::new("name"));
cols.push(Identifier::new("email"));
cols.push(Identifier::new("created_at"));
assert!(!cols.spilled());
assert_eq!(cols.len(), 4);
}
#[test]
fn test_column_list_heap_spillover() {
let mut cols: ColumnList = SmallVec::new();
for i in 0..10 {
cols.push(Identifier::new(format!("col_{}", i)));
}
assert!(cols.spilled());
assert_eq!(cols.len(), 10);
}
#[test]
fn test_reusable_builder() {
let mut builder = ReusableBuilder::with_capacity(64);
builder.push("SELECT * FROM users");
assert_eq!(builder.as_str(), "SELECT * FROM users");
builder.reset();
assert!(builder.is_empty());
assert!(builder.capacity() >= 64);
builder.push("SELECT * FROM posts");
assert_eq!(builder.as_str(), "SELECT * FROM posts");
}
#[test]
fn test_reusable_builder_take() {
let mut builder = ReusableBuilder::new();
builder.push("test");
let taken = builder.take();
assert_eq!(taken, "test");
assert!(builder.is_empty());
}
#[test]
fn test_builder_pool() {
let pool = BuilderPool::new(4, 128);
let b1 = pool.get();
let b2 = pool.get();
let _b3 = pool.get();
let _b4 = pool.get();
assert!(pool.is_empty());
pool.put(b1);
pool.put(b2);
assert_eq!(pool.len(), 2);
}
#[test]
fn test_optimized_window_spec() {
use crate::types::SortOrder;
let spec = OptimizedWindowSpec::new()
.partition_by(["dept", "team"])
.order_by("salary", SortOrder::Desc)
.rows_unbounded_preceding();
let sql = spec.to_sql(crate::sql::DatabaseType::PostgreSQL);
assert!(sql.contains("PARTITION BY"));
assert!(sql.contains("ORDER BY"));
assert!(sql.contains("ROWS"));
}
}