Class Db
SQLite persistence for server-side binaries, on the engine the translator already bundles. Not com.codename1.db.Database, which needs a CodenameOneImplementation for every call.
Parameters are always bound, never interpolated: string concatenation into SQL is how injection happens, and a server parses input it did not write.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceA unit of work run insidetransaction(Db.Work). -
Method Summary
Modifier and TypeMethodDescriptionvoidclose()SYNCHRONIZED, like execute, query and transaction, and it keeps the handle when the close does not take.voidSwitches the database to write-ahead logging, which is what lets readers run while a writer is active.intRuns a statement that returns no rows.longThe rowid the most recent insert produced.static DbOpens (or creates) the database at the given path.Runs a query and returns every row as a column-name to value map.voidsetBusyTimeout(int millis) How long a blocked writer waits for a competing one before giving up.transaction(Db.Work body) Synchronized because a TRANSACTION is not one call.
-
Method Details
-
open
Opens (or creates) the database at the given path. ":memory:" gives a process-lifetime database, which is what a stateless function usually wants for a cache.- Throws:
IOException
-
execute
Runs a statement that returns no rows. Returns the number of rows changed.- Throws:
IOException
-
query
Runs a query and returns every row as a column-name to value map. Values are String, Long, Double or null, which is exactly what the JSON writer accepts.- Throws:
IOException
-
transaction
Synchronized because a TRANSACTION is not one call.
SQLite serializes each API call on a connection, which is what made "one shared connection is correct, and SQLite serializes it" look true. It serializes the calls, not the BEGIN/body/COMMIT sequence around them: a second handler sharing this Db can execute between another's BEGIN and COMMIT and have its write committed -- or rolled back -- by a request that knows nothing about it, or meet "cannot start a transaction within a transaction" and fail for a reason its own code cannot explain.
The monitor is reentrant, which is what makes this work: transaction() holds it for the whole callback and the execute() calls inside it re-enter freely. A pooled connection is used by one thread at a time anyway, so the cost there is an uncontended lock; a shared one is serialized, which is exactly what correctness requires of it.
- Throws:
Exception
-
enableWriteAheadLog
Switches the database to write-ahead logging, which is what lets readers run while a writer is active. Worth doing once after open for anything that serves concurrent requests; pointless for :memory:.- Throws:
IOException
-
setBusyTimeout
How long a blocked writer waits for a competing one before giving up. Without this, two connections writing at once produce SQLITE_BUSY immediately rather than queueing.- Throws:
IOException
-
lastInsertId
public long lastInsertId()The rowid the most recent insert produced.
SYNCHRONIZED, like execute, query, transaction and close -- it was the one operation here that was not, and the only one that hands the native handle over without holding the lock that guards it. close() frees the sqlite3 connection and then zeroes the field, so a reader that had already loaded the handle called sqlite3_last_insert_rowid on freed memory. The native's own null check cannot help with that: the pointer it is given is not null, it is dangling.
-
close
public void close()SYNCHRONIZED, like execute, query and transaction, and it keeps the handle when the close does not take.
sqlite3_close answers SQLITE_BUSY when a prepared statement is still alive on the connection, and does NOT destroy it. Zeroing the handle regardless threw away the only reference to a connection that was still open, so nothing could ever close it again -- the native connection and everything it owns leaked, once per shutdown that raced a borrower.
The lock is what makes that rare rather than merely recoverable: a DbPool.close() arriving while a borrowed connection is mid-query now waits for the query instead of closing underneath it.
-