Skip to content

Types

Now after creating our own Schema, DataProvider and Standard function, most of the cases can be handled at this level, but what if you want to store the values in other type than Int, Float, Text ...etc, for example you want to create a type called Song or Video and then you can run a query asking what is the longest video in each directory? or what if you want to create a function that take a TreeNode and return number children nodes?


To allow this level of customization in type system we need to make it easy to integrate with the parser, type checker and execution engine? we can't support every possible type in the engine.

But .... What if we support 0 types 🤔 .... what if the types are not primitives in the engine but defined in the Standard library or the SDK?


Moving the Types from the Engine to the SDK level

This idea is inspired by Chris Lattner design in Mojo and Swift programming languages, by default the Engine has some built in predefined types like Integer, Float, Boolean ...etc, it know very well what operators can work with them, how can cast them, this can work well and programming languages gives you the ability to compose types and create Structured data types like List, File, GamePlayer ...etc, but what if we create a way to define your own type, and define how the Engine or the Compiler can deal with it, define what operators work with it, in this case we can define Integer, Float as part of the Standard library or in our case the SDK, and that also gives the SDK user the power to built any type he want to make it integrated well in all part of the Query Engine.

Lets for example say we want to create type called IntPair and define attributes and operators for it,

Creating a custom DataType

use std::any::Any;

use super::base::DataType;

#[derive(Clone)]
pub struct IntPairType;

impl DataType for IntPairType {

    /// Define the literal representation for our new type
    fn literal(&self) -> String {
        "IntPair".to_string()
    }

    /// Define how to compare this type with others
    fn equals(&self, other: &Box<dyn DataType>) -> bool {
        let int_pair_type: Box<dyn DataType> = Box::new(self.clone());
        other.is_any() || other.is_int() || other.is_variant_contains(&int_pair_type)
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    /// Allow using the `+` operator between two IntPair's
    fn can_perform_add_op_with(&self) -> Vec<Box<dyn DataType>> {
        vec![Box::new(IntPairType)]
    }

    /// Define that the result of IntPair + IntPair will be another IntPair
    fn add_op_result_type(&self, _other: &Box<dyn DataType>) -> Box<dyn DataType> {
        Box::new(IntPairType)
    }

    /// Define any other operators like -, *, %, >, ...etc
}

Now if we create a new Function with Signature that accept IntPair and we pass Int, it will report an error, but now we created a Type but to create a Value with this type we need to create a Custom Value too (Creating the IntPairValue as Custom value).

Register your type in TypesTable

If you want to support using your type in explicit casting feature, for example

SELECT CAST(value AS IntPairType)

You must register your type to the TypesTable component and pass it to the Environment for example

let types_table = TypesTable::new();
types_table.register("intpair", Box::new(IntPairType));

let env = Environment::new();
env.register_types_table(types_table);