core/vdbe: Fix MakeRecord affinity handling

The MakeRecord instruction now accepts an optional affinity_str
parameter that applies column-specific type conversions before creating
records. When provided, the affinity string is applied
character-by-character to each register using the existing
apply_affinity_char() function, matching SQLite's behavior.

Fixes #2040
Fixes #2041
This commit is contained in:
Pekka Enberg
2025-09-08 15:06:52 +03:00
parent 088cb449a5
commit f88f39082a
17 changed files with 106 additions and 0 deletions

View File

@@ -115,6 +115,7 @@ pub fn handle_distinct(program: &mut ProgramBuilder, agg: &Aggregate, agg_arg_re
count: num_regs,
dest_reg: record_reg,
index_name: Some(distinct_ctx.ephemeral_index_name.to_string()),
affinity_str: None,
});
program.emit_insn(Insn::IdxInsert {
cursor_id: distinct_ctx.cursor_id,

View File

@@ -141,11 +141,18 @@ pub fn translate_alter_table(
let record = program.alloc_register();
let affinity_str = btree
.columns
.iter()
.map(|col| col.affinity().aff_mask())
.collect::<String>();
program.emit_insn(Insn::MakeRecord {
start_reg: first_column,
count: column_count,
dest_reg: record,
index_name: None,
affinity_str: Some(affinity_str),
});
program.emit_insn(Insn::Insert {
@@ -301,6 +308,7 @@ pub fn translate_alter_table(
count: sqlite_schema_column_len,
dest_reg: record,
index_name: None,
affinity_str: None,
});
program.emit_insn(Insn::Insert {
@@ -442,6 +450,7 @@ pub fn translate_alter_table(
count: sqlite_schema_column_len,
dest_reg: record,
index_name: None,
affinity_str: None,
});
program.emit_insn(Insn::Insert {

View File

@@ -212,6 +212,7 @@ pub fn translate_analyze(
count: 3,
dest_reg: record_reg,
index_name: None,
affinity_str: None,
});
program.emit_insn(Insn::NewRowid {
cursor: stat_cursor,

View File

@@ -524,6 +524,7 @@ fn read_intersect_rows(
count: column_count,
dest_reg: row_content_reg,
index_name: None,
affinity_str: None,
});
program.emit_insn(Insn::IdxInsert {
cursor_id: target_cursor_id,

View File

@@ -1038,6 +1038,7 @@ fn emit_update_insns(
count: num_cols + 1,
dest_reg: *record_reg,
index_name: Some(index.name.clone()),
affinity_str: None,
});
if !index.unique {
@@ -1138,11 +1139,19 @@ fn emit_update_insns(
}
let record_reg = program.alloc_register();
let affinity_str = table_ref
.columns()
.iter()
.map(|col| col.affinity().aff_mask())
.collect::<String>();
program.emit_insn(Insn::MakeRecord {
start_reg: start,
count: table_ref.columns().len(),
dest_reg: record_reg,
index_name: None,
affinity_str: Some(affinity_str),
});
if has_user_provided_rowid {
@@ -1282,6 +1291,7 @@ fn emit_update_insns(
count: 2 * table_ref.columns().len(),
dest_reg: record_reg,
index_name: None,
affinity_str: None,
});
Some(record_reg)
} else {
@@ -1398,11 +1408,18 @@ pub fn emit_cdc_patch_record(
dst_reg: columns_reg + rowid_alias_position,
extra_amount: 0,
});
let affinity_str = table
.columns()
.iter()
.map(|col| col.affinity().aff_mask())
.collect::<String>();
program.emit_insn(Insn::MakeRecord {
start_reg: columns_reg,
count: table.columns().len(),
dest_reg: record_reg,
index_name: None,
affinity_str: Some(affinity_str),
});
record_reg
} else {
@@ -1428,11 +1445,17 @@ pub fn emit_cdc_full_record(
program.emit_column_or_rowid(table_cursor_id, i, columns_reg + 1 + i);
}
}
let affinity_str = columns
.iter()
.map(|col| col.affinity().aff_mask())
.collect::<String>();
program.emit_insn(Insn::MakeRecord {
start_reg: columns_reg + 1,
count: columns.len(),
dest_reg: columns_reg,
index_name: None,
affinity_str: Some(affinity_str),
});
columns_reg
}
@@ -1535,6 +1558,7 @@ pub fn emit_cdc_insns(
count: 8,
dest_reg: record_reg,
index_name: None,
affinity_str: None,
});
program.emit_insn(Insn::Insert {

View File

@@ -174,6 +174,7 @@ pub fn translate_create_index(
count: columns.len() + 1,
dest_reg: record_reg,
index_name: Some(idx_name.clone()),
affinity_str: None,
});
program.emit_insn(Insn::SorterInsert {
cursor_id: sorter_cursor_id,

View File

@@ -253,11 +253,35 @@ pub fn translate_insert(
end_offset: yield_label,
});
let record_reg = program.alloc_register();
let affinity_str = if columns.is_empty() {
btree_table
.columns
.iter()
.filter(|col| !col.hidden)
.map(|col| col.affinity().aff_mask())
.collect::<String>()
} else {
columns
.iter()
.map(|col_name| {
let column_name = normalize_ident(col_name.as_str());
table
.get_column_by_name(&column_name)
.unwrap()
.1
.affinity()
.aff_mask()
})
.collect::<String>()
};
program.emit_insn(Insn::MakeRecord {
start_reg: yield_reg + 1,
count: result.num_result_cols,
dest_reg: record_reg,
index_name: None,
affinity_str: Some(affinity_str),
});
let rowid_reg = program.alloc_register();
@@ -507,6 +531,7 @@ pub fn translate_insert(
count: num_cols + 1,
dest_reg: record_reg,
index_name: Some(index.name.clone()),
affinity_str: None,
});
if index.unique {
@@ -627,11 +652,18 @@ pub fn translate_insert(
});
}
// Create and insert the record
let affinity_str = insertion
.col_mappings
.iter()
.map(|col_mapping| col_mapping.column.affinity().aff_mask())
.collect::<String>();
program.emit_insn(Insn::MakeRecord {
start_reg: insertion.first_col_register(),
count: insertion.col_mappings.len(),
dest_reg: insertion.record_register(),
index_name: None,
affinity_str: Some(affinity_str),
});
program.emit_insn(Insn::Insert {
cursor: cursor_id,

View File

@@ -1429,6 +1429,7 @@ fn emit_autoindex(
count: num_regs_to_reserve,
dest_reg: record_reg,
index_name: Some(index.name.clone()),
affinity_str: None,
});
program.emit_insn(Insn::IdxInsert {
cursor_id: index_cursor_id,

View File

@@ -307,6 +307,7 @@ pub fn sorter_insert(
count: column_count,
dest_reg: record_reg,
index_name: None,
affinity_str: None,
});
program.emit_insn(Insn::SorterInsert {
cursor_id,

View File

@@ -264,6 +264,7 @@ impl DistinctCtx {
count: num_regs,
dest_reg: record_reg,
index_name: Some(self.ephemeral_index_name.to_string()),
affinity_str: None,
});
program.emit_insn(Insn::IdxInsert {
cursor_id: self.cursor_id,

View File

@@ -103,6 +103,7 @@ pub fn emit_result_row_and_limit(
count: plan.result_columns.len(),
dest_reg: record_reg,
index_name: Some(dedupe_index.name.clone()),
affinity_str: None,
});
program.emit_insn(Insn::IdxInsert {
cursor_id: *index_cursor_id,
@@ -124,6 +125,7 @@ pub fn emit_result_row_and_limit(
count: plan.result_columns.len() - 1,
dest_reg: record_reg,
index_name: Some(table.name.clone()),
affinity_str: None,
});
}
program.emit_insn(Insn::Insert {

View File

@@ -257,6 +257,7 @@ pub fn emit_schema_entry(
count: 5,
dest_reg: record_reg,
index_name: None,
affinity_str: None,
});
program.emit_insn(Insn::Insert {
@@ -614,6 +615,7 @@ pub fn translate_create_virtual_table(
count: args_vec.len(),
dest_reg: args_record_reg,
index_name: None,
affinity_str: None,
});
Some(args_record_reg)
} else {
@@ -998,6 +1000,7 @@ pub fn translate_drop_table(
count: 5,
dest_reg: new_record_register,
index_name: None,
affinity_str: None,
});
program.emit_insn(Insn::Delete {
cursor_id: sqlite_schema_cursor_id_1,

View File

@@ -379,6 +379,7 @@ pub fn emit_upsert(
count: k + 1,
dest_reg: rec,
index_name: Some((*idx_name).clone()),
affinity_str: None,
});
program.emit_insn(Insn::IdxInsert {
cursor_id: *idx_cid,
@@ -392,11 +393,19 @@ pub fn emit_upsert(
// Write table row (same rowid, new payload)
let rec = program.alloc_register();
let affinity_str = table
.columns()
.iter()
.map(|col| col.affinity().aff_mask())
.collect::<String>();
program.emit_insn(Insn::MakeRecord {
start_reg: new_start,
count: num_cols,
dest_reg: rec,
index_name: None,
affinity_str: Some(affinity_str),
});
program.emit_insn(Insn::Insert {
cursor: tbl_cursor_id,

View File

@@ -199,6 +199,7 @@ fn emit_values_to_index(
count: row_len,
dest_reg: record_reg,
index_name: Some(index.name.clone()),
affinity_str: None,
});
program.emit_insn(Insn::IdxInsert {
cursor_id: *cursor_id,

View File

@@ -1864,10 +1864,27 @@ pub fn op_make_record(
start_reg,
count,
dest_reg,
affinity_str,
..
},
insn
);
if let Some(affinity_str) = affinity_str {
if affinity_str.len() != *count {
return Err(LimboError::InternalError(format!(
"MakeRecord: the length of affinity string ({}) does not match the count ({})",
affinity_str.len(),
*count
)));
}
for (i, affinity_ch) in affinity_str.chars().enumerate().take(*count) {
let reg_index = *start_reg + i;
let affinity = Affinity::from_char(affinity_ch);
apply_affinity_char(&mut state.registers[reg_index], affinity);
}
}
let record = make_record(&state.registers, start_reg, count);
state.registers[*dest_reg] = Register::Record(record);
state.pc += 1;

View File

@@ -584,6 +584,7 @@ pub fn insn_to_str(
count,
dest_reg,
index_name,
affinity_str: _,
} => {
let for_index = index_name.as_ref().map(|name| format!("; for {name}"));
(

View File

@@ -430,6 +430,7 @@ pub enum Insn {
count: usize, // P2
dest_reg: usize, // P3
index_name: Option<String>,
affinity_str: Option<String>,
},
/// Emit a row of results.