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 +4 -4
- data/README.md +25 -32
- data/lib/carton_db.rb +4 -0
- data/lib/carton_db/escaping.rb +29 -11
- data/lib/carton_db/list_map_db.rb +7 -11
- data/lib/carton_db/list_map_db/segment.rb +39 -30
- data/lib/carton_db/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce6b3966ce215081d2036a69277b65cd3958d0ff
|
4
|
+
data.tar.gz: 62f46c6218f8a938a60d6a7b67a0d7314b07f43e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
57
|
-
between calls to its methods
|
58
|
-
|
59
|
-
|
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.
|
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 (
|
80
|
-
|
81
|
-
The speed of database operations is good,
|
82
|
-
|
83
|
-
|
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
|
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
|
data/lib/carton_db/escaping.rb
CHANGED
@@ -44,18 +44,36 @@ module CartonDb
|
|
44
44
|
|
45
45
|
UNESCAPING_MAP = ESCAPING_MAP.invert.freeze
|
46
46
|
|
47
|
-
|
48
|
-
value
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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 |
|
221
|
-
|
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 |
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
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 &&
|
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
|
-
|
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 =
|
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
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
158
|
-
|
159
|
-
)
|
144
|
+
repl_name = "#{segment_filename}.temp"
|
145
|
+
replacement = self.class.new(segment_group, repl_name)
|
160
146
|
begin
|
161
|
-
|
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
|
-
|
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
|
data/lib/carton_db/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2017-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|