Skip to content

Commit 7043e74

Browse files
authored
Feat/dml delete and Remove PhysicalPlan (#56)
* feat: implement dml delete * fix: data is inserted after deletion and the search is empty * perf: kipdb version up * ci * refactor: Remove `PhysicalPlan` and convert `LogicPlan` to `Executor` * feat: remove the coupling between DataBase and Storage * docs: add Storage support statement and code usage instructions * feat: Implement ddl `Drop` * feat: Implement ddl `Truncate` * feat: Implement dql `Count` * feat: Implement dql `Sum` * feat: Implement dql `Avg`\`Min`\`Max` * feat: Implement `GroupBy` clause on dql * fix(rbo): losing `InputRef` when pushing down `Project` * refactor: primary key id is actively specified by the user * fix: 'Alias' fails under aggregate function * feat: Support `DateTime` type
1 parent 803c9e9 commit 7043e74

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+1998
-643
lines changed

.github/workflows/ci.yml

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,55 @@
1-
name: Rust
1+
name: CI
22

33
on:
44
push:
5-
branches: [main]
65
pull_request:
7-
branches: [main]
86

97
env:
108
CARGO_TERM_COLOR: always
9+
CARGO_REGISTRIES_MY_REGISTRY_INDEX: https://github.com/rust-lang/crates.io-index
1110

1211
jobs:
13-
build:
12+
check:
13+
runs-on: ubuntu-20.04
14+
steps:
15+
- uses: actions/checkout@v2
16+
- uses: actions-rs/toolchain@v1
17+
with:
18+
profile: minimal
19+
toolchain: nightly
20+
components: rustfmt, clippy
21+
- name: Check code format
22+
uses: actions-rs/cargo@v1
23+
with:
24+
command: fmt
25+
args: --all -- --check
1426

15-
runs-on: ubuntu-latest
1627

28+
build:
29+
runs-on: ubuntu-20.04
1730
steps:
1831
- uses: actions/checkout@v2
19-
- name: Setup Rust
20-
uses: actions-rs/toolchain@v1
32+
- uses: actions-rs/toolchain@v1
2133
with:
34+
profile: minimal
2235
toolchain: nightly-2023-04-07
23-
override: true
36+
- uses: actions/checkout@v2
37+
- name: Build
38+
uses: actions-rs/cargo@v1
39+
with:
40+
command: build
2441

25-
- name: Run tests
42+
test:
43+
runs-on: ubuntu-20.04
44+
steps:
45+
- uses: actions/checkout@v2
46+
- uses: actions-rs/toolchain@v1
47+
with:
48+
profile: minimal
49+
toolchain: nightly-2023-04-07
50+
- uses: actions/checkout@v2
51+
- name: Test
2652
uses: actions-rs/cargo@v1
2753
with:
28-
command: test
54+
command: test
55+
args: --release --no-fail-fast

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ ahash = "0.8.3"
3030
lazy_static = "1.4.0"
3131
comfy-table = "7.0.1"
3232
bytes = "*"
33-
kip_db = "0.1.2-alpha.10"
33+
kip_db = "0.1.2-alpha.15"
3434
async-recursion = "1.0.5"
3535

3636
[dev-dependencies]

README.md

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,62 @@ Install rust toolchain first.
1111
cargo run
1212
```
1313
test command
14-
```mysql
14+
```sql
1515
create table t1 (a int, b int);
1616

1717
insert into t1 (a, b) values (1, 1), (5, 3), (5, 2);
1818

19+
update t1 set a = 0 where b > 1;
20+
21+
delete from t1 where b > 1;
22+
1923
select * from t1;
2024

2125
select * from t1 order by a asc nulls first
26+
27+
select count(distinct a) from t1;
28+
29+
truncate table t1;
30+
31+
drop table t1;
2232
```
33+
Using KipSQL in code
34+
```rust
35+
let kipsql = Database::with_kipdb("./data").await?;
36+
37+
let tupes = db.run("select * from t1").await?;
38+
```
39+
Storage Support:
40+
- KipDB
41+
- Memory
2342

2443
![demo](./static/images/demo.png)
2544

2645
### Features
2746
- DDL
2847
- Create
29-
- [x] CreateTable
30-
- [ ] CreateIndex
48+
- [x] Table
49+
- [ ] Index
3150
- Drop
51+
- [x] Table
52+
- [ ] Index
53+
- [x] Truncate
3254
- DQL
3355
- [x] Select
3456
- [x] Where
3557
- [ ] Distinct
36-
- [ ] Aggregation: count()/sum()/avg()/min()/max()
58+
- [x] Alias
59+
- [x] Aggregation: count()/sum()/avg()/min()/max()
3760
- [ ] Subquery
3861
- [x] Join: Inner/Left/Right/Full Cross(x)
39-
- [ ] Group By
40-
- [ ] Having
62+
- [x] Group By
63+
- [x] Having
4164
- [x] Order By
4265
- [x] Limit
4366
- DML
4467
- [x] Insert
4568
- [x] Update
46-
- [ ] Delete
69+
- [x] Delete
4770
- DataTypes
4871
- Invalid
4972
- SqlNull
@@ -59,6 +82,7 @@ select * from t1 order by a asc nulls first
5982
- Float
6083
- Double
6184
- Varchar
85+
- DateTime
6286
- Optimizer rules
6387
- Limit Project Transpose
6488
- Eliminate Limits
@@ -67,18 +91,6 @@ select * from t1 order by a asc nulls first
6791
- Combine Filters
6892
- Column Pruning
6993
- Collapse Project
70-
- Executors
71-
- [x] CreateTable
72-
- [x] SeqScan
73-
- [ ] IndexScan
74-
- [x] Filter
75-
- [x] Project
76-
- [x] Limit
77-
- [x] Hash Join
78-
- [x] Insert
79-
- [x] Values
80-
- [x] Update
81-
- [ ] Delete
8294

8395
### Thanks For
8496
- [Fedomn/sqlrs](https://github.com/Fedomn/sqlrs): 主要参考资料,Optimizer、Executor均参考自sqlrs的设计

src/binder/aggregate.rs

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashSet;
2+
use ahash::RandomState;
13
use itertools::Itertools;
24
use sqlparser::ast::{Expr, OrderByExpr};
35

@@ -7,12 +9,13 @@ use crate::{
79
operator::{aggregate::AggregateOperator, sort::SortField},
810
},
911
};
10-
use crate::binder::BindError;
12+
use crate::binder::{BindError, InputRefType};
1113
use crate::planner::LogicalPlan;
14+
use crate::storage::Storage;
1215

1316
use super::Binder;
1417

15-
impl Binder {
18+
impl<S: Storage> Binder<S> {
1619
pub fn bind_aggregate(
1720
&mut self,
1821
children: LogicalPlan,
@@ -90,7 +93,7 @@ impl Binder {
9093
ScalarExpression::AggCall {
9194
ty: return_type, ..
9295
} => {
93-
let index = self.context.agg_calls.len();
96+
let index = self.context.input_ref_index(InputRefType::AggCall);
9497
let input_ref = ScalarExpression::InputRef {
9598
index,
9699
ty: return_type.clone(),
@@ -99,12 +102,14 @@ impl Binder {
99102
ScalarExpression::AggCall {
100103
kind,
101104
args,
102-
ty: return_type,
105+
ty,
106+
distinct
103107
} => {
104108
self.context.agg_calls.push(ScalarExpression::AggCall {
109+
distinct,
105110
kind,
106111
args,
107-
ty: return_type,
112+
ty,
108113
});
109114
}
110115
_ => unreachable!(),
@@ -114,6 +119,7 @@ impl Binder {
114119
ScalarExpression::TypeCast { expr, .. } => self.visit_column_agg_expr(expr),
115120
ScalarExpression::IsNull { expr } => self.visit_column_agg_expr(expr),
116121
ScalarExpression::Unary { expr, .. } => self.visit_column_agg_expr(expr),
122+
ScalarExpression::Alias { expr, .. } => self.visit_column_agg_expr(expr),
117123
ScalarExpression::Binary {
118124
left_expr,
119125
right_expr,
@@ -122,11 +128,9 @@ impl Binder {
122128
self.visit_column_agg_expr(left_expr);
123129
self.visit_column_agg_expr(right_expr);
124130
}
125-
126131
ScalarExpression::Constant(_)
127132
| ScalarExpression::ColumnRef { .. }
128-
| ScalarExpression::InputRef { .. }
129-
| ScalarExpression::Alias { .. } => {}
133+
| ScalarExpression::InputRef { .. } => {}
130134
}
131135
}
132136

@@ -143,6 +147,7 @@ impl Binder {
143147
let mut group_raw_exprs = vec![];
144148
for expr in groupby {
145149
let expr = self.bind_expr(expr).await?;
150+
146151
if let ScalarExpression::Alias { alias, .. } = expr {
147152
let alias_expr = select_items.iter().find(|column| {
148153
if let ScalarExpression::Alias {
@@ -162,22 +167,30 @@ impl Binder {
162167
group_raw_exprs.push(expr);
163168
}
164169
}
170+
let mut group_raw_set: HashSet<&ScalarExpression, RandomState> = HashSet::from_iter(group_raw_exprs.iter());
165171

166-
for column in select_items {
167-
let expr = &column;
168-
if expr.has_agg_call() {
172+
for expr in select_items {
173+
if expr.has_agg_call(&self.context) {
169174
continue;
170175
}
176+
group_raw_set.remove(expr);
171177

172178
if !group_raw_exprs.iter().contains(expr) {
173179
return Err(BindError::AggMiss(
174180
format!(
175-
"{} must appear in the GROUP BY clause or be used in an aggregate function",
181+
"{:?} must appear in the GROUP BY clause or be used in an aggregate function",
176182
expr
177183
)
178184
));
179185
}
180186
}
187+
188+
if !group_raw_set.is_empty() {
189+
return Err(BindError::AggMiss(
190+
format!("In the GROUP BY clause the field must be in the select clause")
191+
));
192+
}
193+
181194
Ok(())
182195
}
183196

@@ -197,14 +210,10 @@ impl Binder {
197210
false
198211
}
199212
}) {
200-
let index = if self.context.group_by_exprs.len() == 0 {
201-
0
202-
} else {
203-
self.context.group_by_exprs.len() + 1
204-
};
205-
213+
let index = self.context.input_ref_index(InputRefType::GroupBy);
206214
let mut select_item = &mut select_list[i];
207215
let return_type = select_item.return_type();
216+
208217
self.context.group_by_exprs.push(std::mem::replace(
209218
&mut select_item,
210219
ScalarExpression::InputRef {
@@ -223,11 +232,8 @@ impl Binder {
223232
self.context.group_by_exprs.push(expr.clone())
224233
}
225234
_ => {
226-
let index = if self.context.group_by_exprs.len() == 0 {
227-
0
228-
} else {
229-
self.context.group_by_exprs.len() + 1
230-
};
235+
let index = self.context.input_ref_index(InputRefType::GroupBy);
236+
231237
self.context.group_by_exprs.push(std::mem::replace(
232238
expr,
233239
ScalarExpression::InputRef {
@@ -256,7 +262,7 @@ impl Binder {
256262

257263
Err(BindError::AggMiss(
258264
format!(
259-
"column {} must appear in the GROUP BY clause or be used in an aggregate function",
265+
"column {:?} must appear in the GROUP BY clause or be used in an aggregate function",
260266
expr
261267
)
262268
))
@@ -268,7 +274,7 @@ impl Binder {
268274

269275
Err(BindError::AggMiss(
270276
format!(
271-
"column {} must appear in the GROUP BY clause or be used in an aggregate function",
277+
"column {:?} must appear in the GROUP BY clause or be used in an aggregate function",
272278
expr
273279
)
274280
))

src/binder/create_table.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ use crate::catalog::ColumnCatalog;
99
use crate::planner::LogicalPlan;
1010
use crate::planner::operator::create_table::CreateTableOperator;
1111
use crate::planner::operator::Operator;
12+
use crate::storage::Storage;
13+
use crate::types::LogicalType;
1214

13-
impl Binder {
15+
impl<S: Storage> Binder<S> {
1416
pub(crate) fn bind_create_table(
1517
&mut self,
1618
name: &ObjectName,
@@ -34,6 +36,14 @@ impl Binder {
3436
.map(|col| ColumnCatalog::from(col.clone()))
3537
.collect_vec();
3638

39+
if let Some(col) = columns.iter().find(|col| col.desc.is_primary) {
40+
if !matches!(col.datatype(), LogicalType::Integer) {
41+
return Err(BindError::InvalidColumn("Primary key types only support signed integers".to_string()));
42+
}
43+
} else {
44+
return Err(BindError::InvalidTable("At least one primary key field exists".to_string()));
45+
}
46+
3747
let plan = LogicalPlan {
3848
operator: Operator::CreateTable(
3949
CreateTableOperator {
@@ -61,7 +71,7 @@ mod tests {
6171
let temp_dir = TempDir::new().expect("unable to create temporary working directory");
6272
let storage = KipStorage::new(temp_dir.path()).await.unwrap();
6373

64-
let sql = "create table t1 (id int , name varchar(10) null)";
74+
let sql = "create table t1 (id int primary key, name varchar(10) null)";
6575
let binder = Binder::new(BinderContext::new(storage));
6676
let stmt = crate::parser::parse_sql(sql).unwrap();
6777
let plan1 = binder.bind(&stmt[0]).await.unwrap();
@@ -71,7 +81,7 @@ mod tests {
7181
assert_eq!(op.table_name, Arc::new("t1".to_string()));
7282
assert_eq!(op.columns[0].name, "id".to_string());
7383
assert_eq!(op.columns[0].nullable, false);
74-
assert_eq!(op.columns[0].desc, ColumnDesc::new(LogicalType::Integer, false));
84+
assert_eq!(op.columns[0].desc, ColumnDesc::new(LogicalType::Integer, true));
7585
assert_eq!(op.columns[1].name, "name".to_string());
7686
assert_eq!(op.columns[1].nullable, true);
7787
assert_eq!(op.columns[1].desc, ColumnDesc::new(LogicalType::Varchar, false));

0 commit comments

Comments
 (0)