Skip to content

Commit

Permalink
Insert fixes (#2)
Browse files Browse the repository at this point in the history
* Not working

* Working, resolved #197

* Remove faulty test

* Working, resolved #190

* Test and resolved #189

* Auto increment columns (#3)

* Remade for v0.5

* Clippy

* Appease no defaults clippy

* Clean up generate

* Remove sled transaction and use ColumnOptionExt for AutoIncrement column option check

* Clip

* Improvements

* Clippy

* No Mut change

* Minimize work in storage

* Split get and set

* Clip

* Move row mutation to end, make procedures occur in two segments.

* Clean up a little, Clip

* Better testing

* Stuff for clippy reasons

* Revert "Stuff for clippy reasons"

This reverts commit 99956a6.

* Ignore clippy stuff for now, should be fixed in different PR

* Use a transaction

* Clippy and remove acceptance of AUTOINCREMENT

* Api select json (#1)

* Working

* Move more into optional feature

* Remove test comment

* Revert "Merge branch 'INSERT_fixes' of https://github.com/KyGost/gluesql into INSERT_fixes"

This reverts commit 4f1b1da, reversing
changes made to 328858d.
  • Loading branch information
KyGost committed Apr 4, 2021
1 parent 9c0fb6d commit f8d0746
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 107 deletions.
2 changes: 1 addition & 1 deletion src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub mod value;

pub use {
literal::{Literal, LiteralError},
row::{Row, RowError},
row::{bulk_build_rows_expr, bulk_build_rows_row, Row, RowError},
schema::Schema,
table::{get_name, Table, TableError},
value::{Value, ValueError},
Expand Down
214 changes: 156 additions & 58 deletions src/data/row.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use {
crate::{
data::{schema::ColumnDefExt, Value},
data::{schema::ColumnDefExt, value::TryFromLiteral, Value},
evaluate,
result::Result,
store::Store,
},
serde::{Deserialize, Serialize},
sqlparser::ast::{ColumnDef, Expr, Ident},
sqlparser::ast::{ColumnDef, DataType, Expr, Ident},
std::fmt::Debug,
thiserror::Error,
};
Expand All @@ -17,14 +19,14 @@ pub enum RowError {
#[error("lack of required column: {0}")]
LackOfRequiredColumn(String),

#[error("literals does not fit to columns")]
LackOfRequiredValue(String),

#[error("literals have more values than target columns")]
TooManyValues,
#[error("columns and values must match")]
WrongNumberOfValues,

#[error("conflict! row cannot be empty")]
ConflictOnEmptyRow,

#[error("unreachable")]
Unreachable,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
Expand All @@ -42,69 +44,165 @@ impl Row {
.ok_or_else(|| RowError::ConflictOnEmptyRow.into())
}

pub fn new(column_defs: &[ColumnDef], columns: &[Ident], values: &[Expr]) -> Result<Self> {
if values.len() > column_defs.len() {
return Err(RowError::TooManyValues.into());
}
pub async fn new<T: 'static + Debug>(
storage: &dyn Store<T>,
column_defs: &[ColumnDef],
columns: &[Ident],
expressions: &[Expr],
) -> Result<Self> {
Ok(bulk_build_rows_expr(
storage,
column_defs,
columns,
vec![expressions.to_vec()],
false,
false,
)
.await?
.into_iter()
.next()
.unwrap())
}
}

async fn evaluate_expression<T: 'static + Debug>(
storage: &dyn Store<T>,
expression: &Expr,
data_type: &DataType,
) -> Result<Value> {
Value::try_from_evaluated(
data_type,
evaluate(storage, None, None, expression, false).await?,
)
}

column_defs
.iter()
.enumerate()
.map(|(i, column_def)| {
// For macro TODO: find a better way
async fn process_value<T: 'static + Debug>(
_a: &dyn Store<T>,
value: Value,
_b: &DataType,
) -> Result<Value> {
Ok(value)
}

async fn process_expr<T: 'static + Debug>(
storage: &dyn Store<T>,
expression: Expr,
data_type: &DataType,
) -> Result<Value> {
evaluate_expression(storage, &expression, data_type).await
}

macro_rules! bulk_build_rows {
($type: ty, $name: ident, $processing_function: ident) => {
pub async fn $name<T: 'static + Debug>(
storage: &dyn Store<T>,
column_defs: &[ColumnDef],
columns: &[Ident],
rows: Vec<Vec<$type>>,
do_default: bool,
do_validate: bool,
) -> Result<Vec<Row>> {
// FAIL: No mut
let table_columns_count = column_defs.len();
let selection_columns_count = if columns.len() == 0 {
table_columns_count
} else {
columns.len()
};

if rows.iter().any(|row| row.len() != selection_columns_count) {
return Err(RowError::WrongNumberOfValues.into());
} // KG: I don't like this

let mut output_values: Vec<Vec<Value>> = Vec::new();
output_values.resize(rows.len(), Vec::new());
for row in &mut output_values {
row.resize(table_columns_count, Value::Null);
}
// This feels slow, how can we initialise better?
let column_defs_indexed = column_defs.into_iter().enumerate();
for (column_index, column_def) in column_defs_indexed {
let ColumnDef {
name, data_type, ..
} = column_def;
let name = name.to_string();

let i = match columns.len() {
0 => Some(i),
_ => columns.iter().position(|target| target.value == name),
let name = name.to_string();
let values_index = if columns.len() == 0 {
Some(column_index)
} else {
columns.iter().position(|target| target.value == name)
};

let expr = i.map(|i| values.get(i));

let nullable = column_def.is_nullable();
#[cfg(feature = "auto-increment")]
if matches!(expr, None | Some(Some(Expr::Value(Literal::Null))))
&& column_def.is_auto_incremented()
{
return Ok(Value::Null);
}
let nullable = nullable || column_def.is_auto_incremented();

let default = column_def.get_default();
let expr = match (expr, default) {
(Some(expr), _) => {
expr.ok_or_else(|| RowError::LackOfRequiredValue(name.clone()))
let failure_value = if do_default && default.is_some() {
if let Some(default) = default {
FailureValue::Default(default) // We can't execute this here because some defaults might be functions and some functions might not always have the same result.
} else {
return Err(RowError::Unreachable.into());
}
(None, Some(expr)) => Ok(expr),
(None, _) => Err(RowError::LackOfRequiredColumn(name.clone())),
}?;

let nullable = column_def.is_nullable();

Value::from_expr(&data_type, nullable, expr)
})
.collect::<Result<_>>()
.map(Self)
}

pub fn validate(&self, column_defs: &[ColumnDef]) -> Result<()> {
let items = column_defs
.iter()
.enumerate()
.filter_map(|(index, column_def)| {
let value = self.get_value(index);

value.map(|v| (v, column_def))
});

for (value, column_def) in items {
let ColumnDef { data_type, .. } = column_def;
let nullable = column_def.is_nullable();
} else if nullable {
FailureValue::Null
} else {
FailureValue::Throw
};

value.validate_type(data_type)?;
value.validate_null(nullable)?;
for (row_index, row) in rows.iter().enumerate() {
output_values[row_index][column_index] = if let Some(index) = values_index {
let found = row[index].clone();
let value = $processing_function(storage, found, data_type).await?;

if do_validate {
value.validate_null(nullable)?;
value.validate_type(data_type)?;
}
value
} else {
match failure_value {
FailureValue::Throw => {
return Err(RowError::LackOfRequiredColumn(name.clone()).into());
}
FailureValue::Null => Value::Null,
FailureValue::Default(expression) => {
evaluate_expression(storage, expression, data_type).await?
}
}
}
}
}
Ok(output_values.into_iter().map(Row).collect())
}
};
}

Ok(())
}
bulk_build_rows!(Expr, bulk_build_rows_expr, process_expr);
bulk_build_rows!(Value, bulk_build_rows_value, process_value);

pub async fn bulk_build_rows_row<T: 'static + Debug>(
storage: &dyn Store<T>,
column_defs: &[ColumnDef],
columns: &[Ident],
rows: Vec<Row>,
do_default: bool,
do_validate: bool,
) -> Result<Vec<Row>> {
bulk_build_rows_value(
storage,
column_defs,
columns,
rows.into_iter().map(|row| row.0).collect(),
do_default,
do_validate,
)
.await
}

enum FailureValue<'a> {
Default(&'a Expr),
Null,
Throw,
}
19 changes: 19 additions & 0 deletions src/data/value/evaluated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use {
crate::{
data::Value,
executor::Evaluated,
result::{Error, Result},
},
std::convert::TryFrom,
};

impl TryFrom<Evaluated<'_>> for Value {
type Error = Error;

fn try_from(evaluated: Evaluated) -> Result<Self> {
match evaluated {
Evaluated::Literal(literal) => Value::try_from(literal),
Evaluated::Value(value) => Ok(value),
}
}
}
10 changes: 10 additions & 0 deletions src/data/value/literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use {
crate::{
data::Literal,
result::{Error, Result},
Evaluated,
},
sqlparser::ast::DataType,
std::{cmp::Ordering, convert::TryFrom},
Expand Down Expand Up @@ -88,12 +89,21 @@ impl TryFrom<Literal<'_>> for Value {
}

pub trait TryFromLiteral {
fn try_from_evaluated(data_type: &DataType, evaluated: Evaluated<'_>) -> Result<Value>;

fn try_from_literal(data_type: &DataType, literal: &Literal<'_>) -> Result<Value>;

fn try_cast_from_literal(data_type: &DataType, literal: &Literal<'_>) -> Result<Value>;
}

impl TryFromLiteral for Value {
fn try_from_evaluated(data_type: &DataType, evaluated: Evaluated<'_>) -> Result<Value> {
match evaluated {
Evaluated::Literal(value) => Value::try_from_literal(data_type, &value),
Evaluated::Value(value) => Ok(value.into_owned()),
}
}

fn try_from_literal(data_type: &DataType, literal: &Literal<'_>) -> Result<Value> {
match (data_type, literal) {
(DataType::Boolean, Literal::Boolean(v)) => Ok(Value::Bool(*v)),
Expand Down
Loading

0 comments on commit f8d0746

Please sign in to comment.