You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@roller.apache.org by sn...@apache.org on 2026/09/17 22:55:55 UTC
(roller) branch master updated: Scope installer table detection to the connection's catalog
This is an automated email from the ASF dual-hosted git repository.
snoopdave pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/roller.git
The following commit(s) were added to refs/heads/master by this push:
new 47c5cf0bb Scope installer table detection to the connection's catalog
47c5cf0bb is described below
commit 47c5cf0bbfa00fe199bc743a7fb88df8e6b21d32
Author: David M. Johnson <sn...@apache.org>
AuthorDate: Thu Sep 17 18:53:45 2026 -0400
Scope installer table detection to the connection's catalog
DatabaseInstaller.tableExists() passed a null catalog to
DatabaseMetaData.getTables(). Connector/J 5.1 read that as "the current
database", but Connector/J 8 reads it as every database on the server,
having changed nullCatalogMeansCurrent to default to false.
On a MySQL server hosting more than one Roller schema, that made
isCreationRequired() find userrole and roller_user in some other
database and report that this one was already installed. Roller then
skipped table creation, fell through to isUpgradeRequired(), logged
ERROR setting database version, and died with "Table
'<schema>.roller_properties' doesn't exist" — while Tomcat reported
only "One or more listeners failed to start", which sends you looking
at the datasource rather than the metadata call.
Pass con.getCatalog() instead. Drivers that do not use catalogs return
null there, which is the query that was being made before.
Also document that the initial-setup token is required for an upgrade
that migrates the schema, not only for a new installation, and that
6.1.x to 6.1.6 needs no token at all. The install guide described only
the new-installation case, in a section titled "Upgrading Roller".
Verified: full build green (325 tests), and the Selenium
InitialLoginTestIT end-to-end run passes against Derby, covering the
token gate, first-administrator registration, weblog creation and
entry rendering on a non-MySQL database.
Claude-Session: https://claude.ai/code/session_015X69HHQ5XnjRkJP8ymzwFf
---
.gitignore | 1 +
CHANGES.md | 6 ++
.../business/startup/DatabaseInstaller.java | 12 ++-
.../startup/DatabaseInstallerCreationTest.java | 102 +++++++++++++++++++++
docs/roller-install-guide.adoc | 13 ++-
5 files changed, 131 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
index 4419d5ba2..c24ba08a6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ docker/postgresql-data
docker/roller-data
assembly-release/release.sh
it-selenium/overlays/
+it-selenium/logs/
diff --git a/CHANGES.md b/CHANGES.md
index f3cc2a331..2f4ad53f8 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -51,6 +51,12 @@ Two more changes are visible in normal use:
attributes.
- Bookmark and configuration parsing share a single JDOM builder that treats a
document strictly as data.
+- Table detection during installation is scoped to the database the connection
+ points at. Installing into an empty schema on a server that also hosts
+ another Roller database previously found that database's tables, skipped
+ table creation, and then failed at startup looking for tables it had never
+ created. MySQL installations are the ones affected, because Connector/J 8
+ changed `nullCatalogMeansCurrent` to default to false.
### Build and packaging
diff --git a/app/src/main/java/org/apache/roller/weblogger/business/startup/DatabaseInstaller.java b/app/src/main/java/org/apache/roller/weblogger/business/startup/DatabaseInstaller.java
index d0892c612..7b2a71765 100644
--- a/app/src/main/java/org/apache/roller/weblogger/business/startup/DatabaseInstaller.java
+++ b/app/src/main/java/org/apache/roller/weblogger/business/startup/DatabaseInstaller.java
@@ -816,9 +816,19 @@ public class DatabaseInstaller {
/**
* Return true if named table exists in database.
+ *
+ * The lookup is scoped to the catalog the connection is actually pointed
+ * at. A null catalog means "every catalog on the server" to some drivers —
+ * MySQL Connector/J 8 among them, which changed the default of
+ * nullCatalogMeansCurrent from true to false — and an unscoped lookup then
+ * finds Roller tables belonging to a different database on the same
+ * server. An empty schema would be mistaken for an installed one, and
+ * Roller would skip table creation and fail later looking for tables that
+ * were never created. Drivers that do not use catalogs return null here,
+ * which is the same query as before.
*/
private boolean tableExists(Connection con, String tableName) throws SQLException {
- ResultSet rs = con.getMetaData().getTables(null, null, "%", null);
+ ResultSet rs = con.getMetaData().getTables(con.getCatalog(), null, "%", null);
while (rs.next()) {
if (tableName.equalsIgnoreCase(rs.getString("TABLE_NAME").toLowerCase())) {
return true;
diff --git a/app/src/test/java/org/apache/roller/weblogger/business/startup/DatabaseInstallerCreationTest.java b/app/src/test/java/org/apache/roller/weblogger/business/startup/DatabaseInstallerCreationTest.java
new file mode 100644
index 000000000..1b7682bc9
--- /dev/null
+++ b/app/src/test/java/org/apache/roller/weblogger/business/startup/DatabaseInstallerCreationTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0.
+ */
+package org.apache.roller.weblogger.business.startup;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.roller.weblogger.business.DatabaseProvider;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+class DatabaseInstallerCreationTest {
+
+ private static final String CATALOG = "rollerdb";
+
+ private DatabaseMetaData metaData;
+
+ /**
+ * An installer whose catalog contains exactly the named tables. Each
+ * getTables() call yields a fresh cursor, as a real driver would.
+ */
+ private DatabaseInstaller installerSeeing(String... tableNames) throws Exception {
+ DatabaseProvider db = mock(DatabaseProvider.class);
+ Connection con = mock(Connection.class);
+ metaData = mock(DatabaseMetaData.class);
+
+ when(db.getConnection()).thenReturn(con);
+ when(con.getCatalog()).thenReturn(CATALOG);
+ when(con.getMetaData()).thenReturn(metaData);
+ when(metaData.getTables(any(), any(), any(), any()))
+ .thenAnswer(invocation -> cursorOver(tableNames));
+
+ return new DatabaseInstaller(db, mock(DatabaseScriptProvider.class));
+ }
+
+ private static ResultSet cursorOver(String[] tableNames) throws Exception {
+ ResultSet rs = mock(ResultSet.class);
+ Iterator<String> remaining = Arrays.asList(tableNames).iterator();
+ AtomicReference<String> current = new AtomicReference<>();
+ when(rs.next()).thenAnswer(invocation -> {
+ if (!remaining.hasNext()) {
+ return false;
+ }
+ current.set(remaining.next());
+ return true;
+ });
+ when(rs.getString("TABLE_NAME")).thenAnswer(invocation -> current.get());
+ return rs;
+ }
+
+ @Test
+ void tableLookupIsScopedToTheConnectionCatalog() throws Exception {
+ DatabaseInstaller installer = installerSeeing();
+
+ assertTrue(installer.isCreationRequired());
+
+ // A null catalog means "every catalog on the server" to some drivers,
+ // which would find another Roller database's tables and wrongly skip
+ // creation of this one.
+ verify(metaData, atLeastOnce()).getTables(eq(CATALOG), isNull(), eq("%"), isNull());
+ verify(metaData, never()).getTables(isNull(), any(), any(), any());
+ }
+
+ @Test
+ void unrelatedTablesStillNeedCreation() throws Exception {
+ DatabaseInstaller installer = installerSeeing("some_unrelated_table");
+
+ assertTrue(installer.isCreationRequired());
+ }
+
+ @Test
+ void partialSchemaStillNeedsCreation() throws Exception {
+ DatabaseInstaller installer = installerSeeing("userrole");
+
+ assertTrue(installer.isCreationRequired());
+ }
+
+ @Test
+ void populatedCatalogDoesNotNeedCreation() throws Exception {
+ DatabaseInstaller installer = installerSeeing("userrole", "roller_user");
+
+ assertFalse(installer.isCreationRequired());
+ }
+
+ @Test
+ void preRoller51TableNamesAreRecognised() throws Exception {
+ DatabaseInstaller installer = installerSeeing("userrole", "rolleruser");
+
+ assertFalse(installer.isCreationRequired());
+ }
+}
diff --git a/docs/roller-install-guide.adoc b/docs/roller-install-guide.adoc
index d18581205..e75be3604 100644
--- a/docs/roller-install-guide.adoc
+++ b/docs/roller-install-guide.adoc
@@ -793,9 +793,18 @@ error type which the automated installation process is coded to ignore.
=== Initial setup security token
-For a new installation Roller generates a cryptographically random, one-time setup token during startup and prints it to the Roller server log. Open `/roller-ui/bootstrap-token.rol` over HTTPS (or a secured local tunnel) and enter the token before opening the installer or registering the first administrator. The token is accepted only in the POST body, expires after 60 minutes, and is consumed on successful entry. Restart Roller to obtain a new token if it expires or the browser session [...]
+Whenever Roller starts and finds setup work to do, it generates a cryptographically random, one-time setup token and prints it to the Roller server log. This applies to two cases:
-The token authorizes the installer and first-user flow for the current browser session only. Run database creation or upgrade on one setup node at a time. After the first administrator is committed, bootstrap access is permanently disabled, including after deleting or disabling that account. Existing installations with users continue to use their normal login and registration settings.
+* a **new installation**, where the database tables have yet to be created; and
+* an **upgrade of an existing site** whose database schema needs to be migrated — for example from Roller 5.2.x or 6.0.x.
+
+Upgrades that require no schema change issue no token and show no installer. Upgrading from any Roller 6.1.x release to another, including to 6.1.6, is such a case: the site starts up normally with no operator interaction.
+
+When a token is issued, open `/roller-ui/bootstrap-token.rol` over HTTPS (or a secured local tunnel) and enter the token before opening the installer or registering the first administrator. The token is accepted only in the POST body, expires after 60 minutes, and is consumed on successful entry. Restart Roller to obtain a new token if it expires or the browser session is lost. The token is never included in a URL or Roller page.
+
+NOTE: Because the token is written only to the server log, make sure you can read the Roller log — `roller.log`, or the container's console output — before you begin an upgrade that needs one. Plan for this in deployments where logs are shipped elsewhere or the console is not readily accessible.
+
+The token authorizes the installer and first-user flow for the current browser session only. Run database creation or upgrade on one setup node at a time. Bootstrap access closes permanently as soon as setup finishes: on a new installation when the first administrator is committed, including after deleting or disabling that account; on an existing site when the database upgrade completes. Existing installations with users continue to use their normal login and registration settings.
This section describes how to upgrade an existing Roller installation to
the latest release of Roller by shutting down, backing up and then