Skip to content

Commit 0ad82e7

Browse files
loloxwgKKould
andauthored
feat(copy): add support for file copying operations in database (#75)
* feat(copy): add support for file copying operations in database Added files and functions necessary for implementing the COPY command in the database, allowing for both copying to and from files. This includes the creation of the CopyFromFile and CopyToFile operators and executors, modifications to the binder and executor mod files to support the new COPY command structure, and the creation of several associated test files. Additionally, made necessary changes to the CreateTable executor to yield tuples as the execution result, and introduced the 'csv' dependency in Cargo.toml for handling CSV file reading and writing. This feature allows for efficient data importation and exportation between database tables and external files. * remove the he unwrap * remove duplicate module import in operator * cargo fmt * style: added PrimaryKeyNotFound error * fix: handle error process --------- Co-authored-by: Kould <2435992353@qq.com>
1 parent 4bf7385 commit 0ad82e7

File tree

18 files changed

+523
-3
lines changed

18 files changed

+523
-3
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ bytes = "1.5.0"
4040
kip_db = "0.1.2-alpha.16"
4141
async-recursion = "1.0.5"
4242
rust_decimal = "1"
43+
csv = "1"
4344

4445
[dev-dependencies]
4546
tokio-test = "0.4.2"

src/binder/copy.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use std::path::PathBuf;
2+
use std::str::FromStr;
3+
4+
use crate::planner::operator::copy_from_file::CopyFromFileOperator;
5+
use crate::planner::operator::copy_to_file::CopyToFileOperator;
6+
use crate::planner::operator::Operator;
7+
use serde::{Deserialize, Serialize};
8+
use sqlparser::ast::{CopyOption, CopySource, CopyTarget};
9+
10+
use super::*;
11+
12+
#[derive(Debug, PartialEq, PartialOrd, Ord, Hash, Eq, Clone, Serialize, Deserialize)]
13+
pub struct ExtSource {
14+
pub path: PathBuf,
15+
pub format: FileFormat,
16+
}
17+
18+
/// File format.
19+
#[derive(Debug, PartialEq, PartialOrd, Ord, Hash, Eq, Clone, Serialize, Deserialize)]
20+
pub enum FileFormat {
21+
Csv {
22+
/// Delimiter to parse.
23+
delimiter: char,
24+
/// Quote to use.
25+
quote: char,
26+
/// Escape character to use.
27+
escape: Option<char>,
28+
/// Whether or not the file has a header line.
29+
header: bool,
30+
},
31+
}
32+
33+
impl std::fmt::Display for ExtSource {
34+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35+
write!(f, "{self:?}")
36+
}
37+
}
38+
39+
impl std::fmt::Display for FileFormat {
40+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41+
write!(f, "{self:?}")
42+
}
43+
}
44+
45+
impl FromStr for ExtSource {
46+
type Err = ();
47+
fn from_str(_s: &str) -> std::result::Result<Self, Self::Err> {
48+
Err(())
49+
}
50+
}
51+
52+
impl<S: Storage> Binder<S> {
53+
pub(super) async fn bind_copy(
54+
&mut self,
55+
source: CopySource,
56+
to: bool,
57+
target: CopyTarget,
58+
options: &[CopyOption],
59+
) -> Result<LogicalPlan, BindError> {
60+
let (table_name, ..) = match source {
61+
CopySource::Table {
62+
table_name,
63+
columns,
64+
} => (table_name, columns),
65+
CopySource::Query(_) => {
66+
return Err(BindError::UnsupportedCopySource(
67+
"bad copy source".to_string(),
68+
));
69+
}
70+
};
71+
72+
if let Some(table) = self.context.storage.table(&table_name.to_string()).await {
73+
let cols = table.all_columns();
74+
let ext_source = ExtSource {
75+
path: match target {
76+
CopyTarget::File { filename } => filename.into(),
77+
t => todo!("unsupported copy target: {:?}", t),
78+
},
79+
format: FileFormat::from_options(options),
80+
};
81+
let types = cols.iter().map(|c| c.desc.column_datatype).collect();
82+
83+
let copy = if to {
84+
// COPY <source_table> TO <dest_file>
85+
LogicalPlan {
86+
operator: Operator::CopyToFile(CopyToFileOperator { source: ext_source }),
87+
childrens: vec![],
88+
}
89+
} else {
90+
// COPY <dest_table> FROM <source_file>
91+
LogicalPlan {
92+
operator: Operator::CopyFromFile(CopyFromFileOperator {
93+
source: ext_source,
94+
types,
95+
columns: cols,
96+
table: table_name.to_string(),
97+
}),
98+
childrens: vec![],
99+
}
100+
};
101+
Ok(copy)
102+
} else {
103+
Err(BindError::InvalidTable(format!(
104+
"not found table {}",
105+
table_name
106+
)))
107+
}
108+
}
109+
}
110+
111+
impl FileFormat {
112+
/// Create from copy options.
113+
pub fn from_options(options: &[CopyOption]) -> Self {
114+
let mut delimiter = ',';
115+
let mut quote = '"';
116+
let mut escape = None;
117+
let mut header = false;
118+
for opt in options {
119+
match opt {
120+
CopyOption::Format(fmt) => {
121+
assert_eq!(fmt.value.to_lowercase(), "csv", "only support CSV format")
122+
}
123+
CopyOption::Delimiter(c) => delimiter = *c,
124+
CopyOption::Header(b) => header = *b,
125+
CopyOption::Quote(c) => quote = *c,
126+
CopyOption::Escape(c) => escape = Some(*c),
127+
o => panic!("unsupported copy option: {:?}", o),
128+
}
129+
}
130+
FileFormat::Csv {
131+
delimiter,
132+
quote,
133+
escape,
134+
header,
135+
}
136+
}
137+
}

src/binder/create_table.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ impl<S: Storage> Binder<S> {
1717
&mut self,
1818
name: &ObjectName,
1919
columns: &[ColumnDef],
20-
constraints: &[TableConstraint],
20+
_constraints: &[TableConstraint],
2121
) -> Result<LogicalPlan, BindError> {
2222
let name = lower_case_name(&name);
2323
let (_, name) = split_name(&name)?;

src/binder/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod aggregate;
2+
pub mod copy;
23
mod create_table;
34
mod delete;
45
mod distinct;
@@ -129,6 +130,16 @@ impl<S: Storage> Binder<S> {
129130
}
130131
Statement::Truncate { table_name, .. } => self.bind_truncate(table_name).await?,
131132
Statement::ShowTables { .. } => self.bind_show_tables()?,
133+
Statement::Copy {
134+
source,
135+
to,
136+
target,
137+
options,
138+
..
139+
} => {
140+
self.bind_copy(source.clone(), *to, target.clone(), &options)
141+
.await?
142+
}
132143
_ => return Err(BindError::UnsupportedStmt(stmt.to_string())),
133144
};
134145
Ok(plan)
@@ -176,6 +187,8 @@ pub enum BindError {
176187
CatalogError(#[from] CatalogError),
177188
#[error("type error: {0}")]
178189
TypeError(#[from] TypeError),
190+
#[error("copy error: {0}")]
191+
UnsupportedCopySource(String),
179192
}
180193

181194
#[cfg(test)]

src/execution/executor/ddl/create_table.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::execution::ExecutorError;
33
use crate::planner::operator::create_table::CreateTableOperator;
44
use crate::storage::Storage;
55
use crate::types::tuple::Tuple;
6+
use crate::types::tuple_builder::TupleBuilder;
67
use futures_async_stream::try_stream;
78

89
pub struct CreateTable {
@@ -28,7 +29,10 @@ impl CreateTable {
2829
table_name,
2930
columns,
3031
} = self.op;
31-
32-
let _ = storage.create_table(table_name, columns).await?;
32+
let _ = storage.create_table(table_name.clone(), columns).await?;
33+
let tuple_builder = TupleBuilder::new_result();
34+
let tuple = tuple_builder
35+
.push_result("CREATE TABLE SUCCESS", format!("{}", table_name).as_str())?;
36+
yield tuple;
3337
}
3438
}

0 commit comments

Comments
 (0)