├── src ├── oracle │ ├── query_dsl │ │ ├── mod.rs │ │ └── save_changes_dsl.rs │ ├── connection │ │ ├── create_migration_table.sql │ │ ├── define_create_if_not_exists.sql │ │ ├── stmt_iter.rs │ │ ├── bind_collector.rs │ │ ├── row.rs │ │ ├── transaction.rs │ │ ├── oracle_value.rs │ │ └── mod.rs │ ├── query_builder │ │ ├── exists.rs │ │ ├── mod.rs │ │ ├── returning.rs │ │ ├── alias.rs │ │ └── limit_offset.rs │ ├── mod.rs │ ├── backend.rs │ ├── types │ │ ├── chrono_date_time.rs │ │ ├── mod.rs │ │ ├── primitives.rs │ │ └── interval.rs │ └── insertable.rs ├── logger.rs ├── lib.rs └── test │ └── dynamic_select.rs ├── .gitignore ├── wait-for-oralce.sh ├── LICENSE-MIT ├── README.md ├── Cargo.toml ├── .travis.yml ├── .github └── workflows │ └── ci.yml └── LICENSE-APACHE /src/oracle/query_dsl/mod.rs: -------------------------------------------------------------------------------- 1 | mod save_changes_dsl; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .cargo 4 | *~ 5 | *\#*\# 6 | 7 | .idea/ 8 | -------------------------------------------------------------------------------- /wait-for-oralce.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | k=0 4 | while : 5 | do 6 | if [[ $k -eq 10 ]]; then 7 | break 8 | fi 9 | echo "generic wait" 10 | sleep 60 11 | k=$k+1 12 | done -------------------------------------------------------------------------------- /src/oracle/connection/create_migration_table.sql: -------------------------------------------------------------------------------- 1 | declare 2 | begin 3 | create_if_not_exists('CREATE TABLE "__DIESEL_SCHEMA_MIGRATIONS" ( 4 | "VERSION" VARCHAR2(50) PRIMARY KEY NOT NULL, 5 | "RUN_ON" TIMESTAMP with time zone DEFAULT sysdate not null 6 | )'); 7 | end; 8 | -------------------------------------------------------------------------------- /src/oracle/connection/define_create_if_not_exists.sql: -------------------------------------------------------------------------------- 1 | create or replace procedure create_if_not_exists(input_sql varchar2) 2 | as 3 | begin 4 | execute immediate input_sql; 5 | exception 6 | when others then 7 | if sqlcode = -955 then 8 | NULL; 9 | else 10 | raise; 11 | end if; 12 | end; 13 | -------------------------------------------------------------------------------- /src/oracle/connection/stmt_iter.rs: -------------------------------------------------------------------------------- 1 | use diesel::QueryResult; 2 | 3 | use super::row::OciRow; 4 | 5 | pub struct RowIter { 6 | rows: Vec, 7 | } 8 | 9 | impl RowIter { 10 | pub(super) fn new(mut rows: Vec) -> Self { 11 | rows.reverse(); 12 | Self { rows } 13 | } 14 | } 15 | 16 | impl Iterator for RowIter { 17 | type Item = QueryResult; 18 | 19 | fn next(&mut self) -> Option { 20 | self.rows.pop().map(Ok) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/oracle/query_builder/exists.rs: -------------------------------------------------------------------------------- 1 | use crate::oracle::backend::{Oracle, OracleExistsSyntax}; 2 | 3 | use diesel::expression::exists::Exists; 4 | use diesel::query_builder::{AstPass, QueryFragment}; 5 | use diesel::result::QueryResult; 6 | 7 | impl QueryFragment for Exists 8 | where 9 | T: QueryFragment, 10 | { 11 | fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Oracle>) -> QueryResult<()> { 12 | out.push_sql("CASE WHEN EXISTS ("); 13 | self.subselect.walk_ast(out.reborrow())?; 14 | out.push_sql(") THEN 1 ELSE 0 END"); 15 | Ok(()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/oracle/mod.rs: -------------------------------------------------------------------------------- 1 | //! Provides types and functions related to working with Oracle 2 | //! 3 | //! This module contains a diesel backend and connection implementation for 4 | //! Oracle databases 5 | 6 | pub(crate) mod backend; 7 | pub(crate) mod connection; 8 | pub(crate) mod insertable; 9 | /// Oracle specific query builder implementation 10 | pub mod query_builder; 11 | pub(crate) mod query_dsl; 12 | pub(crate) mod types; 13 | 14 | pub use self::backend::Oracle; 15 | pub use self::connection::{OciConnection, OracleValue}; 16 | pub use self::types::{ 17 | OciDataType, OciIntervalDS, OciIntervalYM, OciTypeMetadata, SqlIntervalDS, SqlIntervalYM, 18 | }; 19 | -------------------------------------------------------------------------------- /src/logger.rs: -------------------------------------------------------------------------------- 1 | use log::Log; 2 | use log::Metadata; 3 | use log::Record; 4 | use std::sync::Once; 5 | 6 | static START: Once = Once::new(); 7 | 8 | struct SimpleLogger; 9 | 10 | impl Log for SimpleLogger { 11 | fn enabled(&self, _metadata: &Metadata) -> bool { 12 | true 13 | } 14 | 15 | fn log(&self, record: &Record) { 16 | if self.enabled(record.metadata()) { 17 | println!("{} - {}", record.level(), record.args()); 18 | } 19 | } 20 | 21 | fn flush(&self) {} 22 | } 23 | 24 | pub(crate) fn init() { 25 | START.call_once(|| { 26 | ::log::set_logger(&SimpleLogger).expect("This logger needs to be present"); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2018 GiGa infosystems GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/oracle/query_dsl/save_changes_dsl.rs: -------------------------------------------------------------------------------- 1 | use diesel::associations::HasTable; 2 | use diesel::associations::Identifiable; 3 | use diesel::dsl::Find; 4 | use diesel::dsl::Update; 5 | use diesel::query_builder::{AsChangeset, AsQuery, IntoUpdateTarget}; 6 | use diesel::query_dsl::methods::{ExecuteDsl, FindDsl}; 7 | use diesel::query_dsl::{LoadQuery, RunQueryDsl}; 8 | use diesel::result::QueryResult; 9 | 10 | use crate::oracle::connection::OciConnection; 11 | use diesel::query_dsl::UpdateAndFetchResults; 12 | 13 | impl<'query, Changes, Output> UpdateAndFetchResults for OciConnection 14 | where 15 | Changes: Copy + Identifiable, 16 | Changes: AsChangeset::Table> + IntoUpdateTarget, 17 | Changes::Table: FindDsl, 18 | Update: ExecuteDsl + AsQuery, 19 | Find: LoadQuery<'query, OciConnection, Output>, 20 | { 21 | fn update_and_fetch(&mut self, changeset: Changes) -> QueryResult { 22 | diesel::update(changeset).set(changeset).execute(self)?; 23 | Changes::table().find(changeset.id()).get_result(self) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Diesel-oci 2 | 3 | A Oracle SQL database backend implementation for 4 | [Diesel](https://github.com/diesel-rs/diesel). 5 | 6 | ## Status: 7 | 8 | - [x] Builds with Diesel 2.0.0. 9 | - [x] Support for DML statements (`SELECT`, `INSERT`, `UPDATE`, `DELETE`). 10 | - [x] Support for Diesel `sql_types`: `Bool`, `SmallInt`, 11 | `Integer`, `Bigint`, `Float`, `Double`, `Date`, `Time`, `Timestamp`. 12 | - [x] Support for diesel-dynamic-schema and diesel-migrations 13 | 14 | ## Code of conduct 15 | 16 | Anyone who interacts with Diesel in any space, including but not limited to 17 | this GitHub repository, must follow our [code of conduct](https://github.com/diesel-rs/diesel/blob/master/code_of_conduct.md). 18 | 19 | ## License 20 | 21 | Licensed under either of these: 22 | 23 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or 24 | http://www.apache.org/licenses/LICENSE-2.0) 25 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or 26 | http://opensource.org/licenses/MIT) 27 | 28 | ### Contributing 29 | 30 | Unless you explicitly state otherwise, any contribution you intentionally submit 31 | for inclusion in the work, as defined in the Apache-2.0 license, shall be 32 | dual-licensed as above, without any additional terms or conditions. 33 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | //! The Oracle Diesel Backend 3 | //! 4 | //! This crate only implements an oracle backend and connection for Diesel. 5 | //! To use diesel features, you must import it. 6 | //! 7 | //! ```no_run 8 | //! // Import diesel 9 | //! use diesel::prelude::*; 10 | //! // Import the oracle connection type 11 | //! use diesel_oci::OciConnection; 12 | //! 13 | //! table! { 14 | //! users { 15 | //! id -> Integer, 16 | //! name -> Text, 17 | //! } 18 | //! } 19 | //! 20 | //! # fn run_test() -> Result<(), Box> { 21 | //! // establish a connection 22 | //! let mut conn = OciConnection::establish("oracle://user:secret@127.0.0.1/MY_DB")?; 23 | //! 24 | //! // use the connection similary to any other diesel connection 25 | //! let res = users::table.load::<(i32, String)>(&mut conn)?; 26 | //! # Ok(()) 27 | //! # } 28 | //! ``` 29 | //! 30 | //! # Feature flags 31 | //! 32 | //! * `chrono` Enables support for the `chrono` crate 33 | //! * `r2d2` Enables support for r2d2 connection pooling 34 | //! * `dynamic-schema` Enables support for diesel-dynamic-schema 35 | 36 | pub mod oracle; 37 | 38 | #[doc(inline)] 39 | pub use crate::oracle::*; 40 | 41 | #[cfg(test)] 42 | mod test; 43 | 44 | #[cfg(test)] 45 | mod logger; 46 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = [ 3 | "Georg Semmler ", 4 | "Paul Gabriel ", 5 | "Daniel Buse ", 6 | ] 7 | name = "diesel-oci" 8 | version = "0.5.1" 9 | license = "MIT OR Apache-2.0" 10 | description = "A oci database adapter for diesel" 11 | readme = "README.md" 12 | keywords = ["diesel", "oci", "oracle", "sql"] 13 | categories = ["database"] 14 | edition = "2021" 15 | repository = "https://github.com/GiGainfosystems/diesel-oci/" 16 | documentation = "https://docs.rs/diesel-oci" 17 | include = [ 18 | "Cargo.toml", 19 | "LICENSE-MIT", 20 | "LICENSE-APACHE", 21 | "README.md", 22 | "src/**/*.rs", 23 | "src/oracle/connection/define_create_if_not_exists.sql", 24 | "src/oracle/connection/create_migration_table.sql", 25 | ] 26 | 27 | [dependencies] 28 | oracle = {version = "0.6.0", features = ["chrono"]} 29 | url = "2" 30 | percent-encoding = "2" 31 | 32 | [dependencies.chrono-time] 33 | optional = true 34 | default-features = false 35 | version = "0.4" 36 | package = "chrono" 37 | 38 | [dependencies.diesel] 39 | default-features = false 40 | features = ["i-implement-a-third-party-backend-and-opt-into-breaking-changes"] 41 | version = "~2.3.0" 42 | 43 | [dependencies.diesel_derives] 44 | version = "~2.3.0" 45 | 46 | [dependencies.diesel-dynamic-schema] 47 | optional = true 48 | default-features = false 49 | version = "0.2.4" 50 | 51 | [dev-dependencies] 52 | log = "0.4" 53 | dotenvy = "0.15" 54 | num = { version = "0.4", default-features = false } 55 | num-derive = "0.4" 56 | num-traits = "0.2" 57 | 58 | [features] 59 | default = ["chrono", "r2d2", "dynamic-schema"] 60 | chrono = ["chrono-time", "diesel/chrono"] 61 | r2d2 = ["diesel/r2d2"] 62 | dynamic-schema = ["diesel-dynamic-schema"] 63 | gst = [] 64 | 65 | -------------------------------------------------------------------------------- /src/oracle/query_builder/mod.rs: -------------------------------------------------------------------------------- 1 | use super::backend::Oracle; 2 | use super::backend::OracleDualForEmptySelectClause; 3 | 4 | use diesel::query_builder::NoFromClause; 5 | use diesel::query_builder::QueryBuilder; 6 | use diesel::query_builder::QueryFragment; 7 | use diesel::result::Error as DieselError; 8 | 9 | mod alias; 10 | mod exists; 11 | mod limit_offset; 12 | mod returning; 13 | 14 | pub use self::alias::{Alias, As}; 15 | pub use self::returning::BindColumnList; 16 | 17 | /// The Oracle query builder 18 | #[derive(Default)] 19 | pub struct OciQueryBuilder { 20 | pub(crate) sql: String, 21 | bind_idx: u32, 22 | } 23 | 24 | impl OciQueryBuilder { 25 | /// Constructs a new query builder with an empty query 26 | pub fn new() -> Self { 27 | OciQueryBuilder { 28 | sql: String::new(), 29 | bind_idx: 0, 30 | } 31 | } 32 | } 33 | 34 | impl QueryBuilder for OciQueryBuilder { 35 | fn push_sql(&mut self, sql: &str) { 36 | self.sql.push_str(sql); 37 | } 38 | 39 | fn push_identifier(&mut self, identifier: &str) -> Result<(), DieselError> { 40 | // TODO: check if there is a better way for escaping strings 41 | self.push_sql("\""); 42 | self.push_sql(&identifier.replace('`', "``").to_uppercase()); 43 | self.push_sql("\""); 44 | Ok(()) 45 | } 46 | 47 | fn push_bind_param(&mut self) { 48 | let sql = format!(":in{}", self.bind_idx); 49 | self.bind_idx += 1; 50 | self.push_sql(&sql); 51 | } 52 | 53 | fn finish(self) -> String { 54 | self.sql 55 | } 56 | } 57 | 58 | impl QueryFragment for NoFromClause { 59 | fn walk_ast(&self, mut out: diesel::query_builder::AstPass) -> diesel::QueryResult<()> { 60 | out.push_sql(" FROM DUAL "); 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/oracle/query_builder/returning.rs: -------------------------------------------------------------------------------- 1 | use crate::oracle::{backend::OracleReturningClause, Oracle}; 2 | use diesel::query_builder::{AstPass, QueryFragment, ReturningClause}; 3 | use diesel::Column; 4 | 5 | impl QueryFragment for ReturningClause 6 | where 7 | Expr: BindColumnList + QueryFragment, 8 | { 9 | fn walk_ast<'b>( 10 | &'b self, 11 | mut out: diesel::query_builder::AstPass<'_, 'b, Oracle>, 12 | ) -> diesel::QueryResult<()> { 13 | out.push_sql(" RETURNING "); 14 | self.0.walk_ast(out.reborrow())?; 15 | out.push_sql(" INTO "); 16 | Expr::bind_column_list(out)?; 17 | Ok(()) 18 | } 19 | } 20 | 21 | /// A helper trait to collect columns into output binds 22 | /// to support RETURNING clauses for Insert/Update/Delete statements 23 | pub trait BindColumnList { 24 | #[doc(hidden)] 25 | fn bind_column_list(out: AstPass) -> diesel::QueryResult<()>; 26 | } 27 | 28 | macro_rules! impl_bind_column_list { 29 | ($( 30 | $Tuple:tt { 31 | $(($idx:tt) -> $T:ident, $ST:ident, $TT:ident,)+ 32 | } 33 | )+) => { 34 | $( 35 | impl<$($T: Column,)+> BindColumnList for ($($T,)+) { 36 | #[allow(unused_assignments)] 37 | fn bind_column_list(mut out: AstPass) -> diesel::QueryResult<()> { 38 | let mut needs_comma = false; 39 | $( 40 | if needs_comma { 41 | out.push_sql(", "); 42 | } 43 | let placeholder = format!(":out{}", $idx); 44 | out.push_sql(&placeholder); 45 | needs_comma = true; 46 | )+ 47 | Ok(()) 48 | } 49 | } 50 | )+ 51 | } 52 | } 53 | 54 | diesel_derives::__diesel_for_each_tuple!(impl_bind_column_list); 55 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | dist: bionic 3 | sudo: required 4 | services: 5 | - docker 6 | cache: 7 | cargo: true 8 | directories: 9 | - download/ 10 | env: 11 | - OCI_LIB_DIR=$HOME/instantclient_12_2/ LD_LIBRARY_PATH=$HOME/instantclient_12_2/ OCI_DATABASE_URL=oci://SYSTEM/travis@//localhost:1521/travis 12 | before_install: 13 | - mkdir download || true 14 | - wget --http-user "$SSO_GIGA_USER" --http-password "$SSO_PWD" https://support.giga-infosystems.com/3rdparty/oracle/instantclient-sdk-linux.x64-12.2.0.1.0.zip -q -O "./download/instantclient-sdk-linux.x64-12.2.0.1.0.zip" && unzip "./download/instantclient-sdk-linux.x64-12.2.0.1.0.zip" 15 | - wget --http-user "$SSO_GIGA_USER" --http-password "$SSO_PWD" https://support.giga-infosystems.com/3rdparty/oracle/instantclient-basiclite-linux.x64-12.2.0.1.0.zip -q -O "./download/instantclient-basiclite-linux.x64-12.2.0.1.0.zip" && unzip "./download/instantclient-basiclite-linux.x64-12.2.0.1.0.zip" 16 | - sudo cp -R instantclient_12_2/* /usr/lib/ 17 | - sudo ln -s /usr/lib/libclntsh.so.12.1 /usr/lib/libclntsh.so 18 | - sudo ln -s /usr/lib/libocci.so.12.1 /usr/lib/libocci.so 19 | # - if ! [ -f ./download/linuxx64_12201_database.zip ]; then 20 | # wget --http-user "$SSO_GIGA_USER" --http-password "$SSO_PWD" https://support.giga-infosystems.com/3rdparty/oracle/V839960-01.zip -q -O "./download/linuxx64_12201_database.zip"; 21 | # fi 22 | # - cp "./download/linuxx64_12201_database.zip" ".travis/ora/12.2.0.1/linuxx64_12201_database.zip" 23 | - echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin 24 | # - .travis/ora/buildDocker.sh -v 12.2.0.1 -s 25 | # - docker tag oracle/database:12.2.0.1-se2 pgab/oracle:12.2.0.1-se2 26 | - docker pull pgab/oracle:12.2.0.1-se2 27 | - docker run -d -p 127.0.0.1:1521:1521 -e ORACLE_SID=travis -e ORACLE_PWD=travis -e ORACLE_CHARACTERSET=UTF8 pgab/oracle:12.2.0.1-se2 28 | before_script: 29 | - export PATH=$HOME/.local/bin:$PATH 30 | - ./wait-for-oralce.sh 31 | rust: 32 | - nightly-2019-11-16 33 | script: 34 | - | 35 | cargo test --package diesel-oci -- --exact --test-threads=1 -------------------------------------------------------------------------------- /src/oracle/query_builder/alias.rs: -------------------------------------------------------------------------------- 1 | use super::Oracle; 2 | 3 | use diesel::query_builder::{AstPass, QueryFragment, QueryId}; 4 | use diesel::result::QueryResult; 5 | 6 | /// Create an alias for a given expression 7 | /// 8 | /// This is a helper to provide aliasing support while it's not in diesel itself 9 | /// It probably needs improvements before something like this can be merged to diesel 10 | pub trait Alias: Sized { 11 | /// Create an alias with the given name 12 | fn alias(self, alias: String) -> As; 13 | } 14 | 15 | impl Alias for T { 16 | fn alias(self, alias: String) -> As { 17 | As { query: self, alias } 18 | } 19 | } 20 | 21 | /// Return type of [`Alias::alias`] 22 | #[derive(Debug, Clone, QueryId)] 23 | pub struct As { 24 | query: T, 25 | alias: String, 26 | } 27 | 28 | use diesel::expression::Expression; 29 | impl Expression for As { 30 | type SqlType = T::SqlType; 31 | } 32 | 33 | use diesel::expression::AppearsOnTable; 34 | impl AppearsOnTable for As {} 35 | 36 | use diesel::expression::SelectableExpression; 37 | impl SelectableExpression for As where T: SelectableExpression {} 38 | 39 | use diesel::expression::ValidGrouping; 40 | 41 | impl ValidGrouping for As 42 | where 43 | T: ValidGrouping, 44 | { 45 | type IsAggregate = T::IsAggregate; 46 | } 47 | 48 | impl QueryFragment for As 49 | where 50 | T: QueryFragment, 51 | { 52 | fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Oracle>) -> QueryResult<()> { 53 | self.query.walk_ast(out.reborrow())?; 54 | out.push_sql(" "); 55 | out.push_identifier(&self.alias) 56 | } 57 | } 58 | 59 | impl QueryFragment 60 | for diesel::query_source::Alias 61 | where 62 | S: diesel::query_source::AliasSource, 63 | S::Target: QueryFragment, 64 | { 65 | fn walk_ast<'b>(&'b self, mut pass: AstPass<'_, 'b, Oracle>) -> QueryResult<()> { 66 | self.source.target().walk_ast(pass.reborrow())?; 67 | pass.push_sql(" "); 68 | pass.push_identifier(S::NAME)?; 69 | Ok(()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/oracle/backend.rs: -------------------------------------------------------------------------------- 1 | use diesel::backend::*; 2 | use diesel::sql_types::TypeMetadata; 3 | 4 | use super::connection::bind_collector::OracleBindCollector; 5 | use super::connection::OracleValue; 6 | use super::query_builder::OciQueryBuilder; 7 | use super::types::OciTypeMetadata; 8 | 9 | /// The Oracle backend type 10 | #[derive(Debug, Hash, PartialEq, Eq, Default)] 11 | pub struct Oracle; 12 | 13 | impl Backend for Oracle { 14 | type QueryBuilder = OciQueryBuilder; 15 | type BindCollector<'a> = OracleBindCollector<'a>; 16 | type RawValue<'a> = OracleValue<'a>; 17 | } 18 | 19 | impl TypeMetadata for Oracle { 20 | type TypeMetadata = OciTypeMetadata; 21 | type MetadataLookup = (); 22 | } 23 | 24 | impl TrustedBackend for Oracle {} 25 | impl DieselReserveSpecialization for Oracle {} 26 | 27 | impl SqlDialect for Oracle { 28 | type ReturningClause = OracleReturningClause; 29 | 30 | type OnConflictClause = sql_dialect::on_conflict_clause::DoesNotSupportOnConflictClause; 31 | 32 | type InsertWithDefaultKeyword = sql_dialect::default_keyword_for_insert::IsoSqlDefaultKeyword; 33 | type BatchInsertSupport = OracleStyleBatchInsert; 34 | type DefaultValueClauseForInsert = sql_dialect::default_value_clause::AnsiDefaultValueClause; 35 | 36 | type EmptyFromClauseSyntax = OracleDualForEmptySelectClause; 37 | type ExistsSyntax = OracleExistsSyntax; 38 | 39 | type ConcatClause = sql_dialect::concat_clause::ConcatWithPipesClause; 40 | type ArrayComparison = sql_dialect::array_comparison::AnsiSqlArrayComparison; 41 | type SelectStatementSyntax = sql_dialect::select_statement_syntax::AnsiSqlSelectStatement; 42 | type AliasSyntax = OracleAliasSyntax; 43 | 44 | type WindowFrameClauseGroupSupport = 45 | sql_dialect::window_frame_clause_group_support::NoGroupWindowFrameUnit; 46 | 47 | type WindowFrameExclusionSupport = 48 | sql_dialect::window_frame_exclusion_support::NoFrameFrameExclusionSupport; 49 | 50 | type AggregateFunctionExpressions = 51 | sql_dialect::aggregate_function_expressions::NoAggregateFunctionExpressions; 52 | 53 | type BuiltInWindowFunctionRequireOrder = 54 | sql_dialect::built_in_window_function_require_order::NoOrderRequired; 55 | } 56 | 57 | pub struct OracleStyleBatchInsert; 58 | pub struct OracleReturningClause; 59 | pub struct OracleDualForEmptySelectClause; 60 | pub struct OracleExistsSyntax; 61 | pub struct OracleAliasSyntax; 62 | -------------------------------------------------------------------------------- /src/oracle/types/chrono_date_time.rs: -------------------------------------------------------------------------------- 1 | extern crate chrono_time as chrono; 2 | use chrono::NaiveTime; 3 | use diesel::deserialize::FromSql; 4 | use diesel::serialize::{IsNull, Output, ToSql}; 5 | use diesel::sql_types::*; 6 | use std::error::Error; 7 | 8 | use crate::oracle::backend::Oracle; 9 | 10 | use crate::oracle::connection::bind_collector::BindValue; 11 | 12 | use self::chrono::{NaiveDate, NaiveDateTime}; 13 | 14 | use super::super::connection::{InnerValue, OracleValue}; 15 | 16 | impl FromSql for NaiveDateTime { 17 | fn from_sql(bytes: OracleValue<'_>) -> Result> { 18 | match bytes.inner { 19 | InnerValue::Raw { raw_value, .. } => { 20 | ::from_sql(raw_value).map_err(Into::into) 21 | } 22 | InnerValue::Timestamp(t) => Ok(t), 23 | _ => Err("Invalid timestamp value".into()), 24 | } 25 | } 26 | } 27 | 28 | impl ToSql for NaiveDateTime { 29 | fn to_sql<'b>( 30 | &'b self, 31 | out: &mut Output<'b, '_, Oracle>, 32 | ) -> Result> { 33 | out.set_value(BindValue::Borrowed(self)); 34 | Ok(IsNull::No) 35 | } 36 | } 37 | 38 | impl FromSql for NaiveDate { 39 | fn from_sql(bytes: OracleValue<'_>) -> Result> { 40 | match bytes.inner { 41 | InnerValue::Raw { raw_value, .. } => { 42 | ::from_sql(raw_value).map_err(Into::into) 43 | } 44 | InnerValue::Date(d) => Ok(d), 45 | _ => Err("Invalid value for date".into()), 46 | } 47 | } 48 | } 49 | 50 | impl ToSql for NaiveDate { 51 | fn to_sql<'b>( 52 | &'b self, 53 | out: &mut Output<'b, '_, Oracle>, 54 | ) -> Result> { 55 | out.set_value(BindValue::Borrowed(self)); 56 | Ok(IsNull::No) 57 | } 58 | } 59 | 60 | impl ToSql for NaiveTime { 61 | fn to_sql<'b>(&'b self, _out: &mut Output<'b, '_, Oracle>) -> diesel::serialize::Result { 62 | unimplemented!("No time support in the oracle crate yet") 63 | } 64 | } 65 | 66 | impl FromSql for NaiveTime { 67 | fn from_sql( 68 | _bytes: ::RawValue<'_>, 69 | ) -> diesel::deserialize::Result { 70 | unimplemented!("No time support in the oracle crate yet") 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/oracle/connection/bind_collector.rs: -------------------------------------------------------------------------------- 1 | use crate::oracle::types::OciTypeMetadata; 2 | use crate::oracle::OciDataType; 3 | use crate::oracle::Oracle; 4 | use diesel::query_builder::BindCollector; 5 | use diesel::sql_types::HasSqlType; 6 | use std::ops::Deref; 7 | 8 | #[derive(Default)] 9 | pub struct OracleBindCollector<'a> { 10 | pub(crate) binds: Vec<(String, BindValue<'a>)>, 11 | } 12 | 13 | pub enum BindValue<'a> { 14 | Owned(Box), 15 | Borrowed(&'a dyn oracle::sql_type::ToSql), 16 | NotSet(OciDataType), 17 | } 18 | 19 | impl<'a> Deref for BindValue<'a> { 20 | type Target = dyn oracle::sql_type::ToSql + 'a; 21 | 22 | fn deref(&self) -> &Self::Target { 23 | match self { 24 | BindValue::Owned(b) => &**b, 25 | BindValue::Borrowed(b) => *b, 26 | BindValue::NotSet(d) => default_value(d), 27 | } 28 | } 29 | } 30 | 31 | fn default_value(d: &'_ OciDataType) -> &'static dyn oracle::sql_type::ToSql { 32 | match d { 33 | OciDataType::Bool | OciDataType::SmallInt | OciDataType::Integer | OciDataType::BigInt => { 34 | &oracle::sql_type::OracleType::Number(0, 0) 35 | } 36 | OciDataType::Float => &oracle::sql_type::OracleType::BinaryFloat, 37 | OciDataType::Double => &oracle::sql_type::OracleType::BinaryDouble, 38 | OciDataType::Text => &oracle::sql_type::OracleType::Varchar2(0), 39 | OciDataType::Binary => &oracle::sql_type::OracleType::BLOB, 40 | OciDataType::Date => &oracle::sql_type::OracleType::Date, 41 | OciDataType::Time => unimplemented!("No time support in the oracle crate yet"), 42 | OciDataType::Timestamp => &oracle::sql_type::OracleType::Timestamp(0), 43 | OciDataType::IntervalYM => &oracle::sql_type::OracleType::IntervalYM(2), 44 | OciDataType::IntervalDS => &oracle::sql_type::OracleType::IntervalDS(2, 6), 45 | } 46 | } 47 | 48 | impl<'a> BindCollector<'a, Oracle> for OracleBindCollector<'a> { 49 | type Buffer = BindValue<'a>; 50 | 51 | fn push_bound_value( 52 | &mut self, 53 | bind: &'a U, 54 | metadata_lookup: &mut (), 55 | ) -> diesel::QueryResult<()> 56 | where 57 | Oracle: HasSqlType, 58 | U: diesel::serialize::ToSql + ?Sized + 'a, 59 | { 60 | let OciTypeMetadata { tpe: ty } = Oracle::metadata(metadata_lookup); 61 | 62 | let out = { 63 | let out = BindValue::NotSet(ty); 64 | let mut out = diesel::serialize::Output::::new(out, metadata_lookup); 65 | 66 | bind.to_sql(&mut out).unwrap(); 67 | out.into_inner() 68 | }; 69 | let len = self.binds.len(); 70 | 71 | self.binds.push((format!("in{}", len), out)); 72 | 73 | Ok(()) 74 | } 75 | 76 | fn push_null_value( 77 | &mut self, 78 | metadata: ::TypeMetadata, 79 | ) -> diesel::prelude::QueryResult<()> { 80 | let len = self.binds.len(); 81 | self.binds 82 | .push((format!("in{}", len), BindValue::NotSet(metadata.tpe))); 83 | Ok(()) 84 | } 85 | } 86 | 87 | impl<'a, T> From for BindValue<'a> 88 | where 89 | T: oracle::sql_type::ToSql + 'static, 90 | { 91 | fn from(t: T) -> Self { 92 | Self::Owned(Box::new(t)) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: 3 | pull_request: 4 | types: [opened, synchronize, reopened] 5 | push: 6 | branches: 7 | - master 8 | 9 | # See: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency. 10 | # This will ensure that only one commit will be running tests at a time on each PR. 11 | concurrency: 12 | group: ${{ github.ref }}-${{ github.workflow }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | rustfmt: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: dtolnay/rust-toolchain@stable 21 | with: 22 | components: rustfmt 23 | - name: Rustfmt check 24 | run: cargo +stable fmt --all -- --check 25 | clippy: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: dtolnay/rust-toolchain@stable 30 | with: 31 | components: clippy 32 | - name: Clippy 33 | run: cargo +stable clippy --all-features 34 | tests: 35 | needs: 36 | - rustfmt 37 | - clippy 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | os: [ubuntu-22.04] 42 | rust_toolchain: ["stable", "beta", "nightly"] 43 | 44 | runs-on: ${{ matrix.os }} 45 | 46 | services: 47 | oracle: 48 | image: gvenzl/oracle-xe:latest 49 | env: 50 | ORACLE_PASSWORD: sys_passwd 51 | APP_USER: diesel_oci 52 | APP_USER_PASSWORD: diesel_oci 53 | ports: 54 | - 1521:1521 55 | options: >- 56 | --health-cmd healthcheck.sh 57 | --health-interval 10s 58 | --health-timeout 5s 59 | --health-retries 10 60 | steps: 61 | - uses: actions/checkout@v2 62 | 63 | - name: Install dependencies 64 | run: | 65 | sudo apt update 66 | sudo apt-get install -y libaio1 67 | 68 | - name: Install the latest Oracle instant client 69 | run: | 70 | curl -Lo basic.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-basic-linuxx64.zip 71 | curl -Lo sqlplus.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-sqlplus-linuxx64.zip 72 | mkdir linux 73 | unzip -o basic.zip -d linux 74 | unzip -o sqlplus.zip -d linux 75 | IC_DIR=$PWD/$(ls -d linux/instantclient*) 76 | mkdir windows 77 | echo LD_LIBRARY_PATH=$IC_DIR:$LD_LIBRARY_PATH >> $GITHUB_ENV 78 | echo $IC_DIR >> $GITHUB_PATH 79 | - name: Get the Oracle container IP address 80 | env: 81 | ORACLE_SERVICE_ID: ${{ job.services.oracle.id }} 82 | run: | 83 | ORACLE_IP_ADDRESS=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' $ORACLE_SERVICE_ID) 84 | if test -z "$ORACLE_IP_ADDRESS"; then 85 | echo "Cannot get ORACLE_IP_ADDRESS." 86 | docker inspect $ORACLE_SERVICE_ID 87 | exit 1 88 | fi 89 | echo TWO_TASK=//$ORACLE_IP_ADDRESS:1521/XEPDB1 >> $GITHUB_ENV 90 | echo ODPIC_TEST_CONNECT_STRING=//$ORACLE_IP_ADDRESS:1521/XEPDB1 >> $GITHUB_ENV 91 | echo NLS_LANG=AMERICAN_AMERICA.AL32UTF8 >> $GITHUB_ENV 92 | echo OCI_DATABASE_URL=oracle://diesel_oci:diesel_oci@$ORACLE_IP_ADDRESS:1521/XEPDB1 >> $GITHUB_ENV 93 | - uses: dtolnay/rust-toolchain@master 94 | with: 95 | toolchain: ${{ matrix.rust_toolchain }} 96 | 97 | - name: cargo test 98 | run: cargo +${{ matrix.rust_toolchain }} test --features "r2d2 chrono dynamic-schema" -- --test-threads=1 99 | -------------------------------------------------------------------------------- /src/oracle/connection/row.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::oracle::backend::Oracle; 4 | use diesel::row::{self, Row, RowIndex, RowSealed}; 5 | 6 | use super::oracle_value::OracleValue; 7 | 8 | pub struct OciRow { 9 | row: InnerOciRow, 10 | column_infos: Rc>, 11 | } 12 | 13 | enum InnerOciRow { 14 | Row(oracle::Row), 15 | Values(Vec>>), 16 | } 17 | 18 | impl OciRow { 19 | pub fn new(row: oracle::Row, column_infos: Rc>) -> Self { 20 | OciRow { 21 | row: InnerOciRow::Row(row), 22 | column_infos, 23 | } 24 | } 25 | 26 | pub fn new_from_value(values: Vec>>) -> Self { 27 | Self { 28 | row: InnerOciRow::Values(values), 29 | column_infos: Rc::new(Vec::new()), 30 | } 31 | } 32 | } 33 | 34 | impl RowIndex for OciRow { 35 | fn idx(&self, idx: usize) -> Option { 36 | if idx < self.row.len() { 37 | Some(idx) 38 | } else { 39 | None 40 | } 41 | } 42 | } 43 | 44 | impl<'a> RowIndex<&'a str> for OciRow { 45 | fn idx(&self, field_name: &'a str) -> Option { 46 | self.column_infos 47 | .iter() 48 | .enumerate() 49 | .find(|(_, c)| c.name() == field_name) 50 | .map(|(idx, _)| idx) 51 | } 52 | } 53 | 54 | impl RowSealed for OciRow {} 55 | 56 | impl<'a> Row<'a, Oracle> for OciRow { 57 | type InnerPartialRow = Self; 58 | type Field<'f> 59 | = OciField<'f> 60 | where 61 | 'a: 'f, 62 | Self: 'f; 63 | 64 | fn field_count(&self) -> usize { 65 | self.row.len() 66 | } 67 | 68 | fn get<'row, I>(&'row self, idx: I) -> Option> 69 | where 70 | 'a: 'row, 71 | Self: diesel::row::RowIndex, 72 | { 73 | let idx = self.idx(idx)?; 74 | Some(OciField { 75 | field_value: self.row.value_at(idx, &self.column_infos), 76 | column_info: self.column_infos.get(idx), 77 | }) 78 | } 79 | 80 | fn partial_row( 81 | &self, 82 | range: std::ops::Range, 83 | ) -> diesel::row::PartialRow<'_, Self::InnerPartialRow> { 84 | diesel::row::PartialRow::new(self, range) 85 | } 86 | } 87 | 88 | pub struct OciField<'a> { 89 | field_value: Option>, 90 | column_info: Option<&'a oracle::ColumnInfo>, 91 | } 92 | 93 | impl<'a> row::Field<'a, Oracle> for OciField<'a> { 94 | fn field_name(&self) -> Option<&'a str> { 95 | self.column_info.map(|c| c.name()) 96 | } 97 | 98 | fn value(&self) -> Option> { 99 | self.field_value.clone() 100 | } 101 | 102 | fn is_null(&self) -> bool { 103 | self.field_value.is_none() 104 | } 105 | } 106 | 107 | impl InnerOciRow { 108 | fn value_at(&self, idx: usize, col_infos: &[oracle::ColumnInfo]) -> Option> { 109 | match self { 110 | InnerOciRow::Row(row) => { 111 | let sql = &row.sql_values()[idx]; 112 | if sql.is_null().unwrap_or(true) { 113 | None 114 | } else { 115 | let tpe = col_infos[idx].oracle_type().clone(); 116 | Some(OracleValue::new(sql, tpe)) 117 | } 118 | } 119 | InnerOciRow::Values(ref v) => v[idx].clone(), 120 | } 121 | } 122 | 123 | fn len(&self) -> usize { 124 | match self { 125 | InnerOciRow::Row(row) => row.sql_values().len(), 126 | InnerOciRow::Values(v) => v.len(), 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/oracle/query_builder/limit_offset.rs: -------------------------------------------------------------------------------- 1 | use crate::oracle::Oracle; 2 | use diesel::query_builder::{AstPass, QueryFragment}; 3 | use diesel::query_builder::{BoxedLimitOffsetClause, IntoBoxedClause, LimitOffsetClause}; 4 | use diesel::query_builder::{LimitClause, NoLimitClause}; 5 | use diesel::query_builder::{NoOffsetClause, OffsetClause}; 6 | use diesel::result::QueryResult; 7 | 8 | impl QueryFragment for LimitOffsetClause { 9 | fn walk_ast(&self, _out: AstPass) -> QueryResult<()> { 10 | Ok(()) 11 | } 12 | } 13 | 14 | impl QueryFragment for LimitOffsetClause, NoOffsetClause> 15 | where 16 | L: QueryFragment, 17 | { 18 | fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Oracle>) -> QueryResult<()> { 19 | out.push_sql(" FETCH FIRST "); 20 | self.limit_clause.0.walk_ast(out.reborrow())?; 21 | out.push_sql(" ROWS ONLY "); 22 | Ok(()) 23 | } 24 | } 25 | 26 | impl QueryFragment for LimitOffsetClause> 27 | where 28 | O: QueryFragment, 29 | { 30 | fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Oracle>) -> QueryResult<()> { 31 | out.push_sql(" OFFSET "); 32 | self.offset_clause.0.walk_ast(out.reborrow())?; 33 | out.push_sql(" ROWS "); 34 | Ok(()) 35 | } 36 | } 37 | 38 | impl QueryFragment for LimitOffsetClause, OffsetClause> 39 | where 40 | L: QueryFragment, 41 | O: QueryFragment, 42 | { 43 | fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Oracle>) -> QueryResult<()> { 44 | out.push_sql(" OFFSET "); 45 | self.offset_clause.0.walk_ast(out.reborrow())?; 46 | out.push_sql(" ROWS FETCH NEXT "); 47 | self.limit_clause.0.walk_ast(out.reborrow())?; 48 | out.push_sql(" ROWS ONLY "); 49 | Ok(()) 50 | } 51 | } 52 | 53 | impl<'a> QueryFragment for BoxedLimitOffsetClause<'a, Oracle> { 54 | fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Oracle>) -> QueryResult<()> { 55 | match (self.limit.as_ref(), self.offset.as_ref()) { 56 | (Some(limit), Some(offset)) => { 57 | out.push_sql(" OFFSET "); 58 | offset.walk_ast(out.reborrow())?; 59 | out.push_sql(" ROWS FETCH NEXT "); 60 | limit.walk_ast(out.reborrow())?; 61 | out.push_sql(" ROWS ONLY "); 62 | } 63 | (Some(limit), None) => { 64 | out.push_sql(" FETCH FIRST "); 65 | limit.walk_ast(out.reborrow())?; 66 | out.push_sql(" ROWS ONLY "); 67 | } 68 | (None, Some(offset)) => { 69 | out.push_sql(" OFFSET "); 70 | offset.walk_ast(out.reborrow())?; 71 | out.push_sql(" ROWS "); 72 | } 73 | (None, None) => {} 74 | } 75 | Ok(()) 76 | } 77 | } 78 | 79 | impl<'a> IntoBoxedClause<'a, Oracle> for LimitOffsetClause { 80 | type BoxedClause = BoxedLimitOffsetClause<'a, Oracle>; 81 | 82 | fn into_boxed(self) -> Self::BoxedClause { 83 | BoxedLimitOffsetClause { 84 | limit: None, 85 | offset: None, 86 | } 87 | } 88 | } 89 | 90 | impl<'a, L> IntoBoxedClause<'a, Oracle> for LimitOffsetClause, NoOffsetClause> 91 | where 92 | L: QueryFragment + Send + 'a, 93 | { 94 | type BoxedClause = BoxedLimitOffsetClause<'a, Oracle>; 95 | 96 | fn into_boxed(self) -> Self::BoxedClause { 97 | BoxedLimitOffsetClause { 98 | limit: Some(Box::new(self.limit_clause.0)), 99 | offset: None, 100 | } 101 | } 102 | } 103 | 104 | impl<'a, O> IntoBoxedClause<'a, Oracle> for LimitOffsetClause> 105 | where 106 | O: QueryFragment + Send + 'a, 107 | { 108 | type BoxedClause = BoxedLimitOffsetClause<'a, Oracle>; 109 | 110 | fn into_boxed(self) -> Self::BoxedClause { 111 | BoxedLimitOffsetClause { 112 | limit: None, 113 | offset: Some(Box::new(self.offset_clause.0)), 114 | } 115 | } 116 | } 117 | 118 | impl<'a, L, O> IntoBoxedClause<'a, Oracle> for LimitOffsetClause, OffsetClause> 119 | where 120 | L: QueryFragment + Send + 'a, 121 | O: QueryFragment + Send + 'a, 122 | { 123 | type BoxedClause = BoxedLimitOffsetClause<'a, Oracle>; 124 | 125 | fn into_boxed(self) -> Self::BoxedClause { 126 | BoxedLimitOffsetClause { 127 | limit: Some(Box::new(self.limit_clause.0)), 128 | offset: Some(Box::new(self.offset_clause.0)), 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/test/dynamic_select.rs: -------------------------------------------------------------------------------- 1 | extern crate diesel_dynamic_schema; 2 | use self::diesel_dynamic_schema::dynamic_value::*; 3 | use self::diesel_dynamic_schema::DynamicSelectClause; 4 | use crate::oracle::{OciDataType, Oracle, OracleValue}; 5 | use diesel::deserialize::*; 6 | use diesel::prelude::*; 7 | use diesel::sql_query; 8 | use diesel::sql_types::*; 9 | 10 | #[derive(PartialEq, Debug)] 11 | enum MyDynamicValue { 12 | String(String), 13 | Integer(i32), 14 | Null, 15 | } 16 | 17 | impl FromSql for MyDynamicValue { 18 | fn from_sql(value: OracleValue) -> Result { 19 | match value.value_type() { 20 | OciDataType::Integer => { 21 | >::from_sql(value).map(MyDynamicValue::Integer) 22 | } 23 | OciDataType::Text => { 24 | >::from_sql(value).map(MyDynamicValue::String) 25 | } 26 | e => Err(format!("Unknown data type: {:?}", e).into()), 27 | } 28 | } 29 | 30 | fn from_nullable_sql(value: Option) -> Result { 31 | if let Some(value) = value { 32 | Self::from_sql(value) 33 | } else { 34 | Ok(MyDynamicValue::Null) 35 | } 36 | } 37 | } 38 | 39 | #[test] 40 | fn dynamic_query() { 41 | let mut connection = super::init_testing(); 42 | let _ = sql_query("DROP TABLE my_users").execute(&mut connection); 43 | sql_query("CREATE TABLE my_users (id NUMBER(10) NOT NULL PRIMARY KEY, name VARCHAR(50) NOT NULL, hair_color VARCHAR(50))") 44 | .execute(&mut connection) 45 | .unwrap(); 46 | sql_query( 47 | "INSERT ALL 48 | INTO my_users (id, name) VALUES (3, 'Sean') 49 | INTO my_users (id, name) VALUES (2, 'Tess') 50 | SELECT * FROM DUAL", 51 | ) 52 | .execute(&mut connection) 53 | .unwrap(); 54 | 55 | let users = diesel_dynamic_schema::table("my_users"); 56 | let id = users.column::("id"); 57 | let name = users.column::("name"); 58 | let hair_color = users.column::, _>("hair_color"); 59 | 60 | let mut select = DynamicSelectClause::new(); 61 | 62 | select.add_field(id); 63 | select.add_field(name); 64 | select.add_field(hair_color); 65 | 66 | let actual_data: Vec>> = 67 | users.select(select).load(&mut connection).unwrap(); 68 | 69 | assert_eq!( 70 | actual_data[0]["NAME"], 71 | MyDynamicValue::String("Sean".into()) 72 | ); 73 | assert_eq!( 74 | actual_data[0][1], 75 | NamedField { 76 | name: "NAME".into(), 77 | value: MyDynamicValue::String("Sean".into()) 78 | } 79 | ); 80 | assert_eq!( 81 | actual_data[1]["NAME"], 82 | MyDynamicValue::String("Tess".into()) 83 | ); 84 | assert_eq!( 85 | actual_data[1][1], 86 | NamedField { 87 | name: "NAME".into(), 88 | value: MyDynamicValue::String("Tess".into()) 89 | } 90 | ); 91 | assert_eq!(actual_data[0]["HAIR_COLOR"], MyDynamicValue::Null); 92 | assert_eq!( 93 | actual_data[0][2], 94 | NamedField { 95 | name: "HAIR_COLOR".into(), 96 | value: MyDynamicValue::Null 97 | } 98 | ); 99 | assert_eq!(actual_data[1]["HAIR_COLOR"], MyDynamicValue::Null); 100 | assert_eq!( 101 | actual_data[1][2], 102 | NamedField { 103 | name: "HAIR_COLOR".into(), 104 | value: MyDynamicValue::Null 105 | } 106 | ); 107 | 108 | let mut select = DynamicSelectClause::new(); 109 | 110 | select.add_field(id); 111 | select.add_field(name); 112 | select.add_field(hair_color); 113 | 114 | let actual_data: Vec> = 115 | users.select(select).load(&mut connection).unwrap(); 116 | 117 | assert_eq!(actual_data[0][1], MyDynamicValue::String("Sean".into())); 118 | assert_eq!(actual_data[1][1], MyDynamicValue::String("Tess".into())); 119 | assert_eq!(actual_data[0][2], MyDynamicValue::Null); 120 | assert_eq!(actual_data[1][2], MyDynamicValue::Null); 121 | } 122 | 123 | #[test] 124 | fn mixed_value_query() { 125 | use diesel::dsl::sql; 126 | let mut connection = super::init_testing(); 127 | let _ = sql_query("DROP TABLE my_users").execute(&mut connection); 128 | sql_query("CREATE TABLE my_users (id NUMBER(10) NOT NULL PRIMARY KEY, name VARCHAR(50) NOT NULL, hair_color VARCHAR(50))") 129 | .execute(&mut connection) 130 | .unwrap(); 131 | 132 | sql_query( 133 | "INSERT ALL 134 | INTO my_users (id, name, hair_color) VALUES (42, 'Sean', 'black') 135 | INTO my_users (id, name, hair_color) VALUES (43, 'Tess', 'black') 136 | SELECT * FROM DUAL", 137 | ) 138 | .execute(&mut connection) 139 | .unwrap(); 140 | 141 | let users = diesel_dynamic_schema::table("my_users"); 142 | let id = users.column::("id"); 143 | 144 | let (id, row) = users 145 | .select((id, sql::("name, hair_color"))) 146 | .first::<(i32, DynamicRow>)>(&mut connection) 147 | .unwrap(); 148 | 149 | assert_eq!(id, 42); 150 | assert_eq!(row["NAME"], MyDynamicValue::String("Sean".into())); 151 | assert_eq!(row["HAIR_COLOR"], MyDynamicValue::String("black".into())); 152 | } 153 | -------------------------------------------------------------------------------- /src/oracle/types/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "dynamic-schema")] 2 | extern crate diesel_dynamic_schema; 3 | 4 | use super::backend::*; 5 | use diesel::sql_types::*; 6 | use std::hash::Hash; 7 | 8 | mod interval; 9 | mod primitives; 10 | 11 | /// Oracle specfic metadata about the type of a bind value 12 | #[derive(Clone, Copy)] 13 | pub struct OciTypeMetadata { 14 | pub(crate) tpe: OciDataType, 15 | } 16 | 17 | impl PartialEq for OciTypeMetadata { 18 | fn eq(&self, other: &Self) -> bool { 19 | self.tpe.eq(&other.tpe) 20 | } 21 | } 22 | 23 | impl Eq for OciTypeMetadata {} 24 | 25 | impl Hash for OciTypeMetadata { 26 | fn hash(&self, hasher: &mut H) { 27 | self.tpe.hash(hasher) 28 | } 29 | } 30 | 31 | /// A list of database side datatypes 32 | /// 33 | /// This list closely mirrors the datatypes expected by diesel 34 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 35 | #[non_exhaustive] 36 | pub enum OciDataType { 37 | /// A boolean 38 | Bool, 39 | /// A 2 byte integer 40 | SmallInt, 41 | /// A 4 byte integer 42 | Integer, 43 | /// A 8 byte integer 44 | BigInt, 45 | /// A 4 byte floating point values 46 | Float, 47 | /// A 8 byte floating point value 48 | Double, 49 | /// A text value 50 | Text, 51 | /// A binary value 52 | Binary, 53 | /// A date value 54 | Date, 55 | /// A time value 56 | Time, 57 | /// A timestamp value 58 | Timestamp, 59 | /// A time interval value with years and months 60 | IntervalYM, 61 | /// A time interval value with days, hours, minutes, seconds and possibly fractions of seconds 62 | IntervalDS, 63 | } 64 | 65 | impl HasSqlType for Oracle { 66 | fn metadata(_: &mut Self::MetadataLookup) -> Self::TypeMetadata { 67 | OciTypeMetadata { 68 | tpe: OciDataType::SmallInt, 69 | } 70 | } 71 | } 72 | 73 | impl HasSqlType for Oracle { 74 | fn metadata(_: &mut Self::MetadataLookup) -> Self::TypeMetadata { 75 | OciTypeMetadata { 76 | tpe: OciDataType::Integer, 77 | } 78 | } 79 | } 80 | 81 | impl HasSqlType for Oracle { 82 | fn metadata(_: &mut Self::MetadataLookup) -> Self::TypeMetadata { 83 | OciTypeMetadata { 84 | tpe: OciDataType::BigInt, 85 | } 86 | } 87 | } 88 | 89 | impl HasSqlType for Oracle { 90 | fn metadata(_: &mut Self::MetadataLookup) -> Self::TypeMetadata { 91 | OciTypeMetadata { 92 | tpe: OciDataType::Float, 93 | } 94 | } 95 | } 96 | 97 | impl HasSqlType for Oracle { 98 | fn metadata(_: &mut Self::MetadataLookup) -> Self::TypeMetadata { 99 | OciTypeMetadata { 100 | tpe: OciDataType::Double, 101 | } 102 | } 103 | } 104 | 105 | impl HasSqlType for Oracle { 106 | fn metadata(_: &mut Self::MetadataLookup) -> Self::TypeMetadata { 107 | OciTypeMetadata { 108 | tpe: OciDataType::Text, 109 | } 110 | } 111 | } 112 | 113 | impl HasSqlType for Oracle { 114 | fn metadata(_: &mut Self::MetadataLookup) -> Self::TypeMetadata { 115 | OciTypeMetadata { 116 | tpe: OciDataType::Binary, 117 | } 118 | } 119 | } 120 | 121 | impl HasSqlType