diff --git a/src/sqlsyntax/ast.rs b/src/sqlsyntax/ast.rs index 97d48e4..12b859f 100644 --- a/src/sqlsyntax/ast.rs +++ b/src/sqlsyntax/ast.rs @@ -136,7 +136,7 @@ pub struct CreateTableColumnConstraint { pub constraint: CreateTableColumnConstraintType } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum CreateTableColumnConstraintType { PrimaryKey, Unique, diff --git a/src/tempdb/mod.rs b/src/tempdb/mod.rs index 8f37248..8a739d6 100644 --- a/src/tempdb/mod.rs +++ b/src/tempdb/mod.rs @@ -6,7 +6,7 @@ use std::borrow::Cow; use std::collections::BTreeSet; -use columnvalueops::ColumnValueOps; +use columnvalueops::{ColumnValueOps, ColumnValueOpsExt}; use databaseinfo::{DatabaseInfo, TableInfo, ColumnInfo}; use databasestorage::{Group, DatabaseStorage}; use identifier::Identifier; @@ -84,21 +84,33 @@ impl<'a> Group for ScanGroup<'a> { let mut key_offset = 8; let v: Vec = columns.iter().map(|column| { - let size = match column.dbtype.get_fixed_length() { - Some(l) => l as usize, - None => { - let l = variable_lengths[variable_length_offset]; - variable_length_offset += 1; - l as usize - } + let is_null = if column.nullable { + let flag = raw_key[key_offset]; + key_offset += 1; + flag != 0 + } else { + false }; - let bytes = &raw_key[key_offset..key_offset + size]; + if is_null { + ColumnValueOpsExt::null() + } else { + let size = match column.dbtype.get_fixed_length() { + Some(l) => l as usize, + None => { + let l = variable_lengths[variable_length_offset]; + variable_length_offset += 1; + l as usize + } + }; - trace!("from bytes: {:?}, {:?}", column.dbtype, bytes); - let value = ColumnValueOps::from_bytes(column.dbtype, bytes.into_cow()).unwrap(); - key_offset += size; - value + let bytes = &raw_key[key_offset..key_offset + size]; + + trace!("from bytes: {:?}, {:?}", column.dbtype, bytes); + let value = ColumnValueOps::from_bytes(column.dbtype, bytes.into_cow()).unwrap(); + key_offset += size; + value + } }).collect(); v.into_cow() @@ -160,10 +172,15 @@ impl TempDb { let dbtype = try!(DbType::from_identifier(&type_name, type_array_size).ok_or(format!("{} is not a valid column type", type_name))); + let nullable = column.constraints.iter().any(|c| { + c.constraint == ast::CreateTableColumnConstraintType::Nullable + }); + Ok(table::Column { offset: i as u32, name: name, - dbtype: dbtype + dbtype: dbtype, + nullable: nullable }) }).collect(); @@ -186,8 +203,8 @@ impl TempDb { let mut table = try!(self.get_table_mut(stmt.table)); - let column_types: Vec = table.get_columns().iter().map(|c| { - c.dbtype + let column_types: Vec<(DbType, bool)> = table.get_columns().iter().map(|c| { + (c.dbtype, c.nullable) }).collect(); let ast_index_to_column_index: Vec = match stmt.into_columns { @@ -220,7 +237,7 @@ impl TempDb { return Err(format!("INSERT value contains wrong amount of columns")); } - let iter = column_types.iter().enumerate().map(|(i, &dbtype)| { + let iter = column_types.iter().enumerate().map(|(i, &(dbtype, nullable))| { let ast_index = column_index_to_ast_index.get(&i); match ast_index { @@ -228,12 +245,13 @@ impl TempDb { // TODO - allocate buffer outside of loop let mut buf = Vec::new(); let expr = &row[*ast_index]; - ast_expression_to_data(expr, dbtype, &mut buf); - Cow::Owned(buf) + let is_null = ast_expression_to_data(expr, dbtype, nullable, &mut buf); + (Cow::Owned(buf), is_null) }, None => { // use default value for column type - dbtype.get_default() + let is_null = if nullable { Some(true) } else { None }; + (dbtype.get_default(), is_null) } } }); @@ -313,11 +331,14 @@ impl TempDb { } } -fn ast_expression_to_data(expr: &ast::Expression, column_type: DbType, buf: &mut Vec) { +fn ast_expression_to_data(expr: &ast::Expression, column_type: DbType, nullable: bool, buf: &mut Vec) -> Option { use sqlsyntax::ast::Expression::*; use std::borrow::IntoCow; let value: Variant = match expr { + &Null => { + ColumnValueOpsExt::null() + }, &StringLiteral(ref s) => { let r: &str = &s; ColumnValueOps::from_string_literal(r.into_cow()).unwrap() @@ -329,6 +350,17 @@ fn ast_expression_to_data(expr: &ast::Expression, column_type: DbType, buf: &mut _ => unimplemented!() }; - let bytes = value.to_bytes(column_type).unwrap(); - buf.push_all(&bytes); + match (value.is_null(), nullable) { + (true, true) => Some(true), + (true, false) => { + // TODO: return error + unimplemented!() + }, + (false, nullable) => { + let bytes = value.to_bytes(column_type).unwrap(); + buf.push_all(&bytes); + + if nullable { Some(false) } else { None } + } + } } diff --git a/src/tempdb/table.rs b/src/tempdb/table.rs index fb1d0b3..2b20afd 100644 --- a/src/tempdb/table.rs +++ b/src/tempdb/table.rs @@ -36,6 +36,7 @@ pub struct Column { pub offset: u32, pub name: Identifier, pub dbtype: DbType, + pub nullable: bool } impl TableInfo for Table { @@ -63,7 +64,7 @@ impl TableInfo for Table { impl Table { /// rowid is automatically added, and is not included as a specified column pub fn insert_row<'a, I>(&mut self, column_data: I) -> Result<(), UpdateError> - where I: ExactSizeIterator, I: Iterator> + where I: ExactSizeIterator, I: Iterator, Option)> { // TODO - remove Cow in favor of yielding either `u8` slices or `u8` iterators @@ -80,25 +81,50 @@ impl Table { trace!("columns: {:?}", self.columns); - for (column, data_cow) in self.columns.iter().zip(column_data) { + for (column, (data_cow, is_null)) in self.columns.iter().zip(column_data) { let data: &[u8] = &*data_cow; trace!("column data for {}: {:?}", column.name, data); let len = data.len() as u64; - if column.dbtype.is_valid_length(len) { - if column.dbtype.is_variable_length() { - let mut buf = [0; 8]; - byteutils::write_udbinteger(len, &mut buf); - lengths.push_all(&buf); - } + let append_data = match is_null { + Some(true) => { + assert_eq!(len, 0); + key.push(1); - key.push_all(data); - } else { - return Err(UpdateError::ValidationError { - column_name: column.name.clone() - }); + false + }, + Some(false) => { + key.push(0); + + true + }, + None => true + }; + + if append_data { + if column.dbtype.is_valid_length(len) { + if column.dbtype.is_variable_length() { + let mut buf = [0; 8]; + byteutils::write_udbinteger(len, &mut buf); + lengths.push_all(&buf); + } + + assert_eq!(column.nullable, is_null.is_some()); + + if let Some(is_null) = is_null { + if is_null { + } + + } + + key.push_all(data); + } else { + return Err(UpdateError::ValidationError { + column_name: column.name.clone() + }); + } } }