Skip to content

Commit

Permalink
feat: support SUBSTRING() (#134)
Browse files Browse the repository at this point in the history
* feat: support `SUBSTRING()`

* docs: fix tupes -> tuples
  • Loading branch information
KKould committed Feb 9, 2024
1 parent 4b4c8e6 commit 1dc1fa8
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 58 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ then use `psql` to enter sql
Using FnckSQL in code
```rust
let fnck_sql = Database::with_kipdb("./data").await?;
let tupes = fnck_sql.run("select * from t1").await?;
let tuples = fnck_sql.run("select * from t1").await?;
```
Storage Support:
- KipDB
Expand Down
27 changes: 27 additions & 0 deletions src/binder/aggregate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,19 @@ impl<'a, T: Transaction> Binder<'a, T> {
self.visit_column_agg_expr(left_expr)?;
self.visit_column_agg_expr(right_expr)?;
}
ScalarExpression::SubString {
expr,
for_expr,
from_expr,
} => {
self.visit_column_agg_expr(expr)?;
if let Some(expr) = for_expr {
self.visit_column_agg_expr(expr)?;
}
if let Some(expr) = from_expr {
self.visit_column_agg_expr(expr)?;
}
}
ScalarExpression::Constant(_) | ScalarExpression::ColumnRef { .. } => {}
}

Expand Down Expand Up @@ -278,6 +291,20 @@ impl<'a, T: Transaction> Binder<'a, T> {
self.validate_having_orderby(right_expr)?;
Ok(())
}
ScalarExpression::SubString {
expr,
for_expr,
from_expr,
} => {
self.validate_having_orderby(expr)?;
if let Some(expr) = for_expr {
self.validate_having_orderby(expr)?;
}
if let Some(expr) = from_expr {
self.validate_having_orderby(expr)?;
}
Ok(())
}
ScalarExpression::Constant(_) => Ok(()),
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/binder/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,27 @@ impl<'a, T: Transaction> Binder<'a, T> {
left_expr: Box::new(self.bind_expr(low)?),
right_expr: Box::new(self.bind_expr(high)?),
}),
Expr::Substring {
expr,
substring_for,
substring_from,
} => {
let mut for_expr = None;
let mut from_expr = None;

if let Some(expr) = substring_for {
for_expr = Some(Box::new(self.bind_expr(expr)?))
}
if let Some(expr) = substring_from {
from_expr = Some(Box::new(self.bind_expr(expr)?))
}

Ok(ScalarExpression::SubString {
expr: Box::new(self.bind_expr(expr)?),
for_expr,
from_expr,
})
}
_ => {
todo!()
}
Expand Down
35 changes: 35 additions & 0 deletions src/expression/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::expression::value_compute::{binary_op, unary_op};
use crate::expression::ScalarExpression;
use crate::types::tuple::Tuple;
use crate::types::value::{DataValue, ValueRef};
use crate::types::LogicalType;
use itertools::Itertools;
use lazy_static::lazy_static;
use std::cmp::Ordering;
Expand All @@ -13,6 +14,19 @@ lazy_static! {
static ref NULL_VALUE: ValueRef = Arc::new(DataValue::Null);
}

macro_rules! eval_to_num {
($num_expr:expr, $tuple:expr) => {
if let Some(num_i32) = DataValue::clone($num_expr.eval($tuple)?.as_ref())
.cast(&LogicalType::Integer)?
.i32()
{
num_i32 as usize
} else {
return Ok(Arc::new(DataValue::Utf8(None)));
}
};
}

impl ScalarExpression {
pub fn eval(&self, tuple: &Tuple) -> Result<ValueRef, DatabaseError> {
if let Some(value) = Self::eval_with_summary(tuple, self.output_column().summary()) {
Expand Down Expand Up @@ -124,6 +138,27 @@ impl ScalarExpression {
}
Ok(Arc::new(DataValue::Boolean(Some(is_between))))
}
ScalarExpression::SubString {
expr,
for_expr,
from_expr,
} => {
if let Some(mut string) = DataValue::clone(expr.eval(tuple)?.as_ref())
.cast(&LogicalType::Varchar(None))?
.utf8()
{
if let Some(from_expr) = from_expr {
string = string.split_off(eval_to_num!(from_expr, tuple).saturating_sub(1));
}
if let Some(for_expr) = for_expr {
let _ = string.split_off(eval_to_num!(for_expr, tuple));
}

Ok(Arc::new(DataValue::Utf8(Some(string))))
} else {
Ok(Arc::new(DataValue::Utf8(None)))
}
}
}
}

Expand Down
66 changes: 40 additions & 26 deletions src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ pub enum ScalarExpression {
left_expr: Box<ScalarExpression>,
right_expr: Box<ScalarExpression>,
},
SubString {
expr: Box<ScalarExpression>,
for_expr: Option<Box<ScalarExpression>>,
from_expr: Option<Box<ScalarExpression>>,
},
}

impl ScalarExpression {
Expand Down Expand Up @@ -91,32 +96,6 @@ impl ScalarExpression {
}
}

pub fn nullable(&self) -> bool {
match self {
ScalarExpression::Constant(_) => false,
ScalarExpression::ColumnRef(col) => col.nullable,
ScalarExpression::Alias { expr, .. } => expr.nullable(),
ScalarExpression::TypeCast { expr, .. } => expr.nullable(),
ScalarExpression::IsNull { expr, .. } => expr.nullable(),
ScalarExpression::Unary { expr, .. } => expr.nullable(),
ScalarExpression::Binary {
left_expr,
right_expr,
..
} => left_expr.nullable() && right_expr.nullable(),
ScalarExpression::In { expr, args, .. } => {
expr.nullable() && args.iter().all(ScalarExpression::nullable)
}
ScalarExpression::AggCall { args, .. } => args.iter().all(ScalarExpression::nullable),
ScalarExpression::Between {
expr,
left_expr,
right_expr,
..
} => expr.nullable() && left_expr.nullable() && right_expr.nullable(),
}
}

pub fn return_type(&self) -> LogicalType {
match self {
Self::Constant(v) => v.logical_type(),
Expand All @@ -136,6 +115,7 @@ impl ScalarExpression {
Self::IsNull { .. } | Self::In { .. } | ScalarExpression::Between { .. } => {
LogicalType::Boolean
}
Self::SubString { .. } => LogicalType::Varchar(None),
Self::Alias { expr, .. } => expr.return_type(),
}
}
Expand Down Expand Up @@ -214,6 +194,21 @@ impl ScalarExpression {
right_expr,
..
} => expr.has_agg_call() || left_expr.has_agg_call() || right_expr.has_agg_call(),
ScalarExpression::SubString {
expr,
for_expr,
from_expr,
} => {
expr.has_agg_call()
|| matches!(
for_expr.as_ref().map(|expr| expr.has_agg_call()),
Some(true)
)
|| matches!(
from_expr.as_ref().map(|expr| expr.has_agg_call()),
Some(true)
)
}
}
}

Expand Down Expand Up @@ -287,6 +282,25 @@ impl ScalarExpression {
right_expr.output_name()
)
}
ScalarExpression::SubString {
expr,
for_expr,
from_expr,
} => {
let op = |tag: &str, num_expr: &Option<Box<ScalarExpression>>| {
num_expr
.as_ref()
.map(|expr| format!(", {}: {}", tag, expr.output_name()))
.unwrap_or_default()
};

format!(
"substring({}{}{})",
expr.output_name(),
op("from", from_expr),
op("for", for_expr),
)
}
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/expression/simplify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,8 @@ impl ScalarExpression {
| ScalarExpression::TypeCast { expr, .. }
| ScalarExpression::Unary { expr, .. }
| ScalarExpression::In { expr, .. }
| ScalarExpression::Between { expr, .. } => expr.convert_binary(col_id),
| ScalarExpression::Between { expr, .. }
| ScalarExpression::SubString { expr, .. } => expr.convert_binary(col_id),
ScalarExpression::IsNull { expr, negated, .. } => match expr.as_ref() {
ScalarExpression::ColumnRef(column) => {
Ok(column.id().is_some_and(|id| col_id == &id).then(|| {
Expand All @@ -936,7 +937,8 @@ impl ScalarExpression {
| ScalarExpression::Binary { .. }
| ScalarExpression::AggCall { .. }
| ScalarExpression::In { .. }
| ScalarExpression::Between { .. } => expr.convert_binary(col_id),
| ScalarExpression::Between { .. }
| ScalarExpression::SubString { .. } => expr.convert_binary(col_id),
},
ScalarExpression::Constant(_)
| ScalarExpression::ColumnRef(_)
Expand Down
32 changes: 16 additions & 16 deletions tests/slt/basic_test.slt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# query I
# select 1
# ----
# 1
query I
select 1
----
1

# query R
# select 10000.00::FLOAT + 234.567::FLOAT
Expand All @@ -13,20 +13,20 @@
# ----
# 12.5

# query B
# select 2>1
# ----
# true
query B
select 2>1
----
true

# query B
# select 3>4
# ----
# false
query B
select 3>4
----
false

# query T
# select DATE '2001-02-16'
# ----
# 2001-02-16
query T
select DATE '2001-02-16'
----
2001-02-16

subtest NullType

Expand Down
25 changes: 12 additions & 13 deletions tests/slt/sql_2016/E021_06.slt
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
# E021-06: SUBSTRING function

# TODO: SUBSTRING()
query T
SELECT SUBSTRING ( 'foo' FROM 1 )
----
foo

# query T
# SELECT SUBSTRING ( 'foo' FROM 1 )
# ----
# 'foo'

# query T
# SELECT SUBSTRING ( 'foo' FROM 1 FOR 2 )
# ----
# 'fo'
query T
SELECT SUBSTRING ( 'foo' FROM 1 FOR 2 )
----
fo

# sqlparser-rs unsupported
# query I
# SELECT SUBSTRING ( 'foo' FROM 1 FOR 2 USING CHARACTERS )


# sqlparser-rs unsupported
# query I
# SELECT SUBSTRING ( 'foo' FROM 1 FOR 2 USING OCTETS )


# sqlparser-rs unsupported
# query I
# SELECT SUBSTRING ( 'foo' FROM 1 USING CHARACTERS )


# sqlparser-rs unsupported
# query I
# SELECT SUBSTRING ( 'foo' FROM 1 USING OCTETS )
26 changes: 26 additions & 0 deletions tests/slt/substring
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
query T
select substring('pineapple' from 5 for 10 )
----
app

query T
select substring('pineapple' for 4 )
----
pine

query T
select substring('pineapple' from 5 )
----
apple

query T
select substring('pineapple' from 1 for null )
----

query T
select substring('pineapple' from null for 4 )
----

query T
select substring(null from 1 for 4 )
----

0 comments on commit 1dc1fa8

Please sign in to comment.