daybreak 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - ruby-head
6
+ - jruby-18mode
7
+ - jruby-19mode
8
+ - rbx-18mode
9
+ - rbx-19mode
data/README CHANGED
@@ -5,10 +5,10 @@
5
5
 
6
6
  Daybreak is a simple key value store for ruby. It has user defined persistence,
7
7
  and all data is stored in a table in memory so ruby niceties are available.
8
- Daybreak is faster than other options ruby options like pstore and dbm.
8
+ Daybreak is faster than other ruby options like pstore and dbm.
9
9
 
10
10
  $ gem install daybreak
11
11
 
12
12
  Docs: http://propublica.github.com/daybreak/
13
13
  Issue Tracker: http://propublica.github.com/daybreak/issues
14
- Benchmarks: https://gist.github.com/4146590
14
+ Benchmarks: https://gist.github.com/4146590
data/daybreak.gemspec CHANGED
@@ -14,7 +14,6 @@ Gem::Specification.new do |gem|
14
14
  gem.require_paths = ["lib"]
15
15
  gem.licenses = ["MIT"]
16
16
  gem.version = Daybreak::VERSION
17
+ gem.add_development_dependency 'rake'
17
18
  gem.add_development_dependency 'minitest'
18
- gem.add_development_dependency 'simplecov'
19
- gem.add_development_dependency 'ruby-prof'
20
19
  end
data/lib/daybreak.rb CHANGED
@@ -1,17 +1,11 @@
1
- # Daybreak, a simple dimple key value store for ruby.
2
- module Daybreak
3
- # The root path for Daybreak
4
- ROOT = File.expand_path(File.dirname(__FILE__))
5
- end
6
-
7
1
  require 'tempfile'
8
2
  require 'thread'
9
- require 'zlib'
10
3
  require 'fcntl'
4
+ require 'zlib'
11
5
 
12
- require "#{Daybreak::ROOT}/daybreak/version"
13
- require "#{Daybreak::ROOT}/daybreak/locking"
14
- require "#{Daybreak::ROOT}/daybreak/record"
15
- require "#{Daybreak::ROOT}/daybreak/writer"
16
- require "#{Daybreak::ROOT}/daybreak/reader"
17
- require "#{Daybreak::ROOT}/daybreak/db"
6
+ require 'daybreak/version'
7
+ require 'daybreak/locking'
8
+ require 'daybreak/record'
9
+ require 'daybreak/writer'
10
+ require 'daybreak/reader'
11
+ require 'daybreak/db'
data/lib/daybreak/db.rb CHANGED
@@ -14,10 +14,11 @@ module Daybreak
14
14
  # @yield [key] a block that will return the default value to store.
15
15
  # @yieldparam [String] key the key to be stored.
16
16
  def initialize(file, default=nil, &blk)
17
+ @table = {}
17
18
  @file_name = file
18
- reset!
19
- @default = default
20
- @default = blk if block_given?
19
+ @reader = Reader.new(@file_name)
20
+ @writer = Writer.new(@file_name)
21
+ @default = block_given? ? blk : default
21
22
  read!
22
23
  end
23
24
 
@@ -64,12 +65,7 @@ module Daybreak
64
65
  if @table.has_key? key
65
66
  @table[key]
66
67
  elsif default?
67
- if @default.is_a? Proc
68
- value = @default.call(key)
69
- else
70
- value = @default
71
- end
72
- set key, value
68
+ set key, Proc === @default ? @default.call(key) : @default
73
69
  end
74
70
  end
75
71
  alias_method :get, :"[]"
@@ -78,8 +74,8 @@ module Daybreak
78
74
  # @yield [key, value] blk the iterator for each key value pair.
79
75
  # @yieldparam [String] key the key.
80
76
  # @yieldparam value the value from the database.
81
- def each(&blk)
82
- keys.each { |k| blk.call(k, get(k)) }
77
+ def each
78
+ keys.each { |k| yield(k, get(k)) }
83
79
  end
84
80
 
85
81
  # Does this db have a default value.
@@ -104,6 +100,7 @@ module Daybreak
104
100
  def length
105
101
  @table.keys.length
106
102
  end
103
+ alias_method :size, :length
107
104
 
108
105
  # Serialize the data for writing to disk, if you don't want to use <tt>Marshal</tt>
109
106
  # overwrite this method.
@@ -121,11 +118,10 @@ module Daybreak
121
118
  Marshal.load(value)
122
119
  end
123
120
 
124
- # Reset and empty the database file.
121
+ # Empty the database file.
125
122
  def empty!
126
123
  @writer.truncate!
127
- close!
128
- reset!
124
+ @table.clear
129
125
  read!
130
126
  end
131
127
  alias_method :clear, :empty!
@@ -135,53 +131,42 @@ module Daybreak
135
131
  @writer.flush!
136
132
  end
137
133
 
138
- # Reset the state of the database, you should call <tt>read!</tt> after calling this.
139
- def reset!
140
- @table = {}
141
- @writer = Daybreak::Writer.new(@file_name)
142
- @reader = Daybreak::Reader.new(@file_name)
143
- end
144
-
145
134
  # Close the database for reading and writing.
146
135
  def close!
147
136
  @writer.close!
148
- @reader.close!
149
137
  end
150
138
 
151
139
  # Compact the database to remove stale commits and reduce the file size.
152
140
  def compact!
153
- # Create a new temporary file
154
- tmp_file = Tempfile.new File.basename(@file_name)
155
- copy_db = self.class.new tmp_file.path
141
+ # Create a new temporary database
142
+ tmp_file = @file_name + "-#{$$}-#{Thread.current.object_id}"
143
+ copy_db = self.class.new tmp_file
156
144
 
157
145
  # Copy the database key by key into the temporary table
158
- each do |key|
146
+ each do |key, value|
159
147
  copy_db.set(key, get(key))
160
148
  end
161
149
  copy_db.close!
162
150
 
163
- # Empty this database
164
- empty!
165
151
  close!
166
152
 
167
153
  # Move the copy into place
168
- tmp_file.close
169
- FileUtils.mv tmp_file.path, @file_name
170
- tmp_file.unlink
154
+ File.rename tmp_file, @file_name
171
155
 
172
- # Reset this database
173
- reset!
156
+ # Reopen this database
157
+ @writer = Writer.new(@file_name)
158
+ @table.clear
174
159
  read!
175
160
  end
176
161
 
177
162
  # Read all values from the log file. If you want to check for changed data
178
163
  # call this again.
179
164
  def read!
180
- @reader.read do |record|
181
- if record.deleted?
182
- @table.delete record.key
165
+ @reader.read do |(key, data, deleted)|
166
+ if deleted
167
+ @table.delete key
183
168
  else
184
- @table[record.key] = parse(record.data)
169
+ @table[key] = parse(data)
185
170
  end
186
171
  end
187
172
  end
@@ -189,7 +174,7 @@ module Daybreak
189
174
  private
190
175
 
191
176
  def write(key, value, sync = false, delete = false)
192
- @writer.write(Record.new(key, serialize(value), delete))
177
+ @writer.write([key, serialize(value), delete])
193
178
  flush! if sync
194
179
  end
195
180
  end
@@ -8,30 +8,19 @@ module Daybreak
8
8
  @file_name = file
9
9
  end
10
10
 
11
- # Close the Reader's file descriptor.
12
- def close!
13
- @fd.close unless @fd.nil? || !@fd.closed?
14
- end
15
-
16
11
  # Read all values from the aof file.
17
12
  #
18
13
  # Right now this is really expensive, every call to read will
19
14
  # close and reread the whole db file, but since cross process
20
15
  # consistency is handled by the user, this should be fair warning.
21
- def read(&blk)
22
- open!
23
- while !@fd.eof?
24
- blk.call(Record.read(@fd))
16
+ def read
17
+ File.open(@file_name, 'r') do |fd|
18
+ fd.binmode
19
+ fd.advise(:sequential) if fd.respond_to? :advise
20
+ while !fd.eof?
21
+ yield Record.read(fd)
22
+ end
25
23
  end
26
- close!
27
- end
28
-
29
- private
30
-
31
- def open!
32
- @fd = File.open @file_name, 'r'
33
- @fd.binmode
34
- @fd.advise(:sequential) if @fd.respond_to? :advise
35
24
  end
36
25
  end
37
26
  end
@@ -1,87 +1,67 @@
1
1
  module Daybreak
2
2
  # Records define how data is serialized and read from disk.
3
- class Record
3
+ module Record
4
4
  # Thrown when either key or data is missing
5
5
  class UnnacceptableDataError < Exception; end
6
6
 
7
7
  # Thrown when there is a CRC mismatch between the data from the disk
8
8
  # and what was written to disk previously.
9
9
  class CorruptDataError < Exception; end
10
- include Locking
11
10
 
12
- # The mask a record uses to check for deletion.
13
- DELETION_MASK = (1 << 31)
14
-
15
- attr_accessor :key, :data
11
+ extend self
12
+ extend Locking
16
13
 
17
- def initialize(key = nil, data = nil, deleted = false)
18
- @key = key
19
- @data = data
20
- if deleted
21
- @deleted = DELETION_MASK
22
- else
23
- @deleted = 0
24
- end
25
- end
14
+ # The mask a record uses to check for deletion.
15
+ DELETION_MASK = 1 << 31
26
16
 
27
17
  # Read a record from an open io source, check the CRC, and set <tt>@key</tt>
28
18
  # and <tt>@data</tt>.
29
19
  # @param [#read] io an IO instance to read from
30
- def read(io)
31
- lock io do
32
- @key = read_key(io)
33
- @data = read_data(io)
34
- crc = io.read(4)
35
- raise CorruptDataError, "CRC mismatch #{crc} should be #{crc_string}" unless crc == crc_string
36
- end
37
- self
38
- end
39
20
 
40
21
  # The serialized representation of the key value pair plus the CRC.
41
22
  # @return [String]
42
- def representation
43
- raise UnnacceptableDataError, "key and data must be defined" if @key.nil? || @data.nil?
44
- byte_string + crc_string
23
+ def serialize(record)
24
+ raise UnnacceptableDataError, 'key and data must be defined' unless record[0] && record[1]
25
+ s = key_data_string(record)
26
+ s << crc_string(s)
45
27
  end
46
28
 
47
29
  # Create a new record to read from IO.
48
30
  # @param [#read] io an IO instance to read from
49
- def self.read(io)
50
- new.read(io)
51
- end
52
-
53
- def deleted?
54
- @deleted > 0
31
+ def read(io)
32
+ lock io do
33
+ record = []
34
+ masked = read32(io)
35
+ # Read the record's key bytes
36
+ record << io.read(masked & (DELETION_MASK - 1)) <<
37
+ # Read the record's value bytes
38
+ io.read(read32(io)) <<
39
+ # Set the deletion flag
40
+ ((masked & DELETION_MASK) != 0)
41
+ crc = io.read(4)
42
+ raise CorruptDataError, 'CRC mismatch' unless crc == crc_string(key_data_string(record))
43
+ record
44
+ end
55
45
  end
56
46
 
57
47
  private
58
48
 
59
- def byte_string
60
- @byte_string ||= part(@key, @key.bytesize + @deleted) + part(@data, @data.bytesize)
61
- end
62
-
63
- def crc_string
64
- [Zlib.crc32(byte_string, 0)].pack('N')
49
+ # Return the deletion flag plus two length prefixed cells
50
+ def key_data_string(record)
51
+ part(record[0], record[0].bytesize + (record[2] ? DELETION_MASK : 0)) << part(record[1], record[1].bytesize)
65
52
  end
66
53
 
67
- def read_data(io)
68
- io.read read32(io)
54
+ def crc_string(s)
55
+ [Zlib.crc32(s, 0)].pack('N')
69
56
  end
70
57
 
71
- def read_key(io)
72
- masked = read32 io
73
- @deleted = masked & DELETION_MASK
74
- length = masked & (DELETION_MASK - 1)
75
- io.read length
58
+ def part(data, length)
59
+ [length].pack('N') << data
76
60
  end
77
61
 
78
62
  def read32(io)
79
63
  raw = io.read(4)
80
64
  raw.unpack('N')[0]
81
65
  end
82
-
83
- def part(data, length)
84
- [length].pack('N') + data
85
- end
86
66
  end
87
67
  end
@@ -1,4 +1,4 @@
1
1
  module Daybreak
2
2
  # Updated using SemVer
3
- VERSION = "0.1.1"
3
+ VERSION = "0.1.2"
4
4
  end
@@ -5,9 +5,7 @@ module Daybreak
5
5
  # Open up the file, ready it for binary and nonblocking writing.
6
6
  def initialize(file)
7
7
  @file = file
8
-
9
8
  open!
10
-
11
9
  @worker = Worker.new(@fd)
12
10
  end
13
11
 
@@ -65,20 +63,20 @@ module Daybreak
65
63
 
66
64
  # Queue up a write to be committed later.
67
65
  def enqueue(record)
68
- @queue << record.representation
66
+ @queue << record
69
67
  end
70
68
 
71
69
  # Loop and block if we don't have work to do or if
72
70
  # the file isn't ready for another write just yet.
73
71
  def work
74
- buf = ""
72
+ buf = ''
75
73
  loop do
76
- str = @queue.pop
77
- if str.nil?
74
+ record = @queue.pop
75
+ unless record
78
76
  @fd.flush
79
77
  break
80
78
  end
81
- buf << str
79
+ buf << Record.serialize(record)
82
80
  read, write = IO.select [], [@fd]
83
81
  if write and fd = write.first
84
82
  lock(fd, File::LOCK_EX) { buf = try_write fd, buf }
@@ -98,7 +96,7 @@ module Daybreak
98
96
  if s < buf.length
99
97
  buf = buf[s..-1] # didn't finish
100
98
  else
101
- buf = ""
99
+ buf = ''
102
100
  end
103
101
  rescue Errno::EAGAIN
104
102
  buf = buf # try this again
data/test/test.rb CHANGED
@@ -1,8 +1,12 @@
1
- require 'rubygems'
2
- require 'simplecov'
3
1
  require 'set'
4
- SimpleCov.start
5
- SimpleCov.command_name "Unit tests"
2
+
3
+ begin
4
+ require 'simplecov'
5
+ SimpleCov.start
6
+ SimpleCov.command_name "Unit tests"
7
+ rescue Exception => ex
8
+ puts "No coverage report generated: #{ex.message}"
9
+ end
6
10
 
7
11
  require File.expand_path(File.dirname(__FILE__)) + '/test_helper.rb'
8
12
 
data/test/test_helper.rb CHANGED
@@ -4,4 +4,5 @@ require 'minitest/benchmark'
4
4
  HERE = File.expand_path(File.dirname(__FILE__))
5
5
  DB_PATH = File.join HERE, "test.db"
6
6
 
7
- require File.join HERE, '..', 'lib', 'daybreak'
7
+ $: << File.join(HERE, '..', 'lib')
8
+ require 'daybreak'
metadata CHANGED
@@ -1,72 +1,63 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: daybreak
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
4
5
  prerelease:
5
- version: 0.1.1
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 2
10
+ version: 0.1.2
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Jeff Larson
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2013-01-07 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- version_requirements: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ! '>='
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- none: false
21
- name: minitest
22
- type: :development
17
+
18
+ date: 2013-01-07 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rake
23
23
  prerelease: false
24
- requirement: !ruby/object:Gem::Requirement
25
- requirements:
26
- - - ! '>='
27
- - !ruby/object:Gem::Version
28
- version: '0'
29
- none: false
30
- - !ruby/object:Gem::Dependency
31
- version_requirements: !ruby/object:Gem::Requirement
32
- requirements:
33
- - - ! '>='
34
- - !ruby/object:Gem::Version
35
- version: '0'
24
+ requirement: &id001 !ruby/object:Gem::Requirement
36
25
  none: false
37
- name: simplecov
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
38
33
  type: :development
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: minitest
39
37
  prerelease: false
40
- requirement: !ruby/object:Gem::Requirement
41
- requirements:
42
- - - ! '>='
43
- - !ruby/object:Gem::Version
44
- version: '0'
45
- none: false
46
- - !ruby/object:Gem::Dependency
47
- version_requirements: !ruby/object:Gem::Requirement
48
- requirements:
49
- - - ! '>='
50
- - !ruby/object:Gem::Version
51
- version: '0'
38
+ requirement: &id002 !ruby/object:Gem::Requirement
52
39
  none: false
53
- name: ruby-prof
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
54
47
  type: :development
55
- prerelease: false
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ! '>='
59
- - !ruby/object:Gem::Version
60
- version: '0'
61
- none: false
48
+ version_requirements: *id002
62
49
  description: A simple dimple key-value store for ruby.
63
- email:
50
+ email:
64
51
  - thejefflarson@gmail.com
65
52
  executables: []
53
+
66
54
  extensions: []
55
+
67
56
  extra_rdoc_files: []
68
- files:
57
+
58
+ files:
69
59
  - .gitignore
60
+ - .travis.yml
70
61
  - .yardopts
71
62
  - EPIGRAPH
72
63
  - Gemfile
@@ -86,36 +77,43 @@ files:
86
77
  - test/prof.rb
87
78
  - test/test.rb
88
79
  - test/test_helper.rb
80
+ has_rdoc: true
89
81
  homepage: http://propublica.github.com/daybreak/
90
- licenses:
82
+ licenses:
91
83
  - MIT
92
84
  post_install_message:
93
85
  rdoc_options: []
94
- require_paths:
86
+
87
+ require_paths:
95
88
  - lib
96
- required_ruby_version: !ruby/object:Gem::Requirement
97
- requirements:
98
- - - ! '>='
99
- - !ruby/object:Gem::Version
100
- version: '0'
89
+ required_ruby_version: !ruby/object:Gem::Requirement
101
90
  none: false
102
- required_rubygems_version: !ruby/object:Gem::Requirement
103
- requirements:
104
- - - ! '>='
105
- - !ruby/object:Gem::Version
106
- version: '0'
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
99
  none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
108
107
  requirements: []
108
+
109
109
  rubyforge_project:
110
- rubygems_version: 1.8.23
110
+ rubygems_version: 1.6.2
111
111
  signing_key:
112
112
  specification_version: 3
113
- summary: Daybreak provides an in memory key-value store that is easily enumerable
114
- in ruby.
115
- test_files:
113
+ summary: Daybreak provides an in memory key-value store that is easily enumerable in ruby.
114
+ test_files:
116
115
  - test/bench.rb
117
116
  - test/compare.rb
118
117
  - test/prof.rb
119
118
  - test/test.rb
120
119
  - test/test_helper.rb
121
- has_rdoc: