lims-core 3.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.rvmrc +2 -0
- data/.travis.yml +2 -0
- data/.vimrc +27 -0
- data/.yard_templates/default/layout/html/footer.erb +0 -0
- data/.yardopts +1 -0
- data/Gemfile +54 -0
- data/Gemfile.lock +197 -0
- data/Guardfile +21 -0
- data/Guardfile.tmux +28 -0
- data/README.markdown +67 -0
- data/Rakefile +16 -0
- data/config/database.yml +16 -0
- data/doc/Array.html +116 -0
- data/doc/Array/ArrayLoggerPersistor.html +152 -0
- data/doc/Lims.html +114 -0
- data/doc/Lims/Core.html +178 -0
- data/doc/Lims/Core/Action.html +91 -0
- data/doc/Lims/Core/Actions.html +116 -0
- data/doc/Lims/Core/Actions/Action.html +216 -0
- data/doc/Lims/Core/Actions/Action/AfterEval.html +853 -0
- data/doc/Lims/Core/Actions/Action/InvalidParameters.html +268 -0
- data/doc/Lims/Core/Actions/ActionGroup.html +196 -0
- data/doc/Lims/Core/Actions/ActionGroup/AfterEval.html +315 -0
- data/doc/Lims/Core/Actions/BulkAction.html +224 -0
- data/doc/Lims/Core/Actions/BulkAction/AfterEval.html +253 -0
- data/doc/Lims/Core/Actions/TestActionGroup.html +101 -0
- data/doc/Lims/Core/Actions/TestActionGroup/Action.html +133 -0
- data/doc/Lims/Core/Actions/TestActionGroup/ActionGroup.html +127 -0
- data/doc/Lims/Core/Base.html +287 -0
- data/doc/Lims/Core/Base/AccessibleViaSuper.html +252 -0
- data/doc/Lims/Core/Base/ClassMethod.html +177 -0
- data/doc/Lims/Core/Base/HashString.html +177 -0
- data/doc/Lims/Core/Base/IsArrayOf.html +606 -0
- data/doc/Lims/Core/Base/State.html +130 -0
- data/doc/Lims/Core/Organization.html +113 -0
- data/doc/Lims/Core/Organization/Batch.html +106 -0
- data/doc/Lims/Core/Persistence.html +267 -0
- data/doc/Lims/Core/Persistence/ComparisonFilter.html +318 -0
- data/doc/Lims/Core/Persistence/Filter.html +252 -0
- data/doc/Lims/Core/Persistence/IdentityMap.html +409 -0
- data/doc/Lims/Core/Persistence/IdentityMap/Class.html +144 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateError.html +126 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateIdError.html +136 -0
- data/doc/Lims/Core/Persistence/IdentityMap/DuplicateObjectError.html +136 -0
- data/doc/Lims/Core/Persistence/IdentityMapClass.html +133 -0
- data/doc/Lims/Core/Persistence/Logger.html +105 -0
- data/doc/Lims/Core/Persistence/Logger/Persistor.html +334 -0
- data/doc/Lims/Core/Persistence/Logger/Session.html +452 -0
- data/doc/Lims/Core/Persistence/Logger/Store.html +470 -0
- data/doc/Lims/Core/Persistence/MessageBus.html +871 -0
- data/doc/Lims/Core/Persistence/MessageBus/ConnectionError.html +123 -0
- data/doc/Lims/Core/Persistence/MessageBus/InvalidSettingsError.html +122 -0
- data/doc/Lims/Core/Persistence/MultiCriteriaFilter.html +293 -0
- data/doc/Lims/Core/Persistence/PersistAssociationTrait.html +91 -0
- data/doc/Lims/Core/Persistence/PersistableTrait.html +91 -0
- data/doc/Lims/Core/Persistence/Persistor.html +3072 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateError.html +205 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateIdError.html +147 -0
- data/doc/Lims/Core/Persistence/Persistor/DuplicateObjectError.html +147 -0
- data/doc/Lims/Core/Persistence/PersistorTrait.html +91 -0
- data/doc/Lims/Core/Persistence/ResourceState.html +1738 -0
- data/doc/Lims/Core/Persistence/Search.html +269 -0
- data/doc/Lims/Core/Persistence/Search/CreateSearch.html +251 -0
- data/doc/Lims/Core/Persistence/Search/SearchPersistor.html +240 -0
- data/doc/Lims/Core/Persistence/Search/SearchSequelPersistor.html +396 -0
- data/doc/Lims/Core/Persistence/Sequel.html +117 -0
- data/doc/Lims/Core/Persistence/Sequel/Filters.html +462 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest.html +101 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest/Name.html +137 -0
- data/doc/Lims/Core/Persistence/Sequel/ForTest/Name/NamePersitor.html +143 -0
- data/doc/Lims/Core/Persistence/Sequel/Migrations.html +266 -0
- data/doc/Lims/Core/Persistence/Sequel/Persistor.html +665 -0
- data/doc/Lims/Core/Persistence/Sequel/Session.html +501 -0
- data/doc/Lims/Core/Persistence/Sequel/Store.html +417 -0
- data/doc/Lims/Core/Persistence/Session.html +2751 -0
- data/doc/Lims/Core/Persistence/Session/UnmanagedObjectError.html +111 -0
- data/doc/Lims/Core/Persistence/StateGroup.html +696 -0
- data/doc/Lims/Core/Persistence/StateList.html +498 -0
- data/doc/Lims/Core/Persistence/Store.html +695 -0
- data/doc/Lims/Core/Persistence/UuidResource.html +1044 -0
- data/doc/Lims/Core/Persistence/UuidResource/InvalidUuidError.html +111 -0
- data/doc/Lims/Core/Persistence/UuidResource/UuidResourcePersistor.html +337 -0
- data/doc/Lims/Core/Persistence/Uuidable.html +799 -0
- data/doc/Lims/Core/Persistor.html +320 -0
- data/doc/Lims/Core/Resource.html +165 -0
- data/doc/Object.html +228 -0
- data/doc/SessionSpec.html +101 -0
- data/doc/SessionSpec/Model.html +279 -0
- data/doc/SessionSpec/Model/ModelPersistor.html +327 -0
- data/doc/_index.html +732 -0
- data/doc/class_list.html +47 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +55 -0
- data/doc/css/style.css +322 -0
- data/doc/file.README.html +127 -0
- data/doc/file_list.html +49 -0
- data/doc/frames.html +13 -0
- data/doc/index.html +127 -0
- data/doc/js/app.js +205 -0
- data/doc/js/full_list.js +167 -0
- data/doc/js/jquery.js +16 -0
- data/doc/method_list.html +1894 -0
- data/doc/top-level-namespace.html +100 -0
- data/lib/common.rb +18 -0
- data/lib/lims-core.rb +29 -0
- data/lib/lims-core/actions.rb +10 -0
- data/lib/lims-core/actions/action.rb +185 -0
- data/lib/lims-core/actions/action_group.rb +54 -0
- data/lib/lims-core/actions/bulk_action.rb +65 -0
- data/lib/lims-core/base.rb +132 -0
- data/lib/lims-core/helpers.rb +41 -0
- data/lib/lims-core/persistence.rb +15 -0
- data/lib/lims-core/persistence/comparison_filter.rb +54 -0
- data/lib/lims-core/persistence/filter.rb +23 -0
- data/lib/lims-core/persistence/identity_map.rb +55 -0
- data/lib/lims-core/persistence/logger/all.rb +5 -0
- data/lib/lims-core/persistence/logger/persistor.rb +35 -0
- data/lib/lims-core/persistence/logger/session.rb +30 -0
- data/lib/lims-core/persistence/logger/store.rb +37 -0
- data/lib/lims-core/persistence/message_bus.rb +131 -0
- data/lib/lims-core/persistence/multi_criteria_filter.rb +50 -0
- data/lib/lims-core/persistence/persist_association_trait.rb +96 -0
- data/lib/lims-core/persistence/persistable_trait.rb +150 -0
- data/lib/lims-core/persistence/persistor.rb +495 -0
- data/lib/lims-core/persistence/resource_state.rb +157 -0
- data/lib/lims-core/persistence/search.rb +3 -0
- data/lib/lims-core/persistence/search/all.rb +3 -0
- data/lib/lims-core/persistence/search/create_search.rb +55 -0
- data/lib/lims-core/persistence/search/search_persistor.rb +45 -0
- data/lib/lims-core/persistence/search/search_sequel_persistor.rb +40 -0
- data/lib/lims-core/persistence/sequel.rb +14 -0
- data/lib/lims-core/persistence/sequel/filters.rb +106 -0
- data/lib/lims-core/persistence/sequel/migrations.rb +14 -0
- data/lib/lims-core/persistence/sequel/migrations/add_audit_tables.rb +147 -0
- data/lib/lims-core/persistence/sequel/migrations/initial.rb +156 -0
- data/lib/lims-core/persistence/sequel/persistor.rb +200 -0
- data/lib/lims-core/persistence/sequel/session.rb +136 -0
- data/lib/lims-core/persistence/sequel/store.rb +37 -0
- data/lib/lims-core/persistence/session.rb +409 -0
- data/lib/lims-core/persistence/state_group.rb +97 -0
- data/lib/lims-core/persistence/state_list.rb +56 -0
- data/lib/lims-core/persistence/store.rb +73 -0
- data/lib/lims-core/persistence/uuid_resource.rb +115 -0
- data/lib/lims-core/persistence/uuid_resource_persistor.rb +43 -0
- data/lib/lims-core/persistence/uuidable.rb +107 -0
- data/lib/lims-core/resource.rb +21 -0
- data/lib/lims-core/subclass_tracker.rb +30 -0
- data/lib/lims-core/version.rb +5 -0
- data/lims-core.gemspec +40 -0
- data/makefile +52 -0
- data/showoff/core-2012-06-11/core/01_slide.md +237 -0
- data/showoff/core-2012-06-11/core/02_slide.md +110 -0
- data/showoff/core-2012-06-11/custom.css +44 -0
- data/showoff/core-2012-06-11/main/01_slide.md +53 -0
- data/showoff/core-2012-06-11/showoff.json +10 -0
- data/showoff/core-2012-06-11/tp1.tpl +1 -0
- data/spec/actions/action_group_spec.rb +39 -0
- data/spec/actions/spec_helper.rb +1 -0
- data/spec/persistence/identity_map_spec.rb +55 -0
- data/spec/persistence/logger/spec_helper.rb +7 -0
- data/spec/persistence/logger/store_spec.rb +48 -0
- data/spec/persistence/message_bus_spec.rb +76 -0
- data/spec/persistence/sequel/session_spec.rb +125 -0
- data/spec/persistence/sequel/spec_helper.rb +39 -0
- data/spec/persistence/sequel/store_shared.rb +25 -0
- data/spec/persistence/sequel/store_spec.rb +22 -0
- data/spec/persistence/session_spec.rb +199 -0
- data/spec/persistence/spec_helper.rb +2 -0
- data/spec/persistence/uuid_resource_spec.rb +80 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/subclass_tracker_sperc.rb +62 -0
- data/utils/constant_tree.rb +29 -0
- data/utils/stack.rb +48 -0
- metadata +402 -0
@@ -0,0 +1,147 @@
|
|
1
|
+
module Lims::Core::Persistence::Sequel::Migrations
|
2
|
+
module AddAuditTables
|
3
|
+
def self.migration(exclude_tables={}, additional_tables_to_update=[])
|
4
|
+
[:schema_info, :sessions, :primary_keys].each do |table|
|
5
|
+
exclude_tables[table] = true
|
6
|
+
end
|
7
|
+
this = self
|
8
|
+
Proc.new do
|
9
|
+
next unless defined?(DB)
|
10
|
+
table_names = []
|
11
|
+
change do
|
12
|
+
if additional_tables_to_update.size == 0
|
13
|
+
# Create session table
|
14
|
+
create_table :sessions do
|
15
|
+
primary_key :id
|
16
|
+
String :user
|
17
|
+
String :backend_application_id
|
18
|
+
String :parameters, :text => true
|
19
|
+
boolean :success
|
20
|
+
timestamp :start_time
|
21
|
+
DateTime :end_time
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#create migration session
|
26
|
+
self << <<-EOS
|
27
|
+
INSERT INTO sessions(user, backend_application_id)
|
28
|
+
VALUES('admin', 'lims-core');
|
29
|
+
EOS
|
30
|
+
|
31
|
+
session_id = DB[:sessions].order(:id).last[:id]
|
32
|
+
|
33
|
+
tables_to_update = additional_tables_to_update.size == 0 ? DB.tables : additional_tables_to_update
|
34
|
+
tables_to_update.each do |table_name|
|
35
|
+
next if exclude_tables.include?(table_name)
|
36
|
+
table_names << table_name
|
37
|
+
table = DB[table_name]
|
38
|
+
|
39
|
+
# extend all tables with revision_id
|
40
|
+
if DB[table].columns.include?(:revision) == false
|
41
|
+
alter_table table_name do
|
42
|
+
add_column :revision, Integer, :default => 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# create history table
|
48
|
+
revision_table = "#{table_name}_revision"
|
49
|
+
self << <<-EOS
|
50
|
+
CREATE TABLE #{revision_table} AS
|
51
|
+
SELECT *, 'initial' AS `action`, #{session_id} as session_id
|
52
|
+
FROM #{table_name}
|
53
|
+
EOS
|
54
|
+
|
55
|
+
puts "adding key to #{revision_table}"
|
56
|
+
alter_table revision_table do
|
57
|
+
add_primary_key :internal_id
|
58
|
+
add_index [:id, :revision], :unique => true
|
59
|
+
add_index [:id, :session_id], :unique => true
|
60
|
+
add_foreign_key [:session_id], :sessions, :key => :id
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
# Create trigger
|
65
|
+
trigger_name = "maintain_#{table_name}_on_insert"
|
66
|
+
self << "DROP TRIGGER IF EXISTS #{trigger_name};"
|
67
|
+
trigger_code = <<-EOT
|
68
|
+
|
69
|
+
CREATE TRIGGER #{trigger_name} AFTER INSERT ON #{table_name}
|
70
|
+
FOR EACH ROW
|
71
|
+
BEGIN
|
72
|
+
#{this.insert_into_revision(table, revision_table, :insert)}
|
73
|
+
EOT
|
74
|
+
|
75
|
+
puts trigger_code
|
76
|
+
self << trigger_code
|
77
|
+
|
78
|
+
trigger_name = "maintain_#{table_name}_on_update"
|
79
|
+
self << "DROP TRIGGER IF EXISTS #{trigger_name};"
|
80
|
+
trigger_code = <<-EOT
|
81
|
+
|
82
|
+
CREATE TRIGGER #{trigger_name} BEFORE UPDATE ON #{table_name}
|
83
|
+
FOR EACH ROW
|
84
|
+
BEGIN
|
85
|
+
# Update the revision number
|
86
|
+
SET NEW.revision = OLD.revision+1;
|
87
|
+
# Update the revision table
|
88
|
+
#{this.insert_into_revision(table, revision_table, :update)}
|
89
|
+
EOT
|
90
|
+
|
91
|
+
puts trigger_code
|
92
|
+
self << trigger_code
|
93
|
+
|
94
|
+
trigger_name = "maintain_#{table_name}_on_delete"
|
95
|
+
self << "DROP TRIGGER IF EXISTS #{trigger_name};"
|
96
|
+
trigger_code = <<-EOT
|
97
|
+
|
98
|
+
CREATE TRIGGER #{trigger_name} BEFORE DELETE ON #{table_name}
|
99
|
+
FOR EACH ROW
|
100
|
+
BEGIN
|
101
|
+
#{this.insert_into_revision(table, revision_table, :delete)}
|
102
|
+
EOT
|
103
|
+
|
104
|
+
puts trigger_code
|
105
|
+
self << trigger_code
|
106
|
+
end
|
107
|
+
|
108
|
+
if additional_tables_to_update.size > 0
|
109
|
+
self << "DROP VIEW IF EXISTS revisions"
|
110
|
+
end
|
111
|
+
|
112
|
+
revision_tables = DB.tables.map { |table| table unless table.match(/revision/) }.compact
|
113
|
+
revision_tables -= exclude_tables.keys
|
114
|
+
|
115
|
+
view_code = "CREATE VIEW revisions AS " + revision_tables.map do |table_name|
|
116
|
+
revision_table = "#{table_name}_revision"
|
117
|
+
%Q{ SELECT '#{table_name}' AS revision_table,
|
118
|
+
id,
|
119
|
+
action,
|
120
|
+
session_id
|
121
|
+
FROM #{revision_table}
|
122
|
+
|
123
|
+
}
|
124
|
+
end.join(' UNION ')
|
125
|
+
|
126
|
+
puts view_code
|
127
|
+
self << view_code
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def self.insert_into_revision(table, revision_table, type)
|
133
|
+
%Q{ INSERT INTO #{revision_table}
|
134
|
+
SET #{
|
135
|
+
if type == :delete
|
136
|
+
'id = OLD.id, revision = OLD.revision+1'
|
137
|
+
else
|
138
|
+
table.columns.map { |c| "`#{c}` = NEW.#{c}" }.join(', ')
|
139
|
+
end
|
140
|
+
},
|
141
|
+
`action` = '#{type}',
|
142
|
+
`session_id` = @current_session_id;
|
143
|
+
END;
|
144
|
+
}
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# This module should not be require on it's own.
|
2
|
+
# Require migrations instead.
|
3
|
+
|
4
|
+
module Lims::Core::Persistence::Sequel::Migrations
|
5
|
+
Initial = Proc.new do
|
6
|
+
change do
|
7
|
+
create_table :samples do
|
8
|
+
primary_key :id
|
9
|
+
String :name
|
10
|
+
end
|
11
|
+
|
12
|
+
create_table :oligos do
|
13
|
+
primary_key :id
|
14
|
+
String :sequence
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :aliquots do
|
18
|
+
primary_key :id
|
19
|
+
foreign_key :sample_id, :samples, :key => :id
|
20
|
+
foreign_key :tag_id, :oligos, :key => :id
|
21
|
+
Integer :quantity
|
22
|
+
String :type
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table :flowcells do
|
26
|
+
primary_key :id
|
27
|
+
Integer :number_of_lanes
|
28
|
+
end
|
29
|
+
|
30
|
+
create_table :lanes do
|
31
|
+
#primary_key :flowcell_id, :position
|
32
|
+
primary_key :id
|
33
|
+
foreign_key :flowcell_id, :flowcells, :key => :id
|
34
|
+
Integer :position
|
35
|
+
foreign_key :aliquot_id, :aliquots, :key => :id
|
36
|
+
end
|
37
|
+
|
38
|
+
create_table :tubes do
|
39
|
+
primary_key :id
|
40
|
+
end
|
41
|
+
|
42
|
+
create_table :tube_aliquots do
|
43
|
+
primary_key :id
|
44
|
+
Integer :tube_id
|
45
|
+
foreign_key :aliquot_id, :aliquots, :key => :id
|
46
|
+
end
|
47
|
+
|
48
|
+
create_table :spin_columns do
|
49
|
+
primary_key :id
|
50
|
+
end
|
51
|
+
|
52
|
+
create_table :spin_column_aliquots do
|
53
|
+
primary_key :id
|
54
|
+
Integer :spin_column_id
|
55
|
+
foreign_key :aliquot_id, :aliquots, :key => :id
|
56
|
+
end
|
57
|
+
|
58
|
+
create_table :plates do
|
59
|
+
primary_key :id
|
60
|
+
Integer :number_of_rows
|
61
|
+
Integer :number_of_columns
|
62
|
+
end
|
63
|
+
|
64
|
+
create_table :wells do
|
65
|
+
primary_key :id
|
66
|
+
foreign_key :plate_id, :plates, :key => :id
|
67
|
+
Integer :position
|
68
|
+
foreign_key :aliquot_id, :aliquots, :key => :id
|
69
|
+
end
|
70
|
+
|
71
|
+
create_table :tube_racks do
|
72
|
+
primary_key :id
|
73
|
+
Integer :number_of_rows
|
74
|
+
Integer :number_of_columns
|
75
|
+
end
|
76
|
+
|
77
|
+
create_table :tube_rack_slots do
|
78
|
+
primary_key :id
|
79
|
+
foreign_key :tube_rack_id, :tube_racks, :key => :id
|
80
|
+
Integer :position
|
81
|
+
foreign_key :tube_id, :tubes, :key=> :id
|
82
|
+
end
|
83
|
+
|
84
|
+
create_table :tag_groups do
|
85
|
+
primary_key :id
|
86
|
+
String :name
|
87
|
+
end
|
88
|
+
|
89
|
+
create_table :tag_group_associations do
|
90
|
+
primary_key :id
|
91
|
+
foreign_key :tag_group_id, :tag_groups, :key => :id
|
92
|
+
Integer :position
|
93
|
+
foreign_key :oligo_id, :oligos, :key => :id
|
94
|
+
end
|
95
|
+
|
96
|
+
create_table :uuid_resources do
|
97
|
+
primary_key :id
|
98
|
+
String :uuid, :fixed => true, :size => 64
|
99
|
+
String :model_class
|
100
|
+
Integer :key
|
101
|
+
end
|
102
|
+
|
103
|
+
create_table :users do
|
104
|
+
primary_key :id
|
105
|
+
end
|
106
|
+
|
107
|
+
create_table :studies do
|
108
|
+
primary_key :id
|
109
|
+
end
|
110
|
+
|
111
|
+
create_table :orders do
|
112
|
+
primary_key :id
|
113
|
+
foreign_key :creator_id, :users, :key => :id
|
114
|
+
|
115
|
+
String :pipeline
|
116
|
+
String :parameters
|
117
|
+
String :status
|
118
|
+
blob :state
|
119
|
+
foreign_key :study_id, :studies, :key => :id
|
120
|
+
String :cost_code
|
121
|
+
end
|
122
|
+
|
123
|
+
create_table :items do
|
124
|
+
primary_key :id
|
125
|
+
foreign_key :order_id, :orders, :key => :id
|
126
|
+
String :role
|
127
|
+
foreign_key :resource_id, :uuid_resources, :key => :id
|
128
|
+
String :uuid, :fixed => true, :size => 64
|
129
|
+
String :status
|
130
|
+
Integer :iteration, :default => 0
|
131
|
+
end
|
132
|
+
|
133
|
+
create_table :searches do
|
134
|
+
primary_key :id
|
135
|
+
String :description
|
136
|
+
String :filter_type
|
137
|
+
String :model
|
138
|
+
blob :filter_parameters
|
139
|
+
end
|
140
|
+
|
141
|
+
create_table :labellables do
|
142
|
+
primary_key :id
|
143
|
+
String :name
|
144
|
+
String :type
|
145
|
+
end
|
146
|
+
|
147
|
+
create_table :labels do
|
148
|
+
primary_key :id
|
149
|
+
foreign_key :labellable_id, :labellables, :key => :id
|
150
|
+
String :type
|
151
|
+
String :position
|
152
|
+
String :value
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# vi: ts=2:sts=2:et:sw=2 spell:spelllang=en
|
2
|
+
|
3
|
+
require 'lims-core/persistence/identity_map'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
require 'lims-core/persistence/sequel/filters'
|
6
|
+
|
7
|
+
module Lims::Core
|
8
|
+
module Persistence
|
9
|
+
module Sequel
|
10
|
+
# Mixin giving extended the persistor classes with
|
11
|
+
# the Sequel (load/save) behavior.
|
12
|
+
module Persistor
|
13
|
+
|
14
|
+
include Filters
|
15
|
+
|
16
|
+
def self.included(klass)
|
17
|
+
klass.class_eval do
|
18
|
+
# @return [String] the name of SQL table.
|
19
|
+
def self.table_name
|
20
|
+
@table_name ||= parent_scope.name.split('::').last.pluralize.snakecase.to_sym
|
21
|
+
end
|
22
|
+
# The Sequel::Dataset.
|
23
|
+
# Corresponds to table.
|
24
|
+
# @param [Sequel::Session] session
|
25
|
+
# @return [::Sequel::Dataset]
|
26
|
+
def self.dataset(session)
|
27
|
+
session.database[self.table_name]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(session_or_persistor, dataset=nil, *args, &block )
|
33
|
+
id_to_state, object_to_state = [nil, nil]
|
34
|
+
case session_or_persistor
|
35
|
+
when Sequel::Persistor
|
36
|
+
# We link the session and the identity map variables,
|
37
|
+
# so that object loaded via this persistor can be found (and their id)
|
38
|
+
# through the origial persistor.
|
39
|
+
# Hack to get those private variables.
|
40
|
+
session, identity_map_parameters = session_or_persistor.instance_eval do
|
41
|
+
[@session, [@id_to_state, @object_to_state]]
|
42
|
+
end
|
43
|
+
super(session, *args, &block)
|
44
|
+
@id_to_state , @object_to_state = identity_map_parameters
|
45
|
+
else Session
|
46
|
+
super(session_or_persistor, *args, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
@dataset = dataset || self.class.dataset(@session)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String] the name of the table
|
53
|
+
def table_name
|
54
|
+
self.class.table_name
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# The Sequel::Dataset.
|
59
|
+
# Corresponds to a table.
|
60
|
+
# @return [::Sequel::Dataset]
|
61
|
+
def dataset
|
62
|
+
@dataset
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# Returns the number of object in the store
|
67
|
+
# @return [Fixnum]
|
68
|
+
def count
|
69
|
+
dataset.count
|
70
|
+
end
|
71
|
+
|
72
|
+
protected
|
73
|
+
# load a slice.
|
74
|
+
def for_each_in_slice(start, length)
|
75
|
+
return if length == 0
|
76
|
+
dataset.order(primary_key).limit(length, start).each do |h|
|
77
|
+
yield(h)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# The primary key
|
82
|
+
# @return [Symbol]
|
83
|
+
def primary_key()
|
84
|
+
:id
|
85
|
+
end
|
86
|
+
|
87
|
+
def qualified_key
|
88
|
+
@qualified_key ||= ::Sequel.qualify(self.class.table_name, primary_key)
|
89
|
+
end
|
90
|
+
|
91
|
+
def bulk_load(ids, *params, &block)
|
92
|
+
dataset.filter(qualified_key => ids.map(&:id)).all(&block)
|
93
|
+
end
|
94
|
+
public :bulk_load
|
95
|
+
|
96
|
+
def ids_for(criteria)
|
97
|
+
dataset.select(qualified_key).filter(criteria).map { |h| h[primary_key] }
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
# Save a raw object, i.e. the object
|
102
|
+
# attributes excluding any associations.
|
103
|
+
# @param [Resource] object the object
|
104
|
+
# @return [Fixnum, nil] the Id if save successful
|
105
|
+
def insert(state, *params)
|
106
|
+
# use prepared statement for everything
|
107
|
+
# We only need it at the moment as a workaround for saving the UUID
|
108
|
+
# So we might in the future either move it to a UuidResourcePersistor
|
109
|
+
# or cached it by attributes
|
110
|
+
# @todo benchmark against normal insert
|
111
|
+
attributes = filter_attributes_on_save(state.resource.attributes, *params)
|
112
|
+
dataset.insert(attributes).tap { |id| state.id = id }
|
113
|
+
end
|
114
|
+
|
115
|
+
def bulk_insert(states, *params)
|
116
|
+
#super(states, *params)
|
117
|
+
bulk_insert_multi(states, *params)
|
118
|
+
#bulk_insert_prepared(states, *params)
|
119
|
+
end
|
120
|
+
public :bulk_insert
|
121
|
+
|
122
|
+
def bulk_insert_prepared(states, *params)
|
123
|
+
# use prepared statement for everything
|
124
|
+
# We only need it at the moment as a workaround for saving the UUID
|
125
|
+
# So we might in the future either move it to a UuidResourcePersistor
|
126
|
+
# or cached it by attributes
|
127
|
+
# @todo benchmark against normal insert
|
128
|
+
attributes = filter_attributes_on_save(states.first.resource.attributes, *params)
|
129
|
+
statement_name = :"#{table_name}__save_raw"
|
130
|
+
dataset.prepare(:insert, statement_name, attributes.keys.mash { |k| [k, :"$#{k}"] })
|
131
|
+
|
132
|
+
states.each do |state|
|
133
|
+
attributes = filter_attributes_on_save(state.resource.attributes, *params)
|
134
|
+
@session.database.call(statement_name, attributes)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# @todo
|
139
|
+
def bulk_insert_multi(states, *params)
|
140
|
+
free_ids = get_next_available_ids(states.size)
|
141
|
+
states.inject(0) { |i,s| s.id = free_ids[i]; i+1 }
|
142
|
+
attributes = states.map { |state| filter_attributes_on_save(state.resource.attributes, *params).merge(primary_key => state.id) }
|
143
|
+
dataset.multi_insert(attributes)
|
144
|
+
end
|
145
|
+
|
146
|
+
def bulk_update_raw_attributes(attributes, *params)
|
147
|
+
return dataset.on_duplicate_key_update.multi_insert(attributes) if dataset.respond_to? :on_duplicate_key_update
|
148
|
+
attributes.each { |att| dataset.filter(primary_key => att.delete(primary_key)).update(att) }
|
149
|
+
end
|
150
|
+
|
151
|
+
def bulk_delete_raw(ids, *params)
|
152
|
+
dataset.filter(primary_key => ids).delete
|
153
|
+
end
|
154
|
+
|
155
|
+
def delete_raw(objec, id, *params)
|
156
|
+
id.tap do
|
157
|
+
dataset.filter(primary_key => id).delete
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Return a sequence of free ids, ready to be inserted.
|
162
|
+
# The last used id corresponding to each table is store in a special table.
|
163
|
+
# We need to lock the table to avoid to thread or process to 'use' the same ids.
|
164
|
+
# @param [Integer] quantity
|
165
|
+
# @return [Array<Integer>]
|
166
|
+
def get_next_available_ids(quantity = 1)
|
167
|
+
@session.lock(dataset.from(:primary_keys), true) do |primary_keys|
|
168
|
+
current_key_row = primary_keys.first(:table_name => table_name.to_s)
|
169
|
+
if current_key_row
|
170
|
+
current_key = current_key_row[:current_key]
|
171
|
+
else
|
172
|
+
# We lock again primary keys and the dataset, because doing a new MySQL lock
|
173
|
+
# unlock the previous locked tables.
|
174
|
+
# Also we need to check again at this point if the primary_keys table has not
|
175
|
+
# been updated by another request. In fact, doing the lock below unlock first
|
176
|
+
# primary_keys table, and then lock it again with dataset table. If during this laps
|
177
|
+
# of time (between unlock and lock), another request try to initialize primary_keys table,
|
178
|
+
# we could have duplicate rows.
|
179
|
+
current_key = @session.lock([primary_keys, dataset]) do |primary_keys_dataset, resource_dataset|
|
180
|
+
primary_key_row = primary_keys_dataset.first(:table_name => table_name.to_s)
|
181
|
+
if primary_key_row
|
182
|
+
primary_key_row[:current_key]
|
183
|
+
else
|
184
|
+
last_id = resource_dataset.max(primary_key) || 0
|
185
|
+
primary_keys_dataset.insert(:table_name => table_name.to_s, :current_key => last_id)
|
186
|
+
last_id
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
new_current_key = current_key + quantity
|
192
|
+
primary_keys.where(:table_name => table_name.to_s).update(:current_key => new_current_key)
|
193
|
+
(current_key+1..new_current_key).to_a
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
require 'lims-core/persistence/sequel/filters'
|