mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-17 08:34:19 +01:00
Fix: actually enforce uniqueness in CREATE UNIQUE INDEX
...we just didn't do it
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::bail_parse_error;
|
use crate::bail_parse_error;
|
||||||
|
use crate::error::SQLITE_CONSTRAINT_UNIQUE;
|
||||||
use crate::schema::{Table, RESERVED_TABLE_PREFIXES};
|
use crate::schema::{Table, RESERVED_TABLE_PREFIXES};
|
||||||
use crate::translate::emitter::{
|
use crate::translate::emitter::{
|
||||||
emit_cdc_full_record, emit_cdc_insns, prepare_cdc_if_necessary, OperationMode, Resolver,
|
emit_cdc_full_record, emit_cdc_insns, prepare_cdc_if_necessary, OperationMode, Resolver,
|
||||||
};
|
};
|
||||||
use crate::translate::expr::{translate_condition_expr, ConditionMetadata};
|
use crate::translate::expr::{translate_condition_expr, ConditionMetadata};
|
||||||
|
use crate::translate::insert::format_unique_violation_desc;
|
||||||
use crate::translate::plan::{
|
use crate::translate::plan::{
|
||||||
ColumnUsedMask, IterationDirection, JoinedTable, Operation, Scan, TableReferences,
|
ColumnUsedMask, IterationDirection, JoinedTable, Operation, Scan, TableReferences,
|
||||||
};
|
};
|
||||||
@@ -91,6 +93,7 @@ pub fn translate_create_index(
|
|||||||
};
|
};
|
||||||
let original_columns = columns;
|
let original_columns = columns;
|
||||||
let columns = resolve_sorted_columns(&tbl, columns)?;
|
let columns = resolve_sorted_columns(&tbl, columns)?;
|
||||||
|
let unique = unique_if_not_exists.0;
|
||||||
|
|
||||||
let idx = Arc::new(Index {
|
let idx = Arc::new(Index {
|
||||||
name: idx_name.clone(),
|
name: idx_name.clone(),
|
||||||
@@ -106,7 +109,7 @@ pub fn translate_create_index(
|
|||||||
default: col.default.clone(),
|
default: col.default.clone(),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
unique: unique_if_not_exists.0,
|
unique,
|
||||||
ephemeral: false,
|
ephemeral: false,
|
||||||
has_rowid: tbl.has_rowid,
|
has_rowid: tbl.has_rowid,
|
||||||
// store the *original* where clause, because we need to rewrite it
|
// store the *original* where clause, because we need to rewrite it
|
||||||
@@ -295,8 +298,34 @@ pub fn translate_create_index(
|
|||||||
cursor_id: sorter_cursor_id,
|
cursor_id: sorter_cursor_id,
|
||||||
pc_if_empty: sorted_loop_end,
|
pc_if_empty: sorted_loop_end,
|
||||||
});
|
});
|
||||||
program.preassign_label_to_next_insn(sorted_loop_start);
|
|
||||||
let sorted_record_reg = program.alloc_register();
|
let sorted_record_reg = program.alloc_register();
|
||||||
|
|
||||||
|
if unique {
|
||||||
|
// Since the records to be inserted are sorted, we can compare prev with current and if they are equal,
|
||||||
|
// we fall through to Halt with a unique constraint violation error.
|
||||||
|
let goto_label = program.allocate_label();
|
||||||
|
let label_after_sorter_compare = program.allocate_label();
|
||||||
|
program.resolve_label(goto_label, program.offset());
|
||||||
|
program.emit_insn(Insn::Goto {
|
||||||
|
target_pc: label_after_sorter_compare,
|
||||||
|
});
|
||||||
|
program.preassign_label_to_next_insn(sorted_loop_start);
|
||||||
|
program.emit_insn(Insn::SorterCompare {
|
||||||
|
cursor_id: sorter_cursor_id,
|
||||||
|
sorted_record_reg,
|
||||||
|
num_regs: columns.len(),
|
||||||
|
pc_when_nonequal: goto_label,
|
||||||
|
});
|
||||||
|
program.emit_insn(Insn::Halt {
|
||||||
|
err_code: SQLITE_CONSTRAINT_UNIQUE,
|
||||||
|
description: format_unique_violation_desc(tbl_name.as_str(), &idx),
|
||||||
|
});
|
||||||
|
program.preassign_label_to_next_insn(label_after_sorter_compare);
|
||||||
|
} else {
|
||||||
|
program.preassign_label_to_next_insn(sorted_loop_start);
|
||||||
|
}
|
||||||
|
|
||||||
program.emit_insn(Insn::SorterData {
|
program.emit_insn(Insn::SorterData {
|
||||||
pseudo_cursor: pseudo_cursor_id,
|
pseudo_cursor: pseudo_cursor_id,
|
||||||
cursor_id: sorter_cursor_id,
|
cursor_id: sorter_cursor_id,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ pub struct Sorter {
|
|||||||
/// The number of values in the key.
|
/// The number of values in the key.
|
||||||
key_len: usize,
|
key_len: usize,
|
||||||
/// The key info.
|
/// The key info.
|
||||||
index_key_info: Rc<Vec<KeyInfo>>,
|
pub index_key_info: Rc<Vec<KeyInfo>>,
|
||||||
/// Sorted chunks stored on disk.
|
/// Sorted chunks stored on disk.
|
||||||
chunks: Vec<SortedChunk>,
|
chunks: Vec<SortedChunk>,
|
||||||
/// The heap of records consumed from the chunks and their corresponding chunk index.
|
/// The heap of records consumed from the chunks and their corresponding chunk index.
|
||||||
|
|||||||
@@ -13,3 +13,49 @@ do_execsql_test_on_specific_db {:memory:} create-index-quoted-identifiers {
|
|||||||
"CREATE INDEX \"idx idx\" ON \"t t\" (\"a a\")"
|
"CREATE INDEX \"idx idx\" ON \"t t\" (\"a a\")"
|
||||||
"CREATE UNIQUE INDEX \"unique idx idx\" ON \"t t\" (\"a a\")"
|
"CREATE UNIQUE INDEX \"unique idx idx\" ON \"t t\" (\"a a\")"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# single-column index key: creating unique index fails with duplicates
|
||||||
|
do_execsql_test_in_memory_any_error create-unique-index-with-duplicates-1 {
|
||||||
|
CREATE TABLE t1(a, b);
|
||||||
|
INSERT INTO t1 VALUES(1, 1);
|
||||||
|
INSERT INTO t1 VALUES(1, 2);
|
||||||
|
CREATE UNIQUE INDEX idx1 ON t1(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
# multi-column index key: creating unique index fails with duplicates
|
||||||
|
do_execsql_test_in_memory_any_error create-unique-index-with-duplicates-2 {
|
||||||
|
CREATE TABLE t2(a, b, c);
|
||||||
|
INSERT INTO t2 VALUES(1, 2, 3);
|
||||||
|
INSERT INTO t2 VALUES(1, 2, 4);
|
||||||
|
CREATE UNIQUE INDEX idx2 ON t2(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# single-column index key: creating unique index succeeds because NULLs are never equal
|
||||||
|
do_execsql_test_on_specific_db {:memory:} create-unique-index-with-duplicates-3 {
|
||||||
|
CREATE TABLE t3(a);
|
||||||
|
INSERT INTO t3 VALUES(NULL);
|
||||||
|
INSERT INTO t3 VALUES(NULL);
|
||||||
|
CREATE UNIQUE INDEX idx3 ON t3(a);
|
||||||
|
SELECT count(*) FROM t3;
|
||||||
|
} {2}
|
||||||
|
|
||||||
|
# multi-column index key: creating unique index succeeds because NULLs are never equal
|
||||||
|
do_execsql_test_on_specific_db {:memory:} create-unique-index-with-duplicates-4 {
|
||||||
|
CREATE TABLE t4(a, b);
|
||||||
|
INSERT INTO t4 VALUES(1, NULL);
|
||||||
|
INSERT INTO t4 VALUES(1, NULL);
|
||||||
|
CREATE UNIQUE INDEX idx4 ON t4(a, b);
|
||||||
|
SELECT count(*) FROM t4;
|
||||||
|
} {2}
|
||||||
|
|
||||||
|
# multi-column index key: creating unique index succeeds when all NULLs
|
||||||
|
do_execsql_test_on_specific_db {:memory:} create-unique-index-with-duplicates-5 {
|
||||||
|
CREATE TABLE t5(a, b);
|
||||||
|
INSERT INTO t5 VALUES(NULL, NULL);
|
||||||
|
INSERT INTO t5 VALUES(NULL, NULL);
|
||||||
|
CREATE UNIQUE INDEX idx5 ON t5(a, b);
|
||||||
|
SELECT count(*) FROM t5;
|
||||||
|
} {2}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user