carton_db 1.1.3 → 1.1.4
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.
- 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
|