Repository: gluesql/gluesql
Pull request #69 (2020-09-13 20:33:10)

Add support for Inserting Multiple rows
`INSERT INTO Table (id, age) VALUES (1, 10), (2, 15);`


Patch:
diff --git a/src/data/row.rs b/src/data/row.rs
index b2da3a11..ad142414 100644
--- a/src/data/row.rs
+++ b/src/data/row.rs
@@ -20,9 +20,6 @@ pub enum RowError {
     #[error("unsupported ast value type")]
     UnsupportedAstValueType,
 
-    #[error("inserting multiple rows not supported")]
-    MultiRowInsertNotSupported,
-
     #[error("Unreachable")]
     Unreachable,
 
@@ -45,7 +42,11 @@ impl Row {
             .ok_or_else(|| RowError::ConflictOnEmptyRow.into())
     }
 
-    pub fn new(column_defs: Vec<ColumnDef>, columns: &[Ident], source: &Query) -> Result<Self> {
+    pub fn new(
+        column_defs: Vec<ColumnDef>,
+        columns: &[Ident],
+        source: &Query,
+    ) -> Result<Vec<Self>> {
         let values = match &source.body {
             SetExpr::Values(Values(values)) => values,
             _ => {
@@ -53,50 +54,51 @@ impl Row {
             }
         };
 
-        let values = match values.len() {
-            1 => &values[0],
-            0 => {
-                return Err(RowError::Unreachable.into());
-            }
-            _ => {
-                return Err(RowError::MultiRowInsertNotSupported.into());
-            }
-        };
+        if values.is_empty() {
+            return Err(RowError::Unreachable.into());
+        }
 
-        column_defs
-            .into_iter()
-            .enumerate()
-            .map(|(i, column_def)| {
-                let ColumnDef {
-                    name,
-                    data_type,
-                    options,
-                    ..
-                } = column_def;
-                let name = name.to_string();
-
-                let i = match columns.len() {
-                    0 => Ok(i),
-                    _ => columns
-                        .iter()
-                        .position(|target| target.value == name)
-                        .ok_or_else(|| RowError::LackOfRequiredColumn(name.clone())),
-                }?;
-
-                let literal = values
-                    .get(i)
-                    .ok_or_else(|| RowError::LackOfRequiredValue(name.clone()))?;
-                let nullable = options
+        values
+            .iter()
+            .map(|values| {
+                (&column_defs)
                     .iter()
-                    .any(|ColumnOptionDef { option, .. }| option == &ColumnOption::Null);
-
-                match literal {
-                    Expr::Value(literal) => Value::from_data_type(data_type, nullable, literal),
-                    Expr::Identifier(Ident { value, .. }) => Ok(Value::Str(value.clone())),
-                    _ => Err(RowError::UnsupportedAstValueType.into()),
-                }
+                    .enumerate()
+                    .map(|(i, column_def)| {
+                        let ColumnDef {
+                            name,
+                            data_type,
+                            options,
+                            ..
+                        } = column_def;
+                        let name = name.to_string();
+
+                        let i = match columns.len() {
+                            0 => Ok(i),
+                            _ => columns
+                                .iter()
+                                .position(|target| target.value == name)
+                                .ok_or_else(|| RowError::LackOfRequiredColumn(name.clone())),
+                        }?;
+
+                        let literal = values
+                            .get(i)
+                            .ok_or_else(|| RowError::LackOfRequiredValue(name.clone()))?;
+                        let nullable = options
+                            .iter()
+                            .any(|ColumnOptionDef { option, .. }| option == &ColumnOption::Null);
+
+                        match literal {
+                            Expr::Value(literal) => {
+                                Value::from_data_type(&data_type, nullable, literal)
+                            }
+                            Expr::Identifier(Ident { value, .. }) => Ok(Value::Str(value.clone())),
+                            _ => Err(RowError::UnsupportedAstValueType.into()),
+                        }
+                    })
+                    .collect::<Result<_>>()
+                    .map(Self)
             })
-            .collect::<Result<_>>()
-            .map(Self)
+            .collect()
     }
 }
diff --git a/src/data/value.rs b/src/data/value.rs
index 706304d4..4e61358d 100644
--- a/src/data/value.rs
+++ b/src/data/value.rs
@@ -175,7 +175,11 @@ impl BoolToValue for bool {
 }
 
 impl Value {
-    pub fn from_data_type(data_type: DataType, nullable: bool, literal: &AstValue) -> Result<Self> {
+    pub fn from_data_type(
+        data_type: &DataType,
+        nullable: bool,
+        literal: &AstValue,
+    ) -> Result<Self> {
         match (data_type, literal) {
             (DataType::Int, AstValue::Number(v)) => v
                 .parse()
diff --git a/src/executor/execute.rs b/src/executor/execute.rs
index 49e2dd4d..b07263e4 100644
--- a/src/executor/execute.rs
+++ b/src/executor/execute.rs
@@ -25,7 +25,7 @@ pub enum ExecuteError {
 #[derive(Serialize, Debug, PartialEq)]
 pub enum Payload {
     Create,
-    Insert(Row),
+    Insert(Vec<Row>),
     Select(Vec<Row>),
     Delete(usize),
     Update(usize),
@@ -48,12 +48,15 @@ pub fn execute<T: 'static + Debug, U: Store<T> + StoreMut<T>>(
         Prepared::Create(schema) => storage
             .insert_schema(&schema)
             .map(|(storage, _)| (storage, Payload::Create)),
-        Prepared::Insert(table_name, row) => {
-            let (storage, key) = storage.generate_id(&table_name)?;
-            let (storage, row) = storage.insert_data(&key, row)?;
-
-            Ok((storage, Payload::Insert(row)))
-        }
+        Prepared::Insert(table_name, rows) => rows
+            .into_iter()
+            .try_fold((storage, Vec::<Row>::new()), |(storage, mut rows), row| {
+                let (storage, key) = storage.generate_id(&table_name)?;
+                let (storage, row) = storage.insert_data(&key, row)?;
+                rows.push(row);
+                Ok((storage, rows))
+            })
+            .map(|(storage, rows)| (storage, Payload::Insert(rows))),
         Prepared::Delete(keys) => {
             let (storage, num_rows) =
                 keys.into_iter()
@@ -95,7 +98,7 @@ pub fn execute<T: 'static + Debug, U: Store<T> + StoreMut<T>>(
 
 enum Prepared<'a, T> {
     Create(Schema),
-    Insert(&'a str, Row),
+    Insert(&'a str, Vec<Row>),
     Delete(Vec<T>),
     Update(Vec<(T, Row)>),
     Select(Vec<Row>),
@@ -127,9 +130,9 @@ fn prepare<'a, T: 'static + Debug>(
         } => {
             let table_name = get_name(table_name)?;
             let Schema { column_defs, .. } = storage.fetch_schema(table_name)?;
-            let row = Row::new(column_defs, columns, source)?;
+            let rows = Row::new(column_defs, columns, source)?;
 
-            Ok(Prepared::Insert(table_name, row))
+            Ok(Prepared::Insert(table_name, rows))
         }
         Statement::Update {
             table_name,


Test patch:
diff --git a/src/tests/basic.rs b/src/tests/basic.rs
index 989a7af9..4cc2a419 100644
--- a/src/tests/basic.rs
+++ b/src/tests/basic.rs
@@ -11,7 +11,9 @@ CREATE TABLE Test (
     );
     tester.run_and_print("INSERT INTO Test (id, num, name) VALUES (1, 2, \"Hello\")");
     tester.run_and_print("INSERT INTO Test (id, num, name) VALUES (1, 9, \"World\")");
-    tester.run_and_print("INSERT INTO Test (id, num, name) VALUES (3, 4, \"Great\")");
+    tester.run_and_print(
+        "INSERT INTO Test (id, num, name) VALUES (3, 4, \"Great\"), (4, 7, \"Job\")",
+    );
 
     use Value::*;
 
@@ -22,17 +24,18 @@ CREATE TABLE Test (
         I64 I64 Str;
         1   2   "Hello".to_owned();
         1   9   "World".to_owned();
-        3   4   "Great".to_owned()
+        3   4   "Great".to_owned();
+        4   7   "Job".to_owned()
     );
     assert_eq!(expected, found);
 
     tester.run_and_print("UPDATE Test SET id = 2");
 
     let found = tester.run("SELECT id FROM Test").expect("select");
-    let expected = select!(I64; 2; 2; 2);
+    let expected = select!(I64; 2; 2; 2; 2);
     assert_eq!(expected, found);
 
     let found = tester.run("SELECT id, num FROM Test").expect("select");
-    let expected = select!(I64 I64; 2 2; 2 9; 2 4);
+    let expected = select!(I64 I64; 2 2; 2 9; 2 4; 2 7);
     assert_eq!(expected, found);
 }
diff --git a/src/tests/migrate.rs b/src/tests/migrate.rs
index a034ce43..cad6af55 100644
--- a/src/tests/migrate.rs
+++ b/src/tests/migrate.rs
@@ -25,10 +25,6 @@ CREATE TABLE Test (
             RowError::UnsupportedAstValueType.into(),
             "INSERT INTO Test (id, num) VALUES (3 * 2, 1);",
         ),
-        (
-            RowError::MultiRowInsertNotSupported.into(),
-            "INSERT INTO Test (id, num) VALUES (1, 1), (2, 1);",
-        ),
         (
             ValueError::FailedToParseNumber.into(),
             "INSERT INTO Test (id, num) VALUES (1.1, 1);",
diff --git a/src/tests/tester.rs b/src/tests/tester.rs
index e745c137..7cb2e61b 100644
--- a/src/tests/tester.rs
+++ b/src/tests/tester.rs
@@ -32,7 +32,7 @@ pub trait Tester {
 
         match result.unwrap() {
             Payload::Select(rows) => println!("[Ok ]\n{:#?}\n", rows),
-            Payload::Insert(row) => println!("[Ok ]\n{:#?}\n", row),
+            Payload::Insert(rows) => println!("[Ok ]\n{:#?}\n", rows),
             Payload::Delete(num) => println!("[Ok ] {} rows deleted.\n", num),
             Payload::Update(num) => println!("[Ok ] {} rows updated.\n", num),
             Payload::DropTable => println!("[Ok ] :)\n"),