Merge branch 'main' into persistence-2-electric-boogaloo
# Conflicts: # Cargo.lock # minisql/Cargo.toml # minisql/src/internals/row.rs # minisql/src/interpreter.rs # minisql/src/schema.rs # minisql/src/type_system.rs
This commit is contained in:
commit
6bf4e34006
28 changed files with 1146 additions and 654 deletions
|
|
@ -7,4 +7,5 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
bimap = { version = "0.6.3", features = ["serde"] }
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
thiserror = "1.0.50"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,33 @@
|
|||
use std::num::{ParseFloatError, ParseIntError};
|
||||
use std::str::Utf8Error;
|
||||
use thiserror::Error;
|
||||
use crate::internals::row::ColumnPosition;
|
||||
use crate::schema::{ColumnName, TableName};
|
||||
use crate::operation::InsertionValues;
|
||||
use crate::type_system::{DbType, Uuid, Value};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
TableDoesNotExist(TableName),
|
||||
ColumnDoesNotExist(TableName, ColumnName),
|
||||
#[error("column position {1} of table {0} does not exist")]
|
||||
ColumnPositionDoesNotExist(TableName, ColumnPosition),
|
||||
#[error("column {1} of table {0} has unexpected type {2:?} and value {3:?}")]
|
||||
ValueDoesNotMatchExpectedType(TableName, ColumnName, DbType, Value),
|
||||
#[error("table {0} already contains row with id {1}")]
|
||||
AttemptingToInsertAlreadyPresentId(TableName, Uuid),
|
||||
MissingTypeAnnotationOfColumn(TableName, ColumnPosition),
|
||||
MissingColumnInInsertValues(TableName, ColumnName, InsertionValues),
|
||||
MismatchBetweenInsertValuesAndColumns(TableName, InsertionValues),
|
||||
#[error("table {0} cannot be indexed on column {1}")]
|
||||
AttemptToIndexNonIndexableColumn(TableName, ColumnName),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TypeConversionError {
|
||||
#[error("failed to decode bytes to string")]
|
||||
TextDecodeFailed(#[from] Utf8Error),
|
||||
#[error("failed to parse float from text")]
|
||||
NumberDecodeFailed(#[from] ParseFloatError),
|
||||
#[error("failed to parse int from text")]
|
||||
IntDecodeFailed(#[from] ParseIntError),
|
||||
#[error("unknown type with oid {oid} and size {size}")]
|
||||
UnknownType {
|
||||
oid: i32,
|
||||
size: i16
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use crate::type_system::Value;
|
||||
use crate::operation::InsertionValues;
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::slice::SliceIndex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::restricted_row::RestrictedRow;
|
||||
|
||||
pub type ColumnPosition = usize;
|
||||
|
||||
|
|
@ -43,6 +45,10 @@ impl Row {
|
|||
Row(vec![])
|
||||
}
|
||||
|
||||
pub fn new_from_insertion_values(insertion_values: InsertionValues) -> Self {
|
||||
Row(insertion_values)
|
||||
}
|
||||
|
||||
pub fn with_number_of_columns(number_of_columns: usize) -> Self {
|
||||
Row(Vec::with_capacity(number_of_columns))
|
||||
}
|
||||
|
|
@ -59,14 +65,15 @@ impl Row {
|
|||
self.0.get(column_position)
|
||||
}
|
||||
|
||||
pub fn restrict_columns(&self, columns: &Vec<ColumnPosition>) -> Row {
|
||||
pub fn restrict_columns(&self, columns: &Vec<ColumnPosition>) -> RestrictedRow {
|
||||
// If the index from `columns` is non-existant in `row`, it will just ignore it.
|
||||
let mut subrow: Row = Row::new();
|
||||
let mut subrow: Vec<(ColumnPosition, Value)> = vec![];
|
||||
for column_position in columns {
|
||||
if let Some(value) = self.get(*column_position) {
|
||||
subrow.0.push(value.clone())
|
||||
subrow.push((*column_position, value.clone()));
|
||||
}
|
||||
}
|
||||
subrow
|
||||
|
||||
subrow.into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::error::Error;
|
||||
use crate::internals::column_index::ColumnIndex;
|
||||
use crate::internals::row::{ColumnPosition, Row};
|
||||
use crate::restricted_row::RestrictedRow;
|
||||
use crate::schema::{ColumnName, TableSchema, TableName};
|
||||
use crate::result::DbResult;
|
||||
use crate::type_system::{IndexableValue, Uuid, Value};
|
||||
|
|
@ -68,7 +69,7 @@ impl Table {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub fn select_all_rows<'a>(&'a self, selected_column_positions: Vec<ColumnPosition>) -> impl Iterator<Item=Row> + 'a {
|
||||
pub fn select_all_rows<'a>(&'a self, selected_column_positions: Vec<ColumnPosition>) -> impl Iterator<Item=RestrictedRow> + 'a {
|
||||
self.rows
|
||||
.values()
|
||||
.map(move |row| row.restrict_columns(&selected_column_positions))
|
||||
|
|
@ -79,7 +80,7 @@ impl Table {
|
|||
selected_column_positions: Vec<ColumnPosition>,
|
||||
column_position: ColumnPosition,
|
||||
value: Value,
|
||||
) -> DbResult<impl Iterator<Item=Row> + 'a> {
|
||||
) -> DbResult<impl Iterator<Item=RestrictedRow> + 'a> {
|
||||
let restrict_columns_of_row = move |row: Row| row.restrict_columns(&selected_column_positions);
|
||||
match value {
|
||||
Value::Indexable(value) => match self.fetch_ids_from_index(column_position, &value)? {
|
||||
|
|
@ -207,6 +208,7 @@ impl Table {
|
|||
match value {
|
||||
IndexableValue::Uuid(id) => Ok(Some(HashSet::from([*id]))),
|
||||
_ => {
|
||||
// TODO: This validation step is not really necessary.
|
||||
let column_name: ColumnName = self
|
||||
.schema
|
||||
.column_name_from_column_position(column_position)?;
|
||||
|
|
@ -223,8 +225,8 @@ impl Table {
|
|||
match self.indexes.get(&column_position) {
|
||||
Some(index) => {
|
||||
// Note that we are cloning the ids here! This can be very wasteful in some cases.
|
||||
// It would be possible to just return a reference,
|
||||
// but this seems fairly non-trivial.
|
||||
// Theoretically it would be possible to return a reference,
|
||||
// but after attempting to do this it seems very non-trivial.
|
||||
let ids = index.get(value).cloned();
|
||||
Ok(ids)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
use crate::error::Error;
|
||||
use crate::internals::row::{ColumnPosition, Row};
|
||||
use crate::internals::row::ColumnPosition;
|
||||
use crate::schema::{TableName, TableSchema};
|
||||
use crate::internals::table::Table;
|
||||
use crate::operation::{ColumnSelection, Condition, Operation};
|
||||
use crate::operation::{Operation, Condition};
|
||||
use crate::result::DbResult;
|
||||
use crate::type_system::{DbType, IndexableValue, Value};
|
||||
use bimap::BiMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::restricted_row::RestrictedRow;
|
||||
|
||||
// Use `TablePosition` as index
|
||||
pub type Tables = Vec<Table>;
|
||||
|
|
@ -21,18 +20,20 @@ pub struct State {
|
|||
|
||||
// #[derive(Debug)]
|
||||
pub enum Response<'a> {
|
||||
Selected(Box<dyn Iterator<Item=Row> + 'a + Send>),
|
||||
Selected(&'a TableSchema, Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>),
|
||||
Inserted,
|
||||
Deleted(usize), // how many were deleted
|
||||
TableCreated,
|
||||
IndexCreated,
|
||||
}
|
||||
|
||||
pub type DbSchema<'a> = Vec<(TableName, TablePosition, &'a TableSchema)>;
|
||||
|
||||
impl std::fmt::Debug for Response<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
use Response::*;
|
||||
match self {
|
||||
Selected(_rows) =>
|
||||
Selected(_schema, _rows) =>
|
||||
// TODO: How can we iterate through the rows without having to take ownership of
|
||||
// them?
|
||||
f.write_str("Some rows... trust me"),
|
||||
|
|
@ -56,37 +57,21 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
/// TODO: return a reference to avoid allocations
|
||||
pub fn metadata<'a>(&'a self) -> Vec<(String, &'a TableSchema)> {
|
||||
let mut m = Vec::new();
|
||||
for (name, pos) in &self.table_name_position_mapping {
|
||||
let table_schema = self.tables.get(*pos).unwrap().schema();
|
||||
m.push((name.clone(), table_schema));
|
||||
pub fn db_schema<'a>(&'a self) -> DbSchema {
|
||||
let mut schema: DbSchema = Vec::new();
|
||||
for (table_name, &table_position) in &self.table_name_position_mapping {
|
||||
let table_schema = self.tables[table_position].schema();
|
||||
schema.push((table_name.clone(), table_position, table_schema));
|
||||
}
|
||||
m
|
||||
schema
|
||||
}
|
||||
|
||||
fn table_from_name<'a>(&'a self, table_name: &TableName) -> DbResult<&'a Table> {
|
||||
match self.table_name_position_mapping.get_by_left(table_name) {
|
||||
Some(table_position) => {
|
||||
let table = &self.tables[*table_position];
|
||||
Ok(table)
|
||||
}
|
||||
None => Err(Error::TableDoesNotExist(table_name.clone())),
|
||||
}
|
||||
fn table_at<'a>(&'a self, table_position: TablePosition) -> &'a Table {
|
||||
&self.tables[table_position]
|
||||
}
|
||||
|
||||
fn table_from_name_mut<'b: 'a, 'a>(
|
||||
&'b mut self,
|
||||
table_name: &TableName,
|
||||
) -> DbResult<&'a mut Table> {
|
||||
match self.table_name_position_mapping.get_by_left(table_name) {
|
||||
Some(table_position) => {
|
||||
let table = &mut self.tables[*table_position];
|
||||
Ok(table)
|
||||
}
|
||||
None => Err(Error::TableDoesNotExist(table_name.clone())),
|
||||
}
|
||||
fn table_at_mut<'a>(&'a mut self, table_position: TablePosition) -> &'a mut Table {
|
||||
&mut self.tables[table_position]
|
||||
}
|
||||
|
||||
fn attach_table(&mut self, table_name: TableName, table: Table) {
|
||||
|
|
@ -96,56 +81,47 @@ impl State {
|
|||
self.tables.push(table);
|
||||
}
|
||||
|
||||
pub fn interpret<'a>(&'a mut self, operation: Operation) -> DbResult<Response<'a>> {
|
||||
pub fn interpret<'a>(&'a mut self, operation: Operation) -> DbResult<Response<'a>> {
|
||||
// TODO: lock stuff
|
||||
use Operation::*;
|
||||
|
||||
match operation {
|
||||
Select(table_name, column_selection, maybe_condition) => {
|
||||
let table: &Table = self.table_from_name(&table_name)?;
|
||||
Select(table_position, column_selection, maybe_condition) => {
|
||||
let table: &Table = self.table_at(table_position);
|
||||
|
||||
let selected_column_positions: Vec<ColumnPosition> = table
|
||||
.schema()
|
||||
.column_positions_from_column_selection(&column_selection)?;
|
||||
let selected_rows = match maybe_condition {
|
||||
None => {
|
||||
let x = table.select_all_rows(selected_column_positions);
|
||||
Box::new(x) as Box<dyn Iterator<Item=Row> + 'a + Send>
|
||||
let rows = table.select_all_rows(column_selection);
|
||||
Box::new(rows) as Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>
|
||||
},
|
||||
|
||||
Some(Condition::Eq(eq_column_name, value)) => {
|
||||
let eq_column_position = table
|
||||
.schema()
|
||||
.column_position_from_column_name(&eq_column_name)?;
|
||||
Some(Condition::Eq(eq_column, value)) => {
|
||||
let x =
|
||||
table.select_rows_where_eq(
|
||||
selected_column_positions,
|
||||
eq_column_position,
|
||||
column_selection,
|
||||
eq_column,
|
||||
value,
|
||||
)?;
|
||||
Box::new(x) as Box<dyn Iterator<Item=Row> + 'a + Send>
|
||||
Box::new(x) as Box<dyn Iterator<Item=RestrictedRow> + 'a + Send>
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::Selected(selected_rows))
|
||||
}
|
||||
Insert(table_name, values) => {
|
||||
let table: &mut Table = self.table_from_name_mut(&table_name)?;
|
||||
Ok(Response::Selected(table.schema(), selected_rows))
|
||||
},
|
||||
Insert(table_position, values) => {
|
||||
let table: &mut Table = self.table_at_mut(table_position);
|
||||
|
||||
let (id, row) = table.schema().row_from_insertion_values(values)?;
|
||||
table.insert_row_at(id, row)?;
|
||||
Ok(Response::Inserted)
|
||||
}
|
||||
Delete(table_name, maybe_condition) => {
|
||||
let table: &mut Table = self.table_from_name_mut(&table_name)?;
|
||||
Delete(table_position, maybe_condition) => {
|
||||
let table: &mut Table = self.table_at_mut(table_position);
|
||||
|
||||
let rows_affected = match maybe_condition {
|
||||
None => table.delete_all_rows(),
|
||||
Some(Condition::Eq(eq_column_name, value)) => {
|
||||
let eq_column_position = table
|
||||
.schema()
|
||||
.column_position_from_column_name(&eq_column_name)?;
|
||||
table.delete_rows_where_eq(eq_column_position, value)?
|
||||
Some(Condition::Eq(eq_column, value)) => {
|
||||
table.delete_rows_where_eq(eq_column, value)?
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -157,13 +133,9 @@ impl State {
|
|||
|
||||
Ok(Response::TableCreated)
|
||||
}
|
||||
CreateIndex(table_name, column_name) => {
|
||||
let table: &mut Table = self.table_from_name_mut(&table_name)?;
|
||||
let column_position: ColumnPosition = table
|
||||
.schema()
|
||||
.column_position_from_column_name(&column_name)?;
|
||||
|
||||
table.attach_index(column_position)?;
|
||||
CreateIndex(table_position, column) => {
|
||||
let table: &mut Table = self.table_at_mut(table_position);
|
||||
table.attach_index(column)?;
|
||||
Ok(Response::IndexCreated)
|
||||
}
|
||||
}
|
||||
|
|
@ -173,7 +145,10 @@ impl State {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::internals::row::ColumnPosition;
|
||||
use std::collections::HashSet;
|
||||
use crate::type_system::{DbType, IndexableValue, Value};
|
||||
use crate::operation::Operation;
|
||||
|
||||
fn users_schema() -> TableSchema {
|
||||
let id: ColumnPosition = 0;
|
||||
|
|
@ -214,33 +189,22 @@ mod tests {
|
|||
let mut state = State::new();
|
||||
let users_schema = users_schema();
|
||||
let users = users_schema.table_name().clone();
|
||||
let users_position = 0;
|
||||
|
||||
state
|
||||
.interpret(Operation::CreateTable(users.clone(), users_schema))
|
||||
.interpret(Operation::CreateTable(users, users_schema.clone()))
|
||||
.unwrap();
|
||||
let response: Response = state
|
||||
.interpret(Operation::Select(users.clone(), ColumnSelection::All, None))
|
||||
.interpret(Operation::Select(users_position, users_schema.all_selection(), None))
|
||||
.unwrap();
|
||||
assert!(matches!(response, Response::Selected(_)));
|
||||
let Response::Selected(rows) = response else {
|
||||
assert!(matches!(response, Response::Selected(_, _)));
|
||||
let Response::Selected(_schema, rows) = response else {
|
||||
panic!()
|
||||
};
|
||||
let rows: Vec<_> = rows.collect();
|
||||
assert!(rows.len() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_select_nonexistant_table() {
|
||||
let mut state = State::new();
|
||||
|
||||
let response: DbResult<Response> = state.interpret(Operation::Select(
|
||||
"table_that_doesnt_exist".to_string(),
|
||||
ColumnSelection::All,
|
||||
None,
|
||||
));
|
||||
assert!(matches!(response, Err(Error::TableDoesNotExist(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_select_basic1() {
|
||||
use IndexableValue::*;
|
||||
|
|
@ -248,10 +212,11 @@ mod tests {
|
|||
|
||||
let mut state = State::new();
|
||||
let users_schema = users_schema();
|
||||
let users = users_schema.table_name().clone();
|
||||
let users = 0;
|
||||
|
||||
|
||||
state
|
||||
.interpret(Operation::CreateTable(users.clone(), users_schema))
|
||||
.interpret(Operation::CreateTable("users".to_string(), users_schema.clone()))
|
||||
.unwrap();
|
||||
|
||||
let (id, name, age) = (
|
||||
|
|
@ -261,36 +226,35 @@ mod tests {
|
|||
);
|
||||
state
|
||||
.interpret(Operation::Insert(
|
||||
users.clone(),
|
||||
users,
|
||||
vec![
|
||||
("id".to_string(), id.clone()),
|
||||
("name".to_string(), name.clone()),
|
||||
("age".to_string(), age.clone()),
|
||||
id.clone(),
|
||||
name.clone(),
|
||||
age.clone(),
|
||||
],
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let response: Response = state
|
||||
.interpret(Operation::Select(users.clone(), ColumnSelection::All, None))
|
||||
.interpret(Operation::Select(users, users_schema.all_selection(), None))
|
||||
.unwrap();
|
||||
|
||||
assert!(matches!(response, Response::Selected(_)));
|
||||
let Response::Selected(rows) = response else {
|
||||
assert!(matches!(response, Response::Selected(_, _)));
|
||||
let Response::Selected(_schema, rows) = response else {
|
||||
panic!()
|
||||
};
|
||||
let rows: Vec<_> = rows.collect();
|
||||
assert!(rows.len() == 1);
|
||||
let row = &rows[0];
|
||||
|
||||
|
||||
assert!(row.len() == 3);
|
||||
assert!(row[0] == id);
|
||||
assert!(row[1] == name);
|
||||
assert!(row[2] == age);
|
||||
assert!(row[0].1 == id);
|
||||
assert!(row[1].1 == name);
|
||||
assert!(row[2].1 == age);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_select_basic2() {
|
||||
use ColumnSelection::*;
|
||||
use Condition::*;
|
||||
use IndexableValue::*;
|
||||
use Operation::*;
|
||||
|
|
@ -298,10 +262,13 @@ mod tests {
|
|||
|
||||
let mut state = State::new();
|
||||
let users_schema = users_schema();
|
||||
let users = users_schema.table_name().clone();
|
||||
let users_position: TablePosition = 0;
|
||||
|
||||
let id_column: ColumnPosition = 0;
|
||||
let name_column: ColumnPosition = 1;
|
||||
|
||||
state
|
||||
.interpret(CreateTable(users.clone(), users_schema))
|
||||
.interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone()))
|
||||
.unwrap();
|
||||
|
||||
let (id0, name0, age0) = (
|
||||
|
|
@ -311,11 +278,11 @@ mod tests {
|
|||
);
|
||||
state
|
||||
.interpret(Insert(
|
||||
users.clone(),
|
||||
users_position,
|
||||
vec![
|
||||
("id".to_string(), id0.clone()),
|
||||
("name".to_string(), name0.clone()),
|
||||
("age".to_string(), age0.clone()),
|
||||
id0.clone(),
|
||||
name0.clone(),
|
||||
age0.clone(),
|
||||
],
|
||||
))
|
||||
.unwrap();
|
||||
|
|
@ -327,48 +294,49 @@ mod tests {
|
|||
);
|
||||
state
|
||||
.interpret(Insert(
|
||||
users.clone(),
|
||||
users_position,
|
||||
vec![
|
||||
("id".to_string(), id1.clone()),
|
||||
("name".to_string(), name1.clone()),
|
||||
("age".to_string(), age1.clone()),
|
||||
id1.clone(),
|
||||
name1.clone(),
|
||||
age1.clone(),
|
||||
],
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap();
|
||||
let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap();
|
||||
|
||||
assert!(matches!(response, Response::Selected(_)));
|
||||
let Response::Selected(rows) = response else {
|
||||
assert!(matches!(response, Response::Selected(_, _)));
|
||||
let Response::Selected(_, rows) = response else {
|
||||
panic!()
|
||||
};
|
||||
let rows: Vec<_> = rows.collect();
|
||||
|
||||
let rows: Vec<_> = rows.collect();
|
||||
assert!(rows.len() == 2);
|
||||
let row0 = &rows[0];
|
||||
let row1 = &rows[1];
|
||||
|
||||
assert!(row0.len() == 3);
|
||||
assert!(row0[0] == id0);
|
||||
assert!(row0[1] == name0);
|
||||
assert!(row0[2] == age0);
|
||||
assert!(row0[0].1 == id0);
|
||||
assert!(row0[1].1 == name0);
|
||||
assert!(row0[2].1 == age0);
|
||||
|
||||
assert!(row1.len() == 3);
|
||||
assert!(row1[0] == id1);
|
||||
assert!(row1[1] == name1);
|
||||
assert!(row1[2] == age1);
|
||||
assert!(row1[0].1 == id1);
|
||||
assert!(row1[1].1 == name1);
|
||||
assert!(row1[2].1 == age1);
|
||||
}
|
||||
|
||||
{
|
||||
let response: Response = state
|
||||
.interpret(Select(
|
||||
users.clone(),
|
||||
All,
|
||||
Some(Eq("id".to_string(), id0.clone())),
|
||||
users_position,
|
||||
users_schema.all_selection(),
|
||||
Some(Eq(id_column, id0.clone())),
|
||||
))
|
||||
.unwrap();
|
||||
assert!(matches!(response, Response::Selected(_)));
|
||||
let Response::Selected(rows) = response else {
|
||||
assert!(matches!(response, Response::Selected(_, _)));
|
||||
let Response::Selected(_, rows) = response else {
|
||||
panic!()
|
||||
};
|
||||
let rows: Vec<_> = rows.collect();
|
||||
|
|
@ -376,21 +344,21 @@ mod tests {
|
|||
let row0 = &rows[0];
|
||||
|
||||
assert!(row0.len() == 3);
|
||||
assert!(row0[0] == id0);
|
||||
assert!(row0[1] == name0);
|
||||
assert!(row0[2] == age0);
|
||||
assert!(row0[0].1 == id0);
|
||||
assert!(row0[1].1 == name0);
|
||||
assert!(row0[2].1 == age0);
|
||||
}
|
||||
|
||||
{
|
||||
let response: Response = state
|
||||
.interpret(Select(
|
||||
users.clone(),
|
||||
Columns(vec!["name".to_string(), "id".to_string()]),
|
||||
Some(Eq("id".to_string(), id0.clone())),
|
||||
users_position,
|
||||
vec![name_column, id_column],
|
||||
Some(Eq(id_column, id0.clone())),
|
||||
))
|
||||
.unwrap();
|
||||
assert!(matches!(response, Response::Selected(_)));
|
||||
let Response::Selected(rows) = response else {
|
||||
assert!(matches!(response, Response::Selected(_, _)));
|
||||
let Response::Selected(_, rows) = response else {
|
||||
panic!()
|
||||
};
|
||||
let rows: Vec<_> = rows.collect();
|
||||
|
|
@ -398,14 +366,13 @@ mod tests {
|
|||
let row0 = &rows[0];
|
||||
|
||||
assert!(row0.len() == 2);
|
||||
assert!(row0[0] == name0);
|
||||
assert!(row0[1] == id0);
|
||||
assert!(row0[0].1 == name0);
|
||||
assert!(row0[1].1 == id0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete() {
|
||||
use ColumnSelection::*;
|
||||
use Condition::*;
|
||||
use IndexableValue::*;
|
||||
use Operation::*;
|
||||
|
|
@ -413,10 +380,12 @@ mod tests {
|
|||
|
||||
let mut state = State::new();
|
||||
let users_schema = users_schema();
|
||||
let users = users_schema.table_name().clone();
|
||||
let users_position: TablePosition = 0;
|
||||
|
||||
let id_column: ColumnPosition = 0;
|
||||
|
||||
state
|
||||
.interpret(CreateTable(users.clone(), users_schema))
|
||||
.interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone()))
|
||||
.unwrap();
|
||||
|
||||
let (id0, name0, age0) = (
|
||||
|
|
@ -426,11 +395,11 @@ mod tests {
|
|||
);
|
||||
state
|
||||
.interpret(Insert(
|
||||
users.clone(),
|
||||
users_position,
|
||||
vec![
|
||||
("id".to_string(), id0.clone()),
|
||||
("name".to_string(), name0.clone()),
|
||||
("age".to_string(), age0.clone()),
|
||||
id0.clone(),
|
||||
name0.clone(),
|
||||
age0.clone(),
|
||||
],
|
||||
))
|
||||
.unwrap();
|
||||
|
|
@ -442,11 +411,11 @@ mod tests {
|
|||
);
|
||||
state
|
||||
.interpret(Insert(
|
||||
users.clone(),
|
||||
users_position,
|
||||
vec![
|
||||
("id".to_string(), id1.clone()),
|
||||
("name".to_string(), name1.clone()),
|
||||
("age".to_string(), age1.clone()),
|
||||
id1.clone(),
|
||||
name1.clone(),
|
||||
age1.clone(),
|
||||
],
|
||||
))
|
||||
.unwrap();
|
||||
|
|
@ -454,17 +423,17 @@ mod tests {
|
|||
{
|
||||
let delete_response: Response = state
|
||||
.interpret(Delete(
|
||||
users.clone(),
|
||||
Some(Eq("id".to_string(), id0.clone())),
|
||||
users_position,
|
||||
Some(Eq(id_column, id0.clone())),
|
||||
))
|
||||
.unwrap();
|
||||
assert!(matches!(delete_response, Response::Deleted(1)));
|
||||
}
|
||||
|
||||
let response: Response = state.interpret(Select(users.clone(), All, None)).unwrap();
|
||||
let response: Response = state.interpret(Select(users_position, users_schema.all_selection(), None)).unwrap();
|
||||
|
||||
assert!(matches!(response, Response::Selected(_)));
|
||||
let Response::Selected(rows) = response else {
|
||||
assert!(matches!(response, Response::Selected(_, _)));
|
||||
let Response::Selected(_, rows) = response else {
|
||||
panic!()
|
||||
};
|
||||
let rows: Vec<_> = rows.collect();
|
||||
|
|
@ -472,9 +441,9 @@ mod tests {
|
|||
let row = &rows[0];
|
||||
|
||||
assert!(row.len() == 3);
|
||||
assert!(row[0] == id1);
|
||||
assert!(row[1] == name1);
|
||||
assert!(row[2] == age1);
|
||||
assert!(row[0].1 == id1);
|
||||
assert!(row[1].1 == name1);
|
||||
assert!(row[2].1 == age1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -485,14 +454,16 @@ mod tests {
|
|||
|
||||
let mut state = State::new();
|
||||
let users_schema = users_schema();
|
||||
let users = users_schema.table_name().clone();
|
||||
let users_position: TablePosition = 0;
|
||||
|
||||
let name_column: ColumnPosition = 1;
|
||||
|
||||
state
|
||||
.interpret(CreateTable(users.clone(), users_schema))
|
||||
.interpret(CreateTable(users_schema.table_name().clone(), users_schema.clone()))
|
||||
.unwrap();
|
||||
|
||||
state
|
||||
.interpret(CreateIndex(users.clone(), "name".to_string()))
|
||||
.interpret(CreateIndex(users_position, name_column))
|
||||
.unwrap();
|
||||
|
||||
let (id0, name0, age0) = (
|
||||
|
|
@ -502,11 +473,11 @@ mod tests {
|
|||
);
|
||||
state
|
||||
.interpret(Insert(
|
||||
users.clone(),
|
||||
users_position,
|
||||
vec![
|
||||
("id".to_string(), id0.clone()),
|
||||
("name".to_string(), name0.clone()),
|
||||
("age".to_string(), age0.clone()),
|
||||
id0.clone(),
|
||||
name0.clone(),
|
||||
age0.clone(),
|
||||
],
|
||||
))
|
||||
.unwrap();
|
||||
|
|
@ -518,11 +489,11 @@ mod tests {
|
|||
);
|
||||
state
|
||||
.interpret(Insert(
|
||||
users.clone(),
|
||||
users_position,
|
||||
vec![
|
||||
("id".to_string(), id1.clone()),
|
||||
("name".to_string(), name1.clone()),
|
||||
("age".to_string(), age1.clone()),
|
||||
id1.clone(),
|
||||
name1.clone(),
|
||||
age1.clone(),
|
||||
],
|
||||
))
|
||||
.unwrap();
|
||||
|
|
@ -547,33 +518,34 @@ mod tests {
|
|||
}
|
||||
|
||||
pub fn example() {
|
||||
use ColumnSelection::*;
|
||||
use crate::type_system::{IndexableValue, Value, DbType};
|
||||
use Condition::*;
|
||||
use IndexableValue::*;
|
||||
use Operation::*;
|
||||
use Value::*;
|
||||
|
||||
let users_schema: TableSchema = {
|
||||
let id: ColumnPosition = 0;
|
||||
let name: ColumnPosition = 1;
|
||||
let age: ColumnPosition = 2;
|
||||
let id_column: ColumnPosition = 0;
|
||||
let name_column: ColumnPosition = 1;
|
||||
let age_column: ColumnPosition = 2;
|
||||
|
||||
let users_schema: TableSchema = {
|
||||
TableSchema::new(
|
||||
"users".to_string(),
|
||||
id,
|
||||
id_column,
|
||||
vec!(
|
||||
("id".to_string(), id),
|
||||
("name".to_string(), name),
|
||||
("age".to_string(), age),
|
||||
("id".to_string(), id_column),
|
||||
("name".to_string(), name_column),
|
||||
("age".to_string(), age_column),
|
||||
),
|
||||
vec![DbType::Uuid, DbType::String, DbType::Int],
|
||||
)
|
||||
};
|
||||
let users_position: TablePosition = 0;
|
||||
let users = users_schema.table_name().clone();
|
||||
|
||||
let mut state = State::new();
|
||||
state
|
||||
.interpret(Operation::CreateTable(users.clone(), users_schema))
|
||||
.interpret(Operation::CreateTable(users, users_schema.clone()))
|
||||
.unwrap();
|
||||
|
||||
let (id0, name0, age0) = (
|
||||
|
|
@ -584,11 +556,11 @@ pub fn example() {
|
|||
println!("==INSERT Plato==");
|
||||
state
|
||||
.interpret(Insert(
|
||||
users.clone(),
|
||||
users_position,
|
||||
vec![
|
||||
("id".to_string(), id0.clone()),
|
||||
("name".to_string(), name0.clone()),
|
||||
("age".to_string(), age0.clone()),
|
||||
id0.clone(),
|
||||
name0.clone(),
|
||||
age0.clone(),
|
||||
],
|
||||
))
|
||||
.unwrap();
|
||||
|
|
@ -601,11 +573,11 @@ pub fn example() {
|
|||
println!("==INSERT Aristotle==");
|
||||
state
|
||||
.interpret(Insert(
|
||||
users.clone(),
|
||||
users_position,
|
||||
vec![
|
||||
("id".to_string(), id1.clone()),
|
||||
("name".to_string(), name1.clone()),
|
||||
("age".to_string(), age1.clone()),
|
||||
id1.clone(),
|
||||
name1.clone(),
|
||||
age1.clone(),
|
||||
],
|
||||
))
|
||||
.unwrap();
|
||||
|
|
@ -613,7 +585,7 @@ pub fn example() {
|
|||
|
||||
{
|
||||
let response: Response = state
|
||||
.interpret(Operation::Select(users.clone(), ColumnSelection::All, None))
|
||||
.interpret(Operation::Select(users_position, users_schema.all_selection(), None))
|
||||
.unwrap();
|
||||
println!("==SELECT ALL==");
|
||||
println!("{:?}", response);
|
||||
|
|
@ -622,9 +594,9 @@ pub fn example() {
|
|||
{
|
||||
let response: Response = state
|
||||
.interpret(Select(
|
||||
users.clone(),
|
||||
All,
|
||||
Some(Eq("id".to_string(), id0.clone())),
|
||||
users_position,
|
||||
users_schema.all_selection(),
|
||||
Some(Eq(id_column, id0.clone())),
|
||||
))
|
||||
.unwrap();
|
||||
println!("==SELECT Plato==");
|
||||
|
|
@ -638,16 +610,16 @@ pub fn example() {
|
|||
// "infer" them?
|
||||
let _delete_response: Response = state
|
||||
.interpret(Delete(
|
||||
users.clone(),
|
||||
Some(Eq("id".to_string(), id0.clone())),
|
||||
users_position,
|
||||
Some(Eq(id_column, id0.clone())),
|
||||
))
|
||||
.unwrap();
|
||||
println!("==DELETE Plato==");
|
||||
}
|
||||
let response: Response = state
|
||||
.interpret(Select(
|
||||
users.clone(),
|
||||
Columns(vec!["name".to_string(), "id".to_string()]),
|
||||
users_position,
|
||||
vec![name_column, id_column],
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ pub mod type_system;
|
|||
mod error;
|
||||
mod internals;
|
||||
mod result;
|
||||
pub mod restricted_row;
|
||||
|
|
|
|||
|
|
@ -1,40 +1,23 @@
|
|||
use crate::schema::{ColumnName, TableName, TableSchema};
|
||||
use crate::schema::{TableName, TableSchema};
|
||||
use crate::type_system::Value;
|
||||
use crate::internals::row::ColumnPosition;
|
||||
use crate::interpreter::TablePosition;
|
||||
|
||||
// ==============SQL operations================
|
||||
// TODO: Note that every operation has a table name.
|
||||
// Perhaps consider factoring the table name out
|
||||
// and think of the operations as operating on a unique table.
|
||||
// TODO: `TableName` should be replaced by `TablePosition`
|
||||
// Validated operation. Constructed by validation crate.
|
||||
#[derive(Debug)]
|
||||
pub enum Operation {
|
||||
Select(TableName, ColumnSelection, Option<Condition>),
|
||||
Insert(TableName, InsertionValues),
|
||||
Delete(TableName, Option<Condition>),
|
||||
// Update(...),
|
||||
Select(TablePosition, ColumnSelection, Option<Condition>),
|
||||
Insert(TablePosition, InsertionValues),
|
||||
Delete(TablePosition, Option<Condition>),
|
||||
CreateTable(TableName, TableSchema),
|
||||
CreateIndex(TableName, ColumnName),
|
||||
// DropTable(TableName),
|
||||
CreateIndex(TablePosition, ColumnPosition),
|
||||
}
|
||||
|
||||
pub type InsertionValues = Vec<(ColumnName, Value)>;
|
||||
pub type InsertionValues = Vec<Value>;
|
||||
|
||||
pub enum ColumnSelection {
|
||||
All,
|
||||
Columns(Vec<ColumnName>),
|
||||
}
|
||||
pub type ColumnSelection = Vec<ColumnPosition>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Condition {
|
||||
// And(Box<Condition>, Box<Condition>),
|
||||
// Or(Box<Condition>, Box<Condition>),
|
||||
// Not(Box<Condition>),
|
||||
Eq(ColumnName, Value),
|
||||
// LessOrEqual(ColumnName, DbValue),
|
||||
// Less(ColumnName, DbValue),
|
||||
|
||||
// StringCondition(StringCondition),
|
||||
Eq(ColumnPosition, Value),
|
||||
}
|
||||
|
||||
// enum StringCondition {
|
||||
// Prefix(ColumnName, String),
|
||||
// Substring(ColumnName, String),
|
||||
// }
|
||||
|
|
|
|||
35
minisql/src/restricted_row.rs
Normal file
35
minisql/src/restricted_row.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use std::ops::Index;
|
||||
use std::slice::SliceIndex;
|
||||
use crate::internals::row::ColumnPosition;
|
||||
use crate::type_system::Value;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RestrictedRow(Vec<(ColumnPosition, Value)>);
|
||||
|
||||
impl<Idx> Index<Idx> for RestrictedRow
|
||||
where
|
||||
Idx: SliceIndex<[(ColumnPosition, Value)]>,
|
||||
{
|
||||
type Output = Idx::Output;
|
||||
|
||||
fn index(&self, index: Idx) -> &Self::Output {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<(ColumnPosition, Value)>> for RestrictedRow {
|
||||
fn from(v: Vec<(ColumnPosition, Value)>) -> Self {
|
||||
RestrictedRow(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl RestrictedRow {
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item=&(ColumnPosition, Value)> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
use crate::error::Error;
|
||||
use crate::internals::row::{ColumnPosition, Row};
|
||||
use crate::operation::{ColumnSelection, InsertionValues};
|
||||
use crate::operation::{InsertionValues, ColumnSelection};
|
||||
use crate::result::DbResult;
|
||||
use crate::type_system::{DbType, IndexableValue, Uuid, Value};
|
||||
use bimap::BiMap;
|
||||
use std::collections::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Note that it is nice to split metadata from the data because
|
||||
// then you can give the metadata to the parser without giving it the data.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TableSchema {
|
||||
table_name: TableName, // used for descriptive errors
|
||||
primary_key: ColumnPosition,
|
||||
|
|
@ -49,53 +48,26 @@ impl TableSchema {
|
|||
self.column_name_position_mapping.get_by_left(column_name).copied()
|
||||
}
|
||||
|
||||
pub fn all_selection(&self) -> ColumnSelection {
|
||||
let mut selection: ColumnSelection = self.column_name_position_mapping.iter().map(|(_, column)| *column).collect();
|
||||
selection.sort();
|
||||
selection
|
||||
}
|
||||
|
||||
pub fn get_column(&self, column_name: &ColumnName) -> Option<(ColumnPosition, DbType)> {
|
||||
let column = self.get_column_position(column_name)?;
|
||||
Some((column, self.column_type(column)))
|
||||
}
|
||||
|
||||
pub fn get_type_at(&self, column_name: &ColumnName) -> Option<DbType> {
|
||||
let position = self.get_column_position(column_name)?;
|
||||
self.types.get(position).copied()
|
||||
}
|
||||
|
||||
// TODO: Get rid of this after validation is merged
|
||||
fn get_column(&self, column_name: &ColumnName) -> DbResult<(DbType, ColumnPosition)> {
|
||||
match self.column_name_position_mapping.get_by_left(column_name) {
|
||||
Some(column_position) => match self.types.get(*column_position) {
|
||||
Some(type_) => Ok((*type_, *column_position)),
|
||||
None => Err(Error::MissingTypeAnnotationOfColumn(
|
||||
self.table_name.clone(),
|
||||
*column_position,
|
||||
)),
|
||||
},
|
||||
None => Err(Error::ColumnDoesNotExist(
|
||||
self.table_name.clone(),
|
||||
column_name.clone(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Get rid of this after validation is merged
|
||||
pub fn column_position_from_column_name(
|
||||
&self,
|
||||
column_name: &ColumnName,
|
||||
) -> DbResult<ColumnPosition> {
|
||||
self.get_column(column_name)
|
||||
.map(|(_, column_position)| column_position)
|
||||
}
|
||||
|
||||
pub fn is_primary(&self, column_position: ColumnPosition) -> bool {
|
||||
self.primary_key == column_position
|
||||
}
|
||||
|
||||
fn column_positions_from_column_names(
|
||||
&self,
|
||||
column_names: &[ColumnName],
|
||||
) -> DbResult<Vec<ColumnPosition>> {
|
||||
let mut positions: Vec<ColumnPosition> = Vec::with_capacity(column_names.len());
|
||||
for column_name in column_names {
|
||||
let column_position = self.column_position_from_column_name(column_name)?;
|
||||
positions.push(column_position)
|
||||
}
|
||||
Ok(positions)
|
||||
}
|
||||
|
||||
pub fn column_name_from_column_position(
|
||||
&self,
|
||||
column_position: ColumnPosition,
|
||||
|
|
@ -112,27 +84,6 @@ impl TableSchema {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn column_positions_from_column_selection(
|
||||
&self,
|
||||
column_selection: &ColumnSelection,
|
||||
) -> DbResult<Vec<ColumnPosition>> {
|
||||
match column_selection {
|
||||
ColumnSelection::All => {
|
||||
let mut column_positions: Vec<ColumnPosition> = self
|
||||
.column_name_position_mapping
|
||||
.iter()
|
||||
.map(|(_, column_position)| *column_position)
|
||||
.collect();
|
||||
column_positions.sort();
|
||||
Ok(column_positions)
|
||||
}
|
||||
|
||||
ColumnSelection::Columns(column_names) => {
|
||||
self.column_positions_from_column_names(column_names)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn number_of_columns(&self) -> usize {
|
||||
self.column_name_position_mapping.len()
|
||||
}
|
||||
|
|
@ -141,44 +92,12 @@ impl TableSchema {
|
|||
&self,
|
||||
insertion_values: InsertionValues,
|
||||
) -> DbResult<(Uuid, Row)> {
|
||||
// TODO: There should be proper validation of the insertion_values.
|
||||
// And it shouldn't really be done here.
|
||||
//
|
||||
// In the below we don't check for duplicate column names
|
||||
//
|
||||
let number_of_columns = self.number_of_columns();
|
||||
if number_of_columns != insertion_values.len() {
|
||||
return Err(Error::MismatchBetweenInsertValuesAndColumns(
|
||||
self.table_name.clone(),
|
||||
insertion_values,
|
||||
));
|
||||
}
|
||||
|
||||
let mut row: Row = Row::with_number_of_columns(number_of_columns);
|
||||
|
||||
let mut values: HashMap<ColumnName, Value> = HashMap::new();
|
||||
for (column_name, db_value) in &insertion_values {
|
||||
values.insert(column_name.clone(), db_value.clone());
|
||||
}
|
||||
|
||||
for column_position in 0..number_of_columns {
|
||||
let column_name: ColumnName = self.column_name_from_column_position(column_position)?;
|
||||
match values.get(&column_name) {
|
||||
Some(db_value) => row.push(db_value.clone()),
|
||||
None => {
|
||||
return Err(Error::MissingColumnInInsertValues(
|
||||
self.table_name.clone(),
|
||||
column_name,
|
||||
insertion_values,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
let row: Row = Row::new_from_insertion_values(insertion_values);
|
||||
|
||||
let id: Uuid = match row.get(self.primary_key) {
|
||||
Some(Value::Indexable(IndexableValue::Uuid(id))) => *id,
|
||||
Some(_) => unreachable!(),
|
||||
None => unreachable!(),
|
||||
Some(_) => unreachable!(), // SAFETY: Should be guaranteed by validation
|
||||
None => unreachable!(), // SAFETY: Should be guaranteed by validation
|
||||
};
|
||||
|
||||
Ok((id, row))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use crate::error::TypeConversionError;
|
||||
|
||||
// ==============Types================
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
|
@ -41,4 +42,156 @@ impl Value {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_oid(&self) -> i32 {
|
||||
match self {
|
||||
Self::Number(_) => 701,
|
||||
Self::Indexable(val) => match val {
|
||||
IndexableValue::String(_) => 25,
|
||||
IndexableValue::Int(_) => 23,
|
||||
IndexableValue::Uuid(_) => 2950,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_size(&self) -> i16 {
|
||||
match self {
|
||||
Self::Number(_) => 8,
|
||||
Self::Indexable(val) => match val {
|
||||
IndexableValue::String(_) => -2, // null terminated string
|
||||
IndexableValue::Int(_) => 8,
|
||||
IndexableValue::Uuid(_) => 16,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_text_bytes(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Self::Number(n) => format!("{n}").into_bytes(),
|
||||
Self::Indexable(i) => match i {
|
||||
IndexableValue::String(s) => format!("{s}\0").into_bytes(),
|
||||
IndexableValue::Int(i) => format!("{i}").into_bytes(),
|
||||
IndexableValue::Uuid(u) => format!("{u}").into_bytes(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_text_bytes(bytes: &[u8], type_oid: i32, type_size: i16) -> Result<Value, TypeConversionError> {
|
||||
match (type_oid, type_size) {
|
||||
(701, 8) => {
|
||||
let s = std::str::from_utf8(bytes)?;
|
||||
let n = s.parse::<f64>()?;
|
||||
Ok(Value::Number(n))
|
||||
}
|
||||
(25, -2) => {
|
||||
let s = std::str::from_utf8(bytes)?;
|
||||
let s = &s[..s.len() - 1]; // remove null terminator
|
||||
Ok(Value::Indexable(IndexableValue::String(s.to_string())))
|
||||
}
|
||||
(23, 8) => {
|
||||
let s = std::str::from_utf8(bytes)?;
|
||||
let n = s.parse::<u64>()?;
|
||||
Ok(Value::Indexable(IndexableValue::Int(n)))
|
||||
}
|
||||
(2950, 16) => {
|
||||
let s = std::str::from_utf8(bytes)?;
|
||||
let n = s.parse::<Uuid>()?;
|
||||
Ok(Value::Indexable(IndexableValue::Uuid(n)))
|
||||
}
|
||||
(oid, size) => Err(TypeConversionError::UnknownType { oid, size }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::error::TypeConversionError::UnknownType;
|
||||
use super::{Value, IndexableValue};
|
||||
|
||||
#[test]
|
||||
fn test_encode_number() {
|
||||
let value = Value::Number(123.456);
|
||||
let oid = value.type_oid();
|
||||
let size = value.type_size();
|
||||
|
||||
let bytes = value.as_text_bytes();
|
||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||
|
||||
assert_eq!(value, from_bytes);
|
||||
|
||||
assert_eq!(oid, 701);
|
||||
assert_eq!(size, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_string() {
|
||||
let value = Value::Indexable(IndexableValue::String("hello".to_string()));
|
||||
let oid = value.type_oid();
|
||||
let size = value.type_size();
|
||||
|
||||
let bytes = value.as_text_bytes();
|
||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||
|
||||
assert_eq!(value, from_bytes);
|
||||
|
||||
assert_eq!(oid, 25);
|
||||
assert_eq!(size, -2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_string_utf8() {
|
||||
let value = Value::Indexable(IndexableValue::String("#速度与激情9 早上好中国 现在我有冰激淋 我很喜欢冰激淋 但是《速度与激情9》比冰激淋 🍧🍦🍨".to_string()));
|
||||
let oid = value.type_oid();
|
||||
let size = value.type_size();
|
||||
|
||||
let bytes = value.as_text_bytes();
|
||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||
|
||||
assert_eq!(value, from_bytes);
|
||||
|
||||
assert_eq!(oid, 25);
|
||||
assert_eq!(size, -2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_int() {
|
||||
let value = Value::Indexable(IndexableValue::Int(123));
|
||||
let oid = value.type_oid();
|
||||
let size = value.type_size();
|
||||
|
||||
let bytes = value.as_text_bytes();
|
||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||
|
||||
assert_eq!(value, from_bytes);
|
||||
|
||||
assert_eq!(oid, 23);
|
||||
assert_eq!(size, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_uuid() {
|
||||
let value = Value::Indexable(IndexableValue::Uuid(123));
|
||||
let oid = value.type_oid();
|
||||
let size = value.type_size();
|
||||
|
||||
let bytes = value.as_text_bytes();
|
||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size).unwrap();
|
||||
|
||||
assert_eq!(value, from_bytes);
|
||||
|
||||
assert_eq!(oid, 2950);
|
||||
assert_eq!(size, 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mismatched_size() {
|
||||
let value = Value::Indexable(IndexableValue::Uuid(123));
|
||||
let oid = value.type_oid();
|
||||
let size = 8;
|
||||
|
||||
let bytes = value.as_text_bytes();
|
||||
let from_bytes = Value::from_text_bytes(&bytes, oid, size);
|
||||
|
||||
assert!(matches!(from_bytes, Err(UnknownType { oid: 2950, size: 8 })))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue