1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
//! DML statement compilation (INSERT, UPDATE, DELETE)
//!
//! This module extends SqlCompiler with DML statement compilation methods.
use sqlparser::ast::{Expr, Select, SelectItem, SetExpr, TableFactor};
use super::ast_compat::assignment_column;
use super::bytecode::OpCode;
use super::compiler::{NameCtx, SqlCompiler};
use crate::error::{SqawkError, SqawkResult};
impl<'a> SqlCompiler<'a> {
/// Compile an INSERT statement
pub(crate) fn compile_insert(
&mut self,
table_name: &sqlparser::ast::ObjectName,
columns: &[sqlparser::ast::Ident],
source: &sqlparser::ast::Query,
) -> SqawkResult<()> {
// Get table name as string
let table_name_str = self.get_table_name(table_name)?;
// Get table info
let table = self.database.get_table(&table_name_str)?;
let column_count = table.column_count();
// Determine column indices for insertion
let column_indices: Vec<usize> = if columns.is_empty() {
(0..column_count).collect()
} else {
columns
.iter()
.map(|ident| {
table
.column_index(&ident.value)
.ok_or_else(|| SqawkError::ColumnNotFound(ident.value.clone()))
})
.collect::<Result<Vec<_>, _>>()?
};
// Handle different source types: VALUES or SELECT
match &*source.body {
SetExpr::Values(values) => {
// INSERT ... VALUES
// Generate Init and jump
let init_addr = self.program.len();
self.emit(OpCode::Init, 0, 0, 0, None, "Start INSERT");
// Open table for writing (cursor 0)
self.emit(
OpCode::OpenWrite,
0,
0,
0,
Some(table_name_str.clone()),
&format!("Open {} for writing", table_name_str),
);
// Process each row of values
let base_reg = self.register_counter as usize;
for value_row in &values.rows {
if value_row.len() != column_indices.len() {
return Err(SqawkError::InvalidSqlQuery(format!(
"INSERT statement has {} values but {} columns were specified",
value_row.len(),
column_indices.len()
)));
}
// Initialize all columns to NULL
for i in 0..column_count {
self.emit(
OpCode::Null,
(base_reg + i) as i64,
0,
0,
None,
&format!("Init col {} to NULL", i),
);
}
// Compile each value expression into the appropriate register
for (i, expr) in value_row.iter().enumerate() {
let col_idx = column_indices[i];
let value_reg = base_reg + col_idx;
self.compile_expr_into_register(expr, value_reg)?;
}
// Insert the row
self.emit(
OpCode::InsertRow,
0, // cursor
base_reg as i64,
column_count as i64,
Some(table_name_str.clone()),
"Insert row",
);
}
// Close cursor and halt
self.emit(OpCode::Close, 0, 0, 0, None, "Close cursor");
self.emit(OpCode::Halt, 0, 0, 0, None, "End INSERT");
// Patch init to jump past itself
self.patch_jump(init_addr, init_addr + 1);
// Update register counter
self.register_counter = (base_reg + column_count) as i64;
}
SetExpr::Select(select) => {
// INSERT ... SELECT
self.compile_insert_select(&table_name_str, column_count, &column_indices, select)?;
}
_ => {
return Err(SqawkError::UnsupportedSqlFeature(
"Only INSERT ... VALUES and INSERT ... SELECT are supported".to_string(),
));
}
}
Ok(())
}
/// Compile INSERT ... SELECT statement
pub(crate) fn compile_insert_select(
&mut self,
target_table_name: &str,
target_column_count: usize,
column_indices: &[usize],
select: &Select,
) -> SqawkResult<()> {
// Get the source table from the SELECT
if select.from.is_empty() {
return Err(SqawkError::InvalidSqlQuery(
"INSERT ... SELECT requires a FROM clause".to_string(),
));
}
let source_table_name = match &select.from[0].relation {
TableFactor::Table { name, .. } => self.get_table_name(name)?,
_ => {
return Err(SqawkError::UnsupportedSqlFeature(
"Only simple table references are supported in INSERT...SELECT".to_string(),
))
}
};
let source_table = self.database.get_table(&source_table_name)?;
// Generate Init
let init_addr = self.program.len();
self.emit(OpCode::Init, 0, 0, 0, None, "Start INSERT...SELECT");
// Open source table for reading (cursor 0)
self.emit(
OpCode::OpenRead,
0,
0,
0,
Some(source_table_name.clone()),
&format!("Open {} for reading", source_table_name),
);
// Open target table for writing (cursor 1)
self.emit(
OpCode::OpenWrite,
1,
0,
0,
Some(target_table_name.to_string()),
&format!("Open {} for writing", target_table_name),
);
// Rewind source cursor
let rewind_addr = self.program.len();
self.emit(OpCode::Rewind, 0, 0, 0, None, "Rewind source cursor");
// Loop body start
let loop_start = self.program.len();
// Allocate registers for the row data
let base_reg = self.register_counter as usize;
self.register_counter += target_column_count as i64;
// Initialize all columns to NULL
for i in 0..target_column_count {
self.emit(
OpCode::Null,
(base_reg + i) as i64,
0,
0,
None,
&format!("Init col {} to NULL", i),
);
}
// Load columns from SELECT projection
for (i, proj_item) in select.projection.iter().enumerate() {
if i >= column_indices.len() {
break;
}
let target_col_idx = column_indices[i];
let dest_reg = base_reg + target_col_idx;
match proj_item {
SelectItem::Wildcard(_) => {
// SELECT * - load all columns from source
for col_idx in 0..source_table.column_count() {
if col_idx < target_column_count {
self.emit(
OpCode::Column,
0,
col_idx as i64,
(base_reg + col_idx) as i64,
None,
&format!("Load source col {} to target col {}", col_idx, col_idx),
);
}
}
break; // Wildcard handles all columns
}
SelectItem::UnnamedExpr(expr) => {
// Compile the expression
match expr {
Expr::Identifier(ident) => {
let col_name = ident.value.to_lowercase();
if let Some(col_idx) = source_table.column_index(&col_name) {
self.emit(
OpCode::Column,
0,
col_idx as i64,
dest_reg as i64,
None,
&format!("Load {} to reg {}", col_name, dest_reg),
);
}
}
Expr::CompoundIdentifier(parts) if parts.len() == 2 => {
let col_name = parts[1].value.to_lowercase();
if let Some(col_idx) = source_table.column_index(&col_name) {
self.emit(
OpCode::Column,
0,
col_idx as i64,
dest_reg as i64,
None,
&format!("Load {} to reg {}", col_name, dest_reg),
);
}
}
_ => {
// For other expressions, try to compile them
self.compile_expr_into_register(expr, dest_reg)?;
}
}
}
SelectItem::ExprWithAlias { expr, .. } => {
self.compile_expr_into_register(expr, dest_reg)?;
}
_ => {}
}
}
// Check WHERE clause if present
let skip_addr = if let Some(where_expr) = &select.selection {
let cond_reg = self.compile_where_condition(where_expr, source_table, 0)?;
let addr = self.program.len();
self.emit(
OpCode::IfZ,
cond_reg,
0, // will patch
0,
None,
"Skip if WHERE is false",
);
Some(addr)
} else {
None
};
// Insert the row into target table
self.emit(
OpCode::InsertRow,
1, // target cursor
base_reg as i64,
target_column_count as i64,
Some(target_table_name.to_string()),
"Insert row into target",
);
// Patch skip if present
if let Some(addr) = skip_addr {
self.patch_jump(addr, self.program.len());
}
// Next row
self.emit(
OpCode::Next,
0, // source cursor
loop_start as i64,
0,
None,
"Next source row",
);
// Patch rewind
let end_addr = self.program.len();
self.patch_jump(rewind_addr, end_addr);
// Close cursors and halt
self.emit(OpCode::Close, 0, 0, 0, None, "Close source cursor");
self.emit(OpCode::Close, 1, 0, 0, None, "Close target cursor");
self.emit(OpCode::Halt, 0, 0, 0, None, "End INSERT...SELECT");
// Patch init
self.patch_jump(init_addr, init_addr + 1);
Ok(())
}
/// Compile a DELETE statement
pub(crate) fn compile_delete(
&mut self,
from: &[sqlparser::ast::TableWithJoins],
selection: Option<&Expr>,
) -> SqawkResult<()> {
if from.len() != 1 {
return Err(SqawkError::UnsupportedSqlFeature(
"DELETE with multiple tables is not supported".to_string(),
));
}
// Get table name
let table_with_joins = &from[0];
let table_name = match &table_with_joins.relation {
TableFactor::Table { name, .. } => self.get_table_name(name)?,
_ => {
return Err(SqawkError::UnsupportedSqlFeature(
"Only simple table references are supported in DELETE".to_string(),
))
}
};
// Get table reference for WHERE condition compilation
let table = self.database.get_table(&table_name)?;
// Generate Init
let init_addr = self.program.len();
self.emit(OpCode::Init, 0, 0, 0, None, "Start DELETE");
// Open table for reading (cursor 0)
let cursor_idx = 0;
self.emit(
OpCode::OpenRead,
cursor_idx,
0,
0,
Some(table_name.clone()),
&format!("Open {} for reading", table_name),
);
// Rewind to first row
let rewind_addr = self.program.len();
self.emit(
OpCode::Rewind,
cursor_idx,
0,
0,
None,
"Rewind to first row",
);
// Main loop: iterate over rows
let loop_start = self.program.len();
// If there's a WHERE clause, check if row matches
if let Some(where_expr) = selection {
// Compile WHERE condition using table context
let cond_reg = self.compile_where_condition(where_expr, table, cursor_idx as usize)?;
// If condition is false (0), skip to next row
let skip_addr = self.program.len();
self.emit(
OpCode::IfZ,
cond_reg,
0, // will patch
0,
None,
"Skip if WHERE is false",
);
// Delete the current row
self.emit(
OpCode::DeleteRow,
cursor_idx,
0,
0,
Some(table_name.clone()),
"Delete matching row",
);
// Patch skip to jump to Next
let next_addr = self.program.len();
self.patch_jump(skip_addr, next_addr);
} else {
// No WHERE clause - delete all rows
self.emit(
OpCode::DeleteRow,
0,
0,
0,
Some(table_name.clone()),
"Delete row",
);
}
// Next row
self.emit(
OpCode::Next,
0,
loop_start as i64,
0,
None,
"Move to next row",
);
// Patch Rewind to jump here if table is empty
let end_addr = self.program.len();
self.patch_jump(rewind_addr, end_addr);
// Close and halt
self.emit(OpCode::Close, 0, 0, 0, None, "Close cursor");
self.emit(OpCode::Halt, 0, 0, 0, None, "End DELETE");
// Patch init
self.patch_jump(init_addr, init_addr + 1);
Ok(())
}
/// Compile an UPDATE statement
pub(crate) fn compile_update(
&mut self,
table: &sqlparser::ast::TableWithJoins,
assignments: &[sqlparser::ast::Assignment],
selection: Option<&Expr>,
) -> SqawkResult<()> {
// Get table name
let table_name = match &table.relation {
TableFactor::Table { name, .. } => self.get_table_name(name)?,
_ => {
return Err(SqawkError::UnsupportedSqlFeature(
"Only simple table references are supported in UPDATE".to_string(),
))
}
};
// Get table info for column lookups
let table_ref = self.database.get_table(&table_name)?;
let column_count = table_ref.column_count();
// Parse assignments to get column indices and expressions
let mut assignment_map: Vec<(usize, &Expr)> = Vec::new();
for assignment in assignments {
// Handle different column identifier formats
let col_name = assignment_column(assignment)?;
let col_idx = table_ref
.column_index(&col_name)
.ok_or_else(|| SqawkError::ColumnNotFound(col_name.clone()))?;
assignment_map.push((col_idx, &assignment.value));
}
// Generate Init
let init_addr = self.program.len();
self.emit(OpCode::Init, 0, 0, 0, None, "Start UPDATE");
// Open table for reading (cursor 0)
self.emit(
OpCode::OpenRead,
0,
0,
0,
Some(table_name.clone()),
&format!("Open {} for reading", table_name),
);
// Rewind to first row
let rewind_addr = self.program.len();
self.emit(OpCode::Rewind, 0, 0, 0, None, "Rewind to first row");
// Main loop
let loop_start = self.program.len();
// Allocate registers for the row data
let base_reg = self.register_counter as usize;
self.register_counter += column_count as i64;
// Load all columns into registers
for col_idx in 0..column_count {
self.emit(
OpCode::Column,
0, // cursor
col_idx as i64,
(base_reg + col_idx) as i64,
None,
&format!("Load column {}", col_idx),
);
}
// If there's a WHERE clause, check if row matches
let skip_addr = if let Some(where_expr) = selection {
// Compile WHERE condition using table context
let cond_reg = self.compile_where_condition(where_expr, table_ref, 0)?;
// If condition is false (0), skip to next row
let addr = self.program.len();
self.emit(
OpCode::IfZ,
cond_reg,
0, // will patch
0,
None,
"Skip if WHERE is false",
);
Some(addr)
} else {
None
};
// Apply assignments.
//
// The right-hand side is compiled in the row's scope, so a column may
// appear there: `SET salary = salary + 1`. compile_expr, which this
// used to call, has no Identifier arm at all and rejected it.
//
// Reading through a Column op against the cursor yields the ORIGINAL
// row value, which is the required semantics -- every RHS is evaluated
// against the row as it was before any assignment in this statement.
let update_ctx = NameCtx::single(table_ref, 0);
for (col_idx, expr) in &assignment_map {
self.code_expr(expr, &update_ctx, Some((base_reg + *col_idx) as i64))?;
}
// Replace the row in place.
//
// This was DeleteRow followed by InsertRow, and the insert appends, so
// every updated row jumped to the end of the table -- persisted to the
// user's file under --write. UpdateRow keeps the row where it is, and
// it also makes the affected-row count exact instead of inferred from
// matching insert and delete tallies.
self.emit(
OpCode::UpdateRow,
0,
base_reg as i64,
column_count as i64,
Some(table_name.clone()),
"Replace row in place",
);
// Patch skip if present
if let Some(addr) = skip_addr {
self.patch_jump(addr, self.program.len());
}
// Next row
self.emit(
OpCode::Next,
0,
loop_start as i64,
0,
None,
"Move to next row",
);
// Patch Rewind to jump here if table is empty
let end_addr = self.program.len();
self.patch_jump(rewind_addr, end_addr);
// Close and halt
self.emit(OpCode::Close, 0, 0, 0, None, "Close cursor");
self.emit(OpCode::Halt, 0, 0, 0, None, "End UPDATE");
// Patch init
self.patch_jump(init_addr, init_addr + 1);
Ok(())
}
}