Skip to content

Commit a1b67b8

Browse files
committed
refactor(WIP)!: towards a better token emission phase
1 parent 04605ff commit a1b67b8

15 files changed

Lines changed: 291 additions & 195 deletions

File tree

canyon_core/src/connection/database_type.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,12 @@ impl DatabaseType {
9494
.get_default_db_type()
9595
.map_err(|err| Box::new(err) as Box<dyn Error + Send + Sync>)
9696
}
97+
98+
pub fn get_placeholder_symbol(self) -> &'static str {
99+
match self {
100+
DatabaseType::SqlServer => "@P",
101+
DatabaseType::MySQL => "?",
102+
_ => "$", // postgres and default
103+
}
104+
}
97105
}

canyon_core/src/query/operators.rs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1+
use std::fmt::Display;
12
use crate::connection::database_type::DatabaseType;
2-
use std::fmt::{Display, Formatter};
3-
4-
pub trait Operator: Display {
5-
fn as_str(&self, placeholder_counter: usize, datasource_type: &DatabaseType) -> String;
6-
}
3+
use crate::query::querybuilder::syntax::tokens::{SqlToken, Symbol, ToSqlTokens};
74

85
/// Enumerated type for represent the comparison operations
96
/// in SQL sentences
@@ -34,12 +31,35 @@ impl Display for Comp {
3431
Self::GtEq => ">=",
3532
Self::Lt => "<",
3633
Self::LtEq => "<=",
37-
Self::Like(ref __kind) => "LIKE",
34+
Self::Like(ref __kind) => "LIKE"
3835
};
3936
write!(f, "{}", op)
4037
}
4138
}
4239

40+
impl<'a> ToSqlTokens<'a> for Comp {
41+
fn to_tokens(&self, out: &mut Vec<SqlToken<'a>>) {
42+
match *self {
43+
Comp::Eq => out.push(SqlToken::Symbol(Symbol::Equals)),
44+
Comp::Neq => {
45+
out.push(SqlToken::Symbol(Symbol::Not));
46+
out.push(SqlToken::Symbol(Symbol::Equals))
47+
}
48+
Comp::Gt => out.push(SqlToken::Symbol(Symbol::RAngle)),
49+
Comp::GtEq => {
50+
out.push(SqlToken::Symbol(Symbol::RAngle));
51+
out.push(SqlToken::Symbol(Symbol::Equals))
52+
}
53+
Comp::Lt => out.push(SqlToken::Symbol(Symbol::LAngle)),
54+
Comp::LtEq => {
55+
out.push(SqlToken::Symbol(Symbol::LAngle));
56+
out.push(SqlToken::Symbol(Symbol::Equals))
57+
}
58+
Comp::Like(__kind) => out.push(SqlToken::new_keyword("LIKE"))
59+
}
60+
}
61+
}
62+
4363
#[derive(Debug, PartialEq, Copy, Clone)]
4464
pub enum LikeKind {
4565
/// Operator "LIKE" as '%pattern%'
@@ -50,8 +70,8 @@ pub enum LikeKind {
5070
Right,
5171
}
5272

53-
impl Operator for LikeKind {
54-
fn as_str(&self, placeholder_counter: usize, datasource_type: &DatabaseType) -> String {
73+
impl LikeKind {
74+
pub(crate) fn as_str(&self, placeholder_counter: usize, datasource_type: DatabaseType) -> String {
5575
let type_data_to_cast_str = match datasource_type {
5676
#[cfg(feature = "postgres")]
5777
DatabaseType::PostgreSql => "VARCHAR",
@@ -77,17 +97,3 @@ impl Operator for LikeKind {
7797
}
7898
}
7999
}
80-
81-
impl Display for LikeKind {
82-
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
83-
write!(
84-
f,
85-
"{}",
86-
match *self {
87-
Self::Full => "Like::Full",
88-
Self::Left => "Like::Left",
89-
Self::Right => "Like::Right",
90-
}
91-
)
92-
}
93-
}

canyon_core/src/query/querybuilder/syntax/ast/select.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ impl<'a> EmitFrom<'a> for SelectAst<'a> {
5656
out.push(SqlToken::Symbol(Symbol::Comma));
5757
}
5858
col.to_tokens(out);
59-
// out.push(SqlToken::new_ident(col));
6059
}
6160
}
6261
// FROM
@@ -65,15 +64,13 @@ impl<'a> EmitFrom<'a> for SelectAst<'a> {
6564
}
6665
}
6766
impl<'a> EmitBody<'a> for SelectAst<'a> {
68-
fn emit_body(&self, _out: &mut Vec<SqlToken<'a>>) {
69-
// optional: ORDER BY, LIMIT, etc. left for child builder
70-
71-
// joins (simplified)
72-
// for j in &self.joins {
73-
// _out.push(SqlToken::new_keyword("LEFT JOIN")); // TODO: actual placeholder until
74-
// // we bring the JoinClauses
75-
// // TODO: out.push(SqlToken::new_ident(*j));
76-
// }
67+
fn emit_body(&self, out: &mut Vec<SqlToken<'a>>) {
68+
for j in &self.joins {
69+
j.to_tokens(out)
70+
}
71+
for c in &self._inner.conditions {
72+
c.accept(self);
73+
}
7774
}
7875
}
7976

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::query::operators::Comp;
22
use crate::query::querybuilder::syntax::column::ColumnRef;
3+
use crate::query::querybuilder::syntax::tokens::SqlToken::{Operator, Placeholder};
4+
use crate::query::querybuilder::syntax::tokens::{PlaceholderKind, SqlToken, ToSqlTokens};
35

46
#[derive(Clone)]
57
pub struct ConditionClause<'a> {
@@ -16,8 +18,8 @@ pub enum ConditionClauseKind {
1618
In,
1719
}
1820

19-
impl AsRef<str> for ConditionClauseKind {
20-
fn as_ref(&self) -> &str {
21+
impl ConditionClauseKind {
22+
fn as_str(&self) -> &'static str {
2123
match self {
2224
ConditionClauseKind::Where => "WHERE",
2325
ConditionClauseKind::And => "AND",
@@ -26,3 +28,23 @@ impl AsRef<str> for ConditionClauseKind {
2628
}
2729
}
2830
}
31+
32+
impl<'a> ToSqlTokens<'a> for ConditionClause<'a> {
33+
fn to_tokens(&self, out: &mut Vec<SqlToken<'a>>) {
34+
35+
// Clause keyword
36+
out.push(SqlToken::new_keyword(self.kind.as_str()));
37+
38+
// Column
39+
self.column_name.to_tokens(out);
40+
41+
// Operator
42+
out.push(Operator(self.operator));
43+
44+
// Value placeholder
45+
out.push(Placeholder(match self.operator {
46+
Comp::Like(kind) => PlaceholderKind::Like(kind, self.value_index),
47+
_ => PlaceholderKind::Value(self.value_index)
48+
}));
49+
}
50+
}

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

Lines changed: 59 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -120,69 +120,9 @@ mod __detail {
120120
}
121121

122122
#[cfg(test)]
123-
mod tests {
123+
mod column_ref_from_str_tests {
124124
use super::ColumnRef;
125-
use crate::query::querybuilder::syntax::column::__detail::find_case_insensitive_as;
126-
127-
// ------------------------------------------------------------
128-
// Tests for find_case_insensitive_as
129-
// ------------------------------------------------------------
130-
#[test]
131-
fn test_find_as_basic_uppercase() {
132-
let idx = find_case_insensitive_as("col AS x").unwrap();
133-
assert_eq!(&"col AS x"[idx..idx + 2], "AS");
134-
}
135-
136-
#[test]
137-
fn test_find_as_lowercase() {
138-
let idx = find_case_insensitive_as("col as x").unwrap();
139-
assert_eq!(&"col as x"[idx..idx + 2], "as");
140-
}
141-
142-
#[test]
143-
fn test_find_as_mixed_case() {
144-
let idx = find_case_insensitive_as("col As x").unwrap();
145-
assert_eq!(&"col As x"[idx..idx + 2], "As");
146-
}
147-
148-
#[test]
149-
fn test_find_as_with_multiple_spaces() {
150-
let idx = find_case_insensitive_as("col AS x").unwrap();
151-
assert_eq!(&"col AS x"[idx..idx + 2], "AS");
152-
}
153-
154-
#[test]
155-
fn test_find_as_requires_space_before_and_after() {
156-
assert!(find_case_insensitive_as("colASx").is_none());
157-
assert!(find_case_insensitive_as("col ASx").is_none());
158-
assert!(find_case_insensitive_as("colAS x").is_none());
159-
assert!(find_case_insensitive_as("ASx").is_none());
160-
assert!(find_case_insensitive_as("xAS").is_none());
161-
}
162-
163-
#[test]
164-
fn test_find_as_at_start_or_end() {
165-
assert!(find_case_insensitive_as(" AS x").is_some());
166-
assert!(find_case_insensitive_as("x AS ").is_some());
167-
}
168-
169-
#[test]
170-
fn test_find_as_no_match() {
171-
assert!(find_case_insensitive_as("column something").is_none());
172-
assert!(find_case_insensitive_as("").is_none());
173-
assert!(find_case_insensitive_as("a s").is_none());
174-
assert!(find_case_insensitive_as("col AX x").is_none());
175-
}
176-
177-
#[test]
178-
fn test_find_as_with_table_column() {
179-
let idx = find_case_insensitive_as("table.col as alias").unwrap();
180-
assert_eq!(&"table.col as alias"[idx..idx + 2], "as");
181-
}
182125

183-
// ------------------------------------------------------------
184-
// Tests for From<&str> for ColumnRef
185-
// ------------------------------------------------------------
186126
#[test]
187127
fn test_column_ref_simple_column() {
188128
let c = ColumnRef::from("name");
@@ -287,3 +227,61 @@ mod tests {
287227
assert_eq!(c.alias, None);
288228
}
289229
}
230+
231+
#[cfg(test)]
232+
mod column_ref_alias_as_detection_tests {
233+
use crate::query::querybuilder::syntax::column::__detail::find_case_insensitive_as;
234+
235+
#[test]
236+
fn test_find_as_basic_uppercase() {
237+
let idx = find_case_insensitive_as("col AS x").unwrap();
238+
assert_eq!(&"col AS x"[idx..idx + 2], "AS");
239+
}
240+
241+
#[test]
242+
fn test_find_as_lowercase() {
243+
let idx = find_case_insensitive_as("col as x").unwrap();
244+
assert_eq!(&"col as x"[idx..idx + 2], "as");
245+
}
246+
247+
#[test]
248+
fn test_find_as_mixed_case() {
249+
let idx = find_case_insensitive_as("col As x").unwrap();
250+
assert_eq!(&"col As x"[idx..idx + 2], "As");
251+
}
252+
253+
#[test]
254+
fn test_find_as_with_multiple_spaces() {
255+
let idx = find_case_insensitive_as("col AS x").unwrap();
256+
assert_eq!(&"col AS x"[idx..idx + 2], "AS");
257+
}
258+
259+
#[test]
260+
fn test_find_as_requires_space_before_and_after() {
261+
assert!(find_case_insensitive_as("colASx").is_none());
262+
assert!(find_case_insensitive_as("col ASx").is_none());
263+
assert!(find_case_insensitive_as("colAS x").is_none());
264+
assert!(find_case_insensitive_as("ASx").is_none());
265+
assert!(find_case_insensitive_as("xAS").is_none());
266+
}
267+
268+
#[test]
269+
fn test_find_as_at_start_or_end() {
270+
assert!(find_case_insensitive_as(" AS x").is_some());
271+
assert!(find_case_insensitive_as("x AS ").is_some());
272+
}
273+
274+
#[test]
275+
fn test_find_as_no_match() {
276+
assert!(find_case_insensitive_as("column something").is_none());
277+
assert!(find_case_insensitive_as("").is_none());
278+
assert!(find_case_insensitive_as("a s").is_none());
279+
assert!(find_case_insensitive_as("col AX x").is_none());
280+
}
281+
282+
#[test]
283+
fn test_find_as_with_table_column() {
284+
let idx = find_case_insensitive_as("table.col as alias").unwrap();
285+
assert_eq!(&"table.col as alias"[idx..idx + 2], "as");
286+
}
287+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ pub trait EmitFrom<'a> {
7272
pub trait EmitBody<'a> {
7373
fn emit_body(&self, out: &mut Vec<SqlToken<'a>>);
7474
}
75+
76+
pub trait Emit
7577
//
7678
//
7779
// // ---------- QueryEmitter enum ----------

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ impl JoinKind {
2424

2525
pub struct JoinClause<'a> {
2626
pub kind: JoinKind,
27-
pub target_table: TableMetadata<'a>, // TODO: this should be the target table, and the origin table
28-
pub left: ColumnRef<'a>, // e.g. "t1.id" // TODO: we need to filter and check the syntax
27+
pub target_table: TableMetadata<'a>,
28+
pub left: ColumnRef<'a>,
2929
pub operator: Comp, // usually Eq
3030
pub right: ColumnRef<'a>, // e.g. "t2.t1_id" // TODO: this is always the base or the previous (at least, in one of the sides)
3131
// so we could look in the vector for the previous clause and auto-add the join
@@ -51,8 +51,12 @@ impl<'a> JoinClause<'a> {
5151

5252
impl<'a> ToSqlTokens<'a> for JoinClause<'a> {
5353
fn to_tokens(&self, out: &mut Vec<SqlToken<'a>>) {
54-
out.push(SqlToken::new_ident(self.kind.as_str()));
54+
out.push(SqlToken::new_keyword(self.kind.as_str()));
5555
self.target_table.to_tokens(out);
56+
out.push(SqlToken::new_keyword("ON"));
57+
self.left.to_tokens(out);
58+
out.push(SqlToken::Operator(self.operator));
59+
self.right.to_tokens(out);
5660
}
5761
}
5862
#[test]
@@ -64,8 +68,8 @@ fn test_join_clause_basic() {
6468

6569
let join = JoinClause {
6670
kind: JoinKind::Inner,
67-
target_table: TableMetadata::new("target_table"),
68-
left: "t.id".into(),
71+
target_table: TableMetadata::new("users"),
72+
left: ColumnRef::from("t.id"),
6973
operator: Comp::Eq,
7074
right: "users.team_id".into(),
7175
};
@@ -77,9 +81,16 @@ fn test_join_clause_basic() {
7781
SqlToken::Keyword("INNER JOIN".into()),
7882
SqlToken::Ident("users".into()),
7983
SqlToken::Keyword("ON".into()),
80-
SqlToken::Ident("t.id".into()),
84+
85+
SqlToken::Ident("t".into()),
86+
SqlToken::Symbol(Symbol::Dot),
87+
SqlToken::Ident("id".into()),
88+
8189
SqlToken::Symbol(Symbol::Equals),
82-
SqlToken::Ident("users.team_id".into()),
90+
91+
SqlToken::Ident("users".into()),
92+
SqlToken::Symbol(Symbol::Dot),
93+
SqlToken::Ident("team_id".into()),
8394
];
8495

8596
assert_eq!(tokens, expected);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ pub(crate) mod query_kind;
1010
mod symbol;
1111
pub mod table_metadata;
1212
pub(crate) mod tokens;
13+
pub(crate) mod writer;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
#[derive(Debug, Clone, PartialEq)]
22
pub enum Symbol {
3+
Not,
34
LParen,
45
RParen,
6+
Apostrophe,
57
Comma,
68
Dot,
79
Equals,
810
Semicolon,
911
Asterisk,
12+
LAngle,
13+
RAngle,
14+
PercentSign
1015
}

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,7 @@ impl<'a> From<&'a str> for TableMetadata<'a> {
5050

5151
impl<'a> TableMetadata<'a> {
5252
pub fn new(table_name: &'a str) -> Self {
53-
Self {
54-
schema: None,
55-
name: Cow::from(table_name),
56-
}
53+
Self::from(table_name)
5754
}
5855
pub fn schema(&mut self, schema: String) {
5956
self.schema = Some(Cow::from(schema));

0 commit comments

Comments
 (0)