You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gg...@apache.org on 2018/11/20 10:24:19 UTC

[karaf] branch master updated: [KARAF-6014] Improve jdbc:* commands

This is an automated email from the ASF dual-hosted git repository.

ggrzybek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/master by this push:
     new d52ca6e  [KARAF-6014] Improve jdbc:* commands
d52ca6e is described below

commit d52ca6e9c74373cb1961ed81a8bdcafe9338d5f0
Author: Grzegorz Grzybek <gr...@gmail.com>
AuthorDate: Tue Nov 20 10:44:40 2018 +0100

    [KARAF-6014] Improve jdbc:* commands
---
 .../java/org/apache/karaf/jdbc/JdbcService.java    |  8 ++
 .../apache/karaf/jdbc/command/ExecuteCommand.java  |  2 +-
 .../apache/karaf/jdbc/command/QueryCommand.java    |  2 +-
 .../apache/karaf/jdbc/command/TablesCommand.java   |  2 +-
 .../completers/DataSourcesFileNameCompleter.java   | 55 -------------
 .../karaf/jdbc/command/ds/DSFactoriesCommand.java  | 92 +++++++++++++++++++++-
 .../karaf/jdbc/command/ds/DeleteCommand.java       |  4 +-
 .../apache/karaf/jdbc/command/ds/InfoCommand.java  |  2 +-
 .../apache/karaf/jdbc/command/ds/ListCommand.java  | 25 ++++--
 .../apache/karaf/jdbc/internal/JdbcMBeanImpl.java  | 20 ++---
 .../karaf/jdbc/internal/JdbcServiceImpl.java       | 30 ++++++-
 manual/src/main/asciidoc/user-guide/jdbc.adoc      | 11 ++-
 12 files changed, 168 insertions(+), 85 deletions(-)

diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/JdbcService.java b/jdbc/src/main/java/org/apache/karaf/jdbc/JdbcService.java
index 5c245e2..c937a9f 100644
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/JdbcService.java
+++ b/jdbc/src/main/java/org/apache/karaf/jdbc/JdbcService.java
@@ -65,6 +65,14 @@ public interface JdbcService {
     List<String> datasources() throws Exception;
 
     /**
+     * List service IDs of JDBC datasource OSGi services available
+     *
+     * @return A {@link List} of datasources OSGi service IDs.
+     * @throws Exception If the service fails.
+     */
+    List<Long> datasourceServiceIds() throws Exception;
+
+    /**
      * Execute a SQL query on a given JDBC datasource.
      *
      * @param datasource The JDBC datasource name.
diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/command/ExecuteCommand.java b/jdbc/src/main/java/org/apache/karaf/jdbc/command/ExecuteCommand.java
index 137f085..a17cc64 100644
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/command/ExecuteCommand.java
+++ b/jdbc/src/main/java/org/apache/karaf/jdbc/command/ExecuteCommand.java
@@ -30,7 +30,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 @Service
 public class ExecuteCommand extends JdbcCommandSupport {
 
-    @Argument(index = 0, name = "datasource", description = "The JDBC datasource", required = true, multiValued = false)
+    @Argument(index = 0, name = "datasource", description = "The JDBC datasource name, its JNDI name or service.id", required = true, multiValued = false)
     @Completion(DataSourcesNameCompleter.class)
     String datasource;
 
diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/command/QueryCommand.java b/jdbc/src/main/java/org/apache/karaf/jdbc/command/QueryCommand.java
index 34089b3..20c0ed9 100644
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/command/QueryCommand.java
+++ b/jdbc/src/main/java/org/apache/karaf/jdbc/command/QueryCommand.java
@@ -35,7 +35,7 @@ import org.apache.karaf.shell.support.table.ShellTable;
 @Service
 public class QueryCommand extends JdbcCommandSupport {
 
-    @Argument(index = 0, name = "datasource", description = "The JDBC datasource to use", required = true, multiValued = false)
+    @Argument(index = 0, name = "datasource", description = "The JDBC datasource name, its JNDI name or service.id to use", required = true, multiValued = false)
     @Completion(DataSourcesNameCompleter.class)
     String datasource;
 
diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/command/TablesCommand.java b/jdbc/src/main/java/org/apache/karaf/jdbc/command/TablesCommand.java
index 04baa70..afa3a53 100644
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/command/TablesCommand.java
+++ b/jdbc/src/main/java/org/apache/karaf/jdbc/command/TablesCommand.java
@@ -31,7 +31,7 @@ import org.apache.karaf.shell.support.table.ShellTable;
 @Service
 public class TablesCommand extends JdbcCommandSupport {
 
-    @Argument(index = 0, name = "datasource", description = "The JDBC datasource to use", required = true, multiValued = false)
+    @Argument(index = 0, name = "datasource", description = "The JDBC datasource name, its JNDI name or service.id to use", required = true, multiValued = false)
     @Completion(DataSourcesNameCompleter.class)
     String datasource;
 
diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/command/completers/DataSourcesFileNameCompleter.java b/jdbc/src/main/java/org/apache/karaf/jdbc/command/completers/DataSourcesFileNameCompleter.java
deleted file mode 100644
index 98fcb38..0000000
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/command/completers/DataSourcesFileNameCompleter.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.karaf.jdbc.command.completers;
-
-import org.apache.karaf.jdbc.JdbcService;
-import org.apache.karaf.shell.api.action.lifecycle.Reference;
-import org.apache.karaf.shell.api.action.lifecycle.Service;
-import org.apache.karaf.shell.api.console.CommandLine;
-import org.apache.karaf.shell.api.console.Completer;
-import org.apache.karaf.shell.api.console.Session;
-import org.apache.karaf.shell.support.completers.StringsCompleter;
-
-import java.util.List;
-
-/**
- * Completer on the JDBC datasources file name.
- */
-@Service
-public class DataSourcesFileNameCompleter implements Completer {
-
-    @Reference
-    private JdbcService jdbcService;
-
-    @Override
-    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
-        StringsCompleter delegate = new StringsCompleter();
-        try {
-            for (String datasourceFileName : jdbcService.datasources()) {
-                delegate.getStrings().add(datasourceFileName);
-            }
-        } catch (Exception e) {
-            // nothing to do
-        }
-        return delegate.complete(session, commandLine, candidates);
-    }
-
-    public void setJdbcService(JdbcService jdbcService) {
-        this.jdbcService = jdbcService;
-    }
-
-}
diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/DSFactoriesCommand.java b/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/DSFactoriesCommand.java
index 9cf5c9d..a0aa8e4 100644
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/DSFactoriesCommand.java
+++ b/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/DSFactoriesCommand.java
@@ -17,6 +17,10 @@
 package org.apache.karaf.jdbc.command.ds;
 
 import java.util.Collection;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeSet;
 
 import org.apache.karaf.jdbc.command.JdbcCommandSupport;
 import org.apache.karaf.shell.api.action.Command;
@@ -33,6 +37,8 @@ public class DSFactoriesCommand extends JdbcCommandSupport {
 
     @Reference
     BundleContext context;
+
+    private Comparator<DataSourceFactoryInfo> comparator = new DataSourceFactoryComparator();
     
     @Override
     public Object execute() throws Exception {
@@ -40,15 +46,93 @@ public class DSFactoriesCommand extends JdbcCommandSupport {
         table.column("Name");
         table.column("Class");
         table.column("Version");
+        table.column("Registration bundle");
+
+        Set<DataSourceFactoryInfo> factories = new TreeSet<>(comparator);
+
         Collection<ServiceReference<DataSourceFactory>> refs = context.getServiceReferences(DataSourceFactory.class, null);
         for (ServiceReference<DataSourceFactory> ref : refs) {
-            String driverName = (String)ref.getProperty(DataSourceFactory.OSGI_JDBC_DRIVER_NAME);
-            String driverClass = (String)ref.getProperty(DataSourceFactory.OSGI_JDBC_DRIVER_CLASS);
-            String driverVersion = (String)ref.getProperty(DataSourceFactory.OSGI_JDBC_DRIVER_VERSION);
-            table.addRow().addContent(driverName, driverClass, driverVersion);
+            DataSourceFactoryInfo info = new DataSourceFactoryInfo();
+            info.driverName = (String)ref.getProperty(DataSourceFactory.OSGI_JDBC_DRIVER_NAME);
+            info.driverClass = (String)ref.getProperty(DataSourceFactory.OSGI_JDBC_DRIVER_CLASS);
+            info.driverVersion = (String)ref.getProperty(DataSourceFactory.OSGI_JDBC_DRIVER_VERSION);
+            if (ref.getBundle() != null && ref.getBundle().getSymbolicName() != null) {
+                info.bundle = String.format("%s [%s]",
+                        ref.getBundle().getSymbolicName(),
+                        ref.getBundle().getBundleId());
+            } else {
+                info.bundle = "";
+            }
+            factories.add(info);
+        }
+        for (DataSourceFactoryInfo info : factories) {
+            table.addRow().addContent(info.driverName, info.driverClass, info.driverVersion, info.bundle);
         }
+
         table.print(System.out);
         return null;
     }
 
+    private static class DataSourceFactoryComparator implements Comparator<DataSourceFactoryInfo> {
+        @Override
+        public int compare(DataSourceFactoryInfo dsf1, DataSourceFactoryInfo dsf2) {
+            int r1 = 0;
+            int r2 = 0;
+            int r3 = 0;
+
+            if (dsf1.bundle != null || dsf2.bundle != null) {
+                if (dsf1.bundle == null) {
+                    r1 = -1;
+                } else if (dsf2.bundle == null) {
+                    r1 = 1;
+                } else {
+                    r1 = dsf1.bundle.compareTo(dsf2.bundle);
+                }
+            }
+            if (dsf1.driverName != null || dsf2.driverName != null) {
+                if (dsf1.driverName == null) {
+                    r2 = -1;
+                } else if (dsf2.driverName == null) {
+                    r2 = 1;
+                } else {
+                    r2 = dsf1.driverName.compareTo(dsf2.driverName);
+                }
+            }
+            if (dsf1.driverClass != null || dsf2.driverClass != null) {
+                if (dsf1.driverClass == null) {
+                    r3 = -1;
+                } else if (dsf2.driverClass == null) {
+                    r3 = 1;
+                } else {
+                    r3 = dsf1.driverClass.compareTo(dsf2.driverClass);
+                }
+            }
+
+            return r1 == 0 ? (r2 == 0 ? r3 : r2) : r1;
+        }
+    }
+
+    private static class DataSourceFactoryInfo {
+        public String driverName;
+        public String driverClass;
+        public String driverVersion;
+        public String bundle;
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            DataSourceFactoryInfo that = (DataSourceFactoryInfo) o;
+            return Objects.equals(driverName, that.driverName) &&
+                    Objects.equals(driverClass, that.driverClass) &&
+                    Objects.equals(driverVersion, that.driverVersion) &&
+                    Objects.equals(bundle, that.bundle);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(driverName, driverClass, driverVersion, bundle);
+        }
+    }
+
 }
diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/DeleteCommand.java b/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/DeleteCommand.java
index afa025d..7f268dd 100644
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/DeleteCommand.java
+++ b/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/DeleteCommand.java
@@ -17,7 +17,7 @@
 package org.apache.karaf.jdbc.command.ds;
 
 import org.apache.karaf.jdbc.command.JdbcCommandSupport;
-import org.apache.karaf.jdbc.command.completers.DataSourcesFileNameCompleter;
+import org.apache.karaf.jdbc.command.completers.DataSourcesNameCompleter;
 import org.apache.karaf.shell.api.action.Argument;
 import org.apache.karaf.shell.api.action.Command;
 import org.apache.karaf.shell.api.action.Completion;
@@ -28,7 +28,7 @@ import org.apache.karaf.shell.api.action.lifecycle.Service;
 public class DeleteCommand extends JdbcCommandSupport {
 
     @Argument(index = 0, name = "name", description = "The JDBC datasource name (the one used at creation time)", required = true, multiValued = false)
-    @Completion(DataSourcesFileNameCompleter.class)
+    @Completion(DataSourcesNameCompleter.class)
     String name;
 
     @Override
diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/InfoCommand.java b/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/InfoCommand.java
index 0ab22c5..deff3bd 100644
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/InfoCommand.java
+++ b/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/InfoCommand.java
@@ -30,7 +30,7 @@ import java.util.Map;
 @Service
 public class InfoCommand extends JdbcCommandSupport {
 
-    @Argument(index = 0, name = "datasource", description = "The JDBC datasource name", required = true, multiValued = false)
+    @Argument(index = 0, name = "datasource", description = "The JDBC datasource name, its JNDI name or service.id", required = true, multiValued = false)
     @Completion(DataSourcesNameCompleter.class)
     String datasource;
 
diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/ListCommand.java b/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/ListCommand.java
index d665fa5..5353872 100644
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/ListCommand.java
+++ b/jdbc/src/main/java/org/apache/karaf/jdbc/command/ds/ListCommand.java
@@ -16,6 +16,8 @@
  */
 package org.apache.karaf.jdbc.command.ds;
 
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -32,22 +34,35 @@ public class ListCommand extends JdbcCommandSupport {
     public Object execute() throws Exception {
         ShellTable table = new ShellTable();
         table.column("Name");
+        table.column("Service Id");
         table.column("Product");
         table.column("Version");
         table.column("URL");
         table.column("Status");
 
-        List<String> datasources = this.getJdbcService().datasources();
-        for (String dsName: datasources) {
+        boolean duplication = false;
+        Map<String, Long> nameToId = new HashMap<>();
+
+        List<Long> datasources = this.getJdbcService().datasourceServiceIds();
+        Collections.sort(datasources);
+        for (Long id: datasources) {
             try {
-                Map<String, String> info = this.getJdbcService().info(dsName);
-                table.addRow().addContent(dsName, info.get("db.product"), info.get("db.version"), info.get("url"), "OK");
+                Map<String, String> info = this.getJdbcService().info(Long.toString(id));
+                table.addRow().addContent(info.get("name"), info.get("service.id"), info.get("db.product"), info.get("db.version"), info.get("url"), "OK");
+                if (nameToId.put(info.get("name"), id) != null) {
+                    duplication = true;
+                }
             } catch (Exception e) {
-                table.addRow().addContent(dsName, "", "", "", "Error " + e.getMessage());
+                table.addRow().addContent(id, "", "", "", "", "Error " + e.getMessage());
             }
         }
 
         table.print(System.out);
+
+        if (duplication) {
+            System.out.println("\nThere are multiple data source services registered with the same name. Please review your configuration.");
+        }
+
         return null;
     }
 
diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/internal/JdbcMBeanImpl.java b/jdbc/src/main/java/org/apache/karaf/jdbc/internal/JdbcMBeanImpl.java
index 630106c..22128bf 100644
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/internal/JdbcMBeanImpl.java
+++ b/jdbc/src/main/java/org/apache/karaf/jdbc/internal/JdbcMBeanImpl.java
@@ -36,24 +36,24 @@ public class JdbcMBeanImpl implements JdbcMBean {
     public TabularData getDatasources() throws MBeanException {
         try {
             CompositeType type = new CompositeType("DataSource", "JDBC DataSource",
-                    new String[]{ "name", "product", "version", "url", "status"},
-                    new String[]{ "Name", "Database product", "Database version", "JDBC URL", "Status" },
-                    new OpenType[]{ SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING });
+                    new String[]{ "name", "product", "version", "url", "status", "service.id"},
+                    new String[]{ "Name", "Database product", "Database version", "JDBC URL", "Status", "Service ID" },
+                    new OpenType[]{ SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING });
             TabularType tableType = new TabularType("JDBC DataSources", "Table of the JDBC DataSources",
-                    type, new String[]{ "name" });
+                    type, new String[]{ "service.id" });
             TabularData table = new TabularDataSupport(tableType);
 
-            for (String datasource : jdbcService.datasources()) {
+            for (Long datasource : jdbcService.datasourceServiceIds()) {
                 try {
-                    Map<String, String> info = jdbcService.info(datasource);
+                    Map<String, String> info = jdbcService.info(Long.toString(datasource));
                     CompositeData data = new CompositeDataSupport(type,
-                            new String[]{"name", "product", "version", "url", "status"},
-                            new Object[]{datasource, info.get("db.product"), info.get("db.version"), info.get("url"), "OK"});
+                            new String[]{"name", "product", "version", "url", "status", "service.id"},
+                            new Object[]{info.get("name"), info.get("db.product"), info.get("db.version"), info.get("url"), "OK", info.get("service.id")});
                     table.put(data);
                 } catch (Exception e) {
                     CompositeData data = new CompositeDataSupport(type,
-                            new String[]{"name", "product", "version", "url", "status"},
-                            new Object[]{datasource, "", "", "", "ERROR"});
+                            new String[]{"name", "product", "version", "url", "status", "service.id"},
+                            new Object[]{datasource, "", "", "", "ERROR", ""});
                     table.put(data);
                 }
             }
diff --git a/jdbc/src/main/java/org/apache/karaf/jdbc/internal/JdbcServiceImpl.java b/jdbc/src/main/java/org/apache/karaf/jdbc/internal/JdbcServiceImpl.java
index 896bce8..7cf52c9 100644
--- a/jdbc/src/main/java/org/apache/karaf/jdbc/internal/JdbcServiceImpl.java
+++ b/jdbc/src/main/java/org/apache/karaf/jdbc/internal/JdbcServiceImpl.java
@@ -117,6 +117,22 @@ public class JdbcServiceImpl implements JdbcService {
     }
 
     @Override
+    public List<Long> datasourceServiceIds() throws Exception {
+        List<Long> datasources = new ArrayList<>();
+
+        ServiceReference<?>[] references = bundleContext.getServiceReferences((String) null,
+                "(|(" + Constants.OBJECTCLASS + "=" + DataSource.class.getName() + ")("
+                        + Constants.OBJECTCLASS + "=" + XADataSource.class.getName() + "))");
+        if (references != null) {
+            for (ServiceReference reference : references) {
+                datasources.add((Long) reference.getProperty(Constants.SERVICE_ID));
+            }
+        }
+
+        return datasources;
+    }
+
+    @Override
     public Map<String, List<String>> query(String datasource, String query) throws Exception {
         try (JdbcConnector jdbcConnector = new JdbcConnector(bundleContext, lookupDataSource(datasource))) {
             Map<String, List<String>> map = new HashMap<>();
@@ -163,9 +179,21 @@ public class JdbcServiceImpl implements JdbcService {
 
     @Override
     public Map<String, String> info(String datasource) throws Exception {
-        try (JdbcConnector jdbcConnector = new JdbcConnector(bundleContext, lookupDataSource(datasource))) {
+        ServiceReference<?> reference = lookupDataSource(datasource);
+        String dsName = datasource;
+        if (reference.getProperty("osgi.jndi.service.name") != null) {
+            dsName = (String) reference.getProperty("osgi.jndi.service.name");
+        } else if (reference.getProperty("datasource") != null) {
+            dsName = (String) reference.getProperty("datasource");
+        } else if (reference.getProperty("name") != null) {
+            dsName = (String) reference.getProperty("name");
+        }
+
+        try (JdbcConnector jdbcConnector = new JdbcConnector(bundleContext, reference)) {
             DatabaseMetaData dbMetaData = jdbcConnector.connect().getMetaData();
             Map<String, String> map = new HashMap<>();
+            map.put("name", dsName);
+            map.put("service.id", reference.getProperty(Constants.SERVICE_ID).toString());
             map.put("db.product", dbMetaData.getDatabaseProductName());
             map.put("db.version", dbMetaData.getDatabaseProductVersion());
             map.put("url", dbMetaData.getURL());
diff --git a/manual/src/main/asciidoc/user-guide/jdbc.adoc b/manual/src/main/asciidoc/user-guide/jdbc.adoc
index 8ed2562..873a632 100644
--- a/manual/src/main/asciidoc/user-guide/jdbc.adoc
+++ b/manual/src/main/asciidoc/user-guide/jdbc.adoc
@@ -110,20 +110,22 @@ The `jdbc:ds-list` command lists the JDBC datasources:
 
 ----
 karaf@root()> jdbc:ds-list
-Name | Product | Version | URL | Status
----------------------------------------
-
+Name │ Service Id │ Product        │ Version              │ URL             │ Status
+─────┼────────────┼────────────────┼──────────────────────┼─────────────────┼───────
+test │ 112        │ Apache Derby   │ 10.8.2.2 - (1181258) │ jdbc:derby:test │ OK
 ----
 
 ===== `jdbc:ds-info`
 
-The `jdbc:ds-info` command provides details about a JDBC datasource:
+The `jdbc:ds-info` command provides details about a JDBC datasource. The data source may be specified using name
+or service.id:
 
 ----
 karaf@root()> jdbc:ds-info test
 Property       | Value
 --------------------------------------------------
 driver.version | 10.8.2.2 - (1181258)
+service.id     | 112
 username       | APP
 db.version     | 10.8.2.2 - (1181258)
 db.product     | Apache Derby
@@ -208,6 +210,7 @@ The object name to use is `org.apache.karaf:type=jdbc,name=*`.
 The `Datasources` attribute provides a tabular data of all JDBC datasource, containing:
 
 * `name` is the JDBC datasource name
+* `service.id` is the JDBC datasource ID of OSGi service
 * `product` is the database product backend
 * `url` is the JDBC URL used by the datasource
 * `version` is the database version backend.