fdb 5.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ce18c924f0fa5dac7b6e44a98860a7e41a001e6a
4
+ data.tar.gz: 3d72f399a0ae2cb63f1676a51a1f1b231c952268
5
+ SHA512:
6
+ metadata.gz: acc9dcacfe050de9bc637cf4e8071a34f1f2798c664c3f334636824ee8cfc7a5419fe15166dda5975b46a033d4fcdf4d96755b34f6d07f550a6fa4ee0568dcae
7
+ data.tar.gz: e1bc5edd31b700b2ffc7b8c849d8bf442c9609853ed0fdd27ac0c8b78f07f6ceba0b3627c545152f599602b59567e9d10f9078850a914f8bc8e32821c6537589
data/LICENSE ADDED
@@ -0,0 +1,207 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Licensed under the Apache License, Version 2.0 (the "License");
191
+ you may not use this file except in compliance with the License.
192
+ You may obtain a copy of the License at
193
+
194
+ http://www.apache.org/licenses/LICENSE-2.0
195
+
196
+ Unless required by applicable law or agreed to in writing, software
197
+ distributed under the License is distributed on an "AS IS" BASIS,
198
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
199
+ See the License for the specific language governing permissions and
200
+ limitations under the License.
201
+
202
+ -------------------------------------------------------------------------------
203
+ SOFTWARE DISTRIBUTED WITH FOUNDATIONDB:
204
+
205
+ The FoundationDB software includes a number of subcomponents with separate
206
+ copyright notices and license terms - please see the file ACKNOWLEDGEMENTS.
207
+ -------------------------------------------------------------------------------
@@ -0,0 +1,71 @@
1
+ #
2
+ # fdb.rb
3
+ #
4
+ # This source file is part of the FoundationDB open source project
5
+ #
6
+ # Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ # FoundationDB Ruby API
22
+
23
+ # Documentation for this API can be found at
24
+ # https://www.foundationdb.org/documentation/api-ruby.html
25
+
26
+ module FDB
27
+ @@chosen_version = -1
28
+ def self.api_version(version)
29
+ header_version = 510
30
+ if @@chosen_version >= 0
31
+ if@@chosen_version != version
32
+ raise "FDB API already loaded at version #{@@chosen_version}."
33
+ end
34
+ return
35
+ end
36
+
37
+ if version < 14
38
+ raise "FDB API versions before 14 are not supported"
39
+ end
40
+
41
+ if version > header_version
42
+ raise "Latest known FDB API version is #{header_version}"
43
+ end
44
+
45
+ @@chosen_version = version
46
+
47
+ require_relative 'fdbimpl'
48
+
49
+ err = FDBC.fdb_select_api_version_impl(version, header_version)
50
+ if err.nonzero?
51
+ if err == 2203
52
+ max_supported_version = FDBC.fdb_get_max_api_version()
53
+ if header_version > max_supported_version
54
+ raise "This version of the FoundationDB Ruby binding is not supported by the installed FoundationDB C library. The binding requires a library that supports API version #{header_version}, but the installed library supports a maximum version of #{max_supported_version}."
55
+
56
+ else
57
+ raise "API version #{version} is not supported by the installed FoundationDB C library."
58
+ end
59
+ end
60
+ raise "FoundationDB API version error"
61
+ end
62
+
63
+ require_relative 'fdbtuple'
64
+ require_relative 'fdbdirectory'
65
+ if version > 22
66
+ require_relative 'fdblocality'
67
+ end
68
+
69
+ return
70
+ end
71
+ end
@@ -0,0 +1,689 @@
1
+ #encoding: BINARY
2
+
3
+ #
4
+ # fdbdirectory.rb
5
+ #
6
+ # This source file is part of the FoundationDB open source project
7
+ #
8
+ # Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
9
+ #
10
+ # Licensed under the Apache License, Version 2.0 (the "License");
11
+ # you may not use this file except in compliance with the License.
12
+ # You may obtain a copy of the License at
13
+ #
14
+ # http://www.apache.org/licenses/LICENSE-2.0
15
+ #
16
+ # Unless required by applicable law or agreed to in writing, software
17
+ # distributed under the License is distributed on an "AS IS" BASIS,
18
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ # See the License for the specific language governing permissions and
20
+ # limitations under the License.
21
+ #
22
+
23
+ # FoundationDB Ruby API
24
+
25
+ # Documentation for this API can be found at
26
+ # https://www.foundationdb.org/documentation/api-ruby.html
27
+
28
+ require 'thread'
29
+
30
+ require_relative 'fdbimpl'
31
+ require_relative 'fdbsubspace'
32
+
33
+ module FDB
34
+ class AllocatorTransactionState
35
+ def initialize()
36
+ @lock = Mutex.new
37
+ end
38
+
39
+ attr_reader :lock
40
+ end
41
+
42
+ class HighContentionAllocator
43
+ def initialize(subspace)
44
+ @counters = subspace[0]
45
+ @recent = subspace[1]
46
+ @lock = Mutex.new
47
+ end
48
+
49
+ def allocate(db_or_tr)
50
+ db_or_tr.transact do |tr|
51
+ if !tr.instance_variable_defined?(:@__fdb_directory_layer_hca_state__)
52
+ @lock.synchronize do
53
+ if !tr.instance_variable_defined?(:@__fdb_directory_layer_hca_state__)
54
+ tr.instance_variable_set(:@__fdb_directory_layer_hca_state__, AllocatorTransactionState.new)
55
+ end
56
+ end
57
+ end
58
+
59
+ tr_state = tr.instance_variable_get(:@__fdb_directory_layer_hca_state__)
60
+
61
+ loop do
62
+ start, count =
63
+ tr.snapshot.get_range(@counters.range[0],
64
+ @counters.range[1],
65
+ {:limit => 1, :reverse => true})
66
+ .map { |kv|
67
+ [ @counters.unpack(kv.key)[0], kv.value.unpack('q<')[0] ]
68
+ }.first || [0,0]
69
+
70
+ window = 0
71
+ window_advanced = false
72
+ loop do
73
+ tr_state.lock.synchronize do
74
+ if window_advanced
75
+ tr.clear_range(@counters, @counters[start])
76
+ tr.options.set_next_write_no_write_conflict_range()
77
+ tr.clear_range(@recent, @recent[start])
78
+ end
79
+
80
+ tr.add(@counters[start], [1].pack('q<'))
81
+ count = tr.snapshot[@counters[start]]
82
+ end
83
+
84
+ count = count.nil? ? 0 : count.unpack('q<')[0]
85
+ window = window_size(start)
86
+ if count * 2 < window
87
+ break
88
+ end
89
+
90
+ start += window
91
+ window_advanced = true
92
+ end
93
+
94
+ candidate = 0
95
+ found = false
96
+ loop do
97
+ candidate = rand(start...start+window)
98
+ latest_counter = nil
99
+ candidate_value = nil
100
+
101
+ tr_state.lock.synchronize do
102
+ latest_counter = tr.snapshot.get_range(@counters.range[0],
103
+ @counters.range[1],
104
+ {:limit => 1, :reverse => true})
105
+ candidate_value = tr[@recent[candidate]]
106
+ tr.options.set_next_write_no_write_conflict_range()
107
+ tr[@recent[candidate]] = ''
108
+ end
109
+
110
+ latest_counter = latest_counter.map{ |kv| [ @counters.unpack(kv.key)[0] ] }.first || [0]
111
+ if latest_counter.length > 0 and latest_counter[0] > start
112
+ break
113
+ end
114
+
115
+ if candidate_value.nil?
116
+ found = true
117
+ tr.add_write_conflict_key(@recent[candidate])
118
+ break
119
+ end
120
+ end
121
+
122
+ if found
123
+ break FDB::Tuple.pack([candidate])
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ def window_size(start)
130
+ if start < 255
131
+ 64
132
+ elsif start < 65535
133
+ 1024
134
+ else
135
+ 8192
136
+ end
137
+ end
138
+
139
+ private :window_size
140
+ end
141
+
142
+ class DirectoryLayer
143
+ @@SUBDIRS = 0
144
+ @@VERSION = [1,0,0]
145
+
146
+ def initialize(options={})
147
+ defaults = { :node_subspace => Subspace.new([], "\xfe"),
148
+ :content_subspace =>Subspace.new,
149
+ :allow_manual_prefixes => false }
150
+
151
+ options = defaults.merge(options)
152
+
153
+ @content_subspace = options[:content_subspace]
154
+ @node_subspace = options[:node_subspace]
155
+ @allow_manual_prefixes = options[:allow_manual_prefixes]
156
+
157
+ @root_node = @node_subspace[@node_subspace.key]
158
+ @allocator = HighContentionAllocator.new(@root_node['hca'])
159
+
160
+ @path = []
161
+ @layer = ''
162
+ end
163
+
164
+ def path
165
+ return @path.dup
166
+ end
167
+
168
+ def layer
169
+ return @layer.dup
170
+ end
171
+
172
+ attr_writer :path
173
+ private :path=
174
+
175
+ def create_or_open(db_or_tr, path, options={})
176
+ create_or_open_internal(db_or_tr, path, true, true, options)
177
+ end
178
+
179
+ def open(db_or_tr, path, options={})
180
+ create_or_open_internal(db_or_tr, path, false, true, options)
181
+ end
182
+
183
+ def create(db_or_tr, path, options={})
184
+ create_or_open_internal(db_or_tr, path, true, false, options)
185
+ end
186
+
187
+ def move_to(db_or_tr, new_absolute_path)
188
+ raise 'The root directory cannot be moved.'
189
+ end
190
+
191
+ def move(db_or_tr, old_path, new_path)
192
+ db_or_tr.transact do |tr|
193
+ check_version(tr, true)
194
+
195
+ old_path = to_unicode_path(old_path)
196
+ new_path = to_unicode_path(new_path)
197
+
198
+ if old_path == new_path[0...old_path.length]
199
+ raise ArgumentError,
200
+ 'The desination directory cannot be a subdirectory of the source directory.'
201
+ end
202
+
203
+ old_node = find(tr, old_path).prefetch_metadata(tr)
204
+ new_node = find(tr, new_path).prefetch_metadata(tr)
205
+
206
+ raise ArgumentError, 'The source directory does not exist.' unless old_node.exists?
207
+
208
+ if old_node.is_in_partition? || new_node.is_in_partition?
209
+ if !old_node.is_in_partition? ||
210
+ !new_node.is_in_partition? ||
211
+ old_node.path != new_node.path
212
+ then
213
+ raise ArgumentError, 'Cannot move between partitions'
214
+ end
215
+
216
+ next new_node
217
+ .get_contents(self)
218
+ .move(tr, old_node.get_partition_subpath, new_node.get_partition_subpath)
219
+ end
220
+
221
+ if new_node.exists?
222
+ raise ArgumentError, 'The destination directory already exists. Remove it first.'
223
+ end
224
+
225
+ parent_node = find(tr, new_path[0...-1])
226
+ if !parent_node.exists?
227
+ raise ArgumentError,
228
+ 'The parent directory of the destination directory does not exist. Create it first.'
229
+ end
230
+
231
+ tr[parent_node.subspace[@@SUBDIRS][new_path[-1]]] =
232
+ @node_subspace.unpack(old_node.subspace.key)[0]
233
+ remove_from_parent(tr, old_path)
234
+
235
+ contents_of_node(old_node.subspace, new_path, old_node.layer)
236
+ end
237
+ end
238
+
239
+ def remove(db_or_tr, path=[])
240
+ remove_internal(db_or_tr, path, true)
241
+ end
242
+
243
+ def remove_if_exists(db_or_tr, path=[])
244
+ remove_internal(db_or_tr, path, false)
245
+ end
246
+
247
+ def list(db_or_tr, path=[])
248
+ db_or_tr.transact do |tr|
249
+ check_version(tr, false)
250
+
251
+ path = to_unicode_path(path)
252
+ node = find(tr, path).prefetch_metadata(tr)
253
+
254
+ raise ArgumentError, 'The directory does not exist.' unless node.exists?
255
+
256
+ if node.is_in_partition?(nil, true)
257
+ next node.get_contents(self).list(tr, node.get_partition_subpath)
258
+ end
259
+
260
+ subdir_names_and_nodes(tr, node.subspace).map { |name, node| name }
261
+ end
262
+ end
263
+
264
+ def exists?(db_or_tr, path=[])
265
+ db_or_tr.transact do |tr|
266
+ check_version(tr, false)
267
+
268
+ path = to_unicode_path(path)
269
+ node = find(tr, path).prefetch_metadata(tr)
270
+
271
+ next false if !node.exists?
272
+
273
+ if node.is_in_partition?
274
+ next node.get_contents(self).exists?(tr, node.get_partition_subpath)
275
+ end
276
+
277
+ true
278
+ end
279
+ end
280
+
281
+ protected
282
+
283
+ def create_or_open_internal(db_or_tr, path, allow_create, allow_open, options={})
284
+ defaults = { :layer => '', :prefix => nil }
285
+ options = defaults.merge(options)
286
+
287
+ if !options[:prefix].nil? and allow_open and allow_create
288
+ raise ArgumentError, 'Cannot specify a prefix when calling create_or_open.'
289
+ end
290
+
291
+ if !options[:prefix].nil? and !@allow_manual_prefixes
292
+ if @path.length == 0
293
+ raise ArgumentError, 'Cannot specify a prefix unless manual prefixes are enabled.'
294
+ else
295
+ raise ArgumentError, 'Cannot specify a prefix in a partition.'
296
+ end
297
+ end
298
+
299
+ db_or_tr.transact do |tr|
300
+ check_version(tr, false)
301
+ path = to_unicode_path(path)
302
+
303
+ raise ArgumentError, 'The root directory cannot be opened.' if path.length == 0
304
+
305
+ existing_node = find(tr, path).prefetch_metadata(tr)
306
+
307
+ if existing_node.exists?
308
+ if existing_node.is_in_partition?
309
+ subpath = existing_node.get_partition_subpath
310
+ existing_node.get_contents(self).directory_layer.create_or_open_internal(tr, subpath, allow_create, allow_open, options)
311
+ else
312
+ raise ArgumentError, 'The directory already exists.' unless allow_open
313
+ open_directory(path, options, existing_node)
314
+ end
315
+ else
316
+ raise ArgumentError, 'The directory does not exist.' unless allow_create
317
+ create_directory(tr, path, options)
318
+ end
319
+ end
320
+ end
321
+ def open_directory(path, options, existing_node)
322
+ if options[:layer] and !options[:layer].empty? and options[:layer] != existing_node.layer
323
+ raise 'The directory was created with an incompatible layer.'
324
+ end
325
+ existing_node.get_contents(self)
326
+ end
327
+
328
+ def create_directory(tr, path, options)
329
+ check_version(tr, true)
330
+
331
+ prefix = options[:prefix]
332
+ if prefix.nil?
333
+ prefix = @content_subspace.key + @allocator.allocate(tr)
334
+ if !tr.get_range_start_with(prefix, { :limit => 1 }).to_a.empty?
335
+ raise "The database has keys stored at the prefix chosen by the automatic prefix allocator: #{prefix.dump}."
336
+ end
337
+
338
+ if !is_prefix_free?(tr.snapshot, prefix)
339
+ raise 'The directory layer has manually allocated prefixes that conflict with the automatic prefix allocator.'
340
+ end
341
+ elsif !is_prefix_free?(tr, prefix)
342
+ raise ArgumentError, 'The given prefix is already in use.'
343
+ end
344
+
345
+ parent_node = if path[0...-1].length > 0
346
+ node_with_prefix(create_or_open(tr, path[0...-1]).key)
347
+ else
348
+ @root_node
349
+ end
350
+
351
+ raise 'The parent directory does not exist.' unless parent_node
352
+
353
+ node = node_with_prefix(prefix)
354
+ tr[parent_node[@@SUBDIRS][path[-1]]] = prefix
355
+ tr[node['layer']] = options[:layer]
356
+
357
+ contents_of_node(node, path, options[:layer])
358
+ end
359
+
360
+ def remove_internal(db_or_tr, path, fail_on_nonexistent)
361
+ db_or_tr.transact do |tr|
362
+ check_version(tr, true)
363
+
364
+ path = to_unicode_path(path)
365
+
366
+ if path.empty?
367
+ raise ArgumentError, 'The root directory cannot be removed.'
368
+ end
369
+
370
+ node = find(tr, path).prefetch_metadata(tr)
371
+
372
+ if !node.exists?
373
+ raise ArgumentError, 'The directory does not exist.' if fail_on_nonexistent
374
+ next false
375
+ end
376
+
377
+ if node.is_in_partition?
378
+ next node.get_contents(self).directory_layer
379
+ .remove_internal(tr, node.get_partition_subpath, fail_on_nonexistent)
380
+ end
381
+
382
+ remove_recursive(tr, node.subspace)
383
+ remove_from_parent(tr, path)
384
+ true
385
+ end
386
+ end
387
+
388
+ private
389
+
390
+ def check_version(tr, write_access)
391
+ version = tr[@root_node['version']]
392
+
393
+ initialize_directory(tr) if !version && write_access
394
+ return if !version
395
+
396
+ version = version.to_s.unpack('III<')
397
+
398
+ dir_ver = "#{version[0]}.#{version[1]}.#{version[2]}"
399
+ layer_ver = "#{@@VERSION[0]}.#{@@VERSION[1]}.#{@@VERSION[2]}"
400
+
401
+ if version[0] != @@VERSION[0]
402
+ raise "Cannot load directory with version #{dir_ver} using directory layer #{layer_ver}"
403
+ elsif version[1] != @@VERSION[1] && write_access
404
+ raise "Directory with version #{dir_ver} is read-only
405
+ when opened using directory layer #{layer_ver}"
406
+ end
407
+ end
408
+
409
+ def initialize_directory(tr)
410
+ tr[@root_node['version']] = @@VERSION.pack('III<')
411
+ end
412
+
413
+ def node_containing_key(tr, key)
414
+ return @root_node if key.start_with?(@node_subspace.key)
415
+
416
+ tr.get_range(@node_subspace.range[0],
417
+ @node_subspace.pack([key]) + "\x00",
418
+ { :reverse => true, :limit => 1})
419
+ .map { |kv|
420
+ prev_prefix = @node_subspace.unpack(kv.key)[0]
421
+ node_with_prefix(prev_prefix) if key.start_with?(prev_prefix)
422
+ }[0]
423
+ end
424
+
425
+ def node_with_prefix(prefix)
426
+ @node_subspace[prefix] if !prefix.nil?
427
+ end
428
+
429
+ def contents_of_node(node, path, layer='')
430
+ prefix = @node_subspace.unpack(node.key)[0]
431
+ if layer == 'partition'
432
+ DirectoryPartition.new(@path + path, prefix, self)
433
+ else
434
+ DirectorySubspace.new(@path + path, prefix, self, layer)
435
+ end
436
+ end
437
+
438
+ def find(tr, path)
439
+ node = Internal::Node.new(@root_node, [], path)
440
+ path.each_with_index do |name, index|
441
+ node = Internal::Node.new(node_with_prefix(tr[node.subspace[@@SUBDIRS][name]]),
442
+ path[0..index], path)
443
+
444
+ return node unless node.exists? and node.layer(tr) != 'partition'
445
+ end
446
+
447
+ node
448
+ end
449
+
450
+ def subdir_names_and_nodes(tr, node)
451
+ subdir = node[@@SUBDIRS]
452
+ tr.get_range(subdir.range[0], subdir.range[1]).map { |kv|
453
+ [subdir.unpack(kv.key)[0], node_with_prefix(kv.value)]
454
+ }
455
+ end
456
+
457
+ def remove_from_parent(tr, path)
458
+ parent = find(tr, path[0...-1])
459
+ tr.clear(parent.subspace[@@SUBDIRS][path[-1]])
460
+ end
461
+
462
+ def remove_recursive(tr, node)
463
+ subdir_names_and_nodes(tr, node).each do |name, subnode|
464
+ remove_recursive(tr, subnode)
465
+ end
466
+
467
+ tr.clear_range_start_with(@node_subspace.unpack(node.key)[0])
468
+ tr.clear_range(node.range[0], node.range[1])
469
+ end
470
+
471
+ def is_prefix_free?(tr, prefix)
472
+ prefix &&
473
+ prefix.length > 0 &&
474
+ !node_containing_key(tr, prefix) &&
475
+ tr.get_range(@node_subspace.pack([prefix]), @node_subspace.pack([FDB.strinc(prefix)]),
476
+ { :limit => 1 }).to_a.empty?
477
+ end
478
+
479
+ def convert_path_element(name)
480
+ if !name.kind_of? String
481
+ raise TypeError, 'Invalid path: must be a unicode string or an array of unicode strings'
482
+ end
483
+ name.dup.force_encoding('UTF-8')
484
+ end
485
+
486
+ def to_unicode_path(path)
487
+ if path.respond_to? 'each_with_index'
488
+ path.each_with_index { |name, index| path[index] = convert_path_element(name) }
489
+ else
490
+ [convert_path_element(path)]
491
+ end
492
+ end
493
+ end
494
+
495
+ @@directory = DirectoryLayer.new
496
+ def self.directory
497
+ @@directory
498
+ end
499
+
500
+ class DirectorySubspace < Subspace
501
+ def initialize(path, prefix, directory_layer=FDB::directory, layer='')
502
+ super([], prefix)
503
+ @path = path
504
+ @layer = layer
505
+ @directory_layer = directory_layer
506
+ end
507
+
508
+ def path
509
+ return @path.dup
510
+ end
511
+
512
+ def layer
513
+ return @layer.dup
514
+ end
515
+
516
+ attr_reader :directory_layer
517
+
518
+ def create_or_open(db_or_tr, name_or_path, options={})
519
+ path = tuplify_path(name_or_path)
520
+ @directory_layer.create_or_open(db_or_tr, partition_subpath(path), options)
521
+ end
522
+
523
+ def open(db_or_tr, name_or_path, options={})
524
+ path = tuplify_path(name_or_path)
525
+ @directory_layer.open(db_or_tr, partition_subpath(path), options)
526
+ end
527
+
528
+ def create(db_or_tr, name_or_path, options={})
529
+ path = tuplify_path(name_or_path)
530
+ @directory_layer.create(db_or_tr, partition_subpath(path), options)
531
+ end
532
+
533
+ def list(db_or_tr, name_or_path=[])
534
+ path = tuplify_path(name_or_path)
535
+ @directory_layer.list(db_or_tr, partition_subpath(path))
536
+ end
537
+
538
+ def move(db_or_tr, old_name_or_path, new_name_or_path)
539
+ old_path = tuplify_path(old_name_or_path)
540
+ new_path = tuplify_path(new_name_or_path)
541
+ @directory_layer.move(db_or_tr, partition_subpath(old_path), partition_subpath(new_path))
542
+ end
543
+
544
+ def move_to(db_or_tr, new_absolute_name_or_path)
545
+ directory_layer = get_layer_for_path([])
546
+ new_absolute_path = directory_layer.send(:to_unicode_path, new_absolute_name_or_path)
547
+ partition_len = directory_layer.path.length
548
+ partition_path = new_absolute_path[0...partition_len]
549
+ raise ArgumentError, 'Cannot move between partitions.' if partition_path != directory_layer.path
550
+ directory_layer.move(db_or_tr, @path[partition_len..-1],
551
+ new_absolute_path[partition_len..-1])
552
+ end
553
+
554
+ def remove(db_or_tr, name_or_path=[])
555
+ path = tuplify_path(name_or_path)
556
+ directory_layer = get_layer_for_path(path)
557
+ directory_layer.remove(db_or_tr, partition_subpath(path, directory_layer))
558
+ end
559
+
560
+ def remove_if_exists(db_or_tr, name_or_path=[])
561
+ path = tuplify_path(name_or_path)
562
+ directory_layer = get_layer_for_path(path)
563
+ directory_layer.remove_if_exists(db_or_tr, partition_subpath(path, directory_layer))
564
+ end
565
+
566
+ def exists?(db_or_tr, name_or_path=[])
567
+ path = tuplify_path(name_or_path)
568
+ directory_layer = get_layer_for_path(path)
569
+ directory_layer.exists?(db_or_tr, partition_subpath(path, directory_layer))
570
+ end
571
+
572
+ def tuplify_path(path)
573
+ if path.is_a? String
574
+ [path]
575
+ else
576
+ path
577
+ end
578
+ end
579
+ private :tuplify_path
580
+
581
+ def partition_subpath(path, directory_layer = @directory_layer)
582
+ self.path[directory_layer.path.length..-1] + path
583
+ end
584
+ private :partition_subpath
585
+
586
+ def get_layer_for_path(path)
587
+ @directory_layer
588
+ end
589
+ private :get_layer_for_path
590
+ end
591
+
592
+ class DirectoryPartition < DirectorySubspace
593
+ def initialize(path, prefix, parent_directory_layer)
594
+ directory_layer = DirectoryLayer.new(:node_subspace => Subspace.new([], prefix + "\xfe"),
595
+ :content_subspace => Subspace.new([], prefix))
596
+ directory_layer.send(:path=, path)
597
+ super(path, prefix, directory_layer, 'partition')
598
+ @parent_directory_layer = parent_directory_layer
599
+ end
600
+
601
+ def [](name)
602
+ raise 'Cannot open subspace in the root of a directory partition.'
603
+ end
604
+
605
+ def key
606
+ raise 'Cannot get key for the root of a directory partition.'
607
+ end
608
+
609
+ def pack(tuple)
610
+ raise 'Cannot pack keys using the root of a directory partition.'
611
+ end
612
+
613
+ def unpack(key)
614
+ raise 'Cannot unpack keys using the root of a directory partition.'
615
+ end
616
+
617
+ def range(tuple=[])
618
+ raise 'Cannot get range for the root of a directory partition.'
619
+ end
620
+
621
+ def contains?(key)
622
+ raise 'Cannot check whether a key belongs to the root of a directory partition.'
623
+ end
624
+
625
+ def as_foundationdb_key
626
+ raise 'Cannot use the root of a directory partition as a key.'
627
+ end
628
+
629
+ def subspace(tuple)
630
+ raise 'Cannot open subspace in the root of a directory partition.'
631
+ end
632
+
633
+ def get_layer_for_path(path)
634
+ if path.length == 0
635
+ @parent_directory_layer
636
+ else
637
+ @directory_layer
638
+ end
639
+ end
640
+ private :get_layer_for_path
641
+ end
642
+
643
+ module Internal
644
+ class Node
645
+ def initialize(subspace, path, target_path)
646
+ @subspace = subspace
647
+ @path = path
648
+ @target_path = target_path
649
+ @layer = nil
650
+ end
651
+
652
+ attr_reader :subspace
653
+ attr_reader :path
654
+
655
+ def exists?
656
+ !@subspace.nil?
657
+ end
658
+
659
+ def prefetch_metadata(tr)
660
+ layer(tr) if exists?
661
+ self
662
+ end
663
+
664
+ def layer(tr=nil)
665
+ if tr
666
+ @layer = tr[@subspace['layer']]
667
+ else
668
+ raise 'Layer has not been read' unless @layer
669
+ end
670
+
671
+ @layer
672
+ end
673
+
674
+ def is_in_partition?(tr=nil, include_empty_subpath=false)
675
+ exists? &&
676
+ @layer == 'partition' &&
677
+ (include_empty_subpath || @path.length < @target_path.length)
678
+ end
679
+
680
+ def get_partition_subpath(tr=nil)
681
+ @target_path[@path.length..-1]
682
+ end
683
+
684
+ def get_contents(directory_layer, tr=nil)
685
+ directory_layer.send(:contents_of_node, @subspace, @path, layer(tr))
686
+ end
687
+ end
688
+ end
689
+ end