Module: Fresco::Db
- Defined in:
- lib/fresco/runtime/db_sqlite.rb,
lib/fresco/runtime/db_postgres.rb
Defined Under Namespace
Constant Summary collapse
- Active =
Alias the chosen adapter so generated code (M3+) and user actions can refer to one name regardless of which is linked.
Don’t call ‘Fresco::Db::Active.new` directly from action code: Spinel’s analyzer doesn’t chase constant aliases through Active, treats the receiver as int, and emits 0 for every subsequent method call on the returned object. Use ‘Fresco::Db.new_instance` below instead — the typed factory gives Spinel a concrete class to bind dispatch against.
Postgres
Class Method Summary collapse
-
.begin_request! ⇒ Object
The per-request SQL totals (‘@db_sql_total_micros`, `@db_sql_count`) live on the `Fresco` module in runtime.rb, not here.
-
.begin_tx ⇒ Object
Transaction support.
- .bind_int(cid = 0, idx = 0, value = 0, name = "") ⇒ Object
-
.bind_str(cid = 0, idx = 0, value = "", name = "") ⇒ Object
The trailing ‘name = “”` is the column-name annotation for log output — see db_sqlite.rb for design notes.
-
.boot! ⇒ Object
Eager-open the connection.
- .cached_prepare(sql = "") ⇒ Object
- .col_int(cid = 0, idx = 0) ⇒ Object
- .col_str(cid = 0, idx = 0) ⇒ Object
- .commit_tx ⇒ Object
-
.emit(line = "") ⇒ Object
Single-line emit.
- .ensure_connection_dbh! ⇒ Object
-
.exec(sql = "") ⇒ Object
Adapter-agnostic surface for the model codegen.
- .finalize(cid = 0) ⇒ Object
-
.flush_log! ⇒ Object
See db_sqlite.rb for the in-place-clear rationale — repeated ‘@log_buffer = [“”]` reassignment crashes under Spinel after a few hundred requests (orphaned-array bus error).
-
.flush_pending! ⇒ Object
Emit the current pending entry (if any) and clear it.
- .last_rowid ⇒ Object
-
.log_sql? ⇒ Boolean
SQL query logging.
-
.new_instance ⇒ Object
Adapter-agnostic factory.
- .prepare(sql = "") ⇒ Object
-
.record_bind_int(idx = 0, value = 0, name = "") ⇒ Object
Record one bind on the current pending entry.
- .record_bind_str(idx = 0, value = "", name = "") ⇒ Object
- .reset(cid = 0) ⇒ Object
- .rollback! ⇒ Object
-
.step(cid = 0) ⇒ Object
See db_sqlite.rb for design notes on the two timing paths.
Class Method Details
.begin_request! ⇒ Object
The per-request SQL totals (‘@db_sql_total_micros`, `@db_sql_count`) live on the `Fresco` module in runtime.rb, not here. That’s the workaround for the Spinel cross-file- value-return collapse — see the comment on those counters in runtime.rb. Below we call ‘Fresco.add_sql_micros!` (side-effect cross-file call, works fine) and `Fresco.reset_db_sql_stats!` (same).
249 250 251 252 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 249 def self.begin_request! @in_request = true Fresco.reset_db_sql_stats! end |
.begin_tx ⇒ Object
Transaction support. Same shape as the SQLite-template version — explicit ‘Fresco::Db.rollback!` instead of rescue-on-raise because Spinel’s rescue handling across FFI boundaries is iffy. Transaction primitives. See db_sqlite.rb for design notes —Spinel doesn’t lower ‘&block` cleanly so we expose explicit begin/commit/rollback instead of a block-yielding transaction.
479 480 481 482 483 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 479 def self.begin_tx exec("BEGIN") @tx_rollback_requested = false true end |
.bind_int(cid = 0, idx = 0, value = 0, name = "") ⇒ Object
410 411 412 413 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 410 def self.bind_int(cid = 0, idx = 0, value = 0, name = "") record_bind_int(idx, value, name) if log_sql? Sqlite.fresco_sqlite_bind_int(cid, idx, value) end |
.bind_str(cid = 0, idx = 0, value = "", name = "") ⇒ Object
The trailing ‘name = “”` is the column-name annotation for log output — see db_sqlite.rb for design notes.
405 406 407 408 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 405 def self.bind_str(cid = 0, idx = 0, value = "", name = "") record_bind_str(idx, value, name) if log_sql? Sqlite.fresco_sqlite_bind_str(cid, idx, value) end |
.boot! ⇒ Object
Eager-open the connection. Called once from Fresco::App#run at startup. Same rationale as the SQLite-template version —surface a bad DSN at process start instead of inside the first request.
450 451 452 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 450 def self.boot! ensure_connection_dbh! end |
.cached_prepare(sql = "") ⇒ Object
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 383 def self.cached_prepare(sql = "") if @stmt_cache.key?(sql) cid = @stmt_cache[sql] Sqlite.fresco_sqlite_reset(cid) flush_pending! if @has_pending @pending_t0 = Sock.sphttp_elapsed_micros @has_pending = true if log_sql? @pending_sql = sql @pending_label = Color.dim(" [db cached] ") @pending_binds = "" end return cid end cid = prepare(sql) @stmt_cache[sql] = cid if cid > 0 cid end |
.col_int(cid = 0, idx = 0) ⇒ Object
436 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 436 def self.col_int(cid = 0, idx = 0); Sqlite.fresco_sqlite_col_int(cid, idx); end |
.col_str(cid = 0, idx = 0) ⇒ Object
435 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 435 def self.col_str(cid = 0, idx = 0); Sqlite.fresco_sqlite_col_str(cid, idx); end |
.commit_tx ⇒ Object
485 486 487 488 489 490 491 492 493 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 485 def self.commit_tx if @tx_rollback_requested exec("ROLLBACK") @tx_rollback_requested = false return false end exec("COMMIT") true end |
.emit(line = "") ⇒ Object
Single-line emit. Routes through the buffer when we’re inside a request (so the line lands under the [request] log), or straight to stdout for migrations / one-off CLI work where there’s no request to nest under.
311 312 313 314 315 316 317 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 311 def self.emit(line = "") if @in_request @log_buffer.push(line) else puts line end end |
.ensure_connection_dbh! ⇒ Object
184 185 186 187 188 189 190 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 184 def self.ensure_connection_dbh! if @conn_dbh < 0 h = Sqlite.fresco_sqlite_open(Fresco.database_path) @conn_dbh = h if h > 0 end @conn_dbh end |
.exec(sql = "") ⇒ Object
Adapter-agnostic surface for the model codegen. See db_sqlite.rb for design notes — same method names, same return conventions; only the underlying FFI delegate differs.
345 346 347 348 349 350 351 352 353 354 355 356 357 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 345 def self.exec(sql = "") h = ensure_connection_dbh! return false if h < 0 flush_pending! if @has_pending t0 = Sock.sphttp_elapsed_micros result = Sqlite.fresco_sqlite_exec(h, sql) == 0 dt = Sock.sphttp_elapsed_micros - t0 Fresco.add_sql_micros!(dt) if log_sql? emit(Color.dim(" [db] (" + fmt_micros(dt) + ") ") + color_sql(sql)) end result end |
.finalize(cid = 0) ⇒ Object
437 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 437 def self.finalize(cid = 0); Sqlite.fresco_sqlite_finalize(cid); end |
.flush_log! ⇒ Object
See db_sqlite.rb for the in-place-clear rationale — repeated ‘@log_buffer = [“”]` reassignment crashes under Spinel after a few hundred requests (orphaned-array bus error).
266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 266 def self.flush_log! flush_pending! i = 0 while i < @log_buffer.length puts @log_buffer[i] i += 1 end while @log_buffer.length > 0 @log_buffer.delete_at(@log_buffer.length - 1) end @in_request = false end |
.flush_pending! ⇒ Object
Emit the current pending entry (if any) and clear it. Two jobs:
1. Compute the [prepare → now] duration and fold it into the
per-request totals. Runs even when FRESCO_LOG_SQL is off so
the request-line roll-up stays accurate.
2. When FRESCO_LOG_SQL is on, emit the formatted log line with
that duration, the column-binding annotation, and the
[db] / [db cached] marker.
Used as a boundary between consecutive prepares (so a prepare- without-step still gets timed and surfaced) and as the bind→step hand-off (so step’s emission picks up the accumulated binds).
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 289 def self.flush_pending! return unless @has_pending dt = Sock.sphttp_elapsed_micros - @pending_t0 Fresco.add_sql_micros!(dt) if @pending_sql != "" line = @pending_label + Color.dim("(" + fmt_micros(dt) + ") ") + color_sql(@pending_sql) if @pending_binds != "" line += Color.dim(" [" + @pending_binds + "]") end emit(line) end @has_pending = false @pending_t0 = 0 @pending_sql = "" @pending_label = "" @pending_binds = "" end |
.last_rowid ⇒ Object
440 441 442 443 444 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 440 def self.last_rowid h = ensure_connection_dbh! return -1 if h < 0 Sqlite.fresco_sqlite_last_insert_rowid(h) end |
.log_sql? ⇒ Boolean
SQL query logging. See db_sqlite.rb for full design notes — same FRESCO_LOG_SQL env gate, same in-request buffer, same ‘[db]` / `[db cached]` line format, same verb-driven color palette via `color_sql` in runtime.rb, same per-bind value capture for the trailing `[$1=…, $2=…]` annotation.
215 216 217 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 215 def self.log_sql? ENV.fetch("FRESCO_LOG_SQL", "") != "" end |
.new_instance ⇒ Object
Adapter-agnostic factory. Returns a fresh unopened Postgres (or SQLite, in the other template) instance. Action code that wants to be DB-agnostic calls this; Spinel’s analyzer sees the concrete return type per-build and dispatches correctly.
170 171 172 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 170 def self.new_instance SQLite.new end |
.prepare(sql = "") ⇒ Object
359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 359 def self.prepare(sql = "") h = ensure_connection_dbh! return -1 if h < 0 flush_pending! if @has_pending @pending_t0 = Sock.sphttp_elapsed_micros @has_pending = true if log_sql? @pending_sql = sql @pending_label = Color.dim(" [db] ") @pending_binds = "" end Sqlite.fresco_sqlite_prepare(h, sql) end |
.record_bind_int(idx = 0, value = 0, name = "") ⇒ Object
Record one bind on the current pending entry. When ‘name` is non-empty, use it as the label (`email=“bob”`); when empty, fall back to the placeholder index (`$1=“bob”`). Model codegen passes the column name through; ad-hoc `Db.exec` / `Model.query` / hand- written SQL paths call without a name and get the `$N` form. Strings get double-quoted so a numeric-looking string (“42”) isn’t ambiguous with the int 42 when reading logs.
326 327 328 329 330 331 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 326 def self.record_bind_int(idx = 0, value = 0, name = "") return if @pending_sql == "" sep = @pending_binds == "" ? "" : ", " label = name == "" ? "$" + idx.to_s : name @pending_binds = @pending_binds + sep + label + "=" + value.to_s end |
.record_bind_str(idx = 0, value = "", name = "") ⇒ Object
333 334 335 336 337 338 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 333 def self.record_bind_str(idx = 0, value = "", name = "") return if @pending_sql == "" sep = @pending_binds == "" ? "" : ", " label = name == "" ? "$" + idx.to_s : name @pending_binds = @pending_binds + sep + label + "=\"" + value + "\"" end |
.reset(cid = 0) ⇒ Object
438 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 438 def self.reset(cid = 0); Sqlite.fresco_sqlite_reset(cid); end |
.rollback! ⇒ Object
462 463 464 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 462 def self.rollback! @tx_rollback_requested = true end |
.step(cid = 0) ⇒ Object
See db_sqlite.rb for design notes on the two timing paths.
423 424 425 426 427 428 429 430 431 432 433 |
# File 'lib/fresco/runtime/db_sqlite.rb', line 423 def self.step(cid = 0) if @has_pending result = Sqlite.fresco_sqlite_step(cid) flush_pending! return result end t0 = Sock.sphttp_elapsed_micros result = Sqlite.fresco_sqlite_step(cid) Fresco.add_sql_micros!(Sock.sphttp_elapsed_micros - t0) result end |