use crate::identifier::Identifier;
use crate::operation::Expressive;
use crate::typed_expression::TypedExpression;
use std::collections::HashSet;
use std::marker::PhantomData;
use surreal_client::types::{Any, SurrealType, TypeInfo};
use vantage_expressions::Expression;
use vantage_table::{ColumnFlag, ColumnLike};
#[derive(Debug, Clone)]
pub struct SurrealColumn<Type: SurrealType = Any> {
name: String,
alias: Option<String>,
flags: HashSet<ColumnFlag>,
type_info: Box<dyn TypeInfo>,
_phantom: PhantomData<Type>,
}
impl<T: SurrealType> SurrealColumn<T> {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
alias: None,
flags: HashSet::new(),
type_info: T::as_type_info(),
_phantom: PhantomData,
}
}
pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
self.alias = Some(alias.into());
self
}
pub fn with_flags(mut self, flags: &[ColumnFlag]) -> Self {
self.flags.extend(flags.iter().cloned());
self
}
pub fn identifier(&self) -> Identifier {
Identifier::new(&self.name)
}
pub fn expr(&self) -> TypedExpression<T> {
TypedExpression::new(self.identifier().expr())
}
pub fn get_type_info(&self) -> &dyn TypeInfo {
self.type_info.as_ref()
}
}
impl SurrealColumn<Any> {
pub fn new_any(name: impl Into<String>) -> Self {
Self::new(name)
}
pub fn into_type<NewType: SurrealType>(self) -> SurrealColumn<NewType> {
SurrealColumn {
name: self.name,
alias: self.alias,
flags: self.flags,
type_info: NewType::as_type_info(),
_phantom: PhantomData,
}
}
}
impl<T: SurrealType> SurrealColumn<T> {
pub fn into_any(self) -> SurrealColumn<Any> {
SurrealColumn {
name: self.name,
alias: self.alias,
flags: self.flags,
type_info: self.type_info.clone(),
_phantom: PhantomData,
}
}
}
impl<T: SurrealType> From<SurrealColumn<T>> for TypedExpression<T> {
fn from(column: SurrealColumn<T>) -> Self {
TypedExpression::new(column.identifier().expr())
}
}
impl<T: SurrealType> From<&SurrealColumn<T>> for TypedExpression<T> {
fn from(column: &SurrealColumn<T>) -> Self {
TypedExpression::new(column.identifier().expr())
}
}
impl<T: SurrealType> From<SurrealColumn<T>> for Expression {
fn from(column: SurrealColumn<T>) -> Self {
column.identifier().expr()
}
}
impl<T: SurrealType> Expressive for SurrealColumn<T> {
fn expr(&self) -> Expression {
self.identifier().expr()
}
}
impl<T: SurrealType> ColumnLike for SurrealColumn<T> {
fn name(&self) -> &str {
&self.name
}
fn alias(&self) -> Option<&str> {
self.alias.as_deref()
}
fn expr(&self) -> Expression {
self.identifier().expr()
}
fn flags(&self) -> HashSet<ColumnFlag> {
self.flags.clone()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn get_type(&self) -> &'static str {
self.type_info.type_name()
}
}
impl From<&str> for SurrealColumn<Any> {
fn from(name: &str) -> Self {
Self::new(name)
}
}
impl From<String> for SurrealColumn<Any> {
fn from(name: String) -> Self {
Self::new(name)
}
}
impl From<vantage_table::Column> for SurrealColumn<Any> {
fn from(column: vantage_table::Column) -> Self {
Self {
name: column.name().to_string(),
alias: column.alias().map(|s| s.to_string()),
flags: column.flags().clone(),
type_info: Any::as_type_info(),
_phantom: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_surreal_column_basic() {
let col = SurrealColumn::<Any>::new("user_name");
assert_eq!(col.name(), "user_name");
assert_eq!(ColumnLike::alias(&col), None);
}
#[test]
fn test_surreal_column_with_alias() {
let col = SurrealColumn::<Any>::new("user_name").with_alias("name");
assert_eq!(col.name(), "user_name");
assert_eq!(ColumnLike::alias(&col), Some("name"));
}
#[test]
fn test_surreal_column_expr() {
let col = SurrealColumn::<Any>::new("user_name");
let expr = col.expr();
assert_eq!(expr.preview(), "user_name");
}
#[test]
fn test_surreal_column_reserved_keyword() {
let col = SurrealColumn::<Any>::new("SELECT");
let expr = col.expr();
assert_eq!(expr.preview(), "⟨SELECT⟩");
}
#[test]
fn test_from_str() {
let col: SurrealColumn = "email".into();
assert_eq!(col.name(), "email");
}
#[test]
fn test_from_vantage_column() {
let vantage_col = vantage_table::Column::new("test").with_alias("test_alias");
let surreal_col: SurrealColumn = vantage_col.into();
assert_eq!(surreal_col.name(), "test");
assert_eq!(ColumnLike::alias(&surreal_col), Some("test_alias"));
}
#[test]
fn test_surreal_column_with_flags() {
let col = SurrealColumn::<Any>::new("email").with_flags(&[ColumnFlag::Mandatory]);
assert!(col.flags().contains(&ColumnFlag::Mandatory));
assert_eq!(col.flags().len(), 1);
}
#[test]
fn test_surreal_column_no_flags() {
let col = SurrealColumn::<Any>::new("optional_field");
assert!(col.flags().is_empty());
}
#[test]
fn test_from_vantage_column_with_flags() {
let vantage_col = vantage_table::Column::new("test")
.with_flags(&[ColumnFlag::Mandatory])
.with_alias("test_alias");
let surreal_col: SurrealColumn = vantage_col.into();
assert_eq!(surreal_col.name(), "test");
assert_eq!(ColumnLike::alias(&surreal_col), Some("test_alias"));
assert!(surreal_col.flags().contains(&ColumnFlag::Mandatory));
}
}
#[cfg(test)]
mod column_conversion_tests {
use super::*;
#[test]
fn test_column_to_typed_expression_any() {
let col = SurrealColumn::<Any>::new("field");
let typed: TypedExpression<Any> = col.into();
assert_eq!(typed.type_name(), "any");
}
#[test]
fn test_column_to_typed_expression_i64() {
let col = SurrealColumn::<i64>::new("age");
let typed: TypedExpression<i64> = col.into();
assert_eq!(typed.type_name(), "int");
}
#[test]
fn test_column_to_typed_expression_string() {
let col = SurrealColumn::<String>::new("name");
let typed: TypedExpression<String> = col.into();
assert_eq!(typed.type_name(), "string");
}
#[test]
fn test_column_ref_to_typed_expression() {
let col = SurrealColumn::<i64>::new("price");
let typed: TypedExpression<i64> = (&col).into();
assert_eq!(typed.type_name(), "int");
}
#[test]
fn test_typed_expression_operations_from_column() {
let col1 = SurrealColumn::<i64>::new("age");
let col2 = SurrealColumn::<i64>::new("min_age");
let typed1: TypedExpression<i64> = col1.into();
let typed2: TypedExpression<i64> = col2.into();
let expr = typed1.eq(typed2);
assert_eq!(expr.preview(), "age = min_age");
}
#[test]
fn test_any_column_with_values() {
let col = SurrealColumn::<Any>::new("field");
let typed: TypedExpression<Any> = col.into();
let expr1 = typed.eq_value(42);
assert_eq!(expr1.preview(), "field = 42");
let col2 = SurrealColumn::<Any>::new("field");
let typed2: TypedExpression<Any> = col2.into();
let expr2 = typed2.eq_value("hello");
assert_eq!(expr2.preview(), "field = \"hello\"");
}
#[test]
fn test_column_direct_operations() {
use crate::prelude::*;
let id_col = SurrealColumn::<String>::new("id");
let expr = id_col.eq("user:123");
assert_eq!(expr.preview(), "id = \"user:123\"");
}
}