From 738afb2e37275cc0014e82ba424fce59bd526fbf Mon Sep 17 00:00:00 2001 From: Erik Grinaker Date: Tue, 18 Jun 2024 21:24:12 +0200 Subject: [PATCH] wip sql: clean up `Local` engine --- src/sql/engine/local.rs | 349 +++++++++++------- src/sql/engine/raft.rs | 2 +- src/sql/types/schema.rs | 25 +- tests/sql/schema/delete_ref_conflict | 2 +- tests/sql/schema/delete_ref_self_all | 9 +- tests/sql/schema/delete_ref_self_conflict | 2 +- tests/sql/schema/drop_table_ref_target | 2 +- tests/sql/schema/insert_pk_boolean_conflict | 2 +- tests/sql/schema/insert_pk_float_conflict | 2 +- tests/sql/schema/insert_pk_float_infinity | 2 +- tests/sql/schema/insert_pk_float_nan | 2 +- tests/sql/schema/insert_pk_integer_conflict | 2 +- tests/sql/schema/insert_pk_string_conflict | 2 +- tests/sql/schema/update_pk_float_conflict | 2 +- tests/sql/schema/update_pk_float_conflict_all | 2 +- tests/sql/schema/update_pk_integer_conflict | 2 +- .../sql/schema/update_pk_integer_conflict_all | 2 +- tests/sql/schema/update_pk_string_conflict | 2 +- .../sql/schema/update_pk_string_conflict_all | 2 +- tests/sql/schema/update_ref_pk | 2 +- tests/sql/schema/update_ref_self_pk | 2 +- 21 files changed, 243 insertions(+), 176 deletions(-) diff --git a/src/sql/engine/local.rs b/src/sql/engine/local.rs index 6b102875c..b7d41829a 100644 --- a/src/sql/engine/local.rs +++ b/src/sql/engine/local.rs @@ -1,44 +1,45 @@ -use super::super::types::schema::Table; -use super::super::types::{Expression, Row, Rows, Value}; -use super::{Catalog, Transaction as _}; +use super::Catalog; use crate::encoding::{self, Key as _, Value as _}; use crate::error::Result; -use crate::storage; +use crate::sql::types::schema::Table; +use crate::sql::types::{Expression, Row, Rows, Value}; +use crate::storage::{self, mvcc}; use crate::{errdata, errinput}; use serde::{Deserialize, Serialize}; use std::borrow::Cow; -use std::clone::Clone; use std::collections::{HashMap, HashSet}; -/// A SQL engine using a local storage engine. +/// A SQL engine using local storage. This provides the main SQL storage logic, +/// including with the Raft SQL engine which dispatches to this engine for +/// node-local SQL storage. pub struct Local { - /// The underlying key/value store. - pub(super) kv: storage::mvcc::MVCC, + /// The local MVCC storage engine. + pub(super) mvcc: mvcc::MVCC, } impl Local { - /// Creates a new key/value-based SQL engine + /// Creates a new local SQL engine using the given storage engine. pub fn new(engine: E) -> Self { - Self { kv: storage::mvcc::MVCC::new(engine) } + Self { mvcc: mvcc::MVCC::new(engine) } } - /// Resumes a transaction from the given state - pub fn resume( - &self, - state: storage::mvcc::TransactionState, - ) -> Result<::Transaction> { - Ok(::Transaction::new(self.kv.resume(state)?)) + /// Resumes a transaction from the given state. This is usually encapsulated + /// in `mvcc::Transaction`, but the Raft-based engine can't retain the MVCC + /// transaction between each request since it may be executed across + /// multiple leader nodes, so it instead keeps the state in the session. + pub fn resume(&self, state: mvcc::TransactionState) -> Result> { + Ok(Transaction::new(self.mvcc.resume(state)?)) } - /// Fetches an unversioned key. + /// Fetches an unversioned key, or None if it doesn't exist. pub fn get_unversioned(&self, key: &[u8]) -> Result>> { - self.kv.get_unversioned(key) + self.mvcc.get_unversioned(key) } /// Sets an unversioned key. pub fn set_unversioned(&self, key: &[u8], value: Vec) -> Result<()> { - self.kv.set_unversioned(key, value) + self.mvcc.set_unversioned(key, value) } } @@ -46,77 +47,86 @@ impl<'a, E: storage::Engine + 'a> super::Engine<'a> for Local { type Transaction = Transaction; fn begin(&self) -> Result { - Ok(Self::Transaction::new(self.kv.begin()?)) + Ok(Self::Transaction::new(self.mvcc.begin()?)) } fn begin_read_only(&self) -> Result { - Ok(Self::Transaction::new(self.kv.begin_read_only()?)) + Ok(Self::Transaction::new(self.mvcc.begin_read_only()?)) } - fn begin_as_of(&self, version: u64) -> Result { - Ok(Self::Transaction::new(self.kv.begin_as_of(version)?)) + fn begin_as_of(&self, version: mvcc::Version) -> Result { + Ok(Self::Transaction::new(self.mvcc.begin_as_of(version)?)) } } -/// An SQL transaction based on an MVCC key/value transaction +/// A SQL transaction, wrapping an MVCC transaction. pub struct Transaction { - txn: storage::mvcc::Transaction, + txn: mvcc::Transaction, } impl Transaction { - /// Creates a new SQL transaction from an MVCC transaction - fn new(txn: storage::mvcc::Transaction) -> Self { + /// Creates a new SQL transaction using the given MVCC transaction. + fn new(txn: mvcc::Transaction) -> Self { Self { txn } } - /// Returns the transaction's serialized state. - pub(super) fn state(&self) -> &storage::mvcc::TransactionState { + /// Returns the transaction's internal state. + pub(super) fn state(&self) -> &mvcc::TransactionState { self.txn.state() } - /// Loads an index entry - fn index_load(&self, table: &str, column: &str, value: &Value) -> Result> { + /// Fetches the matching primary keys for the given secondary index value, + /// or an empty set if there is none. + fn get_index(&self, table: &str, column: &str, value: &Value) -> Result> { + debug_assert!(self.has_index(table, column)?, "no index on {table}.{column}"); Ok(self .txn .get(&Key::Index(table.into(), column.into(), value.into()).encode())? - .map(|v| HashSet::::decode(&v)) + .map(|v| HashSet::decode(&v)) .transpose()? .unwrap_or_default()) } - /// Saves an index entry. - fn index_save( + /// Returns true if the given secondary index exists. + fn has_index(&self, table: &str, column: &str) -> Result { + Ok(self.must_get_table(table)?.get_column(column)?.index) + } + + /// Stores a secondary index entry for the given column value, replacing the + /// existing entry. + fn set_index( &self, table: &str, column: &str, value: &Value, - index: HashSet, + ids: HashSet, ) -> Result<()> { + debug_assert!(self.has_index(table, column)?, "no index on {table}.{column}"); let key = Key::Index(table.into(), column.into(), value.into()).encode(); - if index.is_empty() { + if ids.is_empty() { self.txn.delete(&key) } else { - self.txn.set(&key, index.encode()) + self.txn.set(&key, ids.encode()) } } - /// Returns all foreign key references to a table, as table -> columns. + /// Returns all tables referencing a table, as (table, column index) pairs. /// This includes references from the table itself. - fn references(&self, table: &str) -> Result)>> { + fn table_references(&self, table: &str) -> Result)>> { Ok(self .list_tables()? .into_iter() .map(|t| { - ( - t.name, - t.columns - .iter() - .filter(|c| c.references.as_deref() == Some(table)) - .map(|c| c.name.clone()) - .collect::>(), - ) + let references: Vec = t + .columns + .iter() + .enumerate() + .filter(|(_, c)| c.references.as_deref() == Some(table)) + .map(|(i, _)| i) + .collect(); + (t, references) }) - .filter(|(_, cs)| !cs.is_empty()) + .filter(|(_, references)| !references.is_empty()) .collect()) } } @@ -138,65 +148,63 @@ impl super::Transaction for Transaction { self.txn.rollback() } - fn insert(&self, table: &str, rows: Vec) -> Result<()> { - let table = self.must_get_table(table)?; - for row in rows { - table.validate_row(&row, self)?; - let id = table.get_row_key(&row)?; - if !self.get(&table.name, &[id.clone()])?.is_empty() { - return errinput!("primary key {id} already exists for table {}", table.name); - } - self.txn.set(&Key::Row((&table.name).into(), id.into()).encode(), row.encode())?; - - // Update indexes - for (i, column) in table.columns.iter().enumerate().filter(|(_, c)| c.index) { - let mut index = self.index_load(&table.name, &column.name, &row[i])?; - index.insert(id.clone()); - self.index_save(&table.name, &column.name, &row[i], index)?; - } - } - Ok(()) - } - fn delete(&self, table: &str, ids: &[Value]) -> Result<()> { - // Check for foreign key referenes. - - // TODO: try to be more clever than simply iterating over each ID. - for id in ids { - let table = self.must_get_table(table)?; - for (t, cs) in self.references(&table.name)? { - let t = self.must_get_table(&t)?; - let cs = cs - .into_iter() - .map(|c| Ok((t.get_column_index(&c)?, c))) - .collect::>>()?; - let mut scan = self.scan(&t.name, None)?; - while let Some(row) = scan.next().transpose()? { - for (i, c) in &cs { - if &row[*i] == id - && (table.name != t.name || id != table.get_row_key(&row)?) - { - return errinput!( - "primary key {id} referenced by table {} column {c}", - t.name - ); - } + let table = self.must_get_table(table)?; + let indexes: Vec<_> = table.columns.iter().enumerate().filter(|(_, c)| c.index).collect(); + + // Check for foreign key references to the deleted rows. + for (source, refs) in self.table_references(&table.name)? { + let self_reference = source.name == table.name; + for idx in refs { + let column = &source.columns[idx]; + let mut source_ids = if idx == source.primary_key { + // If the reference is from a primary key column, do a lookup. + self.get(&source.name, ids)? + .into_iter() + .map(|row| row.into_iter().nth(idx).expect("short row")) + .collect() + } else { + // Otherwise (commonly), do a secondary index lookup. + // All foreign keys have a secondary index. + self.lookup_index(&source.name, &column.name, ids)? + }; + // We can ignore any references between the deleted rows, + // including a row referring to itself. + if self_reference { + for id in ids { + source_ids.remove(id); } } + if let Some(source_id) = source_ids.into_iter().next() { + return errinput!( + "row referenced by {}.{} for {}.{}={source_id}", + source.name, + column.name, + source.name, + source.columns[source.primary_key].name + ); + } } + } - let indexes: Vec<_> = - table.columns.iter().enumerate().filter(|(_, c)| c.index).collect(); + for (id, idview) in ids.windows(1).map(|w| (&w[0], w)) { + // Remove the primary key from any index entries. There must be + // an index entry for each row. + // + // TODO: NULL entries shouldn't be indexed, we can skip + // those. But they're currently indexed. if !indexes.is_empty() { - for row in self.get(&table.name, &[id.clone()])? { - for (i, column) in &indexes { - let mut index = self.index_load(&table.name, &column.name, &row[*i])?; + for row in self.get(&table.name, idview)? { + for (i, column) in indexes.iter().copied() { + let mut index = self.get_index(&table.name, &column.name, &row[i])?; index.remove(id); - self.index_save(&table.name, &column.name, &row[*i], index)?; + self.set_index(&table.name, &column.name, &row[i], index)?; } } } - self.txn.delete(&Key::Row(table.name.into(), id.into()).encode())?; + + // Delete the row. + self.txn.delete(&Key::Row((&table.name).into(), id.into()).encode())?; } Ok(()) } @@ -212,22 +220,37 @@ impl super::Transaction for Transaction { .collect() } - fn lookup_index(&self, table: &str, column: &str, value: &[Value]) -> Result> { - if !self.must_get_table(table)?.get_column(column)?.index { - return errinput!("no index on {table}.{column}"); + fn insert(&self, table: &str, rows: Vec) -> Result<()> { + let table = self.must_get_table(table)?; + for row in rows { + // Insert the row. + table.validate_row(&row, false, self)?; + let id = &row[table.primary_key]; + self.txn.set(&Key::Row((&table.name).into(), id.into()).encode(), row.encode())?; + + // Update any secondary indexes. + for (i, column) in table.columns.iter().enumerate().filter(|(_, c)| c.index) { + let mut index = self.get_index(&table.name, &column.name, &row[i])?; + index.insert(id.clone()); + self.set_index(&table.name, &column.name, &row[i], index)?; + } } + Ok(()) + } + + fn lookup_index(&self, table: &str, column: &str, values: &[Value]) -> Result> { + debug_assert!(self.has_index(table, column)?, "index lookup without index"); let mut pks = HashSet::new(); - for v in value { - pks.extend(self.index_load(table, column, v)?) + for v in values { + pks.extend(self.get_index(table, column, v)?) } Ok(pks) } fn scan(&self, table: &str, filter: Option) -> Result { - let table = self.must_get_table(table)?; Ok(Box::new( self.txn - .scan_prefix(&KeyPrefix::Row((&table.name).into()).encode()) + .scan_prefix(&KeyPrefix::Row(table.into()).encode()) .iter() .map(|r| r.and_then(|(_, v)| Row::decode(&v))) .filter_map(move |r| match r { @@ -242,22 +265,17 @@ impl super::Transaction for Transaction { }, err => Some(err), }) + // TODO: don't collect. .collect::>() .into_iter(), )) } fn scan_index(&self, table: &str, column: &str) -> Result { - let table = self.must_get_table(table)?; - let column = table.get_column(column)?; - if !column.index { - return errinput!("no index for {}.{}", table.name, column.name); - } + debug_assert!(self.has_index(table, column)?, "index scan without index"); Ok(Box::new( self.txn - .scan_prefix( - &KeyPrefix::Index((&table.name).into(), (&column.name).into()).encode(), - ) + .scan_prefix(&KeyPrefix::Index(table.into(), column.into()).encode()) .iter() .map(|r| -> Result<(Value, HashSet)> { let (k, v) = r?; @@ -267,6 +285,7 @@ impl super::Transaction for Transaction { }; Ok((value, HashSet::::decode(&v)?)) }) + // TODO: don't collect. .collect::>() .into_iter(), )) @@ -274,36 +293,45 @@ impl super::Transaction for Transaction { fn update(&self, table: &str, rows: HashMap) -> Result<()> { let table = self.must_get_table(table)?; - // TODO: be more clever than just iterating here. + for (id, row) in rows { - // If the primary key changes we do a delete and create, otherwise we replace the row - if id != *table.get_row_key(&row)? { - self.delete(&table.name, &[id.clone()])?; + // If the primary key changes, we simply do a delete and insert. + // This simplifies constraint validation. + if id != row[table.primary_key] { + self.delete(&table.name, &[id])?; self.insert(&table.name, vec![row])?; return Ok(()); } - // Update indexes, knowing that the primary key has not changed + // Validate the row, but don't write it yet since we may need to + // read the existing value to update secondary indexes. + table.validate_row(&row, true, self)?; + + // Update indexes, knowing that the primary key has not changed. let indexes: Vec<_> = table.columns.iter().enumerate().filter(|(_, c)| c.index).collect(); if !indexes.is_empty() { let old = self.get(&table.name, &[id.clone()])?.remove(0); for (i, column) in indexes { + // If the value didn't change, we don't have to do anything. if old[i] == row[i] { continue; } - let mut index = self.index_load(&table.name, &column.name, &old[i])?; + + // Remove the old value from the index entry. + let mut index = self.get_index(&table.name, &column.name, &old[i])?; index.remove(&id); - self.index_save(&table.name, &column.name, &old[i], index)?; + self.set_index(&table.name, &column.name, &old[i], index)?; - let mut index = self.index_load(&table.name, &column.name, &row[i])?; + // Insert the new value into the index entry. + let mut index = self.get_index(&table.name, &column.name, &row[i])?; index.insert(id.clone()); - self.index_save(&table.name, &column.name, &row[i], index)?; + self.set_index(&table.name, &column.name, &row[i], index)?; } } - table.validate_row(&row, self)?; - self.txn.set(&Key::Row((&table.name).into(), id.into()).encode(), row.encode())?; + // Update the row. + self.txn.set(&Key::Row((&table.name).into(), (&id).into()).encode(), row.encode())?; } Ok(()) } @@ -319,22 +347,56 @@ impl Catalog for Transaction { } fn drop_table(&self, table: &str, if_exists: bool) -> Result { - let table = if !if_exists { - self.must_get_table(table)? - } else if let Some(table) = self.get_table(table)? { - table - } else { - return Ok(false); + let table = match self.get_table(table)? { + Some(table) => table, + None if if_exists => return Ok(false), + None => return errinput!("table {table} does not exist"), }; - if let Some((t, cs)) = self.references(&table.name)?.iter().find(|(t, _)| *t != table.name) + + // Check for foreign key references. + if let Some((source, refs)) = + self.table_references(&table.name)?.iter().find(|(t, _)| t.name != table.name) { - return errinput!("table {} is referenced by table {} column {}", table.name, t, cs[0]); + return errinput!( + "table {} is referenced from {}.{}", + table.name, + source.name, + source.columns[refs[0]].name + ); + } + + // Delete the table schema entry. + self.txn.delete(&Key::Table((&table.name).into()).encode())?; + + // Delete the table rows. storage::Engine doesn't support writing while + // scanning, so we buffer all keys in a vector. We could also do this in + // batches, although we'd want to do the batching above Raft to avoid + // blocking Raft processing for the duration of the drop. + let keys: Vec> = self + .txn + .scan_prefix(&KeyPrefix::Row((&table.name).into()).encode()) + .iter() + .map(|r| r.map(|(key, _)| key)) + .collect::>()?; + for key in keys { + self.txn.delete(&key)?; } - let mut scan = self.scan(&table.name, None)?; - while let Some(row) = scan.next().transpose()? { - self.delete(&table.name, &[table.get_row_key(&row)?.clone()])? + + // Delete any secondary indexes. + for column in table.columns.iter().filter(|c| c.index) { + let keys: Vec> = self + .txn + .scan_prefix( + &KeyPrefix::Index((&table.name).into(), (&column.name).into()).encode(), + ) + .iter() + .map(|r| r.map(|(key, _)| key)) + .collect::>()?; + for key in keys { + self.txn.delete(&key)?; + } } - self.txn.delete(&Key::Table(table.name.into()).encode())?; + Ok(true) } @@ -351,11 +413,16 @@ impl Catalog for Transaction { } } -/// SQL keys, using the KeyCode order-preserving encoding. Uses table and column -/// names directly as identifiers, to avoid additional indirection. It is not -/// possible to change names, so this is ok. Cow strings allow encoding borrowed -/// values and decoding into owned values. -#[derive(Debug, Deserialize, Serialize)] +/// SQL engine keys, using the KeyCode order-preserving encoding. For +/// simplicity, table and column names are used directly as identifiers in +/// keys, instead of e.g. numberic IDs. It is not possible to change +/// table/column names, so this is fine. +/// +/// Uses Cow to allow encoding borrowed values but decoding owned values. +/// +/// TODO: add helper methods here to encode borrowed keys. This should also +/// encode into a reused byte buffer, see keycode::serialize() comment. +#[derive(Deserialize, Serialize)] enum Key<'a> { /// A table schema by table name. Table(Cow<'a, str>), @@ -369,7 +436,7 @@ impl<'a> encoding::Key<'a> for Key<'a> {} /// Key prefixes, allowing prefix scans of specific parts of the keyspace. These /// must match the keys -- in particular, the enum variant indexes must match. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Deserialize, Serialize)] enum KeyPrefix<'a> { /// All table schemas. Table, diff --git a/src/sql/engine/raft.rs b/src/sql/engine/raft.rs index 8c61dd1ef..fb96e36ef 100644 --- a/src/sql/engine/raft.rs +++ b/src/sql/engine/raft.rs @@ -316,7 +316,7 @@ impl raft::State for State { }; txn.state().encode() } - Read::Status => self.local.kv.status()?.encode(), + Read::Status => self.local.mvcc.status()?.encode(), Read::Get { txn, table, ids } => self.local.resume(txn)?.get(&table, &ids)?.encode(), Read::LookupIndex { txn, table, column, values } => { diff --git a/src/sql/types/schema.rs b/src/sql/types/schema.rs index 9b32096a0..7a2d4017a 100644 --- a/src/sql/types/schema.rs +++ b/src/sql/types/schema.rs @@ -7,7 +7,7 @@ use crate::{errdata, errinput}; use serde::{Deserialize, Serialize}; use std::borrow::Cow; -/// A table schema, which specifies the structure and constraints of its data. +/// A table schema, which specifies the data structure and constraints. /// /// Tables can't change after they are created. There is no ALTER TABLE nor /// CREATE/DROP INDEX -- only CREATE TABLE and DROP TABLE. @@ -183,16 +183,23 @@ impl Table { Ok(()) } - /// Validates a row. + /// Validates a row, include uniqueness constraints and references. /// - /// TODO: clean this up together with the Local engine. Who should be - /// responsible for non-local validation (i.e. primary/unique conflicts and - /// reference integrity)? - pub fn validate_row(&self, row: &[Value], txn: &impl Transaction) -> Result<()> { + /// If update is true, the row replaces an existing entry with the same + /// primary key. Otherwise, it is an insert. Primary key changes are + /// implemented as a delete+insert. + pub fn validate_row(&self, row: &[Value], update: bool, txn: &impl Transaction) -> Result<()> { if row.len() != self.columns.len() { return errinput!("invalid row size for table {}", self.name); } - let pk = self.get_row_key(row)?; + + // Validate primary key. + let id = &row[self.primary_key]; + let idslice = &row[self.primary_key..=self.primary_key]; + if !update && !txn.get(&self.name, idslice)?.is_empty() { + return errinput!("primary key {id} already exists"); + } + for (i, (column, value)) in self.columns.iter().zip(row.iter()).enumerate() { // Validate datatype. match value.datatype() { @@ -222,7 +229,7 @@ impl Table { match value { Value::Null => Ok(()), Value::Float(f) if f.is_nan() => Ok(()), - v if target == &self.name && v == pk => Ok(()), + v if target == &self.name && v == id => Ok(()), v if txn.get(target, &[v.clone()])?.is_empty() => { errinput!("referenced primary key {v} in table {target} does not exist",) } @@ -237,7 +244,7 @@ impl Table { let mut scan = txn.scan(&self.name, None)?; while let Some(row) = scan.next().transpose()? { if row.get(index).unwrap_or(&Value::Null) == value - && self.get_row_key(&row)? != pk + && self.get_row_key(&row)? != id { return errinput!( "unique value {value} already exists for column {}", diff --git a/tests/sql/schema/delete_ref_conflict b/tests/sql/schema/delete_ref_conflict index 6734a1328..e1bd2b1c7 100644 --- a/tests/sql/schema/delete_ref_conflict +++ b/tests/sql/schema/delete_ref_conflict @@ -1,5 +1,5 @@ Query: DELETE FROM target WHERE id = 1 -Error: InvalidInput("primary key 1 referenced by table source column target_id") +Error: InvalidInput("row referenced by source.target_id for source.id=1") Storage: CREATE TABLE source ( diff --git a/tests/sql/schema/delete_ref_self_all b/tests/sql/schema/delete_ref_self_all index 0798d66b3..e0a0f3be9 100644 --- a/tests/sql/schema/delete_ref_self_all +++ b/tests/sql/schema/delete_ref_self_all @@ -1,5 +1,5 @@ Query: DELETE FROM self -Error: InvalidInput("primary key 1 referenced by table self column self_id") +Result: Delete { count: 4 } Storage: CREATE TABLE self ( @@ -7,12 +7,5 @@ CREATE TABLE self ( self_id INTEGER DEFAULT NULL INDEX REFERENCES self, value STRING DEFAULT NULL ) -[Integer(1), Integer(1), String("a")] -[Integer(2), Integer(1), String("b")] -[Integer(3), Integer(3), String("c")] -[Integer(4), Null, String("d")] Index self.self_id -Null => [Integer(4)] -Integer(1) => [Integer(1), Integer(2)] -Integer(3) => [Integer(3)] diff --git a/tests/sql/schema/delete_ref_self_conflict b/tests/sql/schema/delete_ref_self_conflict index ec7306368..65e95fd29 100644 --- a/tests/sql/schema/delete_ref_self_conflict +++ b/tests/sql/schema/delete_ref_self_conflict @@ -1,5 +1,5 @@ Query: DELETE FROM self WHERE id = 1 -Error: InvalidInput("primary key 1 referenced by table self column self_id") +Error: InvalidInput("row referenced by self.self_id for self.id=2") Storage: CREATE TABLE self ( diff --git a/tests/sql/schema/drop_table_ref_target b/tests/sql/schema/drop_table_ref_target index 6dd8b39b6..21befc2f5 100644 --- a/tests/sql/schema/drop_table_ref_target +++ b/tests/sql/schema/drop_table_ref_target @@ -1,5 +1,5 @@ Query: DROP TABLE target -Error: InvalidInput("table target is referenced by table source column target_id") +Error: InvalidInput("table target is referenced from source.target_id") Storage: CREATE TABLE self ( diff --git a/tests/sql/schema/insert_pk_boolean_conflict b/tests/sql/schema/insert_pk_boolean_conflict index d757c5632..c3584446b 100644 --- a/tests/sql/schema/insert_pk_boolean_conflict +++ b/tests/sql/schema/insert_pk_boolean_conflict @@ -1,5 +1,5 @@ Query: INSERT INTO "boolean" VALUES (FALSE) -Error: InvalidInput("primary key FALSE already exists for table boolean") +Error: InvalidInput("primary key FALSE already exists") Storage: CREATE TABLE "boolean" ( diff --git a/tests/sql/schema/insert_pk_float_conflict b/tests/sql/schema/insert_pk_float_conflict index bdbd4637e..774a20d68 100644 --- a/tests/sql/schema/insert_pk_float_conflict +++ b/tests/sql/schema/insert_pk_float_conflict @@ -1,5 +1,5 @@ Query: INSERT INTO "float" VALUES (3.14) -Error: InvalidInput("primary key 3.14 already exists for table float") +Error: InvalidInput("primary key 3.14 already exists") Storage: CREATE TABLE "float" ( diff --git a/tests/sql/schema/insert_pk_float_infinity b/tests/sql/schema/insert_pk_float_infinity index ee2cf927a..43379a791 100644 --- a/tests/sql/schema/insert_pk_float_infinity +++ b/tests/sql/schema/insert_pk_float_infinity @@ -1,5 +1,5 @@ Query: INSERT INTO "float" VALUES (INFINITY) -Error: InvalidInput("primary key inf already exists for table float") +Error: InvalidInput("primary key inf already exists") Storage: CREATE TABLE "float" ( diff --git a/tests/sql/schema/insert_pk_float_nan b/tests/sql/schema/insert_pk_float_nan index d8d8e47f9..3d40d527d 100644 --- a/tests/sql/schema/insert_pk_float_nan +++ b/tests/sql/schema/insert_pk_float_nan @@ -1,5 +1,5 @@ Query: INSERT INTO "float" VALUES (NAN) -Error: InvalidInput("primary key NaN already exists for table float") +Error: InvalidInput("primary key NaN already exists") Storage: CREATE TABLE "float" ( diff --git a/tests/sql/schema/insert_pk_integer_conflict b/tests/sql/schema/insert_pk_integer_conflict index 697619633..31100ac1b 100644 --- a/tests/sql/schema/insert_pk_integer_conflict +++ b/tests/sql/schema/insert_pk_integer_conflict @@ -1,5 +1,5 @@ Query: INSERT INTO "integer" VALUES (1) -Error: InvalidInput("primary key 1 already exists for table integer") +Error: InvalidInput("primary key 1 already exists") Storage: CREATE TABLE "integer" ( diff --git a/tests/sql/schema/insert_pk_string_conflict b/tests/sql/schema/insert_pk_string_conflict index 00d95d407..c840a3a08 100644 --- a/tests/sql/schema/insert_pk_string_conflict +++ b/tests/sql/schema/insert_pk_string_conflict @@ -1,5 +1,5 @@ Query: INSERT INTO "string" VALUES ('foo') -Error: InvalidInput("primary key foo already exists for table string") +Error: InvalidInput("primary key foo already exists") Storage: CREATE TABLE "string" ( diff --git a/tests/sql/schema/update_pk_float_conflict b/tests/sql/schema/update_pk_float_conflict index 6d7de2d70..e06d06782 100644 --- a/tests/sql/schema/update_pk_float_conflict +++ b/tests/sql/schema/update_pk_float_conflict @@ -1,5 +1,5 @@ Query: UPDATE "float" SET pk = 2.718 WHERE pk = 3.14 -Error: InvalidInput("primary key 2.718 already exists for table float") +Error: InvalidInput("primary key 2.718 already exists") Storage: CREATE TABLE "float" ( diff --git a/tests/sql/schema/update_pk_float_conflict_all b/tests/sql/schema/update_pk_float_conflict_all index bfb7af4e3..25d3e4c5f 100644 --- a/tests/sql/schema/update_pk_float_conflict_all +++ b/tests/sql/schema/update_pk_float_conflict_all @@ -1,5 +1,5 @@ Query: UPDATE "float" SET pk = 3.14 -Error: InvalidInput("primary key 3.14 already exists for table float") +Error: InvalidInput("primary key 3.14 already exists") Storage: CREATE TABLE "float" ( diff --git a/tests/sql/schema/update_pk_integer_conflict b/tests/sql/schema/update_pk_integer_conflict index 4c7f31881..c19dc9956 100644 --- a/tests/sql/schema/update_pk_integer_conflict +++ b/tests/sql/schema/update_pk_integer_conflict @@ -1,5 +1,5 @@ Query: UPDATE "integer" SET pk = 1 WHERE pk = 2 -Error: InvalidInput("primary key 1 already exists for table integer") +Error: InvalidInput("primary key 1 already exists") Storage: CREATE TABLE "integer" ( diff --git a/tests/sql/schema/update_pk_integer_conflict_all b/tests/sql/schema/update_pk_integer_conflict_all index a10f111cd..f1b2ac4a5 100644 --- a/tests/sql/schema/update_pk_integer_conflict_all +++ b/tests/sql/schema/update_pk_integer_conflict_all @@ -1,5 +1,5 @@ Query: UPDATE "integer" SET pk = 1 -Error: InvalidInput("primary key 1 already exists for table integer") +Error: InvalidInput("primary key 1 already exists") Storage: CREATE TABLE "integer" ( diff --git a/tests/sql/schema/update_pk_string_conflict b/tests/sql/schema/update_pk_string_conflict index f316959b0..fedf76ae0 100644 --- a/tests/sql/schema/update_pk_string_conflict +++ b/tests/sql/schema/update_pk_string_conflict @@ -1,5 +1,5 @@ Query: UPDATE "string" SET pk = 'bar' WHERE pk = 'foo' -Error: InvalidInput("primary key bar already exists for table string") +Error: InvalidInput("primary key bar already exists") Storage: CREATE TABLE "string" ( diff --git a/tests/sql/schema/update_pk_string_conflict_all b/tests/sql/schema/update_pk_string_conflict_all index 779bf5b14..e44ad1d0a 100644 --- a/tests/sql/schema/update_pk_string_conflict_all +++ b/tests/sql/schema/update_pk_string_conflict_all @@ -1,5 +1,5 @@ Query: UPDATE "string" SET pk = 'foo' -Error: InvalidInput("primary key foo already exists for table string") +Error: InvalidInput("primary key foo already exists") Storage: CREATE TABLE "string" ( diff --git a/tests/sql/schema/update_ref_pk b/tests/sql/schema/update_ref_pk index d18b30017..ca675a004 100644 --- a/tests/sql/schema/update_ref_pk +++ b/tests/sql/schema/update_ref_pk @@ -1,5 +1,5 @@ Query: UPDATE target SET id = 9 WHERE id = 1 -Error: InvalidInput("primary key 1 referenced by table source column target_id") +Error: InvalidInput("row referenced by source.target_id for source.id=1") Storage: CREATE TABLE source ( diff --git a/tests/sql/schema/update_ref_self_pk b/tests/sql/schema/update_ref_self_pk index 78ed8b39a..57afe70da 100644 --- a/tests/sql/schema/update_ref_self_pk +++ b/tests/sql/schema/update_ref_self_pk @@ -1,5 +1,5 @@ Query: UPDATE self SET id = 9 WHERE id = 1 -Error: InvalidInput("primary key 1 referenced by table self column self_id") +Error: InvalidInput("row referenced by self.self_id for self.id=2") Storage: CREATE TABLE self (