persistence-adapter-kyotocabinet 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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