persistence-adapter-kyotocabinet 0.0.1

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/CHANGELOG.rdoc ADDED
@@ -0,0 +1,4 @@
1
+
2
+ ## 6/26/2012 ##
3
+
4
+ Initial release.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # KyotoCabinet Persistence Adapter #
2
+
3
+ http://rubygems.org/gems/persistence-adapter-kyotocabinet
4
+
5
+ # Summary #
6
+
7
+ Adapter to use <a href="http://fallabs.com/kyotocabinet/">KyotoCabinet</a> as storage port for <a href="https://rubygems.org/gems/persistence">Persistence</a> (<a href="https://github.com/RidiculousPower/persistence">on GitHub</a>).
8
+
9
+ # Description #
10
+
11
+ Implements necessary methods to run Persistence on top of Kyoto Cabinet.
12
+
13
+ # Install #
14
+
15
+ * sudo gem install persistence-adapter-kyotocabinet
16
+
17
+ # Usage #
18
+
19
+ The KyotoCabinet adapter is an abstract implementation. Using it requires specifying serialization methods. This permits the creation of concrete adapter implementations that are highly configurable.
20
+
21
+ At this point, two versions exist:
22
+
23
+ * KyotoCabinet::Marshal
24
+ * KyotoCabinet::YAML
25
+
26
+ To use Marshal:
27
+
28
+ ```ruby
29
+ kyotocabinet_marshal_adapter = ::Persistence::Adapter::KyotoCabinet::Marshal.new
30
+
31
+ Persistence.enable_port( :kyotocabinet_marshal_port, kyotocabinet_marshal_adapter )
32
+ ```
33
+
34
+ To use YAML:
35
+
36
+ ```ruby
37
+ kyotocabinet_yaml_adapter = ::Persistence::Adapter::KyotoCabinet::YAML.new
38
+
39
+ Persistence.enable_port( :kyotocabinet_yaml_port, kyotocabinet_yaml_adapter )
40
+ ```
41
+
42
+ # License #
43
+
44
+ (The MIT License)
45
+
46
+ Copyright (c) 2012, Asher, Ridiculous Power
47
+
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of this software and associated documentation files (the
50
+ 'Software'), to deal in the Software without restriction, including
51
+ without limitation the rights to use, copy, modify, merge, publish,
52
+ distribute, sublicense, and/or sell copies of the Software, and to
53
+ permit persons to whom the Software is furnished to do so, subject to
54
+ the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be
57
+ included in all copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
60
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
61
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
62
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
63
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
64
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
65
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,18 @@
1
+
2
+ require 'persistence'
3
+
4
+ require 'kyotocabinet'
5
+
6
+ require 'yaml'
7
+
8
+ # namespaces that have to be declared ahead of time for proper load order
9
+ require_relative './namespaces'
10
+
11
+ # source file requires
12
+ require_relative './requires.rb'
13
+
14
+ class ::Persistence::Adapter::KyotoCabinet
15
+
16
+ include ::Persistence::Adapter::KyotoCabinet::AdapterInterface
17
+
18
+ end
@@ -0,0 +1,196 @@
1
+
2
+ module ::Persistence::Adapter::KyotoCabinet::AdapterInterface
3
+
4
+ include ::Persistence::Adapter::Abstract::EnableDisable
5
+
6
+ include ::Persistence::Adapter::KyotoCabinet::DatabaseSupport
7
+
8
+ DatabaseFlags = ::KyotoCabinet::DB::OWRITER | ::KyotoCabinet::DB::OCREATE
9
+
10
+ Delimiter = '.'
11
+
12
+ ################
13
+ # initialize #
14
+ ################
15
+
16
+ def initialize( home_directory )
17
+
18
+ super( home_directory )
19
+
20
+ @buckets = { }
21
+
22
+ end
23
+
24
+ ############
25
+ # enable #
26
+ ############
27
+
28
+ def enable
29
+
30
+ super
31
+
32
+ # holds global ID sequence
33
+ @database__id_sequence = ::KyotoCabinet::DB.new
34
+ @database__id_sequence.open( file__id_sequence_database, DatabaseFlags )
35
+
36
+ # holds global ID => primary bucket
37
+ @database__primary_bucket_for_id = ::KyotoCabinet::DB.new
38
+ @database__primary_bucket_for_id.open( file__primary_bucket_for_id_database, DatabaseFlags )
39
+
40
+ return self
41
+
42
+ end
43
+
44
+ #############
45
+ # disable #
46
+ #############
47
+
48
+ def disable
49
+
50
+ super
51
+
52
+ @database__id_sequence.close
53
+ @database__primary_bucket_for_id.close
54
+
55
+ return self
56
+
57
+ end
58
+
59
+ ########################
60
+ # persistence_bucket #
61
+ ########################
62
+
63
+ def persistence_bucket( bucket_name )
64
+
65
+ bucket_instance = nil
66
+
67
+ unless bucket_instance = @buckets[ bucket_name ]
68
+ bucket_instance = ::Persistence::Adapter::KyotoCabinet::Bucket.new( self, bucket_name )
69
+ @buckets[ bucket_name ] = bucket_instance
70
+ end
71
+
72
+ return bucket_instance
73
+
74
+ end
75
+
76
+ ###################################
77
+ # get_bucket_name_for_object_id #
78
+ ###################################
79
+
80
+ def get_bucket_name_for_object_id( global_id )
81
+
82
+ bucket_name = @database__primary_bucket_for_id.get( global_id )
83
+
84
+ bucket_name = bucket_name.to_sym if bucket_name
85
+
86
+ return bucket_name
87
+
88
+ end
89
+
90
+ #############################
91
+ # get_class_for_object_id #
92
+ #############################
93
+
94
+ def get_class_for_object_id( global_id )
95
+
96
+ bucket_name = get_bucket_name_for_object_id( global_id )
97
+
98
+ bucket_instance = persistence_bucket( bucket_name )
99
+
100
+ return bucket_instance.get_class( global_id )
101
+
102
+ end
103
+
104
+ #################################
105
+ # delete_bucket_for_object_id #
106
+ #################################
107
+
108
+ def delete_bucket_for_object_id( global_id )
109
+
110
+ return @database__primary_bucket_for_id.remove( global_id )
111
+
112
+ end
113
+
114
+ ################################
115
+ # delete_class_for_object_id #
116
+ ################################
117
+
118
+ def delete_class_for_object_id( global_id )
119
+
120
+ bucket_name = get_bucket_name_for_object_id( global_id )
121
+
122
+ bucket_instance = persistence_bucket( bucket_name )
123
+
124
+ return bucket_instance.delete_class( global_id )
125
+
126
+ end
127
+
128
+ ##########################################
129
+ # ensure_object_has_globally_unique_id #
130
+ ##########################################
131
+
132
+ def ensure_object_has_globally_unique_id( object )
133
+
134
+ unless object.persistence_id
135
+
136
+ # we only store one sequence so we don't need a key; increment it by 1
137
+ global_id = @database__id_sequence.increment( :sequence, 1, -1 )
138
+
139
+ # and write it to our global object database with a bucket/key struct as data
140
+ @database__primary_bucket_for_id.set( global_id, object.persistence_bucket.name )
141
+
142
+ object.persistence_id = global_id
143
+
144
+ end
145
+
146
+ return self
147
+
148
+ end
149
+
150
+ ##################################################################################################
151
+ private ######################################################################################
152
+ ##################################################################################################
153
+
154
+ ################################
155
+ # file__id_sequence_database #
156
+ ################################
157
+
158
+ def file__id_sequence_database
159
+
160
+ return File.join( home_directory,
161
+ 'IDSequence' + extension__id_sequence_database )
162
+
163
+ end
164
+
165
+ ##########################################
166
+ # file__primary_bucket_for_id_database #
167
+ ##########################################
168
+
169
+ def file__primary_bucket_for_id_database
170
+
171
+ return File.join( home_directory,
172
+ 'PrimaryBucketForID' + extension__primary_bucket_for_id_database )
173
+
174
+ end
175
+
176
+ #####################################
177
+ # extension__id_sequence_database #
178
+ #####################################
179
+
180
+ def extension__id_sequence_database
181
+
182
+ return extension__database( :tree )
183
+
184
+ end
185
+
186
+ ###############################################
187
+ # extension__primary_bucket_for_id_database #
188
+ ###############################################
189
+
190
+ def extension__primary_bucket_for_id_database
191
+
192
+ return extension__database( :hash )
193
+
194
+ end
195
+
196
+ end
@@ -0,0 +1,6 @@
1
+
2
+ class ::Persistence::Adapter::KyotoCabinet::Bucket
3
+
4
+ include ::Persistence::Adapter::KyotoCabinet::Bucket::BucketInterface
5
+
6
+ end
@@ -0,0 +1,435 @@
1
+
2
+ module ::Persistence::Adapter::KyotoCabinet::Bucket::BucketInterface
3
+
4
+ include ::Persistence::Adapter::Abstract::PrimaryKey::IDPropertyString
5
+
6
+ include ::Persistence::Adapter::KyotoCabinet::DatabaseSupport
7
+
8
+ attr_accessor :parent_adapter, :name
9
+
10
+ # we're always opening as writers and creating the files if they don't exist
11
+
12
+ ################
13
+ # initialize #
14
+ ################
15
+
16
+ def initialize( parent_adapter, bucket_name )
17
+
18
+ @parent_adapter = parent_adapter
19
+ @name = bucket_name
20
+
21
+ # storage for index objects
22
+ @indexes = { }
23
+
24
+ database_flags = @parent_adapter.class::DatabaseFlags
25
+
26
+ # bucket database corresponding to self - holds properties
27
+ #
28
+ # objectID => klass
29
+ # objectID.property_A => property_value_A
30
+ # objectID.property_B => property_value_B
31
+ #
32
+ @database__bucket = ::KyotoCabinet::DB.new
33
+ @database__bucket.open( file__bucket_database( bucket_name ), database_flags )
34
+
35
+ # holds IDs that are presently in this bucket so we can iterate objects normally
36
+ #
37
+ # objectID => objectID
38
+ #
39
+ @database__ids_in_bucket = ::KyotoCabinet::DB.new
40
+ @database__ids_in_bucket.open( file__ids_in_bucket_database( bucket_name ), database_flags )
41
+
42
+ # holds whether each index permits duplicates
43
+ @database__index_permits_duplicates = ::KyotoCabinet::DB.new
44
+ @database__index_permits_duplicates.open( file__index_permits_duplicates_database( bucket_name ), database_flags )
45
+
46
+ end
47
+
48
+ ###########
49
+ # count #
50
+ ###########
51
+
52
+ def count
53
+
54
+ return @database__ids_in_bucket.count
55
+
56
+ end
57
+
58
+ ###########
59
+ # close #
60
+ ###########
61
+
62
+ def close
63
+
64
+ close_indexes
65
+
66
+ @database__index_permits_duplicates.close
67
+
68
+ super
69
+
70
+ end
71
+
72
+ ###################
73
+ # close_indexes #
74
+ ###################
75
+
76
+ def close_indexes
77
+
78
+ @indexes.each do |this_index_name, this_index_instance|
79
+ this_index_instance.close
80
+ end
81
+
82
+ end
83
+
84
+ ############
85
+ # cursor #
86
+ ############
87
+
88
+ def cursor
89
+ return ::Persistence::Adapter::KyotoCabinet::Cursor.new( self, nil, @database__ids_in_bucket.cursor )
90
+ end
91
+
92
+ #########################
93
+ # permits_duplicates? #
94
+ #########################
95
+
96
+ def permits_duplicates?( index )
97
+
98
+ permits_duplicates = @database__index_permits_duplicates.get( index )
99
+ permits_duplicates = ( permits_duplicates == 1 ? true : false ) unless permits_duplicates.nil?
100
+
101
+ return permits_duplicates
102
+
103
+ end
104
+
105
+ #################
106
+ # put_object! #
107
+ #################
108
+
109
+ def put_object!( object )
110
+
111
+ @parent_adapter.ensure_object_has_globally_unique_id( object )
112
+
113
+ # insert object class definition: ID => klass
114
+ # class definition is used as header/placeholder for object properties
115
+ @database__bucket.set( object.persistence_id, object.class.to_s )
116
+
117
+ # insert ID to cursor index
118
+ @database__ids_in_bucket.set( object.persistence_id, object.persistence_id )
119
+
120
+ # insert properties
121
+ object.persistence_hash_to_port.each do |primary_key, attribute_value|
122
+ put_attribute!( object.persistence_id, primary_key, attribute_value )
123
+ end
124
+
125
+ return object.persistence_id
126
+
127
+ end
128
+
129
+ ################
130
+ # get_object #
131
+ ################
132
+
133
+ def get_object( global_id )
134
+
135
+ object_persistence_hash = { }
136
+
137
+ # create cursor and set to position of ID
138
+ @database__bucket.cursor_process do |object_cursor|
139
+
140
+ if object_cursor.jump( global_id )
141
+
142
+ # Iterate until the key no longer begins with ID
143
+ # First record (ID only, no attribute) points to klass, so we have to move forward to start.
144
+ while this_attribute = next_attribute_of_this_object( object_cursor, global_id )
145
+
146
+ serialized_value = object_cursor.get_value
147
+
148
+ value = @parent_adapter.class::SerializationClass.__send__( @parent_adapter.class::UnserializationMethod, serialized_value )
149
+
150
+ object_persistence_hash[ this_attribute ] = value
151
+
152
+ end
153
+
154
+ end
155
+
156
+ end
157
+
158
+ return object_persistence_hash.empty? ? nil : object_persistence_hash
159
+
160
+ end
161
+
162
+ ####################
163
+ # delete_object! #
164
+ ####################
165
+
166
+ def delete_object!( global_id )
167
+
168
+ # delete from IDs in bucket database
169
+ @database__ids_in_bucket.remove( global_id )
170
+
171
+ # create cursor and set to position of ID
172
+ @database__bucket.cursor_process do |object_cursor|
173
+
174
+ if object_cursor.jump( global_id )
175
+
176
+ object_cursor.remove
177
+
178
+ # Iterate until the key no longer begins with ID
179
+ # First record (ID only, no attribute) points to klass, so we have to move forward to start.
180
+ while this_attribute = next_attribute_of_this_object( object_cursor, global_id )
181
+
182
+ this_attribute_value = object_cursor.get_value
183
+
184
+ object_cursor.remove
185
+
186
+ end
187
+
188
+ end
189
+
190
+ end
191
+
192
+ end
193
+
194
+ ####################
195
+ # put_attribute! #
196
+ ####################
197
+
198
+ def put_attribute!( global_id, attribute_name, value )
199
+
200
+ serialization_class = @parent_adapter.class::SerializationClass
201
+
202
+ serialized_value = serialization_class.__send__( @parent_adapter.class::SerializationMethod, value )
203
+
204
+ @database__bucket.set( attribute_name, serialized_value )
205
+
206
+ end
207
+
208
+ ###################
209
+ # get_attribute #
210
+ ###################
211
+
212
+ def get_attribute( global_id, attribute_name )
213
+
214
+ value = nil
215
+
216
+ if serialized_value = @database__bucket.get( attribute_name )
217
+
218
+ serialization_class = @parent_adapter.class::SerializationClass
219
+
220
+ value = serialization_class.__send__( @parent_adapter.class::UnserializationMethod, serialized_value )
221
+
222
+ end
223
+
224
+ return value
225
+
226
+ end
227
+
228
+ #######################
229
+ # delete_attribute! #
230
+ #######################
231
+
232
+ def delete_attribute!( global_id, attribute_name )
233
+
234
+ # delete primary info on attribute
235
+ @database__bucket.remove( attribute_name )
236
+
237
+ end
238
+
239
+ ##################
240
+ # create_index #
241
+ ##################
242
+
243
+ def create_index( index_name, permits_duplicates )
244
+
245
+ # make sure index doesn't already exist with conflict duplicate permission
246
+ unless ( permits_duplicates_value = permits_duplicates?( index_name ) ).nil?
247
+ if ! permits_duplicates_value != ! permits_duplicates
248
+ raise 'Index on :' + index_name.to_s + ' already exists and ' +
249
+ ( permits_duplicates ? 'does not permit' : 'permits' ) + ' duplicates, which conflicts.'
250
+ end
251
+
252
+ else
253
+
254
+ @database__index_permits_duplicates.set( index_name, permits_duplicates )
255
+
256
+ end
257
+
258
+ # create/instantiate the index
259
+ index_instance = self.class::Index.new( index_name, self, permits_duplicates )
260
+
261
+ # store index instance
262
+ @indexes[ index_name ] = index_instance
263
+
264
+ return index_instance
265
+
266
+ end
267
+
268
+ ###########
269
+ # index #
270
+ ###########
271
+
272
+ def index( index_name )
273
+
274
+ return @indexes[ index_name ]
275
+
276
+ end
277
+
278
+ ##################
279
+ # delete_index #
280
+ ##################
281
+
282
+ def delete_index( index_name )
283
+
284
+ # remove permits_duplicates configuration
285
+ @database__index_permits_duplicates.remove( index_name )
286
+
287
+ index_instance = @indexes.delete( index_name )
288
+
289
+ index_instance.delete
290
+
291
+ end
292
+
293
+ ################
294
+ # has_index? #
295
+ ################
296
+
297
+ def has_index?( index_name )
298
+
299
+ return @indexes.has_key?( index_name )
300
+
301
+ end
302
+
303
+ ###############
304
+ # get_class #
305
+ ###############
306
+
307
+ def get_class( global_id )
308
+
309
+ klass = nil
310
+
311
+ if klass_path_string = @database__bucket.get( global_id )
312
+
313
+ klass_path_parts = klass_path_string.split( '::' )
314
+
315
+ klass = klass_path_parts.inject( Object ) do |object_container_namespace, next_path_part|
316
+ object_container_namespace.const_get( next_path_part )
317
+ end
318
+
319
+ end
320
+
321
+ return klass
322
+
323
+ end
324
+
325
+ ###############
326
+ # get_class #
327
+ ###############
328
+
329
+ def delete_class( global_id )
330
+
331
+ @database__bucket.remove( global_id )
332
+
333
+ end
334
+
335
+ ##################################################################################################
336
+ private ######################################################################################
337
+ ##################################################################################################
338
+
339
+ ###################################
340
+ # next_attribute_of_this_object #
341
+ ###################################
342
+
343
+ def next_attribute_of_this_object( object_cursor, global_id )
344
+
345
+ attribute_name = nil
346
+
347
+ if object_cursor.step and primary_key = object_cursor.get_key
348
+
349
+ this_global_id, this_attribute = primary_key.split( @parent_adapter.class::Delimiter )
350
+
351
+ if this_global_id.to_i == global_id
352
+ attribute_name = this_attribute.to_sym
353
+ end
354
+
355
+ end
356
+
357
+ return attribute_name
358
+
359
+ end
360
+
361
+ ###########################
362
+ # file__bucket_database #
363
+ ###########################
364
+
365
+ def file__bucket_database( bucket_name )
366
+
367
+ return File.join( @parent_adapter.home_directory,
368
+ bucket_name.to_s + extension__bucket_database )
369
+
370
+ end
371
+
372
+ ##################################
373
+ # file__ids_in_bucket_database #
374
+ ##################################
375
+
376
+ def file__ids_in_bucket_database( bucket_name )
377
+
378
+ return File.join( @parent_adapter.home_directory,
379
+ bucket_name.to_s + '__ids_in_bucket__' + extension__ids_in_bucket_database )
380
+
381
+ end
382
+
383
+ #############################################
384
+ # file__index_permits_duplicates_database #
385
+ #############################################
386
+
387
+ def file__index_permits_duplicates_database( bucket_name )
388
+
389
+ index_file_name = bucket_name.to_s + '__index_permits_duplicates__' + extension__index_permits_duplicates_database
390
+
391
+ return File.join( @parent_adapter.home_directory, index_file_name )
392
+
393
+ end
394
+
395
+ ################################
396
+ # extension__bucket_database #
397
+ ################################
398
+
399
+ def extension__bucket_database
400
+
401
+ return extension__database( :tree )
402
+
403
+ end
404
+
405
+ ###################################################
406
+ # extension__indexes_permit_duplicates_database #
407
+ ###################################################
408
+
409
+ def extension__indexes_permit_duplicates_database
410
+
411
+ return extension__database( :hash )
412
+
413
+ end
414
+
415
+ #######################################
416
+ # extension__ids_in_bucket_database #
417
+ #######################################
418
+
419
+ def extension__ids_in_bucket_database
420
+
421
+ return extension__database( :hash )
422
+
423
+ end
424
+
425
+ ##################################################
426
+ # extension__index_permits_duplicates_database #
427
+ ##################################################
428
+
429
+ def extension__index_permits_duplicates_database
430
+
431
+ return extension__database( :hash )
432
+
433
+ end
434
+
435
+ end