Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #[macro_use]
- pub mod query_builder {
- //! SQL like generic query builder.
- //! 構文木の生成までを行うので、それをトラバースして必要な処理を行う。
- use std::rc::Rc;
- use std::fmt::Debug;
- use std::marker::PhantomData;
- /// Entityのフィールドを表す型が実装するメソッド
- pub trait Field: Debug {
- /// 演算子の右辺の型
- type Data: Debug;
- }
- /// クエリ全体を表すtrait。単なるマーク。
- pub trait Query<P, O>: Debug {
- fn walk(&self) -> O;
- }
- #[derive(Debug)]
- pub struct BoxedQuery<P, O> {
- pub query: Rc<Query<P, O>>,
- }
- // impl<P, O> Query<P, O> for BoxedQuery<P, O> {}
- impl<P, O> BoxedQuery<P, O> {
- fn new<Q: Query<P, O> + 'static>(query: Q) -> BoxedQuery<P, O> {
- BoxedQuery {
- query: Rc::new(query),
- }
- }
- }
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct And<P, O, Q1: Query<P, O>, Q2: Query<P, O>>(pub Q1, pub Q2, PhantomData<P>, PhantomData<O>);
- // impl<P, O, Q1: Query<P, O>, Q2: Query<P, O>> Query<P, O> for And<P, O, Q1, Q2> {}
- impl<P, O, T, S> And<P, O, T, S>
- where
- T: Query<P, O>,
- S: Query<P, O>,
- {
- pub fn new(l: T, r: S) -> And<P, O, T, S> {
- And(l, r, PhantomData, PhantomData)
- }
- }
- impl<P, O, T, S> And<P, O, T, S>
- where
- T: Query<P, O>,
- S: Query<P, O>,
- Self: Query<P, O>,
- {
- pub fn and<Q: Query<P, O>>(self, rhs: Q) -> And<P, O, Self, Q> {
- And::new(self, rhs)
- }
- }
- impl<P, O, T, S> And<P, O, T, S>
- where
- P: 'static,
- O: 'static,
- T: Query<P, O> + 'static,
- S: Query<P, O> + 'static,
- Self: Query<P, O>,
- {
- pub fn into_boxed(self) -> BoxedQuery<P, O> {
- BoxedQuery::new(self)
- }
- }
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct Or<P, O, T: Query<P, O>, S: Query<P, O>>(pub T, pub S, PhantomData<P>, PhantomData<O>);
- // impl<P, O, T: Query<P, O>, S: Query<P, O>> Query<P, O> for Or<P, O, T, S> {}
- impl<P, O, T, S> Or<P, O, T, S>
- where
- T: Query<P, O>,
- S: Query<P, O>,
- {
- pub fn new(l: T, r: S) -> Or<P, O, T, S> {
- Or(l, r, PhantomData, PhantomData)
- }
- }
- impl<P, O, T: Query<P, O>, S: Query<P, O>> Or<P, O, T, S>
- where
- T: Query<P, O>,
- S: Query<P, O>,
- Self: Query<P, O>,
- {
- pub fn or<Q: Query<P,O>>(self, rhs: Q) -> Or<P, O, Self, Q> {
- Or::new(self, rhs)
- }
- }
- impl<P, O, T: Query<P, O> + 'static, S: Query<P, O> + 'static> Or<P, O, T, S>
- where
- P: 'static,
- O: 'static,
- T: Query<P, O> + 'static,
- S: Query<P, O> + 'static,
- Self: Query<P, O>,
- {
- pub fn into_boxed(self) -> BoxedQuery<P, O> {
- BoxedQuery::new(self)
- }
- }
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct Not<P, O, T: Query<P, O>>(pub T, PhantomData<P>, PhantomData<O>);
- // impl<P, O, T: Query<P, O>> Query<P,O> for Not<P, O, T> {}
- pub fn not<P, O, T: Query<P,O>>(q: T) -> Not<P, O, T> {
- Not(q, PhantomData, PhantomData)
- }
- impl<P, O, T> Not<P, O, T>
- where
- T: Query<P, O>,
- Self: Query<P, O>,
- {
- pub fn and<Q: Query<P, O>>(self, rhs: Q) -> And<P, O, Self, Q> {
- And::new(self, rhs)
- }
- pub fn or<Q: Query<P, O>>(self, rhs: Q) -> Or<P, O, Self, Q> {
- Or::new(self, rhs)
- }
- }
- impl<P, O, T> Not<P, O, T>
- where
- P: 'static,
- O: 'static,
- T: Query<P, O> + 'static,
- Self: Query<P, O>,
- {
- pub fn into_boxed(self) -> BoxedQuery<P, O> {
- BoxedQuery::new(self)
- }
- }
- #[derive(Debug, Clone)]
- pub enum Operator<T: Field> {
- /// `==`
- Eq(T, T::Data),
- /// `!=`
- NotEq(T, T::Data),
- /// `>`
- Gt(T, T::Data),
- /// `>=`
- Ge(T, T::Data),
- /// `<`
- Lt(T, T::Data),
- /// `<=`
- Le(T, T::Data),
- /// Like SQL `between`
- Between(T, T::Data, T::Data),
- /// Like SQL `like`
- Like(T, T::Data),
- Any(T, Vec<T::Data>),
- NotAny(T, Vec<T::Data>),
- }
- // impl<P, O, T: Field + Debug + 'static> Query<P, O> for Operator<T> {}
- impl<T> Operator<T>
- where
- T: Field + Debug + 'static,
- {
- pub fn and<P, O, Q>(self, rhs: Q) -> And<P, O, Self, Q>
- where
- Q: Query<P, O>,
- Self: Query<P, O>,
- {
- And::new(self, rhs)
- }
- pub fn or<P, O, Q>(self, rhs: Q) -> Or<P, O, Self, Q>
- where
- Q: Query<P, O>,
- Self: Query<P, O>,
- {
- Or::new(self, rhs)
- }
- pub fn into_boxed<P, O>(self) -> BoxedQuery<P, O>
- where
- Self: Query<P, O>,
- {
- BoxedQuery::new(self)
- }
- }
- /// Entityのfield型に生やすメソッド。演算子。
- pub trait OperatorMethod: Field + Sized {
- /// `==`
- fn eq(self, rhs: Self::Data) -> Operator<Self> {
- Operator::Eq(self, rhs)
- }
- /// `!=`
- fn not_eq(self, rhs: Self::Data) -> Operator<Self> {
- Operator::NotEq(self, rhs)
- }
- /// `>`
- fn gt(self, rhs: Self::Data) -> Operator<Self> {
- Operator::Gt(self, rhs)
- }
- /// `>=`
- fn ge(self, rhs: Self::Data) -> Operator<Self> {
- Operator::Ge(self, rhs)
- }
- /// `<`
- fn lt(self, rhs: Self::Data) -> Operator<Self> {
- Operator::Lt(self, rhs)
- }
- /// `<=`
- fn le(self, rhs: Self::Data) -> Operator<Self> {
- Operator::Le(self, rhs)
- }
- /// Like SQL `between`
- fn between(self, from: Self::Data, until: Self::Data) -> Operator<Self> {
- Operator::Between(self, from, until)
- }
- /// Like SQL `like`
- fn like(self, rhs: Self::Data) -> Operator<Self> {
- Operator::Like(self, rhs)
- }
- fn any(self, rhs: Vec<Self::Data>) -> Operator<Self> {
- Operator::Any(self, rhs)
- }
- fn not_any(self, rhs: Vec<Self::Data>) -> Operator<Self> {
- Operator::NotAny(self, rhs)
- }
- }
- #[macro_export]
- macro_rules! query_info {
- ( $( @ $item:item )* $name:ident { $( $field:ident : $field_type:ty ),* $(,)* } ) => {
- pub mod $name {
- pub mod query {
- use crate::query_builder::*;
- $( $item )*
- $(
- #[allow(dead_code, non_camel_case_types)]
- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
- pub struct $field;
- impl Field for $field {
- type Data = $field_type;
- }
- impl OperatorMethod for $field {}
- )*
- }
- }
- }
- }
- }
- mod entity {
- use chrono::prelude::*;
- pub trait FieldName {
- fn name(&self) -> &str;
- }
- macro_rules! impl_field_name {
- ( $( $field:ty > $name:tt ),* $(,)* ) => {
- $(
- impl FieldName for $field {
- fn name(&self) -> &str {
- $name
- }
- }
- )*
- }
- }
- #[derive(Debug)]
- pub struct User {
- pub id: u64,
- pub id_name: String,
- pub display_name: String,
- pub password: String,
- pub status: u8,
- pub created_at: DateTime<Utc>,
- pub updated_at: DateTime<Utc>,
- }
- query_info! {
- @use chrono::prelude::*;
- user {
- id: u64,
- id_name: String,
- display_name: String,
- password: String,
- status: u8,
- created_at: DateTime<Utc>,
- updated_at: DateTime<Utc>,
- }
- }
- impl_field_name!(
- self::user::query::id > "id",
- self::user::query::id_name > "id_name",
- self::user::query::display_name > "display_name",
- self::user::query::password > "password",
- self::user::query::status > "status",
- self::user::query::created_at > "created_at",
- self::user::query::updated_at > "updated_at",
- );
- pub struct User2 {
- pub id: u64,
- pub id_name: String,
- pub display_name: String,
- pub password: String,
- pub status: u8,
- pub created_at: DateTime<Utc>,
- pub updated_at: DateTime<Utc>,
- }
- query_info! {
- @use chrono::prelude::*;
- user2 {
- id: u64,
- id_name: String,
- display_name: String,
- password: String,
- status: u8,
- created_at: DateTime<Utc>,
- updated_at: DateTime<Utc>,
- }
- }
- impl_field_name!(
- self::user2::query::id > "id",
- self::user2::query::id_name > "id_name",
- self::user2::query::display_name > "display_name",
- self::user2::query::password > "password",
- self::user2::query::status > "status",
- self::user2::query::created_at > "created_at",
- self::user2::query::updated_at > "updated_at",
- );
- }
- mod repository {
- pub mod user {
- //! ストレージ上での格納形式に対応する型はここで定義。
- use crate::entity::{FieldName, User};
- use crate::query_builder::{And, Field, Not, Operator, Or, Query, BoxedQuery};
- use failure::bail;
- use std::fmt::Display;
- pub struct Repository;
- impl Repository {
- // 複数entityに対してはtraitを使った型による条件分岐が必要。
- pub fn search<Q>(&self, query: Q) -> Result<Vec<User>, failure::Error>
- where
- Q: Query<StringProcessor, String>,
- {
- println!("Build: {}", query.walk());
- bail!("hoge")
- }
- }
- pub trait Processor<T> {
- type Input;
- type Output;
- fn process(input: &Self::Input) -> Self::Output;
- }
- #[derive(Debug)]
- pub struct StringProcessor;
- impl<T> Processor<Operator<T>> for StringProcessor
- where
- T: Field + FieldName,
- T::Data: Display,
- {
- type Input = Operator<T>;
- type Output = String;
- fn process(input: &Operator<T>) -> String {
- use itertools::join;
- match input {
- Operator::Eq(field, rhs) => format!("{} = {}", field.name(), rhs),
- Operator::NotEq(field, rhs) => format!("{} != {}", field.name(), rhs),
- Operator::Gt(field, rhs) => format!("{} > {}", field.name(), rhs),
- Operator::Ge(field, rhs) => format!("{} >= {}", field.name(), rhs),
- Operator::Lt(field, rhs) => format!("{} < {}", field.name(), rhs),
- Operator::Le(field, rhs) => format!("{} <= {}", field.name(), rhs),
- Operator::Between(field, from, until) => {
- format!("{} between {} and {}", field.name(), from, until)
- }
- Operator::Like(field, rhs) => format!("{} like '{}'", field.name(), rhs),
- Operator::Any(field, rhs) => format!("{} in ({})", field.name(), join(rhs.iter(), ", ")),
- Operator::NotAny(field, rhs) => format!("{} like ({})", field.name(), join(rhs.iter(), ", ")),
- }
- }
- }
- impl<Q1, Q2> Processor<And<StringProcessor, String, Q1, Q2>> for StringProcessor
- where
- Q1: Query<StringProcessor, String>,
- Q2: Query<StringProcessor, String>,
- {
- type Input = (String, String);
- type Output = String;
- fn process(input: &(String, String)) -> String {
- format!("({}) and ({})", input.0, input.1)
- }
- }
- impl<Q1, Q2> Processor<Or<StringProcessor, String, Q1, Q2>> for StringProcessor
- where
- Q1: Query<StringProcessor, String>,
- Q2: Query<StringProcessor, String>,
- {
- type Input = (String, String);
- type Output = String;
- fn process(input: &(String, String)) -> String {
- format!("({}) or ({})", input.0, input.1)
- }
- }
- impl<Q1> Processor<Not<StringProcessor, String, Q1>> for StringProcessor
- where
- Q1: Query<StringProcessor, String>,
- {
- type Input = String;
- type Output = String;
- fn process(input: &String) -> String {
- format!("not ({})", input)
- }
- }
- impl<T> Query<StringProcessor, String> for Operator<T>
- where
- T: Field + FieldName,
- T::Data: Display,
- {
- fn walk(&self) -> String {
- <StringProcessor as Processor<Self>>::process(self)
- }
- }
- impl<Q1, Q2> Query<StringProcessor, String> for And<StringProcessor, String, Q1, Q2>
- where
- Q1: Query<StringProcessor, String>,
- Q2: Query<StringProcessor, String>,
- {
- fn walk(&self) -> String {
- <StringProcessor as Processor<Self>>::process(&(self.0.walk(), self.1.walk()))
- }
- }
- impl<Q1, Q2> Query<StringProcessor, String> for Or<StringProcessor, String, Q1, Q2>
- where
- Q1: Query<StringProcessor, String>,
- Q2: Query<StringProcessor, String>,
- {
- fn walk(&self) -> String {
- <StringProcessor as Processor<Self>>::process(&(self.0.walk(), self.1.walk()))
- }
- }
- impl<Q1> Query<StringProcessor, String> for Not<StringProcessor, String, Q1>
- where
- Q1: Query<StringProcessor, String>,
- {
- fn walk(&self) -> String {
- <StringProcessor as Processor<Self>>::process(&self.0.walk())
- }
- }
- impl Query<StringProcessor, String> for BoxedQuery<StringProcessor, String>
- {
- fn walk(&self) -> String {
- self.query.as_ref().walk()
- }
- }
- }
- }
- fn main() -> Result<(), failure::Error> {
- use crate::entity::user2::query::*;
- use crate::query_builder::*;
- use crate::repository;
- use chrono::prelude::*;
- println!("===== static query =====");
- let query = id
- .eq(32)
- .and(created_at.eq(Utc::now()).or(updated_at.eq(Utc::now())));
- let query = display_name.like("foo".into()).or(query);
- println!("{:?}", query);
- let _ = repository::user::Repository.search(query);
- let _ = repository::user::Repository.search(status.eq(1));
- println!("===== dynamic query =====");
- let mut query = id.eq(1).and(id.eq(2)).into_boxed();
- if rand::random::<u8>() < 127 {
- query = created_at.eq(Utc::now()).or(query).into_boxed();
- }
- query = And::new(
- updated_at.eq(Utc::now()),
- if rand::random::<u8>() % 2 == 0 {
- status.eq(1).or(query).into_boxed()
- } else {
- status.eq(2).or(query).into_boxed()
- }
- ).and(id_name.any(vec!["yamada".into(), "satoh".into()])).into_boxed();
- println!("{:?}", query);
- let _ = repository::user::Repository.search(query);
- Ok(())
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement