Class: Cloudflare::D1Database

Inherits:
Object
  • Object
show all
Defined in:
lib/homura/runtime.rb

Overview

D1Database wraps a Cloudflare D1 JS binding. The public API is modelled on CRuby’s ‘sqlite3-ruby` gem (`SQLite3::Database`) so that the calling code reads identically:

# sqlite3-ruby on CRuby:
rows = db.execute("SELECT * FROM users WHERE id = ?", [1])

# homura on Opal (+ async):
rows = db.execute("SELECT * FROM users WHERE id = ?", [1]).__await__

Every query method returns a JS Promise. Use ‘.__await__` inside a `# await: true` route block to unwrap it synchronously (Opal compiles `.__await__` to a native JS `await`).

Results are always Hashes — ‘results_as_hash` is effectively hardcoded to `true`. This matches the common `db.results_as_hash = true` convention in the sqlite3-ruby world and gives downstream ORM code a ready-made Hash-per-row interface to build on.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(js) ⇒ D1Database

Returns a new instance of D1Database.



796
797
798
# File 'lib/homura/runtime.rb', line 796

def initialize(js)
  @js = js
end

Class Method Details

.flatten_meta(result) ⇒ Object

Flatten common keys from ‘result` to the top of the hash so callers can write `meta` without descending into the nested D1 metadata object. Preserves the original shape unchanged.

The gem’s own Ruby sources are NOT processed by the build’s auto-await pass — only user app code is. So when ‘execute_insert` passes the result of `stmt.run` here, `result` may still be a JS Promise. Await it explicitly so the flatten branch actually runs; otherwise the early `unless result.is_a?(Hash)` would short-circuit and `meta` would come back as nil at the call site (the caller’s auto-await resolves the Promise after this method returns).



858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
# File 'lib/homura/runtime.rb', line 858

def self.flatten_meta(result)
  if defined?(::Cloudflare) && ::Cloudflare.js_promise?(result)
    result = result.__await__
  end
  return result unless result.is_a?(Hash)
  nested = result["meta"]
  return result unless nested.is_a?(Hash)

  %w[
    last_row_id
    changes
    duration
    size_after
    rows_read
    rows_written
  ].each { |k| result[k] = nested[k] unless result.key?(k) }
  result
end

Instance Method Details

#exec(sql) ⇒ Object



890
891
892
893
# File 'lib/homura/runtime.rb', line 890

def exec(sql)
  js = @js
  `(#{js}.exec ? #{js}.exec(#{sql}) : #{js}.prepare(#{sql}).run())`
end

#execute(sql, bind_params = []) ⇒ Object

Execute a SQL statement with optional bind parameters. Returns a JS Promise resolving to an Array of Hashes; the build-time auto-await pass rewrites the usual Sinatra call sites so app code can stay ‘db.execute(…)` instead of spelling `.__await__`.

db.execute("SELECT * FROM users")                           → Array<Hash>
db.execute("SELECT * FROM users WHERE id = ?", [1])         → Array<Hash>
db.execute("INSERT INTO users (name) VALUES (?)", ["alice"]) → Array<Hash> (empty for writes)


810
811
812
813
814
# File 'lib/homura/runtime.rb', line 810

def execute(sql, bind_params = [])
  stmt = prepare(sql)
  stmt = stmt.bind(*bind_params) unless bind_params.empty?
  stmt.all
end

#execute_batch(sql) ⇒ Object

Execute one or more raw SQL statements separated by semicolons. Useful for schema migrations. Returns the raw async D1 exec result.



879
880
881
# File 'lib/homura/runtime.rb', line 879

def execute_batch(sql)
  exec(sql)
end

#execute_insert(sql, bind_params = []) ⇒ Object

Execute a write statement (INSERT / UPDATE / DELETE) and return a metadata Hash with ‘changes`, `last_row_id`, `duration`, etc. Returns a JS Promise resolving to that metadata Hash.

The raw D1 ‘run()` payload nests these fields under `meta`:

{ 'success' => true, 'meta' => { 'last_row_id' => 7, ... }, 'results' => [...] }

sqlite3-ruby exposes them at the top level. To keep the documented “sqlite3-ruby compatible” surface and still leave the raw shape available, flatten the common keys to the top level.

meta = db.execute_insert("INSERT INTO users (name) VALUES (?)", ["alice"])
meta['last_row_id']          # → 7  (flattened convenience)
meta['meta']['last_row_id']  # → 7  (raw D1 shape)


839
840
841
842
843
844
# File 'lib/homura/runtime.rb', line 839

def execute_insert(sql, bind_params = [])
  stmt = prepare(sql)
  stmt = stmt.bind(*bind_params) unless bind_params.empty?
  raw = stmt.run
  D1Database.flatten_meta(raw)
end

#get_first_row(sql, bind_params = []) ⇒ Object

Execute and return only the first row (or nil). Returns a JS Promise resolving to a Hash or nil.

db.get_first_row("SELECT * FROM users WHERE id = ?", [1])  → Hash or nil


820
821
822
823
824
# File 'lib/homura/runtime.rb', line 820

def get_first_row(sql, bind_params = [])
  stmt = prepare(sql)
  stmt = stmt.bind(*bind_params) unless bind_params.empty?
  stmt.first
end

#prepare(sql) ⇒ Object

—- low-level D1 API (prepare/bind/all/first/run) —————



885
886
887
888
# File 'lib/homura/runtime.rb', line 885

def prepare(sql)
  js = @js
  D1Statement.new(`#{js}.prepare(#{sql})`)
end