Skip to content

Commit

Permalink
fix: predicate evaluate
Browse files Browse the repository at this point in the history
  • Loading branch information
killme2008 committed Jan 3, 2024
1 parent 825d7d1 commit 281c5de
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 64 deletions.
11 changes: 6 additions & 5 deletions src/catalog/src/information_schema/columns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,12 @@ impl InformationSchemaColumnsBuilder {
let data_type = &column_schema.data_type.name();

let row = [
("catalog_name", &Value::from(catalog_name)),
("schema_name", &Value::from(schema_name)),
("table_name", &Value::from(table_name)),
("semantic_type", &Value::from(semantic_type)),
("data_type", &Value::from(data_type.as_str())),
(TABLE_CATALOG, &Value::from(catalog_name)),
(TABLE_SCHEMA, &Value::from(schema_name)),
(TABLE_NAME, &Value::from(table_name)),
(COLUMN_NAME, &Value::from(column_schema.name.as_str())),
(DATA_TYPE, &Value::from(data_type.as_str())),
(SEMANTIC_TYPE, &Value::from(semantic_type)),
];

if !predicates.eval(&row) {
Expand Down
194 changes: 139 additions & 55 deletions src/catalog/src/information_schema/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,58 +32,63 @@ enum Predicate {
}

impl Predicate {
/// Evaluate the predicate with value, returns:
/// - `None` when the predicate can't run on value,
/// - `Some(true)` when the predicate is satisfied,.
/// - `Some(false)` when the predicate is not satisfied.
fn eval(&self, column: &str, value: &Value) -> Option<bool> {
/// Evaluate the predicate with the row, returns:
/// - None when the predicate can't evaluate with the row.
/// - Some(true) when the predicate is satisfied,
/// - Some(false) when the predicate is satisfied,
fn eval(&self, row: &[(&str, &Value)]) -> Option<bool> {
match self {
Predicate::Eq(c, v) => {
if c != column {
return None;
for (column, value) in row {
if c != column {
continue;
}
return Some(v == *value);
}
Some(v == value)
}
Predicate::NotEq(c, v) => {
if c != column {
return None;
for (column, value) in row {
if c != column {
continue;
}
return Some(v != *value);
}
Some(v != value)
}
Predicate::InList(c, values) => {
if c != column {
return None;
for (column, value) in row {
if c != column {
continue;
}
return Some(values.iter().any(|v| v == *value));
}
Some(values.iter().any(|v| v == value))
}
Predicate::And(left, right) => {
let Some(left) = left.eval(column, value) else {
return None;
};
let Some(right) = right.eval(column, value) else {
return None;
return match (left.eval(row), right.eval(row)) {
(Some(left), Some(right)) => Some(left && right),
(Some(false), None) => Some(false),
(None, Some(false)) => Some(false),
_ => None,
};

Some(left && right)
}
Predicate::Or(left, right) => {
let Some(left) = left.eval(column, value) else {
return None;
};
let Some(right) = right.eval(column, value) else {
return None;
return match (left.eval(row), right.eval(row)) {
(Some(left), Some(right)) => Some(left || right),
(Some(true), None) => Some(true),
(None, Some(true)) => Some(true),
_ => None,
};

Some(left || right)
}
Predicate::Not(p) => {
let Some(p) = p.eval(column, value) else {
let Some(b) = p.eval(row) else {
return None;
};

Some(!p)
return Some(!b);
}
}

// Can't evaluate predicate on the row
None
}

// Try to create a predicate from datafusion `Expr`, return None if fails.
Expand Down Expand Up @@ -203,29 +208,6 @@ impl Predicates {
}
}

/// Evaluate the predicates with the column value,
/// returns true when all the predicates are satisfied or can't be evaluated.
fn eval_column(&self, column: &str, value: &Value) -> bool {
let mut result = true;
for predicate in &self.predicates {
match predicate.eval(column, value) {
Some(b) => {
result = result && b;
}
None => {
// Can't eval this predicate, continue
continue;
}
}

if !result {
break;
}
}

result
}

/// Evaluate the predicates with the columns and values,
/// returns true when all the predicates are satisfied or can't be evaluated.
pub fn eval(&self, row: &[(&str, &Value)]) -> bool {
Expand All @@ -235,8 +217,15 @@ impl Predicates {
}

let mut result = true;
for (column, value) in row {
result = result && self.eval_column(column, value);

for predicate in &self.predicates {
match predicate.eval(row) {
Some(b) => {
result = result && b;
}
// The predicate can't evalute on the row, continue

Check warning on line 226 in src/catalog/src/information_schema/predicate.rs

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"evalute" should be "evaluate".
None => continue,
}

if !result {
break;
Expand All @@ -251,3 +240,98 @@ impl Predicates {
fn is_all_scalars(list: &[DfExpr]) -> bool {
list.iter().all(|v| matches!(v, DfExpr::Literal(_)))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_predicate_eval() {
let a_col = "a".to_string();
let b_col = "b".to_string();
let a_value = Value::from("a_value");
let b_value = Value::from("b_value");
let wrong_value = Value::from("wrong_value");

let a_row = [(a_col.as_str(), &a_value)];
let b_row = [("b", &wrong_value)];
let wrong_row = [(a_col.as_str(), &wrong_value)];

// Predicate::Eq
let p = Predicate::Eq(a_col.clone(), a_value.clone());
assert!(p.eval(&a_row).unwrap());
assert!(p.eval(&b_row).is_none());
assert!(!p.eval(&wrong_row).unwrap());

// Predicate::NotEq
let p = Predicate::NotEq(a_col.clone(), a_value.clone());
assert!(!p.eval(&a_row).unwrap());
assert!(p.eval(&b_row).is_none());
assert!(p.eval(&wrong_row).unwrap());

// Predicate::InList
let p = Predicate::InList(a_col.clone(), vec![a_value.clone(), b_value.clone()]);
assert!(p.eval(&a_row).unwrap());
assert!(p.eval(&b_row).is_none());
assert!(!p.eval(&wrong_row).unwrap());
assert!(p.eval(&[(&a_col, &b_value)]).unwrap());

let p1 = Predicate::Eq(a_col.clone(), a_value.clone());
let p2 = Predicate::Eq(b_col.clone(), b_value.clone());
let row = [(a_col.as_str(), &a_value), (b_col.as_str(), &b_value)];
let wrong_row = [(a_col.as_str(), &a_value), (b_col.as_str(), &wrong_value)];

//Predicate::And
let p = Predicate::And(Box::new(p1.clone()), Box::new(p2.clone()));
assert!(p.eval(&row).unwrap());
assert!(!p.eval(&wrong_row).unwrap());
assert!(p.eval(&[]).is_none());
assert!(p.eval(&[("c", &a_value)]).is_none());
assert!(!p
.eval(&[(a_col.as_str(), &b_value), (b_col.as_str(), &a_value)])
.unwrap());
assert!(!p
.eval(&[(a_col.as_str(), &b_value), (b_col.as_str(), &b_value)])
.unwrap());
assert!(p
.eval(&[(a_col.as_ref(), &a_value), ("c", &a_value)])
.is_none());
assert!(!p
.eval(&[(a_col.as_ref(), &b_value), ("c", &a_value)])
.unwrap());

//Predicate::Or
let p = Predicate::Or(Box::new(p1), Box::new(p2));
assert!(p.eval(&row).unwrap());
assert!(p.eval(&wrong_row).unwrap());
assert!(p.eval(&[]).is_none());
assert!(p.eval(&[("c", &a_value)]).is_none());
assert!(!p
.eval(&[(a_col.as_str(), &b_value), (b_col.as_str(), &a_value)])
.unwrap());
assert!(p
.eval(&[(a_col.as_str(), &b_value), (b_col.as_str(), &b_value)])
.unwrap());
assert!(p
.eval(&[(a_col.as_ref(), &a_value), ("c", &a_value)])
.unwrap());
assert!(p
.eval(&[(a_col.as_ref(), &b_value), ("c", &a_value)])
.is_none());
}

#[test]
fn test_predicate_from_expr() {
todo!()
}

#[test]
fn test_predicates_from_scan_request() {
todo!()
}

#[test]
fn test_predicates_eval_row() {
todo!()
}
}
4 changes: 2 additions & 2 deletions src/catalog/src/information_schema/schemata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ impl InformationSchemaSchemataBuilder {
let row = [
("catalog_name", &Value::from(catalog_name)),
("schema_name", &Value::from(schema_name)),
("charset_name", &Value::from("utf8")),
("collation_name", &Value::from("utf8_bin")),
("default_character_set_name", &Value::from("utf8")),
("default_collation_name", &Value::from("utf8_bin")),
];

if !predicates.eval(&row) {
Expand Down
4 changes: 2 additions & 2 deletions src/catalog/src/information_schema/tables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ impl InformationSchemaTablesBuilder {
};

let row = [
("catalog_name", &Value::from(catalog_name)),
("schema_name", &Value::from(schema_name)),
("table_catalog", &Value::from(catalog_name)),
("table_schema", &Value::from(schema_name)),
("table_name", &Value::from(table_name)),
("table_type", &Value::from(table_type)),
];
Expand Down
61 changes: 61 additions & 0 deletions tests/cases/standalone/common/system/information_schema.result
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,29 @@ order by table_name;
| foo |
+------------+

select table_name
from information_schema.tables
where table_schema in ('my_db', 'public')
order by table_name;

+------------+
| table_name |
+------------+
| foo |
| numbers |
+------------+

select table_name
from information_schema.tables
where table_schema not in ('my_db', 'information_schema')
order by table_name;

+------------+
| table_name |
+------------+
| numbers |
+------------+

select table_catalog, table_schema, table_name, table_type, engine
from information_schema.tables
where table_catalog = 'greptime'
Expand All @@ -350,6 +373,22 @@ order by table_schema, table_name, column_name;
| greptime | my_db | foo | ts | TimestampMillisecond | TIMESTAMP |
+---------------+--------------+------------+-------------+----------------------+---------------+

-- test query filter for columns --
select table_catalog, table_schema, table_name, column_name, data_type, semantic_type
from information_schema.columns
where table_catalog = 'greptime'
and (table_schema in ('public')
or
table_schema == 'my_db')
order by table_schema, table_name, column_name;

+---------------+--------------+------------+-------------+----------------------+---------------+
| table_catalog | table_schema | table_name | column_name | data_type | semantic_type |
+---------------+--------------+------------+-------------+----------------------+---------------+
| greptime | my_db | foo | ts | TimestampMillisecond | TIMESTAMP |
| greptime | public | numbers | number | UInt32 | TAG |
+---------------+--------------+------------+-------------+----------------------+---------------+

use public;

Affected Rows: 0
Expand All @@ -362,6 +401,28 @@ use information_schema;

Affected Rows: 0

-- test query filter for key_column_usage --
select * from KEY_COLUMN_USAGE where CONSTRAINT_NAME = 'TIME INDEX';

+--------------------+-------------------+-----------------+---------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+
| constraint_catalog | constraint_schema | constraint_name | table_catalog | table_schema | table_name | column_name | ordinal_position | position_in_unique_constraint | referenced_table_schema | referenced_table_name | referenced_column_name |
+--------------------+-------------------+-----------------+---------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+
| def | my_db | TIME INDEX | def | my_db | foo | ts | 1 | | | | |
+--------------------+-------------------+-----------------+---------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+

select * from KEY_COLUMN_USAGE where CONSTRAINT_NAME != 'TIME INDEX';

+--------------------+-------------------+-----------------+---------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+
| constraint_catalog | constraint_schema | constraint_name | table_catalog | table_schema | table_name | column_name | ordinal_position | position_in_unique_constraint | referenced_table_schema | referenced_table_name | referenced_column_name |
+--------------------+-------------------+-----------------+---------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+
| def | public | PRIMARY | def | public | numbers | number | 1 | | | | |
+--------------------+-------------------+-----------------+---------------+--------------+------------+-------------+------------------+-------------------------------+-------------------------+-----------------------+------------------------+

select * from KEY_COLUMN_USAGE where CONSTRAINT_NAME == 'TIME INDEX' AND CONSTRAINT_SCHEMA != 'my_db';

++
++

-- schemata --
desc table schemata;

Expand Down
Loading

0 comments on commit 281c5de

Please sign in to comment.