audrey 0.2 → 0.3

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.
@@ -0,0 +1,1609 @@
1
+ require 'sqlite3'
2
+ require 'json'
3
+
4
+ #===============================================================================
5
+ # Audrey::Engine::SQLite3
6
+ #
7
+ class Audrey::Engine::SQLite3 < Audrey::Engine
8
+ attr_reader :dbh
9
+ attr_reader :transactions
10
+ attr_reader :path
11
+ attr_reader :preps
12
+ attr_accessor :partition
13
+ extend Forwardable
14
+
15
+
16
+ #---------------------------------------------------------------------------
17
+ # initialize
18
+ #
19
+ def initialize(p_path, p_mode, opts={})
20
+ # default options
21
+ opts = {'immediate_commit'=>true}.merge(opts)
22
+
23
+ # set some properties
24
+ @path = p_path
25
+ @immediate_commit = opts['immediate_commit'] ? true : false
26
+ @preps = {}
27
+ @mode = p_mode
28
+
29
+ # KLUDGE: For now, default to partition 'm'
30
+ @partition = 'm'
31
+
32
+ # lock file
33
+ set_lock()
34
+
35
+ # if file exists
36
+ existed = File.exist?(@path)
37
+
38
+ # open database handle
39
+ @dbh = get_dbh()
40
+
41
+ # custom functions
42
+ custom_functions()
43
+
44
+ # create and structure database if the file doesn't exist
45
+ if not existed
46
+ require 'audrey/engine/sqlite3/init'
47
+ Audrey::Engine::SQLite3::Init.build @dbh
48
+ end
49
+
50
+ # transactions
51
+ @transactions = []
52
+
53
+ # initial transaction
54
+ if @immediate_commit
55
+ @transactions.push Audrey::Engine::SQLite3::Transaction::AutoCommit.new(self)
56
+ else
57
+ @transactions.push Audrey::Engine::SQLite3::Transaction::RC.new(self)
58
+ end
59
+
60
+ # create stuff just for this session
61
+ self.class.session_tables @dbh
62
+ end
63
+ #
64
+ # initialize
65
+ #---------------------------------------------------------------------------
66
+
67
+
68
+ #---------------------------------------------------------------------------
69
+ # get_dbh
70
+ #
71
+ def get_dbh
72
+ # get database handle
73
+ rv = ::SQLite3::Database.new(@path)
74
+
75
+ # enforce referential integrity
76
+ rv.execute 'pragma foreign_keys=on'
77
+
78
+ # results as hash
79
+ rv.results_as_hash = true
80
+
81
+ # return
82
+ return rv
83
+ end
84
+ #
85
+ # get_dbh
86
+ #---------------------------------------------------------------------------
87
+
88
+
89
+ #---------------------------------------------------------------------------
90
+ # commit, rollback
91
+ #
92
+ def commit
93
+ return @transactions[0].commit
94
+ end
95
+
96
+ def rollback
97
+ return @transactions[0].rollback
98
+ end
99
+ #
100
+ # commit, rollback
101
+ #---------------------------------------------------------------------------
102
+
103
+
104
+ #---------------------------------------------------------------------------
105
+ # in_transaction?
106
+ #
107
+ def in_transaction?
108
+ # loop through transactions
109
+ @transactions.each do |tr|
110
+ tr.is_a?(Audrey::Engine::SQLite3::Transaction::RC) and return true
111
+ end
112
+
113
+ # else not in transaction
114
+ return false
115
+ end
116
+ #
117
+ # in_transaction?
118
+ #---------------------------------------------------------------------------
119
+
120
+
121
+ #---------------------------------------------------------------------------
122
+ # transaction
123
+ #
124
+ def transaction(opts={})
125
+ # $tm.hrm
126
+ tr = Audrey::Engine::SQLite3::Transaction::RC.new(self)
127
+ @transactions.push tr
128
+
129
+ # block
130
+ if block_given?
131
+ begin
132
+ # yield
133
+ yield tr
134
+
135
+ # autocommit if necessary
136
+ if opts['autocommit']
137
+ tr.commit
138
+ end
139
+
140
+ # exit transaction
141
+ rescue Audrey::Exception::TransactionExit => e
142
+ unless e.tr == tr
143
+ raise e
144
+ end
145
+
146
+ # return out of this method
147
+ return nil
148
+ ensure
149
+ tr.terminate
150
+ @transactions.pop
151
+ end
152
+
153
+ # return nil
154
+ return nil
155
+
156
+ # non-block
157
+ else
158
+ return tr
159
+ end
160
+ end
161
+ #
162
+ # transaction
163
+ #---------------------------------------------------------------------------
164
+
165
+
166
+ #---------------------------------------------------------------------------
167
+ # reset_savepoints
168
+ #
169
+ def reset_savepoints(ftr)
170
+ found = false
171
+
172
+ @transactions.each do |tr|
173
+ if tr == ftr
174
+ found = true
175
+ end
176
+
177
+ if found
178
+ tr.start
179
+ end
180
+ end
181
+ end
182
+ #
183
+ # reset_savepoints
184
+ #---------------------------------------------------------------------------
185
+
186
+
187
+ #---------------------------------------------------------------------------
188
+ # close
189
+ #
190
+ def close(opts={})
191
+ # $tm.hrm
192
+
193
+ # close prepared statements
194
+ @preps.keys.each do |key|
195
+ stmt = @preps[key]
196
+
197
+ if stmt and (not stmt.closed?)
198
+ stmt.close
199
+ end
200
+
201
+ @preps.delete key
202
+ end
203
+
204
+ # purge
205
+ if opts['purge']
206
+ purge()
207
+ end
208
+
209
+ # close database handle
210
+ if not @dbh.closed?
211
+ @dbh.close
212
+ end
213
+
214
+ # unlock file
215
+ if @lock
216
+ @lock.flock File::LOCK_UN
217
+ @lock.close
218
+ end
219
+ end
220
+ #
221
+ # close
222
+ #---------------------------------------------------------------------------
223
+
224
+
225
+ #---------------------------------------------------------------------------
226
+ # custom_functions
227
+ #
228
+ def custom_functions()
229
+ #-----------------------------------------------------------------------
230
+ # partition
231
+ #
232
+ @dbh.create_function('current_partition', 0) do |func|
233
+ func.result = @partition
234
+ end
235
+ #
236
+ # partition
237
+ #-----------------------------------------------------------------------
238
+
239
+
240
+ #-----------------------------------------------------------------------
241
+ # insert_object_journal
242
+ #
243
+ @dbh.create_function('insert_object_journal', 4 ) do |func, partition, root, fclass, scalar, xt|
244
+ # $tm.hrm
245
+
246
+ # translate scalars to objects for storage in JSON
247
+ if converter = Audrey::AUDREY_SCALAR_TO_RUBY[fclass]
248
+ scalar = converter.call(scalar)
249
+ end
250
+
251
+ # initialize
252
+ rv = {}
253
+ rv['fc'] = fclass
254
+ rv['pt'] = partition
255
+
256
+ # scalar
257
+ if rv['s'].is_a?(String)
258
+ rv['s'] = scalar.force_encoding('UTF-8')
259
+ end
260
+
261
+ # xt
262
+ if xt
263
+ rv['xt'] = xt
264
+ end
265
+
266
+ # root
267
+ if root == 1
268
+ rv['root'] = true
269
+ end
270
+
271
+ # set return result
272
+ func.result = JSON.generate(rv)
273
+ end
274
+ #
275
+ # insert_object_journal
276
+ #-----------------------------------------------------------------------
277
+
278
+
279
+ #-----------------------------------------------------------------------
280
+ # fclass_fco, fclass_isolate
281
+ #
282
+ @dbh.create_function('fclass_fco', 1) do |func, fclass|
283
+ func.result = Audrey::Util.fclass_fco(fclass) ? 1 : 0
284
+ end
285
+
286
+ @dbh.create_function('fclass_isolate', 1) do |func, fclass|
287
+ func.result = Audrey::Util.fclass_isolate(fclass) ? 1 : 0
288
+ end
289
+ #
290
+ # fclass_fco, fclass_isolate
291
+ #-----------------------------------------------------------------------
292
+
293
+
294
+ #-----------------------------------------------------------------------
295
+ # graph_field
296
+ #
297
+ @dbh.create_function('graph_field', 2) do |func, fclass, hkey|
298
+ func.result = Audrey::Util.graph_field?(fclass, hkey) ? 1 : 0
299
+ end
300
+ #
301
+ # graph_field
302
+ #-----------------------------------------------------------------------
303
+
304
+
305
+ #-----------------------------------------------------------------------
306
+ # change_to_true
307
+ #
308
+ # @dbh.create_function('change_to_true', 1) do |func, fclass|
309
+ # func.result = 1
310
+ # end
311
+ #
312
+ # change_to_true
313
+ #-----------------------------------------------------------------------
314
+
315
+
316
+ #-----------------------------------------------------------------------
317
+ # custom_class
318
+ #
319
+ @dbh.create_function('custom_class', 1) do |func, fclass|
320
+ func.result = Audrey::Util.custom_class?(fclass) ? 1 : 0
321
+ end
322
+ #
323
+ # custom_class
324
+ #-----------------------------------------------------------------------
325
+
326
+
327
+ #-----------------------------------------------------------------------
328
+ # debug
329
+ #
330
+ @dbh.create_function('debug', 1) do |func, msg|
331
+ puts msg
332
+ func.result = 1
333
+ end
334
+ #
335
+ # debug
336
+ #-----------------------------------------------------------------------
337
+ end
338
+ #
339
+ # custom_functions
340
+ #---------------------------------------------------------------------------
341
+
342
+
343
+ #---------------------------------------------------------------------------
344
+ # purge
345
+ #
346
+ def purge
347
+ # $tm.hrm
348
+
349
+ if @mode.write
350
+ sql = 'delete from objects where pk in (select pk from fco_not_traced)'
351
+ @dbh.execute sql
352
+ end
353
+ end
354
+ #
355
+ # purge
356
+ #---------------------------------------------------------------------------
357
+
358
+
359
+ #---------------------------------------------------------------------------
360
+ # root_pk
361
+ #
362
+ def root_pk
363
+ sql = 'select pk from objects where root and partition=:partition'
364
+ return @dbh.get_first_value(sql, 'partition'=>@partition)
365
+ end
366
+ #
367
+ # root_pk
368
+ #---------------------------------------------------------------------------
369
+
370
+
371
+ #---------------------------------------------------------------------------
372
+ # insert_object
373
+ #
374
+ def insert_object(object, opts={})
375
+ # $tm.hrm
376
+
377
+ # pk
378
+ pk = opts['pk'] || Audrey::Util.uuid
379
+
380
+ # if scalar
381
+ if dfn = Audrey::Engine::SQLite3::RUBY_SCALAR_TO_SQLITE[object.class]
382
+ if not @preps['insert_scalar']
383
+ sql = <<~'SQL'
384
+ insert into objects(pk, partition, bclass, fclass, scalar)
385
+ values(:pk, :partition, :bclass, :fclass, :scalar)
386
+ SQL
387
+
388
+ @preps['insert_scalar'] = @dbh.prepare(sql)
389
+ end
390
+
391
+ # insert record
392
+ simple_exec(
393
+ @preps['insert_scalar'],
394
+ 'pk' => pk,
395
+ 'partition' => @partition,
396
+ 'bclass' => dfn['fclass'].bclass.to_s,
397
+ 'fclass' => dfn['fclass'].to_s,
398
+ 'scalar' => dfn['to_db'].call(object)
399
+ );
400
+
401
+ # if hash or array
402
+ elsif dfn = Audrey::RUBY_OBJECT_TO_AUDREY[object.class] and dfn['nclass']
403
+ simple_exec(
404
+ prepare_insert_collection(),
405
+ 'pk' => pk,
406
+ 'partition' => @partition,
407
+ 'bclass' => dfn['fclass'].bclass.to_s,
408
+ 'fclass' => dfn['fclass'].to_s
409
+ );
410
+
411
+ # if custom object
412
+ elsif object.is_a?(Audrey::Object::Custom)
413
+ simple_exec(
414
+ prepare_insert_collection(),
415
+ 'pk' => pk,
416
+ 'partition' => @partition,
417
+ 'bclass' => object.class.bclass.to_s,
418
+ 'fclass' => object.class.to_s,
419
+ );
420
+
421
+ # else unknown
422
+ else
423
+ raise 'unknown-object-class: ' + object.class.to_s
424
+ end
425
+
426
+ # return pk
427
+ return pk
428
+ end
429
+ #
430
+ # insert_object
431
+ #---------------------------------------------------------------------------
432
+
433
+
434
+ #---------------------------------------------------------------------------
435
+ # delete_object
436
+ #
437
+ def delete_object(pk)
438
+ # $tm.hrm
439
+
440
+ # ensure statement handle
441
+ if not @preps['delete_object']
442
+ sql = <<~'SQL'
443
+ delete from objects
444
+ where pk=:pk and partition=:partition
445
+ SQL
446
+
447
+ @preps['delete_object'] = @dbh.prepare(sql)
448
+ end
449
+
450
+ # execute
451
+ simple_exec @preps['delete_object'], 'pk' => pk, 'partition' => @partition
452
+ end
453
+ #
454
+ # delete_object
455
+ #---------------------------------------------------------------------------
456
+
457
+
458
+ #---------------------------------------------------------------------------
459
+ # add_xt
460
+ #
461
+ def add_xt(obj_pk, xt_pk)
462
+ # $tm.hrm
463
+
464
+ # prepare insert statement if necessary
465
+ if not @preps['insert_xt']
466
+ sql = <<~SQL
467
+ insert into objects(pk, partition, bclass, fclass)
468
+ values(?, (select partition from objects where pk=?), 'Audrey::Object::Hash', 'xt')
469
+ SQL
470
+
471
+ @preps['insert_xt'] ||= @dbh.prepare(sql)
472
+ end
473
+
474
+ # create xt
475
+ simple_exec @preps['insert_xt'], xt_pk, obj_pk
476
+
477
+ # associate record
478
+ @preps['add_xt'] ||= @dbh.prepare('update objects set xt=:xtpk where pk=:pk and partition=:partition')
479
+ simple_exec @preps['add_xt'], 'xtpk'=>xt_pk, 'pk'=>obj_pk, 'partition'=>@partition
480
+ end
481
+ #
482
+ # add_xt
483
+ #---------------------------------------------------------------------------
484
+
485
+
486
+ #---------------------------------------------------------------------------
487
+ # get_xt
488
+ #
489
+ def get_xt(obj_pk)
490
+ # prepare statement if necessary
491
+ @preps['existing_xt'] ||= @dbh.prepare('select xt from objects where pk=:pk and partition=:partition')
492
+
493
+ # search
494
+ @preps['existing_xt'].execute('pk'=>obj_pk, 'partition'=>@partition).each do |row|
495
+ return row['xt']
496
+ end
497
+
498
+ # create
499
+ xt_pk = Audrey::Util.uuid
500
+ add_xt obj_pk, xt_pk
501
+
502
+ # return
503
+ return xt_pk
504
+ ensure
505
+ @preps['existing_xt'] and @preps['existing_xt'].reset!
506
+ end
507
+ #
508
+ # get_xt
509
+ #---------------------------------------------------------------------------
510
+
511
+
512
+ #---------------------------------------------------------------------------
513
+ # add_relationship
514
+ #
515
+ def add_relationship(parent_pk, child_pk, opts={})
516
+ # ensure prepared statement
517
+ if not @preps['add_relationship']
518
+ sql = <<~SQL
519
+ insert into relationships(pk, parent, child, ord, hkey)
520
+ values(
521
+ :pk,
522
+ :parent,
523
+ :child,
524
+
525
+ case
526
+ when :ord is null then
527
+ coalesce((select max(ord) from relationships where parent=parent), 0) + 1
528
+ else :ord
529
+ end,
530
+ :hkey)
531
+ SQL
532
+
533
+ @preps['add_relationship'] = @dbh.prepare(sql)
534
+ end
535
+
536
+ # uuid for new relationship record
537
+ pk = Audrey::Util.uuid
538
+
539
+ # execute
540
+ begin
541
+ @preps['add_relationship'].execute(
542
+ sql,
543
+ 'pk'=>pk,
544
+ 'parent'=>parent_pk,
545
+ 'child'=>child_pk,
546
+ 'ord'=>opts['ord'],
547
+ 'hkey'=>opts['hkey']
548
+ );
549
+ ensure
550
+ @preps['add_relationship'].reset!
551
+ end
552
+
553
+ # return
554
+ return pk
555
+ end
556
+ #
557
+ # add_relationship
558
+ #---------------------------------------------------------------------------
559
+
560
+
561
+ #---------------------------------------------------------------------------
562
+ # hash_has_key?
563
+ #
564
+ def hash_has_key?(parent_pk, key)
565
+ # $tm.hrm
566
+ # puts "parent_pk: #{parent_pk}"
567
+ # puts "key: #{key}"
568
+
569
+ # prepare
570
+ if not @preps['hash_has_key']
571
+ sql = <<~'SQL'
572
+ select count(*) as count
573
+ from relationships
574
+ where parent=:parent and hkey=:hkey
575
+ SQL
576
+
577
+ @preps['hash_has_key'] ||= @dbh.prepare(sql)
578
+ end
579
+
580
+ # get count
581
+ @preps['hash_has_key'].execute('parent'=>parent_pk, 'hkey'=>key).each_hash do |row|
582
+ return row['count'] > 0
583
+ end
584
+ ensure
585
+ @preps['hash_has_key'] and @preps['hash_has_key'].reset!
586
+ end
587
+ #
588
+ # hash_has_key?
589
+ #---------------------------------------------------------------------------
590
+
591
+
592
+
593
+ #---------------------------------------------------------------------------
594
+ # row_hash_by_key
595
+ #
596
+ def row_hash_by_key(parent_pk, hkey)
597
+ # $tm.hrm
598
+
599
+ # ensure statement handle
600
+ if not @preps['row_hash_by_key']
601
+ sql = <<~SQL
602
+ select * from objects
603
+ where pk =
604
+ (select child from relationships where parent=:parent and hkey=:hkey) and
605
+ partition=:partition
606
+ SQL
607
+
608
+ @preps['row_hash_by_key'] = @dbh.prepare(sql)
609
+ end
610
+
611
+ # params
612
+ params = {}
613
+ params['parent'] = parent_pk
614
+ params['hkey'] = hkey
615
+ params['partition'] = @partition
616
+
617
+ # get record
618
+ @preps['row_hash_by_key'].execute(params).each_hash do |row|
619
+ return self.class.set_row_scalar(row.to_h)
620
+ end
621
+
622
+ ensure
623
+ @preps['row_hash_by_key'] and @preps['row_hash_by_key'].reset!
624
+ end
625
+ #
626
+ # row_hash_by_key
627
+ #---------------------------------------------------------------------------
628
+
629
+
630
+
631
+ #---------------------------------------------------------------------------
632
+ # hash_element_delete
633
+ #
634
+ def hash_element_delete(parent_pk, key)
635
+ # $tm.hrm
636
+
637
+ # get record
638
+ record = row_hash_by_key(parent_pk, key)
639
+
640
+ # execute
641
+ if record
642
+ if not @preps['delete_hash_key']
643
+ sql = <<~'SQL'
644
+ delete from relationships
645
+ where parent=:parent and hkey=:hkey
646
+ SQL
647
+
648
+ @preps['delete_hash_key'] = @dbh.prepare(sql)
649
+ end
650
+
651
+ simple_exec @preps['delete_hash_key'], 'parent'=>parent_pk, 'hkey'=>key
652
+ end
653
+
654
+ # return
655
+ return record
656
+ end
657
+ #
658
+ # hash_element_delete
659
+ #---------------------------------------------------------------------------
660
+
661
+
662
+ #---------------------------------------------------------------------------
663
+ # row_by_pk
664
+ #
665
+ def row_by_pk(pk)
666
+ # $tm.hrm
667
+
668
+ # ensure prepared statement
669
+ @preps['row_by_pk'] ||= @dbh.prepare('select * from objects where pk=:pk and partition=:partition')
670
+
671
+ # get record
672
+ @preps['row_by_pk'].execute('pk'=>pk, 'partition'=>@partition).each_hash do |row|
673
+ return self.class.set_row_scalar(row.to_h)
674
+ end
675
+
676
+ # didn't find the object, so return nil
677
+ return nil
678
+
679
+ ensure
680
+ @preps['row_by_pk'] and @preps['row_by_pk'].reset!
681
+ end
682
+ #
683
+ # row_by_pk
684
+ #---------------------------------------------------------------------------
685
+
686
+
687
+ #---------------------------------------------------------------------------
688
+ # row_by_array_index
689
+ #
690
+ def row_by_array_index(parent_pk, idx)
691
+ # $tm.hrm
692
+
693
+ # ensure statement handle
694
+ if not @preps['row_by_array_index']
695
+ sql = <<~SQL
696
+ select r.ord as ord, o.*
697
+ from relationships r, objects o
698
+ where r.parent=:parent and o.pk=r.child
699
+ order by r.ord
700
+ limit 1
701
+ offset :offset
702
+ SQL
703
+
704
+ @preps['row_by_array_index'] = @dbh.prepare(sql)
705
+ end
706
+
707
+ # get record
708
+ @preps['row_by_array_index'].execute('parent'=>parent_pk, 'offset'=>idx).each_hash do |row|
709
+ return self.class.set_row_scalar(row.to_h)
710
+ end
711
+
712
+ # else return nil
713
+ return nil
714
+ ensure
715
+ @preps['row_by_array_index'] and @preps['row_by_array_index'].reset!
716
+ end
717
+ #
718
+ # row_by_array_index
719
+ #---------------------------------------------------------------------------
720
+
721
+
722
+ #---------------------------------------------------------------------------
723
+ # hash_each
724
+ #
725
+ def hash_each(parent_pk)
726
+ # $tm.hrm
727
+ rv = nil
728
+
729
+ # loop through keys
730
+ hash_keys(parent_pk).each do |key|
731
+ if block_given?
732
+ yield key, row_hash_by_key(parent_pk, key)
733
+ else
734
+ rv ||= []
735
+ rv.push [key, row_hash_by_key(parent_pk, key)]
736
+ end
737
+ end
738
+
739
+ # return
740
+ return rv
741
+ end
742
+ #
743
+ # hash_each
744
+ #---------------------------------------------------------------------------
745
+
746
+
747
+
748
+ #---------------------------------------------------------------------------
749
+ # hash_keys
750
+ #
751
+ def hash_keys(parent_pk)
752
+ # $tm.hrm
753
+ rv = []
754
+
755
+ # prepare
756
+ @preps['hash_keys'] ||= @dbh.prepare('select hkey from relationships where parent=:parent order by ord')
757
+
758
+ # loop through records
759
+ @preps['hash_keys'].execute('parent'=>parent_pk).each_hash do |row|
760
+ rv.push row['hkey']
761
+ end
762
+
763
+ # return
764
+ return rv
765
+
766
+ ensure
767
+ @preps['hash_keys'] and @preps['hash_keys'].reset!
768
+ end
769
+ #
770
+ # hash_keys
771
+ #---------------------------------------------------------------------------
772
+
773
+
774
+ #---------------------------------------------------------------------------
775
+ # clear_collection
776
+ #
777
+ def clear_collection(parent_pk)
778
+ # $tm.hrm
779
+
780
+ # prepare
781
+ @preps['clear_collection'] ||= @dbh.prepare('delete from relationships where parent=:parent')
782
+
783
+ # execute
784
+ simple_exec @preps['clear_collection'], 'parent'=>parent_pk
785
+ end
786
+ #
787
+ # clear_collection
788
+ #---------------------------------------------------------------------------
789
+
790
+
791
+ #---------------------------------------------------------------------------
792
+ # changed_get
793
+ #
794
+ def changed_get(pk)
795
+ # $tm.hrm
796
+
797
+ # prepare
798
+ @preps['changed_get'] ||= @dbh.prepare('select changed from objects where pk=:pk')
799
+
800
+ # execute
801
+ @preps['changed_get'].execute('pk'=>pk).each_hash do |row|
802
+ return row['changed']
803
+ end
804
+ ensure
805
+ @preps['changed_get'] and @preps['changed_get'].reset!
806
+ end
807
+ #
808
+ # changed_get
809
+ #---------------------------------------------------------------------------
810
+
811
+
812
+ #---------------------------------------------------------------------------
813
+ # changed_set
814
+ #
815
+ def changed_set(pk, bool)
816
+ # $tm.hrm
817
+
818
+ # set to changed
819
+ if bool
820
+ @preps['changed_set_true'] ||= @dbh.prepare('update base_objects set changed=current_timestamp where pk=:pk')
821
+ simple_exec @preps['changed_set_true'], 'pk'=>pk
822
+ else
823
+ @preps['changed_set_false'] ||= @dbh.prepare('update base_objects set changed=null where pk=:pk')
824
+ simple_exec @preps['changed_set_false'], 'pk'=>pk
825
+ end
826
+ end
827
+ #
828
+ # changed_set
829
+ #---------------------------------------------------------------------------
830
+
831
+
832
+ #---------------------------------------------------------------------------
833
+ # collection_child_pks
834
+ #
835
+ def collection_child_pks(parent_pk)
836
+ # $tm.hrm
837
+
838
+ # prepare
839
+ if not @preps['collection_child_pks']
840
+ sql = <<~'SQL'
841
+ select child
842
+ from relationships
843
+ where parent=:parent
844
+ SQL
845
+
846
+ @preps['collection_child_pks'] ||= @dbh.prepare(sql)
847
+ end
848
+
849
+ # init
850
+ rv = []
851
+
852
+ # get count
853
+ @preps['collection_child_pks'].execute(parent_pk).each_hash do |row|
854
+ rv.push row['child']
855
+ end
856
+
857
+ # return
858
+ return rv
859
+ ensure
860
+ @preps['collection_child_pks'] and @preps['collection_child_pks'].reset!
861
+ end
862
+ #
863
+ # collection_child_pks
864
+ #---------------------------------------------------------------------------
865
+
866
+
867
+ #---------------------------------------------------------------------------
868
+ # collection_length
869
+ #
870
+ def collection_length(parent_pk)
871
+ # $tm.hrm
872
+
873
+ # prepare
874
+ if not @preps['collection_length']
875
+ sql = <<~'SQL'
876
+ select count(*) as count
877
+ from relationships
878
+ where parent=:parent
879
+ SQL
880
+
881
+ @preps['collection_length'] ||= @dbh.prepare(sql)
882
+ end
883
+
884
+ # get count
885
+ @preps['collection_length'].execute(parent_pk).each_hash do |row|
886
+ return row['count']
887
+ end
888
+ ensure
889
+ @preps['collection_length'] and @preps['collection_length'].reset!
890
+ end
891
+ #
892
+ # collection_length
893
+ #---------------------------------------------------------------------------
894
+
895
+
896
+ #---------------------------------------------------------------------------
897
+ # array_each
898
+ #
899
+ def array_each(parent_pk)
900
+ # $tm.hrm
901
+
902
+ # ensure statement handle
903
+ if not @preps['array_each']
904
+ sql = <<~SQL
905
+ select r.ord as ord, o.*
906
+ from relationships r, objects o
907
+ where r.parent=:parent and o.pk=r.child
908
+ order by r.ord
909
+ SQL
910
+
911
+ @preps['array_each'] = @dbh.prepare(sql)
912
+ end
913
+
914
+ # loop through records
915
+ @preps['array_each'].execute('parent'=>parent_pk).each_hash do |row|
916
+ yield row
917
+ end
918
+ ensure
919
+ @preps['array_each'] and @preps['array_each'].reset!
920
+ end
921
+ #
922
+ # array_each
923
+ #---------------------------------------------------------------------------
924
+
925
+
926
+ #---------------------------------------------------------------------------
927
+ # object_exists?
928
+ #
929
+ def object_exists?(pk)
930
+ # $tm.hrm
931
+
932
+ # ensure statement handle
933
+ if not @preps['object_exists']
934
+ sql = <<~'SQL'
935
+ select count(*) as count
936
+ from objects
937
+ where
938
+ pk=:pk and
939
+ partition=:partition
940
+ SQL
941
+
942
+ @preps['object_exists'] = @dbh.prepare(sql)
943
+ end
944
+
945
+ @preps['object_exists'].execute('pk'=>pk, 'partition'=>@partition).each do |row|
946
+ return row['count'] > 0
947
+ end
948
+ ensure
949
+ @preps['object_exists'] and @preps['object_exists'].reset!
950
+ end
951
+ #
952
+ # object_exists?
953
+ #---------------------------------------------------------------------------
954
+
955
+
956
+ #---------------------------------------------------------------------------
957
+ # ancestors
958
+ #
959
+ def ancestors(child_pk)
960
+ # sql to add parents
961
+ if not @preps['ancestors']
962
+ sql = <<~'SQL'
963
+ select *
964
+ from objects where pk in (
965
+ with recursive
966
+ ancestors(c) as (
967
+ values(:child_pk)
968
+ union
969
+ select parent from relationships, ancestors
970
+ where relationships.child=ancestors.c
971
+ )
972
+ select parent from relationships
973
+ where relationships.child in ancestors
974
+ );
975
+ SQL
976
+
977
+ @preps['ancestors'] = @dbh.prepare(sql)
978
+ end
979
+
980
+ # loop through results
981
+ @preps['ancestors'].execute('child_pk'=>child_pk).each do |row|
982
+ yield row
983
+ end
984
+ ensure
985
+ @preps['ancestors'] and @preps['ancestors'].reset!
986
+ end
987
+ #
988
+ # ancestors
989
+ #---------------------------------------------------------------------------
990
+
991
+
992
+ #---------------------------------------------------------------------------
993
+ # q0
994
+ #
995
+ def q0(p_fquery)
996
+ require 'audrey/engine/sqlite3/query/q0'
997
+ return Audrey::Engine::SQLite3::Query::Q0.new(self, p_fquery)
998
+ end
999
+ #
1000
+ # q0
1001
+ #---------------------------------------------------------------------------
1002
+
1003
+
1004
+ #---------------------------------------------------------------------------
1005
+ # import_csv
1006
+ #
1007
+ def import_csv(fclass, path, opts={})
1008
+ # $tm.hrm
1009
+ return Audrey::Engine::SQLite3::Importer::CSV.new(self, fclass, path, opts={})
1010
+ end
1011
+ #
1012
+ # import_csv
1013
+ #---------------------------------------------------------------------------
1014
+
1015
+
1016
+ #---------------------------------------------------------------------------
1017
+ # changed_objects
1018
+ #
1019
+ def changed_objects
1020
+ # $tm.hrm
1021
+
1022
+ # sql to add parents
1023
+ if not @preps['changed_objects']
1024
+ sql = <<~'SQL'
1025
+ select *
1026
+ from objects
1027
+ where
1028
+ changed and
1029
+ custom_class(fclass)
1030
+ order by
1031
+ changed
1032
+ SQL
1033
+
1034
+ @preps['changed_objects'] = @dbh.prepare(sql)
1035
+ end
1036
+
1037
+ # loop through results
1038
+ @preps['changed_objects'].execute().each do |row|
1039
+ yield row
1040
+ end
1041
+ ensure
1042
+ @preps['changed_objects'] and @preps['changed_objects'].reset!
1043
+ end
1044
+ #
1045
+ # changed_objects
1046
+ #---------------------------------------------------------------------------
1047
+
1048
+
1049
+ # private
1050
+ private
1051
+
1052
+
1053
+ #---------------------------------------------------------------------------
1054
+ # simple_exec
1055
+ #
1056
+ def simple_exec(stmt, *opts)
1057
+ begin
1058
+ stmt.execute(*opts)
1059
+ ensure
1060
+ stmt.reset!
1061
+ end
1062
+ end
1063
+ #
1064
+ # simple_exec
1065
+ #---------------------------------------------------------------------------
1066
+
1067
+
1068
+ #---------------------------------------------------------------------------
1069
+ # RUBY_SCALAR_TO_SQLITE
1070
+ #
1071
+ RUBY_SCALAR_TO_SQLITE = {}
1072
+
1073
+ RUBY_SCALAR_TO_SQLITE[::String] = {
1074
+ 'fclass' => Audrey::Object::Scalar::String,
1075
+ 'to_db' => Proc.new {|obj| obj}
1076
+ }
1077
+
1078
+ RUBY_SCALAR_TO_SQLITE[::Integer] = {
1079
+ 'fclass' => Audrey::Object::Scalar::Number,
1080
+ 'to_db' => Proc.new {|obj| obj.to_s}
1081
+ }
1082
+
1083
+ RUBY_SCALAR_TO_SQLITE[::Float] = RUBY_SCALAR_TO_SQLITE[::Integer]
1084
+
1085
+ RUBY_SCALAR_TO_SQLITE[::TrueClass] = {
1086
+ 'fclass' => Audrey::Object::Scalar::Boolean,
1087
+ 'to_db' => Proc.new {|obj| obj ? 1 : 0}
1088
+ }
1089
+
1090
+ RUBY_SCALAR_TO_SQLITE[::FalseClass] = RUBY_SCALAR_TO_SQLITE[::TrueClass]
1091
+
1092
+ RUBY_SCALAR_TO_SQLITE[::NilClass] = {
1093
+ 'fclass' => Audrey::Object::Scalar::Null,
1094
+ 'to_db' => Proc.new {|obj| nil}
1095
+ }
1096
+
1097
+ RUBY_SCALAR_TO_SQLITE.freeze
1098
+ #
1099
+ # RUBY_SCALAR_TO_SQLITE
1100
+ #---------------------------------------------------------------------------
1101
+
1102
+
1103
+ #---------------------------------------------------------------------------
1104
+ # prepare_insert_collection
1105
+ #
1106
+ def prepare_insert_collection
1107
+ if not @preps['insert_collection']
1108
+ sql = <<~'SQL'
1109
+ insert into objects(pk, partition, bclass, fclass)
1110
+ values(:pk, :partition, :bclass, :fclass)
1111
+ SQL
1112
+
1113
+ @preps['insert_collection'] = @dbh.prepare(sql)
1114
+ end
1115
+
1116
+ return @preps['insert_collection']
1117
+ end
1118
+ #
1119
+ # prepare_insert_collection
1120
+ #---------------------------------------------------------------------------
1121
+
1122
+
1123
+ #---------------------------------------------------------------------------
1124
+ # set_row_scalar
1125
+ #
1126
+ def self.set_row_scalar(row)
1127
+ # fix scalar if necessary
1128
+ if converter = Audrey::AUDREY_SCALAR_TO_RUBY[row['fclass']]
1129
+ row['scalar'] = converter.call(row['scalar'])
1130
+ end
1131
+
1132
+ # return
1133
+ return row
1134
+ end
1135
+ #
1136
+ # set_row_scalar
1137
+ #---------------------------------------------------------------------------
1138
+
1139
+
1140
+ #---------------------------------------------------------------------------
1141
+ # session_tables
1142
+ #
1143
+ def self.session_tables(dbh)
1144
+ # create temp tables
1145
+ # KLUDGE: query_rows references objects, but apparently you can't reference
1146
+ # from a temp table to a main table. For now just not enforcing referential
1147
+ # integrity.
1148
+ sql = <<~"SQL"
1149
+ create temp table connection(key varchar(5) primary key, val varchar(64));
1150
+ insert into connection(key, val) values('partition', null);
1151
+
1152
+ create temp table queries (pk varchar(5) primary key);
1153
+ create temp table subqueries (pk varchar(5) primary key, query not null references queries(pk));
1154
+
1155
+ create temp table query_rows (
1156
+ subquery varchar(5) not null references subqueries(pk) on delete cascade,
1157
+ parent varchar(36) not null, -- references objects(pk),
1158
+ unique(subquery, parent)
1159
+ );
1160
+ SQL
1161
+
1162
+ # execute
1163
+ dbh.execute_batch sql
1164
+ end
1165
+ #
1166
+ # session_tables
1167
+ #---------------------------------------------------------------------------
1168
+
1169
+
1170
+ #---------------------------------------------------------------------------
1171
+ # set_lock
1172
+ #
1173
+ def set_lock()
1174
+ # $tm.hrm
1175
+
1176
+ # open lock file
1177
+ @lock = File.open(@path + '.lock', File::RDWR|File::CREAT)
1178
+
1179
+ # exclusive
1180
+ if @mode.write
1181
+ @lock.flock File::LOCK_EX
1182
+
1183
+ # shared
1184
+ else
1185
+ @lock.flock File::LOCK_SH
1186
+ end
1187
+ end
1188
+ #
1189
+ # set_lock
1190
+ #---------------------------------------------------------------------------
1191
+ end
1192
+ #
1193
+ # Audrey::Engine::SQLite3
1194
+ #===============================================================================
1195
+
1196
+
1197
+ #===============================================================================
1198
+ # Audrey::Engine::SQLite3::Importer
1199
+ #
1200
+ class Audrey::Engine::SQLite3::Importer
1201
+ end
1202
+ #
1203
+ # Audrey::Engine::SQLite3::Importer
1204
+ #===============================================================================
1205
+
1206
+
1207
+ #===============================================================================
1208
+ # Audrey::Engine::SQLite3::Importer::CSV
1209
+ #
1210
+ class Audrey::Engine::SQLite3::Importer::CSV < Audrey::Engine::SQLite3::Importer
1211
+ attr_reader :fields
1212
+ attr_accessor :verbose
1213
+
1214
+ #---------------------------------------------------------------------------
1215
+ # initialize
1216
+ #
1217
+ def initialize(engine, fclass, path, opts={})
1218
+ # $tm.hrm
1219
+ require 'csv'
1220
+ @engine = engine
1221
+ @dbh = @engine.dbh
1222
+ @fclass = fclass.to_s
1223
+ @path = path
1224
+ @partition = 'm'
1225
+ @fields = {}
1226
+ @verbose = false
1227
+ end
1228
+ #
1229
+ # initialize
1230
+ #---------------------------------------------------------------------------
1231
+
1232
+
1233
+ #---------------------------------------------------------------------------
1234
+ # run
1235
+ #
1236
+ def run
1237
+ # $tm.hrm
1238
+ converters = {}
1239
+ count = 0
1240
+
1241
+ # set fclass primary keys
1242
+ fclass_pks()
1243
+
1244
+ # prepare statements
1245
+ prepare_statements()
1246
+
1247
+ # build converters
1248
+ @fields.each do |k, dfn|
1249
+ converters[k] = Audrey::AUDREY_SCALAR_TO_RUBY[dfn.to_s]
1250
+ end
1251
+
1252
+ # begin transaction
1253
+ @dbh.execute 'begin transaction'
1254
+
1255
+ # TESTING
1256
+ $tm.timer('import') do
1257
+ # loop through CSV
1258
+ ::CSV.foreach(@path, headers: true) do |row|
1259
+ # verbosify
1260
+ if @verbose
1261
+ count += 1
1262
+ puts count.to_s + ': ' + row.to_s
1263
+ end
1264
+
1265
+ # init collection ord
1266
+ ord = 0
1267
+
1268
+ # convert
1269
+ converters.each do |k, converter|
1270
+ if row.has_key?(k)
1271
+ row[k] = converter.call(row[k])
1272
+ end
1273
+ end
1274
+
1275
+ # object pk
1276
+ obj_pk = Audrey::Util.uuid
1277
+
1278
+ # add hash object
1279
+ @insert_hash.execute(
1280
+ 'pk'=>obj_pk,
1281
+ 'partition'=>@partition,
1282
+ 'bclass'=>@obj_bclass_pk,
1283
+ 'fclass'=>@obj_fclass_pk
1284
+ );
1285
+
1286
+ # add scalars and relationships
1287
+ row.each do |k,v|
1288
+ ord += 1
1289
+ scalar_uuid = Audrey::Util.uuid
1290
+
1291
+ # add scalar object
1292
+ @insert_scalar.execute(
1293
+ 'pk'=>scalar_uuid,
1294
+ 'partition'=>@partition,
1295
+ 'bclass'=>@scalar_bclass_pk,
1296
+ 'fclass'=>@bclasses[v.class],
1297
+ 'scalar'=>v
1298
+ );
1299
+
1300
+ # add relationship
1301
+ @insert_relationship.execute(
1302
+ 'pk'=>Audrey::Util.uuid,
1303
+ 'parent'=>obj_pk,
1304
+ 'child'=>scalar_uuid,
1305
+ 'hkey'=>k,
1306
+ 'ord'=>ord
1307
+ );
1308
+ end
1309
+ end
1310
+ end
1311
+
1312
+ # commit transaction
1313
+ $tm.timer('commit') do
1314
+ @dbh.execute 'commit'
1315
+ end
1316
+
1317
+ # close prepared statements
1318
+ ensure
1319
+ $tm.puts 'close prepared statements'
1320
+
1321
+ $tm.timer('close prepared statements') do
1322
+ @insert_hash and @insert_hash.close
1323
+ @insert_scalar and @insert_scalar.close
1324
+ @insert_relationship and @insert_relationship.close
1325
+ end
1326
+ end
1327
+ #
1328
+ # run
1329
+ #---------------------------------------------------------------------------
1330
+
1331
+
1332
+ #---------------------------------------------------------------------------
1333
+ # prepare_statements
1334
+ #
1335
+ def prepare_statements
1336
+ # $tm.hrm
1337
+
1338
+ # hash
1339
+ sql = <<~'SQL'
1340
+ insert into
1341
+ base_objects(
1342
+ pk,
1343
+ partition,
1344
+ bclass_pk,
1345
+ fclass_pk
1346
+ )
1347
+
1348
+ values(
1349
+ :pk,
1350
+ :partition,
1351
+ :bclass,
1352
+ :fclass
1353
+ );
1354
+ SQL
1355
+
1356
+ # prepare
1357
+ @insert_hash = @dbh.prepare(sql)
1358
+
1359
+ # scalar
1360
+ sql = <<~'SQL'
1361
+ insert into
1362
+ base_objects(
1363
+ pk,
1364
+ partition,
1365
+ bclass_pk,
1366
+ fclass_pk,
1367
+ scalar
1368
+ )
1369
+
1370
+ values(
1371
+ :pk,
1372
+ :partition,
1373
+ :bclass,
1374
+ :fclass,
1375
+ :scalar
1376
+ );
1377
+ SQL
1378
+
1379
+ # prepare
1380
+ @insert_scalar = @dbh.prepare(sql)
1381
+
1382
+ # relationship
1383
+ sql = <<~'SQL'
1384
+ insert into relationships(
1385
+ pk,
1386
+ parent,
1387
+ child,
1388
+ hkey,
1389
+ ord
1390
+ )
1391
+
1392
+ values(
1393
+ :pk,
1394
+ :parent,
1395
+ :child,
1396
+ :hkey,
1397
+ :ord
1398
+ )
1399
+ SQL
1400
+
1401
+ # prepare
1402
+ @insert_relationship = @dbh.prepare(sql)
1403
+ end
1404
+ #
1405
+ # prepare_statements
1406
+ #---------------------------------------------------------------------------
1407
+
1408
+
1409
+ #---------------------------------------------------------------------------
1410
+ # fclass_pks
1411
+ #
1412
+ def fclass_pks
1413
+ @bclasses = {}
1414
+
1415
+ # object bclass primary key
1416
+ @obj_bclass_pk = set_fclass_pk('Audrey::Object::Hash')
1417
+
1418
+ # object fclass primary key
1419
+ @obj_fclass_pk = set_fclass_pk(@fclass)
1420
+
1421
+ # scalar base class
1422
+ @scalar_bclass_pk = set_fclass_pk('Audrey::Object::Scalar')
1423
+
1424
+ # loop through scalar classes
1425
+ [String, Integer, Float, NilClass].each do |clss|
1426
+ @bclasses[clss] = set_fclass_pk(Audrey::RUBY_OBJECT_TO_AUDREY[clss]['fclass'].to_s)
1427
+ end
1428
+ end
1429
+ #
1430
+ # fclass_pks
1431
+ #---------------------------------------------------------------------------
1432
+
1433
+
1434
+ #---------------------------------------------------------------------------
1435
+ # set_fclass_pk
1436
+ #
1437
+ def set_fclass_pk(set_fclass)
1438
+ # $tm.hrm
1439
+
1440
+ # ensure fclass record
1441
+ sql = <<~'SQL'
1442
+ insert or ignore into
1443
+ fclasses(name)
1444
+ values(:fclass);
1445
+ SQL
1446
+
1447
+ # execute
1448
+ @dbh.execute sql, 'fclass'=>set_fclass
1449
+
1450
+ # get fclass pk
1451
+ sql = 'select pk from fclasses where name=:fclass'
1452
+ return @dbh.get_first_value(sql, 'fclass'=>set_fclass)
1453
+ end
1454
+ #
1455
+ # set_fclass_pk
1456
+ #---------------------------------------------------------------------------
1457
+
1458
+
1459
+ #---------------------------------------------------------------------------
1460
+ # set_bclass_pks
1461
+ #
1462
+ # def set_bclass_pks
1463
+ # $tm.hrm
1464
+ # end
1465
+ #
1466
+ # set_bclass_pks
1467
+ #---------------------------------------------------------------------------
1468
+ end
1469
+ #
1470
+ # Audrey::Engine::SQLite3::Importer::CSV
1471
+ #===============================================================================
1472
+
1473
+
1474
+ #===============================================================================
1475
+ # Audrey::Engine::SQLite3::Query
1476
+ #
1477
+ class Audrey::Engine::SQLite3::Query
1478
+ end
1479
+ #
1480
+ # Audrey::Engine::SQLite3::Query
1481
+ #===============================================================================
1482
+
1483
+
1484
+ #===============================================================================
1485
+ # Audrey::Engine::SQLite3::Transaction
1486
+ #
1487
+ class Audrey::Engine::SQLite3::Transaction < Audrey::Transaction
1488
+ #---------------------------------------------------------------------------
1489
+ # initialize
1490
+ #
1491
+ def initialize(p_engine)
1492
+ @engine = p_engine
1493
+ end
1494
+ #
1495
+ # initialize
1496
+ #---------------------------------------------------------------------------
1497
+ end
1498
+ #
1499
+ # Audrey::Engine::SQLite3::Transaction
1500
+ #===============================================================================
1501
+
1502
+
1503
+ #===============================================================================
1504
+ # Audrey::Engine::SQLite3::Transaction::AutoCommit
1505
+ #
1506
+ class Audrey::Engine::SQLite3::Transaction::AutoCommit < Audrey::Engine::SQLite3::Transaction
1507
+ def commit
1508
+ @engine.reset_savepoints self
1509
+ end
1510
+
1511
+ def start
1512
+ end
1513
+
1514
+ def rollback
1515
+ end
1516
+
1517
+ def release
1518
+ end
1519
+ end
1520
+ #
1521
+ # Audrey::Engine::SQLite3::Transaction::AutoCommit
1522
+ #===============================================================================
1523
+
1524
+
1525
+ #===============================================================================
1526
+ # Audrey::Engine::SQLite3::Transaction::RC
1527
+ # An object of this class represents a transaction that can be rolled back or
1528
+ # committed.
1529
+ #
1530
+ class Audrey::Engine::SQLite3::Transaction::RC < Audrey::Engine::SQLite3::Transaction
1531
+ attr_reader :savepoint
1532
+
1533
+ #---------------------------------------------------------------------------
1534
+ # initialize
1535
+ #
1536
+ def initialize(*opts)
1537
+ super(*opts)
1538
+ start()
1539
+ end
1540
+ #
1541
+ # initialize
1542
+ #---------------------------------------------------------------------------
1543
+
1544
+
1545
+ #---------------------------------------------------------------------------
1546
+ # start
1547
+ #
1548
+ def start
1549
+ @savepoint = Audrey::Util.randstr()
1550
+ @engine.dbh.execute "savepoint #{@savepoint}"
1551
+ end
1552
+ #
1553
+ # start
1554
+ #---------------------------------------------------------------------------
1555
+
1556
+
1557
+ #---------------------------------------------------------------------------
1558
+ # commit
1559
+ #
1560
+ def commit
1561
+ release()
1562
+ @engine.reset_savepoints self
1563
+ end
1564
+ #
1565
+ # commit
1566
+ #---------------------------------------------------------------------------
1567
+
1568
+
1569
+ #---------------------------------------------------------------------------
1570
+ # rollback
1571
+ #
1572
+ def rollback
1573
+ @engine.dbh.execute "rollback to #{@savepoint}"
1574
+ end
1575
+ #
1576
+ # rollback
1577
+ #---------------------------------------------------------------------------
1578
+
1579
+
1580
+ #---------------------------------------------------------------------------
1581
+ # terminate
1582
+ #
1583
+ def terminate
1584
+ rollback()
1585
+ release()
1586
+ end
1587
+ #
1588
+ # terminate
1589
+ #---------------------------------------------------------------------------
1590
+
1591
+
1592
+ # private
1593
+ private
1594
+
1595
+
1596
+ #---------------------------------------------------------------------------
1597
+ # release
1598
+ #
1599
+ def release
1600
+ @engine.dbh.execute "release #{@savepoint}"
1601
+ @savepoint = nil
1602
+ end
1603
+ #
1604
+ # release
1605
+ #---------------------------------------------------------------------------
1606
+ end
1607
+ #
1608
+ # Audrey::Engine::SQLite3::Transaction::RC
1609
+ #===============================================================================