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
use std::ffi::CString;

use libc::{c_int, c_uint};

use ejdb_sys;

use super::Collection;
use Result;

impl<'db> Collection<'db> {
    /// Returns an index builder for the provided field in this collection.
    ///
    /// The index builder may be used to create, modify or delete various indices on
    /// collection fields. A field may have more than one index of different types.
    /// This may be useful if this field is heterogeneous, i.e. if it may hold different types
    /// of data.
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use ejdb::Database;
    /// let db = Database::open("/path/to/db").unwrap();
    /// let coll = db.collection("some_collection").unwrap();
    ///
    /// // create a case sensitive string index on "name" field in this collection
    /// coll.index("name").string(true).set().unwrap();
    /// ```
    ///
    /// See builder methods for more examples.
    pub fn index<S: Into<String>>(&self, key: S) -> Index {
        Index {
            coll: self,
            key: key.into(),
            flags: None,
        }
    }
}

/// A builder for an operation on an index of a certain field of an EJDB collection.
///
/// In EJDB every collection can have an index on the fields of its records. Indices can be
/// of one of three types:
///
/// * string (possibly case insensitive);
/// * number;
/// * array.
///
/// Indices can be set, dropped, optimized or rebuilt. Indices are stored in a separate file
/// from their collections and can speed up certain access patterns in queries. Naturally,
/// indices are specified for some field in collection records, so this structure is used to
/// configure indices for one specific field.
///
/// Index manipulation is done with this structure which provides a builder-like interface
/// to create, change properties or drop an index on one field. Since an index can't exist
/// separately from a collection, this structure is linked via a lifetime to its corresponding
/// collection object. An instance of this structure is obtained with `Collection::index()` method.
///
/// # Example
///
/// ```no_run
/// # use ejdb::Database;
/// let db = Database::open("/path/to/db").unwrap();
/// let coll = db.collection("some_collection").unwrap();
///
/// // create a string index on `name` field
/// coll.index("name").string(true).set().unwrap();
///
/// // create multiple indices on `coords` field
/// coll.index("coords").number().array().set().unwrap();
/// ```
pub struct Index<'coll, 'db: 'coll> {
    coll: &'coll Collection<'db>,
    key: String,
    flags: Option<c_uint>,
}

impl<'coll, 'db: 'coll> Index<'coll, 'db> {
    fn add_flags(self, flags: c_uint) -> Self {
        Index {
            coll: self.coll,
            key: self.key,
            flags: Some(self.flags.unwrap_or(0) | flags),
        }
    }

    /// Specifies that this index must be built over string values of this field.
    ///
    /// `case_sensitive` argument determines whether this index must take string case into account,
    /// `true` for case sensitive matching, `false` for the opposite.
    pub fn string(self, case_sensitive: bool) -> Self {
        self.add_flags(if case_sensitive {
            ejdb_sys::JBIDXSTR
        } else {
            ejdb_sys::JBIDXISTR
        })
    }

    /// Specifies that this index must be built over numeric values of this field.
    pub fn number(self) -> Self {
        self.add_flags(ejdb_sys::JBIDXNUM)
    }

    /// Specifies that this index must be built over array values of this field.
    pub fn array(self) -> Self {
        self.add_flags(ejdb_sys::JBIDXARR)
    }

    /// Creates one or more indices of specified types on this field.
    ///
    /// Panics if no types were specified before calling this method.
    pub fn set(self) -> Result<()> {
        self.check_type().execute()
    }

    /// Drops all indices on this field.
    pub fn drop_all(mut self) -> Result<()> {
        self.flags = Some(ejdb_sys::JBIDXDROPALL);
        self.execute()
    }

    /// Drops indices of the previously specified types on this field.
    ///
    /// Panics if no type has been set prior to calling this method.
    pub fn drop(self) -> Result<()> {
        self.add_flags(ejdb_sys::JBIDXDROP).check_type().execute()
    }

    /// Rebuilds indices of the previously specified types on this field from scratch.
    ///
    /// Panics if no type has been set prior to calling this method.
    pub fn rebuild(self) -> Result<()> {
        self.add_flags(ejdb_sys::JBIDXREBLD).check_type().execute()
    }

    /// Optimizes indices of the previously specified types on this field.
    ///
    /// Panics if no type has been set prior to calling this method.
    pub fn optimize(self) -> Result<()> {
        self.add_flags(ejdb_sys::JBIDXOP).check_type().execute()
    }

    fn check_type(self) -> Self {
        let flags = self.flags.expect("index type is not specified");
        assert!(
            [
                ejdb_sys::JBIDXSTR,
                ejdb_sys::JBIDXISTR,
                ejdb_sys::JBIDXNUM,
                ejdb_sys::JBIDXARR
            ]
                .iter()
                .any(|&f| flags & f != 0),
            "index type is not specified"
        );
        self
    }

    fn execute(self) -> Result<()> {
        let flags = self.flags.expect("index flags are not defined"); // should always unwrap
        let key = try!(CString::new(self.key).map_err(|_| "invalid key"));
        let result =
            unsafe { ejdb_sys::ejdbsetindex(self.coll.coll, key.as_ptr(), flags as c_int) };
        if result {
            Ok(())
        } else {
            self.coll.db.last_error("cannot update index")
        }
    }
}