Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
5b0f0b0
Define the portable Database contract and add the encryption API
shai-almog Aug 6, 2026
a29da0d
Make the simulator conformant and able to open encrypted databases
shai-almog Aug 6, 2026
85f6127
Make the iOS database conformant and fix three defects in it
shai-almog Aug 6, 2026
0fedd67
Bring Android to the database contract and add encrypted databases
shai-almog Aug 6, 2026
b0e2de8
Give the native Windows and Linux ports a database
shai-almog Aug 6, 2026
dcf1f1f
Replace WebSQL with SQLite on WebAssembly and gate the engine per app
shai-almog Aug 6, 2026
ce77b83
Add device conformance tests, documentation and the interop proof
shai-almog Aug 6, 2026
70dc81f
Fix the three CI failures from the first run
shai-almog Aug 6, 2026
d595bd9
Exclude the SQLCipher package from the Ant Android build too
shai-almog Aug 6, 2026
7f2f2c7
Address the review findings and the second round of CI failures
shai-almog Aug 6, 2026
766ab43
Address the second batch of review findings
shai-almog Aug 6, 2026
1b78be8
Address the third batch of review findings
shai-almog Aug 6, 2026
c8ca4a5
Satisfy the forbidden PMD rules
shai-almog Aug 6, 2026
dbcceec
Compile the engine's ciphers without assuming CPU features are enabled
shai-almog Aug 6, 2026
ba13354
Add copyright headers to the new guide snippet files
shai-almog Aug 6, 2026
ecad03f
Address the fourth review batch, the guide prose gate and CodeQL noise
shai-almog Aug 6, 2026
efbab6c
Fix failed-commit recovery, parameter counts and the JS key probe
shai-almog Aug 6, 2026
f5767b0
Report unbound parameters from executeQuery(String) with the shared m…
shai-almog Aug 6, 2026
e282819
Default Ant projects to database compatibility mode
shai-almog Aug 6, 2026
730a69a
Roll back through the engine when an Android commit fails
shai-almog Aug 6, 2026
cb0456a
Close out the fourth review round
shai-almog Aug 6, 2026
c6f57d4
Make Android encryption buildable, and actually exercise it
shai-almog Aug 6, 2026
452c6cf
Make encrypt and decrypt work on Android
shai-almog Aug 6, 2026
952f337
Stop the JavaScript port hanging when the SQLite engine will not start
shai-almog Aug 6, 2026
7a70937
Do not assert a filesystem path where databases are not filesystem ba…
shai-almog Aug 6, 2026
62e2271
Close out the fifth review round
shai-almog Aug 6, 2026
4a11dc9
Stop every JavaScript native binding from throwing across the bridge
shai-almog Aug 6, 2026
d4d3c89
Fix three ways the previous round's fixes could fail
shai-almog Aug 6, 2026
483acb2
Complete the migration recovery, and stop OPFS trouble losing data
shai-almog Aug 6, 2026
81cbb36
Type-check the SQLCipher package, which nothing else compiles
shai-almog Aug 6, 2026
4c32755
Close the remaining native and dispatch error paths
shai-almog Aug 7, 2026
13cef92
Resolve the cipher check's dependencies from the reactor, not a repos…
shai-almog Aug 7, 2026
2b527c3
Sweep the error paths I had fixed in only one implementation
shai-almog Aug 7, 2026
ce68bdd
Fix three defects the previous round's fixes introduced or left
shai-almog Aug 7, 2026
79aceb2
Move migration artifacts out of the database namespace entirely
shai-almog Aug 7, 2026
ecf667b
Make delete and exists aware of interrupted conversions, and finish t…
shai-almog Aug 7, 2026
14c1630
Prove ownership of migration files instead of picking a location
shai-almog Aug 7, 2026
be69b0b
Report close failures, and track trigger bodies by position
shai-almog Aug 7, 2026
da4d201
Merge remote-tracking branch 'origin/master' into feature/portable-en…
shai-almog Aug 7, 2026
2420d98
Hoist casts out of catch regions and exchange SQL text as UTF-8 bytes
shai-almog Aug 7, 2026
26dcced
Detect the port from the database, not from Display
shai-almog Aug 7, 2026
c1fc047
Record the export before writing it, and refuse a passphrase SQLite w…
shai-almog Aug 7, 2026
f3e483b
Count named and numbered placeholders the way the engine does
shai-almog Aug 7, 2026
26a59d8
Read a whole SQLite parameter name, not just its identifier characters
shai-almog Aug 7, 2026
643699a
Merge remote-tracking branch 'origin/master' into feature/portable-en…
shai-almog Aug 7, 2026
018356f
Hold a port to its own legacy behaviour, not to another port's
shai-almog Aug 7, 2026
06b22a6
Gate the SQLite payloads on the application, not on the framework
shai-almog Aug 7, 2026
8dc65cf
Keep windows.h from dragging winsock.h into the database bindings
shai-almog Aug 7, 2026
3d74b04
Finish gating the cipher, and stop recovery touching files it does no…
shai-almog Aug 7, 2026
a6bee19
Document what using a database, and encrypting one, costs a build
shai-almog Aug 7, 2026
9b8733a
Track transactions opened by script, and stop three ways to lose data
shai-almog Aug 7, 2026
aabde20
Count every Android connection, and read a script the way the engine …
shai-almog Aug 8, 2026
15865f2
See a transaction whichever entry point opened it
shai-almog Aug 8, 2026
b4b662e
Read ROLLBACK TO as tokens, and hold the database for the whole conve…
shai-almog Aug 8, 2026
59d3853
Take the connection slot before the engine opens, and share one class…
shai-almog Aug 8, 2026
ca1cc1a
Ask the engine whether a transaction is open, instead of inferring it
shai-almog Aug 8, 2026
4712b47
Claim the database for an in-place key rotation too
shai-almog Aug 8, 2026
c7b0fe9
Merge remote-tracking branch 'origin/master' into feature/portable-en…
shai-almog Aug 8, 2026
14555d1
Keep the last readable copy, and make the claim cover recovery
shai-almog Aug 8, 2026
266d6e8
Hold a key change to one connection, and load the JS engine's wasm
shai-almog Aug 8, 2026
406b094
Report an unavailable managed key as a note, not a skipped group
shai-almog Aug 8, 2026
9f409ea
Hold the rekey claim for the whole rewrite, and ship a wasm that comp…
shai-almog Aug 8, 2026
b39c725
Key the simulator registry by the resolved file
shai-almog Aug 8, 2026
e842054
Reserve the slot before opening, and key the browser's engine through…
shai-almog Aug 8, 2026
27c2584
Track savepoint transactions, and read the BEGIN mode as a token
shai-almog Aug 8, 2026
37e28b7
Claim the simulator's file before JDBC opens it, and stop a vacuous c…
shai-almog Aug 8, 2026
6a35664
Read whole savepoint names, and hold the claim until the handle is gone
shai-almog Aug 8, 2026
57167c6
Calibrate the ciphertext check, and stack savepoints of one name
shai-almog Aug 8, 2026
a134e50
Ask the no-key question on a database nothing else depends on
shai-almog Aug 8, 2026
5f0bbb5
End a savepoint transaction through SQL on Android too
shai-almog Aug 8, 2026
9be5fb3
Return a Java long exactly from the browser, and count nested transac…
shai-almog Aug 8, 2026
bf23c85
Merge master into feature/portable-encryptable-database
shai-almog Aug 8, 2026
3a1e67c
Move Google sign-in to a SDK that links on a simulator
shai-almog Aug 8, 2026
185633d
Track a legacy execute, and close the constructor nobody could satisfy
shai-almog Aug 8, 2026
26bc092
Keep a database an unkeyed open could not read, and nest only where n…
shai-almog Aug 8, 2026
9d325d9
Report a database that will not open as an IOException
shai-almog Aug 9, 2026
3f8e3bd
Keep a savepoint stack, and hold the simulator to SQLite's grammar
shai-almog Aug 9, 2026
3150fa1
Unwind the savepoint stack on ROLLBACK TO, and clean up what recovery…
shai-almog Aug 9, 2026
74c115c
Ask which config factory was called, and refresh a rewound cursor's c…
shai-almog Aug 9, 2026
c538064
Key a managed alias on the resolved file on the remaining ports
shai-almog Aug 9, 2026
75e3952
Scan library jars and the database package, and say what count costs
shai-almog Aug 9, 2026
fb4ad8c
Retry the suite build past a Maven Central 403
shai-almog Aug 9, 2026
02b051d
Resolve a managed key the same way wherever it is looked up
shai-almog Aug 9, 2026
2d72a0d
Track a parameterized savepoint, and stop the wrapper deadlocking on …
shai-almog Aug 9, 2026
f664689
Destroy one managed key, not two, and refresh the count on any backwa…
shai-almog Aug 9, 2026
b91482b
End a savepoint transaction through SQL from a script too
shai-almog Aug 9, 2026
0826b63
Merge remote-tracking branch 'origin/master' into feature/portable-en…
shai-almog Aug 9, 2026
8a33f40
Fold savepoint names as SQLite does, and ask the engine after every s…
shai-almog Aug 9, 2026
3bf5d9c
Check the worker before the lock, and read the engine on the failure …
shai-almog Aug 9, 2026
f0840c9
Restore the browser's engine read, and let isInTransaction skip the lock
shai-almog Aug 10, 2026
89a4517
Clear Android's flag when a rollback finds nothing to roll back
shai-almog Aug 10, 2026
a32ef74
Register a wrapped connection, and let queued callers hear that the d…
shai-almog Aug 10, 2026
63f20b4
Read a SQLite URI connection, and clear state when there is nothing l…
shai-almog Aug 10, 2026
594b8df
Match the raw-key literal that is actually written, and free the curs…
shai-almog Aug 10, 2026
a051ebb
Quote by the exact literal, rekey a nameless handle, and stop killing…
shai-almog Aug 10, 2026
5d47d7e
Merge remote-tracking branch 'origin/master' into feature/portable-en…
shai-almog Aug 11, 2026
bb549e4
Resolve opaque SQLite URIs, and count a connection that cannot name i…
shai-almog Aug 11, 2026
34894b8
Keep the constructors a compiled library can still call
shai-almog Aug 11, 2026
fc1620a
Refuse a compatibility handle on a database that is being converted
shai-almog Aug 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Vendored third-party sources. Marking them keeps them out of language
# statistics and collapses them in diffs, so a routine engine update does not
# read as a 400,000 line change and does not push review tooling past its
# size limits.
vm/ByteCodeTranslator/src/cn1_sqlite3_amalgamation.h linguist-vendored linguist-generated -diff
vm/ByteCodeTranslator/src/cn1_sqlite3.h linguist-vendored linguist-generated -diff
Ports/JavaScriptPort/src/main/webapp/js/sqlite3mc.js linguist-vendored -diff
Ports/JavaScriptPort/src/main/webapp/js/sqlite3-opfs-async-proxy.js linguist-vendored -diff
7 changes: 7 additions & 0 deletions .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,10 @@ name: "Codename One CodeQL configuration"
# String sinks across the rest of the codebase).
paths-ignore:
- 'scripts/cn1playground/**/bsh/cn1/gen/**'
# Vendored SQLite engine (SQLite3 Multiple Ciphers), 13MB of upstream C carried
# verbatim. Analysing it reports upstream's findings as ours, and its sheer size
# pushes CodeQL off incremental analysis so it re-reports pre-existing alerts
# across unrelated files. Provenance and update instructions are in
# vm/ByteCodeTranslator/src/cn1_sqlite3_README.md.
- 'vm/ByteCodeTranslator/src/cn1_sqlite3_amalgamation.h'
- 'vm/ByteCodeTranslator/src/cn1_sqlite3.h'
10 changes: 8 additions & 2 deletions .github/workflows/linux-build-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,14 @@ jobs:
with: { distribution: 'temurin', java-version: '8' }

- name: Build plugin + cn1-ads-mock (JDK 8)
# Maven Central intermittently serves 403/429 from the runner CDN edge and Maven treats it
# as a permanent resolution failure, which killed this job at dependency download before
# anything was compiled. See scripts/ci/retry.sh; these steps only build, so a blanket
# retry cannot turn a flaky test into a pass.
run: |
cd maven
JAVA_HOME="$JDK_8_HOME" mvn -B -pl codenameone-maven-plugin,cn1-ads-mock -am \
JAVA_HOME="$JDK_8_HOME" bash ../scripts/ci/retry.sh \
mvn -B -pl codenameone-maven-plugin,cn1-ads-mock -am \
-DskipTests '-Dmaven.javadoc.skip=true' '-Plocal-dev-javase' install

- name: Build hellocodenameone-common (JDK 17)
Expand All @@ -102,7 +107,8 @@ jobs:
sleep 2
mkdir -p "$HOME/.codenameone"
[ -f "$HOME/.codenameone/guibuilder.jar" ] || : > "$HOME/.codenameone/guibuilder.jar"
mvn -B -f scripts/hellocodenameone/pom.xml -pl common -am install -DskipTests '-Dmaven.javadoc.skip=true'
bash scripts/ci/retry.sh \
mvn -B -f scripts/hellocodenameone/pom.xml -pl common -am install -DskipTests '-Dmaven.javadoc.skip=true'

- name: Package suite classes
run: |
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ jobs:
-Dcodename1.platform=javase \
-Dcn1.binaries="${CN1_BINARIES}" \
-pl javase -Plocal-dev-javase test
- name: Verify encrypted databases interoperate with reference SQLCipher
if: ${{ matrix.java-version == 8 }}
# The portability claim is that every port writes one on-disk format. A cipher
# misconfiguration produces files each platform reads happily and nothing else can
# touch, which no single-platform test would catch, so check ours against the stock
# sqlcipher client in both directions.
run: |
apt-get update -qq && apt-get install -y -qq sqlcipher
scripts/ci/db-cipher-interop.sh
- name: Determine AI/ad cn1libs to build
id: cn1libs
if: ${{ matrix.java-version == 8 }}
Expand Down Expand Up @@ -344,6 +353,20 @@ jobs:
-Dcn1.binaries="${CN1_BINARIES}" \
-Pcompile-android \
-pl android,ios,codenameone-maven-plugin -am verify
- name: Type-check the SQLCipher-backed database package
if: ${{ matrix.java-version == 8 }}
env:
CN1_BINARIES: ${{ github.workspace }}/maven/target/cn1-binaries
# That package is excluded from the port build on purpose, so nothing above compiles it
# and a member of com.codename1.impl.android it reaches can stop being public without
# anything noticing until an Android app build forty minutes later. Runs after the port
# is compiled, which is what it type-checks against.
run: scripts/ci/check-android-cipher-package-compiles.sh
- name: Compile the bundled SQLite WebAssembly module
if: ${{ matrix.java-version == 8 }}
# A module no engine accepts does not fail anything on its own: the JavaScript port just
# reports the database as unavailable and its tests skip.
run: scripts/ci/check-js-sqlite-wasm.sh
- name: Run SpotBugs for ByteCodeTranslator
if: ${{ matrix.java-version == 8 }}
run: mvn -B -DskipTests=true -f vm/ByteCodeTranslator/pom.xml verify
Expand Down
140 changes: 71 additions & 69 deletions CodenameOne/src/com/codename1/db/Cursor.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,124 +24,117 @@

import java.io.IOException;

/// The Cursor interface is used to iterate over the results returned from a database query.
/// **IMPORTANT:** Notice that some methods might not be supported on all platforms!
/// Iterates over the results returned from a database query.
///
/// There is more thorough coverage of the `Database API here`.
///
/// The sample code below presents a Database Explorer tool that allows executing arbitrary SQL and
/// viewing the tabular results:
/// Positions are counted from zero and a new cursor sits before the first row, so the usual loop
/// is simply:
///
/// ```java
/// Toolbar.setGlobalToolbar(true);
/// Style s = UIManager.getInstance().getComponentStyle("TitleCommand");
/// FontImage icon = FontImage.createMaterial(FontImage.MATERIAL_QUERY_BUILDER, s);
/// Form hi = new Form("SQL Explorer", new BorderLayout());
/// hi.getToolbar().addCommandToRightBar("", icon, (e) -> {
/// TextArea query = new TextArea(3, 80);
/// Command ok = new Command("Execute");
/// Command cancel = new Command("Cancel");
/// if(Dialog.show("Query", query, ok, cancel) == ok) {
/// Database db = null;
/// Cursor cur = null;
/// try {
/// db = Display.getInstance().openOrCreate("MyDB.db");
/// if(query.getText().startsWith("select")) {
/// cur = db.executeQuery(query.getText());
/// int columns = cur.getColumnCount();
/// hi.removeAll();
/// if(columns > 0) {
/// boolean next = cur.next();
/// if(next) {
/// ArrayList data = new ArrayList<>();
/// String[] columnNames = new String[columns];
/// for(int iter = 0 ; iter
/// Cursor cur = db.executeQuery("SELECT id, body FROM notes ORDER BY id");
/// try {
/// while (cur.next()) {
/// Row row = cur.getRow();
/// System.out.println(row.getInteger(0) + ": " + row.getString(1));
/// }
/// } finally {
/// cur.close();
/// }
/// ```
///
/// Every navigation method works on every platform. Only the cost varies: `#next()` is uniformly
/// cheap, while `#last()`, `#prev()` and `#position(int)` may have to rewind and re-step the
/// underlying statement, which costs time proportional to the distance from the start. For a large
/// result set, prefer iterating forward with `#next()`.
///
/// Because a backward seek re-runs the statement, a cursor is a repeatable read only inside a
/// transaction. See the `com.codename1.db` package documentation for the full contract.
///
/// @author Chen
public interface Cursor {

/// Move the cursor to the first row.
///
/// If cursor provides forward-only navigation and is positioned after the
/// first row then calling first() method would throw a IOException.
/// Moves the cursor onto the first row.
///
/// #### Returns
///
/// true if succeeded
/// true if there is a first row, false for an empty result set
///
/// #### Throws
///
/// - `IOException`
/// - `IOException`: if the cursor is closed
// PMD Fix (UnnecessaryModifier): Interface methods are implicitly public; remove redundant modifiers.
boolean first() throws IOException;

/// Move the cursor to the last row.
/// Moves the cursor onto the last row.
///
/// Costs a full pass over the result set the first time it is called.
///
/// #### Returns
///
/// true if succeeded
/// true if there is a last row, false for an empty result set
///
/// #### Throws
///
/// - `IOException`
/// - `IOException`: if the cursor is closed
boolean last() throws IOException;

/// Moves the cursor to the next row.
/// Calling next() method the first time will position cursor on the first.
/// Advances the cursor one row.
///
/// A new cursor sits before the first row, so the first call lands on it.
///
/// #### Returns
///
/// true if succeeded
/// true if a row was reached, false at the end of the result set
///
/// #### Throws
///
/// - `IOException`
/// - `IOException`: if the cursor is closed
boolean next() throws IOException;

/// Moves the cursor to the previous row.
/// If cursor is forward type then calling prev() would throw a IOException.
/// Moves the cursor back one row.
///
/// #### Returns
///
/// true if succeeded
/// true if a row was reached, false when already at or before the first row
///
/// #### Throws
///
/// - `IOException`
/// - `IOException`: if the cursor is closed
boolean prev() throws IOException;

/// Returns the zero-based index for a given column name.
/// Note that columns meta information is available only after navigation to
/// the first row
/// Returns the zero-based index of a column, or -1 if there is no such column.
///
/// The comparison is case-insensitive and matches the result set label, so a column selected
/// as `SELECT a AS b` is found under `b`. Available as soon as the query returns, before the
/// first `#next()`.
///
/// #### Parameters
///
/// - `columnName`: the name of the column.
/// - `columnName`: the name of the column
///
/// #### Returns
///
/// the index of the column
/// the zero-based index, or -1 when the column is not in the result set
///
/// #### Throws
///
/// - `IOException`
/// - `IOException`: if the cursor is closed
int getColumnIndex(String columnName) throws IOException;

/// Returns the column name at a given zero-based column index.
/// Note that columns meta information is available only after navigation to
/// the first row
/// Returns the label of the column at a zero-based index.
///
/// Available as soon as the query returns, before the first `#next()`.
///
/// #### Parameters
///
/// - `columnIndex`: the index of the column
/// - `columnIndex`: the zero-based index of the column
///
/// #### Returns
///
/// the name of the column
/// the column label
///
/// #### Throws
///
/// - `IOException`
/// - `IOException`: if the cursor is closed
String getColumnName(int columnIndex) throws IOException;

/// Returns the column count
Expand All @@ -155,48 +148,57 @@ public interface Cursor {
/// - `IOException`
int getColumnCount() throws IOException;

/// Returns the current Cursor position.
/// Returns the zero-based position of the cursor.
///
/// Reports -1 before any successful move, and the row count once the result set is exhausted.
///
/// #### Returns
///
/// the cursor position
///
/// #### Throws
///
/// - `IOException`
/// - `IOException`: if the cursor is closed
int getPosition() throws IOException;

/// Move the cursor to an absolute row position
/// Moves the cursor to an absolute zero-based row.
///
/// Passing -1 rewinds to before the first row and returns false.
///
/// #### Parameters
///
/// - `row`: position to move to
/// - `row`: the zero-based row to move to
///
/// #### Returns
///
/// true if succeeded
/// true if the row exists, false if it is out of range
///
/// #### Throws
///
/// - `IOException`
/// - `IOException`: if the cursor is closed
boolean position(int row) throws IOException;

/// Close the cursor and release its resources
/// Closes the cursor and releases its resources.
///
/// Calling this more than once is harmless.
///
/// #### Throws
///
/// - `IOException`
/// - `IOException`: if the underlying statement cannot be released
void close() throws IOException;

/// Get the Row data Object.
/// Returns the current row.
///
/// Valid only while the cursor is on a row.
///
/// #### Returns
///
/// a Row Object
/// the current row
///
/// #### Throws
///
/// - `IOException`
/// - `IOException`: @throws IOException if the cursor is closed, or is before the first row or
/// past the last one
Row getRow() throws IOException;

}
Loading
Loading