Module: Sqlx::Database
- Defined in:
- runtime/database/query_cache.rb,
runtime/database/lease.rb,
runtime/database/postgresql.rb,
runtime/database/sqlite.rb
Overview
The cache's module surface, part of the adapter contract both backends
satisfy. The adapters call these from exec_read (fetch/store), exec /
exec_write / configure (invalidate), and with_connection (scope
lifetime).
Defined Under Namespace
Modules: Libpq, SQLite3 Classes: DatabaseConnection, DatabasePool, LeaseToken, PgConnection, PgPool, PgStmt, Stmt
Class Method Summary collapse
-
.adapter_name ⇒ String
the dialect name apps branch on; each adapter answers its own.
-
.changes ⇒ Integer
the row count of the last non-RETURNING INSERT/UPDATE/DELETE on this lease's connection.
- .close ⇒ Object
-
.configure(path, pool_size: 8, busy_timeout_ms: 5000) ⇒ void
configure the database and build its connection pools:
pool_sizeFFI handles topath, opened on first use. -
.configure_default! ⇒ void
configure from this adapter's own source, so
main.rbnever names an adapter:DATABASE_PATH, elsestorage/development.sqlite3. -
.exec(sql) ⇒ void
run one DDL / INSERT / UPDATE / DELETE statement; no rows come back.
-
.exec_read(sql, values) ⇒ Array
run one statement with the caller's values bound and hand back the rows it produced, keyed by column name.
-
.exec_write(sql, values) ⇒ Hash
run one statement with the caller's values bound and hand back what that execution produced: the rows it returned and the rows it changed.
-
.in_lease? ⇒ Boolean
whether this thread is inside a
with_connectionscope. -
.last_insert_rowid ⇒ Integer
the id the last INSERT in this session produced.
-
.owns_schema? ⇒ Boolean
whether this adapter owns its schema DDL.
-
.query_cache_clear ⇒ void
invalidate every thread's entries after a write or DDL.
-
.quote(value) ⇒ String
the adapter's SQL literal renderer: NULL, a bare number, the adapter's boolean spelling, the stored Time form, else a quoted string.
-
.require_leases! ⇒ void
require a
with_connectionscope for every database access from here on. - .sql_trace? ⇒ Boolean
-
.with_connection ⇒ Object
run the block inside a connection lease scope and hand back its value.
Class Method Details
.adapter_name ⇒ String
the dialect name apps branch on; each adapter answers its own.
706 707 708 |
# File 'runtime/database/postgresql.rb', line 706 def self.adapter_name "PostgreSQL" end |
.changes ⇒ Integer
the row count of the last non-RETURNING INSERT/UPDATE/DELETE on this
lease's connection. A statement with RETURNING reports its count through
exec_write, not here; this accessor keeps the previous count. SQLite's
sqlite3_changes does update for RETURNING — core never reads it after a
RETURNING write.
689 690 691 |
# File 'runtime/database/postgresql.rb', line 689 def self.changes current_conn.changes end |
.close ⇒ Object
511 512 513 514 515 516 517 518 |
# File 'runtime/database/postgresql.rb', line 511 def self.close if !@pool.nil? @pool.close_all @pool = nil end # Cached rows belong to the connections that produced them. query_cache_clear end |
.configure(path, pool_size: 8, busy_timeout_ms: 5000) ⇒ void
configure the database and build its connection pools: pool_size FFI
handles to path, opened on first use. Nothing is opened here, so a
prefork parent may call this before fork; the parent must not touch the
database before forking, or the child would inherit the parent's handles.
Every connection gets the fair busy handler (busy_wait) with the same
timeout. DATABASE_BUSY_TIMEOUT_MS overrides busy_timeout_ms when set.
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 |
# File 'runtime/database/postgresql.rb', line 481 def self.configure(conninfo, pool_size: 8) @sql_trace = ENV.fetch("RH_SQL_TRACE", "") != "" n = pool_size ev = ENV["DATABASE_POOL_SIZE"] if !ev.nil? && ev != "" n = ev.to_i end probe = PgConnection.new(conninfo) probe.close close unless @pool.nil? @pool = PgPool.new(conninfo, n) # A reconnect replaces the database behind any cached rows; invalidate # every request's cache before the new connection serves a read. query_cache_clear end |
.configure_default! ⇒ void
configure from this adapter's own source, so main.rb never names an
adapter: DATABASE_PATH, else storage/development.sqlite3.
503 504 505 506 507 508 509 |
# File 'runtime/database/postgresql.rb', line 503 def self.configure_default! conninfo = ENV["PG_URL"] if conninfo.nil? || conninfo.empty? raise "PG_URL is required - this adapter speaks PostgreSQL (schema owned by the application)" end configure(conninfo) end |
.exec(sql) ⇒ void
run one DDL / INSERT / UPDATE / DELETE statement; no rows come back.
Callers that want the inserted id or the row count read
last_insert_rowid / changes immediately after.
532 533 534 535 536 537 538 539 540 541 542 543 |
# File 'runtime/database/postgresql.rb', line 532 def self.exec(sql) current_conn record_query(sql) started = Snails::Logger::Context.now_us begin res = pq_exec(autoincrement_shim(sql)) Libpq.PQclear(res) ensure query_cache_clear Snails::Logger::Context.add_db_us(Snails::Logger::Context.now_us - started) end end |
.exec_read(sql, values) ⇒ Array
run one statement with the caller's values bound and hand back the rows it
produced, keyed by column name. The binding is this adapter's (the same
one exec_write uses); a caller that wants the rows of a statement with
values comes here.
Inside a with_connection scope, an identical SELECT replays the
first result. The cache key covers the statement and its bind values
(cache_key_for), so bound reads are cached too — the same contract
the PostgreSQL sibling answers. A write or DDL invalidates the
cache; outside a scope every call round-trips.
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 |
# File 'runtime/database/postgresql.rb', line 568 def self.exec_read(sql, values) current_conn bound = values.nil? ? [] : values final = final_sql_for(sql, bound) cacheable = select_read?(sql) generation = @query_cache_generation if cacheable cached = query_cache_fetch(final, generation) unless cached.nil? record_cache_hit(sql) return cached end end record_query(sql) started = Snails::Logger::Context.now_us begin stmt = PgStmt.new(final) rows = read_rows(stmt) finalize(stmt) query_cache_store(final, rows, generation) if cacheable rows ensure Snails::Logger::Context.add_db_us(Snails::Logger::Context.now_us - started) end end |
.exec_write(sql, values) ⇒ Hash
run one statement with the caller's values bound and hand back what that execution produced: the rows it returned and the rows it changed.
The two answers belong to one execution — the count is final only once the statement is done, and a statement that returns rows is done after its last row is stepped — so one call carries both and the caller reads the one it needs.
630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 |
# File 'runtime/database/postgresql.rb', line 630 def self.exec_write(sql, values) current_conn started = Snails::Logger::Context.now_us begin stmt = prepare(sql) i = 0 while i < values.length bind_value(stmt, i + 1, values[i]) i += 1 end rows = read_rows(stmt) status = Libpq.PQresultStatus(stmt.res) count = 0 if status == Libpq::PGRES_COMMAND_OK tuples = Libpq.PQcmdTuples(stmt.res) count = tuples.nil? || tuples == "" ? 0 : tuples.to_i else count = rows.length end finalize(stmt) { "rows" => rows, "count" => count } ensure query_cache_clear Snails::Logger::Context.add_db_us(Snails::Logger::Context.now_us - started) end end |
.in_lease? ⇒ Boolean
whether this thread is inside a with_connection scope. The scope is open
even before the first database access; callers use it to avoid opening a
nested scope that would rebind the outer one.
104 105 106 |
# File 'runtime/database/lease.rb', line 104 def self.in_lease? !Thread.current[:db_lease_scope].nil? || !Thread.current[:db_conn].nil? end |
.last_insert_rowid ⇒ Integer
the id the last INSERT in this session produced. PG identity columns
advance a sequence rather than a rowid, so this reads lastval(): the
session's last nextval(), the per-connection answer this adapter keeps.
A session that has inserted nothing raises from PG; that reads back as 0,
sqlite's answer too.
675 676 677 678 |
# File 'runtime/database/postgresql.rb', line 675 def self.last_insert_rowid v = query_int("SELECT lastval()") v.nil? ? 0 : v end |
.owns_schema? ⇒ Boolean
whether this adapter owns its schema DDL. It answers true here; the PostgreSQL sibling answers false (there the schema belongs to the application's deployment). The server never runs DDL either way — an explicit loader (a harness or test setup step) reads this to decide whether to apply the app's schema.
699 700 701 |
# File 'runtime/database/postgresql.rb', line 699 def self.owns_schema? false end |
.query_cache_clear ⇒ void
invalidate every thread's entries after a write or DDL. The generation bump is serialized: two writes landing together must not collapse into one value and leave a pre-write entry looking current.
167 168 169 170 171 172 173 |
# File 'runtime/database/query_cache.rb', line 167 def self.query_cache_clear @query_cache_lock.synchronize do @query_cache_generation = @query_cache_generation + 1 end cache = Thread.current[:sqlx_query_cache] cache.clear_cached_rows unless cache.nil? end |
.quote(value) ⇒ String
the adapter's SQL literal renderer: NULL, a bare number, the adapter's
boolean spelling, the stored Time form, else a quoted string. The escape
helpers are the implementation; callers go through Sqlx.quote.
715 716 717 718 719 720 721 722 723 724 725 726 727 |
# File 'runtime/database/postgresql.rb', line 715 def self.quote(value) if value.nil? "NULL" elsif value.is_a?(Integer) || value.is_a?(Float) value.to_s elsif value.is_a?(TrueClass) || value.is_a?(FalseClass) escape_bool(value) elsif value.is_a?(Time) escape_string(value.to_fs(:db)) else escape_string(value.to_s) end end |
.require_leases! ⇒ void
require a with_connection scope for every database access from here on.
A server process (the scheduler, worker threads, and one thread per client
connection) may otherwise reach a shared connection another thread holds
mid-transaction. The fallback stays for single-fiber contexts (boot,
tests, one-shot dev commands).
115 116 117 |
# File 'runtime/database/lease.rb', line 115 def self.require_leases! @lease_required = true end |
.sql_trace? ⇒ Boolean
729 730 731 |
# File 'runtime/database/postgresql.rb', line 729 def self.sql_trace? @sql_trace end |
.with_connection ⇒ Object
run the block inside a connection lease scope and hand back its value. The
scope does not touch the pool by itself: the first database access checks
a connection out (see ensure_connection), so a request, job, or log
hand-off that never reaches the database never leases a connection. A
thread already inside a scope re-enters it: the outer connection and
request cache carry on, so a nested block can neither rebind nor unbind
its outer scope. Before configure there is nothing to lease, so the
block runs without a cache; a database access inside still fails loudly on
the absent connection.
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'runtime/database/lease.rb', line 55 def self.with_connection return yield if in_lease? Thread.current[:db_lease_scope] = true finished = false begin result = yield finished = true ensure Thread.current[:db_lease_scope] = nil conn = Thread.current[:db_conn] token = Thread.current[:db_lease_token] Thread.current[:db_lease_token] = nil if !conn.nil? query_cache_end Thread.current[:db_conn] = nil token.release(conn, finished) unless token.nil? end end result end |