Commit e018ad6

mo khan <mo@mokhan.ca>
2025-09-25 16:02:09
feat: add support for insert, update and delete statements
1 parent 3d45c91
Changed files (7)
src
tests
fixtures
create_table_statement
delete_statement
insert_statement
src/formatter.rs
@@ -1,26 +1,17 @@
-use sqlparser::ast::*;
+use sqlparser::ast::{*, Insert, Delete, CreateTable, FromTable};
 
 pub fn format_statement(statement: &Statement, indent_level: usize) -> String {
     match statement {
         Statement::Query(query) => format_query(query, indent_level),
-        Statement::Insert { .. } => {
-            // TODO: Fix INSERT statement formatting for new API
-            "INSERT statement".to_string()
-        }
+        Statement::Insert(insert) => format_insert(insert, indent_level),
         Statement::Update {
             table,
             assignments,
             selection,
             ..
         } => format_update(table, assignments, selection, indent_level),
-        Statement::Delete { .. } => {
-            // TODO: Fix DELETE statement formatting for new API
-            "DELETE statement".to_string()
-        }
-        Statement::CreateTable { .. } => {
-            // TODO: Fix CREATE TABLE statement formatting for new API
-            "CREATE TABLE statement".to_string()
-        }
+        Statement::Delete(delete) => format_delete(delete, indent_level),
+        Statement::CreateTable(create_table) => format_create_table(create_table, indent_level),
         _ => statement.to_string(),
     }
 }
@@ -784,3 +775,145 @@ mod tests {
         assert_eq!(parse_and_format(input), expected);
     }
 }
+
+fn format_insert(insert: &Insert, indent_level: usize) -> String {
+    let mut result = format!("{}INSERT INTO {}", " ".repeat(indent_level), insert.table);
+
+    if !insert.columns.is_empty() {
+        result.push_str(" (");
+        if insert.columns.len() == 1 {
+            result.push_str(&insert.columns[0].value);
+        } else {
+            result.push('\n');
+            for (i, column) in insert.columns.iter().enumerate() {
+                if i > 0 {
+                    result.push_str(",\n");
+                }
+                result.push_str(&format!("{}  {}", " ".repeat(indent_level), column.value));
+            }
+            result.push('\n');
+            result.push_str(&format!("{}", " ".repeat(indent_level)));
+        }
+        result.push(')');
+    }
+
+    if let Some(source) = &insert.source {
+        result.push('\n');
+        result.push_str(&format!("{}VALUES", " ".repeat(indent_level)));
+        if let SetExpr::Values(values) = &*source.body {
+            for (i, row) in values.rows.iter().enumerate() {
+                if i > 0 {
+                    result.push_str(",\n");
+                    result.push_str(&format!("{}      ", " ".repeat(indent_level)));
+                } else {
+                    result.push_str(" (");
+                    if row.len() > 1 {
+                        result.push('\n');
+                    }
+                }
+
+                if row.len() == 1 {
+                    result.push_str(&format_expr(&row[0]));
+                } else {
+                    for (j, value) in row.iter().enumerate() {
+                        if j > 0 {
+                            result.push_str(",\n");
+                        }
+                        result.push_str(&format!("{}  {}", " ".repeat(indent_level), format_expr(value)));
+                    }
+                    result.push('\n');
+                    result.push_str(&format!("{}", " ".repeat(indent_level)));
+                }
+                result.push(')');
+            }
+        }
+    }
+
+    result
+}
+
+fn format_delete(delete: &Delete, indent_level: usize) -> String {
+    let mut result = format!("{}DELETE", " ".repeat(indent_level));
+
+    if !delete.tables.is_empty() {
+        result.push(' ');
+        for (i, table) in delete.tables.iter().enumerate() {
+            if i > 0 {
+                result.push_str(", ");
+            }
+            result.push_str(&table.to_string());
+        }
+    }
+
+    match &delete.from {
+        FromTable::WithFromKeyword(tables) => {
+            result.push_str(" FROM");
+            if !tables.is_empty() {
+                result.push(' ');
+                result.push_str(&tables[0].to_string());
+            }
+        }
+        FromTable::WithoutKeyword(tables) => {
+            if !tables.is_empty() {
+                result.push_str(" FROM ");
+                result.push_str(&tables[0].to_string());
+            }
+        }
+    }
+
+    if let Some(selection) = &delete.selection {
+        result.push('\n');
+        result.push_str(&format!(
+            "{}WHERE {}",
+            " ".repeat(indent_level),
+            format_where_expr(selection, indent_level + 2)
+        ));
+    }
+
+    result
+}
+
+fn format_create_table(create_table: &CreateTable, indent_level: usize) -> String {
+    let mut result = format!("{}CREATE TABLE {} (", " ".repeat(indent_level), create_table.name);
+
+    if create_table.columns.len() == 1 {
+        result.push_str(&format_column_def(&create_table.columns[0]));
+    } else {
+        result.push('\n');
+        for (i, column) in create_table.columns.iter().enumerate() {
+            if i > 0 {
+                result.push_str(",\n");
+            }
+            result.push_str(&format!("{}  {}", " ".repeat(indent_level), format_column_def(column)));
+        }
+        result.push('\n');
+        result.push_str(&format!("{}", " ".repeat(indent_level)));
+    }
+
+    result.push(')');
+    result
+}
+
+fn format_column_def(column: &ColumnDef) -> String {
+    let mut result = format!("{} {}", column.name.value, column.data_type);
+
+    for option in &column.options {
+        match &option.option {
+            ColumnOption::NotNull => result.push_str(" NOT NULL"),
+            ColumnOption::Null => result.push_str(" NULL"),
+            ColumnOption::Default(expr) => {
+                result.push_str(&format!(" DEFAULT {}", format_expr(expr)));
+            }
+            ColumnOption::Unique { is_primary, .. } => {
+                if *is_primary {
+                    result.push_str(" PRIMARY KEY");
+                } else {
+                    result.push_str(" UNIQUE");
+                }
+            }
+            _ => {}
+        }
+    }
+
+    result
+}
tests/fixtures/create_table_statement/input.sql
@@ -0,0 +1,1 @@
+CREATE TABLE users (id INTEGER PRIMARY KEY, name VARCHAR(100) NOT NULL, email VARCHAR(255) UNIQUE, active BOOLEAN DEFAULT true)
\ No newline at end of file
tests/fixtures/create_table_statement/output.sql
@@ -0,0 +1,6 @@
+CREATE TABLE users (
+  id INTEGER PRIMARY KEY,
+  name VARCHAR(100) NOT NULL,
+  email VARCHAR(255) UNIQUE,
+  active BOOLEAN DEFAULT TRUE
+);
\ No newline at end of file
tests/fixtures/delete_statement/input.sql
@@ -0,0 +1,1 @@
+DELETE FROM users WHERE active = false AND last_login < '2020-01-01'
\ No newline at end of file
tests/fixtures/delete_statement/output.sql
@@ -0,0 +1,3 @@
+DELETE FROM users
+WHERE active = FALSE
+  AND last_login < '2020-01-01';
\ No newline at end of file
tests/fixtures/insert_statement/input.sql
@@ -0,0 +1,1 @@
+INSERT INTO users (id, name, email, active) VALUES (1, 'John Doe', 'john@example.com', true)
\ No newline at end of file
tests/fixtures/insert_statement/output.sql
@@ -0,0 +1,12 @@
+INSERT INTO users (
+  id,
+  name,
+  email,
+  active
+)
+VALUES (
+  1,
+  'John Doe',
+  'john@example.com',
+  TRUE
+);
\ No newline at end of file