diff --git a/Cargo.toml b/Cargo.toml index 89ee7f3..a4b4e08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ version = "0.4" [dependencies.diesel] default-features = false features = ["with-deprecated", "unstable"] -version = "=1.4.2" +version = "=1.4.3" [dependencies.oci-sys] path = "oci-sys" @@ -43,5 +43,5 @@ r2d2 = ["diesel/r2d2"] default = ["chrono-time", "r2d2"] [replace] -"diesel:1.4.2" = { git = "https://github.com/GiGainfosystems/diesel", rev = "83ef2f8346" } -"diesel_derives:1.4.0" = { git = "https://github.com/GiGainfosystems/diesel", rev = "83ef2f8346" } +"diesel:1.4.3" = { git = "https://github.com/GiGainfosystems/diesel", rev = "700171d1e607c6675d105fdaa02753e448d216fa" } +"diesel_derives:1.4.1" = { git = "https://github.com/GiGainfosystems/diesel", rev = "700171d1e607c6675d105fdaa02753e448d216fa" } diff --git a/rust-toolchain b/rust-toolchain index 230f0b5..3210ed8 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2019-07-02 +nightly-2019-11-16 diff --git a/src/lib.rs b/src/lib.rs index 047bf61..28ab6e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![feature(re_rebalance_coherence, specialization)] +#![feature(specialization)] #[macro_use] extern crate diesel; diff --git a/src/oracle/backend.rs b/src/oracle/backend.rs index 761c2cf..a2fe0dd 100644 --- a/src/oracle/backend.rs +++ b/src/oracle/backend.rs @@ -1,4 +1,3 @@ -use byteorder::NativeEndian; use diesel::backend::UsesAnsiSavepointSyntax; use diesel::backend::*; use diesel::query_builder::bind_collector::RawBytesBindCollector; @@ -15,8 +14,18 @@ pub struct Oracle; impl Backend for Oracle { type QueryBuilder = OciQueryBuilder; type BindCollector = RawBytesBindCollector; - type RawValue = OracleValue; - type ByteOrder = NativeEndian; +} + +impl<'a> HasRawValue<'a> for Oracle { + type RawValue = OracleValue<'a>; +} + +impl<'a> BinaryRawValue<'a> for Oracle { + type ByteOrder = byteorder::NativeEndian; + + fn as_bytes(value: Self::RawValue) -> &'a [u8] { + value.bytes + } } impl TypeMetadata for Oracle { @@ -31,9 +40,34 @@ impl UsesAnsiSavepointSyntax for Oracle {} impl SupportsReturningClause for Oracle {} pub trait HasSqlTypeExt: HasSqlType { - fn oci_row_metadata(out: &mut Vec) { + fn oci_row_metadata(out: &mut Vec); +} + +impl HasSqlTypeExt for Oracle +where + Oracle: HasSqlType, +{ + default fn oci_row_metadata(out: &mut Vec) { out.push(Self::metadata(&())) } } -impl HasSqlTypeExt for Oracle where Oracle: HasSqlType {} +macro_rules! tuple_impls { + ($( + $Tuple:tt { + $(($idx:tt) -> $T:ident, $ST:ident, $TT:ident,)+ + } + )+) => { + $( + impl<$($T),+> HasSqlTypeExt<($($T,)+)> for Oracle + where $(Oracle: HasSqlTypeExt<$T>,)* + { + fn oci_row_metadata(out: &mut Vec) { + $(>::oci_row_metadata(out);)+ + } + } + )* + }; +} + +__diesel_for_each_tuple!(tuple_impls); diff --git a/src/oracle/clause_impl.rs b/src/oracle/clause_impl.rs deleted file mode 100644 index 6935131..0000000 --- a/src/oracle/clause_impl.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::backend::Oracle; -use diesel::backend::Backend; -use diesel::query_builder::order_clause::{NoOrderClause, OrderClause}; -use diesel::query_builder::{BuildQueryResult, QueryBuilder, QueryFragment}; - -macro_rules! simple_clause_impl { - ($no_clause:ident, $clause:ident, $sql:expr, $DB:ty) => { - impl QueryFragment<$DB> for $no_clause { - fn to_sql(&self, _out: &mut <$DB as Backend>::QueryBuilder) -> BuildQueryResult { - Ok(()) - } - } - - impl QueryFragment<$DB> for $clause - where - $DB: Backend, - Expr: QueryFragment<$DB>, - { - fn to_sql(&self, out: &mut <$DB as Backend>::QueryBuilder) -> BuildQueryResult { - out.push_sql($sql); - self.0.to_sql(out) - } - } - }; -} - -simple_clause_impl!(NoOrderClause, OrderClause, " ORDER BY ", Oracle); diff --git a/src/oracle/connection/create_migration_table.sql b/src/oracle/connection/create_migration_table.sql new file mode 100644 index 0000000..560df01 --- /dev/null +++ b/src/oracle/connection/create_migration_table.sql @@ -0,0 +1,7 @@ +declare +begin + create_if_not_exists('CREATE TABLE "__DIESEL_SCHEMA_MIGRATIONS" ( + "VERSION" VARCHAR2(50) PRIMARY KEY NOT NULL, + "RUN_ON" TIMESTAMP with time zone DEFAULT sysdate not null + )'); +end; diff --git a/src/oracle/connection/cursor.rs b/src/oracle/connection/cursor.rs index 6b33c70..3a8c529 100644 --- a/src/oracle/connection/cursor.rs +++ b/src/oracle/connection/cursor.rs @@ -3,7 +3,6 @@ use diesel::result::Error::DeserializationError; use diesel::result::QueryResult; use diesel::sql_types::HasSqlType; use oci_sys as ffi; -use std::collections::HashMap; use std::marker::PhantomData; use super::row::{NamedOciRow, OciRow}; @@ -40,6 +39,18 @@ impl Field { pub fn is_null(&self) -> bool { *self.null_indicator == -1 } + + pub fn buffer(&self) -> &[u8] { + &self.buffer + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn datatype(&self) -> OciDataType { + self.typ + } } impl Drop for Field { @@ -103,14 +114,7 @@ where } self.current_row += 1; - let null_indicators = self.results.iter().map(|r| r.is_null()).collect(); - let mut row = OciRow::new( - self.results - .iter_mut() - .map(|r: &mut Field| &r.buffer[..]) - .collect::>(), - null_indicators, - ); + let mut row = OciRow::new(&self.results); let value = T::Row::build_from_row(&mut row) .map(T::build) .map_err(DeserializationError); @@ -121,20 +125,13 @@ where pub struct NamedCursor<'a> { stmt: &'a Statement, results: Vec, - lut: HashMap, } impl<'a> NamedCursor<'a> { pub fn new(stmt: &'a Statement, binds: Vec) -> NamedCursor<'a> { - let lut = binds - .iter() - .enumerate() - .map(|(i, b)| (b.name.clone(), i)) - .collect(); NamedCursor { stmt, results: binds, - lut, } } @@ -164,15 +161,7 @@ impl<'a> NamedCursor<'a> { break; } } - let null_indicators = self.results.iter().map(|r| r.is_null()).collect(); - let row = NamedOciRow::new( - self.results - .iter_mut() - .map(|r: &mut Field| &r.buffer[..]) - .collect::>(), - null_indicators, - &self.lut, - ); + let row = NamedOciRow::new(&self.results); ret.push(T::build(&row).map_err(DeserializationError)?); } diff --git a/src/oracle/connection/define_create_if_not_exists.sql b/src/oracle/connection/define_create_if_not_exists.sql new file mode 100644 index 0000000..a8f60d2 --- /dev/null +++ b/src/oracle/connection/define_create_if_not_exists.sql @@ -0,0 +1,12 @@ +create or replace procedure create_if_not_exists(input_sql varchar2) +as +begin + execute immediate input_sql; + exception + when others then + if sqlcode = -955 then + NULL; + else + raise; + end if; +end; diff --git a/src/oracle/connection/mod.rs b/src/oracle/connection/mod.rs index 363ca87..b5ebcc9 100644 --- a/src/oracle/connection/mod.rs +++ b/src/oracle/connection/mod.rs @@ -14,7 +14,8 @@ use diesel::sql_types::HasSqlType; use self::cursor::{Cursor, NamedCursor}; use self::stmt::Statement; use self::transaction::OCITransactionManager; -use super::backend::Oracle; +use super::backend::{HasSqlTypeExt, Oracle}; +use diesel::RunQueryDsl; mod oracle_value; pub use self::oracle_value::OracleValue; @@ -34,29 +35,10 @@ pub struct OciConnection { } impl MigrationConnection for OciConnection { - #[cfg(ka)] - const CREATE_MIGRATIONS_FUNCTION: &'static str = - "create or replace procedure create_if_not_exists(input_sql varchar2) \ - as \ - begin \ - execute immediate input_sql; \ - exception \ - when others then \ - if sqlcode = -955 then \ - NULL; \ - else \ - raise; \ - end if; \ - end; \n "; - - const CREATE_MIGRATIONS_TABLE: &'static str = " - declare \ - begin \ - create_if_not_exists('CREATE TABLE \"__DIESEL_SCHEMA_MIGRATIONS\" (\ - \"VERSION\" VARCHAR2(50) PRIMARY KEY NOT NULL,\ - \"RUN_ON\" TIMESTAMP with time zone DEFAULT sysdate not null\ - )'); \ - end; \n"; + fn setup(&self) -> QueryResult { + diesel::sql_query(include_str!("define_create_if_not_exists.sql")).execute(self)?; + diesel::sql_query(include_str!("create_migration_table.sql")).execute(self) + } } // This relies on the invariant that RawConnection or Statement are never @@ -67,8 +49,8 @@ unsafe impl Send for OciConnection {} impl SimpleConnection for OciConnection { fn batch_execute(&self, query: &str) -> QueryResult<()> { - let mut stmt = try!(Statement::prepare(&self.raw, query)); - try!(stmt.run(self.auto_commit(), &[])); + let mut stmt = Statement::prepare(&self.raw, query)?; + stmt.run(self.auto_commit(), &[])?; stmt.bind_index = 0; Ok(()) } @@ -82,7 +64,7 @@ impl Connection for OciConnection { /// should be a valid connection string for a given backend. See the /// documentation for the specific backend for specifics. fn establish(database_url: &str) -> ConnectionResult { - let r = try!(raw::RawConnection::establish(database_url)); + let r = raw::RawConnection::establish(database_url)?; let ret = OciConnection { raw: Rc::new(r), transaction_manager: OCITransactionManager::new(), @@ -103,10 +85,10 @@ impl Connection for OciConnection { #[doc(hidden)] fn execute(&self, query: &str) -> QueryResult { - let mut stmt = try!(Statement::prepare(&self.raw, query)); - try!(stmt.run(self.auto_commit(), &[])); + let mut stmt = Statement::prepare(&self.raw, query)?; + stmt.run(self.auto_commit(), &[])?; stmt.bind_index = 0; - Ok(try!(stmt.get_affected_rows())) + Ok(stmt.get_affected_rows()?) } #[doc(hidden)] @@ -115,10 +97,10 @@ impl Connection for OciConnection { T: QueryFragment + QueryId, { // TODO: FIXME: this always returns 0 whereas the code looks proper - let mut stmt = try!(self.prepare_query(source)); - try!(stmt.run(self.auto_commit(), &[])); + let mut stmt = self.prepare_query(source)?; + stmt.run(self.auto_commit(), &[])?; stmt.bind_index = 0; - Ok(try!(stmt.get_affected_rows())) + Ok(stmt.get_affected_rows()?) } fn transaction_manager(&self) -> &Self::TransactionManager { @@ -134,9 +116,7 @@ impl Connection for OciConnection { { let mut stmt = self.prepare_query(&source.as_query())?; let mut metadata = Vec::new(); - // TODO: FIXME: Georg will check if this can get un-deprecated. - #[allow(deprecated)] - Oracle::row_metadata(&mut metadata, &()); + Oracle::oci_row_metadata(&mut metadata); let cursor: Cursor = stmt.run_with_cursor(self.auto_commit(), metadata)?; cursor.collect() } @@ -159,14 +139,14 @@ impl OciConnection { &self, source: &T, ) -> QueryResult> { - let mut statement = try!(self.cached_prepared_statement(source)); + let mut statement = self.cached_prepared_statement(source)?; let mut bind_collector = RawBytesBindCollector::::new(); - try!(source.collect_binds(&mut bind_collector, &())); + source.collect_binds(&mut bind_collector, &())?; let metadata = bind_collector.metadata; let binds = bind_collector.binds; for (tpe, value) in metadata.into_iter().zip(binds) { - try!(statement.bind(tpe, value)); + statement.bind(tpe, value)?; } Ok(statement) @@ -200,5 +180,7 @@ use diesel::r2d2::R2D2Connection; #[cfg(feature = "r2d2")] impl R2D2Connection for OciConnection { - const CHECK_QUERY_STRING: &'static str = "SELECT 1 FROM DUAL"; + fn ping(&self) -> QueryResult<()> { + self.execute("SELECT 1 FROM DUAL").map(|_| ()) + } } diff --git a/src/oracle/connection/oracle_value.rs b/src/oracle/connection/oracle_value.rs index 28d73fd..8d4d41e 100644 --- a/src/oracle/connection/oracle_value.rs +++ b/src/oracle/connection/oracle_value.rs @@ -1,10 +1,17 @@ +use oracle::types::OciDataType; + #[derive(Debug)] -pub struct OracleValue { - pub(crate) bytes: [u8], +pub struct OracleValue<'a> { + pub(crate) bytes: &'a [u8], + tpe: OciDataType, } -impl OracleValue { - pub fn new(bytes: &[u8]) -> &Self { - unsafe { &*(bytes as *const [u8] as *const Self) } +impl<'a> OracleValue<'a> { + pub fn new(bytes: &'a [u8], tpe: OciDataType) -> Self { + Self { bytes, tpe } + } + + pub fn datatype(&self) -> OciDataType { + self.tpe } } diff --git a/src/oracle/connection/row.rs b/src/oracle/connection/row.rs index 56dc3db..28a5009 100644 --- a/src/oracle/connection/row.rs +++ b/src/oracle/connection/row.rs @@ -1,32 +1,30 @@ -use super::super::backend::Oracle; +use super::cursor::Field; use diesel::row::{NamedRow, Row}; -use std::collections::HashMap; +use oracle::backend::Oracle; use super::oracle_value::OracleValue; pub struct OciRow<'a> { - buf: Vec<&'a [u8]>, - is_null: Vec, + binds: &'a [Field], col_idx: usize, } impl<'a> OciRow<'a> { - pub fn new(row_buf: Vec<&'a [u8]>, is_null: Vec) -> Self { - OciRow { - buf: row_buf, - is_null, - col_idx: 0, - } + pub fn new(binds: &'a [Field]) -> Self { + OciRow { col_idx: 0, binds } } } impl<'a> Row for OciRow<'a> { - fn take(&mut self) -> Option<&OracleValue> { - let ret = if self.col_idx < self.buf.len() { - if self.is_null[self.col_idx] { + fn take(&'_ mut self) -> Option> { + let ret = if self.col_idx < self.binds.len() { + if self.binds[self.col_idx].is_null() { None } else { - Some(OracleValue::new(self.buf[self.col_idx])) + Some(OracleValue::new( + self.binds[self.col_idx].buffer(), + self.binds[self.col_idx].datatype(), + )) } } else { None @@ -36,40 +34,46 @@ impl<'a> Row for OciRow<'a> { } fn next_is_null(&self, count: usize) -> bool { - (0..count).all(|i| self.is_null[i + self.col_idx]) + (0..count).all(|i| self.binds[i + self.col_idx].is_null()) + } + + fn column_count(&self) -> usize { + self.binds.len() + } + + fn column_name(&self) -> Option<&str> { + Some(self.binds[self.col_idx].name()) } } pub struct NamedOciRow<'a> { - buf: Vec<&'a [u8]>, - is_null: Vec, - lut: &'a HashMap, + binds: &'a [Field], } impl<'a> NamedOciRow<'a> { - pub fn new( - row_buf: Vec<&'a [u8]>, - is_null: Vec, - lut: &'a HashMap, - ) -> Self { - NamedOciRow { - buf: row_buf, - is_null, - lut, - } + pub fn new(binds: &'a [Field]) -> Self { + NamedOciRow { binds } } } impl<'a> NamedRow for NamedOciRow<'a> { fn index_of(&self, column_name: &str) -> Option { - self.lut.get(column_name).map(|ci| *ci as usize) + self.binds + .iter() + .enumerate() + .find(|(_, b)| b.name() == column_name) + .map(|(i, _)| i) } - fn get_raw_value(&self, index: usize) -> Option<&OracleValue> { - if index < self.buf.len() { - if self.is_null[index] { + + fn get_raw_value(&self, index: usize) -> Option> { + if index < self.binds.len() { + if self.binds[index].is_null() { None } else { - Some(OracleValue::new(self.buf[index])) + Some(OracleValue::new( + self.binds[index].buffer(), + self.binds[index].datatype(), + )) } } else { None diff --git a/src/oracle/connection/stmt.rs b/src/oracle/connection/stmt.rs index d19b4c3..4282538 100644 --- a/src/oracle/connection/stmt.rs +++ b/src/oracle/connection/stmt.rs @@ -690,7 +690,7 @@ impl Statement { let mut is_null = false; // using a box here otherwise the string will be deleted before // reaching OCIBindByPos - let (mut buf, size): (Box<[u8]>, i32) = if let Some(mut value) = value { + let (mut buf, size): (Box<[u8]>, i32) = if let Some(value) = value { let len = value.len() as i32; (value.into_boxed_slice(), len) } else { diff --git a/src/oracle/mod.rs b/src/oracle/mod.rs index e748200..65920a4 100644 --- a/src/oracle/mod.rs +++ b/src/oracle/mod.rs @@ -4,3 +4,5 @@ pub mod insertable; pub mod query_builder; pub mod query_dsl; pub mod types; + +pub use self::backend::Oracle; diff --git a/src/oracle/query_builder/limit_clause.rs b/src/oracle/query_builder/limit_clause.rs deleted file mode 100644 index cf7b5c1..0000000 --- a/src/oracle/query_builder/limit_clause.rs +++ /dev/null @@ -1,21 +0,0 @@ -use diesel::query_builder::{AstPass, QueryFragment}; -/// TODO: this is currently not used, IDK why diesel still takes the original limit clause -/// coming from diesel which uses `LIMIT` syntax -use diesel::result::QueryResult; - -use super::Oracle; - -#[derive(Debug, Clone, Copy, QueryId)] -pub struct LimitClause(pub Expr); - -impl QueryFragment for LimitClause -where - Expr: QueryFragment, -{ - fn walk_ast(&self, mut out: AstPass) -> QueryResult<()> { - out.push_sql("OFFSET 0 ROWS FETCH NEXT "); - self.0.walk_ast(out.reborrow())?; - out.push_sql(" ROWS ONLY"); - Ok(()) - } -} diff --git a/src/oracle/query_builder/limit_offset.rs b/src/oracle/query_builder/limit_offset.rs new file mode 100644 index 0000000..1620e56 --- /dev/null +++ b/src/oracle/query_builder/limit_offset.rs @@ -0,0 +1,131 @@ +use diesel::query_builder::{AstPass, QueryFragment}; +use diesel::query_builder::{BoxedLimitOffsetClause, IntoBoxedClause, LimitOffsetClause}; +use diesel::query_builder::{LimitClause, NoLimitClause}; +use diesel::query_builder::{NoOffsetClause, OffsetClause}; +use diesel::result::QueryResult; +use oracle::Oracle; + +impl QueryFragment for LimitOffsetClause { + fn walk_ast(&self, _out: AstPass) -> QueryResult<()> { + Ok(()) + } +} + +impl QueryFragment for LimitOffsetClause, NoOffsetClause> +where + L: QueryFragment, +{ + fn walk_ast(&self, mut out: AstPass) -> QueryResult<()> { + out.push_sql(" FETCH FIRST "); + self.limit_clause.0.walk_ast(out.reborrow())?; + out.push_sql(" ROWS ONLY "); + Ok(()) + } +} + +impl QueryFragment for LimitOffsetClause> +where + O: QueryFragment, +{ + fn walk_ast(&self, mut out: AstPass) -> QueryResult<()> { + out.push_sql(" OFFSET "); + self.offset_clause.0.walk_ast(out.reborrow())?; + out.push_sql(" ROWS "); + Ok(()) + } +} + +impl QueryFragment for LimitOffsetClause, OffsetClause> +where + L: QueryFragment, + O: QueryFragment, +{ + fn walk_ast(&self, mut out: AstPass) -> QueryResult<()> { + out.push_sql(" OFFSET "); + self.offset_clause.0.walk_ast(out.reborrow())?; + out.push_sql(" ROWS FETCH NEXT "); + self.limit_clause.0.walk_ast(out.reborrow())?; + out.push_sql(" ROWS ONLY "); + Ok(()) + } +} + +impl<'a> QueryFragment for BoxedLimitOffsetClause<'a, Oracle> { + fn walk_ast(&self, mut out: AstPass) -> QueryResult<()> { + match (self.limit.as_ref(), self.offset.as_ref()) { + (Some(limit), Some(offset)) => { + out.push_sql(" OFFSET "); + offset.walk_ast(out.reborrow())?; + out.push_sql(" ROWS FETCH NEXT "); + limit.walk_ast(out.reborrow())?; + out.push_sql(" ROWS ONLY "); + } + (Some(limit), None) => { + out.push_sql(" FETCH FIRST "); + limit.walk_ast(out.reborrow())?; + out.push_sql(" ROWS ONLY "); + } + (None, Some(offset)) => { + out.push_sql(" OFFSET "); + offset.walk_ast(out.reborrow())?; + out.push_sql(" ROWS "); + } + (None, None) => {} + } + Ok(()) + } +} + +impl<'a> IntoBoxedClause<'a, Oracle> for LimitOffsetClause { + type BoxedClause = BoxedLimitOffsetClause<'a, Oracle>; + + fn into_boxed(self) -> Self::BoxedClause { + BoxedLimitOffsetClause { + limit: None, + offset: None, + } + } +} + +impl<'a, L> IntoBoxedClause<'a, Oracle> for LimitOffsetClause, NoOffsetClause> +where + L: QueryFragment + 'a, +{ + type BoxedClause = BoxedLimitOffsetClause<'a, Oracle>; + + fn into_boxed(self) -> Self::BoxedClause { + BoxedLimitOffsetClause { + limit: Some(Box::new(self.limit_clause.0)), + offset: None, + } + } +} + +impl<'a, O> IntoBoxedClause<'a, Oracle> for LimitOffsetClause> +where + O: QueryFragment + 'a, +{ + type BoxedClause = BoxedLimitOffsetClause<'a, Oracle>; + + fn into_boxed(self) -> Self::BoxedClause { + BoxedLimitOffsetClause { + limit: None, + offset: Some(Box::new(self.offset_clause.0)), + } + } +} + +impl<'a, L, O> IntoBoxedClause<'a, Oracle> for LimitOffsetClause, OffsetClause> +where + L: QueryFragment + 'a, + O: QueryFragment + 'a, +{ + type BoxedClause = BoxedLimitOffsetClause<'a, Oracle>; + + fn into_boxed(self) -> Self::BoxedClause { + BoxedLimitOffsetClause { + limit: Some(Box::new(self.limit_clause.0)), + offset: Some(Box::new(self.offset_clause.0)), + } + } +} diff --git a/src/oracle/query_builder/mod.rs b/src/oracle/query_builder/mod.rs index 2a26533..8fad92b 100644 --- a/src/oracle/query_builder/mod.rs +++ b/src/oracle/query_builder/mod.rs @@ -5,8 +5,7 @@ use diesel::result::Error as DieselError; mod alias; mod exists; -mod limit_clause; -mod select_statement; +mod limit_offset; pub use self::alias::Alias; diff --git a/src/oracle/query_builder/select_statement.rs b/src/oracle/query_builder/select_statement.rs deleted file mode 100644 index fae9a95..0000000 --- a/src/oracle/query_builder/select_statement.rs +++ /dev/null @@ -1,83 +0,0 @@ -use diesel::query_builder::{AstPass, QueryFragment, SelectClauseQueryFragment, SelectStatement}; -use diesel::query_builder::{LimitClause, NoLimitClause, NoOffsetClause, OffsetClause}; -use diesel::QueryResult; -use diesel::QuerySource; - -use oracle::backend::Oracle; - -impl QueryFragment - for SelectStatement -where - S: SelectClauseQueryFragment, - F: QuerySource, - F::FromClause: QueryFragment, - D: QueryFragment, - W: QueryFragment, - O: QueryFragment, - for<'a> OffsetWrapper<'a, L, Of>: QueryFragment, - L: QueryFragment, - Of: QueryFragment, - G: QueryFragment, - LC: QueryFragment, -{ - default fn walk_ast(&self, mut out: AstPass) -> QueryResult<()> { - out.push_sql("SELECT "); - self.distinct.walk_ast(out.reborrow())?; - self.select.walk_ast(&self.from, out.reborrow())?; - out.push_sql(" FROM "); - self.from.from_clause().walk_ast(out.reborrow())?; - self.where_clause.walk_ast(out.reborrow())?; - self.group_by.walk_ast(out.reborrow())?; - self.order.walk_ast(out.reborrow())?; - OffsetWrapper(&self.limit, &self.offset).walk_ast(out.reborrow())?; - self.locking.walk_ast(out.reborrow())?; - Ok(()) - } -} - -pub struct OffsetWrapper<'a, T, V>(&'a T, &'a V); - -impl<'a> QueryFragment for OffsetWrapper<'a, NoLimitClause, NoOffsetClause> { - fn walk_ast(&self, _out: AstPass) -> QueryResult<()> { - Ok(()) - } -} - -impl<'a, T> QueryFragment for OffsetWrapper<'a, NoLimitClause, OffsetClause> -where - T: QueryFragment, -{ - fn walk_ast(&self, mut out: AstPass) -> QueryResult<()> { - out.push_sql(" OFFSET "); - (self.1).0.walk_ast(out.reborrow())?; - out.push_sql(" ROWS "); - Ok(()) - } -} - -impl<'a, T> QueryFragment for OffsetWrapper<'a, LimitClause, NoOffsetClause> -where - T: QueryFragment, -{ - fn walk_ast(&self, mut out: AstPass) -> QueryResult<()> { - out.push_sql(" FETCH FIRST "); - (self.0).0.walk_ast(out.reborrow())?; - out.push_sql(" ROWS ONLY "); - Ok(()) - } -} - -impl<'a, T1, T2> QueryFragment for OffsetWrapper<'a, LimitClause, OffsetClause> -where - T1: QueryFragment, - T2: QueryFragment, -{ - fn walk_ast(&self, mut out: AstPass) -> QueryResult<()> { - out.push_sql(" OFFSET "); - (self.1).0.walk_ast(out.reborrow())?; - out.push_sql(" ROWS FETCH NEXT "); - (self.0).0.walk_ast(out.reborrow())?; - out.push_sql(" ROWS ONLY "); - Ok(()) - } -} diff --git a/src/oracle/query_builder/subselect.rs b/src/oracle/query_builder/subselect.rs deleted file mode 100644 index 26efa49..0000000 --- a/src/oracle/query_builder/subselect.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::marker::PhantomData; - -use diesel::expression::array_comparison::MaybeEmpty; -use diesel::expression::*; -use diesel::query_builder::*; -use diesel::result::QueryResult; - -use super::Oracle; - -#[derive(Debug, Copy, Clone, QueryId)] -pub struct Subselect { - values: T, - _sql_type: PhantomData, -} - -impl Subselect { - pub(crate) fn new(values: T) -> Self { - Self { - values, - _sql_type: PhantomData, - } - } -} - -impl Expression for Subselect { - type SqlType = ST; -} - -impl MaybeEmpty for Subselect { - fn is_empty(&self) -> bool { - false - } -} - -impl SelectableExpression for Subselect - where - Subselect: AppearsOnTable, - T: ValidSubselect, -{ -} - -impl AppearsOnTable for Subselect - where - Subselect: Expression, - T: ValidSubselect, -{ -} - -impl NonAggregate for Subselect {} - -impl QueryFragment for Subselect - where - T: QueryFragment, -{ - fn walk_ast(&self, mut out: AstPass) -> QueryResult<()> { - self.values.walk_ast(out.reborrow())?; - Ok(()) - } -} - -pub trait ValidSubselect {} diff --git a/src/oracle/types/chrono_date_time.rs b/src/oracle/types/chrono_date_time.rs index 65cd181..80e5028 100644 --- a/src/oracle/types/chrono_date_time.rs +++ b/src/oracle/types/chrono_date_time.rs @@ -3,6 +3,7 @@ use std::error::Error; use std::io::Write; use diesel::deserialize::FromSql; +use diesel::result; use diesel::serialize::{IsNull, Output, ToSql}; use diesel::sql_types::*; @@ -13,7 +14,7 @@ use self::chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike}; use super::super::connection::OracleValue; impl FromSql for NaiveDateTime { - fn from_sql(bytes: Option<&OracleValue>) -> Result> { + fn from_sql(bytes: Option>) -> Result> { let bytes = not_none!(bytes); let bytes = &bytes.bytes; let sec = u32::from(bytes[6]) - 1; @@ -24,25 +25,29 @@ impl FromSql for NaiveDateTime { let year = i32::from(bytes[1]); let century = i32::from(bytes[0]); if century > 100 && year > 100 { - // TODO: error handling - let d = - NaiveDate::from_ymd_opt((century - 100) * 100 + year - 100, month, day).unwrap(); - // ok_or(Box::new( - // result::Error::DatabaseError( - // result::DatabaseErrorKind::__Unknown, - // Box::new(String::from("could not parse timestamp")))))); + let d = NaiveDate::from_ymd_opt((century - 100) * 100 + year - 100, month, day).ok_or( + Box::new(result::Error::DeserializationError( + "could not parse timestamp".into(), + )), + )?; Ok(d.and_hms_opt(hr, min, sec).unwrap()) } else if century < 100 && year < 100 { - // TODO: error handling - let d = NaiveDate::from_ymd_opt(century * -100 + year, month, day).unwrap(); - // .ok_or(Box::new(result::Error::DatabaseError("could not parse \ - // timestamp" - // .to_owned())))); + let d = NaiveDate::from_ymd_opt(century * -100 + year, month, day).ok_or(Box::new( + result::Error::DeserializationError("could not parse timestamp".into()), + ))?; Ok(d.and_hms_opt(hr, min, sec).unwrap()) } else { - unreachable!() + Err(Box::new(result::Error::DeserializationError( + concat!( + "reached a unreachable state while parsing timestamp: ", + file!(), + ":", + line!() + ) + .into(), + ))) } } } @@ -51,20 +56,18 @@ impl ToSql for NaiveDateTime { fn to_sql( &self, out: &mut Output, - ) -> Result> { + ) -> Result> { let year = self.year(); if year > 0 { let c: u8 = (year / 100 + 100) as u8; let y: u8 = (year % 100 + 100) as u8; - try!(out - .write(&[c, y]) - .map_err(|e| Box::new(e) as Box)); + out.write(&[c, y]) + .map_err(|e| result::Error::SerializationError(Box::new(e)))?; } else { let c: u8 = (year / 100) as u8; let y: u8 = (year % 100) as u8; - try!(out - .write(&[c, y]) - .map_err(|e| Box::new(e) as Box)); + out.write(&[c, y]) + .map_err(|e| result::Error::SerializationError(Box::new(e)))?; } let mo = self.month() as u8; let d = self.day() as u8; @@ -72,13 +75,13 @@ impl ToSql for NaiveDateTime { let mi = (self.minute() + 1) as u8; let s = (self.second() + 1) as u8; out.write(&[mo, d, h, mi, s]) - .map_err(|e| Box::new(e) as Box) + .map_err(|e| Box::new(e) as Box) .map(|_| IsNull::No) } } impl FromSql for NaiveDate { - fn from_sql(bytes: Option<&OracleValue>) -> Result> { + fn from_sql(bytes: Option>) -> Result> { let bytes = not_none!(bytes); let bytes = &bytes.bytes; let d = u32::from(bytes[3]); @@ -86,13 +89,27 @@ impl FromSql for NaiveDate { let y = i32::from(bytes[1]); let c = i32::from(bytes[0]); if c > 100 && y > 100 { - // TODO: error handling - Ok(NaiveDate::from_ymd_opt((c - 100) * 100 + y - 100, mo, d).unwrap()) + NaiveDate::from_ymd_opt((c - 100) * 100 + y - 100, mo, d).ok_or_else(|| { + Box::new(result::Error::DeserializationError( + "Unable to parse timestamp".into(), + )) as Box + }) } else if c < 100 && y < 100 { - // TODO: error handling - Ok(NaiveDate::from_ymd_opt(c * -100 + y - 100, mo, d).unwrap()) + NaiveDate::from_ymd_opt(c * -100 + y - 100, mo, d).ok_or_else(|| { + Box::new(result::Error::DeserializationError( + "Unable to parse timestamp".into(), + )) as _ + }) } else { - unreachable!() + Err(Box::new(result::Error::DeserializationError( + concat!( + "reached a unreachable state while parsing timestamp: ", + file!(), + ":", + line!() + ) + .into(), + ))) } } } @@ -101,25 +118,23 @@ impl ToSql for NaiveDate { fn to_sql( &self, out: &mut Output, - ) -> Result> { + ) -> Result> { let year = self.year(); if year > 0 { let c: u8 = (year / 100 + 100) as u8; let y: u8 = (year % 100 + 100) as u8; - try!(out - .write(&[c, y]) - .map_err(|e| Box::new(e) as Box)); + out.write(&[c, y]) + .map_err(|e| result::Error::SerializationError(Box::new(e)))?; } else { let c: u8 = (year / 100) as u8; let y: u8 = (year % 100) as u8; - try!(out - .write(&[c, y]) - .map_err(|e| Box::new(e) as Box)); + out.write(&[c, y]) + .map_err(|e| result::Error::SerializationError(Box::new(e)))?; } let mo = self.month() as u8; let d = self.day() as u8; out.write(&[mo, d, 1, 1, 1]) - .map_err(|e| Box::new(e) as Box) + .map_err(|e| Box::new(e) as Box) .map(|_| IsNull::No) } } diff --git a/src/oracle/types/decimal.rs b/src/oracle/types/decimal.rs deleted file mode 100644 index bcf80fc..0000000 --- a/src/oracle/types/decimal.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::error::Error; - -use diesel::deserialize::FromSql; -use diesel::sql_types::*; - -use oracle::backend::Oracle; - -use super::super::connection::OracleValue; - -use byteorder::ReadBytesExt; -use diesel::backend::*; - -impl FromSql for f64 { - fn from_sql(bytes: Option<&OracleValue>) -> Result> { - let bytes = not_none!(bytes); - let mut bytes = &bytes.bytes; - debug_assert!( - bytes.len() <= 8, - "Received more than 8 bytes while decoding \ - an f64. Was a numeric accidentally marked as double?" - ); - bytes - .read_f64::<::ByteOrder>() - .map_err(|e| Box::new(e) as Box) - } -} - -impl FromSql for f32 { - fn from_sql(bytes: Option<&OracleValue>) -> Result> { - let bytes = not_none!(bytes); - let mut bytes = &bytes.bytes; - debug_assert!( - bytes.len() <= 4, - "Received more than 4 bytes while decoding \ - an f32. Was a numeric accidentally marked as double?" - ); - bytes - .read_f32::<::ByteOrder>() - .map_err(|e| Box::new(e) as Box) - } -} diff --git a/src/oracle/types/integers.rs b/src/oracle/types/integers.rs deleted file mode 100644 index 7cabe11..0000000 --- a/src/oracle/types/integers.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::error::Error; - -use diesel::deserialize::FromSql; -use diesel::sql_types::*; - -use oracle::backend::Oracle; - -use super::super::connection::OracleValue; - -use byteorder::ReadBytesExt; -use diesel::backend::*; - -pub type FromSqlResult = Result; -pub type ErrorType = Box; - -impl FromSql for i64 { - fn from_sql(bytes: Option<&OracleValue>) -> FromSqlResult { - let bytes = not_none!(bytes); - let mut bytes = &bytes.bytes; - debug_assert!( - bytes.len() <= 8, - "Received more than 8 bytes decoding i64. \ - Was an expression of a different type misidentified as BigInteger?" - ); - debug_assert!( - bytes.len() >= 8, - "Received fewer than 8 bytes decoding i64. \ - Was an Integer expression misidentified as BigInteger?" - ); - bytes - .read_i64::<::ByteOrder>() - .map_err(|e| Box::new(e) as Box) - } -} - -impl FromSql for i32 { - fn from_sql(bytes: Option<&OracleValue>) -> FromSqlResult { - let bytes = not_none!(bytes); - let mut bytes = &bytes.bytes; - debug_assert!( - bytes.len() <= 4, - "Received more than 4 bytes decoding i32. \ - Was an expression of a different type misidentified as Integer?" - ); - debug_assert!( - bytes.len() >= 4, - "Received fewer than 4 bytes decoding i32. \ - Was an SmallInt expression misidentified as Integer?" - ); - bytes - .read_i32::<::ByteOrder>() - .map_err(|e| Box::new(e) as Box) - } -} - -impl FromSql for i16 { - fn from_sql(bytes: Option<&OracleValue>) -> FromSqlResult { - let bytes = not_none!(bytes); - let mut bytes = &bytes.bytes; - debug_assert!( - bytes.len() <= 2, - "Received more than 2 bytes decoding i16. \ - Was an expression of a different type misidentified as SmallInteger?" - ); - debug_assert!( - bytes.len() >= 2, - "Received fewer than 2 bytes decoding i16. \ - Was an expression of a different type misidentified as SmallInteger?" - ); - bytes - .read_i16::<::ByteOrder>() - .map_err(|e| Box::new(e) as Box) - } -} diff --git a/src/oracle/types/mod.rs b/src/oracle/types/mod.rs index 4b8c071..5b75168 100644 --- a/src/oracle/types/mod.rs +++ b/src/oracle/types/mod.rs @@ -1,16 +1,15 @@ use super::backend::*; use super::connection::OracleValue; -use byteorder::WriteBytesExt; -use diesel::backend::*; use diesel::deserialize::FromSql; use diesel::serialize::{IsNull, Output, ToSql}; use diesel::sql_types::*; use oci_sys as ffi; use std::error::Error; use std::io::Write; +use std::str; pub type FromSqlResult = Result; -pub type ErrorType = Box; +pub type ErrorType = Box; pub type ToSqlResult = FromSqlResult; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -166,22 +165,32 @@ impl HasSqlType for Oracle { } impl FromSql for bool { - fn from_sql(bytes: Option<&OracleValue>) -> FromSqlResult { + fn from_sql(bytes: Option>) -> FromSqlResult { FromSql::::from_sql(bytes).map(|v: i16| v != 0) } } impl ToSql for bool { fn to_sql(&self, out: &mut Output) -> ToSqlResult { - out.write_i16::<::ByteOrder>(if *self { 1 } else { 0 }) - .map(|_| IsNull::No) - .map_err(|e| Box::new(e) as ErrorType) + >::to_sql(&if *self { 1 } else { 0 }, out) + } +} + +impl FromSql for *const str { + fn from_sql(bytes: Option>) -> FromSqlResult { + use diesel::result::Error as DieselError; + let bytes = not_none!(bytes); + let pos = bytes + .bytes + .iter() + .position(|&b| b == 0) + .ok_or(Box::new(DieselError::DeserializationError( + "Expected at least one null byte".into(), + )) as Box)?; + let string = str::from_utf8(&bytes.bytes[..pos])?; + Ok(string as *const _) } } #[cfg(feature = "chrono-time")] mod chrono_date_time; - -mod decimal; -mod integers; -mod primitives; diff --git a/src/oracle/types/primitives.rs b/src/oracle/types/primitives.rs deleted file mode 100644 index 83f4efb..0000000 --- a/src/oracle/types/primitives.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::error::Error; - -use diesel::deserialize::FromSql; -use diesel::result::Error as DieselError; -use diesel::sql_types::*; -use oracle::backend::Oracle; -use std::ffi::CStr; - -use super::super::connection::OracleValue; - -impl FromSql for String { - fn from_sql(bytes: Option<&OracleValue>) -> Result> { - let bytes = not_none!(bytes); - let pos = bytes - .bytes - .iter() - .position(|&b| b == 0) - .ok_or(Box::new(DieselError::DeserializationError( - "Expected at least one null byte".into(), - )) as Box)?; - Ok(CStr::from_bytes_with_nul(&bytes.bytes[..=pos])? - .to_str()? - .to_owned()) - } -} - -/// The returned pointer is *only* valid for the lifetime to the argument of -/// `from_sql`. This impl is intended for uses where you want to write a new -/// impl in terms of `Vec`, but don't want to allocate. We have to return a -/// raw pointer instead of a reference with a lifetime due to the structure of -/// `FromSql` -impl FromSql for *const [u8] { - fn from_sql(bytes: Option<&OracleValue>) -> Result> { - let bytes = not_none!(bytes); - - Ok(&bytes.bytes as *const [u8]) - } -} diff --git a/src/test/mod.rs b/src/test/mod.rs index 60a4863..0a5a5d2 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -998,7 +998,7 @@ where } } -pub fn make_err(e: E) -> Box +pub fn make_err(e: E) -> Box where E: StdError + Send + Sync + 'static, { @@ -1027,7 +1027,7 @@ impl ToSql for CoordinateSystemType { } impl FromSql for CoordinateSystemType { - fn from_sql(bytes: Option<&OracleValue>) -> deserialize::Result { + fn from_sql(bytes: Option) -> deserialize::Result { let value = >::from_sql(bytes)?; CoordinateSystemType::from_i16(value).ok_or_else(|| { error!("Invalid value for coordinate system type found: {}", value); @@ -1337,7 +1337,7 @@ impl ToSql for PropertyDataType { } impl FromSql for PropertyDataType { - fn from_sql(bytes: Option<&OracleValue>) -> deserialize::Result { + fn from_sql(bytes: Option) -> deserialize::Result { let value = >::from_sql(bytes)?; PropertyDataType::from_i16(value).ok_or_else(|| { error!("Invalid value for property data type found: {}", value); @@ -1942,7 +1942,7 @@ fn updateing_unique_constraint() { let sql = "SELECT * FROM geometries"; let ret = conn.execute(sql); if ret.is_ok() { - let _ret = conn.execute(DROP_GEOMETRIES);; + let _ret = conn.execute(DROP_GEOMETRIES); } let ret = conn.execute(CREATE_GEOMETRIES); assert_result!(ret); @@ -1998,7 +1998,7 @@ fn insert_returning() { let sql = "SELECT * FROM geometries"; let ret = conn.execute(sql); if ret.is_ok() { - let _ret = conn.execute(DROP_GEOMETRIES);; + let _ret = conn.execute(DROP_GEOMETRIES); } let ret = conn.execute(CREATE_GEOMETRIES); assert_result!(ret);