Class: RSpecTracer::Storage::SqliteBackend Private
- Inherits:
-
Object
- Object
- RSpecTracer::Storage::SqliteBackend
- Defined in:
- lib/rspec_tracer/storage/sqlite_backend.rb
Overview
This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.
SQLite-on-disk storage backend. Single file ‘cache_path/rspec_tracer.sqlite3` with a normalized 9-table schema so a warm run can materialize only the rows a given field needs (Filter.select hits ~50-500 example_ids out of millions, JsonBackend eagerly read the whole map). Breaks the RAM-scales-with-cache-size curve that JsonBackend cannot escape without a different on-disk shape.
MRI-only. The ‘sqlite3` gem targets MRI’s C API; JRuby’s ‘jdbc-sqlite3` has a different API that this backend does not target in 2.0. Users who select `:sqlite` on JRuby (or on MRI without the `sqlite3` gem in their Gemfile) see the construct raise `SqliteBackendError`, which the Engine’s backend dispatch converts to a warn + cold-run fallback.
No multi-run history. SqliteBackend stores only the latest run; save_graph full-replaces every table inside a single ‘BEGIN IMMEDIATE` transaction. JsonBackend’s ‘cache_retention_local_count` is a no-op here. Users who need rollback history stay on `:json`.
‘journal_mode = MEMORY` so SQLite’s WAL / SHM sidecars do not leak into the user-facing ‘rspec_tracer_cache/` directory (USER_FACING_SURFACE.md section 6 locks the layout; sidecars would surprise debug scripts that walk the dir expecting only documented files). rubocop:disable Metrics/ClassLength
Defined Under Namespace
Classes: SqliteBackendError, SqliteFieldReader
Constant Summary collapse
- DB_FILENAME =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Internal constant.
'rspec_tracer.sqlite3'- JOURNAL_MODE_SQL =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Internal constant.
'PRAGMA journal_mode = MEMORY'- SYNCHRONOUS_SQL =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Internal constant.
'PRAGMA synchronous = NORMAL'- BUSY_TIMEOUT_MS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Two concurrent save_graph calls (parallel_tests workers, fork- based test harnesses) both reach ‘BEGIN IMMEDIATE` and contend on SQLite’s RESERVED write lock. Without busy_timeout, the losing writer raises SQLite3::BusyException immediately. 5000 ms gives ~5x margin over a worst-case 1 s save on large caches (cache_load benchmark p50 ~0.6 s at 500 examples), preserving the storage layer’s “concurrent writers serialize cleanly” contract verified in spec/edge_cases/concurrent_write_spec.rb.
5_000- STATUS_FIELDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Internal constant.
{ interrupted_examples: 'interrupted', flaky_examples: 'flaky', failed_examples: 'failed', pending_examples: 'pending', skipped_examples: 'skipped' }.freeze
- DIGEST_MAP_KINDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Internal constant.
{ boot_set: 'boot', wsi_snapshot: 'wsi', env_snapshot: 'env' }.freeze
- READABLE_FIELDS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Mirrors FIELD_FILENAMES from JsonBackend - the set of Snapshot fields a LazySnapshot reader may ask about. Used by SqliteFieldReader to reject unknown fields before hitting the DB. Kept in step with Snapshot.members minus the envelope.
( %i[all_examples duplicate_examples all_files dependency reverse_dependency examples_coverage env_dependency cache_hit_reason] + STATUS_FIELDS.keys + DIGEST_MAP_KINDS.keys ).freeze
Instance Attribute Summary collapse
-
#cache_path ⇒ Object
readonly
private
Internal attribute.
Instance Method Summary collapse
-
#clear! ⇒ Object
private
Internal method on the tracer pipeline.
-
#initialize(cache_path:, logger: nil) ⇒ SqliteBackend
constructor
private
Internal method on the tracer pipeline.
-
#last_run_id ⇒ Object
private
Internal method on the tracer pipeline.
-
#load_graph(schema_version:) ⇒ Object
private
Internal method on the tracer pipeline.
-
#read_field(field) ⇒ Object
private
Read one field on behalf of a LazySnapshot.
-
#save_graph(snapshot, schema_version:) ⇒ Object
private
Internal method on the tracer pipeline.
-
#transactional_save(&block) ⇒ Object
private
Internal method on the tracer pipeline.
Constructor Details
#initialize(cache_path:, logger: nil) ⇒ SqliteBackend
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Internal method on the tracer pipeline.
136 137 138 139 140 |
# File 'lib/rspec_tracer/storage/sqlite_backend.rb', line 136 def initialize(cache_path:, logger: nil) @cache_path = File.(cache_path) @logger = logger load_sqlite_driver! end |
Instance Attribute Details
#cache_path ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Internal attribute.
132 133 134 |
# File 'lib/rspec_tracer/storage/sqlite_backend.rb', line 132 def cache_path @cache_path end |
Instance Method Details
#clear! ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Internal method on the tracer pipeline.
204 205 206 207 208 |
# File 'lib/rspec_tracer/storage/sqlite_backend.rb', line 204 def clear! return unless File.directory?(@cache_path) FileUtils.rm_rf(@cache_path) end |
#last_run_id ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Internal method on the tracer pipeline.
144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/rspec_tracer/storage/sqlite_backend.rb', line 144 def last_run_id with_connection do |db| row = db.get_first_row('SELECT run_id FROM meta LIMIT 1') run_id = row&.first return nil if run_id.nil? || run_id.to_s.empty? run_id end rescue StandardError nil end |
#load_graph(schema_version:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Internal method on the tracer pipeline.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/rspec_tracer/storage/sqlite_backend.rb', line 158 def load_graph(schema_version:) with_connection do |db| return nil unless (db) row = db.get_first_row('SELECT schema_version, run_id FROM meta LIMIT 1') return nil if row.nil? stored_sv, run_id = row unless Schema.supported?(stored_sv) && stored_sv == schema_version info("schema_version mismatch (stored=#{stored_sv.inspect}, expected=#{schema_version}); cold run") return nil end return nil if run_id.nil? || run_id.to_s.empty? LazySnapshot.new( schema_version: stored_sv, run_id: run_id, reader: SqliteFieldReader.new(backend: self) ) end rescue StandardError => e info("failed to load sqlite cache: #{e.class}: #{e.}; cold run") nil end |
#read_field(field) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Read one field on behalf of a LazySnapshot. Missing table or empty result returns the per-field empty default (Set for id-set fields, {} for hashes) so partial caches behave identically to JsonBackend under the same conditions. ArgumentError on an unknown field is a programmer mistake and propagates; the StandardError rescue is only for wire / I/O failures on the DB itself.
231 232 233 234 235 236 237 238 239 240 |
# File 'lib/rspec_tracer/storage/sqlite_backend.rb', line 231 def read_field(field) raise ArgumentError, "unknown snapshot field: #{field.inspect}" unless READABLE_FIELDS.include?(field) begin with_connection { |db| dispatch_read(db, field) } rescue StandardError => e info("sqlite read_field(#{field.inspect}) failed: #{e.class}: #{e.}; returning empty default") empty_default_for(field) end end |
#save_graph(snapshot, schema_version:) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Internal method on the tracer pipeline.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/rspec_tracer/storage/sqlite_backend.rb', line 184 def save_graph(snapshot, schema_version:) raise ArgumentError, 'snapshot must not be nil' if snapshot.nil? unless Schema.supported?(schema_version) raise ArgumentError, "unsupported schema_version: #{schema_version.inspect}" end run_id = snapshot.run_id raise ArgumentError, 'snapshot.run_id must be a non-empty string' if run_id.nil? || run_id.to_s.empty? transactional_save do db = @active_db write_all_tables(db, snapshot, schema_version: schema_version) end snapshot end |
#transactional_save(&block) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Internal method on the tracer pipeline.
212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/rspec_tracer/storage/sqlite_backend.rb', line 212 def transactional_save(&block) raise ArgumentError, 'block required' unless block FileUtils.mkdir_p(@cache_path) with_write_connection do |db| @active_db = db db.transaction(:immediate, &block) ensure @active_db = nil end end |