fresco 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/exe/fresco +3 -0
- data/lib/fresco/application.rb +12 -0
- data/lib/fresco/cli/build.rb +682 -0
- data/lib/fresco/cli/dev.rb +17 -0
- data/lib/fresco/cli/dev_loop.rb +815 -0
- data/lib/fresco/cli/new.rb +120 -0
- data/lib/fresco/cli/release.rb +76 -0
- data/lib/fresco/cli.rb +56 -0
- data/lib/fresco/database_config.rb +34 -0
- data/lib/fresco/generators/app/Gemfile.tt +18 -0
- data/lib/fresco/generators/app/README.md.tt +32 -0
- data/lib/fresco/generators/app/app/action.rb.tt +20 -0
- data/lib/fresco/generators/app/app/actions/root_path.rb.tt +5 -0
- data/lib/fresco/generators/app/app/views/layouts/application.html.erb +29 -0
- data/lib/fresco/generators/app/app/views/root_path.html.erb +8 -0
- data/lib/fresco/generators/app/app.rb.tt +15 -0
- data/lib/fresco/generators/app/bin/build +2 -0
- data/lib/fresco/generators/app/bin/dev +2 -0
- data/lib/fresco/generators/app/bin/release +2 -0
- data/lib/fresco/generators/app/config/app.rb.tt +26 -0
- data/lib/fresco/generators/app/config/database.rb +17 -0
- data/lib/fresco/generators/app/config/routes.rb +11 -0
- data/lib/fresco/generators/app/db/schema.rb +14 -0
- data/lib/fresco/generators/app/public/404.html +87 -0
- data/lib/fresco/generators/app/public/500.html +84 -0
- data/lib/fresco/migration_builder.rb +55 -0
- data/lib/fresco/model_builder.rb +54 -0
- data/lib/fresco/paths.rb +20 -0
- data/lib/fresco/router.rb +67 -0
- data/lib/fresco/runtime/boot.rb +34 -0
- data/lib/fresco/runtime/db_postgres.rb +403 -0
- data/lib/fresco/runtime/db_sqlite.rb +495 -0
- data/lib/fresco/runtime/http.c +456 -0
- data/lib/fresco/runtime/postgres.c +339 -0
- data/lib/fresco/runtime/runtime.rb +1810 -0
- data/lib/fresco/runtime/sqlite.c +220 -0
- data/lib/fresco/runtime/welcome.rb +152 -0
- data/lib/fresco/schema_builder.rb +71 -0
- data/lib/fresco/templates/dispatch.rb.erb +32 -0
- data/lib/fresco/templates/layout_dispatch.rb.erb +16 -0
- data/lib/fresco/templates/manifest.rb.erb +5 -0
- data/lib/fresco/templates/migrations.rb.erb +152 -0
- data/lib/fresco/templates/model.rb.erb +223 -0
- data/lib/fresco/templates/view.rb.erb +5 -0
- data/lib/fresco/version.rb +3 -0
- data/lib/fresco.rb +61 -0
- metadata +115 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# Fresco Postgres adapter. Copied verbatim to generated/db_adapter.rb
|
|
2
|
+
# by `fresco build` when config/database.rb declares `Fresco.database
|
|
3
|
+
# :postgres`. Always loaded between runtime.rb and config/app.rb —
|
|
4
|
+
# boot order in lib/fresco/runtime/boot.rb.
|
|
5
|
+
#
|
|
6
|
+
# Same Ruby surface as db_sqlite.rb — `Fresco::Db::Postgres` exposes
|
|
7
|
+
# open/close/exec/prepare/bind_str/bind_int/step/col_str/col_int/
|
|
8
|
+
# col_count/finalize/reset/last_rowid + the first_str/first_int
|
|
9
|
+
# helpers. Generated code (M3+) writes against `Fresco::Db::Active`
|
|
10
|
+
# so swapping adapters is a config edit, not an action edit.
|
|
11
|
+
#
|
|
12
|
+
# Placeholder syntax differs from SQLite: libpq uses `$1, $2, ...` not
|
|
13
|
+
# `?`. User-written SQL has to use the adapter's native syntax; M3+
|
|
14
|
+
# codegen will paper over this when emitting prepared statements.
|
|
15
|
+
#
|
|
16
|
+
# Build dependency: generated/runtime/postgres.c (copied from the
|
|
17
|
+
# fresco gem at build time) needs libpq's headers (`libpq-fe.h`) and
|
|
18
|
+
# library (`-lpq`). `fresco release` auto-detects Homebrew/Postgres.app
|
|
19
|
+
# layouts and exports CPATH/LIBRARY_PATH accordingly. On Linux:
|
|
20
|
+
# `apt-get install libpq-dev`.
|
|
21
|
+
# `Pg` (not `Postgres`) so the FFI module name doesn't collide with
|
|
22
|
+
# `Fresco::Db::Postgres`. Matches the `fresco_pg_*` C-side prefix and
|
|
23
|
+
# mirrors the `Sqlite` (FFI) vs `SQLite` (wrapper) split.
|
|
24
|
+
module Pg
|
|
25
|
+
ffi_cflags "generated/runtime/postgres.c"
|
|
26
|
+
ffi_lib "pq"
|
|
27
|
+
|
|
28
|
+
ffi_func :fresco_pg_open, [:str], :int
|
|
29
|
+
ffi_func :fresco_pg_close, [:int], :int
|
|
30
|
+
ffi_func :fresco_pg_exec, [:int, :str], :int
|
|
31
|
+
ffi_func :fresco_pg_prepare, [:int, :str], :int
|
|
32
|
+
ffi_func :fresco_pg_bind_str, [:int, :int, :str], :int
|
|
33
|
+
ffi_func :fresco_pg_bind_int, [:int, :int, :int], :int
|
|
34
|
+
ffi_func :fresco_pg_step, [:int], :int
|
|
35
|
+
ffi_func :fresco_pg_col_str, [:int, :int], :str
|
|
36
|
+
ffi_func :fresco_pg_col_int, [:int, :int], :int
|
|
37
|
+
ffi_func :fresco_pg_col_count, [:int], :int
|
|
38
|
+
ffi_func :fresco_pg_finalize, [:int], :int
|
|
39
|
+
ffi_func :fresco_pg_reset, [:int], :int
|
|
40
|
+
ffi_func :fresco_pg_last_insert_rowid, [:int], :int
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
module Fresco
|
|
44
|
+
module Db
|
|
45
|
+
# Thin Ruby cover over the Postgres FFI module. Same shape as
|
|
46
|
+
# Fresco::Db::SQLite — identical method names, identical return
|
|
47
|
+
# conventions (0/-1 status, 1/0/-1 step, Int/Str cols, etc.).
|
|
48
|
+
#
|
|
49
|
+
# Differences worth knowing per-adapter:
|
|
50
|
+
# - URL-based open instead of file path. Pass a libpq conninfo
|
|
51
|
+
# string ("postgres://user@host/db" or "host=... user=...").
|
|
52
|
+
# - last_rowid runs `SELECT lastval()` — only valid when the
|
|
53
|
+
# prior statement triggered a sequence. For predictable INSERT
|
|
54
|
+
# id reads, use `INSERT ... RETURNING id` + step + col_int(0).
|
|
55
|
+
# - Placeholders are `$1, $2, ...` not `?`.
|
|
56
|
+
class Postgres
|
|
57
|
+
attr_accessor :dbh
|
|
58
|
+
|
|
59
|
+
def initialize
|
|
60
|
+
@dbh = -1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns true on success, false on failure. URL accepts any libpq
|
|
64
|
+
# conninfo string. Calling #open twice on the same instance leaks
|
|
65
|
+
# the prior handle — close first.
|
|
66
|
+
def open(url = "")
|
|
67
|
+
h = Pg.fresco_pg_open(url)
|
|
68
|
+
return false if h < 0
|
|
69
|
+
@dbh = h
|
|
70
|
+
true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def close
|
|
74
|
+
if @dbh >= 0
|
|
75
|
+
Pg.fresco_pg_close(@dbh)
|
|
76
|
+
@dbh = -1
|
|
77
|
+
end
|
|
78
|
+
0
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Run a no-bind statement (DDL, BEGIN/COMMIT, queries with literal
|
|
82
|
+
# constants). Returns true on success. For any user-supplied value
|
|
83
|
+
# always use prepare + bind + step + finalize.
|
|
84
|
+
def exec(sql = "")
|
|
85
|
+
return false if @dbh < 0
|
|
86
|
+
Pg.fresco_pg_exec(@dbh, sql) == 0
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Open a cursor on a parameterised statement. Returns an Int
|
|
90
|
+
# cursor id (>= 1) on success, -1 on failure. The query doesn't
|
|
91
|
+
# actually hit Postgres until the first #step — bind_str/bind_int
|
|
92
|
+
# calls between prepare and step accumulate the values that will
|
|
93
|
+
# be passed to PQexecParams.
|
|
94
|
+
def prepare(sql = "")
|
|
95
|
+
return -1 if @dbh < 0
|
|
96
|
+
Pg.fresco_pg_prepare(@dbh, sql)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def bind_str(cid = 0, idx = 0, value = "")
|
|
100
|
+
Pg.fresco_pg_bind_str(cid, idx, value)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def bind_int(cid = 0, idx = 0, value = 0)
|
|
104
|
+
Pg.fresco_pg_bind_int(cid, idx, value)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# 1 -> row available, 0 -> done, -1 -> error or invalid cursor.
|
|
108
|
+
# The first call fires PQexecParams with accumulated binds;
|
|
109
|
+
# subsequent calls walk the result one row at a time.
|
|
110
|
+
def step(cid = 0); Pg.fresco_pg_step(cid); end
|
|
111
|
+
def col_str(cid = 0, idx = 0); Pg.fresco_pg_col_str(cid, idx); end
|
|
112
|
+
def col_int(cid = 0, idx = 0); Pg.fresco_pg_col_int(cid, idx); end
|
|
113
|
+
def col_count(cid = 0); Pg.fresco_pg_col_count(cid); end
|
|
114
|
+
def finalize(cid = 0); Pg.fresco_pg_finalize(cid); end
|
|
115
|
+
def reset(cid = 0); Pg.fresco_pg_reset(cid); end
|
|
116
|
+
|
|
117
|
+
def last_rowid
|
|
118
|
+
return -1 if @dbh < 0
|
|
119
|
+
Pg.fresco_pg_last_insert_rowid(@dbh)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Single-row single-column convenience. Same semantics as
|
|
123
|
+
# Fresco::Db::SQLite#first_str — pass `""` for no bind. Always
|
|
124
|
+
# finalises the cursor before returning.
|
|
125
|
+
def first_str(sql = "", p1 = "")
|
|
126
|
+
return "" if @dbh < 0
|
|
127
|
+
cid = Pg.fresco_pg_prepare(@dbh, sql)
|
|
128
|
+
return "" if cid < 0
|
|
129
|
+
if p1.length > 0
|
|
130
|
+
Pg.fresco_pg_bind_str(cid, 1, p1)
|
|
131
|
+
end
|
|
132
|
+
result = ""
|
|
133
|
+
if Pg.fresco_pg_step(cid) == 1
|
|
134
|
+
result = Pg.fresco_pg_col_str(cid, 0)
|
|
135
|
+
end
|
|
136
|
+
Pg.fresco_pg_finalize(cid)
|
|
137
|
+
result
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def first_int(sql = "", p1 = "")
|
|
141
|
+
return 0 if @dbh < 0
|
|
142
|
+
cid = Pg.fresco_pg_prepare(@dbh, sql)
|
|
143
|
+
return 0 if cid < 0
|
|
144
|
+
if p1.length > 0
|
|
145
|
+
Pg.fresco_pg_bind_str(cid, 1, p1)
|
|
146
|
+
end
|
|
147
|
+
result = 0
|
|
148
|
+
if Pg.fresco_pg_step(cid) == 1
|
|
149
|
+
result = Pg.fresco_pg_col_int(cid, 0)
|
|
150
|
+
end
|
|
151
|
+
Pg.fresco_pg_finalize(cid)
|
|
152
|
+
result
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Alias the chosen adapter so generated code (M3+) and user actions
|
|
157
|
+
# can refer to one name regardless of which is linked.
|
|
158
|
+
#
|
|
159
|
+
# Don't call `Fresco::Db::Active.new` directly from action code:
|
|
160
|
+
# Spinel's analyzer doesn't chase constant aliases through Active,
|
|
161
|
+
# treats the receiver as int, and emits 0 for every subsequent
|
|
162
|
+
# method call on the returned object. Use `Fresco::Db.new_instance`
|
|
163
|
+
# below instead — the typed factory gives Spinel a concrete class
|
|
164
|
+
# to bind dispatch against.
|
|
165
|
+
Active = Postgres
|
|
166
|
+
|
|
167
|
+
# Adapter-agnostic factory. Returns a fresh unopened Postgres (or
|
|
168
|
+
# SQLite, in the other template) instance. Action code that wants
|
|
169
|
+
# to be DB-agnostic calls this; Spinel's analyzer sees the concrete
|
|
170
|
+
# return type per-build and dispatches correctly.
|
|
171
|
+
def self.new_instance
|
|
172
|
+
Postgres.new
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Lazy connection handle for the generated model layer. See the
|
|
176
|
+
# SQLite-template version for design notes — same pattern, same
|
|
177
|
+
# int-default-pinning rationale.
|
|
178
|
+
@conn_dbh = -1
|
|
179
|
+
|
|
180
|
+
def self.ensure_connection_dbh!
|
|
181
|
+
if @conn_dbh < 0
|
|
182
|
+
h = Pg.fresco_pg_open(Fresco.database_url)
|
|
183
|
+
@conn_dbh = h if h > 0
|
|
184
|
+
end
|
|
185
|
+
@conn_dbh
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# SQL query logging. See db_sqlite.rb for full design notes — same
|
|
189
|
+
# FRESCO_LOG_SQL env gate, same in-request buffer, same `[db]` /
|
|
190
|
+
# `[db cached]` line format, same verb-driven color palette via
|
|
191
|
+
# `color_sql` in runtime.rb, same per-bind value capture for the
|
|
192
|
+
# trailing `[$1=..., $2=...]` annotation.
|
|
193
|
+
def self.log_sql?
|
|
194
|
+
ENV.fetch("FRESCO_LOG_SQL", "") != ""
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
@in_request = false
|
|
198
|
+
@log_buffer = [""]
|
|
199
|
+
@log_buffer.delete_at(0)
|
|
200
|
+
|
|
201
|
+
# See db_sqlite.rb for design notes on `@has_pending` vs
|
|
202
|
+
# `@pending_sql`: the former is the timing-state flag (always
|
|
203
|
+
# tracked), the latter is set only when FRESCO_LOG_SQL is on.
|
|
204
|
+
# Per-request totals live on `Fresco` (in runtime.rb) — same
|
|
205
|
+
# workaround for Spinel cross-file value-return collapse.
|
|
206
|
+
@has_pending = false
|
|
207
|
+
@pending_t0 = 0
|
|
208
|
+
@pending_sql = ""
|
|
209
|
+
@pending_label = ""
|
|
210
|
+
@pending_binds = ""
|
|
211
|
+
|
|
212
|
+
def self.begin_request!
|
|
213
|
+
@in_request = true
|
|
214
|
+
Fresco.reset_db_sql_stats!
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# See db_sqlite.rb for the in-place-clear rationale — repeated
|
|
218
|
+
# `@log_buffer = [""]` reassignment crashes under Spinel after a
|
|
219
|
+
# few hundred requests (orphaned-array bus error).
|
|
220
|
+
def self.flush_log!
|
|
221
|
+
flush_pending!
|
|
222
|
+
i = 0
|
|
223
|
+
while i < @log_buffer.length
|
|
224
|
+
puts @log_buffer[i]
|
|
225
|
+
i += 1
|
|
226
|
+
end
|
|
227
|
+
while @log_buffer.length > 0
|
|
228
|
+
@log_buffer.delete_at(@log_buffer.length - 1)
|
|
229
|
+
end
|
|
230
|
+
@in_request = false
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def self.flush_pending!
|
|
234
|
+
return unless @has_pending
|
|
235
|
+
dt = Sock.sphttp_elapsed_micros - @pending_t0
|
|
236
|
+
Fresco.add_sql_micros!(dt)
|
|
237
|
+
if @pending_sql != ""
|
|
238
|
+
line = @pending_label + Color.dim("(" + fmt_micros(dt) + ") ") + color_sql(@pending_sql)
|
|
239
|
+
if @pending_binds != ""
|
|
240
|
+
line += Color.dim(" [" + @pending_binds + "]")
|
|
241
|
+
end
|
|
242
|
+
emit(line)
|
|
243
|
+
end
|
|
244
|
+
@has_pending = false
|
|
245
|
+
@pending_t0 = 0
|
|
246
|
+
@pending_sql = ""
|
|
247
|
+
@pending_label = ""
|
|
248
|
+
@pending_binds = ""
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def self.emit(line = "")
|
|
252
|
+
if @in_request
|
|
253
|
+
@log_buffer.push(line)
|
|
254
|
+
else
|
|
255
|
+
puts line
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def self.record_bind_int(idx = 0, value = 0, name = "")
|
|
260
|
+
return if @pending_sql == ""
|
|
261
|
+
sep = @pending_binds == "" ? "" : ", "
|
|
262
|
+
label = name == "" ? "$" + idx.to_s : name
|
|
263
|
+
@pending_binds = @pending_binds + sep + label + "=" + value.to_s
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def self.record_bind_str(idx = 0, value = "", name = "")
|
|
267
|
+
return if @pending_sql == ""
|
|
268
|
+
sep = @pending_binds == "" ? "" : ", "
|
|
269
|
+
label = name == "" ? "$" + idx.to_s : name
|
|
270
|
+
@pending_binds = @pending_binds + sep + label + "=\"" + value + "\""
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Adapter-agnostic surface for the model codegen. See db_sqlite.rb
|
|
274
|
+
# for design notes — same method names, same return conventions;
|
|
275
|
+
# only the underlying FFI delegate differs.
|
|
276
|
+
def self.exec(sql = "")
|
|
277
|
+
h = ensure_connection_dbh!
|
|
278
|
+
return false if h < 0
|
|
279
|
+
flush_pending! if @has_pending
|
|
280
|
+
t0 = Sock.sphttp_elapsed_micros
|
|
281
|
+
result = Pg.fresco_pg_exec(h, sql) == 0
|
|
282
|
+
dt = Sock.sphttp_elapsed_micros - t0
|
|
283
|
+
Fresco.add_sql_micros!(dt)
|
|
284
|
+
if log_sql?
|
|
285
|
+
emit(Color.dim(" [db] (" + fmt_micros(dt) + ") ") + color_sql(sql))
|
|
286
|
+
end
|
|
287
|
+
result
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def self.prepare(sql = "")
|
|
291
|
+
h = ensure_connection_dbh!
|
|
292
|
+
return -1 if h < 0
|
|
293
|
+
flush_pending! if @has_pending
|
|
294
|
+
@pending_t0 = Sock.sphttp_elapsed_micros
|
|
295
|
+
@has_pending = true
|
|
296
|
+
if log_sql?
|
|
297
|
+
@pending_sql = sql
|
|
298
|
+
@pending_label = Color.dim(" [db] ")
|
|
299
|
+
@pending_binds = ""
|
|
300
|
+
end
|
|
301
|
+
Pg.fresco_pg_prepare(h, sql)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Prepared-statement cache. See db_sqlite.rb for design notes —
|
|
305
|
+
# same shape, same StrIntHash pinning, same cursor-lifetime
|
|
306
|
+
# contract. For Postgres the reset clears the cached PGresult
|
|
307
|
+
# so the next step re-fires PQexecParams with whatever binds
|
|
308
|
+
# the caller sets between cached_prepare and step.
|
|
309
|
+
@stmt_cache = { "__t" => 0 }
|
|
310
|
+
@stmt_cache.delete("__t")
|
|
311
|
+
|
|
312
|
+
def self.cached_prepare(sql = "")
|
|
313
|
+
if @stmt_cache.key?(sql)
|
|
314
|
+
cid = @stmt_cache[sql]
|
|
315
|
+
Pg.fresco_pg_reset(cid)
|
|
316
|
+
flush_pending! if @has_pending
|
|
317
|
+
@pending_t0 = Sock.sphttp_elapsed_micros
|
|
318
|
+
@has_pending = true
|
|
319
|
+
if log_sql?
|
|
320
|
+
@pending_sql = sql
|
|
321
|
+
@pending_label = Color.dim(" [db cached] ")
|
|
322
|
+
@pending_binds = ""
|
|
323
|
+
end
|
|
324
|
+
return cid
|
|
325
|
+
end
|
|
326
|
+
cid = prepare(sql)
|
|
327
|
+
@stmt_cache[sql] = cid if cid > 0
|
|
328
|
+
cid
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
# The trailing `name = ""` is the column-name annotation for log
|
|
332
|
+
# output — see db_sqlite.rb for design notes.
|
|
333
|
+
def self.bind_str(cid = 0, idx = 0, value = "", name = "")
|
|
334
|
+
record_bind_str(idx, value, name) if log_sql?
|
|
335
|
+
Pg.fresco_pg_bind_str(cid, idx, value)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def self.bind_int(cid = 0, idx = 0, value = 0, name = "")
|
|
339
|
+
record_bind_int(idx, value, name) if log_sql?
|
|
340
|
+
Pg.fresco_pg_bind_int(cid, idx, value)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# See db_sqlite.rb for design notes on the two timing paths.
|
|
344
|
+
def self.step(cid = 0)
|
|
345
|
+
if @has_pending
|
|
346
|
+
result = Pg.fresco_pg_step(cid)
|
|
347
|
+
flush_pending!
|
|
348
|
+
return result
|
|
349
|
+
end
|
|
350
|
+
t0 = Sock.sphttp_elapsed_micros
|
|
351
|
+
result = Pg.fresco_pg_step(cid)
|
|
352
|
+
Fresco.add_sql_micros!(Sock.sphttp_elapsed_micros - t0)
|
|
353
|
+
result
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def self.col_str(cid = 0, idx = 0); Pg.fresco_pg_col_str(cid, idx); end
|
|
357
|
+
def self.col_int(cid = 0, idx = 0); Pg.fresco_pg_col_int(cid, idx); end
|
|
358
|
+
def self.finalize(cid = 0); Pg.fresco_pg_finalize(cid); end
|
|
359
|
+
def self.reset(cid = 0); Pg.fresco_pg_reset(cid); end
|
|
360
|
+
|
|
361
|
+
def self.last_rowid
|
|
362
|
+
h = ensure_connection_dbh!
|
|
363
|
+
return -1 if h < 0
|
|
364
|
+
Pg.fresco_pg_last_insert_rowid(h)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Eager-open the connection. Called once from Fresco::App#run at
|
|
368
|
+
# startup. Same rationale as the SQLite-template version —
|
|
369
|
+
# surface a bad DSN at process start instead of inside the first
|
|
370
|
+
# request.
|
|
371
|
+
def self.boot!
|
|
372
|
+
ensure_connection_dbh!
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
@tx_rollback_requested = false
|
|
376
|
+
|
|
377
|
+
def self.rollback!
|
|
378
|
+
@tx_rollback_requested = true
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Transaction support. Same shape as the SQLite-template version
|
|
382
|
+
# — explicit `Fresco::Db.rollback!` instead of rescue-on-raise
|
|
383
|
+
# because Spinel's rescue handling across FFI boundaries is iffy.
|
|
384
|
+
# Transaction primitives. See db_sqlite.rb for design notes —
|
|
385
|
+
# Spinel doesn't lower `&block` cleanly so we expose explicit
|
|
386
|
+
# begin/commit/rollback instead of a block-yielding transaction.
|
|
387
|
+
def self.begin_tx
|
|
388
|
+
exec("BEGIN")
|
|
389
|
+
@tx_rollback_requested = false
|
|
390
|
+
true
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def self.commit_tx
|
|
394
|
+
if @tx_rollback_requested
|
|
395
|
+
exec("ROLLBACK")
|
|
396
|
+
@tx_rollback_requested = false
|
|
397
|
+
return false
|
|
398
|
+
end
|
|
399
|
+
exec("COMMIT")
|
|
400
|
+
true
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|