Class: Xampl::TokyoCabinetPersister

Inherits:
AbstractCachingPersister show all
Includes:
TokyoCabinet
Defined in:
lib/xamplr/persisters/tokyo-cabinet.rb

Overview

require ‘ruby-prof’

Instance Attribute Summary

Attributes inherited from Persister

#automatic, #block_changes, #cache_hits, #expunged, #format, #last_write_count, #name, #read_count, #rolled_back, #syncing, #total_cache_hits, #total_read_count, #total_rollback_count, #total_sync_count, #total_write_count, #write_count

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from AbstractCachingPersister

#cache, #clear_cache, #dump, #fresh_cache, #read, #read_from_cache, #rollback_cleanup, #sync_done, #uncache, #write_to_cache

Methods inherited from Persister

#busy, #cache, #clear_cache, #count_changed, #find_known, #has_changed, #has_not_changed, #introduce, #is_busy, #lazy_load, #lookup, #print_stats, #put_changed, #read, #realise, replace, #represent, #rollback, #rollback_cleanup, #shutdown, #sync, #sync_done, #uncache

Constructor Details

#initialize(name = nil, format = nil, root = File.join(".", "repo")) ⇒ TokyoCabinetPersister

Returns a new instance of TokyoCabinetPersister.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 50

def initialize(name=nil, format=nil, root=File.join(".", "repo"))
  super(root, name, format)

  @files_dir = "#{ @root_dir }/files"
#      FileUtils.mkdir_p(@root_dir) unless File.exist?(@root_dir)
  FileUtils.mkdir_p(@files_dir) unless File.exist?(@files_dir)
  @filename = "#{@root_dir}/repo.tct"
  @tc_db = nil
#      puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] file: #{ @filename }, db: #{ @tc_db.class.name }"

  open_tc_db()

#      note_errors("TC[[#{ @filename }]]:: optimisation error: %s\n") do
#        @tc_db.optimize(-1, -1, -1, TDB::TDEFLATE)
#      end
#      note_errors("TC[[#{ @filename }]]:: close error: %s\n") do
#        @tc_db.close
#      end

  begin
    note_errors("TC[[#{ @filename }]]:: close error in initialize: %s\n") do
      @tc_db.close
    end
    @tc_db = nil
  rescue => e
    #TODO -- why do this???
    puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] OH CRAP!!! #{ e }"
  end
end

Class Method Details

.add_lexical_indexs(indexes) ⇒ Object



42
43
44
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 42

def TokyoCabinetPersister.add_lexical_indexs(indexes)
  $lexical_indexes.merge(indexes)
end

.add_numeric_indexs(indexes) ⇒ Object



46
47
48
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 46

def TokyoCabinetPersister.add_numeric_indexs(indexes)
  $numeric_indexes.merge(indexes)
end

.kindObject



161
162
163
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 161

def TokyoCabinetPersister.kind
  :tokyo_cabinet
end

Instance Method Details

#closeObject



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 138

def close
#      puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] CLOSE #{ @filename }"
#      callers = caller(0)
#      puts "   0 #{ callers[0] }"
#      puts "   1 #{ callers[1] }"
#      puts "   2 #{ callers[2] }"

  if @tc_db then
    begin
#          puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] NO SELF SYNC?? [#{ @currently_syncing }] --> db: #{ @tc_db.class.name }"
      self.sync unless @currently_syncing
    rescue => e
      #TODO -- why do this
      puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] OH CRAP!!! #{ e }"
    ensure
      note_errors("TC[[#{ @filename }]]:: close error: %s\n") do
        @tc_db.close
      end
      @tc_db = nil
    end
  end
end

#do_sync_writeObject



366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 366

def do_sync_write
  begin
#@start = Time.now
#@last = Time.now
#puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] time: #{ Time.now - @start }/#{ Time.now - @last }"; @last = Time.now
#        puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] **************************"
#        callers = caller(0)
#        puts "   0 #{ callers[0] }"
#        puts "   1 #{ callers[1] }"
#        puts "   2 #{ callers[2] }"

#        open_tc_db
    @time_stamp = Time.now.to_f.to_s

    note_errors("TC[[#{ @filename }]]:: tranbegin error: %s\n") do
      @tc_db.tranbegin
    end
#puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] time: #{ Time.now - @start }/#{ Time.now - @last }"; @last = Time.now

    @changed.each do |xampl, ignore|
      write(xampl)
#puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] time: #{ Time.now - @start }/#{ Time.now - @last }"; @last = Time.now
    end
  rescue => e
    msg = "no TC.abort attempted"
    msg = note_errors("TC[[#{ @filename }]]:: tranabort error: %s\n") do
      @tc_db.tranabort
    end
    #puts "------------------------------------------------------------------------"
    #puts "TokyoCabinetPersister Error:: #{ msg }/#{ e }"
    #puts e.backtrace.join("\n")
    #puts "------------------------------------------------------------------------"
    #raise "TokyoCabinetPersister Error:: #{ msg }/#{ e }"
    raise RuntimeError, "TokyoCabinetPersister Error:: #{ msg }/#{ e }", e.backtrace
  else
#        puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] COMMIT"
#puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] time: #{ Time.now - @start }/#{ Time.now - @last }"; @last = Time.now
    note_errors("TC[[#{ @filename }]]:: trancommit error: %s\n") do
      @tc_db.trancommit
    end
#puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] time: #{ Time.now - @start }/#{ Time.now - @last }"; @last = Time.now
  ensure
#      puts "               num records: #{ @tc_db.rnum() }"
    #      puts "#{ __FILE__ }:#{ __LINE__ } keys..."
    #      @tc_db.keys.each do | key |
    #        meta = @tc_db[key]
    #        meta['xampl'] = (meta['xampl'] || "no rep")[0..25]
    #        puts "         key: [#{ key }] -- #{ meta.inspect }"
    #      end

#        close
#puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] time: #{ Time.now - @start }/#{ Time.now - @last }"; @last = Time.now
  end
end

#done_sync_writeObject



354
355
356
357
358
359
360
361
362
363
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 354

def done_sync_write
  begin
    note_errors("TC[[#{ @filename }]]:: sync error in done_sync_write: %s\n") do
      @tc_db.sync
    end
#        close
  ensure
    @currently_syncing = false
  end
end

#expunge(xampl) ⇒ Object



459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 459

def expunge(xampl)
  #NOTE -- this *must* be in a transaction
  #NOTE -- the expunge operation is in two steps and is completed in write

  mentions = Xampl.find_mentions_of(xampl)
  mentions.each do | has_a_xampl |
#        xampl.remove_from(has_a_xampl)
    remove_all_mention(has_a_xampl, xampl)
  end
  xampl.changed
  self.expunged << xampl

  false
end

#find_mentions_of(xampl) ⇒ Object



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 309

def find_mentions_of(xampl)
  open_tc_db

  place = File.join(xampl.class.name.split("::"), xampl.get_the_index)

  query = TableQuery.new(@tc_db)
  query.add_condition('xampl-to', :equals, place)
  result_keys = query.search

  class_cache = {}
  results = result_keys.collect do | key |
    result = @tc_db[ key ]
    next unless result

    mentioner = result['xampl-from']
    class_name = result['mentioned_class']
    result_class = class_cache[class_name]
    unless result_class then
      class_name.split("::").each do | chunk |
        if result_class then
          result_class = result_class.const_get( chunk )
        else
          result_class = Kernel.const_get( chunk )
        end
      end

      class_cache[class_name] = result_class
    end

    self.lookup(result_class, result['pid'])
  end
  return results.uniq
end

#find_meta(hint = false) {|query| ... } ⇒ Object

Yields:



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 286

def find_meta(hint=false)
  open_tc_db
  query = TableQuery.new(@tc_db)

  yield query

  result_keys = nil
  the_hint = nil
  if hint then
    result_keys, the_hint = query.search(true)
  else
    result_keys = query.search
  end

  results = result_keys.collect { | key | @tc_db[ key ] }

  if hint then
    return results, the_hint
  else
    return results
  end
end

#find_pids(hint = false) {|query| ... } ⇒ Object

Yields:



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 260

def find_pids(hint=false)
  open_tc_db
  query = TableQuery.new(@tc_db)

  yield query

  result_keys = nil
  the_hint = nil
  if hint then
    result_keys, the_hint = query.search(true)
  else
    result_keys = query.search
  end

  results = result_keys.collect do |key|
    meta = @tc_db[ key ]
    meta['xampl-place'] || meta['place']
  end

  if hint then
    return results.uniq, the_hint
  else
    return results.uniq
  end
end

#find_xampl(hint = false) {|query| ... } ⇒ Object

Yields:



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 216

def find_xampl(hint=false)
  open_tc_db
  query = TableQuery.new(@tc_db)

  yield query

  class_cache = {}

  result_keys = nil
  the_hint = nil
  if hint then
    result_keys, the_hint = query.search(true)
  else
    result_keys = query.search
  end

  results = result_keys.collect do | key |
    result = @tc_db[ key ]
    next unless result

    class_name = result['class']
    result_class = class_cache[class_name]
    unless result_class then
      class_name.split("::").each do | chunk |
        if result_class then
          result_class = result_class.const_get( chunk )
        else
          result_class = Kernel.const_get( chunk )
        end
      end

      class_cache[class_name] = result_class
    end

    self.lookup(result_class, result['pid'])
  end

  if hint then
    return results.uniq, the_hint
  else
    return results.uniq
  end
end

#how_indexed(xampl) ⇒ Object

Raises:



421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 421

def how_indexed(xampl)
  raise XamplException.new(:no_index_so_no_persist) unless xampl.get_the_index
  place = File.join(xampl.class.name.split("::"), xampl.get_the_index)

  open_tc_db

  result_keys = Set.new

  query = TableQuery.new(@tc_db)
  query.add_condition('xampl-place', :equals, place)
  search_results = query.search
  result_keys.merge( search_results)

  primary = @tc_db[ place ]
  if primary then
    primary.delete('xampl')
  end

  results = primary ? [ primary ] : []
  result_keys.each do | key |
    result = @tc_db[ key ]
    next unless result

    result.delete('xampl')

    results << result
  end

  results
end

#kindObject



165
166
167
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 165

def kind
  TokyoCabinetPersister.kind
end

#note_errors(msg = "TokyoCabinet Error:: %s\n") ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 13

def note_errors(msg="TokyoCabinet Error:: %s\n")
#      puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] START"
#      puts "--------------------------------------------------"
#      caller(0).each { |trace| puts " #{trace}" }
#      puts "--------------------------------------------------\n\n"
  exception = nil
  begin
    result = yield
  rescue => e
    exception = e
  end

  rmsg = nil
  unless result then
    rmsg = sprintf(msg, @tc_db.errmsg(@tc_db.ecode))
    STDERR.puts "NOTE: TokyoCabinet Error!"
    STDERR.puts(rmsg)
    STDERR.puts "---------"
    STDERR.puts caller(0)
    STDERR.puts "---------"
  end
  raise exception if exception
  return rmsg
end

#open_tc_dbObject



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 80

def open_tc_db
#      if @tcdb then
#        puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] ALREADY OPEN #{ @filename }"
#      else
#        puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] OPEN #{ @filename }"
##        callers = caller(0)
##        puts "   0 #{ callers[0] }"
##        puts "   1 #{ callers[1] }"
##        puts "   2 #{ callers[2] }"
#        #puts "#{File.basename(__FILE__)}:#{__LINE__} callers..."
#        #caller(0).each { | trace | puts "   #{trace}"}
#      end

  return if @tc_db # if there is a tc_db then it is already open

  @tc_db = TDB.new
  note_errors("TC[[#{ @filename }]]:: tuning error: %s\n") do
    @tc_db.tune(-1, -1, -1, TDB::TDEFLATE)
  end

  note_errors("TC[[#{ @filename }]]:: open [#{ @filename }] error: %s\n") do
    if Xampl.raw_persister_options[:otsync] then
      @tc_db.open(@filename, TDB::OWRITER | TDB::OCREAT | TDB::OLCKNB | TDB::OTSYNC ) #TDB::OTSYNC slows it down by almost 50 times
    else
      @tc_db.open(@filename, TDB::OWRITER | TDB::OCREAT | TDB::OLCKNB)
    end
  end

  # Don't care if there are errors (in fact, if the index exists a failure is the expected thing)

  $lexical_indexes.each do | index_name |
    r = @tc_db.setindex(index_name, TDB::ITLEXICAL | TDB::ITKEEP)
  end
  $numeric_indexes.each do | index_name |
    @tc_db.setindex(index_name, TDB::ITDECIMAL | TDB::ITKEEP)
  end

  optimise
end

#optimise(opts = {}) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 120

def optimise(opts={})
  return unless @tc_db

  if opts[:indexes_only] then
    # Don't care if there are errors (in fact, if the index exists a failure is the expected thing)
    $lexical_indexes.each do | index_name |
      @tc_db.setindex(index_name, 9998)
    end
    $numeric_indexes.each do | index_name |
      @tc_db.setindex(index_name, 9998)
    end
  else
    note_errors("TC[[#{ @filename }]]:: optimisation error: %s\n") do
      @tc_db.optimize(-1, -1, -1, 0xff)
    end
  end
end

#query(hint = false) {|query| ... } ⇒ Object

Yields:



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 173

def query(hint=false)
  open_tc_db
  query = TableQuery.new(@tc_db)

  yield query

  result_keys = nil
  the_hint = nil
  if hint then
    result_keys, the_hint = query.search(true)
  else
    result_keys = query.search
  end
  results = result_keys.collect { | key | @tc_db[ key ] }

  class_cache = {}
  results.each do | result |
    next unless result

    class_name = result['class']
    result_class = class_cache[class_name]
    unless result_class then
      class_name.split("::").each do | chunk |
        if result_class then
          result_class = result_class.const_get( chunk )
        else
          result_class = Kernel.const_get( chunk )
        end
      end

      class_cache[class_name] = result_class
    end

    result['xampl'] = self.lookup(result_class, result['pid'])
  end

  if hint then
    return results.uniq, the_hint
  else
    return results.uniq
  end
end

#query_implementedObject



169
170
171
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 169

def query_implemented
  true
end

#read_representation(klass, pid) ⇒ Object



599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 599

def read_representation(klass, pid)
  #TODO -- is this being called too often, e.g. by new_xxx???
  #      puts "#{File.basename(__FILE__)}:#{__LINE__} READ #{ klass }/#{ pid }"
  #      caller(0).each { | trace | puts "  #{trace}"}

  representation = nil

  unless @tc_db then
#        puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] READ REP"
    open_tc_db
  end
  place = File.join(klass.name.split("::"), pid)

  meta = @tc_db[place]
  representation = meta['xampl'] if meta

  #      puts "#{File.basename(__FILE__)}:#{__LINE__} TC: #{ klass }/#{ pid }" if representation
  $TC_COUNT += 1 if representation

  # puts "read: #{ place }, size: #{ representation.size }"
  # puts representation[0..100]

  unless representation then
    # try the filesystem if it is not in the TC repository
    place = File.join(@root_dir, klass.name.split("::"), pid)
    representation = File.read(place) if File.exist?(place)
    $FS_COUNT += 1 if representation
#        puts "#{File.basename(__FILE__)}:#{__LINE__} FS: #{ klass }/#{ pid } (FS: #{ $FS_COUNT}, TC: #{ $TC_COUNT }, NF: #{ $NF_COUNT }" if representation
  end
#      puts "#{File.basename(__FILE__)}:#{__LINE__} ??: #{ klass }/#{ pid }" unless representation
  $NF_COUNT += 1

  return representation
end

#remove_all_mention(root, xampl) ⇒ Object



452
453
454
455
456
457
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 452

def remove_all_mention(root, xampl)
  xampl.remove_from(root)
  root.children.each do | child |
    remove_all_mention(child, xampl)
  end
end

#start_sync_writeObject



343
344
345
346
347
348
349
350
351
352
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 343

def start_sync_write
#      puts "#{ __FILE__ }:#{ __LINE__ } [#{__method__}] #{ @filename }"
#      callers = caller(0)
#      puts "   0 #{ callers[0] }"
#      puts "   1 #{ callers[1] }"
#      puts "   2 #{ callers[2] }"

  @currently_syncing = true
  open_tc_db
end

#write(xampl) ⇒ Object

Raises:



474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
# File 'lib/xamplr/persisters/tokyo-cabinet.rb', line 474

def write(xampl)
  raise XamplException.new(:no_index_so_no_persist) unless xampl.get_the_index

  expunging = self.expunged.include?(xampl)
  self.expunged.delete(xampl) if expunging
#      if expunging
#        puts "#{File.basename(__FILE__)}:#{__LINE__} [#{ __method__ }] EXPUNGING #{ xampl }/#{ expunging }"
#      end

  place_dir = xampl.class.name.split("::")
  place = File.join( place_dir, xampl.get_the_index)
  place_dir = File.join( @files_dir, place_dir )
  mentions = Set.new
  xampl_in_xml = represent(xampl, mentions)

  #get rid of any supplimentary indexes associated with this xampl object
  # TODO -- This can be slow
  query = TableQuery.new(@tc_db)
  query.add_condition('xampl-from', :equals, place)
  note_errors("TC[[#{ @filename }]]:: failed to remove from mentions, error: %s\n") do
    query.searchout
  end

  query = TableQuery.new(@tc_db)
  query.add_condition('xampl-place', :equals, place)
  note_errors("TC[[#{ @filename }]]:: failed to remove from mentions, error: %s\n") do
    query.searchout
  end

  if expunging then
    file_place = "#{ @files_dir }/#{ place }"
    File.delete(file_place) if File.exists?(file_place)
    note_errors("TC[[#{ place }]]:: write error: %s\n") do
      @tc_db.out(place)
    end

    uncache(xampl)
  else
    if Xampl.raw_persister_options[:mentions] then
      # TODO -- This can be slow
      mentions.each do | mention |
        mention_place = File.join(mention.class.name.split("::"), mention.get_the_index)
        #TODO -- will repeadedly changing a persisted xampl object fragment the TC db?

        pk = @tc_db.genuid
        mention_hash = {
                'xampl-from' => place,
                'mentioned_class' => xampl.class.name,
                'pid' => xampl.get_the_index,
                'xampl-to' => mention_place
        }

        note_errors("TC[[#{ @filename }]]:: write error: %s\n") do
          @tc_db.put(pk, mention_hash)
        end
      end
    end

    xampl_hash = {
            'class' => xampl.class.name,
            'pid' => xampl.get_the_index,
            'time-stamp' => @time_stamp,
            'xampl' => xampl_in_xml
    }

    primary_description, secondary_descriptions = xampl.describe_yourself
    if primary_description then
      xampl_hash = primary_description.merge(xampl_hash)
    end

    note_errors("TC[[#{ @filename }]]:: write error: %s\n") do
      if Xampl.raw_persister_options[:write_through] then
        FileUtils.mkdir_p(place_dir) unless File.exist?(place_dir)
        file_place = "#{ @files_dir }/#{ place }"
        File.open(file_place, "w") do |out|
          out.write xampl_hash['xampl']
          if :sync == Xampl.raw_persister_options[:write_through] then
            out.fsync
            if $is_darwin then
              out.fcntl(51, 0) # Attempt an F_FULLFSYNC fcntl to commit data to disk (darwin *ONLY*)
            end
          end
        end

      end
      @tc_db.put(place, xampl_hash)
    end

    #TODO -- smarter regarding when to delete (e.g. mentions)
    if xampl.should_schedule_delete? and xampl.scheduled_for_deletion_at then
      secondary_descriptions = [] unless secondary_descriptions
      secondary_descriptions << { 'scheduled-delete-at' => xampl.scheduled_for_deletion_at }
    elsif xampl.scheduled_for_deletion_at then
      #TODO -- puts "#{ __FILE__ }:#{ __LINE__ } HOW TO DO THIS without violating xampl's change rules????? "
      #xampl.scheduled_for_deletion_at = nil
    end

    if secondary_descriptions then
      xampl_hash = {
              'class' => xampl.class.name,
              'pid' => xampl.get_the_index,
              'xampl-place' => place
      }

      secondary_descriptions.each do | secondary_description |
        description = secondary_description.merge(xampl_hash)

        note_errors("TC[[#{ @filename }]]:: write error: %s\n") do
          pk = @tc_db.genuid
          @tc_db.put(pk, description)
        end
      end
    end

    @write_count = @write_count + 1
    xampl.changes_accepted
  end

  return true
end