# Class Database

java.lang.Object
com.codename1.db.Database

Direct Known Subclasses:
  `ThreadSafeDatabase`

public abstract class Database extends Object

Allows access to SQLite specifically connecting to a database and executing sql queries on the data. There is more thorough coverage of the `Database API here`.

The Database class abstracts the underlying SQLite of the device if available.

Notice that this might not be supported on all platforms in which case the `Database` will be null.

SQLite should be used for very large data handling, for small storage refer to `com.codename1.io.Storage` which is more portable.

##### Example

    Database db = null;
    Cursor cur = null;
    try {
        db = Database.openOrCreate("MyDB.db");
        db.execute("CREATE TABLE IF NOT EXISTS people (id INTEGER PRIMARY KEY, name TEXT)");
        db.execute("INSERT INTO people (name) VALUES (?)", new Object[] {"Alice"});

        cur = db.executeQuery("SELECT id, name FROM people ORDER BY id");
        while (cur.next()) {
            Row row = cur.getRow();
            System.out.println(row.getInteger(0) + " " + row.getString(1));
        }
    } finally {
        if (cur != null) {
            cur.close();
        }
        if (db != null) {
            db.close();
        }
    }

##### Encryption

Pass a `DatabaseConfig` to `#openOrCreate(java.lang.String, com.codename1.db.DatabaseConfig)` to encrypt the database at rest. Check `#isEncryptionSupported()` first, and read the security notes on `DatabaseConfig` before choosing how to key it.

- ## Field Summary

  Fields
  Modifier and Type
  Field
  Description
  `protected boolean`
  `inTransaction`
  Tracks whether a transaction is open on this instance, so the flat-transaction rules in the package documentation are enforced identically on every port rather than being re-derived from each engine's very different native semantics.

- ## Constructor Summary

  Constructors
  Constructor
  Description
  `Database()`

- ## Method Summary

  Modifier and Type
  Method
  Description
  `protected IOException`
  `abandonFailedCommit(Throwable cause)`
  Discards a transaction whose commit failed, and builds the exception to report it with.
  `static void`
  `beforeFirst(Cursor cursor)`
  Rewinds a cursor to before its first row.
  `abstract void`
  `beginTransaction()`
  Starts a transaction.
  `protected static String`
  `beginTransactionMode(String statement)`
  The locking mode a `BEGIN` asks for: `IMMEDIATE`, `EXCLUSIVE` or `DEFERRED`.
  `void`
  `changeKey(DatabaseConfig config)`
  Changes the key of this open database, or removes it entirely.
  `protected void`
  `checkBeginTransaction()`
  `protected void`
  `checkEndTransaction()`
  Rejects a commit or rollback with no open transaction.
  `protected void`
  `checkNoTransactionForKeyChange()`
  Rejects a key change while a transaction is open.
  `abstract void`
  `close()`
  Closes the database
  `protected static String[]`
  `coerceToText(Object[] params, String operation)`
  Renders parameters as text for ports that have not implemented typed binding.
  `abstract void`
  `commitTransaction()`
  Commits current transaction
  `static int`
  `count(Cursor cursor)`
  Returns the number of rows a cursor holds, or -1 where the port cannot determine it.
  `static void`
  `decrypt(String databaseName, DatabaseConfig config)`
  Decrypts an existing encrypted database in place, leaving a plain SQLite file.
  `static void`
  `delete(String databaseName)`
  Deletes database
  `static void`
  `encrypt(String databaseName, DatabaseConfig config)`
  Encrypts an existing plaintext database in place.
  `abstract void`
  `execute(String sql)`
  Execute an update query.
  `void`
  `execute(String sql, Object... params)`
  Execute an update query with params.
  `abstract void`
  `execute(String sql, String[] params)`
  Execute an update query with params.
  `abstract Cursor`
  `executeQuery(String sql)`
  This method should be called with SELECT type statements that return row set.
  `Cursor`
  `executeQuery(String sql, Object... params)`
  This method should be called with SELECT type statements that return row set it accepts object with params.
  `abstract Cursor`
  `executeQuery(String sql, String[] params)`
  This method should be called with SELECT type statements that return row set.
  `static boolean`
  `exists(String databaseName)`
  Indicates weather a database exists
  `static boolean`
  `forgetManagedKey(String keyAlias)`
  Removes the stored managed key for an alias.
  `static String`
  `getDatabasePath(String databaseName)`
  Returns the file path of the Database if exists and if supported on the platform.
  `protected boolean`
  `hasAttachments()`
  Whether this connection currently holds any attached database.
  `static boolean`
  `isBlobQueryParameterSupported()`
  Indicates whether `#executeQuery(java.lang.String, java.lang.Object[])` accepts `byte[]` parameters on this platform.
  `static boolean`
  `isCustomPathSupported()`
  Checks if this platform supports custom database paths.
  `static boolean`
  `isDatabaseBeingDeleted(String key)`
  Whether a delete is running on this file, for a port that opens outside this class.
  `static boolean`
  `isEncrypted(String databaseName)`
  Indicates whether a database file appears to be encrypted.
  `static boolean`
  `isEncryptionSupported()`
  Indicates whether this platform can open encrypted databases.
  `boolean`
  `isInTransaction()`
  Reports whether a transaction is currently open on this database.
  `static boolean`
  `isLegacyBehavior()`
  Returns whether the database API is running in legacy compatibility mode.
  `protected void`
  `markTransactionEnded()`
  Records that a transaction has actually ended.
  `static String`
  `normalizeDatabaseKey(String path)`
  A path reduced to one spelling, for use as an open-database registry key.
  `protected static String`
  `normalizeDatabasePathKey(String path)`
  `protected void`
  `noteConnectionClosed()`
  Releases what this connection held besides its own file.
  `protected void`
  `noteEngineTransactionState(boolean open)`
  Records the transaction state a port read back from its engine.
  `protected void`
  `noteFirstStatementTransactionControl(String sql)`
  Records the transaction control in the first statement of a script, and only that one.
  `protected void`
  `noteScriptTransactionControl(String sql)`
  Records what a script did to the transaction state.
  `protected static int`
  `openDatabaseCount(String key)`
  How many connections are open on a registry key, for callers that only want to look.
  `static Database`
  `openOrCreate(String databaseName)`
  Opens a database or create one if not exists.
  `static Database`
  `openOrCreate(String databaseName, DatabaseConfig config)`
  Opens an encrypted database, creating it if it does not exist.
  `protected static void`
  `registerOpenDatabase(String key)`
  Records that a connection to a database file has been opened.
  `protected static void`
  `releaseKeyChangeClaim(String key)`
  Ends the exclusive claim `#requireSoleConnectionForKeyChange(String)` took.
  `protected static void`
  `releaseOpenDatabase(String key)`
  Records that a connection to a database file has been closed.
  `protected void`
  `requireAttachmentsHeld()`
  Reports an attachment a reconciliation had to undo.
  `protected void`
  `requireQueryStatement(String sql)`
  The first word of a statement, upper cased, or an empty string.
  `protected static void`
  `requireSoleConnectionForKeyChange(String key)`
  Rejects a key change while the same file is open more than once.
  `protected void`
  `reserveAttachments(String sql)`
  Reserves the databases a script is about to attach, before the engine attaches them.
  `protected void`
  `reserveAttachments(String sql, Object[] params)`
  Reserves what a parameterized script is about to attach, including the bound values.
  `abstract void`
  `rollbackTransaction()`
  Rolls back current transaction
  `static void`
  `setLegacyBehavior(boolean legacy)`
  Turns legacy compatibility mode on or off.
  `protected boolean`
  `supportsNestedTransactions()`
  Rejects a nested `#beginTransaction()`, then records that one is open.
  `static boolean`
  `supportsWasNull(Row row)`
  Checks to see if the given row supports `#wasNull(com.codename1.db.Row)`.
  `protected static String`
  `toPragmaLiteral(String keyMaterial)`
  Renders a key literal for use as a `PRAGMA` argument.
  `protected static String`
  `transactionControlKeyword(String statement)`
  The transaction-control keyword a statement starts with, or null if it is not one.
  `static boolean`
  `wasNull(Row row)`
  Checks if the last value accessed from a given row was null.

  ### Methods inherited from class Object

  `clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- ## Field Details

  - ### inTransaction

    protected boolean inTransaction
    Tracks whether a transaction is open on this instance, so the flat-transaction rules in the package documentation are enforced identically on every port rather than being re-derived from each engine's very different native semantics.

- ## Constructor Details

  - ### Database

    public Database()

- ## Method Details

  - ### isCustomPathSupported

    public static boolean isCustomPathSupported()

    Checks if this platform supports custom database paths. On platforms that support this, you can pass a file path to `#openOrCreate(java.lang.String)`, `#exists(java.lang.String)`, `#delete(java.lang.String)`, and `#getDatabasePath(java.lang.String)`.

    ###### Returns

    True on platorms that support custom database paths.

  - ### isLegacyBehavior

    public static boolean isLegacyBehavior()

    Returns whether the database API is running in legacy compatibility mode.

    The behaviour of this API used to differ substantially between platforms. Those differences have been reconciled into the single contract documented in the `com.codename1.db` package, but applications written against the old, divergent behaviour may depend on it. Legacy mode restores each platform's previous behaviour exactly, and is intended as a transition aid rather than a permanent setting.

    Enable it with the `db.legacy` build hint, or from code before the first database call:

        Database.setLegacyBehavior(true);

    The package documentation lists precisely which behaviours the flag covers. Fixes for outright defects, and capabilities that previously threw and now work, are not covered, because no application can depend on those.

    ###### Returns

    true when the pre-normalization behaviour is in effect

  - ### setLegacyBehavior

    public static void setLegacyBehavior(boolean legacy)

    Turns legacy compatibility mode on or off.

    Call this before opening any database; cursors and connections capture the mode as they are created, so flipping it mid-session gives inconsistent results.

    ###### Parameters

    - `legacy`: true to restore the pre-normalization behaviour

    ###### See also

    - #isLegacyBehavior()

  - ### openOrCreate

    public static Database openOrCreate(String databaseName) throws IOException

    Opens a database or create one if not exists.

    ###### Parameters

    - `databaseName`: @param databaseName the name of the database. Platforms that support custom database paths (i.e. `#isCustomPathSupported()` return true), will also accept a file path here.

    ###### Returns

    Database Object or null if not supported on the platform

    ###### Throws

    - `IOException`: if database cannot be created
    Throws:
      `IOException`

  - ### exists

    public static boolean exists(String databaseName)

    Indicates weather a database exists

    NOTE: Not supported in the Javascript port. Will always return false.

    ###### Parameters

    - `databaseName`: @param databaseName the name of the database. Platforms that support custom database paths (i.e. `#isCustomPathSupported()` return true), will also accept a file path here.

    ###### Returns

    true if database exists

  - ### delete

    public static void delete(String databaseName) throws IOException

    Deletes database

    NOTE: This method is not supported in the Javascript port. Will silently fail.

    ###### Parameters

    - `databaseName`: @param databaseName the name of the database. Platforms that support custom database paths (i.e. `#isCustomPathSupported()` return true), will also accept a file path here.

    ###### Throws

    - `IOException`: if database cannot be deleted
    Throws:
      `IOException`

  - ### isDatabaseBeingDeleted

    public static boolean isDatabaseBeingDeleted(String key)

    Whether a delete is running on this file, for a port that opens outside this class.

    ###### Parameters

    - `key`: the identity the port registers connections under

    ###### Returns

    true while a delete holds the file

  - ### openDatabaseCount

    protected static int openDatabaseCount(String key)

    How many connections are open on a registry key, for callers that only want to look.

    ###### Parameters

    - `key`: a normalized path, or null

    ###### Returns

    the number of open connections, or 0 when the key is unknown

  - ### getDatabasePath

    public static String getDatabasePath(String databaseName)

    Returns the file path of the Database if exists and if supported on the platform.

    ###### Parameters

    - `databaseName`: @param databaseName The name of the database. Platforms that support custom database paths (i.e. `#isCustomPathSupported()` return true), will also accept a file path here.

    NOTE: Where `#isCustomPathSupported()` is false the databases are not filesystem backed, so what comes back identifies the database inside the platform's storage but is not a path `com.codename1.io.FileSystemStorage` can open.

    ###### Returns

    the file path of the database

  - ### openOrCreate

    public static Database openOrCreate(String databaseName, DatabaseConfig config) throws IOException

    Opens an encrypted database, creating it if it does not exist.

    The database is encrypted at rest using the key described by `config`. Every platform that supports encryption writes the same on-disk format, so a database created on one device can be opened on another and in the simulator.

    If `config` is null or describes a plaintext database this behaves exactly like `#openOrCreate(java.lang.String)`.

    ###### Example

        if (!Database.isEncryptionSupported()) {
            throw new IOException("This build cannot store data securely");
        }
        DatabaseConfig config = DatabaseConfig.managed();
        Database db = Database.openOrCreate("secure.db", config);
        config.wipe();

    ###### Parameters

    - `databaseName`: @param databaseName the name of the database. Platforms that support custom database paths (see `#isCustomPathSupported()`) also accept a file path.

    - `config`: how to key the database, or null for plaintext

    ###### Returns

    the open database

    ###### Throws

    - `DatabaseEncryptionException`: @throws DatabaseEncryptionException with `DatabaseEncryptionException#NOT_SUPPORTED` if encryption was requested on a platform that cannot provide it, or with `DatabaseEncryptionException#WRONG_KEY` if the key does not decrypt an existing database

    - `IOException`: if the database cannot be opened or created

    Throws:
      `IOException`

  - ### isEncryptionSupported

    public static boolean isEncryptionSupported()

    Indicates whether this platform can open encrypted databases.

    ###### Returns

    true if `#openOrCreate(java.lang.String, com.codename1.db.DatabaseConfig)` accepts an encrypting config

  - ### isEncrypted

    public static boolean isEncrypted(String databaseName)

    Indicates whether a database file appears to be encrypted.

    This inspects the file header: an unencrypted SQLite database begins with the ASCII bytes `SQLite format 3` followed by a zero byte, and an encrypted one does not. It is therefore a header sniff, not a cryptographic assertion -- a truncated or corrupt file also reports true, and a false result only means the file is a readable plaintext SQLite database. An empty file reports false: SQLite writes no header until the first change, so that is what a database that has been created and not yet written to looks like.

    ###### Parameters

    - `databaseName`: the name of the database

    ###### Returns

    false if the file exists and starts with a plaintext SQLite header, true otherwise

  - ### encrypt

    public static void encrypt(String databaseName, DatabaseConfig config) throws IOException

    Encrypts an existing plaintext database in place.

    The conversion is performed by the database engine as a single transaction, so an interruption leaves the original file intact rather than half-converted. Schema metadata such as `PRAGMA user_version` is preserved.

    ###### Parameters

    - `databaseName`: the name of an existing plaintext database

    - `config`: how the encrypted database should be keyed

    ###### Throws

    - `IOException`: if the database cannot be converted
    Throws:
      `IOException`

  - ### decrypt

    public static void decrypt(String databaseName, DatabaseConfig config) throws IOException

    Decrypts an existing encrypted database in place, leaving a plain SQLite file.

    ###### Parameters

    - `databaseName`: the name of an existing encrypted database

    - `config`: the config that currently opens the database

    ###### Throws

    - `IOException`: if the database cannot be converted
    Throws:
      `IOException`

  - ### forgetManagedKey

    public static boolean forgetManagedKey(String keyAlias)

    Removes the stored managed key for an alias.

    `#delete(java.lang.String)` deliberately leaves the managed key in place, because deleting and recreating a database is a normal thing to do and should not discard the identity that protects it. Call this explicitly when the key really should be forgotten -- after which any remaining database encrypted with it is permanently unreadable.

    ###### Parameters

    - `keyAlias`: @param keyAlias the alias passed to `DatabaseConfig#managed(java.lang.String)`, or the database name when `DatabaseConfig#managed()` was used

    ###### Returns

    true if a key was removed

  - ### beforeFirst

    public static void beforeFirst(Cursor cursor) throws IOException

    Rewinds a cursor to before its first row.

    Uses `CursorExt#beforeFirst()` when the cursor provides it, and falls back to `Cursor#position(int)` with -1 otherwise.

    ###### Parameters

    - `cursor`: the cursor to rewind

    ###### Throws

    - `IOException`: if the cursor is closed or the rewind fails
    Throws:
      `IOException`

  - ### count

    public static int count(Cursor cursor) throws IOException

    Returns the number of rows a cursor holds, or -1 where the port cannot determine it.

    This can be expensive. Only Android's engine knows the count without looking; every other port walks the result set to the end and rewinds, so this costs what the query costs and should not be called on the EDT for a large one. See `CursorExt#getCount()`.

    ###### Parameters

    - `cursor`: the cursor to measure

    ###### Returns

    the row count, or -1 where the port cannot determine it

    ###### Throws

    - `IOException`: if the cursor is closed
    Throws:
      `IOException`

  - ### isBlobQueryParameterSupported

    public static boolean isBlobQueryParameterSupported()

    Indicates whether `#executeQuery(java.lang.String, java.lang.Object[])` accepts `byte[]` parameters on this platform.

    Blob values can always be written with `#execute(java.lang.String, java.lang.Object[])`. Using one as a query parameter, for example in `WHERE digest = ?`, needs engine support that not every port can provide.

    ###### Returns

    true if blobs may be used as query parameters

  - ### changeKey

    public void changeKey(DatabaseConfig config) throws IOException

    Changes the key of this open database, or removes it entirely.

    Passing a plaintext config decrypts the database. The engine performs the conversion as a single transaction and preserves schema metadata such as `PRAGMA user_version`.

    Ports that support encryption override this. The default implementation reports that the platform cannot do it; it is deliberately concrete rather than abstract, because `Database` is public and is subclassed outside this repository.

    ###### Parameters

    - `config`: the new key, or `DatabaseConfig#plain()` to decrypt

    ###### Throws

    - `IOException`: if the key cannot be changed
    Throws:
      `IOException`

  - ### wasNull

    public static boolean wasNull(Row row) throws IOException

    Checks if the last value accessed from a given row was null. Not all platforms support wasNull(). If the platform does not support it, this will just return false.

    Check `#supportsWasNull(com.codename1.db.Row)` to see if the platform supports wasNull().

    Currently wasNull() is supported on UWP, iOS, Android, and JavaSE (Simulator).

    ###### Parameters

    - `row`: The row to check.

    ###### Returns

    True if the last value accessed was null.

    ###### Throws

    - `IOException`

    ###### See also

    - RowExt#wasNull()

    - #supportsWasNull(com.codename1.db.Row)

    Throws:
      `IOException`

  - ### supportsWasNull

    public static boolean supportsWasNull(Row row) throws IOException

    Checks to see if the given row supports `#wasNull(com.codename1.db.Row)`.

    ###### Parameters

    - `row`: The row to check.

    ###### Returns

    True if the row supports wasNull().

    ###### Throws

    - `IOException`

    ###### See also

    - #wasNull(com.codename1.db.Row)

    - RowExt#wasNull()

    Throws:
      `IOException`

  - ### isInTransaction

    public boolean isInTransaction()

    Reports whether a transaction is currently open on this database.

    ###### Returns

    true between a successful `#beginTransaction()` and its commit or rollback

  - ### toPragmaLiteral

    protected static String toPragmaLiteral(String keyMaterial)

    Renders a key literal for use as a `PRAGMA` argument.

    Raw keys are already the blob literal `x'...'`, which has to reach the engine unquoted as a literal rather than as a string. Passphrases are arbitrary text, so they are single quoted with any embedded single quote doubled. Interpolating a passphrase directly would let one containing a quote change the statement.

    ###### Parameters

    - `keyMaterial`: the value from `DatabaseConfig#resolveKeyMaterial(java.lang.String)`

    ###### Returns

    the text to place after `PRAGMA key =` or `PRAGMA rekey =`

  - ### noteScriptTransactionControl

    protected void noteScriptTransactionControl(String sql)

    Records what a script did to the transaction state.

    `#execute(java.lang.String)` hands SQL straight to the engine, so `execute("BEGIN")` opens a real transaction that `#beginTransaction()` never saw. Left untracked, the two ways of saying the same thing disagree: a key change would be allowed inside a transaction opened this way, and `beginTransaction(); execute("COMMIT")` would leave the flag set over a transaction that has already ended, so the next `commitTransaction()` addresses one that is not there.

    Ports call this after `#execute(java.lang.String)`, and after a parameterized call, with the SQL they ran. A `BEGIN` opening a trigger body is not transaction control -- the splitter keeps a trigger together, so its body is never a statement here -- and SAVEPOINT is not tracked at all, because it nests and this API's transactions do not.

    A script that failed partway had already run everything before the statement that failed, and the engine does not undo it. The control statements are read in order either way: they are the least likely statement to be the one that failed, since `BEGIN` and `COMMIT` reference nothing that can be missing. So `BEGIN; INSERT INTO missing_table VALUES(1)` is left open, which it is, and `BEGIN; COMMIT; INSERT INTO missing_table VALUES(1)` is left closed, which it also is -- where treating any `BEGIN` as still open would hold the flag over a committed transaction and block every key change until the connection was closed.

    The one case left wrong is a script whose own `BEGIN` failed, reported as open when nothing is. That is the recoverable direction -- `#rollbackTransaction()` clears it -- and the one that refuses a key change rather than allowing one underneath a live transaction.

    ###### Parameters

    - `sql`: the SQL that was run, whether or not it finished

  - ### hasAttachments

    protected boolean hasAttachments()

    Whether this connection currently holds any attached database.

    For a port whose key change is not performed in place. Re-keying through `PRAGMA rekey` keeps the connection, and its attachments with it; converting through an export does not -- the old connection closes, and SQLite drops every attachment when it does. A replacement handle that restores only pragmas looks alive and answers "no such table" for every attached schema, and the reservations taken for those files stay held, so the file nobody is attached to any more still cannot be deleted.

    Re-attaching is not an answer this layer can give: an encrypted attachment was opened with a key this connection did not keep, and there is nowhere honest to get it from. So a port in that position refuses the conversion and says what to detach.

    ###### Returns

    true if at least one ATTACH is live on this connection

  - ### reserveAttachments

    protected void reserveAttachments(String sql) throws IOException

    Reserves the databases a script is about to attach, before the engine attaches them.

    Called by every port at the top of `#execute(String)`. Reserving first is what makes this safe rather than merely watchful: if the file is being deleted the reservation is refused and this throws, so the ATTACH never runs and there is nothing to undo. Compensating afterwards -- attaching, then detaching again when the reservation lost -- could itself fail, on a locked database or inside a transaction, and left the attachment live with the delete already under way.

    Over-reserving is the deliberate direction. A statement that is reserved and then fails to execute leaves a reservation that is given back when the connection closes; the cost is a delete refused until then. The other direction loses data.

    A relative name is reserved under this port's database directory, which is not always the file the engine opens: SQLite resolves a relative name against the process working directory, and only the ports whose engine has no filesystem -- the browser, where a name is a pool entry -- resolve it the same way this does. Predicting the other answer is not possible from here; the working directory belongs to the process, differs per platform, and is not something this API exposes or controls. So the reservation covers the file a Codename One name means, which is what an application attaching `'data.db'` almost certainly intends, and the reconciliation afterwards is what covers the file the engine really opened -- including undoing an attachment that turns out to be unholdable. Attach by an absolute path from `#getDatabasePath(String)` to be reserved exactly.

    ###### Parameters

    - `sql`: the script about to run

    ###### Throws

    - `IOException`: if a database it attaches is being deleted or converted, or if an earlier attachment had to be undone and nothing has reported that yet
    Throws:
      `IOException`

  - ### reserveAttachments

    protected void reserveAttachments(String sql, Object[] params) throws IOException

    Reserves what a parameterized script is about to attach, including the bound values.

    `ATTACH DATABASE ? AS aux` names its file in the parameters, so the statement text alone cannot say what is about to be attached -- and the reservation has to exist before the engine attaches it, because a reservation refused afterwards cannot undo an attach.

    Every parameter that resolves to a database identity is reserved, not just the one the placeholder stands for. Working out which parameter belongs to the ATTACH would mean counting placeholders through quoting and comments for no gain: an over-reservation costs a delete refused until the reconciliation gives it back, moments later.

    ###### Parameters

    - `sql`: the script about to run
    - `params`: the values bound to it, any of which may be the file

    ###### Throws

    - `IOException`: if a database it may attach is being deleted or converted
    Throws:
      `IOException`

  - ### requireAttachmentsHeld

    protected void requireAttachmentsHeld() throws IOException

    Reports an attachment a reconciliation had to undo.

    Thrown from the start of the next statement rather than from the one that attached, because ports reconcile from a `finally` and a throw from there replaces whatever failure the statement was already reporting -- the one error the caller most needs. So the attach is reversed as it is discovered, and the news waits for a place that can carry it.

    Late, but not lost, and not misattributed either: the message names an ATTACH rather than "this statement". The alternative is silence, and an application whose attachment quietly did not happen reads the absence of its tables as corruption.

    ###### Throws

    - `IOException`: if a reconciliation undid an attachment and nothing has reported it yet
    Throws:
      `IOException`

  - ### noteConnectionClosed

    protected void noteConnectionClosed()

    Releases what this connection held besides its own file.

    Every port calls this as it closes. SQLite drops a connection's attachments when it closes, so the registrations taken for them have to go at the same moment -- otherwise a database that was attached once could never be deleted again for the life of the process.

  - ### noteFirstStatementTransactionControl

    protected void noteFirstStatementTransactionControl(String sql)

    Records the transaction control in the first statement of a script, and only that one.

    For the legacy hint, where a script runs as far as its first statement and the rest is discarded. Reading the whole string there would credit statements that never ran: a `BEGIN; COMMIT` would be read as opening and closing, when only the `BEGIN` was executed and the transaction is still open -- the direction that lets a key change run over it.

    ###### Parameters

    - `sql`: the script that was handed to the engine

  - ### noteEngineTransactionState

    protected void noteEngineTransactionState(boolean open)

    Records the transaction state a port read back from its engine.

    The reliable answer where a script runs as a whole. SQLite stops at the first statement that fails and nothing outside can see which one that was, so reading the script cannot tell an unexecuted trailing `COMMIT` from an executed one -- and getting that wrong either clears the flag over a live transaction, which lets a key change replace the database underneath uncommitted work, or holds it over a finished one, which blocks every key change until the connection closes. The engine knows; ports that can ask it should.

    ###### Parameters

    - `open`: whether the engine reports a transaction in progress

  - ### transactionControlKeyword

    protected static String transactionControlKeyword(String statement)

    The transaction-control keyword a statement starts with, or null if it is not one.

    Shared so that a port which has to act on transaction control -- the simulator routes it through JDBC, because there the transaction is the connection's autocommit flag rather than something the driver reads back out of the SQL -- classifies it exactly as the tracking here does. Two copies of this drifted apart once already.

    Only a bare `ROLLBACK` counts: `ROLLBACK TO <savepoint>` unwinds within the transaction rather than ending it, as SAVEPOINT and RELEASE do.

    ###### Parameters

    - `statement`: a single statement

    ###### Returns

    `BEGIN`, `COMMIT`, `END`, `ROLLBACK`, or null

  - ### beginTransactionMode

    protected static String beginTransactionMode(String statement)

    The locking mode a `BEGIN` asks for: `IMMEDIATE`, `EXCLUSIVE` or `DEFERRED`.

    Reads the word after `BEGIN` rather than searching the statement for those names. The words are ordinary text anywhere else, so `/* IMMEDIATE migration */ BEGIN` and `BEGIN /* EXCLUSIVE note */ TRANSACTION` are both deferred -- and a port that searched would take a write lock on them that the same SQL does not take on any other platform.

    Anything that is not one of the three, including a bare `BEGIN` and the optional `TRANSACTION` keyword, is deferred, which is what SQLite does with it.

    ###### Parameters

    - `statement`: a statement whose first keyword is `BEGIN`

    ###### Returns

    `IMMEDIATE`, `EXCLUSIVE` or `DEFERRED`

  - ### requireQueryStatement

    protected void requireQueryStatement(String sql) throws IOException

    The first word of a statement, upper cased, or an empty string.

    Comments count as whitespace here, because they do to the engine: `/* migration */ BEGIN` opens a transaction, and reading the keyword as empty would leave this believing none was opened. The Android port relies on the same fact deliberately, prefixing a comment to a ROLLBACK to get it past a statement classifier that reads the first three characters. Refuses transaction control handed to `executeQuery`.

    A cursor runs its statement when it is stepped, so `executeQuery("BEGIN")` opens a real transaction that nothing here recorded: `#isInTransaction()` answers false over an open one, a typed commit fails, and a key change is allowed across live work. Navigating the cursor could run the control statement a second time on top of that.

    The tracked ways in are `#beginTransaction()` and `execute`, both of which record what they ran. This is not a capability being withdrawn: a transaction control statement returns no rows, so asking for a cursor over one was never useful.

    Skipped under the legacy hint, which restores what each port used to do with it.

    ###### Parameters

    - `sql`: the statement handed to executeQuery

    ###### Throws

    - `IOException`: if the statement is transaction control
    Throws:
      `IOException`

  - ### normalizeDatabaseKey

    public static String normalizeDatabaseKey(String path)

    A path reduced to one spelling, for use as an open-database registry key.

    Two names for one file have to reach the registry as one entry, or the claim a key change takes does not cover the other connection and the file is rewritten underneath it. The ports with a real filesystem behind them (Android, the simulator) ask it to canonicalize, which also resolves symlinks. The ports translated ahead of time have no such call to make, so this collapses what can be collapsed without touching the disk: repeated separators, `.` segments, and `..` against the segment before it.

    A symlink still reaches the registry under two names. That is a smaller hole than `/a/./b` and `/a/b` counting as different databases, which is what an application writing a custom path actually produces.

    ###### Parameters

    - `path`: a native filesystem path, or null

    ###### Returns

    the reduced path, or null for a null input The shared path reduction, for a port that needs it outside a Database instance.

    The implementations resolve a managed key's implicit alias from this, so that two accepted spellings of one file derive one key rather than two.

    ###### Parameters

    - `path`: a native filesystem path, or null

    ###### Returns

    the reduced path, or null for a null input

  - ### normalizeDatabasePathKey

    protected static String normalizeDatabasePathKey(String path)

  - ### registerOpenDatabase

    protected static void registerOpenDatabase(String key) throws IOException

    Records that a connection to a database file has been opened.

    Ports call this once they have a connection, and `#releaseOpenDatabase(String)` when they let it go. A port whose engine cannot be given two connections to one file need not call either.

    ###### Parameters

    - `key`: identifies the file, canonically enough that two spellings of one path agree, or null for a connection that cannot say which file it holds

    ###### Throws

    - `IOException`: if the file is being deleted or re-keyed, or -- for a null key -- if any database is, since such a connection cannot be ruled out as that one
    Throws:
      `IOException`

  - ### releaseOpenDatabase

    protected static void releaseOpenDatabase(String key)

    Records that a connection to a database file has been closed.

    ###### Parameters

    - `key`: the key the connection was registered under

  - ### requireSoleConnectionForKeyChange

    protected static void requireSoleConnectionForKeyChange(String key) throws IOException

    Rejects a key change while the same file is open more than once.

    Ports call this from `#changeKey(DatabaseConfig)`, after `#checkNoTransactionForKeyChange()`. The count includes the connection asking, so more than one means somebody else holds the file too.

    ###### Parameters

    - `key`: the key this connection was registered under

    ###### Throws

    - `IOException`: if another connection has the same file open
    Throws:
      `IOException`

  - ### releaseKeyChangeClaim

    protected static void releaseKeyChangeClaim(String key)

    Ends the exclusive claim `#requireSoleConnectionForKeyChange(String)` took.

    Ports call this from a `finally` around the rewrite, so a key change that throws does not leave the file barred from opening for the rest of the process.

    ###### Parameters

    - `key`: the key the claim was taken under

  - ### checkNoTransactionForKeyChange

    protected void checkNoTransactionForKeyChange() throws IOException

    Rejects a key change while a transaction is open.

    Ports call this at the top of `#changeKey(DatabaseConfig)`. Re-keying is not a statement inside the transaction: depending on the engine it either rewrites the file in place or exports into a new one and swaps it under the connection. Either way the open transaction has nowhere to land -- an export copies the uncommitted rows into the file that becomes the database, and a following commit or rollback addresses a connection that has no transaction to end. Refusing is the only outcome that keeps `commit` and `rollback` meaning what they say, and the caller loses nothing: it can end the transaction and change the key after.

    ###### Throws

    - `IOException`: if a transaction is open
    Throws:
      `IOException`

  - ### supportsNestedTransactions

    protected boolean supportsNestedTransactions()

    Rejects a nested `#beginTransaction()`, then records that one is open.

    Transactions are flat: only that model is expressible on all of the engines behind this API. Ports call this at the top of `#beginTransaction()`. In legacy mode the check is skipped, because a nested begin used to be accepted on Android.

    ###### Throws

    - `IOException`: if a transaction is already open Whether this engine counts nested transactions rather than rejecting the second one.

    Only Android's wrapper does, and only that port's legacy behaviour allowed nesting. On the others a second BEGIN reaches SQLite and fails, and the port clears its flag on the way out -- so allowing the call would report no transaction while the first one is still open, and a caller that caught the expected failure could then change the key across it.

  - ### checkBeginTransaction

    protected void checkBeginTransaction() throws IOException
    Throws:
      `IOException`

  - ### checkEndTransaction

    protected void checkEndTransaction() throws IOException

    Rejects a commit or rollback with no open transaction.

    Ports call this at the top of `#commitTransaction()` and `#rollbackTransaction()`, and `#markTransactionEnded()` once the engine has ended it. The two are separate so that a port can end the transaction on a path that does not commit it, which is what `#abandonFailedCommit(Throwable)` does.

    In legacy mode the check is skipped.

    ###### Throws

    - `IOException`: if no transaction is open
    Throws:
      `IOException`

  - ### markTransactionEnded

    protected void markTransactionEnded()
    Records that a transaction has actually ended. Call only after the engine has committed or rolled back successfully.

  - ### abandonFailedCommit

    protected IOException abandonFailedCommit(Throwable cause)

    Discards a transaction whose commit failed, and builds the exception to report it with.

    A commit that fails cannot be retried, so the only remaining outcome is a rollback. The engines disagree about what they leave behind: Android has already ended the transaction by the time it reports the failure, while the SQLite C API and JDBC leave it open. Ports call this from the failure path of `#commitTransaction()`, after making a best effort to roll back, so that callers see one behavior everywhere -- no transaction is open, and `#beginTransaction()` works again.

    ###### Parameters

    - `cause`: the failure the engine reported

    ###### Returns

    the exception the caller should throw

  - ### beginTransaction

    public abstract void beginTransaction() throws IOException

    Starts a transaction.

    Transactions are flat. Calling this while a transaction is already open throws, and committing or rolling back returns the connection to autocommit. Closing a database with an open transaction rolls it back.

    ###### Throws

    - `IOException`: if the database is not open, or a transaction is already in progress
    Throws:
      `IOException`

  - ### commitTransaction

    public abstract void commitTransaction() throws IOException

    Commits current transaction

    NOTE: Not supported in Javascript port. This method will do nothing when running in Javascript.

    ###### Throws

    - `IOException`: if database is not opened or transaction was not started
    Throws:
      `IOException`

  - ### rollbackTransaction

    public abstract void rollbackTransaction() throws IOException

    Rolls back current transaction

    NOTE: Not supported in Javascript port. This method will do nothing when running in Javascript.

    ###### Throws

    - `IOException`: if database is not opened or transaction was not started
    Throws:
      `IOException`

  - ### close

    public abstract void close() throws IOException

    Closes the database

    ###### Throws

    - `IOException`
    Throws:
      `IOException`

  - ### execute

    public abstract void execute(String sql) throws IOException

    Execute an update query. Used for INSERT, UPDATE, DELETE and similar sql statements.

    ###### Parameters

    - `sql`: the sql to execute

    ###### Throws

    - `IOException`
    Throws:
      `IOException`

  - ### execute

    public abstract void execute(String sql, String[] params) throws IOException

    Execute an update query with params. Used for INSERT, UPDATE, DELETE and similar sql statements. The sql can be constructed with '?' and the params will be binded to the query

    ###### Parameters

    - `sql`: the sql to execute

    - `params`: to bind to the query where the '?' exists

    ###### Throws

    - `IOException`
    Throws:
      `IOException`

  - ### execute

    public void execute(String sql, Object... params) throws IOException

    Execute an update query with params. Used for INSERT, UPDATE, DELETE and similar sql statements. The sql can be constructed with '?' and the params will be binded to the query

    ###### Parameters

    - `sql`: the sql to execute

    - `params`: @param params to bind to the query where the '?' exists, supported object types are String, byte[], Double, Long and null

    ###### Throws

    - `IOException`
    Throws:
      `IOException`

  - ### coerceToText

    protected static String[] coerceToText(Object[] params, String operation) throws IOException

    Renders parameters as text for ports that have not implemented typed binding.

    This is the fallback path only. Ports that can bind by type override the varargs methods and never reach here, which is why hitting a `byte[]` is an error rather than something to paper over: silently storing the result of `byte[].toString()` would write the array's identity hash into the database.

    ###### Parameters

    - `params`: the parameters supplied by the caller

    - `operation`: the calling method name, used in the error message

    ###### Returns

    the parameters rendered as text, preserving nulls

    ###### Throws

    - `IOException`: if a parameter is a `byte[]` and this port cannot bind blobs
    Throws:
      `IOException`

  - ### executeQuery

    public abstract Cursor executeQuery(String sql, String[] params) throws IOException

    This method should be called with SELECT type statements that return row set.

    ###### Parameters

    - `sql`: the sql to execute

    - `params`: to bind to the query where the '?' exists

    ###### Returns

    a cursor to iterate over the results

    ###### Throws

    - `IOException`
    Throws:
      `IOException`

  - ### executeQuery

    public Cursor executeQuery(String sql, Object... params) throws IOException

    This method should be called with SELECT type statements that return row set it accepts object with params.

    ###### Parameters

    - `sql`: the sql to execute

    - `params`: @param params to bind to the query where the '?' exists, supported object types are String, byte[], Double, Long and null

    ###### Returns

    a cursor to iterate over the results

    ###### Throws

    - `IOException`
    Throws:
      `IOException`

  - ### executeQuery

    public abstract Cursor executeQuery(String sql) throws IOException

    This method should be called with SELECT type statements that return row set.

    ###### Parameters

    - `sql`: the sql to execute

    ###### Returns

    a cursor to iterate over the results

    ###### Throws

    - `IOException`
    Throws:
      `IOException`