├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── .gitignore ├── BSDL ├── COPYING ├── Gemfile ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── dbm.gemspec ├── ext └── dbm │ ├── dbm.c │ └── extconf.rb └── test └── dbm └── test_dbm.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | if: ${{ startsWith(github.repository, 'ruby/') || github.event_name != 'schedule' }} 8 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 9 | with: 10 | engine: cruby 11 | min_version: 2.5 12 | 13 | test: 14 | needs: ruby-versions 15 | name: test (${{ matrix.ruby }} / ${{ matrix.os }}) 16 | strategy: 17 | matrix: 18 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 19 | os: [ ubuntu-latest, macos-latest] 20 | exclude: 21 | - { os: macos-latest, ruby: 2.5 } 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Install packages 26 | if: ${{ matrix.os == 'ubuntu-latest' }} 27 | run: | 28 | sudo apt update -qy 29 | sudo apt install libdb-dev 30 | - name: Set up Ruby 31 | uses: ruby/setup-ruby@v1 32 | with: 33 | ruby-version: ${{ matrix.ruby }} 34 | bundler-cache: true 35 | - name: Run test 36 | run: bundle exec rake 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | dbm.bundle 11 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in dbm.gemspec 4 | gemspec 5 | 6 | group :development do 7 | gem "rake-compiler" 8 | gem "test-unit" 9 | end 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DBM 2 | 3 | The DBM class provides a wrapper to a Unix-style [dbm](http://en.wikipedia.org/wiki/Dbm) or Database Manager library. 4 | 5 | Dbm databases do not have tables or columns; they are simple key-value data stores, like a Ruby Hash except not resident in RAM. Keys and values must be strings. 6 | 7 | The exact library used depends on how Ruby was compiled. It could be any of the following: 8 | 9 | * The original ndbm library is released in 4.3BSD. It is based on dbm library in Unix Version 7 but has different API to support multiple databases in a process. 10 | * [Berkeley DB](http://en.wikipedia.org/wiki/Berkeley_DB) versions 1 thru 5, also known as BDB and Sleepycat DB, now owned by Oracle Corporation. 11 | * Berkeley DB 1.x, still found in 4.4BSD derivatives (FreeBSD, OpenBSD, etc). 12 | * [gdbm](http://www.gnu.org/software/gdbm/), the GNU implementation of dbm. 13 | * [qdbm](http://fallabs.com/qdbm/index.html), another open source reimplementation of dbm. 14 | 15 | All of these dbm implementations have their own Ruby interfaces available, which provide richer (but varying) APIs. 16 | 17 | ## Installation 18 | 19 | Add this line to your application's Gemfile: 20 | 21 | ```ruby 22 | gem 'dbm' 23 | ``` 24 | 25 | And then execute: 26 | 27 | $ bundle 28 | 29 | Or install it yourself as: 30 | 31 | $ gem install dbm 32 | 33 | ## Usage 34 | 35 | ```ruby 36 | require 'dbm' 37 | db = DBM.open('rfcs', 0666, DBM::WRCREAT) 38 | db['822'] = 'Standard for the Format of ARPA Internet Text Messages' 39 | db['1123'] = 'Requirements for Internet Hosts - Application and Support' 40 | db['3068'] = 'An Anycast Prefix for 6to4 Relay Routers' 41 | puts db['822'] 42 | ``` 43 | 44 | ## Development 45 | 46 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 47 | 48 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 49 | 50 | ## Contributing 51 | 52 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/dbm. 53 | 54 | ## License 55 | 56 | The gem is available as open source under the terms of the [2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause). 57 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList['test/**/test_*.rb'] 8 | end 9 | 10 | require 'rake/extensiontask' 11 | Rake::ExtensionTask.new("dbm") 12 | 13 | task :default => [:compile, :test] 14 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "dbm" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /dbm.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | Gem::Specification.new do |s| 3 | s.name = "dbm" 4 | s.version = '1.1.0' 5 | s.summary = "Provides a wrapper for the UNIX-style Database Manager Library" 6 | s.description = "Provides a wrapper for the UNIX-style Database Manager Library" 7 | 8 | s.require_path = %w{lib} 9 | s.files = %w{README.md LICENSE.txt ext/dbm/extconf.rb ext/dbm/dbm.c} 10 | s.extensions = %w{ext/dbm/extconf.rb} 11 | s.required_ruby_version = ">= 2.3.0" 12 | 13 | s.authors = ["Yukihiro Matsumoto"] 14 | s.email = ["matz@ruby-lang.org"] 15 | s.homepage = "https://github.com/ruby/dbm" 16 | s.licenses = ["Ruby", "BSD-2-Clause"] 17 | 18 | s.metadata["msys2_mingw_dependencies"] = "gdbm" 19 | end 20 | -------------------------------------------------------------------------------- /ext/dbm/dbm.c: -------------------------------------------------------------------------------- 1 | /************************************************ 2 | 3 | dbm.c - 4 | 5 | $Author$ 6 | created at: Mon Jan 24 15:59:52 JST 1994 7 | 8 | Copyright (C) 1995-2001 Yukihiro Matsumoto 9 | 10 | ************************************************/ 11 | 12 | #include "ruby.h" 13 | 14 | #ifdef HAVE_CDEFS_H 15 | # include 16 | #endif 17 | #ifdef HAVE_SYS_CDEFS_H 18 | # include 19 | #endif 20 | #include DBM_HDR 21 | #include 22 | #include 23 | 24 | #define DSIZE_TYPE TYPEOF_DATUM_DSIZE 25 | #if SIZEOF_DATUM_DSIZE > SIZEOF_INT 26 | # define RSTRING_DSIZE(s) RSTRING_LEN(s) 27 | # define TOO_LONG(n) ((void)(n),0) 28 | #else 29 | # define RSTRING_DSIZE(s) RSTRING_LENINT(s) 30 | # define TOO_LONG(n) ((long)(+(DSIZE_TYPE)(n)) != (n)) 31 | #endif 32 | 33 | static VALUE rb_cDBM, rb_eDBMError; 34 | 35 | #define RUBY_DBM_RW_BIT 0x20000000 36 | 37 | struct dbmdata { 38 | long di_size; 39 | DBM *di_dbm; 40 | }; 41 | 42 | NORETURN(static void closed_dbm(void)); 43 | 44 | static void 45 | closed_dbm(void) 46 | { 47 | rb_raise(rb_eDBMError, "closed DBM file"); 48 | } 49 | 50 | #define GetDBM(obj, dbmp) do {\ 51 | TypedData_Get_Struct((obj), struct dbmdata, &dbm_type, (dbmp));\ 52 | if ((dbmp)->di_dbm == 0) closed_dbm();\ 53 | } while (0) 54 | 55 | #define GetDBM2(obj, dbmp, dbm) do {\ 56 | GetDBM((obj), (dbmp));\ 57 | (dbm) = (dbmp)->di_dbm;\ 58 | } while (0) 59 | 60 | static void 61 | free_dbm(void *ptr) 62 | { 63 | struct dbmdata *dbmp = ptr; 64 | if (dbmp->di_dbm) 65 | dbm_close(dbmp->di_dbm); 66 | xfree(dbmp); 67 | } 68 | 69 | static size_t 70 | memsize_dbm(const void *ptr) 71 | { 72 | const struct dbmdata *dbmp = ptr; 73 | size_t size = sizeof(*dbmp); 74 | if (dbmp->di_dbm) 75 | size += DBM_SIZEOF_DBM; 76 | return size; 77 | } 78 | 79 | static const rb_data_type_t dbm_type = { 80 | "dbm", 81 | {0, free_dbm, memsize_dbm,}, 82 | 0, 0, 83 | RUBY_TYPED_FREE_IMMEDIATELY, 84 | }; 85 | 86 | /* 87 | * call-seq: 88 | * dbm.close 89 | * 90 | * Closes the database. 91 | */ 92 | static VALUE 93 | fdbm_close(VALUE obj) 94 | { 95 | struct dbmdata *dbmp; 96 | 97 | GetDBM(obj, dbmp); 98 | dbm_close(dbmp->di_dbm); 99 | dbmp->di_dbm = 0; 100 | 101 | return Qnil; 102 | } 103 | 104 | /* 105 | * call-seq: 106 | * dbm.closed? -> true or false 107 | * 108 | * Returns true if the database is closed, false otherwise. 109 | */ 110 | static VALUE 111 | fdbm_closed(VALUE obj) 112 | { 113 | struct dbmdata *dbmp; 114 | 115 | TypedData_Get_Struct(obj, struct dbmdata, &dbm_type, dbmp); 116 | if (dbmp->di_dbm == 0) 117 | return Qtrue; 118 | 119 | return Qfalse; 120 | } 121 | 122 | static VALUE 123 | fdbm_alloc(VALUE klass) 124 | { 125 | struct dbmdata *dbmp; 126 | 127 | return TypedData_Make_Struct(klass, struct dbmdata, &dbm_type, dbmp); 128 | } 129 | 130 | /* 131 | * call-seq: 132 | * DBM.new(filename[, mode[, flags]]) -> dbm 133 | * 134 | * Open a dbm database with the specified name, which can include a directory 135 | * path. Any file extensions needed will be supplied automatically by the dbm 136 | * library. For example, Berkeley DB appends '.db', and GNU gdbm uses two 137 | * physical files with extensions '.dir' and '.pag'. 138 | * 139 | * The mode should be an integer, as for Unix chmod. 140 | * 141 | * Flags should be one of READER, WRITER, WRCREAT or NEWDB. 142 | */ 143 | static VALUE 144 | fdbm_initialize(int argc, VALUE *argv, VALUE obj) 145 | { 146 | VALUE file, vmode, vflags; 147 | DBM *dbm; 148 | struct dbmdata *dbmp; 149 | int mode, flags = 0; 150 | 151 | TypedData_Get_Struct(obj, struct dbmdata, &dbm_type, dbmp); 152 | if (rb_scan_args(argc, argv, "12", &file, &vmode, &vflags) == 1) { 153 | mode = 0666; /* default value */ 154 | } 155 | else if (NIL_P(vmode)) { 156 | mode = -1; /* return nil if DB not exist */ 157 | } 158 | else { 159 | mode = NUM2INT(vmode); 160 | } 161 | 162 | if (!NIL_P(vflags)) 163 | flags = NUM2INT(vflags); 164 | 165 | FilePathValue(file); 166 | 167 | /* 168 | * Note: 169 | * gdbm 1.10 works with O_CLOEXEC. gdbm 1.9.1 silently ignore it. 170 | */ 171 | #ifndef O_CLOEXEC 172 | # define O_CLOEXEC 0 173 | #endif 174 | 175 | if (flags & RUBY_DBM_RW_BIT) { 176 | flags &= ~RUBY_DBM_RW_BIT; 177 | dbm = dbm_open(RSTRING_PTR(file), flags|O_CLOEXEC, mode); 178 | } 179 | else { 180 | dbm = 0; 181 | if (mode >= 0) { 182 | dbm = dbm_open(RSTRING_PTR(file), O_RDWR|O_CREAT|O_CLOEXEC, mode); 183 | } 184 | if (!dbm) { 185 | dbm = dbm_open(RSTRING_PTR(file), O_RDWR|O_CLOEXEC, 0); 186 | } 187 | if (!dbm) { 188 | dbm = dbm_open(RSTRING_PTR(file), O_RDONLY|O_CLOEXEC, 0); 189 | } 190 | } 191 | 192 | if (dbm) { 193 | /* 194 | * History of dbm_pagfno() and dbm_dirfno() in ndbm and its compatibles. 195 | * (dbm_pagfno() and dbm_dirfno() is not standardized.) 196 | * 197 | * 1986: 4.3BSD provides ndbm. 198 | * It provides dbm_pagfno() and dbm_dirfno() as macros. 199 | * 1991: gdbm-1.5 provides them as functions. 200 | * They returns a same descriptor. 201 | * (Earlier releases may have the functions too.) 202 | * 1991: Net/2 provides Berkeley DB. 203 | * It doesn't provide dbm_pagfno() and dbm_dirfno(). 204 | * 1992: 4.4BSD Alpha provides Berkeley DB with dbm_dirfno() as a function. 205 | * dbm_pagfno() is a macro as DBM_PAGFNO_NOT_AVAILABLE. 206 | * 1997: Berkeley DB 2.0 is released by Sleepycat Software, Inc. 207 | * It defines dbm_pagfno() and dbm_dirfno() as macros. 208 | * 2011: gdbm-1.9 creates a separate dir file. 209 | * dbm_pagfno() and dbm_dirfno() returns different descriptors. 210 | */ 211 | #if defined(HAVE_DBM_PAGFNO) 212 | rb_fd_fix_cloexec(dbm_pagfno(dbm)); 213 | #endif 214 | #if defined(HAVE_DBM_DIRFNO) 215 | rb_fd_fix_cloexec(dbm_dirfno(dbm)); 216 | #endif 217 | 218 | #if defined(RUBYDBM_DB_HEADER) && defined(HAVE_TYPE_DBC) 219 | /* Disable Berkeley DB error messages such as: 220 | * DB->put: attempt to modify a read-only database */ 221 | ((DBC*)dbm)->dbp->set_errfile(((DBC*)dbm)->dbp, NULL); 222 | #endif 223 | } 224 | 225 | if (!dbm) { 226 | if (mode == -1) return Qnil; 227 | rb_sys_fail_str(file); 228 | } 229 | 230 | if (dbmp->di_dbm) 231 | dbm_close(dbmp->di_dbm); 232 | dbmp->di_dbm = dbm; 233 | dbmp->di_size = -1; 234 | 235 | return obj; 236 | } 237 | 238 | /* 239 | * call-seq: 240 | * DBM.open(filename[, mode[, flags]]) -> dbm 241 | * DBM.open(filename[, mode[, flags]]) {|dbm| block} 242 | * 243 | * Open a dbm database and yields it if a block is given. See also 244 | * DBM.new. 245 | */ 246 | static VALUE 247 | fdbm_s_open(int argc, VALUE *argv, VALUE klass) 248 | { 249 | VALUE obj = fdbm_alloc(klass); 250 | 251 | if (NIL_P(fdbm_initialize(argc, argv, obj))) { 252 | return Qnil; 253 | } 254 | 255 | if (rb_block_given_p()) { 256 | return rb_ensure(rb_yield, obj, fdbm_close, obj); 257 | } 258 | 259 | return obj; 260 | } 261 | 262 | static VALUE 263 | fdbm_fetch(VALUE obj, VALUE keystr, VALUE ifnone) 264 | { 265 | datum key, value; 266 | struct dbmdata *dbmp; 267 | DBM *dbm; 268 | long len; 269 | 270 | ExportStringValue(keystr); 271 | len = RSTRING_LEN(keystr); 272 | if (TOO_LONG(len)) goto not_found; 273 | key.dptr = RSTRING_PTR(keystr); 274 | key.dsize = (DSIZE_TYPE)len; 275 | 276 | GetDBM2(obj, dbmp, dbm); 277 | value = dbm_fetch(dbm, key); 278 | if (value.dptr == 0) { 279 | not_found: 280 | if (NIL_P(ifnone) && rb_block_given_p()) { 281 | keystr = rb_str_dup(keystr); 282 | return rb_yield(keystr); 283 | } 284 | return ifnone; 285 | } 286 | return rb_str_new(value.dptr, value.dsize); 287 | } 288 | 289 | /* 290 | * call-seq: 291 | * dbm[key] -> string value or nil 292 | * 293 | * Return a value from the database by locating the key string 294 | * provided. If the key is not found, returns nil. 295 | */ 296 | static VALUE 297 | fdbm_aref(VALUE obj, VALUE keystr) 298 | { 299 | return fdbm_fetch(obj, keystr, Qnil); 300 | } 301 | 302 | /* 303 | * call-seq: 304 | * dbm.fetch(key[, ifnone]) -> value 305 | * 306 | * Return a value from the database by locating the key string 307 | * provided. If the key is not found, returns +ifnone+. If +ifnone+ 308 | * is not given, raises IndexError. 309 | */ 310 | static VALUE 311 | fdbm_fetch_m(int argc, VALUE *argv, VALUE obj) 312 | { 313 | VALUE keystr, valstr, ifnone; 314 | 315 | rb_scan_args(argc, argv, "11", &keystr, &ifnone); 316 | valstr = fdbm_fetch(obj, keystr, ifnone); 317 | if (argc == 1 && !rb_block_given_p() && NIL_P(valstr)) 318 | rb_raise(rb_eIndexError, "key not found"); 319 | 320 | return valstr; 321 | } 322 | 323 | /* 324 | * call-seq: 325 | * dbm.key(value) -> string 326 | * 327 | * Returns the key for the specified value. 328 | */ 329 | static VALUE 330 | fdbm_key(VALUE obj, VALUE valstr) 331 | { 332 | datum key, val; 333 | struct dbmdata *dbmp; 334 | DBM *dbm; 335 | long len; 336 | 337 | ExportStringValue(valstr); 338 | len = RSTRING_LEN(valstr); 339 | if (TOO_LONG(len)) return Qnil; 340 | 341 | GetDBM2(obj, dbmp, dbm); 342 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 343 | val = dbm_fetch(dbm, key); 344 | if ((long)val.dsize == RSTRING_LEN(valstr) && 345 | memcmp(val.dptr, RSTRING_PTR(valstr), val.dsize) == 0) { 346 | return rb_str_new(key.dptr, key.dsize); 347 | } 348 | } 349 | return Qnil; 350 | } 351 | 352 | /* :nodoc: */ 353 | static VALUE 354 | fdbm_index(VALUE hash, VALUE value) 355 | { 356 | rb_warn("DBM#index is deprecated; use DBM#key"); 357 | return fdbm_key(hash, value); 358 | } 359 | 360 | /* 361 | * call-seq: 362 | * dbm.select {|key, value| block} -> array 363 | * 364 | * Returns a new array consisting of the [key, value] pairs for which the code 365 | * block returns true. 366 | */ 367 | static VALUE 368 | fdbm_select(VALUE obj) 369 | { 370 | VALUE new = rb_ary_new(); 371 | datum key, val; 372 | DBM *dbm; 373 | struct dbmdata *dbmp; 374 | 375 | GetDBM2(obj, dbmp, dbm); 376 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 377 | VALUE assoc, v; 378 | val = dbm_fetch(dbm, key); 379 | assoc = rb_assoc_new(rb_str_new(key.dptr, key.dsize), 380 | rb_str_new(val.dptr, val.dsize)); 381 | v = rb_yield(assoc); 382 | if (RTEST(v)) { 383 | rb_ary_push(new, assoc); 384 | } 385 | GetDBM2(obj, dbmp, dbm); 386 | } 387 | 388 | return new; 389 | } 390 | 391 | /* 392 | * call-seq: 393 | * dbm.values_at(key, ...) -> Array 394 | * 395 | * Returns an array containing the values associated with the given keys. 396 | */ 397 | static VALUE 398 | fdbm_values_at(int argc, VALUE *argv, VALUE obj) 399 | { 400 | VALUE new = rb_ary_new2(argc); 401 | int i; 402 | 403 | for (i=0; idi_size = -1; 452 | rb_raise(rb_eDBMError, "dbm_delete failed"); 453 | } 454 | else if (dbmp->di_size >= 0) { 455 | dbmp->di_size--; 456 | } 457 | return valstr; 458 | } 459 | 460 | /* 461 | * call-seq: 462 | * dbm.shift() -> [key, value] 463 | * 464 | * Removes a [key, value] pair from the database, and returns it. 465 | * If the database is empty, returns nil. 466 | * The order in which values are removed/returned is not guaranteed. 467 | */ 468 | static VALUE 469 | fdbm_shift(VALUE obj) 470 | { 471 | datum key, val; 472 | struct dbmdata *dbmp; 473 | DBM *dbm; 474 | VALUE keystr, valstr; 475 | 476 | fdbm_modify(obj); 477 | GetDBM2(obj, dbmp, dbm); 478 | dbmp->di_size = -1; 479 | 480 | key = dbm_firstkey(dbm); 481 | if (!key.dptr) return Qnil; 482 | val = dbm_fetch(dbm, key); 483 | keystr = rb_str_new(key.dptr, key.dsize); 484 | valstr = rb_str_new(val.dptr, val.dsize); 485 | dbm_delete(dbm, key); 486 | 487 | return rb_assoc_new(keystr, valstr); 488 | } 489 | 490 | /* 491 | * call-seq: 492 | * dbm.reject! {|key, value| block} -> self 493 | * dbm.delete_if {|key, value| block} -> self 494 | * 495 | * Deletes all entries for which the code block returns true. 496 | * Returns self. 497 | */ 498 | static VALUE 499 | fdbm_delete_if(VALUE obj) 500 | { 501 | datum key, val; 502 | struct dbmdata *dbmp; 503 | DBM *dbm; 504 | VALUE keystr, valstr; 505 | VALUE ret, ary = rb_ary_tmp_new(0); 506 | int status = 0; 507 | long i, n; 508 | 509 | fdbm_modify(obj); 510 | GetDBM2(obj, dbmp, dbm); 511 | n = dbmp->di_size; 512 | dbmp->di_size = -1; 513 | 514 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 515 | val = dbm_fetch(dbm, key); 516 | keystr = rb_str_new(key.dptr, key.dsize); 517 | OBJ_FREEZE(keystr); 518 | valstr = rb_str_new(val.dptr, val.dsize); 519 | ret = rb_protect(rb_yield, rb_assoc_new(rb_str_dup(keystr), valstr), &status); 520 | if (status != 0) break; 521 | if (RTEST(ret)) rb_ary_push(ary, keystr); 522 | GetDBM2(obj, dbmp, dbm); 523 | } 524 | 525 | for (i = 0; i < RARRAY_LEN(ary); i++) { 526 | keystr = RARRAY_AREF(ary, i); 527 | key.dptr = RSTRING_PTR(keystr); 528 | key.dsize = (DSIZE_TYPE)RSTRING_LEN(keystr); 529 | if (dbm_delete(dbm, key)) { 530 | rb_raise(rb_eDBMError, "dbm_delete failed"); 531 | } 532 | } 533 | if (status) rb_jump_tag(status); 534 | if (n > 0) dbmp->di_size = n - RARRAY_LEN(ary); 535 | rb_ary_clear(ary); 536 | 537 | return obj; 538 | } 539 | 540 | /* 541 | * call-seq: 542 | * dbm.clear 543 | * 544 | * Deletes all data from the database. 545 | */ 546 | static VALUE 547 | fdbm_clear(VALUE obj) 548 | { 549 | datum key; 550 | struct dbmdata *dbmp; 551 | DBM *dbm; 552 | 553 | fdbm_modify(obj); 554 | GetDBM2(obj, dbmp, dbm); 555 | dbmp->di_size = -1; 556 | while (key = dbm_firstkey(dbm), key.dptr) { 557 | if (dbm_delete(dbm, key)) { 558 | rb_raise(rb_eDBMError, "dbm_delete failed"); 559 | } 560 | } 561 | dbmp->di_size = 0; 562 | 563 | return obj; 564 | } 565 | 566 | /* 567 | * call-seq: 568 | * dbm.invert -> hash 569 | * 570 | * Returns a Hash (not a DBM database) created by using each value in the 571 | * database as a key, with the corresponding key as its value. 572 | */ 573 | static VALUE 574 | fdbm_invert(VALUE obj) 575 | { 576 | datum key, val; 577 | struct dbmdata *dbmp; 578 | DBM *dbm; 579 | VALUE keystr, valstr; 580 | VALUE hash = rb_hash_new(); 581 | 582 | GetDBM2(obj, dbmp, dbm); 583 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 584 | val = dbm_fetch(dbm, key); 585 | keystr = rb_str_new(key.dptr, key.dsize); 586 | valstr = rb_str_new(val.dptr, val.dsize); 587 | rb_hash_aset(hash, valstr, keystr); 588 | } 589 | return hash; 590 | } 591 | 592 | static VALUE fdbm_store(VALUE,VALUE,VALUE); 593 | 594 | static VALUE 595 | update_i(RB_BLOCK_CALL_FUNC_ARGLIST(pair, dbm)) 596 | { 597 | const VALUE *ptr; 598 | Check_Type(pair, T_ARRAY); 599 | if (RARRAY_LEN(pair) < 2) { 600 | rb_raise(rb_eArgError, "pair must be [key, value]"); 601 | } 602 | ptr = RARRAY_CONST_PTR(pair); 603 | fdbm_store(dbm, ptr[0], ptr[1]); 604 | return Qnil; 605 | } 606 | 607 | /* 608 | * call-seq: 609 | * dbm.update(obj) 610 | * 611 | * Updates the database with multiple values from the specified object. 612 | * Takes any object which implements the each_pair method, including 613 | * Hash and DBM objects. 614 | */ 615 | static VALUE 616 | fdbm_update(VALUE obj, VALUE other) 617 | { 618 | rb_block_call(other, rb_intern("each_pair"), 0, 0, update_i, obj); 619 | return obj; 620 | } 621 | 622 | /* 623 | * call-seq: 624 | * dbm.replace(obj) 625 | * 626 | * Replaces the contents of the database with the contents of the specified 627 | * object. Takes any object which implements the each_pair method, including 628 | * Hash and DBM objects. 629 | */ 630 | static VALUE 631 | fdbm_replace(VALUE obj, VALUE other) 632 | { 633 | fdbm_clear(obj); 634 | rb_block_call(other, rb_intern("each_pair"), 0, 0, update_i, obj); 635 | return obj; 636 | } 637 | 638 | /* 639 | * call-seq: 640 | * dbm.store(key, value) -> value 641 | * dbm[key] = value 642 | * 643 | * Stores the specified string value in the database, indexed via the 644 | * string key provided. 645 | */ 646 | static VALUE 647 | fdbm_store(VALUE obj, VALUE keystr, VALUE valstr) 648 | { 649 | datum key, val; 650 | struct dbmdata *dbmp; 651 | DBM *dbm; 652 | 653 | fdbm_modify(obj); 654 | keystr = rb_obj_as_string(keystr); 655 | valstr = rb_obj_as_string(valstr); 656 | 657 | key.dptr = RSTRING_PTR(keystr); 658 | key.dsize = RSTRING_DSIZE(keystr); 659 | 660 | val.dptr = RSTRING_PTR(valstr); 661 | val.dsize = RSTRING_DSIZE(valstr); 662 | 663 | GetDBM2(obj, dbmp, dbm); 664 | dbmp->di_size = -1; 665 | if (dbm_store(dbm, key, val, DBM_REPLACE)) { 666 | dbm_clearerr(dbm); 667 | if (errno == EPERM) rb_sys_fail(0); 668 | rb_raise(rb_eDBMError, "dbm_store failed"); 669 | } 670 | 671 | return valstr; 672 | } 673 | 674 | /* 675 | * call-seq: 676 | * dbm.length -> integer 677 | * dbm.size -> integer 678 | * 679 | * Returns the number of entries in the database. 680 | */ 681 | static VALUE 682 | fdbm_length(VALUE obj) 683 | { 684 | datum key; 685 | struct dbmdata *dbmp; 686 | DBM *dbm; 687 | int i = 0; 688 | 689 | GetDBM2(obj, dbmp, dbm); 690 | if (dbmp->di_size > 0) return INT2FIX(dbmp->di_size); 691 | 692 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 693 | i++; 694 | } 695 | dbmp->di_size = i; 696 | 697 | return INT2FIX(i); 698 | } 699 | 700 | /* 701 | * call-seq: 702 | * dbm.empty? 703 | * 704 | * Returns true if the database is empty, false otherwise. 705 | */ 706 | static VALUE 707 | fdbm_empty_p(VALUE obj) 708 | { 709 | datum key; 710 | struct dbmdata *dbmp; 711 | DBM *dbm; 712 | 713 | GetDBM2(obj, dbmp, dbm); 714 | if (dbmp->di_size < 0) { 715 | dbm = dbmp->di_dbm; 716 | 717 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 718 | return Qfalse; 719 | } 720 | } 721 | else { 722 | if (dbmp->di_size) 723 | return Qfalse; 724 | } 725 | return Qtrue; 726 | } 727 | 728 | /* 729 | * call-seq: 730 | * dbm.each_value {|value| block} -> self 731 | * 732 | * Calls the block once for each value string in the database. Returns self. 733 | */ 734 | static VALUE 735 | fdbm_each_value(VALUE obj) 736 | { 737 | datum key, val; 738 | struct dbmdata *dbmp; 739 | DBM *dbm; 740 | 741 | RETURN_ENUMERATOR(obj, 0, 0); 742 | 743 | GetDBM2(obj, dbmp, dbm); 744 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 745 | val = dbm_fetch(dbm, key); 746 | rb_yield(rb_str_new(val.dptr, val.dsize)); 747 | GetDBM2(obj, dbmp, dbm); 748 | } 749 | return obj; 750 | } 751 | 752 | /* 753 | * call-seq: 754 | * dbm.each_key {|key| block} -> self 755 | * 756 | * Calls the block once for each key string in the database. Returns self. 757 | */ 758 | static VALUE 759 | fdbm_each_key(VALUE obj) 760 | { 761 | datum key; 762 | struct dbmdata *dbmp; 763 | DBM *dbm; 764 | 765 | RETURN_ENUMERATOR(obj, 0, 0); 766 | 767 | GetDBM2(obj, dbmp, dbm); 768 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 769 | rb_yield(rb_str_new(key.dptr, key.dsize)); 770 | GetDBM2(obj, dbmp, dbm); 771 | } 772 | return obj; 773 | } 774 | 775 | /* 776 | * call-seq: 777 | * dbm.each_pair {|key,value| block} -> self 778 | * 779 | * Calls the block once for each [key, value] pair in the database. 780 | * Returns self. 781 | */ 782 | static VALUE 783 | fdbm_each_pair(VALUE obj) 784 | { 785 | datum key, val; 786 | DBM *dbm; 787 | struct dbmdata *dbmp; 788 | VALUE keystr, valstr; 789 | 790 | RETURN_ENUMERATOR(obj, 0, 0); 791 | 792 | GetDBM2(obj, dbmp, dbm); 793 | 794 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 795 | val = dbm_fetch(dbm, key); 796 | keystr = rb_str_new(key.dptr, key.dsize); 797 | valstr = rb_str_new(val.dptr, val.dsize); 798 | rb_yield(rb_assoc_new(keystr, valstr)); 799 | GetDBM2(obj, dbmp, dbm); 800 | } 801 | 802 | return obj; 803 | } 804 | 805 | /* 806 | * call-seq: 807 | * dbm.keys -> array 808 | * 809 | * Returns an array of all the string keys in the database. 810 | */ 811 | static VALUE 812 | fdbm_keys(VALUE obj) 813 | { 814 | datum key; 815 | struct dbmdata *dbmp; 816 | DBM *dbm; 817 | VALUE ary; 818 | 819 | GetDBM2(obj, dbmp, dbm); 820 | 821 | ary = rb_ary_new(); 822 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 823 | rb_ary_push(ary, rb_str_new(key.dptr, key.dsize)); 824 | } 825 | 826 | return ary; 827 | } 828 | 829 | /* 830 | * call-seq: 831 | * dbm.values -> array 832 | * 833 | * Returns an array of all the string values in the database. 834 | */ 835 | static VALUE 836 | fdbm_values(VALUE obj) 837 | { 838 | datum key, val; 839 | struct dbmdata *dbmp; 840 | DBM *dbm; 841 | VALUE ary; 842 | 843 | GetDBM2(obj, dbmp, dbm); 844 | ary = rb_ary_new(); 845 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 846 | val = dbm_fetch(dbm, key); 847 | rb_ary_push(ary, rb_str_new(val.dptr, val.dsize)); 848 | } 849 | 850 | return ary; 851 | } 852 | 853 | /* 854 | * call-seq: 855 | * dbm.include?(key) -> boolean 856 | * dbm.has_key?(key) -> boolean 857 | * dbm.member?(key) -> boolean 858 | * dbm.key?(key) -> boolean 859 | * 860 | * Returns true if the database contains the specified key, false otherwise. 861 | */ 862 | static VALUE 863 | fdbm_has_key(VALUE obj, VALUE keystr) 864 | { 865 | datum key, val; 866 | struct dbmdata *dbmp; 867 | DBM *dbm; 868 | long len; 869 | 870 | ExportStringValue(keystr); 871 | len = RSTRING_LEN(keystr); 872 | if (TOO_LONG(len)) return Qfalse; 873 | key.dptr = RSTRING_PTR(keystr); 874 | key.dsize = (DSIZE_TYPE)len; 875 | 876 | GetDBM2(obj, dbmp, dbm); 877 | val = dbm_fetch(dbm, key); 878 | if (val.dptr) return Qtrue; 879 | return Qfalse; 880 | } 881 | 882 | /* 883 | * call-seq: 884 | * dbm.has_value?(value) -> boolean 885 | * dbm.value?(value) -> boolean 886 | * 887 | * Returns true if the database contains the specified string value, false 888 | * otherwise. 889 | */ 890 | static VALUE 891 | fdbm_has_value(VALUE obj, VALUE valstr) 892 | { 893 | datum key, val; 894 | struct dbmdata *dbmp; 895 | DBM *dbm; 896 | long len; 897 | 898 | ExportStringValue(valstr); 899 | len = RSTRING_LEN(valstr); 900 | if (TOO_LONG(len)) return Qfalse; 901 | val.dptr = RSTRING_PTR(valstr); 902 | val.dsize = (DSIZE_TYPE)len; 903 | 904 | GetDBM2(obj, dbmp, dbm); 905 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 906 | val = dbm_fetch(dbm, key); 907 | if ((DSIZE_TYPE)val.dsize == (DSIZE_TYPE)RSTRING_LEN(valstr) && 908 | memcmp(val.dptr, RSTRING_PTR(valstr), val.dsize) == 0) 909 | return Qtrue; 910 | } 911 | return Qfalse; 912 | } 913 | 914 | /* 915 | * call-seq: 916 | * dbm.to_a -> array 917 | * 918 | * Converts the contents of the database to an array of [key, value] arrays, 919 | * and returns it. 920 | */ 921 | static VALUE 922 | fdbm_to_a(VALUE obj) 923 | { 924 | datum key, val; 925 | struct dbmdata *dbmp; 926 | DBM *dbm; 927 | VALUE ary; 928 | 929 | GetDBM2(obj, dbmp, dbm); 930 | ary = rb_ary_new(); 931 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 932 | val = dbm_fetch(dbm, key); 933 | rb_ary_push(ary, rb_assoc_new(rb_str_new(key.dptr, key.dsize), 934 | rb_str_new(val.dptr, val.dsize))); 935 | } 936 | 937 | return ary; 938 | } 939 | 940 | /* 941 | * call-seq: 942 | * dbm.to_hash -> hash 943 | * 944 | * Converts the contents of the database to an in-memory Hash object, and 945 | * returns it. 946 | */ 947 | static VALUE 948 | fdbm_to_hash(VALUE obj) 949 | { 950 | datum key, val; 951 | struct dbmdata *dbmp; 952 | DBM *dbm; 953 | VALUE hash; 954 | 955 | GetDBM2(obj, dbmp, dbm); 956 | hash = rb_hash_new(); 957 | for (key = dbm_firstkey(dbm); key.dptr; key = dbm_nextkey(dbm)) { 958 | val = dbm_fetch(dbm, key); 959 | rb_hash_aset(hash, rb_str_new(key.dptr, key.dsize), 960 | rb_str_new(val.dptr, val.dsize)); 961 | } 962 | 963 | return hash; 964 | } 965 | 966 | /* 967 | * call-seq: 968 | * dbm.reject {|key,value| block} -> Hash 969 | * 970 | * Converts the contents of the database to an in-memory Hash, then calls 971 | * Hash#reject with the specified code block, returning a new Hash. 972 | */ 973 | static VALUE 974 | fdbm_reject(VALUE obj) 975 | { 976 | return rb_hash_delete_if(fdbm_to_hash(obj)); 977 | } 978 | 979 | /* 980 | * == Introduction 981 | * 982 | * The DBM class provides a wrapper to a Unix-style 983 | * {dbm}[https://en.wikipedia.org/wiki/Dbm] or Database Manager library. 984 | * 985 | * Dbm databases do not have tables or columns; they are simple key-value 986 | * data stores, like a Ruby Hash except not resident in RAM. Keys and values 987 | * must be strings. 988 | * 989 | * The exact library used depends on how Ruby was compiled. It could be any 990 | * of the following: 991 | * 992 | * - The original ndbm library is released in 4.3BSD. 993 | * It is based on dbm library in Unix Version 7 but has different API to 994 | * support multiple databases in a process. 995 | * - {Berkeley DB}[https://en.wikipedia.org/wiki/Berkeley_DB] versions 996 | * 1 thru 6, also known as BDB and Sleepycat DB, now owned by Oracle 997 | * Corporation. 998 | * - Berkeley DB 1.x, still found in 4.4BSD derivatives (FreeBSD, OpenBSD, etc). 999 | * - {gdbm}[http://www.gnu.org/software/gdbm/], the GNU implementation of dbm. 1000 | * - {qdbm}[http://fallabs.com/qdbm/index.html], another open source 1001 | * reimplementation of dbm. 1002 | * 1003 | * All of these dbm implementations have their own Ruby interfaces 1004 | * available, which provide richer (but varying) APIs. 1005 | * 1006 | * == Cautions 1007 | * 1008 | * Before you decide to use DBM, there are some issues you should consider: 1009 | * 1010 | * - Each implementation of dbm has its own file format. Generally, dbm 1011 | * libraries will not read each other's files. This makes dbm files 1012 | * a bad choice for data exchange. 1013 | * 1014 | * - Even running the same OS and the same dbm implementation, the database 1015 | * file format may depend on the CPU architecture. For example, files may 1016 | * not be portable between PowerPC and 386, or between 32 and 64 bit Linux. 1017 | * 1018 | * - Different versions of Berkeley DB use different file formats. A change to 1019 | * the OS may therefore break DBM access to existing files. 1020 | * 1021 | * - Data size limits vary between implementations. Original Berkeley DB was 1022 | * limited to 2GB of data. Dbm libraries also sometimes limit the total 1023 | * size of a key/value pair, and the total size of all the keys that hash 1024 | * to the same value. These limits can be as little as 512 bytes. That said, 1025 | * gdbm and recent versions of Berkeley DB do away with these limits. 1026 | * 1027 | * Given the above cautions, DBM is not a good choice for long term storage of 1028 | * important data. It is probably best used as a fast and easy alternative 1029 | * to a Hash for processing large amounts of data. 1030 | * 1031 | * == Example 1032 | * 1033 | * require 'dbm' 1034 | * db = DBM.open('rfcs', 0666, DBM::WRCREAT) 1035 | * db['822'] = 'Standard for the Format of ARPA Internet Text Messages' 1036 | * db['1123'] = 'Requirements for Internet Hosts - Application and Support' 1037 | * db['3068'] = 'An Anycast Prefix for 6to4 Relay Routers' 1038 | * puts db['822'] 1039 | */ 1040 | void 1041 | Init_dbm(void) 1042 | { 1043 | rb_cDBM = rb_define_class("DBM", rb_cObject); 1044 | /* Document-class: DBMError 1045 | * Exception class used to return errors from the dbm library. 1046 | */ 1047 | rb_eDBMError = rb_define_class("DBMError", rb_eStandardError); 1048 | rb_include_module(rb_cDBM, rb_mEnumerable); 1049 | 1050 | rb_define_alloc_func(rb_cDBM, fdbm_alloc); 1051 | rb_define_singleton_method(rb_cDBM, "open", fdbm_s_open, -1); 1052 | 1053 | rb_define_method(rb_cDBM, "initialize", fdbm_initialize, -1); 1054 | rb_define_method(rb_cDBM, "close", fdbm_close, 0); 1055 | rb_define_method(rb_cDBM, "closed?", fdbm_closed, 0); 1056 | rb_define_method(rb_cDBM, "[]", fdbm_aref, 1); 1057 | rb_define_method(rb_cDBM, "fetch", fdbm_fetch_m, -1); 1058 | rb_define_method(rb_cDBM, "[]=", fdbm_store, 2); 1059 | rb_define_method(rb_cDBM, "store", fdbm_store, 2); 1060 | rb_define_method(rb_cDBM, "index", fdbm_index, 1); 1061 | rb_define_method(rb_cDBM, "key", fdbm_key, 1); 1062 | rb_define_method(rb_cDBM, "select", fdbm_select, 0); 1063 | rb_define_method(rb_cDBM, "values_at", fdbm_values_at, -1); 1064 | rb_define_method(rb_cDBM, "length", fdbm_length, 0); 1065 | rb_define_method(rb_cDBM, "size", fdbm_length, 0); 1066 | rb_define_method(rb_cDBM, "empty?", fdbm_empty_p, 0); 1067 | rb_define_method(rb_cDBM, "each", fdbm_each_pair, 0); 1068 | rb_define_method(rb_cDBM, "each_value", fdbm_each_value, 0); 1069 | rb_define_method(rb_cDBM, "each_key", fdbm_each_key, 0); 1070 | rb_define_method(rb_cDBM, "each_pair", fdbm_each_pair, 0); 1071 | rb_define_method(rb_cDBM, "keys", fdbm_keys, 0); 1072 | rb_define_method(rb_cDBM, "values", fdbm_values, 0); 1073 | rb_define_method(rb_cDBM, "shift", fdbm_shift, 0); 1074 | rb_define_method(rb_cDBM, "delete", fdbm_delete, 1); 1075 | rb_define_method(rb_cDBM, "delete_if", fdbm_delete_if, 0); 1076 | rb_define_method(rb_cDBM, "reject!", fdbm_delete_if, 0); 1077 | rb_define_method(rb_cDBM, "reject", fdbm_reject, 0); 1078 | rb_define_method(rb_cDBM, "clear", fdbm_clear, 0); 1079 | rb_define_method(rb_cDBM, "invert", fdbm_invert, 0); 1080 | rb_define_method(rb_cDBM, "update", fdbm_update, 1); 1081 | rb_define_method(rb_cDBM, "replace", fdbm_replace, 1); 1082 | 1083 | rb_define_method(rb_cDBM, "include?", fdbm_has_key, 1); 1084 | rb_define_method(rb_cDBM, "has_key?", fdbm_has_key, 1); 1085 | rb_define_method(rb_cDBM, "member?", fdbm_has_key, 1); 1086 | rb_define_method(rb_cDBM, "has_value?", fdbm_has_value, 1); 1087 | rb_define_method(rb_cDBM, "key?", fdbm_has_key, 1); 1088 | rb_define_method(rb_cDBM, "value?", fdbm_has_value, 1); 1089 | 1090 | rb_define_method(rb_cDBM, "to_a", fdbm_to_a, 0); 1091 | rb_define_method(rb_cDBM, "to_hash", fdbm_to_hash, 0); 1092 | 1093 | /* Indicates that dbm_open() should open the database in read-only mode */ 1094 | rb_define_const(rb_cDBM, "READER", INT2FIX(O_RDONLY|RUBY_DBM_RW_BIT)); 1095 | 1096 | /* Indicates that dbm_open() should open the database in read/write mode */ 1097 | rb_define_const(rb_cDBM, "WRITER", INT2FIX(O_RDWR|RUBY_DBM_RW_BIT)); 1098 | 1099 | /* Indicates that dbm_open() should open the database in read/write mode, 1100 | * and create it if it does not already exist 1101 | */ 1102 | rb_define_const(rb_cDBM, "WRCREAT", INT2FIX(O_RDWR|O_CREAT|RUBY_DBM_RW_BIT)); 1103 | 1104 | /* Indicates that dbm_open() should open the database in read/write mode, 1105 | * create it if it does not already exist, and delete all contents if it 1106 | * does already exist. 1107 | */ 1108 | rb_define_const(rb_cDBM, "NEWDB", INT2FIX(O_RDWR|O_CREAT|O_TRUNC|RUBY_DBM_RW_BIT)); 1109 | 1110 | { 1111 | VALUE version; 1112 | #if defined(_DBM_IOERR) 1113 | version = rb_str_new2("ndbm (4.3BSD)"); 1114 | #elif defined(RUBYDBM_GDBM_HEADER) 1115 | # if defined(HAVE_DECLARED_LIBVAR_GDBM_VERSION) 1116 | /* since gdbm 1.9 */ 1117 | version = rb_str_new2(gdbm_version); 1118 | # elif defined(HAVE_UNDECLARED_LIBVAR_GDBM_VERSION) 1119 | /* ndbm.h doesn't declare gdbm_version until gdbm 1.8.3. 1120 | * See extconf.rb for more information. */ 1121 | RUBY_EXTERN char *gdbm_version; 1122 | version = rb_str_new2(gdbm_version); 1123 | # else 1124 | version = rb_str_new2("GDBM (unknown)"); 1125 | # endif 1126 | #elif defined(RUBYDBM_DB_HEADER) 1127 | # if defined(HAVE_DB_VERSION) 1128 | /* The version of the dbm library, if using Berkeley DB */ 1129 | version = rb_str_new2(db_version(NULL, NULL, NULL)); 1130 | # else 1131 | version = rb_str_new2("Berkeley DB (unknown)"); 1132 | # endif 1133 | #elif defined(_RELIC_H) 1134 | # if defined(HAVE_DPVERSION) 1135 | version = rb_sprintf("QDBM %s", dpversion); 1136 | # else 1137 | version = rb_str_new2("QDBM (unknown)"); 1138 | # endif 1139 | #else 1140 | version = rb_str_new2("ndbm (unknown)"); 1141 | #endif 1142 | /* 1143 | * Identifies ndbm library version. 1144 | * 1145 | * Examples: 1146 | * 1147 | * - "ndbm (4.3BSD)" 1148 | * - "Berkeley DB 4.8.30: (April 9, 2010)" 1149 | * - "Berkeley DB (unknown)" (4.4BSD, maybe) 1150 | * - "GDBM version 1.8.3. 10/15/2002 (built Jul 1 2011 12:32:45)" 1151 | * - "QDBM 1.8.78" 1152 | * 1153 | */ 1154 | rb_define_const(rb_cDBM, "VERSION", version); 1155 | } 1156 | } 1157 | -------------------------------------------------------------------------------- /ext/dbm/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # configure option: 3 | # --with-dbm-type=COMMA-SEPARATED-NDBM-TYPES 4 | # 5 | # ndbm type: 6 | # libc ndbm compatible library in libc. 7 | # db Berkeley DB (libdb) 8 | # db2 Berkeley DB (libdb2) 9 | # db1 Berkeley DB (libdb1) 10 | # db6 Berkeley DB (libdb6) 11 | # db5 Berkeley DB (libdb5) 12 | # db4 Berkeley DB (libdb4) 13 | # db3 Berkeley DB (libdb3) 14 | # gdbm_compat GDBM since 1.8.1 (libgdbm_compat) 15 | # gdbm GDBM until 1.8.0 (libgdbm) 16 | # qdbm QDBM (libqdbm) 17 | # ndbm Some legacy OS may have libndbm. 18 | 19 | # :stopdoc: 20 | require 'mkmf' 21 | 22 | dir_config("dbm") 23 | 24 | if dblib = with_config("dbm-type", nil) 25 | dblib = dblib.split(/[ ,]+/) 26 | else 27 | dblib = %w(libc db db2 db1 db6 db5 db4 db3 gdbm_compat gdbm qdbm) 28 | end 29 | 30 | headers = { 31 | "libc" => ["ndbm.h"], # 4.3BSD original ndbm, Berkeley DB 1 in 4.4BSD libc. 32 | "db" => ["db.h"], 33 | "db1" => ["db1/ndbm.h", "db1.h", "ndbm.h"], 34 | "db2" => ["db2/db.h", "db2.h", "db.h"], 35 | "db3" => ["db3/db.h", "db3.h", "db.h"], 36 | "db4" => ["db4/db.h", "db4.h", "db.h"], 37 | "db5" => ["db5/db.h", "db5.h", "db.h"], 38 | "db6" => ["db6/db.h", "db6.h", "db.h"], 39 | "gdbm_compat" => ["gdbm-ndbm.h", "gdbm/ndbm.h", "ndbm.h"], # GDBM since 1.8.1 40 | "gdbm" => ["gdbm-ndbm.h", "gdbm/ndbm.h", "ndbm.h"], # GDBM until 1.8.0 41 | "qdbm" => ["qdbm/relic.h", "relic.h"], 42 | } 43 | 44 | class << headers 45 | attr_accessor :found 46 | attr_accessor :defs 47 | end 48 | headers.found = [] 49 | headers.defs = nil 50 | 51 | def headers.db_check(db, hdr) 52 | old_libs = $libs.dup 53 | old_defs = $defs.dup 54 | result = db_check2(db, hdr) 55 | if !result 56 | $libs = old_libs 57 | $defs = old_defs 58 | end 59 | result 60 | end 61 | 62 | def have_declared_libvar(var, headers = nil, opt = "", &b) 63 | checking_for checking_message([*var].compact.join(' '), headers, opt) do 64 | try_declared_libvar(var, headers, opt, &b) 65 | end 66 | end 67 | 68 | def try_declared_libvar(var, headers = nil, opt = "", &b) 69 | if try_link(<<"SRC", opt, &b) 70 | #{cpp_include(headers)} 71 | /*top*/ 72 | int main(int argc, char *argv[]) { 73 | void *conftest_var = &#{var}; 74 | return 0; 75 | } 76 | SRC 77 | $defs.push(format("-DHAVE_DECLARED_LIBVAR_%s", var.tr_cpp)) 78 | true 79 | else 80 | false 81 | end 82 | end 83 | 84 | def have_undeclared_libvar(var, headers = nil, opt = "", &b) 85 | checking_for checking_message([*var].compact.join(' '), headers, opt) do 86 | try_undeclared_libvar(var, headers, opt, &b) 87 | end 88 | end 89 | 90 | def try_undeclared_libvar(var, headers = nil, opt = "", &b) 91 | var, type = *var 92 | if try_link(<<"SRC", opt, &b) 93 | #{cpp_include(headers)} 94 | /*top*/ 95 | int main(int argc, char *argv[]) { 96 | typedef #{type || 'int'} conftest_type; 97 | extern conftest_type #{var}; 98 | conftest_type *conftest_var = &#{var}; 99 | return 0; 100 | } 101 | SRC 102 | $defs.push(format("-DHAVE_UNDECLARED_LIBVAR_%s", var.tr_cpp)) 103 | true 104 | else 105 | false 106 | end 107 | end 108 | 109 | def have_empty_macro_dbm_clearerr(headers = nil, opt = "", &b) 110 | checking_for checking_message('empty macro of dbm_clearerr(foobarbaz)', 111 | headers, opt) do 112 | try_toplevel('dbm_clearerr(foobarbaz)', headers, opt, &b) 113 | end 114 | end 115 | 116 | def try_toplevel(src, headers = nil, opt = "", &b) 117 | if try_compile(<<"SRC", opt, &b) 118 | #{cpp_include(headers)} 119 | /*top*/ 120 | #{src} 121 | SRC 122 | true 123 | else 124 | false 125 | end 126 | end 127 | 128 | 129 | def headers.db_check2(db, hdr) 130 | $defs.push(%{-DRUBYDBM_DBM_HEADER='"#{hdr}"'}) 131 | $defs.push(%{-DRUBYDBM_DBM_TYPE='"#{db}"'}) 132 | 133 | hsearch = nil 134 | 135 | case db 136 | when /^db[2-6]?$/ 137 | hsearch = "-DDB_DBM_HSEARCH" 138 | when "gdbm_compat" 139 | have_library("gdbm") or return false 140 | end 141 | 142 | if !have_type("DBM", hdr, hsearch) 143 | return false 144 | end 145 | 146 | # 'libc' means ndbm is provided by libc. 147 | # 4.3BSD original ndbm is contained in libc. 148 | # 4.4BSD (and its derivatives such as NetBSD) contains Berkeley DB 1 in libc. 149 | if !(db == 'libc' ? have_func('dbm_open("", 0, 0)', hdr, hsearch) : 150 | have_library(db, 'dbm_open("", 0, 0)', hdr, hsearch)) 151 | return false 152 | end 153 | 154 | # Skip a mismatch of Berkeley DB's ndbm.h and old GDBM library. 155 | # 156 | # dbm_clearerr() should be available for any ndbm implementation. 157 | # It is available since the original (4.3BSD) ndbm and standardized by POSIX. 158 | # 159 | # However "can't resolve symbol 'dbm_clearerr'" problem may be caused by 160 | # header/library mismatch: Berkeley DB ndbm.h and GDBM library until 1.8.3. 161 | # GDBM (until 1.8.3) provides dbm_clearerr() as a empty macro in the header 162 | # and the library don't provide dbm_clearerr(). 163 | # Berkeley DB provides dbm_clearerr() as a usual function. 164 | # So Berkeley DB header with GDBM library causes the problem. 165 | # 166 | if !have_func('dbm_clearerr((DBM *)0)', hdr, hsearch) 167 | return false 168 | end 169 | 170 | # Berkeley DB's ndbm.h (since 1.85 at least) defines DBM_SUFFIX. 171 | # Note that _DB_H_ is not defined on Mac OS X because 172 | # it uses Berkeley DB 1 but ndbm.h doesn't include db.h. 173 | have_db_header = have_macro('DBM_SUFFIX', hdr, hsearch) 174 | 175 | # Old GDBM's ndbm.h, until 1.8.3, defines dbm_clearerr as a macro which 176 | # expands to no tokens. 177 | have_gdbm_header1 = have_empty_macro_dbm_clearerr(hdr, hsearch) 178 | 179 | # Recent GDBM's ndbm.h, since 1.9, includes gdbm.h and it defines _GDBM_H_. 180 | # ndbm compatibility layer of GDBM is provided by libgdbm (until 1.8.0) 181 | # and libgdbm_compat (since 1.8.1). 182 | have_gdbm_header2 = have_macro('_GDBM_H_', hdr, hsearch) 183 | 184 | # 4.3BSD's ndbm.h defines _DBM_IOERR. 185 | # The original ndbm is provided by libc in 4.3BSD. 186 | have_ndbm_header = have_macro('_DBM_IOERR', hdr, hsearch) 187 | 188 | # GDBM provides ndbm functions in libgdbm_compat since GDBM 1.8.1. 189 | # GDBM's ndbm.h defines _GDBM_H_ since GDBM 1.9. 190 | # If _GDBM_H_ is defined, 'gdbm_compat' is required and reject 'gdbm'. 191 | if have_gdbm_header2 && db == 'gdbm' 192 | return false 193 | end 194 | 195 | if have_db_header 196 | $defs.push('-DRUBYDBM_DB_HEADER') 197 | end 198 | 199 | have_gdbm_header = have_gdbm_header1 | have_gdbm_header2 200 | if have_gdbm_header 201 | $defs.push('-DRUBYDBM_GDBM_HEADER') 202 | end 203 | 204 | # ndbm.h is provided by the original (4.3BSD) ndbm, 205 | # Berkeley DB 1 in libc of 4.4BSD and 206 | # ndbm compatibility layer of GDBM. 207 | # So, try to check header/library mismatch. 208 | # 209 | # Several (possibly historical) distributions provides libndbm. 210 | # It may be Berkeley DB, GDBM or 4.3BSD ndbm. 211 | # So mismatch check is not performed for that. 212 | # Note that libndbm is searched only when --with-dbm-type=ndbm is 213 | # given for configure. 214 | # 215 | if hdr == 'ndbm.h' && db != 'libc' && db != 'ndbm' 216 | if /\Adb\d?\z/ !~ db && have_db_header 217 | return false 218 | end 219 | 220 | if /\Agdbm/ !~ db && have_gdbm_header 221 | return false 222 | end 223 | 224 | if have_ndbm_header 225 | return false 226 | end 227 | end 228 | 229 | # Berkeley DB 230 | have_func('db_version((int *)0, (int *)0, (int *)0)', hdr, hsearch) 231 | 232 | # GDBM 233 | have_gdbm_version = have_declared_libvar("gdbm_version", hdr, hsearch) 234 | # gdbm_version is available since very old version (GDBM 1.5 at least). 235 | # However it is not declared by ndbm.h until GDBM 1.8.3. 236 | # We can't include both ndbm.h and gdbm.h because they both define datum type. 237 | # ndbm.h includes gdbm.h and gdbm_version is declared since GDBM 1.9. 238 | have_gdbm_version |= have_undeclared_libvar(["gdbm_version", "char *"], hdr, hsearch) 239 | 240 | # QDBM 241 | have_var("dpversion", hdr, hsearch) 242 | 243 | # detect mismatch between GDBM header and other library. 244 | # If GDBM header is included, GDBM library should be linked. 245 | if have_gdbm_header && !have_gdbm_version 246 | return false 247 | end 248 | 249 | # DBC type is required to disable error messages by Berkeley DB 2 or later. 250 | if have_db_header 251 | have_type("DBC", hdr, hsearch) 252 | end 253 | 254 | if hsearch 255 | $defs << hsearch 256 | @defs = hsearch 257 | end 258 | $defs << '-DDBM_HDR="<'+hdr+'>"' 259 | @found << hdr 260 | 261 | puts "header: #{hdr}" 262 | puts "library: #{db}" 263 | 264 | true 265 | end 266 | 267 | if dblib.any? {|db| headers.fetch(db, ["ndbm.h"]).any? {|hdr| headers.db_check(db, hdr) } } 268 | have_header("cdefs.h") 269 | have_header("sys/cdefs.h") 270 | have_func("dbm_pagfno((DBM *)0)", headers.found, headers.defs) 271 | have_func("dbm_dirfno((DBM *)0)", headers.found, headers.defs) 272 | convertible_int("datum.dsize", headers.found, headers.defs) 273 | checking_for("sizeof(DBM) is available") { 274 | if try_compile(< 277 | #endif 278 | #ifdef HAVE_SYS_CDEFS_H 279 | # include 280 | #endif 281 | #include DBM_HDR 282 | 283 | const int sizeof_DBM = (int)sizeof(DBM); 284 | SRC 285 | $defs << '-DDBM_SIZEOF_DBM=sizeof(DBM)' 286 | else 287 | $defs << '-DDBM_SIZEOF_DBM=0' 288 | end 289 | } 290 | create_makefile("dbm") 291 | end 292 | # :startdoc: 293 | -------------------------------------------------------------------------------- /test/dbm/test_dbm.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'test/unit' 3 | require 'tmpdir' 4 | 5 | begin 6 | require 'dbm' 7 | rescue LoadError 8 | end 9 | 10 | if defined? DBM 11 | require 'tmpdir' 12 | require 'fileutils' 13 | 14 | class TestDBM_RDONLY < Test::Unit::TestCase 15 | def TestDBM_RDONLY.uname_s 16 | require 'rbconfig' 17 | case RbConfig::CONFIG['target_os'] 18 | when 'cygwin' 19 | require 'etc' 20 | Etc.uname[:sysname] 21 | else 22 | RbConfig::CONFIG['target_os'] 23 | end 24 | end 25 | SYSTEM = uname_s 26 | 27 | def setup 28 | @tmpdir = Dir.mktmpdir("tmptest_dbm") 29 | @prefix = "tmptest_dbm_#{$$}" 30 | @path = "#{@tmpdir}/#{@prefix}_" 31 | 32 | # prepare to make readonly DBM file 33 | DBM.open("#{@tmpdir}/#{@prefix}_rdonly") {|dbm| 34 | dbm['foo'] = 'FOO' 35 | } 36 | 37 | File.chmod(0400, *Dir.glob("#{@tmpdir}/#{@prefix}_rdonly.*")) 38 | 39 | assert_instance_of(DBM, @dbm_rdonly = DBM.new("#{@tmpdir}/#{@prefix}_rdonly", nil)) 40 | end 41 | def teardown 42 | assert_nil(@dbm_rdonly.close) 43 | ObjectSpace.each_object(DBM) do |obj| 44 | obj.close unless obj.closed? 45 | end 46 | FileUtils.remove_entry_secure @tmpdir 47 | end 48 | 49 | def test_delete_rdonly 50 | skip("skipped because root can read anything") if Process.uid == 0 51 | 52 | if /^CYGWIN_9/ !~ SYSTEM 53 | assert_raise(DBMError) { 54 | @dbm_rdonly.delete("foo") 55 | } 56 | 57 | assert_nil(@dbm_rdonly.delete("bar")) 58 | end 59 | end 60 | 61 | def test_fetch_not_found 62 | notfound = nil 63 | result = Object.new 64 | assert_same(result, @dbm_rdonly.fetch("bar") {|k| notfound = k; result}) 65 | assert_equal("bar", notfound) 66 | end 67 | end 68 | 69 | class TestDBM < Test::Unit::TestCase 70 | def setup 71 | @tmpdir = Dir.mktmpdir("tmptest_dbm") 72 | @prefix = "tmptest_dbm_#{$$}" 73 | @path = "#{@tmpdir}/#{@prefix}_" 74 | assert_instance_of(DBM, @dbm = DBM.new(@path)) 75 | end 76 | def teardown 77 | assert_nil(@dbm.close) unless @dbm.closed? 78 | ObjectSpace.each_object(DBM) do |obj| 79 | obj.close unless obj.closed? 80 | end 81 | FileUtils.remove_entry_secure @tmpdir 82 | end 83 | 84 | def check_size(expect, dbm=@dbm) 85 | assert_equal(expect, dbm.size) 86 | n = 0 87 | dbm.each { n+=1 } 88 | assert_equal(expect, n) 89 | if expect == 0 90 | assert_equal(true, dbm.empty?) 91 | else 92 | assert_equal(false, dbm.empty?) 93 | end 94 | end 95 | 96 | def test_dbmfile_suffix 97 | @dbm.close 98 | prefix = File.basename(@path) 99 | suffixes = Dir.entries(@tmpdir).grep(/\A#{Regexp.escape prefix}/) { $' }.sort 100 | pagname = "#{@path}.pag" 101 | dirname = "#{@path}.dir" 102 | dbname = "#{@path}.db" 103 | case DBM::VERSION 104 | when /\bNDBM\b/ 105 | assert_equal(%w[.dir .pag], suffixes) 106 | assert(File.zero?(pagname)) 107 | assert(File.zero?(dirname)) 108 | when /\bGDBM\b/ 109 | assert_equal(%w[.dir .pag], suffixes) 110 | assert(!File.zero?(pagname)) 111 | assert(!File.zero?(dirname)) 112 | pag = File.binread(pagname, 16) 113 | pag_magics = [ 114 | 0x13579ace, # GDBM_OMAGIC 115 | 0x13579acd, # GDBM_MAGIC32 116 | 0x13579acf, # GDBM_MAGIC64 117 | ] 118 | assert_operator(pag_magics, :include?, 119 | pag.unpack("i")[0]) # native endian, native int. 120 | if !File.identical?(pagname, dirname) 121 | dir = File.binread(dirname, 16) 122 | assert_equal("GDBM", dir[0, 4]) 123 | end 124 | when /\bBerkeley DB\b/ 125 | assert_equal(%w[.db], suffixes) 126 | assert(!File.zero?(dbname)) 127 | db = File.binread(dbname, 16) 128 | assert(db[0,4].unpack("N") == [0x00061561] || # Berkeley DB 1 129 | db[12,4].unpack("L") == [0x00061561]) # Berkeley DBM 2 or later. 130 | when /\bQDBM\b/ 131 | assert_equal(%w[.dir .pag], suffixes) 132 | assert(!File.zero?(pagname)) 133 | assert(!File.zero?(dirname)) 134 | dir = File.binread(dirname, 16) 135 | assert_equal("[depot]\0\v", dir[0, 9]) 136 | pag = File.binread(pagname, 16) 137 | if [1].pack("s") == "\x00\x01" # big endian 138 | assert_equal("[DEPOT]\n\f", pag[0, 9]) 139 | else # little endian 140 | assert_equal("[depot]\n\f", pag[0, 9]) 141 | end 142 | end 143 | if suffixes == %w[.db] 144 | assert_match(/\bBerkeley DB\b/, DBM::VERSION) 145 | end 146 | end 147 | 148 | def test_s_new_has_no_block 149 | # DBM.new ignore the block 150 | foo = true 151 | assert_instance_of(DBM, dbm = DBM.new("#{@tmpdir}/#{@prefix}") { foo = false }) 152 | assert_equal(foo, true) 153 | assert_nil(dbm.close) 154 | end 155 | 156 | def test_s_open_no_create 157 | skip "dbm_open() is broken on libgdbm 1.8.0 or prior (#{DBM::VERSION})" if /GDBM version 1\.(?:[0-7]\b|8\.0)/ =~ DBM::VERSION 158 | assert_nil(dbm = DBM.open("#{@tmpdir}/#{@prefix}", nil)) 159 | ensure 160 | dbm.close if dbm 161 | end 162 | 163 | def test_s_open_with_block 164 | assert_equal(DBM.open("#{@tmpdir}/#{@prefix}") { :foo }, :foo) 165 | end 166 | 167 | def test_close 168 | assert_instance_of(DBM, dbm = DBM.open("#{@tmpdir}/#{@prefix}")) 169 | assert_nil(dbm.close) 170 | 171 | # closed DBM file 172 | assert_raise(DBMError) { dbm.close } 173 | end 174 | 175 | def test_aref 176 | assert_equal('bar', @dbm['foo'] = 'bar') 177 | assert_equal('bar', @dbm['foo']) 178 | 179 | assert_nil(@dbm['bar']) 180 | end 181 | 182 | def test_fetch 183 | assert_equal('bar', @dbm['foo']='bar') 184 | assert_equal('bar', @dbm.fetch('foo')) 185 | 186 | # key not found 187 | assert_raise(IndexError) { 188 | @dbm.fetch('bar') 189 | } 190 | 191 | # test for `ifnone' arg 192 | assert_equal('baz', @dbm.fetch('bar', 'baz')) 193 | 194 | # test for `ifnone' block 195 | assert_equal('foobar', @dbm.fetch('bar') {|key| 'foo' + key }) 196 | end 197 | 198 | def test_aset 199 | num = 0 200 | 2.times {|i| 201 | assert_equal('foo', @dbm['foo'] = 'foo') 202 | assert_equal('foo', @dbm['foo']) 203 | assert_equal('bar', @dbm['foo'] = 'bar') 204 | assert_equal('bar', @dbm['foo']) 205 | 206 | num += 1 if i == 0 207 | assert_equal(num, @dbm.size) 208 | 209 | # assign nil 210 | assert_equal('', @dbm['bar'] = '') 211 | assert_equal('', @dbm['bar']) 212 | 213 | num += 1 if i == 0 214 | assert_equal(num, @dbm.size) 215 | 216 | # empty string 217 | assert_equal('', @dbm[''] = '') 218 | assert_equal('', @dbm['']) 219 | 220 | num += 1 if i == 0 221 | assert_equal(num, @dbm.size) 222 | 223 | # Integer 224 | assert_equal('200', @dbm['100'] = '200') 225 | assert_equal('200', @dbm['100']) 226 | 227 | num += 1 if i == 0 228 | assert_equal(num, @dbm.size) 229 | 230 | # Big key and value 231 | assert_equal('y' * 100, @dbm['x' * 100] = 'y' * 100) 232 | assert_equal('y' * 100, @dbm['x' * 100]) 233 | 234 | num += 1 if i == 0 235 | assert_equal(num, @dbm.size) 236 | } 237 | end 238 | 239 | def test_key 240 | assert_equal('bar', @dbm['foo'] = 'bar') 241 | assert_equal('foo', @dbm.key('bar')) 242 | assert_nil(@dbm['bar']) 243 | end 244 | 245 | def test_values_at 246 | keys = %w(foo bar baz) 247 | values = %w(FOO BAR BAZ) 248 | @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values 249 | assert_equal(values.reverse, @dbm.values_at(*keys.reverse)) 250 | end 251 | 252 | def test_select_with_block 253 | keys = %w(foo bar baz) 254 | values = %w(FOO BAR BAZ) 255 | @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values 256 | ret = @dbm.select {|k,v| 257 | assert_equal(k.upcase, v) 258 | k != "bar" 259 | } 260 | assert_equal([['baz', 'BAZ'], ['foo', 'FOO']], 261 | ret.sort) 262 | end 263 | 264 | def test_length 265 | num = 10 266 | assert_equal(0, @dbm.size) 267 | num.times {|i| 268 | i = i.to_s 269 | @dbm[i] = i 270 | } 271 | assert_equal(num, @dbm.size) 272 | 273 | @dbm.shift 274 | 275 | assert_equal(num - 1, @dbm.size) 276 | end 277 | 278 | def test_empty? 279 | assert_equal(true, @dbm.empty?) 280 | @dbm['foo'] = 'FOO' 281 | assert_equal(false, @dbm.empty?) 282 | end 283 | 284 | def test_each_pair 285 | n = 0 286 | @dbm.each_pair { n += 1 } 287 | assert_equal(0, n) 288 | 289 | keys = %w(foo bar baz) 290 | values = %w(FOO BAR BAZ) 291 | 292 | @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values 293 | 294 | n = 0 295 | ret = @dbm.each_pair {|key, val| 296 | assert_not_nil(i = keys.index(key)) 297 | assert_equal(val, values[i]) 298 | 299 | n += 1 300 | } 301 | assert_equal(keys.size, n) 302 | assert_equal(@dbm, ret) 303 | end 304 | 305 | def test_each_value 306 | n = 0 307 | @dbm.each_value { n += 1 } 308 | assert_equal(0, n) 309 | 310 | keys = %w(foo bar baz) 311 | values = %w(FOO BAR BAZ) 312 | 313 | @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values 314 | 315 | n = 0 316 | ret = @dbm.each_value {|val| 317 | assert_not_nil(key = @dbm.key(val)) 318 | assert_not_nil(i = keys.index(key)) 319 | assert_equal(val, values[i]) 320 | 321 | n += 1 322 | } 323 | assert_equal(keys.size, n) 324 | assert_equal(@dbm, ret) 325 | end 326 | 327 | def test_each_key 328 | n = 0 329 | @dbm.each_key { n += 1 } 330 | assert_equal(0, n) 331 | 332 | keys = %w(foo bar baz) 333 | values = %w(FOO BAR BAZ) 334 | 335 | @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values 336 | 337 | n = 0 338 | ret = @dbm.each_key {|key| 339 | assert_not_nil(i = keys.index(key)) 340 | assert_equal(@dbm[key], values[i]) 341 | 342 | n += 1 343 | } 344 | assert_equal(keys.size, n) 345 | assert_equal(@dbm, ret) 346 | end 347 | 348 | def test_keys 349 | assert_equal([], @dbm.keys) 350 | 351 | keys = %w(foo bar baz) 352 | values = %w(FOO BAR BAZ) 353 | 354 | @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values 355 | 356 | assert_equal(keys.sort, @dbm.keys.sort) 357 | assert_equal(values.sort, @dbm.values.sort) 358 | end 359 | 360 | def test_values 361 | test_keys 362 | end 363 | 364 | def test_shift 365 | assert_nil(@dbm.shift) 366 | assert_equal(0, @dbm.size) 367 | 368 | keys = %w(foo bar baz) 369 | values = %w(FOO BAR BAZ) 370 | 371 | @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values 372 | 373 | ret_keys = [] 374 | ret_values = [] 375 | while ret = @dbm.shift 376 | ret_keys.push ret[0] 377 | ret_values.push ret[1] 378 | 379 | assert_equal(keys.size - ret_keys.size, @dbm.size) 380 | end 381 | 382 | assert_equal(keys.sort, ret_keys.sort) 383 | assert_equal(values.sort, ret_values.sort) 384 | end 385 | 386 | def test_delete 387 | keys = %w(foo bar baz) 388 | values = %w(FOO BAR BAZ) 389 | key = keys[1] 390 | 391 | assert_nil(@dbm.delete(key)) 392 | assert_equal(0, @dbm.size) 393 | 394 | @dbm[keys[0]], @dbm[keys[1]], @dbm[keys[2]] = values 395 | 396 | assert_equal('BAR', @dbm.delete(key)) 397 | assert_nil(@dbm[key]) 398 | assert_equal(2, @dbm.size) 399 | 400 | assert_nil(@dbm.delete(key)) 401 | end 402 | 403 | def test_delete_with_block 404 | key = 'no called block' 405 | @dbm[key] = 'foo' 406 | assert_equal('foo', @dbm.delete(key) {|k| k.replace 'called block'; :blockval}) 407 | assert_equal(0, @dbm.size) 408 | 409 | key = 'no called block'.dup 410 | assert_equal(:blockval, @dbm.delete(key) {|k| k.replace 'called block'; :blockval}) 411 | assert_equal(0, @dbm.size) 412 | end 413 | 414 | def test_delete_if 415 | v = "0" 416 | 100.times {@dbm[v] = v; v = v.next} 417 | 418 | ret = @dbm.delete_if {|key, val| key.to_i < 50} 419 | assert_equal(@dbm, ret) 420 | check_size(50, @dbm) 421 | 422 | ret = @dbm.delete_if {|key, val| key.to_i >= 50} 423 | assert_equal(@dbm, ret) 424 | check_size(0, @dbm) 425 | 426 | # break 427 | v = "0" 428 | 100.times {@dbm[v] = v; v = v.next} 429 | check_size(100, @dbm) 430 | n = 0; 431 | @dbm.delete_if {|key, val| 432 | break if n > 50 433 | n+=1 434 | true 435 | } 436 | assert_equal(51, n) 437 | check_size(49, @dbm) 438 | 439 | @dbm.clear 440 | 441 | # raise 442 | v = "0" 443 | 100.times {@dbm[v] = v; v = v.next} 444 | check_size(100, @dbm) 445 | n = 0; 446 | begin 447 | @dbm.delete_if {|key, val| 448 | raise "runtime error" if n > 50 449 | n+=1 450 | true 451 | } 452 | rescue RuntimeError 453 | end 454 | assert_equal(51, n) 455 | check_size(49, @dbm) 456 | end 457 | 458 | def test_reject 459 | v = "0" 460 | 100.times {@dbm[v] = v; v = v.next} 461 | 462 | hash = @dbm.reject {|key, val| key.to_i < 50} 463 | assert_instance_of(Hash, hash) 464 | assert_equal(100, @dbm.size) 465 | 466 | assert_equal(50, hash.size) 467 | hash.each_pair {|key,val| 468 | assert_equal(false, key.to_i < 50) 469 | assert_equal(key, val) 470 | } 471 | 472 | hash = @dbm.reject {|key, val| key.to_i < 100} 473 | assert_instance_of(Hash, hash) 474 | assert_equal(true, hash.empty?) 475 | end 476 | 477 | def test_clear 478 | v = "1" 479 | 100.times {v = v.next; @dbm[v] = v} 480 | 481 | assert_equal(@dbm, @dbm.clear) 482 | 483 | # validate DBM#size 484 | i = 0 485 | @dbm.each { i += 1 } 486 | assert_equal(@dbm.size, i) 487 | assert_equal(0, i) 488 | end 489 | 490 | def test_invert 491 | v = "0" 492 | 100.times {@dbm[v] = v; v = v.next} 493 | 494 | hash = @dbm.invert 495 | assert_instance_of(Hash, hash) 496 | assert_equal(100, hash.size) 497 | hash.each_pair {|key, val| 498 | assert_equal(key.to_i, val.to_i) 499 | } 500 | end 501 | 502 | def test_update 503 | hash = {} 504 | v = "0" 505 | 100.times {v = v.next; hash[v] = v} 506 | 507 | @dbm["101"] = "101" 508 | @dbm.update hash 509 | assert_equal(101, @dbm.size) 510 | @dbm.each_pair {|key, val| 511 | assert_equal(key.to_i, val.to_i) 512 | } 513 | end 514 | 515 | def test_replace 516 | hash = {} 517 | v = "0" 518 | 100.times {v = v.next; hash[v] = v} 519 | 520 | @dbm["101"] = "101" 521 | @dbm.replace hash 522 | assert_equal(100, @dbm.size) 523 | @dbm.each_pair {|key, val| 524 | assert_equal(key.to_i, val.to_i) 525 | } 526 | end 527 | 528 | def test_haskey? 529 | assert_equal('bar', @dbm['foo']='bar') 530 | assert_equal(true, @dbm.has_key?('foo')) 531 | assert_equal(false, @dbm.has_key?('bar')) 532 | end 533 | 534 | def test_has_value? 535 | assert_equal('bar', @dbm['foo']='bar') 536 | assert_equal(true, @dbm.has_value?('bar')) 537 | assert_equal(false, @dbm.has_value?('foo')) 538 | end 539 | 540 | def test_to_a 541 | v = "0" 542 | 100.times {v = v.next; @dbm[v] = v} 543 | 544 | ary = @dbm.to_a 545 | assert_instance_of(Array, ary) 546 | assert_equal(100, ary.size) 547 | ary.each {|key,val| 548 | assert_equal(key.to_i, val.to_i) 549 | } 550 | end 551 | 552 | def test_to_hash 553 | v = "0" 554 | 100.times {v = v.next; @dbm[v] = v} 555 | 556 | hash = @dbm.to_hash 557 | assert_instance_of(Hash, hash) 558 | assert_equal(100, hash.size) 559 | hash.each {|key,val| 560 | assert_equal(key.to_i, val.to_i) 561 | } 562 | end 563 | end 564 | 565 | class TestDBM2 < Test::Unit::TestCase 566 | def setup 567 | @tmproot = Dir.mktmpdir('ruby-dbm') 568 | end 569 | 570 | def teardown 571 | FileUtils.remove_entry_secure @tmproot if File.directory?(@tmproot) 572 | end 573 | 574 | def test_version 575 | assert_instance_of(String, DBM::VERSION) 576 | end 577 | 578 | def test_reader_open_notexist 579 | assert_raise(Errno::ENOENT) { 580 | DBM.open("#{@tmproot}/a", 0666, DBM::READER) 581 | } 582 | end 583 | 584 | def test_writer_open_notexist 585 | skip "dbm_open() is broken on libgdbm 1.8.0 or prior (#{DBM::VERSION})" if /GDBM version 1\.(?:[0-7]\b|8\.0)/ =~ DBM::VERSION 586 | assert_raise(Errno::ENOENT) { 587 | DBM.open("#{@tmproot}/a", 0666, DBM::WRITER) 588 | } 589 | end 590 | 591 | def test_wrcreat_open_notexist 592 | v = DBM.open("#{@tmproot}/a", 0666, DBM::WRCREAT) 593 | assert_instance_of(DBM, v) 594 | v.close 595 | end 596 | 597 | def test_newdb_open_notexist 598 | v = DBM.open("#{@tmproot}/a", 0666, DBM::NEWDB) 599 | assert_instance_of(DBM, v) 600 | v.close 601 | end 602 | 603 | def test_reader_open 604 | DBM.open("#{@tmproot}/a") {} # create a db. 605 | v = DBM.open("#{@tmproot}/a", nil, DBM::READER) {|d| 606 | # Errno::EPERM is raised on Solaris which use ndbm. 607 | # DBMError is raised on Debian which use gdbm. 608 | assert_raise(Errno::EPERM, DBMError) { d["k"] = "v" } 609 | true 610 | } 611 | assert(v) 612 | end 613 | 614 | def test_newdb_open 615 | DBM.open("#{@tmproot}/a") {|dbm| 616 | dbm["k"] = "v" 617 | } 618 | v = DBM.open("#{@tmproot}/a", nil, DBM::NEWDB) {|d| 619 | assert_equal(0, d.length) 620 | assert_nil(d["k"]) 621 | true 622 | } 623 | assert(v) 624 | end 625 | 626 | def test_freeze 627 | expected_error = defined?(FrozenError) ? FrozenError : RuntimeError 628 | DBM.open("#{@tmproot}/a") {|d| 629 | d.freeze 630 | assert_raise(expected_error) { d["k"] = "v" } 631 | } 632 | end 633 | end 634 | end 635 | --------------------------------------------------------------------------------