Class: Mxrb::Runtime::SQLiteStore
- Inherits:
-
Object
- Object
- Mxrb::Runtime::SQLiteStore
- Defined in:
- lib/mxrb/runtime/sqlite_store.rb
Overview
SQLite-backed replacement for Runtime::Native::Store. ObjectValue stays the public value type, so the native microflow interpreter can use this store without a persistence-specific object abstraction. rubocop:disable Metrics/AbcSize, Metrics/ClassLength, Metrics/CyclomaticComplexity rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
Constant Summary collapse
- EVENTS =
%i[ before_create after_create before_update after_update before_commit after_commit before_delete after_delete ].freeze
- SYSTEM_MEMBERS =
{ owner: %w[Owner __owner_id], created_date: %w[createdDate __created_at], changed_date: %w[changedDate __changed_at], changed_by: %w[changedBy __changed_by_id] }.freeze
Instance Attribute Summary collapse
-
#database ⇒ Object
readonly
Returns the value of attribute database.
-
#schema ⇒ Object
readonly
Returns the value of attribute schema.
Instance Method Summary collapse
- #begin_transaction ⇒ Object
- #close ⇒ Object
- #commit(value = nil, events: true) ⇒ Object
- #count(entity, predicate = nil) ⇒ Object
- #create(entity, events: true) ⇒ Object
- #delete(value, events: true) ⇒ Object
- #find(entity, id) ⇒ Object
-
#initialize(project, path: ':memory:', defaults: {}, hooks: {}, allow_destructive: false) ⇒ SQLiteStore
constructor
A new instance of SQLiteStore.
- #on(event_or_entity, positional_event = nil, entity: nil, &block) ⇒ Object
- #release_cache! ⇒ Object
- #restore(snapshot) ⇒ Object
- #retrieve(entity) ⇒ Object
- #retrieve_association(association, start) ⇒ Object
- #rollback(value = nil) ⇒ Object
- #snapshot ⇒ Object
- #transaction ⇒ Object
Constructor Details
#initialize(project, path: ':memory:', defaults: {}, hooks: {}, allow_destructive: false) ⇒ SQLiteStore
Returns a new instance of SQLiteStore.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 28 def initialize(project, path: ':memory:', defaults: {}, hooks: {}, allow_destructive: false) @database = SQLite3::Database.new(path.to_s) @database.results_as_hash = true @database.execute('PRAGMA foreign_keys = ON') @database.busy_timeout = 5_000 @schema = SchemaMigrator.new( project, database: @database, allow_destructive: ).tap(&:migrate!).schema @defaults = defaults.transform_keys(&:to_s) transient_defaults = project.modules.flat_map do |mod| mod.entities.select { _1.persistable == false }.map do |entity| ["#{mod.name}.#{entity.name}", defaults.fetch("#{mod.name}.#{entity.name}", {})] end end.to_h @transient_entities = transient_defaults.keys.freeze @transient = Native::Store.new(defaults: transient_defaults) @hooks = Hash.new { |values, event| values[event] = [] } @identity = {} @persisted = {} @staged_new = {} @sequence_values = {} @manual_transaction = false hooks.each { |event, callbacks| Array(callbacks).each { on(event, &_1) } } end |
Instance Attribute Details
#database ⇒ Object (readonly)
Returns the value of attribute database.
26 27 28 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 26 def database @database end |
#schema ⇒ Object (readonly)
Returns the value of attribute schema.
26 27 28 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 26 def schema @schema end |
Instance Method Details
#begin_transaction ⇒ Object
177 178 179 180 181 182 183 184 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 177 def begin_transaction raise SQLite3::SQLException, 'transaction already active' if @manual_transaction @manual_snapshot = transaction_snapshot database.execute('BEGIN IMMEDIATE') @manual_transaction = true self end |
#close ⇒ Object
237 238 239 240 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 237 def close rollback if @manual_transaction database.close unless database.closed? end |
#commit(value = nil, events: true) ⇒ Object
168 169 170 171 172 173 174 175 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 168 def commit(value = nil, events: true) values = value.nil? ? dirty_values : Array(value) transient, persistent = values.compact.partition { transient?(_1.entity) } @transient.commit(transient, events:) unless transient.empty? atomic { persistent.each { persist_update(_1, events:) } } finish_manual_transaction if value.nil? && @manual_transaction value end |
#count(entity, predicate = nil) ⇒ Object
158 159 160 161 162 163 164 165 166 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 158 def count(entity, predicate = nil) return @transient.count(transient_name(entity), predicate) if transient?(entity) return retrieve(entity).count { predicate.call(_1) } if predicate definition = schema.entity(entity) durable = database.get_first_value("SELECT COUNT(*) FROM #{quote(definition.table)}").to_i durable + @staged_new.values.count { _1.entity == definition.name } end |
#create(entity, events: true) ⇒ Object
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 70 def create(entity, events: true) return @transient.create(transient_name(entity), events:) if transient?(entity) definition = schema.entity(entity) value = nil atomic do members = defaults_for(definition) value = Native::ObjectValue.new(entity: definition.name, id: SecureRandom.uuid, members:) run_hooks(:before_create, value) if events stage(value) run_hooks(:after_create, value) if events end value rescue StandardError @identity.delete(value&.id) @persisted.delete(value&.id) @staged_new.delete(value&.id) raise end |
#delete(value, events: true) ⇒ Object
151 152 153 154 155 156 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 151 def delete(value, events: true) transient, persistent = Array(value).compact.partition { transient?(_1.entity) } @transient.delete(transient, events:) unless transient.empty? persistent.each { delete_one(_1, events:) } nil end |
#find(entity, id) ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 101 def find(entity, id) return @transient.find(transient_name(entity), id) if transient?(entity) definition = schema.entity(entity) staged = @staged_new[id.to_s] return staged if staged&.entity == definition.name row = database.get_first_row( "SELECT * FROM #{quote(definition.table)} WHERE id = ?", id.to_s ) return unless row materialize(definition, row).tap { load_direct_associations([_1]) } end |
#on(event_or_entity, positional_event = nil, entity: nil, &block) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 53 def on(event_or_entity, positional_event = nil, entity: nil, &block) entity ||= event_or_entity if positional_event event = positional_event || event_or_entity key = event.to_sym raise ArgumentError, "unsupported lifecycle event #{event}" unless EVENTS.include?(key) raise ArgumentError, 'lifecycle hook requires a block' unless block @hooks[key] << [entity&.to_s, block] @transient.on(transient_name(entity), key) { |value| block.call(value, self) } if entity && transient?(entity) unless entity @transient_entities.each do |name| @transient.on(name, key) { |value| block.call(value, self) } end end self end |
#release_cache! ⇒ Object
116 117 118 119 120 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 116 def release_cache! @identity = @staged_new.dup @persisted = {} self end |
#restore(snapshot) ⇒ Object
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 221 def restore(snapshot) unit_of_work = snapshot['__mxrb_uow__'] atomic do schema.associations.each { database.execute("DELETE FROM #{quote(_1.table)}") } schema.entities.each { database.execute("DELETE FROM #{quote(_1.table)}") } clear_cache if unit_of_work restore_unit_of_work(snapshot, unit_of_work) else restore_legacy_snapshot(snapshot) end end @transient.restore(snapshot.fetch('__mxrb_transient__', @transient.snapshot)) self end |
#retrieve(entity) ⇒ Object
90 91 92 93 94 95 96 97 98 99 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 90 def retrieve(entity) return @transient.retrieve(transient_name(entity)) if transient?(entity) definition = schema.entity(entity) rows = database.execute("SELECT * FROM #{quote(definition.table)} ORDER BY rowid") values = rows.map { materialize(definition, _1) } values.concat(@staged_new.values.select { _1.entity == definition.name }) load_direct_associations(values) values.uniq(&:id) end |
#retrieve_association(association, start) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 122 def retrieve_association(association, start) return [] unless start definition = begin schema.association(association) rescue ArgumentError return @transient.retrieve_association(association, start) if transient?(start.entity) raise end return volatile_association_values(definition, association, start) if hybrid_association?(definition) return @transient.retrieve_association(association, start) if transient?(start.entity) if start.entity == definition.from_entity ids = database.execute( "SELECT target_id FROM #{quote(definition.table)} WHERE source_id = ? ORDER BY rowid", [start.id] ).map { _1['target_id'] } materialize_ids(definition.to_entity, ids) elsif start.entity == definition.to_entity ids = database.execute( "SELECT source_id FROM #{quote(definition.table)} WHERE target_id = ? ORDER BY rowid", [start.id] ).map { _1['source_id'] } materialize_ids(definition.from_entity, ids) else [] end end |
#rollback(value = nil) ⇒ Object
197 198 199 200 201 202 203 204 205 206 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 197 def rollback(value = nil) return rollback_values(value) unless value.nil? state = @manual_snapshot database.execute('ROLLBACK') if database.transaction_active? @manual_transaction = false @manual_snapshot = nil restore_transaction_snapshot(state) if state self end |
#snapshot ⇒ Object
208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 208 def snapshot result = schema.entities.to_h do |entity| [entity.name, retrieve(entity.name).map { duplicate_value(_1) }] end result['__mxrb_transient__'] = @transient.snapshot result['__mxrb_uow__'] = { persisted: @persisted.transform_values(&:dup), staged_ids: @staged_new.keys, associations: association_snapshot } result end |
#transaction ⇒ Object
186 187 188 189 190 191 192 193 194 195 |
# File 'lib/mxrb/runtime/sqlite_store.rb', line 186 def transaction begin_transaction result = yield self finish_manual_transaction detach_uncommitted result rescue Exception # rubocop:disable Lint/RescueException rollback if @manual_transaction raise end |