Skip to content

Make the database portable and encryptable (#3848) - #5526

Open
shai-almog wants to merge 86 commits into
masterfrom
feature/portable-encryptable-database
Open

Make the database portable and encryptable (#3848)#5526
shai-almog wants to merge 86 commits into
masterfrom
feature/portable-encryptable-database

Conversation

@shai-almog

@shai-almog shai-almog commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Resolves #3848.

The request was database encryption. Encryption is here, but the reason it took a
whole PR is that com.codename1.db was not one API over SQLite -- it was five
unrelated implementations that happened to share an interface, and there was no
sensible place to add a key to.

What was actually wrong

Verified in the source, not from memory:

Android iOS Simulator JS Windows / Linux
openOrCreate works works works works returns null, callers NPE
last() / prev() / position() works IOException("Unsupported") always threw position(n) always gave row 0 -
getPosition() base 0 starts at -1 1 0 -
first() moves to row 0 returns true on an empty set, then reads unset memory threw - -
getBlob works { return nil; } works threw -
Parameter binding typed text only typed text -
execute(sql) multi-statement rejects runs all silently runs only the first no -
Transactions ref-counted raw BEGIN rollback leaked autocommit println no-ops -
Blob query params threw RuntimeException on every port

Plus three defects worth calling out on their own: sqlDbClose called
sqlite3_free on a sqlite3*, so no iOS connection was ever closed, the WAL was
never checkpointed and the handle went to the wrong allocator; SEDatabase leaked
a PreparedStatement per query; and ThreadSafeDatabase.close() was fire and
forget, so a following delete() raced it.

And no device test touched Database at all -- 142 test classes in the screenshot
suite, none of them about databases. That is why Windows and Linux were allowed to
ship with no implementation.

What this does

One contract. com.codename1.db/package-info.java now states what every port
must do, and DatabaseConformanceSuite in the framework checks it. Seven device
tests run that suite on every port in CI; two of them run in legacy mode.

One cursor implementation. AbstractDBCursor derives all navigation from two
primitives, rewind() and stepForward(), so ports stop re-deriving it. Seeks
rewind and re-step rather than buffering: sqlite3_column_* is only valid on the
current row, so buffering would mean copying every column of every row stepped
past, blobs included. This is what Android's windowed cursor already does on a
window miss.

Encryption, with a passphrase, a keystore-managed random key, or raw bytes.
Managed keys resolve in the core so every platform derives identical material from
an alias, and a key that cannot be stored is fatal rather than a silent downgrade
to plaintext.

Windows and Linux get a database at all.

JavaScript stops using WebSQL, which Chrome removed in 119 and Firefox never
implemented, in favour of the same SQLite compiled to WebAssembly.

Compatibility

Ten behaviours change in ways an application could depend on. All ten are restored
by the db.legacy build hint, per platform, and two device tests assert that it
really does restore them -- so the promise is testable rather than aspirational.
The table is in the developer guide.

The hint deliberately does not cover defects, or capabilities that used to throw
and now work. Nobody can depend on getBlob returning null.

Cost, when unused

Nothing. iOS keeps the system SQLite unless the app references DatabaseConfig;
Android's SQLCipher package is deleted and its AAR never added; Windows and Linux
compile the engine to an empty object; the JavaScript builder prunes 1.5MB from
bundles that never open a database. Two catalog tests hold that line, because the
entry is keyed on DatabaseConfig rather than the package -- keying it on the
package would bundle SQLCipher for every database app and push Android's minimum
SDK from 19 to 23 for people who never asked for encryption.

Verification

  • 4,754 core unit tests, 230 JavaSE port tests, 28 catalog tests, 10 new
    SEDatabaseConformanceTest cases, all green.
  • SpotBugs 0 findings across android, ios, codenameone-maven-plugin and
    ByteCodeTranslator.
  • scripts/ci/db-cipher-interop.sh, wired into PR CI, writes an encrypted database
    with our engine and reads it with the stock sqlcipher client, and vice versa,
    with both a raw key and a passphrase. This is the check that matters: a cipher
    misconfiguration produces files each platform reads happily and nothing else can
    touch, which no single-platform test would catch.
  • Verified against the real sqlcipher 4.17.0 client and the real
    net.zetetic:sqlcipher-android AAR, not against assumed APIs.

Three things the spikes caught

Worth recording, because each would have shipped broken:

  1. sqlcipher_export() does not exist in SQLite3MC, so the ATTACH-based
    migration everyone writes would have failed. PRAGMA rekey works, and also
    preserves user_version, which sqlcipher_export drops.
  2. A wrong key surfaces at getConnection() on the simulator but on first read on
    the device ports, so both paths need handling.
  3. SQLiteMCSqlCipherConfig.getDefault() really does produce files real SQLCipher
    cannot open; getV4Defaults() is required. One line, and nothing but a
    cross-engine test would have found it.

Review rounds

Nineteen findings from the automated reviewers, all real, all fixed. The ones worth knowing about:

  • Database.encrypt() could never have worked on Android. The system SQLite has no cipher, so a
    plaintext database opened through it can never be re-keyed; there is now a platform hook that
    routes the migration through SQLCipher.
  • A managed key resolves its keystore alias from the database name, and every port passed null
    when re-keying, so changeKey(managed()) raised a NullPointerException rather than encrypting.
  • Managed key aliases folded /, \, : and space all to _, so customer/db and customer_db
    shared one key and forgetting either destroyed the other.
  • Closing a database with an open cursor dropped the only statement handle without finalizing it,
    and sqlite3_close_v2 then leaves a zombie connection alive forever.
  • isEncrypted() reported every plaintext JavaScript database as encrypted, because that port has
    no readable path and a failed header read is indistinguishable from ciphertext.
  • Java longs lost precision crossing the JavaScript bridge in both directions.
  • PRAGMA rekey interpolated the key directly, so a passphrase containing a quote changed the
    statement.

Two of the fixes are covered by new conformance checks, including one verified by reinstating the
old code and watching it fail: the exhausted-cursor count went 5 to 8 before the fix.

Two decisions worth a second opinion

  • maven/sqlite-jdbc is no longer frozen. It was pinned and excluded from
    publication because a shade of a fixed driver never changed. It now carries the
    engine used to read encrypted databases, so it has to track upstream security
    releases. Costs ~13.5MB per release, which is what the freeze was avoiding.
  • The engine is SQLite3 Multiple Ciphers, not SQLCipher, on the targets we
    compile. It ships a prebuilt amalgamation where SQLCipher would need its
    configure script run per build, and it is what the simulator's JDBC driver is
    already built from -- so iOS, Windows, Linux, JavaScript and the simulator all
    run one engine at one version. Android still uses the SQLCipher AAR because it
    cannot compile C in our build; both write the same format, which is the part
    that matters.

Companion PR

The build-side gating is mirrored in codenameone/BuildDaemon#172, which is green.

🤖 Generated with Claude Code

shai-almog and others added 7 commits August 6, 2026 10:46
The database API was five unrelated implementations sharing an interface.
Cursors counted from zero on some ports and one on others, iOS reported
success on an empty result set and returned null for every blob, the
simulator could not seek at all, and no port could encrypt anything.

This lands the port-independent half:

- package-info.java now carries the normative contract every port must
  satisfy: zero-based positions, first() lands on a row, execute() runs a
  whole script while the parameterized forms take exactly one statement,
  typed parameter binding, flat transactions, IOException with a chained
  cause, idempotent close.

- AbstractDBCursor derives all navigation from two primitives, rewind()
  and stepForward(), so every port gets identical semantics rather than
  each reimplementing them. Seeks rewind and re-step, which is what
  Android's windowed cursor already does on a window miss; buffering rows
  instead would mean materializing every column of every row stepped past.

- SQLStatementSplitter splits a script the way SQLite does, respecting
  string literals, quoted identifiers, comments and CREATE TRIGGER bodies.

- DatabaseConfig, DatabaseEncryptionException and ManagedKeys add keyed
  opens. Managed keys are resolved in the core so every platform derives
  identical material from an alias, and a key that cannot be stored is
  fatal rather than a silent downgrade to plaintext.

- db.legacy restores each platform's previous behaviour for the ten
  changes that alter a previously successful result. It is read lazily,
  because the generated stubs set it after Display.init.

Blob parameters now raise IOException rather than RuntimeException, and
the truncated javadoc samples in Database, Cursor and Row are replaced
with complete ones.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The simulator was the weakest database implementation, which mattered
more than it sounds: it is where people develop. Its cursor could not
seek at all, because the JDBC driver only produces TYPE_FORWARD_ONLY
result sets and first(), last(), prev() and position() each threw
outright. execute() silently ran the first statement of a script and
discarded the rest. rollbackTransaction() left the connection outside
autocommit, so every following statement quietly joined a new implicit
transaction. Every query leaked its PreparedStatement.

- SECursor now extends AbstractDBCursor, rewinding by re-executing the
  statement. The simulator has working random access for the first time.
- execute(String) splits the script and runs each statement, rather than
  trusting a driver to decide how much of it to run.
- The parameterized forms reject a multi-statement script instead of
  dropping its tail.
- Statements are closed on the success path, cursors are closed with the
  database, close() is idempotent and rollback restores autocommit.
- getColumnName reports the result set label, matching getColumnIndex,
  so an aliased column can be found under the name it was found by.

The shaded driver moves from org.xerial to io.github.willena, which is
the same driver with SQLite3MC compiled in: same package, same config,
verified identical on plaintext databases, plus the SQLCipher-compatible
cipher the simulator needs to open a database written on a device.
getV4Defaults() is required over getDefault() - the latter selects
SQLite3MC's own variant, which real SQLCipher cannot read.

That driver also stops being frozen. Freezing assumed the shaded content
never changed; it now carries a crypto-bearing engine that has to track
upstream security releases.

SEDatabaseConformanceTest runs the portable contract against the real
SEDatabase headlessly in about two seconds, including both the strict
and legacy modes and the encrypt/decrypt round trip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
iOS was the port the "radically different implementations" complaint is
really about, and it had real bugs behind the divergence:

- sqlDbClose called sqlite3_free on the connection handle. That never
  closed it, leaked the file descriptor, skipped the WAL checkpoint and
  handed the pointer to the wrong allocator. Now sqlite3_close_v2.
- sqlCursorValueAtColumnBlob was { return nil; }, so iOS could not read
  a blob at all, in either direction.
- Opening a database called sqlite3_config(SQLITE_CONFIG_SERIALIZED) and,
  on failure, sqlite3_shutdown(). That has to run before
  sqlite3_initialize() to do anything, and calling shutdown with
  connections open is undefined behaviour. Replaced with per-connection
  SQLITE_OPEN_FULLMUTEX.

Behaviour now matches the portable contract:

- CursorImpl extends AbstractDBCursor, so last(), prev() and position()
  work instead of throwing "Unsupported", and first() lands on a row and
  reports false for an empty result set rather than reporting success and
  leaving the statement unpositioned.
- Parameters bind by runtime type through new statement natives. They
  used to be stringified, which stored an Integer as TEXT, and a comment
  conceded it "will probably fail with blobs".
- Parameter count mismatches and multi-statement scripts in the
  parameterized forms are rejected rather than silently mis-executed.
- Errors carry sqlite3_errmsg unconditionally; the dead XMLVM branches
  that gated error reporting are gone.
- finalize() is removed from the database and cursor. Closing sqlite
  handles from the GC thread is the "platform specific nuance" that
  defeated ThreadSafeDatabase.
- Custom file:// database paths work, matching Android and the simulator.

Keying is a separate native that reports success rather than throwing, so
the Java side can tell a wrong key from a failure to open the file
without the native layer naming a core exception class.
isDatabaseEncryptionSupported() asks the linked engine via PRAGMA
cipher_version rather than assuming, so it reports honestly on a build
that does not bundle a cipher-capable SQLite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Android was already the most capable port, so this is mostly tightening
rather than rebuilding:

- A null element in a String[] now binds SQL NULL. bindString rejects
  null, so passing one used to fail the whole statement.
- execute(sql, (Object[]) null) no longer dereferences a null array.
- execute(String) runs a whole script. execSQL refuses anything after the
  first statement, so the script is split and run statement by statement.
- executeQuery forces the window fill before returning, so malformed SQL
  is reported there rather than from the first next(). rawQuery is lazy.
- Transactions use the shared flat-transaction guards, so a nested begin
  is rejected here as it already was everywhere else.
- Exceptions carry their cause and are no longer printStackTrace'd on the
  way out.
- Cursors are invalidated when the database closes, close() is idempotent,
  getRow() off a row throws, getColumnIndex is case insensitive, and
  wasNull() is false before any value has been read.
- Blob query parameters work, bound through a cursor factory, which is the
  only supported route: rawQuery can carry text arguments only. This is
  what androidx.sqlite does for the same reason.

Encryption lives in a new com/codename1/impl/android/cipher package built
on net.zetetic:sqlcipher-android. It compiles against classes that are
only on the classpath of app builds that use encryption, so it is
excluded from the port's own javac and reached purely by reflection,
letting the builder delete it for every app that never touches
DatabaseConfig. That gating is why the package is a near copy of AndroidDB
rather than a shared supertype: any shared type naming net.zetetic would
have to live in the part of the port that must stay deletable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both ports inherited the base openOrCreateDB, which returns null, so
Database.openOrCreate() handed back null and calling code failed with a
NullPointerException. They now have a full implementation that satisfies
the same contract as every other port, encryption included.

Neither runs a JVM, so JDBC was never an option; they needed a C binding.
That is cheap because both are ParparVM C targets whose CMake project
already compiles every .c in the source root.

- The engine is SQLite3 Multiple Ciphers, bundled once in the translator
  and emitted only for applications that use com.codename1.db. iOS shares
  the same copy, so those three targets run one engine at one version,
  and the simulator's JDBC driver is built from the same upstream project.
- The amalgamation is named .h deliberately. The iOS project generator
  lists .h but excludes it from the compile phase; CMake globs *.c for
  sources; and the ParparVM native symbol scanner reads only .c and .m.
  Named .c it would be compiled twice without its build options, named
  .inc it would ship inside the .ipa as 13MB of dead weight.
- cn1_sqlite3.c is the single translation unit that compiles it, with the
  build options set immediately before the include so they cannot leak
  into unrelated sources. It is gated internally, so an emitted but
  disabled build produces an empty object rather than a link error.
- The binding itself is shared. Both ports need identical code but mangle
  their entry points from different Java classes, so the logic lives once
  in cn1_db_sqlite_impl.h and each port's .c expands
  CN1_DB_DEFINE_NATIVES for its own prefix. Verified that every declared
  native has both its plain and its _R_ symbol in both ports.
- iOS stops linking the system libsqlite3 when the bundled engine is used,
  rather than carrying two SQLite implementations in one process.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The JavaScript port sat on WebSQL, which Chrome removed in 119 and
Firefox never implemented, so its database was dead on every current
browser. What it did support was thin: transactions were printlns,
getBlob threw, position(n) always returned the first row, close() did
nothing, and the bridge busy-waited a CN1 thread on a lock.

It now runs the same SQLite build the other ports use, compiled to
WebAssembly, inside the application's own worker. Every call after the
first is an ordinary synchronous call; only the initial load suspends,
through the runtime's existing yield-on-promise support, so the lock and
its 200ms poll are gone.

Storage uses the opfs-sahpool VFS rather than the default OPFS one. The
default needs crossOriginIsolated, which needs COOP/COEP response
headers, which we cannot require of the arbitrary static hosting these
bundles are deployed to. Browsers without synchronous OPFS access fall
back to memory with a console warning, because silently losing every
write on reload is not a failure anyone should discover in production.

Gating, so nobody pays for what they do not use:

- iOS emits the bundled engine, and drops the system libsqlite3, only for
  applications that reference DatabaseConfig. Everyone else keeps the
  system SQLite exactly as before.
- Windows and Linux emit it for anything referencing com.codename1.db,
  since they have no system SQLite at all, and its cipher only when
  encryption is configured.
- Android's SQLCipher package is deleted unless DatabaseConfig is
  referenced, and the AAR arrives through a new PlatformFeatureCatalog
  entry keyed on that same class.
- The JavaScript builder prunes the 1.5MB engine from bundles that never
  open a database.

The catalog entry is keyed on DatabaseConfig rather than the db package
on purpose, and two new tests hold that line: every database application
references com.codename1.db, so keying it there would bundle SQLCipher
for all of them and push the minimum Android SDK from 19 to 23 for people
who never asked for encryption.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The contract and the encryption are only real if they are checked, and the
portability claim in particular is the kind that fails silently: a cipher
misconfiguration produces files each platform reads perfectly well on its
own and nothing else can touch.

- Seven device tests run the shared conformance suite on every port
  through the existing screenshot harness. They are assertion only, so
  they take no screenshots and sit before the ordering-sensitive graphics
  baselines. Ports without a database self-skip, so a port turns green on
  its own once it has one.
- Two of the seven run in legacy mode, which is what makes the
  compatibility promise testable rather than aspirational: they fail the
  moment a refactor changes what db.legacy restores.
- Two Port Status features expose the results publicly, split so a
  threading regression cannot blank the whole database row.
- scripts/ci/db-cipher-interop.sh checks our encrypted files against the
  stock sqlcipher client in both directions, with a raw key to isolate the
  cipher configuration and a passphrase leg to cover the key derivation.
  Wired into the pull request workflow.

The developer guide's SQL section said the iOS SQLite "isn't threadsafe"
and warned that the garbage collector closing a connection would crash the
app. That was true, and this branch is what fixes it, so the section is
rewritten and extended with encryption, key management, threading, cursor
cost and the legacy compatibility table.

ThreadSafeDatabase is un-deprecated. Its note blamed platform nuances; the
nuance was the iOS finalizers, now gone. Its close() was fire and forget,
so it returned before the database was closed and a following delete()
raced it, which is fixed here too.

The cursor inner classes are static: with an explicit owner field the
implicit outer reference was dead weight, which SpotBugs flagged on iOS
and would eventually have flagged everywhere.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 6, 2026 03:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce77b834d3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/db/Database.java
Comment thread Ports/iOSPort/src/com/codename1/impl/ios/DatabaseImpl.java Outdated
Comment thread CodenameOne/src/com/codename1/db/ManagedKeys.java
Comment thread Ports/Android/src/com/codename1/impl/android/AndroidImplementation.java Outdated
Comment thread CodenameOne/src/com/codename1/db/Database.java
Comment thread Ports/Android/src/com/codename1/impl/android/AndroidImplementation.java Outdated
Comment thread CodenameOne/src/com/codename1/impl/AbstractDBCursor.java
Comment thread Ports/Android/src/com/codename1/impl/android/cipher/AndroidCipherDB.java Outdated
@shai-almog

Copy link
Copy Markdown
Collaborator Author

Companion PR with the build-side gating: codenameone/BuildDaemon#172

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

  • Tests: 1788 total, 0 failed, 0 skipped
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 492 total, 0 failed, 54 skipped

Benchmark Results

  • Execution Time: 22318 ms

  • Hotspots (Top 20 sampled methods):

    • 20.87% java.util.ArrayList.indexOf (392 samples)
    • 6.23% com.codename1.tools.translator.BytecodeMethod.addToConstantPool (117 samples)
    • 4.21% java.lang.StringBuilder.append (79 samples)
    • 3.99% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (75 samples)
    • 3.51% com.codename1.tools.translator.BytecodeMethod.optimize (66 samples)
    • 3.30% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (62 samples)
    • 2.18% org.objectweb.asm.tree.analysis.Analyzer.analyze (41 samples)
    • 1.86% com.codename1.tools.translator.Parser.classIndex (35 samples)
    • 1.81% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (34 samples)
    • 1.76% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (33 samples)
    • 1.65% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (31 samples)
    • 1.49% java.lang.System.identityHashCode (28 samples)
    • 1.44% java.lang.Object.hashCode (27 samples)
    • 1.22% org.objectweb.asm.ClassReader.readCode (23 samples)
    • 1.06% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (20 samples)
    • 1.06% java.util.HashMap.hash (20 samples)
    • 1.01% java.lang.String.equals (19 samples)
    • 1.01% sun.nio.fs.UnixNativeDispatcher.open0 (19 samples)
    • 1.01% java.lang.StringCoding.encode (19 samples)
    • 0.96% java.io.UnixFileSystem.getBooleanAttributes0 (18 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

- The Ant build for the JavaSE port links whichever sqlite-jdbc is pinned
  in cn1-binaries, which has no org.sqlite.mc, so importing the driver's
  config builder broke that build for everyone. JavaSEPort now writes the
  SQLCipher connection properties out literally, which needs no extra
  class at compile time, and reports isDatabaseEncryptionSupported() by
  probing for the cipher-capable driver rather than assuming it. The
  simulator therefore answers honestly under either build.

- The Windows cross-compile failed to link. The sample application now
  uses com.codename1.db, but that integration test drives the translator
  directly rather than through the builder, so the engine was never
  emitted and the natives had no definitions. Two fixes: the shared
  binding header is always emitted and defines every entry point either
  way, as real bindings or as stubs that raise a clear IOException, so an
  application always links however the translator was invoked; and the
  integration tests ask for the engine explicitly, so those ports actually
  exercise the database instead of only ever self-skipping. Verified that
  both branches of the header export an identical symbol set.

- The developer guide requires snippets to live in docs/demos and be
  included by tag. Migrated with the repository's own migration script.
  The snippet harness had no com.codename1.db import, which is why all
  three failed to compile once moved; added, since it is a core package
  the guide documents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 6, 2026 04:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Preview

The Maven build already excluded it, but the Ant target compiles every
source in the port, so it tried to build the package against net.zetetic
and failed for anyone building that way -- including BuildDaemon CI, which
clones this repo and runs the Ant target.

Mirrors the exclusion into both places the ARCore and AI packages already
use: the javac in Ports/Android/build.xml and the excludes property in
nbproject/project.properties.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 6, 2026 04:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d595bd94da

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/db/ThreadSafeDatabase.java Outdated
Comment thread Ports/JavaScriptPort/src/main/webapp/port.js Outdated
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/SEDatabase.java Outdated
@shai-almog

shai-almog commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 12 screenshots: 12 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

@shai-almog

shai-almog commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 64ms / native 6ms = 10.6x speedup
SIMD float-mul (64K x300) java 63ms / native 4ms = 15.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 209.000 ms
Base64 CN1 decode 131.000 ms
Base64 SIMD encode 100.000 ms
Base64 encode ratio (SIMD/CN1) 0.478x (52.2% faster)
Base64 SIMD decode 99.000 ms
Base64 decode ratio (SIMD/CN1) 0.756x (24.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 32.000 ms
Image createMask (SIMD on) 31.000 ms
Image createMask ratio (SIMD on/off) 0.969x (3.1% faster)
Image applyMask (SIMD off) 216.000 ms
Image applyMask (SIMD on) 71.000 ms
Image applyMask ratio (SIMD on/off) 0.329x (67.1% faster)
Image modifyAlpha (SIMD off) 83.000 ms
Image modifyAlpha (SIMD on) 71.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.855x (14.5% faster)
Image modifyAlpha removeColor (SIMD off) 90.000 ms
Image modifyAlpha removeColor (SIMD on) 46.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.511x (48.9% faster)

@shai-almog

shai-almog commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 77ms / native 6ms = 12.8x speedup
SIMD float-mul (64K x300) java 82ms / native 4ms = 20.5x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 222.000 ms
Base64 CN1 decode 136.000 ms
Base64 SIMD encode 101.000 ms
Base64 encode ratio (SIMD/CN1) 0.455x (54.5% faster)
Base64 SIMD decode 98.000 ms
Base64 decode ratio (SIMD/CN1) 0.721x (27.9% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 25.000 ms
Image createMask (SIMD on) 21.000 ms
Image createMask ratio (SIMD on/off) 0.840x (16.0% faster)
Image applyMask (SIMD off) 168.000 ms
Image applyMask (SIMD on) 64.000 ms
Image applyMask ratio (SIMD on/off) 0.381x (61.9% faster)
Image modifyAlpha (SIMD off) 56.000 ms
Image modifyAlpha (SIMD on) 33.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.589x (41.1% faster)
Image modifyAlpha removeColor (SIMD off) 51.000 ms
Image modifyAlpha removeColor (SIMD on) 37.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.725x (27.5% faster)

@shai-almog

shai-almog commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 55ms / native 3ms = 18.3x speedup
SIMD float-mul (64K x300) java 55ms / native 4ms = 13.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 246.000 ms
Base64 CN1 decode 128.000 ms
Base64 SIMD encode 64.000 ms
Base64 encode ratio (SIMD/CN1) 0.260x (74.0% faster)
Base64 SIMD decode 63.000 ms
Base64 decode ratio (SIMD/CN1) 0.492x (50.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 13.000 ms
Image createMask (SIMD on) 8.000 ms
Image createMask ratio (SIMD on/off) 0.615x (38.5% faster)
Image applyMask (SIMD off) 24.000 ms
Image applyMask (SIMD on) 20.000 ms
Image applyMask ratio (SIMD on/off) 0.833x (16.7% faster)
Image modifyAlpha (SIMD off) 18.000 ms
Image modifyAlpha (SIMD on) 13.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.722x (27.8% faster)
Image modifyAlpha removeColor (SIMD off) 22.000 ms
Image modifyAlpha removeColor (SIMD on) 13.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.591x (40.9% faster)

Review findings, all eight real:

- Database.encrypt() could never work on Android. The system SQLite has no
  cipher, so a plaintext database opened through it can never be re-keyed.
  Added openOrCreateDBForRekey(), which Android routes through SQLCipher
  (an empty key opens an unencrypted file, which can then be re-keyed).
- A managed key resolves its keystore alias from the database name, and
  every port passed null when re-keying, so changeKey(managed()) raised a
  NullPointerException instead of encrypting. Each Database now retains
  the name it was opened under.
- Two threads first-opening the same managed database could each see
  nothing stored, generate different keys and overwrite each other,
  leaving one of them holding data nobody could ever read. The
  read-generate-store sequence is now serialized.
- isKeyHardwareBacked() inferred hardware backing from the API level, but
  emulators and plenty of real devices back AndroidKeyStore keys in
  software. It now asks the key itself, via KeyInfo. Applications are told
  they may use this to refuse to store sensitive data, so it has to be
  true.
- checkEndTransaction() cleared the flag before the engine had ended the
  transaction, so a failed commit left the transaction open while the API
  believed it was closed, and the recovering rollback was rejected.
  Splitting out markTransactionEnded() means the flag drops only on
  success. A conformance check covers the failed-commit path.
- An encrypted Android database opened by file:// URL had no
  toNativePath() conversion, so java.io.File treated the URL as a literal
  relative name.
- Calling next() past the end repeatedly re-derived the row count each
  time, inflating it, after which last() would seek to a row that does not
  exist. Verified the new check fails against the old code (5 became 8).
- PRAGMA rekey interpolated the key directly, so a passphrase containing a
  quote produced a different statement. Both Android and the simulator now
  go through one helper that quotes text and passes a raw key literal
  through untouched.

CI failures:

- Six SpotBugs findings in core-unittests, a module the earlier local runs
  had not covered: boxed constructors, a default-encoding String, and a
  Boolean-returning method that could return null.
- The arm64 Linux and Windows cross-builds failed compiling the engine's
  ARM AES intrinsics. Where the compiler defines __ARM_FEATURE_CRYPTO the
  engine uses them directly, which is what Apple's toolchain does, so iOS
  is unaffected; otherwise it tags individual functions with
  __attribute__((target)), which the cross-compiling clang does not honour
  for these intrinsics. Rather than require ARM crypto extensions of every
  chip, that path now uses the software implementation.
- DatabaseStatementLegacyTest failed on Android because the legacy
  expectation was wrong, not the code: only iOS ran a whole script before
  this branch, through sqlite3_exec. Android's execSQL and the simulator's
  PreparedStatement both dropped everything after the first statement.
  Corrected in the suite and in both places it is documented.
- The migrated guide snippet fixture needed a copyright header.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 6, 2026 05:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7f2f2c70ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/db/ManagedKeys.java Outdated
Comment thread Ports/iOSPort/src/com/codename1/impl/ios/DatabaseImpl.java
Comment thread Ports/JavaScriptPort/src/main/webapp/port.js Outdated
Comment thread CodenameOne/src/com/codename1/db/ThreadSafeDatabase.java
Comment thread Ports/iOSPort/src/com/codename1/impl/ios/DatabaseImpl.java Outdated
Comment thread Ports/Android/src/com/codename1/impl/android/AndroidDB.java Outdated
@shai-almog

shai-almog commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f0bbb5d34

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/JavaSE/src/com/codename1/impl/javase/SEDatabase.java
Comment thread CodenameOne/src/com/codename1/db/Database.java
…tions

The suite had no long coverage at all, so a 64-bit column was untested
everywhere. Asserting one past 2^53 immediately failed on the browser:
9007199254740993 came back as ...992, close enough to look right and wrong by
one. Longs are a hi/lo pair in that runtime, and handing back a BigInt sent it
through _Lc(), which converts via Number. The read path builds the pair, split
in two's complement so a negative value keeps its sign.

Under the legacy hint Android allows nested transactions because it used to,
and its engine ref-counts them -- but the first commit cleared the flag
outright, so the outer transaction still held uncommitted rows while nothing
said a transaction was open, and a key change was allowed over them. Begins are
counted now, and the paths where the engine ends the transaction outright say
so rather than decrementing.

An outermost SAVEPOINT opens a transaction only its own RELEASE ends, and the
ports that ask their engine got back a boolean with no name attached. A RELEASE
arriving later through a parameterized overload -- which has no engine read of
its own -- was then unrecognizable, leaving a transaction open forever. Those
ports read the names first and let the engine settle the boolean.

The browser's storage pool puts "foo" and "/foo" in one file, so the registry
now sees one database rather than two, and either connection can no longer pass
the sole-connection check while the other holds the same file.

A key change through the simulator's connection-taking constructor is refused:
that connection never said which file it holds, so nothing can be checked
against the connections that did, and the failure of a wrong answer is a
database rewritten under another handle. The fast test now identifies its file
the way JavaSEPort does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9be5fb3780

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/Android/src/com/codename1/impl/android/AndroidImplementation.java Outdated
Comment thread CodenameOne/src/com/codename1/db/Database.java Outdated
Comment thread CodenameOne/src/com/codename1/db/ThreadSafeDatabase.java
shai-almog and others added 2 commits August 9, 2026 04:09
The packaging job started failing on master, and the failure was not a flake:
the last green run mentions GoogleSignIn zero times and the first red one 517.
#5536 fixed build-hint plumbing, so ios.gplus.clientId -- which
ios-packaging.yml has passed for months -- finally reached IPhoneBuilder for
the first time and switched on the pod. The coverage that workflow describes,
compiling GoogleConnectImpl.m, had never actually run, and the moment it did it
took the build down: GoogleSignIn 5.x vendors a binary framework whose arm64
slice is device-only, so it cannot link into an arm64 simulator build, which is
every simulator build on an Apple Silicon machine.

7.1 ships as a source pod, so there is no prebuilt slice to mismatch. Its API
is different in the three places this port touches: the client id rides on a
GIDConfiguration, sign-in reports to a completion block rather than a delegate,
and the token hangs off the user rather than an authentication object. The
GIDSignInDelegate conformance and its two callbacks are gone with it, and the
pre-configuration in initGoogleConnect goes with them -- there is nothing to
set up ahead of a call that carries everything. The GPPSignIn branches are
untouched.

Verified by building the sample the way the packaging job does, with the same
dependency arguments: the workspace compiles and links for the arm64 simulator
with the pod in it. What that does not cover is the sign-in flow itself, which
needs real Google credentials and is no better covered today than before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3a1e67c238

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/Android/src/com/codename1/impl/android/AndroidDB.java
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/SEDatabase.java Outdated
The legacy hint restores what a port used to run, not what it used to know
about it. All three legacy paths -- both Android implementations and the
simulator -- executed the statement and returned without recording it, so
execute("BEGIN") left isInTransaction() false and a key change was allowed
across the transaction it had just opened. On Android that means the export
migration copying uncommitted rows into the file that becomes the database.

They record it now, and record only the statement that actually ran: legacy
executes as far as the first statement and discards the rest, so reading the
whole script would credit a COMMIT that never executed and report no
transaction over one that is open.

SEDatabase's three-argument constructor documented that the caller already
holds the registry claim, while reserveConnection is package private -- so no
caller outside the package could satisfy it, and one that tried would hold a
connection missing from the registry: invisible to another connection's key
change, and decrementing that connection's entry on close. The constructor is
package private now, next to the reservation it depends on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 185633d178

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/JavaSE/src/com/codename1/impl/javase/SEDatabase.java
…esting works

The platform's default answer to corruption is to delete the file, and an
encrypted database opened without its key is ciphertext to the plain engine --
indistinguishable from corruption. A single accidental openOrCreate(name)
therefore destroyed it, in the one case where the data was perfectly intact and
one correct-key open away from being readable. The port supplies a handler that
keeps the file, so a wrong key is a failed open like anywhere else. The
conformance suite now asserts that too: after the refused open, the database
still opens with the right key and its rows are there.

The legacy nesting bypass was shared, but only Android's wrapper ref-counts
begins. Everywhere else the second BEGIN reaches SQLite and fails, and the port
clears its flag on the way out -- so a caller that caught that expected failure
was left with no transaction recorded over one that was still open, and could
change the key across it. Nesting is now allowed only where the engine supports
it, which the Android implementations declare and nobody else does.

ThreadSafeDatabase.close() waited on its own worker when called from a task
dispatched through the publicly exposed getThread(), which is a deadlock: the
hand-off queues behind the task making the call. It closes directly when it is
already on the worker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 26bc092403

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/db/Database.java Outdated
Keeping the file instead of deleting it changed what the caller sees: the
platform reports a file it cannot read by throwing SQLiteDatabaseCorruptException,
which is unchecked, so where the old code silently destroyed the database and
carried on, the new code let a raw RuntimeException out of openOrCreateDB. This
API promises every failure as an IOException so a caller catches one thing
rather than an unchecked type per platform, and an encrypted database opened
without its key is exactly the case that reaches it.

Verified on an emulator rather than inferred: all seven database tests pass,
including the encryption group that caught this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9d325d9758

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/db/DatabaseConfig.java
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/SEDatabase.java
Counting one savepoint name could not express what SQLite does: a release takes
every savepoint above the one named as well, including a reuse of that name. The
count never reached zero, so the transaction stayed reported open after the
engine had ended it, and every later begin and key change was refused until the
connection closed. It is a stack now, with the transaction ending when the
savepoint that opened it is released -- and savepoints under a BEGIN ending
nothing, because that transaction is not theirs to end.

The simulator translated transaction control by reading the leading keyword and
the BEGIN mode and ignoring the rest, so "BEGIN nonsense" opened a transaction
and "COMMIT nonsense" committed one where every native port reports a syntax
error. It compiles the statement first -- without running it -- and lets the
engine be the judge. A second raw BEGIN is refused too: JDBC was already out of
autocommit, so setAutoCommit(false) did nothing and quietly accepted a nesting
SQLite rejects, with the engine's own message so an application matching on it
reads the same here as on a device.

A passphrase shaped exactly like a raw key literal is refused rather than
accepted: the engines read that form as 32 raw bytes, so it would silently skip
the derivation passphrase() promises -- weaker, and reported nowhere. Same
treatment as a passphrase containing a zero character, and for the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f8e3bd007

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/JavaSE/src/com/codename1/impl/javase/SEDatabase.java Outdated
Comment thread Ports/Android/src/com/codename1/impl/android/AndroidImplementation.java Outdated
Comment thread CodenameOne/src/com/codename1/db/Database.java Outdated
… displaces

ROLLBACK TO releases every savepoint above the one it names while keeping that
one, which the stack ignored: the entries above it stayed, the final release
never emptied the stack, and the transaction was reported open after SQLite had
ended it -- refusing every later begin and key change until the connection
closed. Both statements now unwind to the named savepoint; the difference is
that RELEASE takes it too and ROLLBACK TO leaves it open to be rolled back to
again.

Android's recovery moved the rejected file aside and deleted it without reading
the result. After a failed decryption that file is the complete plaintext
database, so a delete that quietly failed left it beside the restored one under
a predictable name while recovery reported success. It goes through the same
delete-or-truncate cleanup an abandoned export gets, and recovery fails loudly
if the copy survives.

The two-argument SEDatabase constructor is package private for the same reason
as the three-argument one: it keys the registry on the name it is handed, and no
caller outside this package can hand it the resolved file that openOrCreate
registers, so the same database would be filed under two entries.

The iOS SQLite flags carry the reasoning that was only in a review reply. Both
are keyed on the cipher flag deliberately: the bundled engine exists to replace
a system libsqlite3 that cannot encrypt, and ByteCodeTranslator links that
system engine whenever the cipher is off, so a plain database application
already has one and emitting the bundle would put two in a process.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3150fa15fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/db/DatabaseConfig.java
Comment thread CodenameOne/src/com/codename1/impl/AbstractDBCursor.java
Comment thread maven/codenameone-maven-plugin/src/main/java/com/codename1/builders/Executor.java Outdated
…ount

Any reference to DatabaseConfig counted as encryption, including the documented
plain() that says a database is not encrypted -- so a plaintext application paid
for SQLCipher and, on Android, lost every device below API 23 for asking not to
encrypt. The scan reads the constant pool and looks for a method reference to
passphrase, rawKey or managed, which is the question that was meant. A class
file it cannot walk still counts as encrypting, because the alternative is an
application that encrypts shipping without a cipher.

A cursor's row count survived a rewind. Rewinding re-executes the statement and
the new pass can see rows written since, so count() answered for a result set
that no longer existed and last() stopped on what used to be the final row. The
count is dropped by beforeFirst() and not by the internal rewinds that counting
and seeking do, which would otherwise discard the count they had just taken --
the conformance test covering it fails with "expected 3 but was 2" against the
previous code.

A managed key with no explicit alias was stored under the name the caller
passed, so two accepted spellings of one database derived two different keys and
the second open reported a wrong key against intact data. Android and the
simulator resolve the file first and key it on that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74c115c9ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/JavaSE/src/com/codename1/impl/javase/SEDatabase.java
Comment thread CodenameOne/src/com/codename1/db/ThreadSafeDatabase.java
The rest of the ports still derived an implicit managed alias from the name the
caller passed, which is the case the finding named for Windows: that filesystem
is case insensitive, so "C:/Data/app.db" and "c:\data\app.db" are one file and
were two keys, and the second open reported a wrong key against data that was
perfectly intact. iOS and Linux have the same hole through "." segments.

Each port now resolves its own identity first -- the shared normalizer on iOS
and Linux, the separator-and-case fold on Windows, the pool path in the browser
-- which is the same string each already registers as the open-database key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c538064e06

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/db/CursorExt.java Outdated
shai-almog and others added 2 commits August 9, 2026 19:58
A library can be the only thing that touches the database: the application calls
it and never names Database itself, and Android stages the jar into libs and
links it through the generated fileTree. The scan read loose class files only,
so it reported no database use and dropped the engine out from under code that
runs it. Archive entries are read the same way loose classes are.

The scan also skipped the whole com/codename1/db directory, which treats the
package as the framework's by ownership rather than by convention: a helper an
application or a library puts there was invisible, so it could configure
encryption and the build would still drop the cipher. The framework's own
classes are skipped by exact name instead, the way every other framework class
in this scan already was.

CursorExt.getCount() said -1 meant "not cheaply available", which read as a
cheap probe that declines when it would be expensive. It is the opposite:
Android's engine knows the count, and every other port walks the result set to
the end and rewinds, so it costs what the query costs and stops the EDT for that
long. Both it and Database.count() say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The job died at dependency download, before anything was compiled: Central
served 403 for junit-bom and Maven treats that as a permanent resolution
failure. That is the exact case scripts/ci/retry.sh documents and that
parparvm-tests.yml already wraps for; this job was simply not wrapped.

Both steps only build and install, so RETRY_ONLY_MATCHING stays unset, which the
helper reserves for steps that run tests -- a blanket retry there could turn a
flaky test into a pass, and nothing here runs one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 75e3952a7a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/Android/src/com/codename1/impl/android/cipher/AndroidCipherDB.java Outdated
Comment thread CodenameOne/src/com/codename1/db/Database.java Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb4ad8ca9f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/JavaScriptPort/src/main/webapp/port.js Outdated
The opens were fixed to key an implicit managed alias on the file rather than
the name; the other three doors into that key were not. changeKey() still
resolved against the raw name on every port, so converting a database wrote a
second key under a second identity and the next open -- which resolves the file
-- reported WRONG_KEY against data that was intact. forgetManagedKey(), the
documented way to destroy a key deliberately, deleted the raw name and normally
found nothing, returning false while the real key stayed. A new
databaseManagedKeyIdentity() hook lets the core ask the port what it stores
under, and each port answers with the identity its open path already uses.

The browser's memory fallback kept its own spelling too: "foo" and "/foo" are
one file in the storage pool and were two stores in memory, so the fallback
disagreed with the storage it stands in for, down to exists() and delete()
seeing only one of them.

The Android builder scanned only the loose class tree while the unzip writes
submitted library jars to libs, so encryption used only inside a library left
dbCipherSupport false and the build deleted the cipher implementation out from
under the library that calls it. It scans both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02b051d22d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread CodenameOne/src/com/codename1/db/Database.java Outdated
Comment thread CodenameOne/src/com/codename1/impl/AbstractDBCursor.java
…itself

The simulator recorded transaction control only when it routed a statement
through JDBC, so execute("SAVEPOINT s", params) ran and was forgotten: the
savepoint opened a real transaction while isInTransaction() stayed false, which
rejects the caller's own commit and lets a key change run over live work. It
records every parameterized statement now, as the device ports do.

ThreadSafeDatabase deadlocked on itself for every call except close(). A task
dispatched through the publicly exposed getThread() that touches the database
handed work to the worker and waited for the worker to run it -- while being the
worker. Both invocation helpers run the call directly when they are already on
that thread, which is what close() was doing alone.

The Display javadoc was written in classic /** form, which the Java 25 markdown
docs check rejects. That check is what failed the build; the SpotBugs "no report"
lines after it are the modules it never got to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2d72a0d2c4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Ports/JavaSE/src/com/codename1/impl/javase/SEDatabase.java Outdated
shai-almog and others added 2 commits August 9, 2026 20:53
…rd seek

forgetManagedKey tried the alias and the resolved identity and deleted both,
which is two different databases when one application's explicit alias is
another's name -- managed("shared") here, a database called shared there. The
fallback runs only when the alias found nothing, so the call destroys the key
its caller named and leaves the other alone.

The cursor count was dropped by beforeFirst() but not by first() or a backward
position(), which re-execute the statement just the same: after a delete
elsewhere, last() still aimed at a row the new pass does not have. Those seeks
drop it too, through a private seek that getCount() calls with the invalidation
off -- its rewinds are how it counts and how it puts the cursor back, and
dropping the count there would discard the one it had just taken.

The build scan now reads .aar as well as .jar, descending into the nested
classes.jar an Android archive keeps its bytecode in, since the generated gradle
links it like any other dependency.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The typed endings already knew that a SAVEPOINT leaves JDBC in autocommit and
end such a transaction with a statement. The raw forms did not: execute("COMMIT")
after execute("SAVEPOINT s") reached conn.commit(), which rejects the call in
autocommit mode, so a SAVEPOINT ... COMMIT script worked on every native port and
failed only on the simulator. Both endings take the same branch now.

Covered by a conformance test that opens with SAVEPOINT, commits through a raw
COMMIT and checks the row survived, then does it again with ROLLBACK and checks
the row did not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possibility to Encrypt sqlite data base

2 participants