xamplr 1.2.0 → 1.3.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. data/.document +5 -0
  2. data/.gitignore +12 -0
  3. data/CHANGES.txt +37 -0
  4. data/COPYING +661 -0
  5. data/Makefile +13 -0
  6. data/README.rdoc +24 -4
  7. data/Rakefile +2 -2
  8. data/VERSION.yml +3 -2
  9. data/bin/xampl-gen +17 -0
  10. data/docs/intro/.gitignore +1 -0
  11. data/docs/intro/example1/example1.rb +6 -0
  12. data/docs/intro/example1/xml/hello.xml +1 -0
  13. data/docs/intro/example2/example2.rb +38 -0
  14. data/docs/intro/example2/project-generator.rb +22 -0
  15. data/docs/intro/example2/xml/hello.xml +2 -0
  16. data/docs/intro/example3/example3.rb +30 -0
  17. data/docs/intro/example3/example3a.rb +16 -0
  18. data/docs/intro/example3/generated.png +0 -0
  19. data/docs/intro/example3/grab-yuml.rb +10 -0
  20. data/docs/intro/example3/greeter.rb +15 -0
  21. data/docs/intro/example3/greeting.rb +9 -0
  22. data/docs/intro/example3/project-generator.rb +25 -0
  23. data/docs/intro/example3/xml/greeter.xml +6 -0
  24. data/docs/intro/example4/example4.rb +27 -0
  25. data/docs/intro/example4/example4a.rb +19 -0
  26. data/docs/intro/example4/grab-yuml.rb +10 -0
  27. data/docs/intro/example4/greeter.rb +15 -0
  28. data/docs/intro/example4/greeting.rb +9 -0
  29. data/docs/intro/example4/project-generator.rb +25 -0
  30. data/docs/intro/example4/xml/greeter.xml +6 -0
  31. data/docs/intro/grab-yuml.rb +10 -0
  32. data/docs/intro/project-generator.rb +51 -0
  33. data/docs/intro/xampl.lyx +1494 -0
  34. data/docs/intro/xampl.pdf +0 -0
  35. data/examples/employees/final/xampl-gen.rb +38 -0
  36. data/examples/employees/final/xml/ddd-final.xml +86 -0
  37. data/examples/employees/final-xampl/xampl-gen.rb +38 -0
  38. data/examples/employees/final-xampl/xml/ddd-final-xampl.xml +86 -0
  39. data/examples/employees/first/xampl-gen.rb +38 -0
  40. data/examples/employees/first/xml/ddd-first.xml +48 -0
  41. data/examples/employees/twist/twist.graphml +333 -0
  42. data/examples/employees/twist/xampl-gen.rb +38 -0
  43. data/examples/employees/twist/xml/twist.xml +90 -0
  44. data/examples/employees/xamplr-twist.xml +2 -0
  45. data/examples/employees/yuml-diagrams/DDD-final-approach.graphml +393 -0
  46. data/examples/employees/yuml-diagrams/DDD-final-xampl-approach.graphml +265 -0
  47. data/examples/employees/yuml-diagrams/DDD-ideal-final-approach.graphml +357 -0
  48. data/examples/employees/yuml-diagrams/ddd-final.png +0 -0
  49. data/examples/employees/yuml-diagrams/ddd-final.yuml +8 -0
  50. data/examples/employees/yuml-diagrams/ddd-first.png +0 -0
  51. data/examples/employees/yuml-diagrams/ddd-first.yuml +7 -0
  52. data/examples/employees/yuml-diagrams/final-yed.png +0 -0
  53. data/examples/employees/yuml-diagrams/first-yed.png +0 -0
  54. data/examples/employees/yuml-diagrams/twist.png +0 -0
  55. data/examples/employees/yuml-diagrams/twist.yuml +12 -0
  56. data/examples/employees/yuml-diagrams/xamplr-final-no-mixins.png +0 -0
  57. data/examples/employees/yuml-diagrams/xamplr-final-simplified.png +0 -0
  58. data/examples/employees/yuml-diagrams/xamplr-final-with-mixins.png +0 -0
  59. data/examples/employees/yuml-diagrams/yuml-simplified.txt +19 -0
  60. data/examples/employees/yuml-diagrams/yuml-with-mixins.txt +53 -0
  61. data/examples/employees/yuml-diagrams/yuml.txt +27 -0
  62. data/examples/hobbies/Makefile +5 -0
  63. data/examples/hobbies/hobbies.rb +193 -0
  64. data/examples/hobbies/project-generator.rb +25 -0
  65. data/examples/hobbies/xampl-gen.rb +38 -0
  66. data/examples/hobbies/xml/hobby.xml +3 -0
  67. data/examples/hobbies/xml/people.xml +5 -0
  68. data/examples/random-people/.gitignore +2 -0
  69. data/examples/random-people/Makefile +5 -1
  70. data/examples/random-people/xampl-gen.rb +5 -2
  71. data/examples/random-people-shared-addresses/.gitignore +2 -0
  72. data/examples/random-people-shared-addresses/Makefile +8 -9
  73. data/examples/random-people-shared-addresses/batch-load-users-profiled.rb +91 -0
  74. data/examples/random-people-shared-addresses/batch-load-users-safe.rb +81 -0
  75. data/examples/random-people-shared-addresses/batch-load-users.rb +59 -63
  76. data/examples/random-people-shared-addresses/xampl-gen.rb +2 -0
  77. data/examples/read-testing/.gitignore +2 -0
  78. data/examples/read-testing/rrr.rb +2 -1
  79. data/examples/read-testing/xampl-gen.rb +2 -0
  80. data/examples/tokyo-cabinet-experimental/.gitignore +2 -0
  81. data/examples/tokyo-cabinet-experimental/xampl-gen.rb +2 -0
  82. data/examples/write-testing/README +4 -0
  83. data/examples/write-testing/RESULTS.home +33 -0
  84. data/examples/write-testing/RESULTS.raconteur +33 -0
  85. data/examples/write-testing/write-speed-test.rb +40 -0
  86. data/lib/xamplr/.cvsignore +1 -0
  87. data/lib/xamplr/TODO +2 -1
  88. data/lib/xamplr/exceptions.rb +24 -1
  89. data/lib/xamplr/from-xml.rb +13 -5
  90. data/lib/xamplr/graphml-out.rb +6 -25
  91. data/lib/xamplr/indexed-array.rb +10 -2
  92. data/lib/xamplr/persist-to-xml.rb +6 -7
  93. data/lib/xamplr/persistence.rb +132 -48
  94. data/lib/xamplr/persister.rb +48 -25
  95. data/lib/xamplr/persisters/filesystem.rb +8 -2
  96. data/lib/xamplr/persisters/tokyo-cabinet.rb +243 -63
  97. data/lib/xamplr/persisters/tokyo-cabinet.rb.1-DB +694 -0
  98. data/lib/xamplr/persisters/tokyo-cabinet.rb.N-DB +692 -0
  99. data/lib/xamplr/persisters/tokyo-cabinet.rb.NICE-TRY +807 -0
  100. data/lib/xamplr/templates/.cvsignore +3 -0
  101. data/lib/xamplr/templates/child_indexed.template +6 -6
  102. data/lib/xamplr/templates/element_data.template +2 -1
  103. data/lib/xamplr/templates/element_empty.template +2 -10
  104. data/lib/xamplr/templates/element_mixed.template +2 -1
  105. data/lib/xamplr/templates/element_simple.template +2 -1
  106. data/lib/xamplr/templates/package.template +13 -1
  107. data/lib/xamplr/visitor.rb +7 -6
  108. data/lib/xamplr/visitors.rb +2 -1
  109. data/lib/xamplr/xampl-cl-gen.rb +89 -0
  110. data/lib/xamplr/xampl-generator.rb +86 -50
  111. data/lib/xamplr/xampl-object.rb +51 -0
  112. data/lib/xamplr/xampl-persisted-object.rb +13 -4
  113. data/lib/xamplr/yuml-out.rb +129 -0
  114. data/lib/xamplr-generator.rb +0 -1
  115. data/lib/xamplr.rb +18 -18
  116. data/regression/tc-indexes-crossing-pid-boundaries/Makefile +14 -0
  117. data/regression/tc-indexes-crossing-pid-boundaries/bad-idea.rb +40 -0
  118. data/regression/tc-indexes-crossing-pid-boundaries/fail-badly.rb +17 -0
  119. data/regression/tc-indexes-crossing-pid-boundaries/fail.rb +11 -0
  120. data/regression/tc-indexes-crossing-pid-boundaries/fucking-bad-idea.rb +44 -0
  121. data/regression/tc-indexes-crossing-pid-boundaries/setup.rb +15 -0
  122. data/regression/tc-indexes-crossing-pid-boundaries/xml/bad-idea.xml +4 -0
  123. data/regression/tightly-nested-mutual-mentions/Makefile +14 -0
  124. data/regression/tightly-nested-mutual-mentions/build.rb +31 -0
  125. data/regression/tightly-nested-mutual-mentions/load.rb +21 -0
  126. data/regression/tightly-nested-mutual-mentions/repo-keep.tgz +0 -0
  127. data/regression/tightly-nested-mutual-mentions/setup.rb +8 -0
  128. data/regression/tightly-nested-mutual-mentions/xampl-gen.rb +36 -0
  129. data/regression/tightly-nested-mutual-mentions/xml/stuff.xml +7 -0
  130. data/xamplr.gemspec +322 -0
  131. metadata +116 -13
  132. data/test/test_helper.rb +0 -10
  133. data/test/xamplr_test.rb +0 -7
@@ -0,0 +1,807 @@
1
+
2
+ # this is an attempt to ensure that the tc db is closed unless actively being accessed.
3
+ # it will not work, because a lot of code is assuming that it is actually open
4
+ # further more, it is incredibly slow
5
+ module Xampl
6
+
7
+ require 'fileutils'
8
+ require 'tokyocabinet'
9
+ require 'xamplr/persisters/caching'
10
+ require 'set'
11
+
12
+ # require 'ruby-prof'
13
+
14
+ class TokyoCabinetPersister < AbstractCachingPersister
15
+ include TokyoCabinet
16
+
17
+ def note_errors(msg="TokyoCabinet Error:: %s\n")
18
+ result = yield
19
+
20
+ rmsg = nil
21
+ unless result then
22
+ rmsg = sprintf(msg, @tc_db.errmsg(@tc_db.ecode))
23
+ STDERR.puts "NOTE: TokyoCabinet Error!"
24
+ STDERR.printf(rmsg)
25
+ STDERR.puts "---------"
26
+ caller(0).each do |trace|
27
+ STDERR.puts(trace)
28
+ end
29
+ STDERR.puts "---------"
30
+
31
+ STDOUT.puts "NOTE: TokyoCabinet Error!"
32
+ STDOUT.printf(rmsg)
33
+ STDOUT.puts "---------"
34
+ caller(0).each do |trace|
35
+ STDOUT.puts(trace)
36
+ end
37
+ STDOUT.puts "---------"
38
+
39
+
40
+ end
41
+ return rmsg
42
+ end
43
+
44
+ $lexical_indexes = Set.new(%w{ class pid time-stamp xampl-from xampl-to xampl-place }) unless defined?($lexical_indexes)
45
+
46
+ $numeric_indexes = Set.new(%w{ scheduled-delete-at }) unless defined?($numeric_indexes)
47
+
48
+ def TokyoCabinetPersister.add_lexical_indexs(indexes)
49
+ $lexical_indexes.merge(indexes)
50
+ end
51
+
52
+ def TokyoCabinetPersister.add_numeric_indexs(indexes)
53
+ $numeric_indexes.merge(indexes)
54
+ end
55
+
56
+ def initialize(name=nil, format=nil, root=File.join(".", "repo"))
57
+ super(root, name, format)
58
+
59
+ FileUtils.mkdir_p(@root_dir) unless File.exist?(@root_dir)
60
+ @filename = "#{@root_dir}/repo.tct"
61
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] "
62
+
63
+ if @tc_db then
64
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] TC[[#{ @filename }]]:: tc db already open"
65
+ end
66
+
67
+ open_tc_db()
68
+
69
+ # note_errors("TC[[#{ @filename }]]:: optimisation error: %s\n") do
70
+ # @tc_db.optimize(-1, -1, -1, TDB::TDEFLATE)
71
+ # end
72
+ # note_errors("TC[[#{ @filename }]]:: close error: %s\n") do
73
+ # @tc_db.close
74
+ # end
75
+
76
+ if @tc_db then
77
+ begin
78
+ note_errors("TC[[#{ @filename }]]:: close error in initialize: %s\n") do
79
+ @tc_db.close
80
+ end
81
+ @tc_db = nil
82
+ rescue => e
83
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] OH CRAP!!! #{ e }"
84
+ end
85
+ end
86
+ end
87
+
88
+ def open_tc_db
89
+ if @tcdb then
90
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] ALREADY OPEN #{ @filename }"
91
+ else
92
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] #{ @filename }"
93
+ # callers = caller(0)
94
+ # puts " 0 #{ callers[0] }"
95
+ # puts " 1 #{ callers[1] }"
96
+ # puts " 2 #{ callers[2] }"
97
+ #puts "#{File.basename(__FILE__)}:#{__LINE__} callers..."
98
+ #caller(0).each { | trace | puts " #{trace}"}
99
+ end
100
+
101
+ return if @tc_db # if there is a tc_db then it is already open
102
+
103
+ @tc_db = TDB.new
104
+ note_errors("TC[[#{ @filename }]]:: tuning error: %s\n") do
105
+ @tc_db.tune(-1, -1, -1, TDB::TDEFLATE)
106
+ end
107
+
108
+ note_errors("TC[[#{ @filename }]]:: open [#{ @filename }] error: %s\n") do
109
+ @tc_db.open(@filename, TDB::OWRITER | TDB::OCREAT | TDB::OLCKNB | TDB::OTSYNC ) #TDB::OTSYNC slows it down by almost 50 times
110
+ end
111
+
112
+ # Don't care if there are errors (in fact, if the index exists a failure is the expected thing)
113
+
114
+ $lexical_indexes.each do | index_name |
115
+ r = @tc_db.setindex(index_name, TDB::ITLEXICAL | TDB::ITKEEP)
116
+ end
117
+ $numeric_indexes.each do | index_name |
118
+ @tc_db.setindex(index_name, TDB::ITDECIMAL | TDB::ITKEEP)
119
+ end
120
+ end
121
+
122
+ def optimise(opts)
123
+ return unless @tc_db
124
+
125
+ if opts[:indexes_only] then
126
+ # Don't care if there are errors (in fact, if the index exists a failure is the expected thing)
127
+ $lexical_indexes.each do | index_name |
128
+ @tc_db.setindex(index_name, 9998)
129
+ end
130
+ $numeric_indexes.each do | index_name |
131
+ @tc_db.setindex(index_name, 9998)
132
+ end
133
+ else
134
+ note_errors("TC[[#{ @filename }]]:: optimisation error: %s\n") do
135
+ @tc_db.optimize(-1, -1, -1, 0xff)
136
+ end
137
+ end
138
+ end
139
+
140
+ def close
141
+ # puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] #{ @filename }"
142
+ # callers = caller(0)
143
+ # puts " 0 #{ callers[0] }"
144
+ # puts " 1 #{ callers[1] }"
145
+ # puts " 2 #{ callers[2] }"
146
+
147
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] "
148
+ if @tc_db then
149
+ begin
150
+ # puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] NO SELF SYNC?? [#{ @currently_syncing }] --> db: #{ @tc_db.class.name }"
151
+ self.sync unless @currently_syncing
152
+ rescue => e
153
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] OH CRAP!!! #{ e }"
154
+ ensure
155
+ note_errors("TC[[#{ @filename }]]:: close error: %s\n") do
156
+ @tc_db.close
157
+ end
158
+ @tc_db = nil
159
+ end
160
+ end
161
+ end
162
+
163
+ def TokyoCabinetPersister.kind
164
+ :tokyo_cabinet
165
+ end
166
+
167
+ def kind
168
+ TokyoCabinetPersister.kind
169
+ end
170
+
171
+ def query_implemented
172
+ true
173
+ end
174
+
175
+ def query(hint=false)
176
+ open_tc_db
177
+ query = TableQuery.new(@tc_db)
178
+
179
+ yield query
180
+
181
+ result_keys = nil
182
+ the_hint = nil
183
+ if hint then
184
+ result_keys, the_hint = query.search(true)
185
+ else
186
+ result_keys = query.search
187
+ end
188
+ results = result_keys.collect { | key | @tc_db[ key ] }
189
+
190
+ class_cache = {}
191
+ results.each do | result |
192
+ next unless result
193
+
194
+ class_name = result['class']
195
+ result_class = class_cache[class_name]
196
+ unless result_class then
197
+ class_name.split("::").each do | chunk |
198
+ if result_class then
199
+ result_class = result_class.const_get( chunk )
200
+ else
201
+ result_class = Kernel.const_get( chunk )
202
+ end
203
+ end
204
+
205
+ class_cache[class_name] = result_class
206
+ end
207
+
208
+ result['xampl'] = self.lookup(result_class, result['pid'])
209
+ end
210
+
211
+ if hint then
212
+ return results.uniq, the_hint
213
+ else
214
+ return results.uniq
215
+ end
216
+ end
217
+
218
+ def find_xampl(hint=false)
219
+ open_tc_db
220
+ query = TableQuery.new(@tc_db)
221
+
222
+ yield query
223
+
224
+ class_cache = {}
225
+
226
+ result_keys = nil
227
+ the_hint = nil
228
+ if hint then
229
+ result_keys, the_hint = query.search(true)
230
+ else
231
+ result_keys = query.search
232
+ end
233
+
234
+ results = result_keys.collect do | key |
235
+ result = @tc_db[ key ]
236
+ next unless result
237
+
238
+ class_name = result['class']
239
+ result_class = class_cache[class_name]
240
+ unless result_class then
241
+ class_name.split("::").each do | chunk |
242
+ if result_class then
243
+ result_class = result_class.const_get( chunk )
244
+ else
245
+ result_class = Kernel.const_get( chunk )
246
+ end
247
+ end
248
+
249
+ class_cache[class_name] = result_class
250
+ end
251
+
252
+ self.lookup(result_class, result['pid'])
253
+ end
254
+
255
+ if hint then
256
+ return results.uniq, the_hint
257
+ else
258
+ return results.uniq
259
+ end
260
+ end
261
+
262
+ def find_pids(hint=false)
263
+ open_tc_db
264
+ query = TableQuery.new(@tc_db)
265
+
266
+ yield query
267
+
268
+ result_keys = nil
269
+ the_hint = nil
270
+ if hint then
271
+ result_keys, the_hint = query.search(true)
272
+ else
273
+ result_keys = query.search
274
+ end
275
+
276
+ results = result_keys.collect do |key|
277
+ meta = @tc_db[ key ]
278
+ meta['xampl-place'] || meta['place']
279
+ end
280
+
281
+ if hint then
282
+ return results.uniq, the_hint
283
+ else
284
+ return results.uniq
285
+ end
286
+ end
287
+
288
+ def find_meta(hint=false)
289
+ open_tc_db
290
+ query = TableQuery.new(@tc_db)
291
+
292
+ yield query
293
+
294
+ result_keys = nil
295
+ the_hint = nil
296
+ if hint then
297
+ result_keys, the_hint = query.search(true)
298
+ else
299
+ result_keys = query.search
300
+ end
301
+
302
+ results = result_keys.collect { | key | @tc_db[ key ] }
303
+
304
+ if hint then
305
+ return results, the_hint
306
+ else
307
+ return results
308
+ end
309
+ end
310
+
311
+ def find_mentions_of(xampl)
312
+ open_tc_db
313
+
314
+ place = File.join(xampl.class.name.split("::"), xampl.get_the_index)
315
+
316
+ query = TableQuery.new(@tc_db)
317
+ query.add_condition('xampl-to', :equals, place)
318
+ result_keys = query.search
319
+
320
+ class_cache = {}
321
+ results = result_keys.collect do | key |
322
+ result = @tc_db[ key ]
323
+ next unless result
324
+
325
+ mentioner = result['xampl-from']
326
+ class_name = result['mentioned_class']
327
+ result_class = class_cache[class_name]
328
+ unless result_class then
329
+ class_name.split("::").each do | chunk |
330
+ if result_class then
331
+ result_class = result_class.const_get( chunk )
332
+ else
333
+ result_class = Kernel.const_get( chunk )
334
+ end
335
+ end
336
+
337
+ class_cache[class_name] = result_class
338
+ end
339
+
340
+ self.lookup(result_class, result['pid'])
341
+ end
342
+ return results.uniq
343
+ end
344
+
345
+ def start_sync_write
346
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] #{ @filename }"
347
+ # callers = caller(0)
348
+ # puts " 0 #{ callers[0] }"
349
+ # puts " 1 #{ callers[1] }"
350
+ # puts " 2 #{ callers[2] }"
351
+
352
+ @currently_syncing = true
353
+ open_tc_db
354
+ end
355
+
356
+ def done_sync_write
357
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] #{ @filename }"
358
+ begin
359
+ close
360
+ ensure
361
+ @currently_syncing = false
362
+ end
363
+ end
364
+
365
+
366
+ def do_sync_write
367
+ begin
368
+ # puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] **************************"
369
+ # callers = caller(0)
370
+ # puts " 0 #{ callers[0] }"
371
+ # puts " 1 #{ callers[1] }"
372
+ # puts " 2 #{ callers[2] }"
373
+
374
+ # open_tc_db
375
+ @time_stamp = Time.now.to_f.to_s
376
+
377
+ note_errors("TC[[#{ @filename }]]:: tranbegin error: %s\n") do
378
+ @tc_db.tranbegin
379
+ end
380
+
381
+ @changed.each do |xampl, ignore|
382
+ write(xampl)
383
+ end
384
+ rescue => e
385
+ msg = "no TC.abort attempted"
386
+ msg = note_errors("TC[[#{ @filename }]]:: tranabort error: %s\n") do
387
+ @tc_db.tranabort
388
+ end
389
+ puts "------------------------------------------------------------------------"
390
+ puts "TokyoCabinetPersister Error:: #{ msg }/#{ e }"
391
+ puts e.backtrace.join("\n")
392
+ puts "------------------------------------------------------------------------"
393
+ raise "TokyoCabinetPersister Error:: #{ msg }/#{ e }"
394
+ else
395
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] COMMIT"
396
+ note_errors("TC[[#{ @filename }]]:: trancommit error: %s\n") do
397
+ @tc_db.trancommit
398
+ end
399
+ ensure
400
+ # puts " num records: #{ @tc_db.rnum() }"
401
+ # puts "#{ __FILE__ }:#{ __LINE__ } keys..."
402
+ # @tc_db.keys.each do | key |
403
+ # meta = @tc_db[key]
404
+ # meta['xampl'] = (meta['xampl'] || "no rep")[0..25]
405
+ # puts " key: [#{ key }] -- #{ meta.inspect }"
406
+ # end
407
+
408
+ # close
409
+ end
410
+ end
411
+
412
+ def how_indexed(xampl)
413
+ raise XamplException.new(:no_index_so_no_persist) unless xampl.get_the_index
414
+ place = File.join(xampl.class.name.split("::"), xampl.get_the_index)
415
+
416
+ open_tc_db
417
+
418
+ result_keys = Set.new
419
+
420
+ query = TableQuery.new(@tc_db)
421
+ query.add_condition('xampl-place', :equals, place)
422
+ search_results = query.search
423
+ result_keys.merge( search_results)
424
+
425
+ primary = @tc_db[ place ]
426
+ if primary then
427
+ primary.delete('xampl')
428
+ end
429
+
430
+ results = primary ? [ primary ] : []
431
+ result_keys.each do | key |
432
+ result = @tc_db[ key ]
433
+ next unless result
434
+
435
+ result.delete('xampl')
436
+
437
+ results << result
438
+ end
439
+
440
+ results
441
+ end
442
+
443
+ def write(xampl)
444
+ raise XamplException.new(:no_index_so_no_persist) unless xampl.get_the_index
445
+
446
+ place = File.join(xampl.class.name.split("::"), xampl.get_the_index)
447
+ mentions = Set.new
448
+ data = represent(xampl, mentions)
449
+
450
+ #get rid of any supplimentary indexes associated with this xampl object
451
+ query = TableQuery.new(@tc_db)
452
+ query.add_condition('xampl-from', :equals, place)
453
+ note_errors("TC[[#{ @filename }]]:: failed to remove from mentions, error: %s\n") do
454
+ query.searchout
455
+ end
456
+
457
+ query = TableQuery.new(@tc_db)
458
+ query.add_condition('xampl-place', :equals, place)
459
+ note_errors("TC[[#{ @filename }]]:: failed to remove from mentions, error: %s\n") do
460
+ query.searchout
461
+ end
462
+
463
+ mentions.each do | mention |
464
+ mention_place = File.join(mention.class.name.split("::"), mention.get_the_index)
465
+ #TODO -- will repeadedly changing a persisted xampl object fragment the TC db?
466
+
467
+ pk = @tc_db.genuid
468
+ mention_hash = {
469
+ 'xampl-from' => place,
470
+ 'mentioned_class' => xampl.class.name,
471
+ 'pid' => xampl.get_the_index,
472
+ 'xampl-to' => mention_place
473
+ }
474
+
475
+ note_errors("TC[[#{ @filename }]]:: write error: %s\n") do
476
+ @tc_db.put(pk, mention_hash)
477
+ end
478
+ end
479
+
480
+ xampl_hash = {
481
+ 'class' => xampl.class.name,
482
+ 'pid' => xampl.get_the_index,
483
+ 'time-stamp' => @time_stamp,
484
+ 'xampl' => data
485
+ }
486
+
487
+ primary_description, secondary_descriptions = xampl.describe_yourself
488
+ if primary_description then
489
+ xampl_hash = primary_description.merge(xampl_hash)
490
+ end
491
+
492
+ note_errors("TC[[#{ @filename }]]:: write error: %s\n") do
493
+ @tc_db.put(place, xampl_hash)
494
+ end
495
+
496
+ #TODO -- smarter regarding when to delete (e.g. mentions)
497
+ if xampl.should_schedule_delete? and xampl.scheduled_for_deletion_at then
498
+ secondary_descriptions = [] unless secondary_descriptions
499
+ secondary_descriptions << { 'scheduled-delete-at' => xampl.scheduled_for_deletion_at }
500
+ elsif xampl.scheduled_for_deletion_at then
501
+ puts "#{ __FILE__ }:#{ __LINE__ } HOW TO DO THIS without violating xampl's change rules????? "
502
+ #xampl.scheduled_for_deletion_at = nil
503
+ end
504
+
505
+ if secondary_descriptions then
506
+ xampl_hash = {
507
+ 'class' => xampl.class.name,
508
+ 'pid' => xampl.get_the_index,
509
+ 'xampl-place' => place
510
+ }
511
+
512
+ secondary_descriptions.each do | secondary_description |
513
+ description = secondary_description.merge(xampl_hash)
514
+
515
+ note_errors("TC[[#{ @filename }]]:: write error: %s\n") do
516
+ pk = @tc_db.genuid
517
+ @tc_db.put(pk, description)
518
+ end
519
+ end
520
+ end
521
+
522
+ @write_count = @write_count + 1
523
+ xampl.changes_accepted
524
+ return true
525
+ end
526
+
527
+ $TC_COUNT = 0
528
+ $FS_COUNT = 0
529
+ $NF_COUNT = 0
530
+
531
+ def read_representation(klass, pid)
532
+ #TODO -- is this being called too often, e.g. by new_xxx???
533
+ # puts "#{File.basename(__FILE__)}:#{__LINE__} READ #{ klass }/#{ pid }"
534
+ # caller(0).each { | trace | puts " #{trace}"}
535
+
536
+ representation = nil
537
+ require_close = false
538
+ begin
539
+ unless @tc_db then
540
+ require_close = true
541
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] OPEN TC DB"
542
+ open_tc_db
543
+ end
544
+ place = File.join(klass.name.split("::"), pid)
545
+
546
+ meta = @tc_db[place]
547
+ representation = meta['xampl'] if meta
548
+
549
+ # puts "#{File.basename(__FILE__)}:#{__LINE__} TC: #{ klass }/#{ pid }" if representation
550
+ $TC_COUNT += 1 if representation
551
+
552
+ # puts "read: #{ place }, size: #{ representation.size }"
553
+ # puts representation[0..100]
554
+
555
+ unless representation then
556
+ # try the filesystem if it is not in the TC repository
557
+ place = File.join(@root_dir, klass.name.split("::"), pid)
558
+ representation = File.read(place) if File.exist?(place)
559
+ $FS_COUNT += 1 if representation
560
+ # puts "#{File.basename(__FILE__)}:#{__LINE__} FS: #{ klass }/#{ pid } (FS: #{ $FS_COUNT}, TC: #{ $TC_COUNT }, NF: #{ $NF_COUNT }" if representation
561
+ end
562
+ # puts "#{File.basename(__FILE__)}:#{__LINE__} ??: #{ klass }/#{ pid }" unless representation
563
+ $NF_COUNT += 1
564
+
565
+ return representation
566
+ ensure
567
+ puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] CLOSE TC DB" if require_close
568
+ # close if require_close
569
+ end
570
+ return representation
571
+ end
572
+ end
573
+
574
+ #
575
+ # Derrived from rufus-tyrant, but simplified significantly, and using the
576
+ # TokyoCabinet named constants rather than numbers
577
+ #
578
+
579
+ class TableQuery
580
+ include TokyoCabinet
581
+
582
+ OPERATORS = {
583
+ # strings...
584
+
585
+ :streq => TDBQRY::QCSTREQ, # string equality
586
+ :eq => TDBQRY::QCSTREQ,
587
+ :eql => TDBQRY::QCSTREQ,
588
+ :equals => TDBQRY::QCSTREQ,
589
+
590
+ :strinc => TDBQRY::QCSTRINC, # string include
591
+ :inc => TDBQRY::QCSTRINC, # string include
592
+ :includes => TDBQRY::QCSTRINC, # string include
593
+
594
+ :strbw => TDBQRY::QCSTRBW, # string begins with
595
+ :bw => TDBQRY::QCSTRBW,
596
+ :starts_with => TDBQRY::QCSTRBW,
597
+ :strew => TDBQRY::QCSTREW, # string ends with
598
+ :ew => TDBQRY::QCSTREW,
599
+ :ends_with => TDBQRY::QCSTREW,
600
+
601
+ :strand => TDBQRY::QCSTRAND, # string which include all the tokens in the given exp
602
+ :and => TDBQRY::QCSTRAND,
603
+
604
+ :stror => TDBQRY::QCSTROR, # string which include at least one of the tokens
605
+ :or => TDBQRY::QCSTROR,
606
+
607
+ :stroreq => TDBQRY::QCSTROREQ, # string which is equal to at least one token
608
+
609
+ :strorrx => TDBQRY::QCSTRRX, # string which matches the given regex
610
+ :regex => TDBQRY::QCSTRRX,
611
+ :matches => TDBQRY::QCSTRRX,
612
+
613
+ # numbers...
614
+
615
+ :numeq => TDBQRY::QCNUMEQ, # equal
616
+ :numequals => TDBQRY::QCNUMEQ,
617
+ :numgt => TDBQRY::QCNUMGT, # greater than
618
+ :gt => TDBQRY::QCNUMGT,
619
+ :numge => TDBQRY::QCNUMGE, # greater or equal
620
+ :ge => TDBQRY::QCNUMGE,
621
+ :gte => TDBQRY::QCNUMGE,
622
+ :numlt => TDBQRY::QCNUMLT, # greater or equal
623
+ :lt => TDBQRY::QCNUMLT,
624
+ :numle => TDBQRY::QCNUMLE, # greater or equal
625
+ :le => TDBQRY::QCNUMLE,
626
+ :lte => TDBQRY::QCNUMLE,
627
+ :numbt => TDBQRY::QCNUMBT, # a number between two tokens in the given exp
628
+ :bt => TDBQRY::QCNUMBT,
629
+ :between => TDBQRY::QCNUMBT,
630
+
631
+ :numoreq => TDBQRY::QCNUMOREQ # number which is equal to at least one token
632
+ }
633
+
634
+ TDBQCNEGATE = TDBQRY::QCNEGATE
635
+ TDBQCNOIDX = TDBQRY::QCNOIDX
636
+
637
+ DIRECTIONS = {
638
+ :strasc => TDBQRY::QOSTRASC,
639
+ :strdesc => TDBQRY::QOSTRDESC,
640
+ :asc => TDBQRY::QOSTRASC,
641
+ :desc => TDBQRY::QOSTRDESC,
642
+ :numasc => TDBQRY::QONUMASC,
643
+ :numdesc => TDBQRY::QONUMDESC
644
+ }
645
+
646
+ #
647
+ # Creates a query for a given Rufus::Tokyo::Table
648
+ #
649
+ # Queries are usually created via the #query (#prepare_query #do_query)
650
+ # of the Table instance.
651
+ #
652
+ # Methods of interest here are :
653
+ #
654
+ # * #add (or #add_condition)
655
+ # * #order_by
656
+ # * #limit
657
+ #
658
+ # also
659
+ #
660
+ # * #pk_only
661
+ # * #no_pk
662
+ #
663
+
664
+ def initialize (table)
665
+ @query = TDBQRY::new(table)
666
+ @text_form = []
667
+ @opts = {}
668
+ end
669
+
670
+ #
671
+ # Performs the search
672
+ #
673
+
674
+ def search(hint=false)
675
+ r = @query.search
676
+ if hint then
677
+ return r, @query.hint
678
+ else
679
+ return r
680
+ end
681
+ end
682
+
683
+ #
684
+ # Performs the search and removes whatever's found
685
+ #
686
+
687
+ def searchout
688
+ r = @query.searchout
689
+ end
690
+
691
+ # limits the search
692
+
693
+ def setlimit(max=nil, skip=nil)
694
+ @query.setlimit(max, skip)
695
+ end
696
+
697
+ #
698
+ # Adds a condition
699
+ #
700
+ # table.query { |q|
701
+ # q.add 'name', :equals, 'Oppenheimer'
702
+ # q.add 'age', :numgt, 35
703
+ # }
704
+ #
705
+ # Understood 'operators' :
706
+ #
707
+ # :streq # string equality
708
+ # :eq
709
+ # :eql
710
+ # :equals
711
+ #
712
+ # :strinc # string include
713
+ # :inc # string include
714
+ # :includes # string include
715
+ #
716
+ # :strbw # string begins with
717
+ # :bw
718
+ # :starts_with
719
+ # :strew # string ends with
720
+ # :ew
721
+ # :ends_with
722
+ #
723
+ # :strand # string which include all the tokens in the given exp
724
+ # :and
725
+ #
726
+ # :stror # string which include at least one of the tokens
727
+ # :or
728
+ #
729
+ # :stroreq # string which is equal to at least one token
730
+ #
731
+ # :strorrx # string which matches the given regex
732
+ # :regex
733
+ # :matches
734
+ #
735
+ # # numbers...
736
+ #
737
+ # :numeq # equal
738
+ # :numequals
739
+ # :numgt # greater than
740
+ # :gt
741
+ # :numge # greater or equal
742
+ # :ge
743
+ # :gte
744
+ # :numlt # greater or equal
745
+ # :lt
746
+ # :numle # greater or equal
747
+ # :le
748
+ # :lte
749
+ # :numbt # a number between two tokens in the given exp
750
+ # :bt
751
+ # :between
752
+ #
753
+ # :numoreq # number which is equal to at least one token
754
+ #
755
+
756
+ def add (colname, operator, val, affirmative=true, no_index=false)
757
+ op = operator.is_a?(Fixnum) ? operator : OPERATORS[operator]
758
+ op = op | TDBQRY::QCNEGATE unless affirmative
759
+ op = op | TDBQRY::QCNOIDX if no_index
760
+
761
+ @text_form << "operator: #{ operator }#{ affirmative ? '' : ' NEGATED'}#{ no_index ? ' NO INDEX' : ''} -- col: '#{ colname }', val: '#{ val }'"
762
+
763
+ @query.addcond(colname, op, val)
764
+ end
765
+
766
+ alias :add_condition :add
767
+
768
+ #
769
+ # Sets the max number of records to return for this query.
770
+ #
771
+ # (If you're using TC >= 1.4.10 the optional 'offset' (skip) parameter
772
+ # is accepted)
773
+ #
774
+
775
+ def limit (i, offset=-1)
776
+ @query.setlimit(i, offset)
777
+ end
778
+
779
+ #
780
+ # Sets the sort order for the result of the query
781
+ #
782
+ # The 'direction' may be :
783
+ #
784
+ # :strasc # string ascending
785
+ # :strdesc
786
+ # :asc # string ascending
787
+ # :desc
788
+ # :numasc # number ascending
789
+ # :numdesc
790
+ #
791
+
792
+ def order_by (colname, direction=:strasc)
793
+ @query.setorder(colname, DIRECTIONS[direction])
794
+ end
795
+
796
+ def inspect
797
+ "TableQuery:\n#{ @text_form.join("\n") }"
798
+ end
799
+
800
+ def to_s
801
+ inspect
802
+ end
803
+ end
804
+
805
+ Xampl.register_persister_kind(TokyoCabinetPersister)
806
+ end
807
+