Skip to content

Commit 22c5318

Browse files
committed
refactor(WIP)!: querybuilder now is made of AST types for query generation
1 parent 30ccc3b commit 22c5318

11 files changed

Lines changed: 510 additions & 14 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use crate::connection::database_type::DatabaseType;
2+
use crate::query::parameters::QueryParameter;
3+
use crate::query::querybuilder::syntax::column::ColumnRef;
4+
use crate::query::querybuilder::syntax::having::HavingClause;
5+
use crate::query::querybuilder::syntax::join::JoinClause;
6+
use crate::query::querybuilder::syntax::order::OrderByClause;
7+
use crate::query::querybuilder::syntax::clause::ConditionClause;
8+
use crate::query::querybuilder::syntax::table_metadata::TableMetadata;
9+
use crate::query::querybuilder::syntax::query_kind::QueryKind;
10+
11+
/// Base AST for common parts
12+
pub struct BaseAst<'a> {
13+
pub kind: QueryKind,
14+
pub table: TableMetadata,
15+
pub conditions: Vec<ConditionClause<'a>>,
16+
pub params: Vec<&'a dyn QueryParameter>,
17+
pub database_type: DatabaseType,
18+
}
19+
20+
impl<'a> BaseAst<'a> {
21+
pub fn new(kind: QueryKind, table: TableMetadata, database_type: DatabaseType) -> Self {
22+
Self {
23+
kind,
24+
table,
25+
conditions: Vec::new(),
26+
params: Vec::new(),
27+
database_type,
28+
}
29+
}
30+
}
31+
32+
/// Select AST
33+
pub struct SelectAst<'a> {
34+
pub base: BaseAst<'a>,
35+
pub columns: Vec<ColumnRef<'a>>,
36+
pub joins: Vec<JoinClause<'a>>,
37+
pub group_by: Vec<&'a str>,
38+
pub having: Vec<HavingClause<'a>>,
39+
pub order_by: Vec<OrderByClause<'a>>,
40+
pub limit: Option<u64>,
41+
pub offset: Option<u64>,
42+
}
43+
44+
impl<'a> SelectAst<'a> {
45+
pub fn new(table: TableMetadata, db: DatabaseType) -> Self {
46+
Self {
47+
base: BaseAst::new(QueryKind::Select, table, db),
48+
columns: Vec::new(),
49+
joins: Vec::new(),
50+
group_by: Vec::new(),
51+
having: Vec::new(),
52+
order_by: Vec::new(),
53+
limit: None,
54+
offset: None,
55+
}
56+
}
57+
}
58+
59+
pub struct InsertAst<'a> {
60+
pub base: BaseAst<'a>,
61+
pub columns: Vec<&'a str>,
62+
pub values: Vec<&'a dyn QueryParameter>,
63+
}
64+
65+
impl<'a> InsertAst<'a> {
66+
pub fn new(table: TableMetadata, db: DatabaseType) -> Self {
67+
Self { base: BaseAst::new(QueryKind::Insert, table, db), columns: Vec::new(), values: Vec::new() }
68+
}
69+
}
70+
71+
pub struct UpdateAst<'a> {
72+
pub base: BaseAst<'a>,
73+
pub set_clauses: Vec<(&'a str, &'a dyn QueryParameter)>,
74+
}
75+
76+
impl<'a> UpdateAst<'a> {
77+
pub fn new(table: TableMetadata, db: DatabaseType) -> Self {
78+
Self { base: BaseAst::new(QueryKind::Update, table, db), set_clauses: Vec::new() }
79+
}
80+
}
81+
82+
pub struct DeleteAst<'a> {
83+
pub base: BaseAst<'a>,
84+
}
85+
86+
impl<'a> DeleteAst<'a> {
87+
pub fn new(table: TableMetadata, db: DatabaseType) -> Self {
88+
Self { base: BaseAst::new(QueryKind::Delete, table, db) }
89+
}
90+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod contracts;
22
pub mod types;
33
pub mod syntax;
4+
mod ast;
45

56
pub use self::{contracts::*, types::*};

canyon_core/src/query/querybuilder/syntax/clause.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub enum ConditionClauseKind {
1515
Where,
1616
And,
1717
Or,
18-
In // TODO: should this one be a Comp instead?
18+
In
1919
}
2020

2121
impl<'a> AsRef<str> for ConditionClauseKind {
@@ -27,4 +27,4 @@ impl<'a> AsRef<str> for ConditionClauseKind {
2727
ConditionClauseKind::Or => "OR",
2828
}
2929
}
30-
}
30+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#[derive(Debug, Clone)]
2+
pub struct ColumnRef<'a> {
3+
pub name: &'a str,
4+
pub alias: Option<&'a str>,
5+
}
6+
7+
impl<'a> ColumnRef<'a> {
8+
pub fn new(name: &'a str) -> Self {
9+
Self { name, alias: None }
10+
}
11+
pub fn aliased(name: &'a str, alias: &'a str) -> Self {
12+
Self { name, alias: Some(alias) }
13+
}
14+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
use crate::connection::database_type::DatabaseType;
2+
use crate::query::parameters::QueryParameter;
3+
use crate::query::querybuilder::syntax::tokens::{SqlToken};
4+
use crate::query::querybuilder::ast::{BaseAst, SelectAst, UpdateAst, InsertAst, DeleteAst};
5+
use crate::query::querybuilder::syntax::clause::ConditionClauseKind;
6+
7+
// Trait each AST piece can optionally implement to emit tokens (we'll implement per-AST)
8+
pub trait EmitTokens<'a> {
9+
fn emit_tokens(&self, out: &mut Vec<SqlToken<'a>>);
10+
}
11+
12+
use crate::query::querybuilder::syntax::table_metadata::TableMetadata;
13+
use crate::query::querybuilder::syntax::query_kind::QueryKind;
14+
use crate::query::querybuilder::syntax::column::ColumnRef;
15+
use crate::query::querybuilder::syntax::join::JoinClause;
16+
use crate::query::querybuilder::syntax::order::OrderByClause;
17+
use crate::query::querybuilder::syntax::having::HavingClause;
18+
use crate::query::querybuilder::syntax::clause::ConditionClause;
19+
20+
// Helper: emit table
21+
fn emit_table<'a>(table: &TableMetadata, out: &mut Vec<SqlToken<'a>>) {
22+
if let Some(s) = &table.schema {
23+
out.push(SqlToken::Ident(s));
24+
out.push(SqlToken::Symbol("."));
25+
}
26+
out.push(SqlToken::Ident(&table.name));
27+
}
28+
29+
impl<'a> EmitTokens<'a> for BaseAst<'a> {
30+
fn emit_tokens(&self, out: &mut Vec<SqlToken<'a>>) {
31+
// query kind handled by caller usually
32+
// emit WHERE/conditions if present (deferred placeholders handled by caller)
33+
if !self.conditions.is_empty() {
34+
for (i, cond) in self.conditions.iter().enumerate() {
35+
// prefix: first cond -> WHERE else -> AND / OR / IN kind
36+
let prefix = if i == 0 { cond.kind.as_str() } else { cond.kind.as_str() };
37+
out.push(SqlToken::Keyword(prefix));
38+
out.push(SqlToken::Ident(cond.column_name));
39+
// for IN, operator text already is "IN", and we will add placeholders externally
40+
if cond.kind == ConditionClauseKind::In {
41+
out.push(SqlToken::Operator("IN"));
42+
out.push(SqlToken::Symbol("("));
43+
// placeholders will be appended by the caller (because IN has multiple params)
44+
out.push(SqlToken::Symbol(")"));
45+
} else {
46+
out.push(SqlToken::Operator(cond.operator.as_str()));
47+
// placeholder token appended by caller emitter once param index known
48+
}
49+
}
50+
}
51+
}
52+
}
53+
54+
impl<'a> EmitTokens<'a> for SelectAst<'a> {
55+
fn emit_tokens(&self, out: &mut Vec<SqlToken<'a>>) {
56+
// SELECT clause
57+
out.push(SqlToken::Keyword("SELECT"));
58+
if self.columns.is_empty() {
59+
out.push(SqlToken::Symbol("*"));
60+
} else {
61+
for (i, col) in self.columns.iter().enumerate() {
62+
if i > 0 { out.push(SqlToken::Symbol(",")); }
63+
out.push(SqlToken::Ident(col.name));
64+
if let Some(alias) = col.alias { out.push(SqlToken::Keyword("AS")); out.push(SqlToken::Ident(alias)); }
65+
}
66+
}
67+
68+
// FROM
69+
out.push(SqlToken::Keyword("FROM"));
70+
emit_table(&self.base.table, out);
71+
72+
// JOINs
73+
for j in &self.joins {
74+
out.push(SqlToken::Keyword(j.kind.as_str()));
75+
emit_table(&j.table, out);
76+
out.push(SqlToken::Keyword("ON"));
77+
out.push(SqlToken::Ident(j.left));
78+
out.push(SqlToken::Operator(j.operator.as_str()));
79+
out.push(SqlToken::Ident(j.right));
80+
}
81+
82+
// WHERE / HAVING / GROUP BY / ORDER BY / LIMIT / OFFSET -> handled by base and specific
83+
self.base.emit_tokens(out);
84+
85+
if !self.group_by.is_empty() {
86+
out.push(SqlToken::Keyword("GROUP BY"));
87+
for (i, col) in self.group_by.iter().enumerate() {
88+
if i > 0 { out.push(SqlToken::Symbol(",")); }
89+
out.push(SqlToken::Ident(col));
90+
}
91+
}
92+
93+
if !self.having.is_empty() {
94+
out.push(SqlToken::Keyword("HAVING"));
95+
for (i, h) in self.having.iter().enumerate() {
96+
if i > 0 { out.push(SqlToken::Keyword("AND")); }
97+
out.push(SqlToken::Ident(h.column));
98+
out.push(SqlToken::Operator(h.operator.as_str()));
99+
// placeholder: appended by caller with param index
100+
}
101+
}
102+
103+
if !self.order_by.is_empty() {
104+
out.push(SqlToken::Keyword("ORDER BY"));
105+
for (i, ob) in self.order_by.iter().enumerate() {
106+
if i > 0 { out.push(SqlToken::Symbol(",")); }
107+
out.push(SqlToken::Ident(ob.column));
108+
if ob.descending { out.push(SqlToken::Keyword("DESC")); }
109+
}
110+
}
111+
112+
if let Some(limit) = self.limit {
113+
out.push(SqlToken::Keyword("LIMIT"));
114+
out.push(SqlToken::Number(limit));
115+
}
116+
if let Some(offset) = self.offset {
117+
out.push(SqlToken::Keyword("OFFSET"));
118+
out.push(SqlToken::Number(offset));
119+
}
120+
}
121+
}
122+
123+
impl<'a> EmitTokens<'a> for UpdateAst<'a> {
124+
fn emit_tokens(&self, out: &mut Vec<SqlToken<'a>>) {
125+
out.push(SqlToken::Keyword("UPDATE"));
126+
emit_table(&self.base.table, out);
127+
out.push(SqlToken::Keyword("SET"));
128+
for (i, (col, _val)) in self.set_clauses.iter().enumerate() {
129+
if i > 0 { out.push(SqlToken::Symbol(",")); }
130+
out.push(SqlToken::Ident(col));
131+
out.push(SqlToken::Operator("="));
132+
// placeholder appended by caller
133+
}
134+
self.base.emit_tokens(out);
135+
}
136+
}
137+
138+
impl<'a> EmitTokens<'a> for DeleteAst<'a> {
139+
fn emit_tokens(&self, out: &mut Vec<SqlToken<'a>>) {
140+
out.push(SqlToken::Keyword("DELETE"));
141+
out.push(SqlToken::Keyword("FROM"));
142+
emit_table(&self.base.table, out);
143+
self.base.emit_tokens(out);
144+
}
145+
}
146+
147+
impl<'a> EmitTokens<'a> for InsertAst<'a> {
148+
fn emit_tokens(&self, out: &mut Vec<SqlToken<'a>>) {
149+
out.push(SqlToken::Keyword("INSERT INTO"));
150+
emit_table(&self.base.table, out);
151+
if !self.columns.is_empty() {
152+
out.push(SqlToken::Symbol("("));
153+
for (i, c) in self.columns.iter().enumerate() {
154+
if i > 0 { out.push(SqlToken::Symbol(",")); }
155+
out.push(SqlToken::Ident(c));
156+
}
157+
out.push(SqlToken::Symbol(")"));
158+
}
159+
out.push(SqlToken::Keyword("VALUES"));
160+
out.push(SqlToken::Symbol("("));
161+
// placeholders appended by caller
162+
out.push(SqlToken::Symbol(")"));
163+
}
164+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use crate::query::operators::Comp;
2+
use crate::query::parameters::QueryParameter;
3+
4+
pub struct HavingClause<'a> {
5+
pub column: &'a str,
6+
pub operator: Comp,
7+
pub value: &'a dyn QueryParameter, // TODO: shouldn't this be a placeholder?
8+
}
9+
10+
impl<'a> HavingClause<'a> {
11+
pub fn new(column: &'a str, operator: Comp, value: &'a dyn QueryParameter) -> Self {
12+
Self { column, operator, value }
13+
}
14+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use crate::query::operators::Comp;
2+
use crate::query::querybuilder::syntax::table_metadata::TableMetadata;
3+
4+
#[derive(Debug, Clone, Copy)]
5+
pub enum JoinKind {
6+
Inner,
7+
Left,
8+
Right,
9+
Full,
10+
}
11+
12+
impl JoinKind {
13+
pub fn as_str(&self) -> &'static str {
14+
match self {
15+
JoinKind::Inner => "INNER JOIN",
16+
JoinKind::Left => "LEFT JOIN",
17+
JoinKind::Right => "RIGHT JOIN",
18+
JoinKind::Full => "FULL JOIN",
19+
}
20+
}
21+
}
22+
23+
pub struct JoinClause<'a> {
24+
pub kind: JoinKind,
25+
pub table: TableMetadata, // TODO: this should be the target table, and the origin table
26+
pub left: &'a str, // e.g. "t1.id" // TODO: we need to filter and check the syntax
27+
pub operator: Comp, // usually Eq
28+
pub right: &'a str, // e.g. "t2.t1_id"
29+
}
30+
31+
impl<'a> JoinClause<'a> {
32+
pub fn new(kind: JoinKind, table: TableMetadata, left: &'a str, operator: Comp, right: &'a str) -> Self {
33+
Self { kind, table, left, operator, right }
34+
}
35+
}

canyon_core/src/query/querybuilder/syntax/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,8 @@ pub(crate) mod query_kind;
22
pub(crate) mod table_metadata;
33
pub(crate) mod tokens;
44
pub(crate) mod clause;
5+
mod column_ref;
6+
mod join;
7+
mod order;
8+
mod having;
9+
mod emitter;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#[derive(Debug, Clone)]
2+
pub struct OrderByClause<'a> {
3+
pub column: &'a str,
4+
pub descending: bool,
5+
}
6+
7+
impl<'a> OrderByClause<'a> {
8+
pub fn asc(column: &'a str) -> Self { Self { column, descending: false } }
9+
pub fn desc(column: &'a str) -> Self { Self { column, descending: true } }
10+
}

0 commit comments

Comments
 (0)