-
Notifications
You must be signed in to change notification settings - Fork 67
Implement .find()
on Entity
#1437
Comments
We would have to generate these functions vec![OrderEntity::amount::equals(5), OrderEntity::address::equals("0x00001")] And they would produce |
|
I have to say that I'm not the greatest fan of using a vector for multiple parameters. I'd much rather a cleaner "object-based" syntax similar to how selection parameters are done in Prisma. However, I don't think that we can easily do that right now given the fact that Rust doesn't allow for anonymous structs; additionally, we'd want type safety wherever possible so doing something with JSON would probably not be worth the headache. In any case, I think this style is probably the best type-safe way and will work for now in order to not block the predicate support work. Perhaps in the future, we could leverage the builder pattern to do something in a more functional style:
...or something to that effect. |
let found_order = OrderEntity.find(OrderEntity::amount::gt(5).and(OrderEntity::address::eq("0x00001"));
|
I like that idea even better and we should aim for that if possible. The only hesitation I have is how we would support operator combination, if at all. From the Prisma docs: const result = await prisma.user.findMany({
where: {
OR: [
{
email: {
endsWith: 'prisma.io',
},
},
{ email: { endsWith: 'gmail.com' } },
],
NOT: {
email: {
endsWith: 'hotmail.com',
},
},
},
select: {
email: true,
},
}) I'd imagine that the vector style would have to make a reappearance here, but we can cross the proverbial bridge when we get to it. |
@deekerno The above ☝🏼 would be written as User.find(
User::email::ends_with("prisma.io")
.or(User::email::ends_with("gmail.com")
.and(User::email::not_ends_with("hotmail.com")
);
|
Again, I'm not married to my method so feel free to push back |
I don't think this is valid Rust:
I was thinking about something like this: pub struct Constraint<T> {
constraint: String,
phantom: std::marker::PhantomData<T>,
}
pub trait Field<T> {
// TODO: Type needs to be convertible SQL fragment
type Type: Sized + std::fmt::Debug;
const NAME: &'static str;
fn equals(val: Self::Type) -> Constraint<T> {
Constraint {
constraint: format!("{} = '{:?}'", Self::NAME, val),
phantom: std::marker::PhantomData,
}
}
} Then: struct Order {
id: usize,
amount: i32,
}
struct OrderIdField;
impl Field<Order> for OrderIdField {
type Type = usize;
const NAME: &'static str = "id";
}
struct OrderAmountField;
impl Field<Order> for OrderAmountField {
type Type = i32;
const NAME: &'static str = "amount";
} And using the pub trait Entity<'a>: Sized + PartialEq + Eq + std::fmt::Debug {
fn find(constraints: Vec<Constraint<Self>>) -> String {
let mut buf = String::new();
for c in constraints {
if !buf.is_empty() {
buf += " AND ";
}
buf += &c.constraint
}
buf
}
} So, we'd have something like: find(vec![OrderIdField::eq(1usize), OrderAmountField::lt(123i32)]) Of course, we can instead have a simple AST: pub struct Constraint<T> {
constraint: String,
phantom: std::marker::PhantomData<T>,
}
impl<T> Constraint<T> {
pub fn and(self, other: impl Into<Expr<T>>) -> Expr<T> {
Expr::And(Box::new(Expr::Leaf(self)), Box::new(other.into()))
}
}
impl<T> From<Constraint<T>> for Expr<T> {
fn from(c: Constraint<T>) -> Expr<T> {
Expr::Leaf(c)
}
}
pub enum Expr<T> {
And(Box<Expr<T>>, Box<Expr<T>>),
Leaf(Constraint<T>)
}
impl<T> Expr<T> {
pub fn and(self, other: impl Into<Expr<T>>) -> Expr<T> {
Expr::And(Box::new(self), Box::new(other.into()))
}
} An example: let x: Expr<Block> = {
let e1: Constraint<Block> = BlockIdField::equals(block_frag.id.clone());
let e2: Constraint<Block> = BlockIdField::equals(block_frag.id.clone());
// constraint and constraint = expr
e1.and(e2)
};
let y: Expr<Block> = {
let e = BlockIdField::equals(block_frag.id.clone());
// constraint and expr = expr
// e.and(x)
// or expr and constraint = expr
x.and(e)
};
let z: Expr<Block> = {
let e1: Constraint<Block> = BlockIdField::equals(block_frag.id.clone());
let e2: Constraint<Block> = BlockIdField::equals(block_frag.id.clone());
e1.and(e2)
};
// expr and expr = expr
let v = z.and(y); It may look a little convoluted, but it shows that these can be easily mixed and matched. Another: let complex = BlockIdField::equals(block_frag.id.clone())
.and(BlockConsensusField::equals(consensus.id.clone()))
.and(BlockHeaderField::equals(header.id.clone())); |
|
@ra0x3, the AST is an internal implementation detail and pretty simple. Avoiding it wouldn't make anything simpler. Some questions:
The first would translate to
Some form of let arr2 = TransactionInputsField::any(
TransactionInputsField::equals(
SizedAsciiString::new("".to_string()).unwrap(),
)
.and(TransactionInputsField::equals(
SizedAsciiString::new("xyz".to_string()).unwrap(),
)),
); This is a little verbose but can be shortened to: let arr2: ArrayExpr<Transaction> = {
type T = TransactionInputsField;
T::any(
T::equals(SizedAsciiString::new("".to_string()).unwrap())
.and(T::equals(SizedAsciiString::new("xyz".to_string()).unwrap())),
)
}; |
Some options for structuring the DSL for writing constraint expressions: Using modules: let c: Constraint<ScriptTransaction> = {
use script_transaction::*;
// ((gas_price < '100' OR gas_price > '200') AND gas_limit = '1000')
gas_price()
.lt(100i32)
.or(gas_price().gt(200))
.and(gas_limit().eq(1000))
}; Using associated functions: let z = ScriptTransaction::gas_price()
.lt(100i32)
.or(ScriptTransaction::gas_price().gt(200))
.and(ScriptTransaction::gas_limit().eq(1000)); Which could be shortened with: use ScriptTransactionResult as T;
T::gas_price()... Having a module with these functions seems cleaner. I still need to think about how array fields would fit into this. |
let result = ScriptTransaction.find(ScriptTransaction::gas_price()
.lt(100i32)
.or(ScriptTransaction::gas_price().gt(200))
.and(ScriptTransaction::gas_limit().eq(1000))); |
|
Context
Entity
What we currently have
impl Entity
in ourfuel_indexer_macros::decoder
module as follows:What we want
#const_fields
would be a series of tokens for each field on theEntity
as aconst
So with the following schema
You'd get the following
const
fields on theEntity
This ☝🏼 would allow us to build a
.find()
method as follows:Entity::field::equals(value)
returnsString
fragments that can be aggregated into an arbitrary singleSELECT
query and easily passed to the DB via the FFIFuture work
SQLFragment
trait to support other things such asThe text was updated successfully, but these errors were encountered: