carton_db 1.1.3 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b0569eb04a7231087ac8ba262cc56330a1c61912
4
- data.tar.gz: aaae82f45cfdeb14e4dea5110e9a09c3b062ab5e
3
+ metadata.gz: ce6b3966ce215081d2036a69277b65cd3958d0ff
4
+ data.tar.gz: 62f46c6218f8a938a60d6a7b67a0d7314b07f43e
5
5
  SHA512:
6
- metadata.gz: e07e734b21b74e9988654549060f615897fb9b5e57763a61d392bf58ef0ff46845ba43118c805766f9177a117d0c4f7d49d1fa52e13e8e2042f410dd385b18a3
7
- data.tar.gz: 5c818326762c092a868e9abc642ce1baa3a1b9a2fbe38a17c7932f0ebbbde99a1e0a8d72cd7479a98e61a5c97cc1516968f92e9c655cb0e1758333f02a778b2d
6
+ metadata.gz: 61c8a47573f2f015934f797309381c250cf7cd96ab00a2a244c6c0ae287c3b75bc90a36c2643b76ecd071ac6103c0c39403d7d7a3e0ded63f4e6836a0244be83
7
+ data.tar.gz: d8ceaa939dcc182dced7fc4e41e896419eac34625598c68a6ddd7b2007b66ca7a12e1be3136aa0cdb1309c115b177620888df457735045af3e9ad426d56c1c67
data/README.md CHANGED
@@ -7,28 +7,17 @@ The primary goals of this library are simplicity of implementation
7
7
  and reliable, predictable behavior when used as intended, along
8
8
  with documentation making it reasonably clear what is intended.
9
9
 
10
+ Secondarily, this library is optimized for fast appending to
11
+ existing entries.
12
+
10
13
  ## Uses
11
14
 
12
- Uses for this gem seem pretty limited, but you might have a
13
- purpose for it that the author has not thought about.
14
-
15
- This is a formalization of a solution that was created to solve
16
- a specific problem. The problem was adding a feature to a
17
- Ruby program running on Heroku to collect data into a map of
18
- sets of elements that would be too large to be effectively
19
- handled in memory. The application didn't already have any use
20
- for a relational database server, and I didn't want to add one
21
- just for this requirement. A Redis db with sufficient capacity
22
- would have been expensive, and solutions such as SQLite are
23
- specifically not supported by Heroku so people don't mistakenly
24
- expect the data to be preserved. Ruby's `PStore`, `DBM` and
25
- `SDMB` each proved to be too unpredicatable and flakey to be a
26
- practical solution.
27
-
28
- Although this tool was initially developed to store transient
29
- data for use within a single process invocation and then
30
- discarded, it is also well suited for long term data storage on a
31
- system that retains filesystem contents.
15
+ This library is useful in some of the same situations in which
16
+ one might consider using the `PStore`, `DBM`, or `SMDB` classes
17
+ provided as part of Ruby's standard library, but you either need
18
+ something more solid and trustworthy or you need something that
19
+ supports fast appending of elements to lists or sets within
20
+ entries.
32
21
 
33
22
  ## Installation
34
23
 
@@ -53,13 +42,13 @@ filesystem containing the files that store the data.
53
42
 
54
43
  A database is accessed through an instance of a database class.
55
44
 
56
- An instance of a database class maintains no internal state
57
- between calls to its methods except for the database name and the
58
- expectation of a directory with that name existing in the
59
- filesystem.
45
+ An instance of a database class assumes nothing about the state
46
+ of the database between calls to its methods, and only expects
47
+ that the database exists and is valid when a call is made that
48
+ reads or writes data.
60
49
 
61
- Only instances of classes maintain any internal state. No global
62
- internal state is maintained.
50
+ Only instances of database classes maintain any internal state.
51
+ No global internal state is maintained.
63
52
 
64
53
  An empty directory is a valid empty database.
65
54
 
@@ -76,11 +65,14 @@ will be raised if it doesn't.
76
65
 
77
66
  The database structure is designed to effectively handle up to
78
67
  several million elements with any entry containing up to around
79
- 50 thousand characters (elements chars in an entry's content).
80
-
81
- The speed of database operations is good, but this is not a high
82
- performance database management system. See the code
83
- documentation in the classes for more details about the
68
+ 50 thousand characters (in all of the entry's elements combined).
69
+
70
+ The speed of database operations is good, and it is particularly
71
+ optimized for appending to new or existing entries. It was not
72
+ designed or optimized to be a "high performance" database
73
+ management system though, and it has not been benchmarked against
74
+ other systems for specific performance comparison. See the inline
75
+ code documentation of the classes for details about the
84
76
  performance of each kind of database operation.
85
77
 
86
78
  ## Usage
@@ -89,7 +81,8 @@ The primary kind of database provided by this gem is the one
89
81
  implemented by `CartonDB::ListMapDb`. It is a map of lists where
90
82
  each entry has a string for a key and a list of of 0 or more
91
83
  string elements as content. Other kinds of database are
92
- implemented on top of that and share the same storage format.
84
+ implemented as specializations of that and share the same storage
85
+ format.
93
86
 
94
87
  The name of the database is the path of a directory in the
95
88
  filesystem that either already exists or shall be created as
data/lib/carton_db.rb CHANGED
@@ -9,4 +9,8 @@ require "carton_db/simple_map_db"
9
9
  require "carton_db/set_map_db"
10
10
 
11
11
  module CartonDb
12
+ Error = Class.new(StandardError)
13
+ UnescapingError = Class.new(Error)
14
+ InvalidEscapeSequence = Class.new(UnescapingError)
15
+ IncompleteEscapeSequence = Class.new(UnescapingError)
12
16
  end
@@ -44,18 +44,36 @@ module CartonDb
44
44
 
45
45
  UNESCAPING_MAP = ESCAPING_MAP.invert.freeze
46
46
 
47
- def self.escape(value)
48
- value.gsub(
49
- /[\x00-\x1F\x7F\\]/,
50
- ESCAPING_MAP
51
- )
52
- end
47
+ class << self
48
+ def escape(value)
49
+ value.gsub(
50
+ /[\x00-\x1F\x7F\\]/,
51
+ ESCAPING_MAP
52
+ )
53
+ end
54
+
55
+ def unescape(esc)
56
+ esc.gsub( /\\(?:\\|x[01][0-9A-F]|x7F|[^x\\]|$)/ ) { |match|
57
+ UNESCAPING_MAP.fetch match do
58
+ incomplete_sequence! match if match == "\\"
59
+ invalid_sequence! match
60
+ end
61
+ }
62
+ end
63
+
64
+ private
65
+
66
+ def incomplete_sequence!(sequence)
67
+ message =
68
+ "Escaped text contains incomplete escape sequence %s" % sequence
69
+ raise CartonDb::IncompleteEscapeSequence, message
70
+ end
53
71
 
54
- def self.unescape(esc)
55
- esc.gsub(
56
- /\\(?:\\|x[01][0-9A-F]|x7F|[abtnvfr])/,
57
- UNESCAPING_MAP
58
- )
72
+ def invalid_sequence!(sequence)
73
+ message =
74
+ "Escaped text contains invalid escape sequence %s" % sequence
75
+ raise CartonDb::InvalidEscapeSequence, message
76
+ end
59
77
  end
60
78
 
61
79
  end
@@ -217,10 +217,8 @@ module CartonDb
217
217
  segment = segment_containing(key_d)
218
218
  return if segment.empty?
219
219
 
220
- segment.replace do |replacement|
221
- replacement.open_overwrite do |repl_io|
222
- segment.copy_entries_except key_d, repl_io
223
- end
220
+ segment.replace do |repl_io|
221
+ segment.copy_entries_except key_d, repl_io
224
222
  end
225
223
  end
226
224
 
@@ -325,13 +323,11 @@ module CartonDb
325
323
  attr_accessor :name
326
324
 
327
325
  def replace_entry_in_file(segment, key_d, content)
328
- segment.replace do |replacement|
329
- replacement.open_overwrite do |repl_io|
330
- segment.copy_entries_except key_d, repl_io
331
- element_count = 0
332
- count = write_key_elements(key_d, content, repl_io)
333
- repl_io << "#{key_d.escaped}\n" if count.zero?
334
- end
326
+ segment.replace do |repl_io|
327
+ segment.copy_entries_except key_d, repl_io
328
+ element_count = 0
329
+ count = write_key_elements(key_d, content, repl_io)
330
+ repl_io << "#{key_d.escaped}\n" if count.zero?
335
331
  end
336
332
  end
337
333
 
@@ -67,11 +67,7 @@ module CartonDb
67
67
  end
68
68
 
69
69
  def touch_d(key_d, optimization)
70
- if optimization == :small && content?
71
- each_entry_element_line do |kd, _ed, _line|
72
- return if kd == key_d
73
- end
74
- end
70
+ return if optimization == :small && key_d?(key_d)
75
71
 
76
72
  open_append do |io|
77
73
  io << key_d.escaped << "\n"
@@ -102,8 +98,7 @@ module CartonDb
102
98
 
103
99
  def collect_content(key_d, collection_class)
104
100
  result = nil
105
- each_entry_element_line do |kd, ed, _line|
106
- next unless kd == key_d
101
+ each_element_for_d key_d do |ed|
107
102
  result ||= collection_class.new
108
103
  result << ed.plain unless ed.placeholder?
109
104
  end
@@ -111,12 +106,7 @@ module CartonDb
111
106
  end
112
107
 
113
108
  def each_entry
114
- entries = nil
115
- each_entry_element_line do |key_d, elem_d, _line|
116
- entries ||= {}
117
- content = entries[key_d] ||= []
118
- content << elem_d.plain unless elem_d.placeholder?
119
- end
109
+ entries = key_d_contents_map
120
110
  return unless entries
121
111
  entries.each do |key_d, content|
122
112
  yield key_d.plain, content
@@ -131,13 +121,10 @@ module CartonDb
131
121
  end
132
122
 
133
123
  def each_first_element
134
- first_entries = nil
135
- each_entry_element_line do |key_d, elem_d, _line|
136
- first_entries ||= {}
137
- first_entries[key_d] ||= elem_d.plain
138
- end
139
- return unless first_entries
140
- first_entries.each do |key_d, element|
124
+ first_element_map = key_d_first_element_map
125
+ return unless first_element_map
126
+
127
+ first_element_map.each do |key_d, element|
141
128
  yield key_d.plain, element
142
129
  end
143
130
  end
@@ -154,11 +141,12 @@ module CartonDb
154
141
  end
155
142
 
156
143
  def replace
157
- replacement = self.class.new(
158
- segment_group, "#{segment_filename}.txt"
159
- )
144
+ repl_name = "#{segment_filename}.temp"
145
+ replacement = self.class.new(segment_group, repl_name)
160
146
  begin
161
- yield replacement
147
+ replacement.open_overwrite do |io|
148
+ yield io
149
+ end
162
150
  rescue StandardError
163
151
  File.unlink replacement.filename
164
152
  raise
@@ -188,12 +176,7 @@ module CartonDb
188
176
  end
189
177
  end
190
178
 
191
- def open_append
192
- touch_dir
193
- File.open filename, 'a', **FILE_ENCODING_OPTS do |io|
194
- yield io
195
- end
196
- end
179
+ protected
197
180
 
198
181
  def open_overwrite
199
182
  touch_dir
@@ -216,6 +199,13 @@ module CartonDb
216
199
  end
217
200
  end
218
201
 
202
+ def open_append
203
+ touch_dir
204
+ File.open filename, 'a', **FILE_ENCODING_OPTS do |io|
205
+ yield io
206
+ end
207
+ end
208
+
219
209
  def touch_dir
220
210
  dir = File.dirname(filename)
221
211
  return if File.directory?(dir)
@@ -230,6 +220,25 @@ module CartonDb
230
220
  end
231
221
  end
232
222
 
223
+ def key_d_contents_map
224
+ entries = nil
225
+ each_entry_element_line do |key_d, elem_d, _line|
226
+ entries ||= {}
227
+ content = entries[key_d] ||= []
228
+ content << elem_d.plain unless elem_d.placeholder?
229
+ end
230
+ entries
231
+ end
232
+
233
+ def key_d_first_element_map
234
+ result = nil
235
+ each_entry_element_line do |key_d, elem_d, _line|
236
+ result ||= {}
237
+ result[key_d] ||= elem_d.plain
238
+ end
239
+ return result
240
+ end
241
+
233
242
  end
234
243
 
235
244
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module CartonDb
5
- VERSION = "1.1.3"
5
+ VERSION = "1.1.4"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carton_db
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.3
4
+ version: 1.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steve Jorgensen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-05-09 00:00:00.000000000 Z
11
+ date: 2017-05-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler