diff --git a/VERSION b/VERSION
index 32447ce3..a87467fb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-5.11.1
+5.11.2
diff --git a/documentation/design/datbase.md b/documentation/design/datbase.md
index 2c21184a..29be8a15 100644
--- a/documentation/design/datbase.md
+++ b/documentation/design/datbase.md
@@ -23,11 +23,13 @@ These come in two flavors:
Notes:
1. Uses DBNorIterator.
+2. Resolves against the **user** connections, i.e. the `gor.db.credentials` file. See [Configuration](#configuration).
#### **sql:// URIs**
Notes:
1. Uses DBNorIterator (and SQLSource).
+2. Resolves against the **user** connections, i.e. the `gor.db.credentials` file. See [Configuration](#configuration).
### **Limited SQL Commands**
These come in flavors:
@@ -50,6 +52,7 @@ gor db://rda:variant_annotations | top 10
Notes:
1. Uses DBSource and DbGenomicIterator.
+2. Resolves against the **system** connections, i.e. the credentials the host passes in. See [Configuration](#configuration).
#### **//db/ Paths**
The //db/ paths are arbitrary SELECT statements that can be used to selected from a given database.
@@ -68,7 +71,8 @@ Their limit is the can ONLY be executed from a link file but not from a GOR que
Notes:
1. Uses DBNorIterator.
-2. This is the old style of doing SQL access in GOR. The new preferred way is to use the `sql`, `norsql`, `gorsql` commands. This is likely to be deprecated.
+2. Resolves against the **system** connections, i.e. the credentials the host passes in. See [Configuration](#configuration).
+3. This is the old style of doing SQL access in GOR. The new preferred way is to use the `sql`, `norsql`, `gorsql` commands. This is likely to be deprecated.
### Sepcial Variables
@@ -79,23 +83,58 @@ TBD
### Configuration
-TBD
+There are two connection caches, and in a server they are fed from **separate** sources:
+
+| Cache | Used by | Fed from |
+|---|---|---|
+| system connections | internal use (e.g. session management) and the access-controlled operations `db://`, `//db:` | credentials the host passes in |
+| user connections | the user-available commands and sources `SQL`, `GORSQL`, `NORSQL`, `sql://` | the `gor.db.credentials` file |
+
+They are kept apart deliberately. System credentials typically rotate, and a file cannot hold rotating
+credentials without going stale; equally, the rotating system credentials are not the ones user queries
+should reach.
+
+In a console app there is no host to supply credentials, so both caches load from the file.
+
+#### The credentials file
+
+`gor.db.credentials` is tab-separated, with a header line:
+
+```
+name driver url user pwd
+rda org.postgresql.Driver jdbc:postgresql://myurl.com:5432/csa rda mypass
+```
+
+The password column is optional. Lines starting with `#` are ignored.
+
+Its location defaults to the config directory and can be set with the `gor.db.credentials` system
+property.
+
+A missing credentials file is an error.
+
+#### Credentials passed in by the host
+
+The host application supplies the system credentials as `DbCredentials`:
+
+```java
+var systemCredentials = List.of(new DbCredentials("rda", url, user, password));
+
+DbConnection.initInServer(systemCredentials);
+// which is, per cache:
+DbConnection.systemConnections.initializeDbSources(systemCredentials);
+DbConnection.userConnections.initializeDbSources(credpath);
+```
+
+Gor never reads credentials from the environment itself, and does not care where the host got them —
+its own configuration, a secret manager, or environment variables the host owns the naming of. This
+keeps deployment-specific naming out of the library.
+
+`DbCredentials` takes `name`, `url`, `user`, `pwd`, and an optional `driver`. When `driver` is null it
+is derived from the url prefix (`jdbc:postgresql:`, `jdbc:oracle:`). Blank values count as unset,
+including the password, so a secret manager rendering an empty string does not become a real empty
+password. Incomplete credentials are logged — naming the missing field, never a value — and skipped,
+rather than failing startup.
-We have two different configuration files:
-*
-*
gor.sql.credentials - contains the user databases, which are used by the user available commands/sources (SQL,
-* GORSQL, NORSQL, sql://). These credentials typically would grant limited/read-only access to
-* the database.
-*
-* The format for these files is:
-*
-* name\tdriver\turl\tuser\tpwd
-* rda\torg.postgresql.Driver\tjdbc:postgresql://myurl.com:5432/csa\trda\tmypass
-* ...
-*
-*
-* The location of the files defaults to the config directory but can be specified by the system properties
-* gor.db.credentials and gor.sql.credentials.
\ No newline at end of file
+The two `initializeDbSources` overloads are alternatives, not additive: each clears the cache first.
+That is what keeps a cache fed from exactly one source. Passing no credentials therefore leaves the
+system cache empty, and `db://` sources will not resolve.
\ No newline at end of file
diff --git a/documentation/src/command/GORSQL.rst b/documentation/src/command/GORSQL.rst
index a2085790..a1b7f120 100644
--- a/documentation/src/command/GORSQL.rst
+++ b/documentation/src/command/GORSQL.rst
@@ -7,7 +7,7 @@
======
GORSQL
======
-The :ref:`GORSQL` command allows you to run arbitrary SQL commands against "the database" (the database here being defined by the content of a file called gor.sql.credentials in the config directory).
+The :ref:`GORSQL` command allows you to run arbitrary SQL commands against "the database" (the databases available here are the user connections, defined by the content of a file called gor.db.credentials in the config directory).
For :ref:`GORSQL` to work properly, the defined query must return Chrom-POS information as first two columns.
@@ -33,7 +33,7 @@ Options
| ``-ff File`` | Read tags from a tag file and filter files and file contents on. Also accepts a nested query. |
| | The following place holders are provided: #{TAGS}. The necessary quoting is done automatically. |
+----------------------+---------------------------------------------------------------------------------------------------+
-| ``-db database`` | Database alias as defied in ``gor.sql.credentials``. |
+| ``-db database`` | Database alias as defied in ``gor.db.credentials``. |
+----------------------+---------------------------------------------------------------------------------------------------+
diff --git a/documentation/src/command/NORSQL.rst b/documentation/src/command/NORSQL.rst
index 2d6449ec..7d80d017 100644
--- a/documentation/src/command/NORSQL.rst
+++ b/documentation/src/command/NORSQL.rst
@@ -7,7 +7,7 @@
======
NORSQL
======
-The :ref:`NORSQL` command allows you to run arbitrary SQL commands against "the database" (the database here being defined by the content of a file called gor.sql.credentials in the config directory).
+The :ref:`NORSQL` command allows you to run arbitrary SQL commands against "the database" (the databases available here are the user connections, defined by the content of a file called gor.db.credentials in the config directory).
:ref:`NORSQL` can be run against any database table.
diff --git a/documentation/src/command/SQL.rst b/documentation/src/command/SQL.rst
index bd71cdf5..257143d4 100644
--- a/documentation/src/command/SQL.rst
+++ b/documentation/src/command/SQL.rst
@@ -7,7 +7,7 @@
===
SQL
===
-The SQL command allows you to run arbitrary SQL commands against "the database" (the database here being defined by the content of a file called gor.sql.credentials in the config directory).
+The SQL command allows you to run arbitrary SQL commands against "the database" (the databases available here are the user connections, defined by the content of a file called gor.db.credentials in the config directory).
SQL statements need to be encapsulated between curly brackets, e.g. sql {sql_query}.
@@ -37,7 +37,7 @@ Options
| ``-ff File`` | Read tags from a tag file and filter files and file contents on. Also accepts a nested query. |
| | The following place holders are provided: #{TAGS}. The necessary quoting is done automatically. |
+----------------------+---------------------------------------------------------------------------------------------------+
-| ``-db database`` | Database alias as defied in ``gor.sql.credentials``. |
+| ``-db database`` | Database alias as defied in ``gor.db.credentials``. |
+----------------------+---------------------------------------------------------------------------------------------------+
diff --git a/gortools/src/test/java/gorsat/UTestSQLInputSource.java b/gortools/src/test/java/gorsat/UTestSQLInputSource.java
index 649ec791..46b1eb31 100644
--- a/gortools/src/test/java/gorsat/UTestSQLInputSource.java
+++ b/gortools/src/test/java/gorsat/UTestSQLInputSource.java
@@ -58,9 +58,9 @@ public static void initDb() throws IOException, ClassNotFoundException, SQLExcep
List credFileLines = Files.readAllLines(Path.of(rdaPaths[2]));
credFileLines.add(Files.readAllLines(Path.of(avasPaths[2])).get(1));
- File credFile = FileTestUtils.createTempFile(new File(rdaPaths[0]), "gor.sql.credentials",
+ File credFile = FileTestUtils.createTempFile(new File(rdaPaths[0]), "gor.db.credentials",
credFileLines.stream().collect(Collectors.joining("\n")));
- System.setProperty("gor.sql.credentials", credFile.getAbsolutePath());
+ System.setProperty("gor.db.credentials", credFile.getAbsolutePath());
DbConnection.initInConsoleApp();
}
diff --git a/gortools/src/test/scala/gorsat/UTestInputSourceParsing.scala b/gortools/src/test/scala/gorsat/UTestInputSourceParsing.scala
index db304ac8..31a20597 100644
--- a/gortools/src/test/scala/gorsat/UTestInputSourceParsing.scala
+++ b/gortools/src/test/scala/gorsat/UTestInputSourceParsing.scala
@@ -119,7 +119,7 @@ class UTestInputSourceParsing extends AnyFunSuite with BeforeAndAfter with Mocki
Class.forName("org.apache.derby.jdbc.EmbeddedDriver")
val paths = createTestDataBase_Derby
- System.setProperty("gor.sql.credentials", paths(2))
+ System.setProperty("gor.db.credentials", paths(2))
DbConnection.initInConsoleApp()
var tempDirectory = FileTestUtils.createTempDirectory(this.getClass.getName)
diff --git a/model/src/main/java/org/gorpipe/gor/model/DbConnection.java b/model/src/main/java/org/gorpipe/gor/model/DbConnection.java
index aa7c8d73..eed8ea8a 100644
--- a/model/src/main/java/org/gorpipe/gor/model/DbConnection.java
+++ b/model/src/main/java/org/gorpipe/gor/model/DbConnection.java
@@ -34,6 +34,7 @@
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.sql.*;
+import java.util.List;
/**
* DbConnection abstract access to database source that are configured on installation time.
@@ -43,24 +44,30 @@
* The static part creates a cache of DbConnection objects that can be used to access all databases defined in the
* database configuration files. This part is located here for backward compatibility reasons.
*
- * We have two different configuration files:
+ * We have one configuration file:
*
- *
gor.db.credentials - contains the system databases, which are used by the system internally (e.g. session management),
- * and by operation where we have strict access controls (db://, //db:). These credentials
- * typically would grant full access to the database.
- * gor.sql.credentials - contains the user databases, which are used by the user available commands/sources (SQL,
- * GORSQL, NORSQL, sql://). These credentials typically would grant limited/read-only access to
- * the database.
+ * gor.db.credentials - contains all the databases gor can reach. It feeds both the system connections,
+ * used internally (e.g. session management) and by operations with strict access
+ * controls (db://, //db:), and the user connections behind the user available
+ * commands/sources (SQL, GORSQL, NORSQL, sql://).
*
- * The format for these files is:
+ * In a server, the two caches are fed from separate sources. {@link #userConnections} comes from the
+ * gor.db.credentials file, while {@link #systemConnections} comes from credentials the host passes in
+ * as {@link DbCredentials} — see {@link #initInServer(java.util.List)}. System credentials typically
+ * rotate, and so cannot be baked into a file without going stale; gor does not care where the host got
+ * them and never reads them from the environment itself.
+ *
+ * In a console app there is no host to supply credentials, so both caches load from the file.
+ *
+ * The format for this file is:
*
* name\tdriver\turl\tuser\tpwd
* rda\torg.postgresql.Driver\tjdbc:postgresql://myurl.com:5432/csa\trda\tmypass
* ...
*
*
- * The location of the files defaults to the config directory but can be specified by the system properties
- * gor.db.credentials and gor.sql.credentials.
+ * The location of the file defaults to the config directory but can be specified by the system property
+ * gor.db.credentials.
*
* TODO: This class needs some refactoring to handle the different databases more gracefully. We should use some kind of
* plugin mechanism (or Guice) instead of if/else statements.
@@ -244,10 +251,7 @@ public static void initInConsoleApp() throws ClassNotFoundException, IOException
File homeDbCredFile = new File(System.getProperty("user.home"), "gor.db.credentials");
final String dbCredpath = homeDbCredFile.exists() ? homeDbCredFile.getCanonicalPath() : System.getProperty("gor.db.credentials");
systemConnections.initializeDbSources(dbCredpath);
-
- File homeSqlCredFile = new File(System.getProperty("user.home"), "gor.sql.credentials");
- final String sqlCredpath = homeSqlCredFile.exists() ? homeSqlCredFile.getCanonicalPath() : System.getProperty("gor.sql.credentials");
- userConnections.initializeDbSources(sqlCredpath);
+ userConnections.initializeDbSources(dbCredpath);
setPasswordCallback(s -> {
final Console console = System.console();
@@ -261,16 +265,39 @@ public static void initInConsoleApp() throws ClassNotFoundException, IOException
* @throws ClassNotFoundException
* @throws IOException
*/
- @SuppressWarnings("unused") // Called from GorQueryTask in gor-services
+ @SuppressWarnings("unused") // Called from gor-services
public static void initInServer() throws ClassNotFoundException, IOException {
- final String dbCredpath = System.getProperty("gor.db.credentials");
- if (dbCredpath != null) {
- systemConnections.initializeDbSources(dbCredpath);
- }
+ initInServer(List.of());
+ }
+
+ /**
+ * Initialize DbSource to be used in a server, with the system credentials supplied by the host
+ * application.
+ *
+ * The two caches are fed from separate sources:
+ *
+ *
{@link #systemConnections}, used internally and by the access-controlled operations
+ * (db://, //db:), comes from the supplied credentials. These typically rotate, so a host
+ * holding them — in its own configuration, a secret manager, or environment variables it
+ * owns the naming of — passes them here rather than gor reading them itself.
+ * {@link #userConnections}, behind the user-available commands and sources (SQL, GORSQL,
+ * NORSQL, sql://), comes from the gor.db.credentials file.
+ *
+ * The sources are kept apart deliberately: the file cannot hold credentials that rotate without
+ * going stale, and the rotating system credentials are not the ones user queries should reach.
+ *
+ * @param systemCredentials Credentials for {@link #systemConnections}. May be empty, which leaves
+ * that cache empty.
+ * @throws ClassNotFoundException
+ * @throws IOException
+ */
+ @SuppressWarnings("unused") // Called from GorQueryTask in gor-services
+ public static void initInServer(List systemCredentials) throws ClassNotFoundException, IOException {
+ systemConnections.initializeDbSources(systemCredentials);
- final String sqlCredpath = System.getProperty("gor.sql.credentials");
- if (sqlCredpath != null) {
- userConnections.initializeDbSources(sqlCredpath);
+ final String userCredpath = System.getProperty("gor.db.credentials");
+ if (userCredpath != null) {
+ userConnections.initializeDbSources(userCredpath);
}
}
diff --git a/model/src/main/java/org/gorpipe/gor/model/DbConnectionCache.java b/model/src/main/java/org/gorpipe/gor/model/DbConnectionCache.java
index f70db082..440652d5 100644
--- a/model/src/main/java/org/gorpipe/gor/model/DbConnectionCache.java
+++ b/model/src/main/java/org/gorpipe/gor/model/DbConnectionCache.java
@@ -12,6 +12,7 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -45,35 +46,55 @@ public DbConnection lookup(String source) {
}
/**
- * Read Database sources from configuration file
+ * Read database sources from the configuration file, replacing any sources already installed.
*
* @param credpath The path to the configuration file
- * @throws ClassNotFoundException
- * @throws IOException
+ * @throws IOException if the credentials file is configured but missing
*/
@SuppressWarnings("WeakerAccess") // Used from gor-services
public void initializeDbSources(String credpath) throws IOException {
clearDbSources();
- if (credpath != null && credpath.trim().length() > 0) {
- final Path path = Paths.get(credpath);
- if (Files.notExists(path)) {
- throw new FileNotFoundException("Specified db credentials file (" + credpath + ") is not found");
- }
+ installAllFromParts(readFileForDbSourceInstallation(credpath));
+ }
- final List lines = Files.readAllLines(path, StandardCharsets.UTF_8);
+ /**
+ * Install database sources from credentials supplied by the host application, replacing any
+ * sources already installed.
+ *
+ * This is the counterpart to {@link #initializeDbSources(String)} for credentials that rotate and
+ * so cannot be baked into a file. The host may source them from its own configuration, a secret
+ * manager, or environment variables it owns the naming of — gor does not read them itself.
+ *
+ * @param credentials The credentials to install. May be empty, which leaves the cache empty.
+ */
+ public void initializeDbSources(List credentials) {
+ clearDbSources();
+ installAllFromParts(toPartsForInstallation(credentials));
+ }
+
+ private List readFileForDbSourceInstallation(String credpath) throws IOException {
+ if (credpath == null || credpath.trim().length() == 0) {
+ log.info("No db credential path specified");
+ return Collections.emptyList();
+ }
- List partsList = parseLinesForDbSourceInstallation(credpath, lines);
+ final Path path = Paths.get(credpath);
+ if (Files.notExists(path)) {
+ throw new FileNotFoundException("Specified db credentials file (" + credpath + ") is not found");
+ }
- for (String[] parts : partsList) {
- try {
- installDbSourceFromParts(parts);
- } catch (ClassNotFoundException e) {
- log.error("Failed to load driver class {} for db source {}. Please ensure the driver is in the classpath.",
- parts[1], parts[0], e);
- }
+ final List lines = Files.readAllLines(path, StandardCharsets.UTF_8);
+ return parseLinesForDbSourceInstallation(credpath, lines);
+ }
+
+ private void installAllFromParts(List partsList) {
+ for (String[] parts : partsList) {
+ try {
+ installDbSourceFromParts(parts);
+ } catch (ClassNotFoundException e) {
+ log.error("Failed to load driver class {} for db source {}. Please ensure the driver is in the classpath.",
+ parts[1], parts[0], e);
}
- } else {
- log.info("No db credential path specified");
}
}
@@ -111,6 +132,83 @@ public static List parseLinesForDbSourceInstallation(String credpath,
return partsList;
}
+ /**
+ * Convert caller-supplied credentials into the {name, driver, url, user[, pwd]} shape that
+ * parseLinesForDbSourceInstallation produces, so both paths share the install code.
+ *
+ * Incomplete credentials are skipped with a warning rather than failing initialization. Blank
+ * values count as unset, including the password: a secret manager or template that renders an
+ * empty value is expressing a missing value, not a real empty password.
+ *
+ * Never logs credential values, only the source name and which field was missing.
+ *
+ * @param credentials the credentials to convert, may be null
+ * @return one entry per usable credential
+ */
+ static List toPartsForInstallation(List credentials) {
+ List partsList = new ArrayList<>();
+ if (credentials == null) {
+ return partsList;
+ }
+
+ for (DbCredentials cred : credentials) {
+ if (cred == null) {
+ continue;
+ }
+
+ String name = trimToNull(cred.name());
+ String url = trimToNull(cred.url());
+ String user = trimToNull(cred.user());
+ String pwd = trimToNull(cred.pwd());
+
+ if (name == null || url == null || user == null) {
+ log.warn("Incomplete db source credentials for source {}: {} is not set. Ignoring it.",
+ name == null ? "" : name,
+ name == null ? "name" : (url == null ? "url" : "user"));
+ continue;
+ }
+
+ String driver = trimToNull(cred.driver());
+ if (driver == null) {
+ driver = driverClassForUrl(url);
+ }
+ if (driver == null) {
+ log.warn("Could not derive a jdbc driver for db source {} from its url, and no driver was given. Ignoring it.",
+ name);
+ continue;
+ }
+
+ if (pwd == null) {
+ partsList.add(new String[]{name, driver, url, user});
+ } else {
+ partsList.add(new String[]{name, driver, url, user, pwd});
+ }
+ }
+ return partsList;
+ }
+
+ /**
+ * @param url a jdbc url
+ * @return the matching driver class name, or null if the prefix is not recognized
+ */
+ static String driverClassForUrl(String url) {
+ if (url.startsWith("jdbc:postgresql:")) {
+ return "org.postgresql.Driver";
+ }
+ if (url.startsWith("jdbc:oracle:")) {
+ return "oracle.jdbc.driver.OracleDriver";
+ }
+ return null;
+ }
+
+ private static String trimToNull(String value) {
+ if (value == null) {
+ return null;
+ }
+ String trimmed = value.trim();
+ return trimmed.isEmpty() ? null : trimmed;
+ }
+
private void installDbSourceFromParts(String[] parts) throws ClassNotFoundException {
Class.forName(parts[1]); // Just load the driver once and for all
final DbConnection source = new DbConnection(parts[0], parts[2], parts[3], parts.length > 4 ? parts[4] : null);
diff --git a/model/src/main/java/org/gorpipe/gor/model/DbCredentials.java b/model/src/main/java/org/gorpipe/gor/model/DbCredentials.java
new file mode 100644
index 00000000..51a986c6
--- /dev/null
+++ b/model/src/main/java/org/gorpipe/gor/model/DbCredentials.java
@@ -0,0 +1,51 @@
+/*
+ * BEGIN_COPYRIGHT
+ *
+ * Copyright (C) 2011-2013 deCODE genetics Inc.
+ * Copyright (C) 2013-2019 WuXi NextCode Inc.
+ * All Rights Reserved.
+ *
+ * GORpipe is free software: you can redistribute it and/or modify
+ * it under the terms of the AFFERO GNU General Public License as published by
+ * the Free Software Foundation.
+ *
+ * GORpipe is distributed "AS-IS" AND WITHOUT ANY WARRANTY OF ANY KIND,
+ * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
+ * NON-INFRINGEMENT, OR FITNESS FOR A PARTICULAR PURPOSE. See
+ * the AFFERO GNU General Public License for the complete license terms.
+ *
+ * You should have received a copy of the AFFERO GNU General Public License
+ * along with GORpipe. If not, see
+ *
+ * END_COPYRIGHT
+ */
+
+package org.gorpipe.gor.model;
+
+/**
+ * Credentials for a single database source, supplied programmatically rather than through the
+ * credentials file.
+ *
+ * This exists so that a host application can source credentials however it likes — from its own
+ * configuration, a secret manager, or environment variables it owns the naming of — and hand them to
+ * {@link DbConnectionCache#initializeDbSources(String, java.util.List)} without gor needing to know
+ * where they came from.
+ *
+ * Credentials passed this way are installed before the credentials file is read, so a file row with
+ * the same name takes precedence.
+ *
+ * @param name the source name, e.g. "rda". Required.
+ * @param url the jdbc url. Required.
+ * @param user the database user. Required.
+ * @param pwd the password. May be null.
+ * @param driver the jdbc driver class. May be null, in which case it is derived from the url prefix.
+ */
+public record DbCredentials(String name, String url, String user, String pwd, String driver) {
+
+ /**
+ * Credentials with the driver derived from the url prefix.
+ */
+ public DbCredentials(String name, String url, String user, String pwd) {
+ this(name, url, user, pwd, null);
+ }
+}
diff --git a/model/src/test/java/org/gorpipe/gor/model/UTestDbSource.java b/model/src/test/java/org/gorpipe/gor/model/UTestDbSource.java
index bc199e6d..42edae10 100644
--- a/model/src/test/java/org/gorpipe/gor/model/UTestDbSource.java
+++ b/model/src/test/java/org/gorpipe/gor/model/UTestDbSource.java
@@ -23,8 +23,14 @@
package org.gorpipe.gor.model;
import org.junit.Assert;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
@@ -71,4 +77,172 @@ public void parseLinesForDbSourceInstallationWithRealTabsAndNewlinesAndInvalidLi
Assert.assertEquals(1, partsList.size());
Assert.assertEquals(5, partsList.get(0).length);
}
-}
\ No newline at end of file
+
+ private static List rdaCreds(String url, String username, String password) {
+ return List.of(new DbCredentials("rda", url, username, password));
+ }
+
+ private static List rdaCreds(String url, String username, String password, String driver) {
+ return List.of(new DbCredentials("rda", url, username, password, driver));
+ }
+
+ @Test
+ public void credentialsBuildPartsInFileParserShape() {
+ List creds = rdaCreds("jdbc:postgresql://db:5432/csa", "gregor_reader", "secret");
+ List partsList = DbConnectionCache.toPartsForInstallation(creds);
+ Assert.assertEquals(1, partsList.size());
+ String[] parts = partsList.get(0);
+ Assert.assertEquals(5, parts.length);
+ Assert.assertEquals("rda", parts[0]);
+ Assert.assertEquals("org.postgresql.Driver", parts[1]);
+ Assert.assertEquals("jdbc:postgresql://db:5432/csa", parts[2]);
+ Assert.assertEquals("gregor_reader", parts[3]);
+ Assert.assertEquals("secret", parts[4]);
+ }
+
+ @Test
+ public void credentialsOmitPasswordFieldWhenPasswordUnset() {
+ List creds = rdaCreds("jdbc:postgresql://db:5432/csa", "gregor_reader", null);
+ List partsList = DbConnectionCache.toPartsForInstallation(creds);
+ Assert.assertEquals(1, partsList.size());
+ Assert.assertEquals(4, partsList.get(0).length);
+ }
+
+ @Test
+ public void credentialsTreatBlankPasswordAsUnset() {
+ List creds = rdaCreds("jdbc:postgresql://db:5432/csa", "gregor_reader", " ");
+ List partsList = DbConnectionCache.toPartsForInstallation(creds);
+ Assert.assertEquals(1, partsList.size());
+ Assert.assertEquals("blank password should be omitted, not installed as an empty password",
+ 4, partsList.get(0).length);
+ }
+
+ @Test
+ public void noCredentialsProducesNoParts() {
+ Assert.assertTrue(DbConnectionCache.toPartsForInstallation(List.of()).isEmpty());
+ }
+
+ @Test
+ public void credentialsSkippedWhenUsernameMissing() {
+ List creds = rdaCreds("jdbc:postgresql://db:5432/csa", null, "secret");
+ Assert.assertTrue(DbConnectionCache.toPartsForInstallation(creds).isEmpty());
+ }
+
+ @Test
+ public void credentialsSkippedWhenUrlMissing() {
+ List creds = rdaCreds(null, "gregor_reader", "secret");
+ Assert.assertTrue(DbConnectionCache.toPartsForInstallation(creds).isEmpty());
+ }
+
+ @Test
+ public void credentialsTreatBlankValuesAsUnset() {
+ List creds = rdaCreds("jdbc:postgresql://db:5432/csa", " ", "secret");
+ Assert.assertTrue(DbConnectionCache.toPartsForInstallation(creds).isEmpty());
+ }
+
+ @Test
+ public void credentialsDeriveOracleDriverFromUrlPrefix() {
+ List creds = rdaCreds("jdbc:oracle:thin:@db:1521:XE", "gregor_reader", "secret");
+ List partsList = DbConnectionCache.toPartsForInstallation(creds);
+ Assert.assertEquals(1, partsList.size());
+ Assert.assertEquals("oracle.jdbc.driver.OracleDriver", partsList.get(0)[1]);
+ }
+
+ @Test
+ public void credentialsHonourExplicitDriverOverride() {
+ List creds = rdaCreds("jdbc:whatever://db/x", "gregor_reader", "secret", "com.example.Driver");
+ List partsList = DbConnectionCache.toPartsForInstallation(creds);
+ Assert.assertEquals(1, partsList.size());
+ Assert.assertEquals("com.example.Driver", partsList.get(0)[1]);
+ }
+
+ @Test
+ public void credentialsSkippedWhenDriverCannotBeDerived() {
+ List creds = rdaCreds("jdbc:whatever://db/x", "gregor_reader", "secret");
+ Assert.assertTrue(DbConnectionCache.toPartsForInstallation(creds).isEmpty());
+ }
+
+ @Rule
+ public TemporaryFolder tempFolder = new TemporaryFolder();
+
+ private String writeCredentialsFile(String... lines) throws Exception {
+ File file = tempFolder.newFile("gor.db.credentials");
+ Files.write(file.toPath(), String.join("\n", lines).getBytes(StandardCharsets.UTF_8));
+ return file.getAbsolutePath();
+ }
+
+ @Test
+ public void suppliedCredentialsOnlyInstallRdaSource() {
+ DbConnectionCache cache = new DbConnectionCache();
+ cache.initializeDbSources(rdaCreds("jdbc:postgresql://db:5432/csa", "gregor_reader", "secret"));
+
+ DbConnection rda = cache.lookup("rda");
+ Assert.assertNotNull("rda source should be installed from the supplied credentials", rda);
+ Assert.assertEquals("jdbc:postgresql://db:5432/csa", rda.url);
+ Assert.assertEquals("gregor_reader", rda.user);
+ Assert.assertEquals("secret", rda.pwd);
+ }
+
+ @Test
+ public void blankSuppliedPasswordInstallsSourceWithNullPassword() {
+ DbConnectionCache cache = new DbConnectionCache();
+ cache.initializeDbSources(rdaCreds("jdbc:postgresql://db:5432/csa", "gregor_reader", " "));
+
+ DbConnection rda = cache.lookup("rda");
+ Assert.assertNotNull("source should still install, only the password is unset", rda);
+ Assert.assertNull("whitespace password must not reach the connection as a real password", rda.pwd);
+ }
+
+ @Test
+ public void partialCredentialsSkippedWithoutThrowing() {
+ DbConnectionCache cache = new DbConnectionCache();
+ cache.initializeDbSources(rdaCreds("jdbc:postgresql://db:5432/csa", null, "secret"));
+
+ Assert.assertNull(cache.lookup("rda"));
+ }
+
+ @Test
+ public void suppliedCredentialsReplaceAnythingAlreadyInstalled() throws Exception {
+ String credpath = writeCredentialsFile(
+ "name\tdriver\turl\tuser\tpwd",
+ "aux\torg.postgresql.Driver\tjdbc:postgresql://auxhost:5432/aux\tauxuser\tauxpwd");
+
+ DbConnectionCache cache = new DbConnectionCache();
+ cache.initializeDbSources(credpath);
+ Assert.assertNotNull(cache.lookup("aux"));
+
+ // The two initializers are alternatives, not additive - each clears the cache first, which is
+ // what keeps the system and user caches fed from exactly one source each.
+ cache.initializeDbSources(rdaCreds("jdbc:postgresql://db:5432/csa", "gregor_reader", "secret"));
+ Assert.assertNotNull(cache.lookup("rda"));
+ Assert.assertNull("file sources should not survive a credentials load", cache.lookup("aux"));
+ }
+
+ @Test
+ public void fileInstallsEveryRow() throws Exception {
+ String credpath = writeCredentialsFile(
+ "name\tdriver\turl\tuser\tpwd",
+ "rda\torg.postgresql.Driver\tjdbc:postgresql://filehost:5432/csa\tfileuser\tfilepwd",
+ "aux\torg.postgresql.Driver\tjdbc:postgresql://auxhost:5432/aux\tauxuser\tauxpwd");
+
+ DbConnectionCache cache = new DbConnectionCache();
+ cache.initializeDbSources(credpath);
+
+ Assert.assertEquals("fileuser", cache.lookup("rda").user);
+ Assert.assertEquals("auxuser", cache.lookup("aux").user);
+ }
+
+ @Test(expected = FileNotFoundException.class)
+ public void missingFileThrows() throws Exception {
+ String missing = new File(tempFolder.getRoot(), "does-not-exist.credentials").getAbsolutePath();
+ new DbConnectionCache().initializeDbSources(missing);
+ }
+
+ @Test
+ public void noCredentialPathLeavesCacheEmpty() throws Exception {
+ DbConnectionCache cache = new DbConnectionCache();
+ cache.initializeDbSources((String) null);
+
+ Assert.assertNull(cache.lookup("rda"));
+ }
+}
diff --git a/model/src/test/java/org/gorpipe/gor/model/UTestSqlSource.java b/model/src/test/java/org/gorpipe/gor/model/UTestSqlSource.java
index 032dd0c8..392c3850 100644
--- a/model/src/test/java/org/gorpipe/gor/model/UTestSqlSource.java
+++ b/model/src/test/java/org/gorpipe/gor/model/UTestSqlSource.java
@@ -26,7 +26,6 @@ public class UTestSqlSource {
public static void setup() throws IOException, ClassNotFoundException, SQLException {
paths = DatabaseHelper.createRdaDatabase();
System.setProperty("gor.db.credentials", paths[2]);
- System.setProperty("gor.sql.credentials", paths[2]);
DbConnection.initInConsoleApp();
}