├── .autotest ├── .gitignore ├── .ruby-version ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── ext └── unqlite │ ├── .dir-locals.el │ ├── extconf.rb │ ├── unqlite.c │ ├── unqlite_codes.c │ ├── unqlite_codes.h │ ├── unqlite_cursor.c │ ├── unqlite_cursor.h │ ├── unqlite_database.c │ ├── unqlite_database.h │ ├── unqlite_exception.c │ ├── unqlite_exception.h │ └── unqlite_ruby.h ├── lib ├── unqlite.rb └── unqlite │ ├── errors.rb │ └── version.rb ├── test ├── helper.rb ├── test_cursor.rb ├── test_database.rb └── test_errors.rb └── unqlite.gemspec /.autotest: -------------------------------------------------------------------------------- 1 | Autotest.add_hook :initialize do |at| 2 | at.add_mapping(/ext\/.*\.c/) do |f, _| 3 | at.files_matching(/(lib|ext)\/test_.*rb$/) 4 | end 5 | end 6 | 7 | Autotest.add_hook :run_command do |at| 8 | system "bundle exec rake clean compile" 9 | end 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | *.so 9 | *.bundle 10 | *.o 11 | _yardoc 12 | coverage 13 | doc/ 14 | lib/bundler/man 15 | pkg 16 | rdoc 17 | spec/reports 18 | test/tmp 19 | test/version_tmp 20 | tmp 21 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.5 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | === 0.1.0 / 08 Jun 2013 2 | 3 | * Database file support (readonly not supported yet) 4 | * Exception handling 5 | * Transaction support 6 | * API change: 7 | * Database#store doesn't return an unqlite_return_code anymore (but still a truthy value). 8 | 9 | === 0.0.1 / 02 Jun 2013 10 | 11 | * First version 12 | * Only basic methods implemented (#new #store #fetch) 13 | * Only in-memory database support 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in unqlite.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Daniel Teixeira 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnQLite for Ruby 2 | 3 | [UnQLite](http://www.unqlite.org/) interface for ruby programs. 4 | 5 | Note: This is an alpha version and many features are missing! But, 6 | I would love to merge some pull-requests to make it better (: 7 | 8 | ## Installation 9 | 10 | You have to install UnQLite into your system and compile it as a shared library. Unfortunately, 11 | UnQLite doesn't have a Makefile (or something like that) to automate that step. If you are on 12 | linux, you can check [this gist](https://gist.github.com/danieltdt/5693070) and compile it using gcc. 13 | 14 | After installing UnQLite, add this line to your application's Gemfile: 15 | ```ruby 16 | gem 'unqlite' 17 | ``` 18 | 19 | And then execute: 20 | 21 | $ bundle 22 | 23 | Or install it yourself as: 24 | 25 | $ gem install unqlite 26 | 27 | ## Usage 28 | 29 | For in-memory databases 30 | ```ruby 31 | db = UnQLite::Database.new(":mem:") 32 | db.store("key", "wabba") 33 | db.fetch("key") # => "wabba" 34 | db.close 35 | ``` 36 | 37 | For regular databases 38 | ```ruby 39 | db = UnQLite::Database.new("database.db") # You may also give a full path 40 | db.store("key", "wabba") 41 | db.fetch("key") # => "wabba" 42 | 43 | # Now you have to commit your changes or close your database 44 | db.commit 45 | 46 | db.store("key2", "wabba2") 47 | db.close # Will automatically commit 48 | ``` 49 | 50 | ## Contributing 51 | 52 | 1. Fork it 53 | 2. Create your feature branch (`git checkout -b my-new-feature`) 54 | 3. Commit your changes (`git commit -am 'Add some feature'`) 55 | 4. Push to the branch (`git push origin my-new-feature`) 56 | 5. Create new Pull Request 57 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require 'rake/extensiontask' 3 | require 'rake/testtask' 4 | 5 | Rake::ExtensionTask.new('unqlite_native') do |ext| 6 | ext.ext_dir = 'ext/unqlite' 7 | ext.lib_dir = 'lib/unqlite' 8 | end 9 | 10 | Rake::TestTask.new do |t| 11 | t.libs << "test" 12 | t.test_files = FileList['test/test*.rb'] 13 | t.verbose = true 14 | end 15 | -------------------------------------------------------------------------------- /ext/unqlite/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; See Info node `(emacs) Directory Variables' for more information. 3 | 4 | ((c-mode 5 | (c-basic-offset . 2))) 6 | -------------------------------------------------------------------------------- /ext/unqlite/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | 3 | abort "unqlite.h is missing. Please, install unqlite." unless find_header 'unqlite.h' 4 | abort "unqlite is missing. Please, install unqlite" unless find_library 'unqlite', 'unqlite_open' 5 | 6 | create_makefile('unqlite/unqlite_native') 7 | -------------------------------------------------------------------------------- /ext/unqlite/unqlite.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | VALUE mUnQLite; 4 | 5 | void Init_unqlite_native() 6 | { 7 | mUnQLite = rb_define_module("UnQLite"); 8 | 9 | Init_unqlite_database(); 10 | Init_unqlite_codes(); 11 | Init_unqlite_cursor(); 12 | } 13 | -------------------------------------------------------------------------------- /ext/unqlite/unqlite_codes.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | VALUE mUnQLiteCodes; 4 | 5 | void Init_unqlite_codes() 6 | { 7 | #if 0 8 | VALUE mUnqlite = rb_define_module("UnQLite"); 9 | #endif 10 | 11 | mUnQLiteCodes = rb_define_module_under(mUnQLite, "Codes"); 12 | rb_define_const(mUnQLiteCodes, "OK", INT2FIX(UNQLITE_OK)); 13 | rb_define_const(mUnQLiteCodes, "NOMEM", INT2FIX(UNQLITE_NOMEM)); 14 | rb_define_const(mUnQLiteCodes, "ABORT", INT2FIX(UNQLITE_ABORT)); 15 | rb_define_const(mUnQLiteCodes, "IOERR", INT2FIX(UNQLITE_IOERR)); 16 | rb_define_const(mUnQLiteCodes, "CORRUPT", INT2FIX(UNQLITE_CORRUPT)); 17 | rb_define_const(mUnQLiteCodes, "LOCKED", INT2FIX(UNQLITE_LOCKED)); 18 | rb_define_const(mUnQLiteCodes, "BUSY", INT2FIX(UNQLITE_BUSY)); 19 | rb_define_const(mUnQLiteCodes, "DONE", INT2FIX(UNQLITE_DONE)); 20 | rb_define_const(mUnQLiteCodes, "PERM", INT2FIX(UNQLITE_PERM)); 21 | rb_define_const(mUnQLiteCodes, "NOTIMPLEMENTED", INT2FIX(UNQLITE_NOTIMPLEMENTED)); 22 | rb_define_const(mUnQLiteCodes, "NOTFOUND", INT2FIX(UNQLITE_NOTFOUND)); 23 | rb_define_const(mUnQLiteCodes, "NOOP", INT2FIX(UNQLITE_NOOP)); // Used only for jx9 (unqlite v1.1.6) 24 | rb_define_const(mUnQLiteCodes, "INVALID", INT2FIX(UNQLITE_INVALID)); 25 | rb_define_const(mUnQLiteCodes, "EOF", INT2FIX(UNQLITE_EOF)); 26 | rb_define_const(mUnQLiteCodes, "UNKNOWN", INT2FIX(UNQLITE_UNKNOWN)); 27 | rb_define_const(mUnQLiteCodes, "LIMIT", INT2FIX(UNQLITE_LIMIT)); 28 | rb_define_const(mUnQLiteCodes, "EXISTS", INT2FIX(UNQLITE_EXISTS)); 29 | rb_define_const(mUnQLiteCodes, "EMPTY", INT2FIX(UNQLITE_EMPTY)); 30 | rb_define_const(mUnQLiteCodes, "COMPILE_ERR", INT2FIX(UNQLITE_COMPILE_ERR)); // Used only for jx9 (unqlite v1.1.6) 31 | rb_define_const(mUnQLiteCodes, "VM_ERR", INT2FIX(UNQLITE_VM_ERR)); // Used only for jx9 (unqlite v1.1.6) 32 | rb_define_const(mUnQLiteCodes, "FULL", INT2FIX(UNQLITE_FULL)); 33 | rb_define_const(mUnQLiteCodes, "CANTOPEN", INT2FIX(UNQLITE_CANTOPEN)); 34 | rb_define_const(mUnQLiteCodes, "READ_ONLY", INT2FIX(UNQLITE_READ_ONLY)); 35 | rb_define_const(mUnQLiteCodes, "LOCKERR", INT2FIX(UNQLITE_LOCKERR)); 36 | } 37 | -------------------------------------------------------------------------------- /ext/unqlite/unqlite_codes.h: -------------------------------------------------------------------------------- 1 | #ifndef UNQLITE_RUBY_CODES 2 | #define UNQLITE_RUBY_CODES 3 | 4 | #include 5 | 6 | extern VALUE mUnQLiteCodes; 7 | 8 | void Init_unqlite_codes(); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /ext/unqlite/unqlite_cursor.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* 4 | * Document-class: UnQLite::Cursor 5 | * 6 | * Cursors provide a mechanism by which you can iterate over the 7 | * records in a database. Using cursors, you can seek, fetch, move, 8 | * and delete database records. 9 | */ 10 | 11 | /* Get cursor context pointer from Ruby object */ 12 | #define GetCursor(obj, cursp) { \ 13 | Data_Get_Struct((obj), unqliteRubyCursor, (cursp)); \ 14 | if ((cursp) == 0) released_cursor(); \ 15 | if ((cursp)->cursor == 0) released_cursor(); \ 16 | } 17 | 18 | /* Get cursor context pointer and native cursor pointer from Ruby object */ 19 | #define GetCursor2(obj, cursp, curs) { \ 20 | GetCursor((obj), (cursp)); \ 21 | (curs) = (cursp)->cursor; \ 22 | } 23 | 24 | /* Raise error for already released cursor */ 25 | static void released_cursor() 26 | { 27 | rb_raise(rb_eRuntimeError, "Released cursor"); 28 | } 29 | 30 | /* Wrapped object: mark */ 31 | static void unqlite_cursor_mark(unqliteRubyCursor* rcursor) 32 | { 33 | rb_gc_mark(rcursor->rb_database); 34 | } 35 | 36 | /* Wrapped object: deallocate */ 37 | static void unqlite_cursor_deallocate(unqliteRubyCursor* rcursor) 38 | { 39 | int rc; 40 | if (rcursor->cursor) { 41 | unqliteRubyPtr rdatabase; 42 | Data_Get_Struct(rcursor->rb_database, unqliteRuby, rdatabase); 43 | rc = unqlite_kv_cursor_release(rdatabase->pDb, rcursor->cursor); 44 | CHECK(0, rc); 45 | } 46 | xfree(rcursor); 47 | } 48 | 49 | /* Wrapped object: allocate */ 50 | VALUE unqlite_cursor_allocate(VALUE klass) 51 | { 52 | unqliteRubyCursor *rcursor = ALLOC(unqliteRubyCursor); 53 | rcursor->cursor = 0; 54 | rcursor->rb_database = Qnil; 55 | return Data_Wrap_Struct(klass, unqlite_cursor_mark, unqlite_cursor_deallocate, rcursor); 56 | } 57 | 58 | /* 59 | * call-seq: 60 | * UnQLite::Cursor.new(unqlite) 61 | * 62 | * Initializes a new cursor from an existing database. 63 | */ 64 | static VALUE unqlite_cursor_initialize(VALUE self, VALUE rb_database) 65 | { 66 | unqliteRubyCursor* rcursor; 67 | unqliteRuby* rdatabase; 68 | int rc; 69 | Data_Get_Struct(self, unqliteRubyCursor, rcursor); 70 | Data_Get_Struct(rb_database, unqliteRuby, rdatabase); 71 | rcursor->rb_database = rb_database; 72 | rb_ary_push(rdatabase->acursors, self); 73 | rc = unqlite_kv_cursor_init(rdatabase->pDb, &rcursor->cursor); 74 | CHECK(rdatabase->pDb, rc); 75 | return self; 76 | } 77 | 78 | /* 79 | * call-seq: 80 | * cursor.reset 81 | * 82 | * Resets the cursor. 83 | */ 84 | static VALUE unqlite_cursor_reset(VALUE self) 85 | { 86 | unqliteRubyCursor* rcursor; 87 | unqlite_kv_cursor* cursor; 88 | unqliteRuby* rdatabase; 89 | int rc; 90 | 91 | GetCursor2(self, rcursor, cursor); 92 | Data_Get_Struct(rcursor->rb_database, unqliteRuby, rdatabase); 93 | rc = unqlite_kv_cursor_reset(cursor); 94 | CHECK(rdatabase->pDb, rc); 95 | return Qtrue; 96 | } 97 | 98 | /* 99 | * call-seq: 100 | * cursor.first! 101 | * 102 | * Sets the cursor to point to the first entry in the database. 103 | */ 104 | static VALUE unqlite_cursor_first(VALUE self) 105 | { 106 | unqliteRubyCursor* rcursor; 107 | unqlite_kv_cursor* cursor; 108 | unqliteRuby* rdatabase; 109 | int rc; 110 | 111 | GetCursor2(self, rcursor, cursor); 112 | Data_Get_Struct(rcursor->rb_database, unqliteRuby, rdatabase); 113 | rc = unqlite_kv_cursor_first_entry(cursor); 114 | CHECK(rdatabase->pDb, rc); 115 | return Qtrue; 116 | } 117 | 118 | /* 119 | * call-seq: 120 | * cursor.last! 121 | * 122 | * Sets the cursor to point to the last entry in the database. 123 | */ 124 | static VALUE unqlite_cursor_last(VALUE self) 125 | { 126 | unqliteRubyCursor* rcursor; 127 | unqlite_kv_cursor* cursor; 128 | unqliteRuby* rdatabase; 129 | int rc; 130 | 131 | GetCursor2(self, rcursor, cursor); 132 | Data_Get_Struct(rcursor->rb_database, unqliteRuby, rdatabase); 133 | rc = unqlite_kv_cursor_last_entry(cursor); 134 | CHECK(rdatabase->pDb, rc); 135 | return Qtrue; 136 | } 137 | 138 | /* 139 | * call-seq: 140 | * cursor.valid? 141 | * 142 | * Returns true if the cursor points to a valid entry, otherwise false. 143 | */ 144 | static VALUE unqlite_cursor_valid(VALUE self) 145 | { 146 | unqliteRubyCursor* rcursor; 147 | unqlite_kv_cursor* cursor; 148 | 149 | GetCursor2(self, rcursor, cursor); 150 | return unqlite_kv_cursor_valid_entry(cursor) ? Qtrue : Qfalse; 151 | } 152 | 153 | /* 154 | * call-seq: 155 | * cursor.next! 156 | * 157 | * Step cursor forwards to the next entry. 158 | */ 159 | static VALUE unqlite_cursor_next(VALUE self) 160 | { 161 | unqliteRubyCursor* rcursor; 162 | unqlite_kv_cursor* cursor; 163 | unqliteRuby* rdatabase; 164 | int rc; 165 | 166 | GetCursor2(self, rcursor, cursor); 167 | Data_Get_Struct(rcursor->rb_database, unqliteRuby, rdatabase); 168 | rc = unqlite_kv_cursor_next_entry(cursor); 169 | CHECK(rdatabase->pDb, rc); 170 | return Qtrue; 171 | } 172 | 173 | /* 174 | * call-seq: 175 | * cursor.prev! 176 | * 177 | * Step cursor backwards to the previous entry. 178 | */ 179 | static VALUE unqlite_cursor_prev(VALUE self) 180 | { 181 | unqliteRubyCursor* rcursor; 182 | unqlite_kv_cursor* cursor; 183 | unqliteRuby* rdatabase; 184 | int rc; 185 | 186 | GetCursor2(self, rcursor, cursor); 187 | Data_Get_Struct(rcursor->rb_database, unqliteRuby, rdatabase); 188 | rc = unqlite_kv_cursor_prev_entry(cursor); 189 | CHECK(rdatabase->pDb, rc); 190 | return Qtrue; 191 | } 192 | 193 | /* 194 | * call-seq: 195 | * cursor.delete! 196 | * 197 | * Delete the entry referenced by the cursor. 198 | */ 199 | static VALUE unqlite_cursor_delete(VALUE self) 200 | { 201 | unqliteRubyCursor* rcursor; 202 | unqlite_kv_cursor* cursor; 203 | unqliteRuby* rdatabase; 204 | int rc; 205 | 206 | GetCursor2(self, rcursor, cursor); 207 | Data_Get_Struct(rcursor->rb_database, unqliteRuby, rdatabase); 208 | rc = unqlite_kv_cursor_delete_entry(cursor); 209 | CHECK(rdatabase->pDb, rc); 210 | return Qtrue; 211 | } 212 | 213 | /* 214 | * call-seq: 215 | * cursor.seek(key) 216 | * cursor.seek(key, direction) 217 | * 218 | * Resets the cursor. Direction can be one of: 219 | * 220 | * * +UnQLite::CURSOR_MATCH_EXACT+ 221 | * * +UnQLite::CURSOR_MATCH_LE+ 222 | * * +UnQLite::CURSOR_MATCH_GE+ 223 | * 224 | * Default direction is exact match. 225 | */ 226 | static VALUE unqlite_cursor_seek(int argc, VALUE* argv, VALUE self) 227 | { 228 | unqliteRubyCursor* rcursor; 229 | unqlite_kv_cursor* cursor; 230 | int rc; 231 | VALUE key, direction; 232 | 233 | GetCursor2(self, rcursor, cursor); 234 | rb_scan_args(argc, argv, "11", &key, &direction); 235 | if (NIL_P(direction)) 236 | direction = INT2NUM(0); 237 | rc = unqlite_kv_cursor_seek(cursor, RSTRING_PTR(key), RSTRING_LEN(key), NUM2INT(direction)); 238 | CHECK(0, rc); 239 | return Qtrue; 240 | } 241 | 242 | /* 243 | * call-seq: 244 | * cursor.key 245 | * 246 | * Returns the key of the entry pointed to by the cursor. 247 | */ 248 | static VALUE unqlite_cursor_key(VALUE self) 249 | { 250 | unqliteRubyCursor* rcursor; 251 | unqlite_kv_cursor* cursor; 252 | int rc; 253 | volatile VALUE rkey; 254 | int key_size; 255 | 256 | GetCursor2(self, rcursor, cursor); 257 | rc = unqlite_kv_cursor_key(cursor, NULL, &key_size); 258 | CHECK(0, rc); 259 | rkey = rb_str_buf_new(key_size); 260 | rc = unqlite_kv_cursor_key(cursor, RSTRING_PTR(rkey), &key_size); 261 | CHECK(0, rc); 262 | rb_str_set_len(rkey, key_size); 263 | return rkey; 264 | } 265 | 266 | /* 267 | * call-seq: 268 | * cursor.value 269 | * cursor.data 270 | * 271 | * Returns the data/value of the entry pointed to by the cursor. 272 | */ 273 | static VALUE unqlite_cursor_value(VALUE self) 274 | { 275 | unqliteRubyCursor* rcursor; 276 | unqlite_kv_cursor* cursor; 277 | int rc; 278 | volatile VALUE rvalue; 279 | unqlite_int64 data_size; 280 | 281 | GetCursor2(self, rcursor, cursor); 282 | rc = unqlite_kv_cursor_data(cursor, NULL, &data_size); 283 | CHECK(0, rc); 284 | rvalue = rb_str_buf_new(data_size); 285 | rc = unqlite_kv_cursor_data(cursor, RSTRING_PTR(rvalue), &data_size); 286 | CHECK(0, rc); 287 | rb_str_set_len(rvalue, data_size); 288 | return rvalue; 289 | } 290 | 291 | /* 292 | * call-seq: 293 | * cursor.release 294 | * cursor.close 295 | * 296 | * Releases the underlying cursor. 297 | */ 298 | VALUE unqlite_cursor_release(VALUE self) 299 | { 300 | unqliteRubyCursor* rcursor; 301 | unqlite_kv_cursor* cursor; 302 | int rc; 303 | 304 | GetCursor2(self, rcursor, cursor); 305 | 306 | if (rcursor->cursor) { 307 | unqliteRubyPtr rdatabase; 308 | Data_Get_Struct(rcursor->rb_database, unqliteRuby, rdatabase); 309 | rc = unqlite_kv_cursor_release(rdatabase->pDb, rcursor->cursor); 310 | CHECK(0, rc); 311 | rcursor->cursor = NULL; 312 | rcursor->rb_database = Qnil; 313 | rb_ary_delete(rdatabase->acursors, self); 314 | } 315 | 316 | return Qtrue; 317 | } 318 | 319 | void Init_unqlite_cursor() 320 | { 321 | VALUE mUnQLite = rb_path2class("UnQLite"); 322 | /* Cursors provide a mechanism by which you can iterate over the records in a database. Using cursors, you can seek, fetch, move, and delete database records. */ 323 | VALUE cUnQLiteCursor = rb_define_class_under(mUnQLite, "Cursor", rb_cObject); 324 | rb_define_alloc_func(cUnQLiteCursor, unqlite_cursor_allocate); 325 | rb_define_method(cUnQLiteCursor, "initialize", unqlite_cursor_initialize, 1); 326 | rb_define_method(cUnQLiteCursor, "release", unqlite_cursor_release, 0); 327 | rb_define_method(cUnQLiteCursor, "close", unqlite_cursor_release, 0); 328 | rb_define_method(cUnQLiteCursor, "reset", unqlite_cursor_reset, 0); 329 | rb_define_method(cUnQLiteCursor, "seek", unqlite_cursor_seek, -1); 330 | rb_define_method(cUnQLiteCursor, "first!", unqlite_cursor_first, 0); 331 | rb_define_method(cUnQLiteCursor, "last!", unqlite_cursor_last, 0); 332 | rb_define_method(cUnQLiteCursor, "valid?", unqlite_cursor_valid, 0); 333 | rb_define_method(cUnQLiteCursor, "next!", unqlite_cursor_next, 0); 334 | rb_define_method(cUnQLiteCursor, "prev!", unqlite_cursor_prev, 0); 335 | rb_define_method(cUnQLiteCursor, "key", unqlite_cursor_key, 0); 336 | rb_define_method(cUnQLiteCursor, "value", unqlite_cursor_value, 0); 337 | rb_define_method(cUnQLiteCursor, "data", unqlite_cursor_value, 0); 338 | rb_define_method(cUnQLiteCursor, "delete!", unqlite_cursor_delete, 0); 339 | } 340 | -------------------------------------------------------------------------------- /ext/unqlite/unqlite_cursor.h: -------------------------------------------------------------------------------- 1 | #ifndef _unqlite_cursor_h 2 | #define _unqlite_cursor_h 3 | 4 | #include 5 | 6 | void Init_unqlite_cursor(); 7 | VALUE unqlite_cursor_release(VALUE self); 8 | 9 | typedef struct 10 | { 11 | unqlite_kv_cursor* cursor; 12 | VALUE rb_database; 13 | } unqliteRubyCursor; 14 | 15 | #endif /* _unqlite_cursor_h */ 16 | -------------------------------------------------------------------------------- /ext/unqlite/unqlite_database.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | VALUE cUnQLiteDatabase; 5 | 6 | /* Get database context pointer from Ruby object */ 7 | #define GetDatabase(obj, databasep) { \ 8 | Data_Get_Struct((obj), unqliteRuby, (databasep)); \ 9 | if ((databasep) == 0) closed_database(); \ 10 | if ((databasep)->pDb == 0) closed_database(); \ 11 | } 12 | 13 | /* Get database context pointer and native unqlite pointer from Ruby object */ 14 | #define GetDatabase2(obj, databasep, database) { \ 15 | GetDatabase((obj), (databasep)); \ 16 | (database) = (databasep)->pDb; \ 17 | } 18 | 19 | /* Raise error for already closed database */ 20 | static void closed_database() 21 | { 22 | rb_raise(rb_eRuntimeError, "Closed database"); 23 | } 24 | 25 | static void unqliteRuby_close(unqliteRubyPtr ctx) 26 | { 27 | if (ctx->pDb) 28 | { 29 | VALUE cur; 30 | int rc; 31 | 32 | /* close lingering cursors */ 33 | if (!NIL_P(ctx->acursors) && RARRAY_LEN(ctx->acursors) > 0) { 34 | while ((cur = rb_ary_pop(ctx->acursors)) != Qnil) 35 | unqlite_cursor_release(cur); 36 | } 37 | 38 | // Close database 39 | rc = unqlite_close(ctx->pDb); 40 | 41 | // Check for errors 42 | CHECK(ctx->pDb, rc); 43 | } 44 | 45 | ctx->pDb = 0; 46 | } 47 | 48 | /* Wrapped object: mark */ 49 | static void unqlite_database_mark(unqliteRubyPtr rdatabase) 50 | { 51 | rb_gc_mark(rdatabase->acursors); 52 | } 53 | 54 | /* Wrapped object: deallocate */ 55 | static void unqlite_database_deallocate(unqliteRubyPtr c) 56 | { 57 | unqliteRuby_close(c); 58 | xfree(c); 59 | } 60 | 61 | /* Wrapped object: allocate */ 62 | static VALUE unqlite_database_allocate(VALUE klass) 63 | { 64 | unqliteRubyPtr ctx = ALLOC(unqliteRuby); 65 | volatile VALUE rb_database; 66 | ctx->pDb = NULL; 67 | ctx->acursors = Qnil; 68 | rb_database = Data_Wrap_Struct(klass, unqlite_database_mark, unqlite_database_deallocate, ctx); 69 | return rb_database; 70 | } 71 | 72 | /* 73 | * call-seq: 74 | * UnQLite::Database.new(filename, flags = nil) 75 | * UnQLite::Database.new(filename, flags = nil) { |unqlite| ... } 76 | * 77 | * Creates a new UnQLite instance by opening an unqlite file named _filename_. 78 | * If the file does not exist, a new file will be 79 | * created. _flags_ may be one of the following: 80 | * 81 | * * +UnQlite::CREATE+ - Create if database does not exist. 82 | * * +UnQLite::READWRITE+ - Open the database with READ+WRITE priviledged. 83 | * * +UnQLite::READONLY+ - Open the database in read-only mode. 84 | * * +UnQLite::MMAP+ - Obtain a read-only memory view of the whole database. 85 | * * +UnQLite::TEMP_DB+ - A private, temporary on-disk database will be created. 86 | * * +UnQLite::IN_MEMORY+ - A private, on-memory database will be created. 87 | * * +UnQLite::OMIT_JOURNALING+ - Disable journaling for this database. 88 | * * +UnQLite::NOMUTEX+ - Disable the private recursive mutex associated with each database handle. 89 | * 90 | * If no _flags_ are specified, the UnQLite object will try to open the database 91 | * file as a writer and will create it if it does not already exist 92 | * (cf. flag CREATE). 93 | */ 94 | static VALUE initialize(int argc, VALUE* argv, VALUE self) 95 | { 96 | int rc; 97 | unqliteRubyPtr ctx; 98 | VALUE filename, vflags; 99 | int flags = UNQLITE_OPEN_CREATE; 100 | 101 | rb_scan_args(argc, argv, "11", &filename, &vflags); 102 | 103 | // Get the flags if specified 104 | if (!NIL_P(vflags)) 105 | flags = NUM2INT(vflags); 106 | 107 | // Ensure the given argument is a ruby string 108 | Check_Type(filename, T_STRING); 109 | 110 | Data_Get_Struct(self, unqliteRuby, ctx); 111 | 112 | // Open database 113 | rc = unqlite_open(&ctx->pDb, StringValueCStr(filename), flags); 114 | 115 | ctx->acursors = rb_ary_new(); 116 | 117 | // Check if any exception should be raised 118 | CHECK(ctx->pDb, rc); 119 | 120 | return self; 121 | } 122 | 123 | /* 124 | * call-seq: 125 | * database.close 126 | * 127 | * Closes the database (automatically commits any open transaction). 128 | */ 129 | static VALUE unqlite_database_close(VALUE self) 130 | { 131 | unqliteRubyPtr ctx; 132 | 133 | // Get class context 134 | Data_Get_Struct(self, unqliteRuby, ctx); 135 | 136 | unqliteRuby_close(ctx); 137 | return Qtrue; 138 | } 139 | 140 | /* 141 | * call-seq: 142 | * database.closed? -> true or false 143 | * 144 | * Returns true if the associated database has been closed. 145 | */ 146 | static VALUE unqlite_database_closed(VALUE self) 147 | { 148 | unqliteRubyPtr ctx; 149 | unqlite* db; 150 | 151 | Data_Get_Struct(self, unqliteRuby, ctx); 152 | db = ctx->pDb; 153 | 154 | if (db) 155 | { 156 | return Qfalse; 157 | } 158 | else 159 | { 160 | return Qtrue; 161 | } 162 | } 163 | 164 | /* 165 | * call-seq: 166 | * UnQLite::Database.open(filename, flags = nil) 167 | * 168 | * If called without a block, this is synonymous to 169 | * UnQLite::Database::new. If a block is given, the new UnQLite 170 | * instance will be passed to the block as a parameter, and the 171 | * corresponding database file will be closed after the execution of 172 | * the block code has been finished. 173 | * 174 | * Example for an open call with a block: 175 | * 176 | * require 'unqlite' 177 | * UnQLite::Database.open("fruitstore.db") do |unq| 178 | * unq.each_pair do |key, value| 179 | * print "#{key}: #{value}\n" 180 | * end 181 | * end 182 | */ 183 | static VALUE unqlite_database_open(int argc, VALUE* argv, VALUE klass) 184 | { 185 | volatile VALUE obj = unqlite_database_allocate(klass); 186 | 187 | if (NIL_P(initialize(argc, argv, obj))) 188 | return Qnil; 189 | 190 | if (rb_block_given_p()) 191 | return rb_ensure(rb_yield, obj, unqlite_database_close, obj); 192 | else 193 | return obj; 194 | } 195 | 196 | 197 | /* 198 | * call-seq: 199 | * database.store key, value 200 | * database[key] = value 201 | * 202 | * Associates the value _value_ with the specified _key_. 203 | */ 204 | static VALUE unqlite_database_store(VALUE self, VALUE key, VALUE value) 205 | { 206 | int rc; 207 | unqliteRubyPtr ctx; 208 | unqlite* db; 209 | 210 | // Ensure the given argument is a ruby string 211 | Check_Type(key, T_STRING); 212 | Check_Type(value, T_STRING); 213 | 214 | GetDatabase2(self, ctx, db); 215 | 216 | // Store it 217 | rc = unqlite_kv_store(db, StringValuePtr(key), RSTRING_LEN(key), StringValuePtr(value), RSTRING_LEN(value)); 218 | 219 | // Check for errors 220 | CHECK(db, rc); 221 | 222 | return Qtrue; 223 | } 224 | 225 | /* 226 | * call-seq: 227 | * database.append key, value 228 | * 229 | * Appends the _value_ to an already existing value associated with _key_. 230 | */ 231 | static VALUE unqlite_database_append(VALUE self, VALUE key, VALUE value) 232 | { 233 | int rc; 234 | unqliteRubyPtr ctx; 235 | unqlite* db; 236 | 237 | // Ensure the given argument is a ruby string 238 | Check_Type(key, T_STRING); 239 | Check_Type(value, T_STRING); 240 | 241 | GetDatabase2(self, ctx, db); 242 | 243 | // Append it 244 | rc = unqlite_kv_append(db, StringValuePtr(key), RSTRING_LEN(key), StringValuePtr(value), RSTRING_LEN(value)); 245 | 246 | // Check for errors 247 | CHECK(db, rc); 248 | 249 | return Qtrue; 250 | } 251 | 252 | /* 253 | * call-seq: 254 | * database.delete key 255 | * 256 | * Removes the key-value pair with the specified _key_ from this database. 257 | */ 258 | static VALUE unqlite_database_delete(VALUE self, VALUE key) 259 | { 260 | int rc; 261 | unqliteRubyPtr ctx; 262 | unqlite* db; 263 | 264 | // Ensure the given argument is a ruby string 265 | Check_Type(key, T_STRING); 266 | 267 | GetDatabase2(self, ctx, db); 268 | 269 | // Delete it 270 | rc = unqlite_kv_delete(db, StringValuePtr(key), RSTRING_LEN(key)); 271 | 272 | // Check for errors 273 | CHECK(db, rc); 274 | 275 | return Qtrue; 276 | } 277 | 278 | /* 279 | * call-seq: 280 | * database.fetch(key) -> value 281 | * 282 | * Retrieves the _value_ corresponding to _key_. If there is no value 283 | * associated with _key_, an exception will be raised. 284 | */ 285 | static VALUE unqlite_database_fetch(VALUE self, VALUE collection_name) 286 | { 287 | unqlite_int64 n_bytes; 288 | int rc; 289 | unqliteRubyPtr ctx; 290 | unqlite* db; 291 | volatile VALUE filename; 292 | 293 | // Ensure the given argument is a ruby string 294 | Check_Type(collection_name, T_STRING); 295 | 296 | GetDatabase2(self, ctx, db); 297 | 298 | // Extract the data size, check for errors and return if any 299 | rc = unqlite_kv_fetch(db, StringValuePtr(collection_name), RSTRING_LEN(collection_name), NULL, &n_bytes); 300 | 301 | CHECK(db, rc); 302 | if( rc != UNQLITE_OK ) { return Qnil; } 303 | 304 | // Allocate string buffer object 305 | filename = rb_str_buf_new(n_bytes); 306 | 307 | // Now, fetch the data 308 | rc = unqlite_kv_fetch(db, StringValuePtr(collection_name), RSTRING_LEN(collection_name), RSTRING_PTR(filename), &n_bytes); 309 | CHECK(db, rc); 310 | 311 | rb_str_set_len(filename, n_bytes); 312 | 313 | return filename; 314 | } 315 | 316 | /* 317 | * call-seq: 318 | * database.has_key?(key) 319 | * database.key?(key) 320 | * database.include?(key) 321 | * database.member?(key) 322 | * 323 | * Returns true if the given _key_ exists within the database. Returns 324 | * false otherwise. 325 | */ 326 | static VALUE unqlite_database_has_key(VALUE self, VALUE collection_name) 327 | { 328 | unqliteRubyPtr ctx; 329 | unqlite* db; 330 | int rc; 331 | unqlite_int64 n_bytes; 332 | 333 | // Ensure the given argument is a ruby string 334 | Check_Type(collection_name, T_STRING); 335 | 336 | GetDatabase2(self, ctx, db); 337 | 338 | // Extract the data size, check for errors and return if any 339 | rc = unqlite_kv_fetch(db, StringValuePtr(collection_name), RSTRING_LEN(collection_name), NULL, &n_bytes); 340 | if (rc == UNQLITE_NOTFOUND) 341 | { 342 | return Qfalse; 343 | } 344 | else 345 | { 346 | CHECK(db, rc); 347 | return Qtrue; 348 | } 349 | } 350 | 351 | /* 352 | * call-seq: 353 | * database[key] -> value 354 | * 355 | * Retrieves the _value_ corresponding to _key_. If the key does not 356 | * exist in the database, nil is returned. 357 | */ 358 | static VALUE unqlite_database_aref(VALUE self, VALUE collection_name) 359 | { 360 | unqlite_int64 n_bytes; 361 | int rc; 362 | unqliteRubyPtr ctx; 363 | unqlite* db; 364 | volatile VALUE rb_string; 365 | 366 | // Ensure the given argument is a ruby string 367 | Check_Type(collection_name, T_STRING); 368 | 369 | GetDatabase2(self, ctx, db); 370 | 371 | // Extract the data size, check for errors and return if any 372 | rc = unqlite_kv_fetch(db, StringValuePtr(collection_name), RSTRING_LEN(collection_name), NULL, &n_bytes); 373 | 374 | if (rc == UNQLITE_NOTFOUND) 375 | return Qnil; 376 | 377 | CHECK(db, rc); 378 | if( rc != UNQLITE_OK ) { return Qnil; } 379 | 380 | // Allocate string buffer object 381 | rb_string = rb_str_buf_new(n_bytes); 382 | 383 | // Now, fetch the data 384 | rc = unqlite_kv_fetch(db, StringValuePtr(collection_name), RSTRING_LEN(collection_name), RSTRING_PTR(rb_string), &n_bytes); 385 | CHECK(db, rc); 386 | 387 | rb_str_set_len(rb_string, n_bytes); 388 | 389 | return rb_string; 390 | } 391 | 392 | /* 393 | * call-seq: 394 | * database.begin_transaction 395 | * 396 | * Begins a write-transaction. Ignored if a transaction has already 397 | * been opened. 398 | */ 399 | static VALUE unqlite_database_begin_transaction(VALUE self) 400 | { 401 | int rc; 402 | unqliteRubyPtr ctx; 403 | unqlite* db; 404 | 405 | GetDatabase2(self, ctx, db); 406 | 407 | // Begin write-transaction manually 408 | rc = unqlite_begin(db); 409 | 410 | // Check for errors 411 | CHECK(db, rc); 412 | 413 | return Qtrue; 414 | } 415 | 416 | /* 417 | * call-seq: 418 | * database.commit 419 | * 420 | * Commit all changes to the database and release the exclusive lock. 421 | */ 422 | static VALUE unqlite_database_commit(VALUE self) 423 | { 424 | int rc; 425 | unqliteRubyPtr ctx; 426 | unqlite* db; 427 | 428 | GetDatabase2(self, ctx, db); 429 | 430 | // Commit transaction 431 | rc = unqlite_commit(db); 432 | 433 | // Check for errors 434 | CHECK(db, rc); 435 | 436 | return Qtrue; 437 | } 438 | 439 | /* 440 | * call-seq: 441 | * database.rollback 442 | * 443 | * Rollback a write-transaction. 444 | */ 445 | static VALUE unqlite_database_rollback(VALUE self) 446 | { 447 | int rc; 448 | unqliteRubyPtr ctx; 449 | unqlite* db; 450 | 451 | GetDatabase2(self, ctx, db); 452 | 453 | // Rollback transaction 454 | rc = unqlite_rollback(db); 455 | 456 | // Check for errors 457 | CHECK(db, rc); 458 | 459 | return Qtrue; 460 | } 461 | 462 | /* 463 | * call-seq: 464 | * database.end_transaction(true or false) 465 | * 466 | * Ends the current transaction. If argument is _true_, the 467 | * transaction is commited, if _false_ it is rolled back. 468 | */ 469 | static VALUE unqlite_database_end_transaction(VALUE self, VALUE commit) 470 | { 471 | if (RTEST(commit)) 472 | return unqlite_database_commit(self); 473 | else 474 | return unqlite_database_rollback(self); 475 | } 476 | 477 | static VALUE unqlite_database_transaction_rescue(VALUE self, VALUE error) 478 | { 479 | unqlite_database_rollback(self); 480 | rb_exc_raise(error); 481 | } 482 | 483 | static VALUE unqlite_database_transaction_body(VALUE vdb) 484 | { 485 | return rb_rescue(rb_yield, vdb, unqlite_database_transaction_rescue, vdb); 486 | } 487 | 488 | static VALUE unqlite_database_transaction_ensure(VALUE self, VALUE vbargs) 489 | { 490 | return unqlite_database_commit(self); 491 | } 492 | 493 | /* 494 | * call-seq: 495 | * database.transaction { |db| ... } 496 | * 497 | * Begins a write-transaction and executes _block_. If the block 498 | * executes to completion, the transaction is committed. If an 499 | * exception is raise by the block, the transaction is rolled back. 500 | */ 501 | static VALUE unqlite_database_transaction(VALUE self) 502 | { 503 | VALUE vrv = unqlite_database_begin_transaction(self); 504 | if (vrv != Qtrue) 505 | return Qfalse; 506 | rb_ensure(unqlite_database_transaction_body, self, unqlite_database_transaction_ensure, self); 507 | return Qtrue; 508 | } 509 | 510 | /* 511 | * call-seq: 512 | * database.each { |key, value| ... } 513 | * database.each_pair { |key, value| ... } 514 | * 515 | * Executes _block_ for each key in the database, passing the _key_ 516 | * and the corresponding _value_ as parameters. 517 | */ 518 | static VALUE unqlite_database_each(VALUE self) 519 | { 520 | int rc; 521 | unqliteRubyPtr ctx; 522 | unqlite* db; 523 | unqlite_kv_cursor *cursor; 524 | 525 | GetDatabase2(self, ctx, db); 526 | 527 | rc = unqlite_kv_cursor_init(db, &cursor); 528 | CHECK(db, rc); 529 | 530 | rc = unqlite_kv_cursor_first_entry(cursor); 531 | while (unqlite_kv_cursor_valid_entry(cursor)) 532 | { 533 | int key_size; 534 | unqlite_int64 data_size; 535 | volatile VALUE rb_key, rb_data; 536 | 537 | // Create Ruby String with key 538 | rc = unqlite_kv_cursor_key(cursor, NULL, &key_size); 539 | CHECK(db, rc); 540 | rb_key = rb_str_buf_new(key_size); 541 | rc = unqlite_kv_cursor_key(cursor, RSTRING_PTR(rb_key), &key_size); 542 | CHECK(db, rc); 543 | rb_str_set_len(rb_key, key_size); 544 | 545 | // Create Ruby String with data 546 | rc = unqlite_kv_cursor_data(cursor, NULL, &data_size); 547 | CHECK(db, rc); 548 | rb_data = rb_str_buf_new(data_size); 549 | rc = unqlite_kv_cursor_data(cursor, RSTRING_PTR(rb_data), &data_size); 550 | CHECK(db, rc); 551 | rb_str_set_len(rb_data, data_size); 552 | 553 | // Yield to block 554 | rb_yield_values(2, rb_key, rb_data); 555 | 556 | rc = unqlite_kv_cursor_next_entry(cursor); 557 | } 558 | 559 | rc = unqlite_kv_cursor_release(db, cursor); 560 | CHECK(db, rc); 561 | 562 | return Qtrue; 563 | } 564 | 565 | /* 566 | * call-seq: 567 | * database.each_value { |value| ... } 568 | * 569 | * Executes _block_ for each value in the database, passing the 570 | * _value_ as parameter. 571 | */ 572 | static VALUE unqlite_database_each_value(VALUE self) 573 | { 574 | int rc; 575 | unqliteRubyPtr ctx; 576 | unqlite* db; 577 | unqlite_kv_cursor *cursor; 578 | 579 | GetDatabase2(self, ctx, db); 580 | 581 | rc = unqlite_kv_cursor_init(db, &cursor); 582 | CHECK(db, rc); 583 | 584 | rc = unqlite_kv_cursor_first_entry(cursor); 585 | while (unqlite_kv_cursor_valid_entry(cursor)) 586 | { 587 | unqlite_int64 data_size; 588 | volatile VALUE rb_data; 589 | 590 | // Create Ruby String with data 591 | rc = unqlite_kv_cursor_data(cursor, NULL, &data_size); 592 | CHECK(db, rc); 593 | rb_data = rb_str_buf_new(data_size); 594 | rc = unqlite_kv_cursor_data(cursor, RSTRING_PTR(rb_data), &data_size); 595 | CHECK(db, rc); 596 | rb_str_set_len(rb_data, data_size); 597 | 598 | // Yield to block 599 | rb_yield_values(1, rb_data); 600 | 601 | rc = unqlite_kv_cursor_next_entry(cursor); 602 | } 603 | 604 | rc = unqlite_kv_cursor_release(db, cursor); 605 | CHECK(db, rc); 606 | 607 | return Qtrue; 608 | } 609 | 610 | /* 611 | * call-seq: 612 | * database.each_key { |key| ... } 613 | * 614 | * Executes _block_ for each key in the database, passing the _key_ 615 | * and as parameter. 616 | */ 617 | static VALUE unqlite_database_each_key(VALUE self) 618 | { 619 | int rc; 620 | unqliteRubyPtr ctx; 621 | unqlite* db; 622 | unqlite_kv_cursor *cursor; 623 | 624 | GetDatabase2(self, ctx, db); 625 | 626 | rc = unqlite_kv_cursor_init(db, &cursor); 627 | CHECK(db, rc); 628 | 629 | rc = unqlite_kv_cursor_first_entry(cursor); 630 | while (unqlite_kv_cursor_valid_entry(cursor)) 631 | { 632 | int key_size; 633 | volatile VALUE rb_key; 634 | 635 | // Create Ruby String with key 636 | rc = unqlite_kv_cursor_key(cursor, NULL, &key_size); 637 | CHECK(db, rc); 638 | rb_key = rb_str_buf_new(key_size); 639 | rc = unqlite_kv_cursor_key(cursor, RSTRING_PTR(rb_key), &key_size); 640 | CHECK(db, rc); 641 | rb_str_set_len(rb_key, key_size); 642 | 643 | // Yield to block 644 | rb_yield_values(1, rb_key); 645 | 646 | rc = unqlite_kv_cursor_next_entry(cursor); 647 | } 648 | 649 | rc = unqlite_kv_cursor_release(db, cursor); 650 | CHECK(db, rc); 651 | 652 | return Qtrue; 653 | } 654 | 655 | /* 656 | * call-seq: 657 | * database.clear 658 | * 659 | * Removes all the key-value pairs in the database. 660 | */ 661 | static VALUE unqlite_database_clear(VALUE self) 662 | { 663 | int rc; 664 | unqliteRubyPtr ctx; 665 | unqlite* db; 666 | unqlite_kv_cursor *cursor; 667 | 668 | GetDatabase2(self, ctx, db); 669 | 670 | rc = unqlite_kv_cursor_init(db, &cursor); 671 | CHECK(db, rc); 672 | 673 | rc = unqlite_kv_cursor_first_entry(cursor); 674 | while (unqlite_kv_cursor_valid_entry(cursor)) 675 | { 676 | rc = unqlite_kv_cursor_delete_entry(cursor); 677 | CHECK(db, rc); 678 | 679 | rc = unqlite_kv_cursor_first_entry(cursor); 680 | } 681 | 682 | rc = unqlite_kv_cursor_release(db, cursor); 683 | CHECK(db, rc); 684 | 685 | return Qtrue; 686 | } 687 | 688 | /* 689 | * call-seq: 690 | * database.empty? -> true or false 691 | * 692 | * Returns _true_ if the database has no key-value pairs, false 693 | * otherwise. 694 | */ 695 | static VALUE unqlite_database_empty(VALUE self) 696 | { 697 | int rc; 698 | unqliteRubyPtr ctx; 699 | unqlite* db; 700 | unqlite_kv_cursor *cursor; 701 | volatile VALUE result; 702 | 703 | GetDatabase2(self, ctx, db); 704 | 705 | rc = unqlite_kv_cursor_init(db, &cursor); 706 | CHECK(db, rc); 707 | 708 | rc = unqlite_kv_cursor_first_entry(cursor); 709 | result = unqlite_kv_cursor_valid_entry(cursor) ? Qfalse : Qtrue; 710 | 711 | rc = unqlite_kv_cursor_release(db, cursor); 712 | CHECK(db, rc); 713 | 714 | return result; 715 | } 716 | 717 | /* 718 | * call-seq: 719 | * database.max_page_cache = count 720 | * 721 | * Sets the maximum raw pages to cache in memory. This is a simple 722 | * hint, UnQLite is not forced to honor it. 723 | */ 724 | static VALUE unqlite_database_set_max_page_cache(VALUE self, VALUE count) 725 | { 726 | int rc; 727 | unqliteRubyPtr ctx; 728 | unqlite* db; 729 | 730 | GetDatabase2(self, ctx, db); 731 | 732 | rc = unqlite_config(db, UNQLITE_CONFIG_MAX_PAGE_CACHE, NUM2INT(count)); 733 | CHECK(db, rc); 734 | 735 | return count; 736 | } 737 | 738 | /* 739 | * call-seq: 740 | * database.kv_engine = engine 741 | * 742 | * Switch to another Key/Value storage engine. 743 | */ 744 | static VALUE unqlite_database_set_kv_engine(VALUE self, VALUE engine) 745 | { 746 | int rc; 747 | unqliteRubyPtr ctx; 748 | unqlite* db; 749 | 750 | GetDatabase2(self, ctx, db); 751 | 752 | SafeStringValue(engine); 753 | rc = unqlite_config(db, UNQLITE_CONFIG_KV_ENGINE, RSTRING_PTR(engine)); 754 | CHECK(db, rc); 755 | 756 | return engine; 757 | } 758 | 759 | /* 760 | * call-seq: 761 | * database.kv_engine -> engine 762 | * 763 | * Extract the name of the underlying Key/Value storage engine 764 | * (i.e. Hash, Mem, R+Tree, LSM, etc.). 765 | */ 766 | static VALUE unqlite_database_get_kv_engine(VALUE self) 767 | { 768 | int rc; 769 | unqliteRubyPtr ctx; 770 | unqlite* db; 771 | const char* name; 772 | 773 | GetDatabase2(self, ctx, db); 774 | 775 | rc = unqlite_config(db, UNQLITE_CONFIG_GET_KV_NAME, &name); 776 | CHECK(db, rc); 777 | 778 | return rb_str_new_cstr(name); 779 | } 780 | 781 | /* 782 | * call-seq: 783 | * database.disable_auto_commit 784 | * 785 | * Normally, If #close is invoked while a transaction is open, the 786 | * transaction is automatically committed. But, if this option is set, 787 | * then the transaction is automatically rolled back and you should 788 | * call #commit manually to commit all database changes. 789 | */ 790 | static VALUE unqlite_database_disable_auto_commit(VALUE self) 791 | { 792 | int rc; 793 | unqliteRubyPtr ctx; 794 | unqlite* db; 795 | 796 | GetDatabase2(self, ctx, db); 797 | 798 | rc = unqlite_config(db, UNQLITE_CONFIG_DISABLE_AUTO_COMMIT, 0); 799 | CHECK(db, rc); 800 | 801 | return Qnil; 802 | } 803 | 804 | void Init_unqlite_database() 805 | { 806 | #if 0 807 | VALUE mUnqlite = rb_define_module("UnQLite"); 808 | #endif 809 | 810 | VALUE mUnqlite = rb_path2class("UnQLite"); 811 | 812 | /* flag for #new and #open: If the database does not exists, it is created. Otherwise, it is opened with read+write privileges. */ 813 | rb_define_const(mUnqlite, "CREATE", INT2FIX(UNQLITE_OPEN_CREATE)); 814 | /* flag for #new and #open: Open the database with read+write privileges. */ 815 | rb_define_const(mUnqlite, "READWRITE", INT2FIX(UNQLITE_OPEN_READWRITE)); 816 | /* flag for #new and #open: Open the database in a read-only mode. */ 817 | rb_define_const(mUnqlite, "READONLY", INT2FIX(UNQLITE_OPEN_READONLY)); 818 | /* flag for #new and #open: Obtain a read-only memory view of the whole database. This flag works only in conjunction with the READONLY control flag */ 819 | rb_define_const(mUnqlite, "MMAP", INT2FIX(UNQLITE_OPEN_MMAP)); 820 | /* flag for #new and #open: A private, temporary on-disk database will be created. This private database will be automatically deleted as soon as the database connection is closed. */ 821 | rb_define_const(mUnqlite, "TEMP_DB", INT2FIX(UNQLITE_OPEN_TEMP_DB)); 822 | /* flag for #new and #open: A private, temporary on-disk database will be created. This private database will be automatically deleted as soon as the database connection is closed. */ 823 | rb_define_const(mUnqlite, "IN_MEMORY", INT2FIX(UNQLITE_OPEN_IN_MEMORY)); 824 | /* flag for #new and #open: Disable journaling for this database. */ 825 | rb_define_const(mUnqlite, "OMIT_JOURNALING", INT2FIX(UNQLITE_OPEN_OMIT_JOURNALING)); 826 | /* flag for #new and #open: Disable the private recursive mutex associated with each database handle. */ 827 | rb_define_const(mUnqlite, "NOMUTEX", INT2FIX(UNQLITE_OPEN_NOMUTEX)); 828 | 829 | /* defining UnQLite::Database class and appending its methods */ 830 | cUnQLiteDatabase = rb_define_class_under(mUnQLite, "Database", rb_cObject); 831 | 832 | rb_define_alloc_func(cUnQLiteDatabase, unqlite_database_allocate); 833 | 834 | rb_define_singleton_method(cUnQLiteDatabase, "open", unqlite_database_open, -1); 835 | 836 | rb_define_method(cUnQLiteDatabase, "store", unqlite_database_store, 2); 837 | rb_define_method(cUnQLiteDatabase, "append", unqlite_database_append, 2); 838 | rb_define_method(cUnQLiteDatabase, "fetch", unqlite_database_fetch, 1); 839 | rb_define_method(cUnQLiteDatabase, "delete", unqlite_database_delete, 1); 840 | 841 | rb_define_method(cUnQLiteDatabase, "closed?", unqlite_database_closed, 0); 842 | rb_define_method(cUnQLiteDatabase, "[]", unqlite_database_aref, 1); 843 | rb_define_method(cUnQLiteDatabase, "[]=", unqlite_database_store, 2); 844 | rb_define_method(cUnQLiteDatabase, "has_key?", unqlite_database_has_key, 1); 845 | rb_define_method(cUnQLiteDatabase, "include?", unqlite_database_has_key, 1); 846 | rb_define_method(cUnQLiteDatabase, "key?", unqlite_database_has_key, 1); 847 | rb_define_method(cUnQLiteDatabase, "member?", unqlite_database_has_key, 1); 848 | rb_define_method(cUnQLiteDatabase, "clear", unqlite_database_clear, 0); 849 | rb_define_method(cUnQLiteDatabase, "empty?", unqlite_database_empty, 0); 850 | 851 | rb_define_method(cUnQLiteDatabase, "each", unqlite_database_each, 0); 852 | rb_define_method(cUnQLiteDatabase, "each_pair", unqlite_database_each, 0); 853 | rb_define_method(cUnQLiteDatabase, "each_key", unqlite_database_each_key, 0); 854 | rb_define_method(cUnQLiteDatabase, "each_value", unqlite_database_each_value, 0); 855 | 856 | rb_define_method(cUnQLiteDatabase, "begin_transaction", unqlite_database_begin_transaction, 0); 857 | rb_define_method(cUnQLiteDatabase, "end_transaction", unqlite_database_end_transaction, 1); 858 | rb_define_method(cUnQLiteDatabase, "commit", unqlite_database_commit, 0); 859 | rb_define_method(cUnQLiteDatabase, "rollback", unqlite_database_rollback, 0); 860 | rb_define_method(cUnQLiteDatabase, "transaction", unqlite_database_transaction, 0); 861 | 862 | rb_define_method(cUnQLiteDatabase, "initialize", initialize, -1); 863 | rb_define_method(cUnQLiteDatabase, "close", unqlite_database_close, 0); 864 | 865 | rb_define_method(cUnQLiteDatabase, "max_page_cache=", unqlite_database_set_max_page_cache, 1); 866 | rb_define_method(cUnQLiteDatabase, "kv_engine=", unqlite_database_set_kv_engine, 1); 867 | rb_define_method(cUnQLiteDatabase, "kv_engine", unqlite_database_get_kv_engine, 0); 868 | rb_define_method(cUnQLiteDatabase, "disable_auto_commit", unqlite_database_disable_auto_commit, 0); 869 | } 870 | -------------------------------------------------------------------------------- /ext/unqlite/unqlite_database.h: -------------------------------------------------------------------------------- 1 | #ifndef UNQLITE_RUBY_DATABASE 2 | #define UNQLITE_RUBY_DATABASE 3 | 4 | #include 5 | 6 | struct _unqliteRuby { 7 | unqlite *pDb; 8 | VALUE acursors; 9 | }; 10 | 11 | typedef struct _unqliteRuby unqliteRuby; 12 | typedef unqliteRuby * unqliteRubyPtr; 13 | 14 | extern VALUE cUnQLiteDatabase; 15 | 16 | void Init_unqlite_database(); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /ext/unqlite/unqlite_exception.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void rb_unqlite_raise(unqlite *db, int rc) 4 | { 5 | VALUE klass = Qnil; 6 | 7 | switch(rc) { 8 | case UNQLITE_NOMEM: 9 | klass = rb_path2class("UnQLite::MemoryException"); 10 | break; 11 | case UNQLITE_ABORT: 12 | klass = rb_path2class("UnQLite::AbortException"); 13 | break; 14 | case UNQLITE_IOERR: 15 | klass = rb_path2class("UnQLite::IOException"); 16 | break; 17 | case UNQLITE_CORRUPT: 18 | klass = rb_path2class("UnQLite::CorruptException"); 19 | break; 20 | case UNQLITE_LOCKED: 21 | klass = rb_path2class("UnQLite::LockedException"); 22 | break; 23 | case UNQLITE_BUSY: 24 | klass = rb_path2class("UnQLite::BusyException"); 25 | break; 26 | /* Not sure if it is an error or not (check lib/unqlite/errors.rb) 27 | case UNQLITE_DONE: 28 | klass = rb_path2class("UnQLite::DoneException"); 29 | break; 30 | */ 31 | case UNQLITE_PERM: 32 | klass = rb_path2class("UnQLite::PermissionException"); 33 | break; 34 | case UNQLITE_NOTIMPLEMENTED: 35 | klass = rb_path2class("UnQLite::NotImplementedException"); 36 | break; 37 | case UNQLITE_NOTFOUND: 38 | klass = rb_path2class("UnQLite::NotFoundException"); 39 | break; 40 | case UNQLITE_EMPTY: 41 | klass = rb_path2class("UnQLite::EmptyException"); 42 | break; 43 | case UNQLITE_INVALID: 44 | klass = rb_path2class("UnQLite::InvalidParameterException"); 45 | break; 46 | case UNQLITE_EOF: 47 | klass = rb_path2class("UnQLite::EOFException"); 48 | break; 49 | case UNQLITE_UNKNOWN: 50 | klass = rb_path2class("UnQLite::UnknownConfigurationException"); 51 | break; 52 | case UNQLITE_LIMIT: 53 | klass = rb_path2class("UnQLite::LimitReachedException"); 54 | break; 55 | case UNQLITE_FULL: 56 | klass = rb_path2class("UnQLite::FullDatabaseException"); 57 | break; 58 | case UNQLITE_CANTOPEN: 59 | klass = rb_path2class("UnQLite::CantOpenDatabaseException"); 60 | break; 61 | case UNQLITE_READ_ONLY: 62 | klass = rb_path2class("UnQLite::ReadOnlyException"); 63 | break; 64 | case UNQLITE_LOCKERR: 65 | klass = rb_path2class("UnQLite::LockProtocolException"); 66 | break; 67 | } 68 | 69 | if( !NIL_P(klass) ) { // Is really an error? 70 | const char *buffer; 71 | int length = 0; 72 | 73 | /* Get error from log */ 74 | if (db) 75 | unqlite_config(db, UNQLITE_CONFIG_ERR_LOG, &buffer, &length); 76 | 77 | // Raise it! 78 | if( length > 0 ) 79 | rb_raise(klass, "%s", buffer); 80 | else { 81 | VALUE klass_name = rb_class_name(klass); 82 | rb_raise(klass, "%s", StringValueCStr(klass_name)); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ext/unqlite/unqlite_exception.h: -------------------------------------------------------------------------------- 1 | #ifndef UNQLITE_RUBY_EXCEPTION 2 | #define UNQLITE_RUBY_EXCEPTION 3 | 4 | #include 5 | 6 | /* Macro to raise the proper exception given a return code */ 7 | #define CHECK(_db, _rc) rb_unqlite_raise(_db, _rc); 8 | void rb_unqlite_raise(unqlite *db, int rc); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /ext/unqlite/unqlite_ruby.h: -------------------------------------------------------------------------------- 1 | #ifndef UNQLITE_RUBY 2 | #define UNQLITE_RUBY 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | extern VALUE mUnQLite; 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /lib/unqlite.rb: -------------------------------------------------------------------------------- 1 | require 'unqlite/unqlite_native' 2 | require 'unqlite/errors' 3 | require 'unqlite/version' 4 | 5 | module UnQLite 6 | # Your code goes here... 7 | end 8 | -------------------------------------------------------------------------------- /lib/unqlite/errors.rb: -------------------------------------------------------------------------------- 1 | module UnQLite 2 | class Exception < ::StandardError 3 | @code = 0 4 | 5 | def self.code 6 | @code 7 | end 8 | 9 | def code 10 | self.class.code 11 | end 12 | end 13 | 14 | class MemoryException < Exception; end 15 | class AbortException < Exception; end 16 | class IOException < Exception; end 17 | class CorruptException < Exception; end 18 | class LockedException < Exception; end # Forbidden operation 19 | class BusyException < Exception; end 20 | #class DoneException < Exception; end # It is not clear on docs if it's an error or not 21 | class PermissionException < Exception; end 22 | class NotImplementedException < Exception; end 23 | class NotFoundException < Exception; end # It is a quite confusing if is an error or an acceptable behavior (unqlite v1.1.6) 24 | class EmptyException < Exception; end # Empty key (and some jx9 functions) 25 | class InvalidParameterException < Exception; end 26 | class EOFException < Exception; end 27 | class UnknownConfigurationException < Exception; end 28 | class LimitReachedException < Exception; end 29 | class FullDatabaseException < Exception; end 30 | class CantOpenDatabaseException < Exception; end 31 | class ReadOnlyException < Exception; end 32 | class LockProtocolException < Exception; end 33 | class UnsupportedException < Exception; end 34 | end 35 | -------------------------------------------------------------------------------- /lib/unqlite/version.rb: -------------------------------------------------------------------------------- 1 | module UnQLite 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'unqlite' 2 | require 'minitest/autorun' 3 | 4 | -------------------------------------------------------------------------------- /test/test_cursor.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'tmpdir' 3 | require 'unqlite' 4 | 5 | module UnQLite 6 | class CursorTest < Minitest::Test 7 | attr_reader :db_path, :db 8 | def setup 9 | @db_path = "#{Dir.mktmpdir("unqlite-ruby-test")}/db" 10 | @db = UnQLite::Database.open(@db_path) 11 | end 12 | 13 | def teardown 14 | db.close 15 | FileUtils.remove_entry(db_path) if File.exist?(db_path) 16 | end 17 | 18 | def test_key_value 19 | db.store "key", "value" 20 | cursor = UnQLite::Cursor.new(db) 21 | cursor.first! 22 | assert_equal "key", cursor.key 23 | assert_equal "value", cursor.value 24 | end 25 | 26 | def test_first_last 27 | db.store "alpha", "first" 28 | db.store "beta", "second" 29 | keys = [] 30 | cursor = UnQLite::Cursor.new(db) 31 | cursor.first! 32 | keys << cursor.key 33 | cursor.last! 34 | keys << cursor.key 35 | assert_equal ["alpha", "beta"], keys.sort # order is undefined 36 | end 37 | 38 | def test_valid 39 | db.store "key", "value" 40 | cursor = UnQLite::Cursor.new(db) 41 | assert !cursor.valid? 42 | cursor.first! 43 | assert cursor.valid? 44 | end 45 | 46 | def test_first_next 47 | db.store "alpha", "first" 48 | db.store "beta", "second" 49 | db.store "gamma", "third" 50 | keys = [] 51 | cursor = UnQLite::Cursor.new(db) 52 | cursor.first! 53 | while cursor.valid? 54 | keys << cursor.key 55 | cursor.next! 56 | end 57 | assert_equal ["alpha", "beta", "gamma"], keys.sort! # order is undefined 58 | end 59 | 60 | def test_last_prev 61 | db.store "alpha", "first" 62 | db.store "beta", "second" 63 | db.store "gamma", "third" 64 | keys = [] 65 | cursor = UnQLite::Cursor.new(db) 66 | cursor.last! 67 | while cursor.valid? 68 | keys << cursor.key 69 | cursor.prev! 70 | end 71 | assert_equal ["alpha", "beta", "gamma"], keys.sort! # order is undefined 72 | end 73 | 74 | def test_seek 75 | db.store "alpha", "first" 76 | db.store "beta", "second" 77 | db.store "gamma", "third" 78 | cursor = UnQLite::Cursor.new(db) 79 | cursor.seek "beta" 80 | assert_equal "second", cursor.value 81 | end 82 | 83 | def test_delete 84 | db.store "alpha", "first" 85 | db.store "beta", "second" 86 | db.store "gamma", "third" 87 | cursor = UnQLite::Cursor.new(db) 88 | cursor.seek "beta" 89 | cursor.delete! 90 | assert_raises(UnQLite::NotFoundException) { @db.fetch "beta" } 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /test/test_database.rb: -------------------------------------------------------------------------------- 1 | require 'tempfile' 2 | require 'helper' 3 | 4 | module UnQLite 5 | module CommonTestsForDatabase 6 | def self.included(klass) 7 | klass.class_eval { attr_reader :db_path } 8 | end 9 | 10 | def setup 11 | @db = UnQLite::Database.new(db_path) 12 | end 13 | 14 | def teardown 15 | @db.close 16 | end 17 | 18 | def test_store 19 | assert @db.store("key", "stored content") 20 | end 21 | 22 | def test_append 23 | assert @db.store("key", "stored content") 24 | assert @db.append("key", " with appendix") 25 | assert_equal "stored content with appendix", @db.fetch("key") 26 | end 27 | 28 | def test_fetch 29 | @db.store("key", "wabba") 30 | 31 | assert_equal("wabba", @db.fetch("key")) 32 | end 33 | 34 | def test_fetchlonger 35 | @db.store("key", "wabbawabba") 36 | 37 | assert_equal("wabbawabba", @db.fetch("key")) 38 | end 39 | 40 | def test_store_multiple_uncommitted_with_failing_fetch_between 41 | @db.store("key1", "wabbauno") 42 | assert_raises UnQLite::NotFoundException do 43 | @db.fetch("key") 44 | end 45 | @db.store("key2", "wabbawabba") 46 | 47 | assert_equal("wabbauno", @db.fetch("key1")) 48 | end 49 | 50 | def test_store_multiple_committed_with_failing_fetch_between 51 | @db.store("key1", "wabbauno") 52 | @db.commit 53 | assert_raises UnQLite::NotFoundException do 54 | @db.fetch("key") 55 | end 56 | @db.store("key2", "wabbawabba") 57 | assert_equal("wabbauno", @db.fetch("key1")) 58 | end 59 | 60 | def test_store_fetch_multiple 61 | @db.store("key1", "wabbauno") 62 | @db.store("key2", "wabbawabba") 63 | 64 | assert_equal("wabbauno", @db.fetch("key1")) 65 | assert_equal("wabbawabba", @db.fetch("key2")) 66 | end 67 | 68 | def test_delete 69 | @db.store("key", "wabba") 70 | assert_equal("wabba", @db.fetch("key")) 71 | @db.delete("key") 72 | assert_raises(UnQLite::NotFoundException) { @db.fetch("key") } 73 | end 74 | 75 | def test_exceptions 76 | # TODO: Test other errors 77 | assert_raises(UnQLite::NotFoundException) { @db.fetch("xxx") } 78 | end 79 | 80 | def test_raw_string 81 | assert @db.store("key\x00value", "store\x00content") 82 | assert_equal "store\x00content", @db.fetch("key\x00value") 83 | assert_raises(UnQLite::NotFoundException) { @db.fetch("key") } 84 | assert_raises(UnQLite::NotFoundException) { @db.fetch("key\x00") } 85 | assert_raises(UnQLite::NotFoundException) { @db.fetch("key\x00value\x00") } 86 | end 87 | 88 | def test_each 89 | pairs = [ [ "alpha", "first" ], [ "beta", "second" ], [ "gamma", "third" ] ] 90 | [ :each, :each_pair ].each do |method_name| 91 | pairs.each { |pair| @db.store(*pair) } 92 | all = [] 93 | @db.send(method_name) { |key, value| all << [key, value] } 94 | assert_equal pairs, all.sort_by { |k,v| k } 95 | end 96 | end 97 | 98 | def test_each_key 99 | pairs = [ [ "alpha", "first" ], [ "beta", "second" ], [ "gamma", "third" ] ] 100 | pairs.each { |pair| @db.store(*pair) } 101 | all = [] 102 | @db.each_key { |key| all << key } 103 | assert_equal pairs.map { |k,v| k }, all.sort 104 | end 105 | 106 | def test_each_value 107 | pairs = [ [ "alpha", "first" ], [ "beta", "second" ], [ "gamma", "third" ] ] 108 | pairs.each { |pair| @db.store(*pair) } 109 | all = [] 110 | @db.each_value { |value| all << value } 111 | assert_equal pairs.map { |k,v| v }, all.sort 112 | end 113 | 114 | def test_aref 115 | @db["key"] = "data" 116 | assert_equal "data", @db["key"] 117 | assert_equal "data", @db.fetch("key") 118 | assert_nil @db["nokey"] 119 | end 120 | 121 | def test_has_key 122 | @db.store "key", "value" 123 | [ :has_key?, :include?, :key?, :member? ].each do |nm| 124 | assert_equal true, @db.send(nm, "key") 125 | assert_equal false, @db.send(nm, "value") 126 | end 127 | end 128 | 129 | def test_closed? 130 | assert !@db.closed? 131 | @db.close 132 | assert @db.closed? 133 | end 134 | 135 | def test_clear 136 | @db.store "key", "value" 137 | @db.store "foo", "bar" 138 | @db.clear 139 | assert_raises(UnQLite::NotFoundException) { @db.fetch("key") } 140 | assert_raises(UnQLite::NotFoundException) { @db.fetch("foo") } 141 | @db.store "key", "value" 142 | assert_equal "value", @db.fetch("key") 143 | end 144 | 145 | def test_empty? 146 | assert @db.empty? 147 | @db.store "key", "value" 148 | assert !@db.empty? 149 | end 150 | 151 | def test_max_page_cache 152 | UnQLite::Database.open(db_path) do |db| 153 | db.max_page_cache = 1024 154 | db["key"] = "value" 155 | end 156 | end 157 | 158 | def test_kv_engine 159 | UnQLite::Database.open(db_path) do |db| 160 | assert db.kv_engine.kind_of?(String) 161 | end 162 | end 163 | 164 | # Not supports by unqlite as of version 1.1.6 165 | # def test_set_kv_engine 166 | # UnQLite::Database.open(db_path) do |db| 167 | # db.kv_engine = "hash" 168 | # end 169 | # end 170 | end 171 | 172 | class TestInMemoryDatabase < Minitest::Test 173 | include CommonTestsForDatabase 174 | 175 | def initialize(*args) 176 | @db_path = ":mem:" 177 | super(*args) 178 | end 179 | end 180 | 181 | class TestDatabase < Minitest::Test 182 | include CommonTestsForDatabase 183 | 184 | def setup 185 | @tmp = Tempfile.new("test_db") 186 | @db_path = @tmp.path 187 | @tmp.close 188 | super() 189 | end 190 | 191 | def teardown 192 | super() 193 | @tmp.unlink 194 | end 195 | 196 | def test_automatic_transaction 197 | @db.store("auto", "wabba") 198 | @db.rollback 199 | 200 | assert_raises(UnQLite::NotFoundException) { @db.fetch("auto") } 201 | end 202 | 203 | def test_manual_transaction 204 | @db.begin_transaction 205 | @db.store("manual", "wabba") 206 | @db.rollback 207 | 208 | assert_raises(UnQLite::NotFoundException) { @db.fetch("manual") } 209 | 210 | @db.store("manual2", "wabba") 211 | @db.commit 212 | @db.store("will_disapper", "wabba") 213 | @db.rollback 214 | 215 | assert_equal("wabba", @db.fetch("manual2")) 216 | assert_raises(UnQLite::NotFoundException) { @db.fetch("will_disapper") } 217 | end 218 | 219 | def test_end_transaction_commit 220 | @db.begin_transaction 221 | @db.store "manual", "wabba" 222 | @db.end_transaction(true) 223 | assert_equal "wabba", @db.fetch("manual") 224 | end 225 | 226 | def test_end_transaction_rollback 227 | @db.begin_transaction 228 | @db.store "will_disappear", "wabba" 229 | @db.end_transaction(false) 230 | assert_raises(UnQLite::NotFoundException) { @db.fetch("will_disappear") } 231 | end 232 | 233 | def test_transaction 234 | @db.transaction do 235 | @db.store "alpha", "first" 236 | @db.store "beta", "second" 237 | end 238 | assert_equal "first", @db.fetch("alpha") 239 | assert_equal "second", @db.fetch("beta") 240 | end 241 | 242 | def test_transaction_failed 243 | assert_raises Exception do 244 | @db.transaction do 245 | @db.store "alpha", "first" 246 | @db.store "beta", "second" 247 | raise Exception 248 | end 249 | end 250 | assert_raises(UnQLite::NotFoundException) { @db.fetch("alpha") } 251 | assert_raises(UnQLite::NotFoundException) { @db.fetch("beta") } 252 | end 253 | 254 | def test_disable_auto_commit 255 | UnQLite::Database.open(db_path) do |db| 256 | db.disable_auto_commit 257 | db["key"] = "value" 258 | end 259 | UnQLite::Database.open(db_path) do |db| 260 | assert !db.include?("key") 261 | end 262 | end 263 | end 264 | 265 | class TestOpen < Minitest::Test 266 | attr_reader :db_path 267 | def setup 268 | @db_path = "#{Dir.mktmpdir("unqlite-ruby-test")}/db" 269 | end 270 | 271 | def teardown 272 | FileUtils.remove_entry(db_path) if File.exist?(db_path) 273 | end 274 | 275 | def test_open 276 | db = UnQLite::Database.open(db_path) 277 | db["key"] = "value" 278 | assert db.kind_of?(UnQLite::Database) 279 | assert !db.closed? 280 | db.close 281 | end 282 | 283 | def test_open_block 284 | UnQLite::Database.open(db_path) do |db| 285 | assert db.kind_of?(UnQLite::Database) 286 | assert !db.closed? 287 | db["key"] = "value" 288 | end 289 | end 290 | 291 | def test_open_create 292 | UnQLite::Database.open(db_path, UnQLite::CREATE) do |db| 293 | db["key"] = "value" 294 | end 295 | UnQLite::Database.open(db_path, UnQLite::READWRITE) do |db| 296 | assert_equal "value", db["key"] 297 | end 298 | end 299 | 300 | def test_open_readonly 301 | UnQLite::Database.open(db_path) do |db| 302 | db["key"] = "value" 303 | end 304 | UnQLite::Database.open(db_path, UnQLite::READONLY) do |db| 305 | assert_equal "value", db["key"] 306 | assert_raises(UnQLite::ReadOnlyException) { db["other"] = "something" } 307 | end 308 | end 309 | 310 | # VFS only? 311 | # def test_open_temp 312 | # UnQLite::Database.open(db_path, UnQLite::TEMP_DB) do |db| 313 | # db["key"] = "value" 314 | # end 315 | # assert !File.exist?(db_path) 316 | # end 317 | 318 | def test_open_readwrite 319 | UnQLite::Database.open(db_path) do |db| 320 | db["key"] = "value" 321 | end 322 | UnQLite::Database.open(db_path, UnQLite::READWRITE) do |db| 323 | assert_equal "value", db["key"] 324 | end 325 | end 326 | 327 | def test_open_readwrite_noexist 328 | assert_raises UnQLite::IOException do 329 | UnQLite::Database.open(db_path, UnQLite::READWRITE) do |db| 330 | db["key"] = "value" 331 | end 332 | end 333 | end 334 | 335 | def test_in_memory 336 | UnQLite::Database.open(db_path, UnQLite::IN_MEMORY | UnQLite::CREATE) do |db| 337 | db["key"] = "value" 338 | end 339 | assert !File.exist?(db_path) 340 | end 341 | 342 | def test_mmap 343 | UnQLite::Database.open(db_path) do |db| 344 | db["key"] = "value" 345 | end 346 | UnQLite::Database.open(db_path, UnQLite::READONLY | UnQLite::MMAP) do |db| 347 | assert_equal "value", db["key"] 348 | assert_raises(UnQLite::ReadOnlyException) { db["other"] = "something" } 349 | end 350 | end 351 | end 352 | end 353 | -------------------------------------------------------------------------------- /test/test_errors.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | module UnQLite 4 | class TestErros < Minitest::Test 5 | attr_reader :exceptions 6 | 7 | def setup 8 | @exceptions = [ 9 | UnQLite::MemoryException, 10 | UnQLite::AbortException, 11 | UnQLite::IOException, 12 | UnQLite::CorruptException, 13 | UnQLite::LockedException, 14 | UnQLite::BusyException, 15 | UnQLite::PermissionException, 16 | UnQLite::NotImplementedException, 17 | UnQLite::NotFoundException, 18 | UnQLite::EmptyException, 19 | UnQLite::InvalidParameterException, 20 | UnQLite::EOFException, 21 | UnQLite::UnknownConfigurationException, 22 | UnQLite::LimitReachedException, 23 | UnQLite::FullDatabaseException, 24 | UnQLite::CantOpenDatabaseException, 25 | UnQLite::ReadOnlyException, 26 | UnQLite::LockedException 27 | ] # + [UnQLite::DoneException] 28 | end 29 | 30 | def test_respond_to_code 31 | exceptions.each do |exception| 32 | assert true, exception.respond_to?(:code) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /unqlite.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'unqlite/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "unqlite" 8 | spec.version = UnQLite::VERSION 9 | spec.authors = ["Daniel Teixeira"] 10 | spec.email = ["daniel.t.dt@gmail.com"] 11 | spec.description = %q{UnQLite lib for ruby, using C extension.} 12 | spec.summary = %q{UnQLite for ruby (using C extension)} 13 | spec.homepage = "https://github.com/danieltdt/unqlite-ruby" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.extensions = ['ext/unqlite/extconf.rb'] 18 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 19 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 20 | spec.require_paths = ["lib"] 21 | 22 | spec.add_development_dependency "bundler", "~> 2.2.10" 23 | spec.add_development_dependency "rake", "~> 13.0.1" 24 | spec.add_development_dependency "ZenTest", "~> 4.12.0" 25 | spec.add_development_dependency "rake-compiler", "~> 1.1.0" 26 | spec.add_development_dependency "minitest", "~> 5.14.0" 27 | end 28 | --------------------------------------------------------------------------------