1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// lib.rs
//
// This file is a part of the eXtremeDB source code
// Copyright (c) 2020 McObject LLC
// All Rights Reserved

//! `extremedb` is a wrapper for the [McObject]'s *e*X*treme*DB database
//! management system.
//!
//! This crate currently supports the *e*X*treme*DB SQL APIs, as well
//! as parts of the core functionality necessary for configuring the
//! *e*X*treme*DB runtime, creating and opening a database, and managing
//! database connections. Core data manipulation APIs are planned for a future
//! release.
//!
//! For more information on the *e*X*treme*DB database management system,
//! refer to the [documentation pages].
//!
//! [documentation pages]: https://www.mcobject.com/docs
//! [McObject]: https://www.mcobject.com
//!
//! # Building
//!
//! This crate depends on the `extremedb_sys` crate, which has some
//! prerequisites, as well as a few environment variables to be set.
//! Refer to the `extremedb_sys` crate reference for more information.
//!
//! # Optional Features
//!
//! - **`sql`** — SQL engine.
//! - **`rsql`** — Remote SQL engine (SQL server and client).
//! - **`sequences`** — Sequences (vertical storage).
//!
//! # SQL Example
//!
//! The example below demonstrates creating an in-memory database and using the
//! DDL and DML SQL statements to manipulate its schema and contents.
//!
//! ```
//! use extremedb::connection::Connection;
//! use extremedb::database::{Database, Params};
//! use extremedb::device::{Assignment, Device};
//! use extremedb::runtime::Runtime;
//! use extremedb::sql::engine::{Engine, LocalEngine};
//! use extremedb::Result;
//!
//! fn main() -> Result<()> {
//!     // Size of the memory region to allocate for the database.
//!     const DB_SIZE: usize = 1024 * 1024;
//!
//!     // eXtremeDB runtime must be started before any operations can be performed.
//!     let runtime = Runtime::start(vec![]);
//!
//! #     if runtime.info().multiprocess_access_supported() {
//! #         return Ok(());
//! #     }
//!     // This example creates a conventional memory device, and will not work with the
//!     // shared memory runtime.
//!     assert!(!runtime.info().multiprocess_access_supported());
//!
//!     // Initialize database parameters. SQL databases require these three
//!     // parameters to be explicitly set.
//!     let mut db_params = Params::new();
//!     db_params
//!         .ddl_dict_size(32768)
//!         .max_classes(100)
//!         .max_indexes(1000);
//!
//!     // Allocate one in-memory device for the database.
//!     let mut devs = vec![Device::new_mem_conv(Assignment::Database, DB_SIZE)?];
//!
//!     // Open the database using the parameters and devices defined above.
//!     let db = Database::open(&runtime, "test_db", None, &mut devs, db_params)?;
//!
//!     // Establish a connection to the database.
//!     let conn = Connection::new(&db)?;
//!
//!     // Create a local SQL engine on top of the database connection.
//!     let engine = LocalEngine::new(&conn)?;
//!
//!     // Use SQL DDL to create a new table.
//!     engine.execute_statement("CREATE TABLE TestTable(i integer, s string);", &[])?;
//!
//!     // Insert two rows into the table.
//!     engine.execute_statement("INSERT INTO TestTable(i, s) VALUES(?, ?);", &[&1, &"Hello"])?;
//!     engine.execute_statement("INSERT INTO TestTable(i, s) VALUES(?, ?);", &[&2, &"World"])?;
//!
//!     // Select the rows.
//!     let ds = engine.execute_query("SELECT i, s FROM TestTable ORDER BY i;", &[])?;
//!
//!     // The returned data source is not expected to be empty in case of a
//!     // SELECT statement.
//!     assert!(ds.is_some());
//!     let ds = ds.unwrap();
//!
//!     // Create a cursor to iterate over the data source.
//!     let mut cur = ds.cursor()?;
//!
//!     // Read the first row.
//!     {
//!         // Advance the cursor to the first row. It is positioned
//!         // before-the-first row initially.
//!         assert_eq!(cur.advance()?, true);
//!
//!         // Get reference to the record which the cursor points at.
//!         // Since cur.advance() above has returned true, the current record
//!         // must not be None.
//!         let rec = cur.current_record();
//!         assert!(rec.is_some());
//!         let rec = rec.unwrap();
//!
//!         // Get the references to the values in the columns at indexes 0 and 1.
//!         let i_val = rec.get_at(0)?;
//!         let s_val = rec.get_at(1)?;
//!
//!         // SQL value references must be explicitly converted to target types.
//!         assert_eq!(i_val.to_i64()?, 1);
//!         assert_eq!(s_val.to_string()?, "Hello");
//!     }
//!
//!     // The second row must be available.
//!     assert_eq!(cur.advance()?, true);
//!
//!     // No more rows are expected.
//!     assert_eq!(cur.advance()?, false);
//!
//!     Ok(())
//! }
//! ```

use extremedb_sys as exdb_sys;

use std::error;
use std::ffi::CStr;
use std::fmt::{Display, Error as FmtError, Formatter};
use std::str;

/// Core return codes (generated by bindgen from `MCO_RET` in *mco.h*).
pub use exdb_sys::MCO_RET_E_ as mco_ret;

pub mod connection;
pub mod database;
pub mod device;
pub mod dict;
pub mod runtime;

#[cfg(feature = "sql")]
pub mod sql;

mod util;

#[cfg(feature = "sql")]
use sql::{McoSqlStatusCode, SqlError};

/// Type alias for the *e*X*treme*DB status codes returned by most functions.
///
/// The actual codes are imported by `bindgen` from the *mco.h* header,
/// and are available in the [`mco_ret`] module.
///
/// [`mco_ret`]: ./mco_ret/index.html
pub type McoRetCode = mco_ret::Type;

/// An `Error`-compatible wrapper for the *e*X*treme*DB status codes.
#[derive(Debug)]
pub struct CoreError(McoRetCode);

impl CoreError {
    pub(crate) fn new(rc: McoRetCode) -> Self {
        CoreError(rc)
    }

    /// Returns the underlying *e*X*treme*DB status code (one of the constants
    /// defined in the [`mco_ret`] module).
    ///
    /// [`mco_ret`]: ./mco_ret/index.html
    pub fn code(&self) -> McoRetCode {
        self.0
    }

    fn strerror(&self) -> &'static str {
        let cstr = unsafe { CStr::from_ptr(exdb_sys::mco_strerror(self.0)) };
        let res = cstr.to_str();

        debug_assert!(res.is_ok(), "invalid string received from mco_strerror()");

        match res {
            Err(_) => "<malformed error string>",
            Ok(s) => s,
        }
    }
}

impl error::Error for CoreError {}

impl Display for CoreError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), FmtError> {
        write!(f, "{}", self.strerror())
    }
}

/// An error type for the core database management functions.
#[derive(Debug)]
pub enum Error {
    /// A core API error.
    Core(CoreError),

    /// An SQL API error.
    #[cfg(feature = "sql")]
    Sql(SqlError),
}

impl Error {
    pub(crate) fn new_core(rc: McoRetCode) -> Self {
        Error::Core(CoreError::new(rc))
    }

    #[cfg(feature = "sql")]
    pub(crate) fn new_sql(rc: McoSqlStatusCode) -> Self {
        Error::Sql(SqlError::new(rc))
    }
}

impl error::Error for Error {}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), FmtError> {
        match self {
            Error::Core(e) => e.fmt(f),

            #[cfg(feature = "sql")]
            Error::Sql(e) => e.fmt(f),
        }
    }
}

/// Type alias for `std::result::Result` used by the *e*X*treme*DB APIs.
pub type Result<T> = std::result::Result<T, Error>;

fn result_from_code(return_code: McoRetCode) -> Result<()> {
    match return_code {
        mco_ret::MCO_S_OK => Ok(()),
        _ => Err(Error::Core(CoreError::new(return_code))),
    }
}