Skip to content

Commit

Permalink
feat!(java/driver-manager): support loading AdbcDrivers from the Serv…
Browse files Browse the repository at this point in the history
…iceLoader (#1475)

Support loading AdbcDriver classes using the ServiceLoader instead of
require explicit registration.

An AdbcDriver module can add a file in META-INF/services named
org.apache.arrow.adbc.core.AdbcDriverFactory that contains the
fully-qualified class names of any AdbcDriver Factory implementations.
The AdbcDriverFactory can be implemented to construct AdbcDriver
instances. It must have a public no-arg constructor.

Update the FlightSQL and JDBC drivers to match above changes.

BREAKING CHANGE: 
- AdbcDriverManager#lookup() is now package-private.
- AdbcDriverManager has a private instead of public constructor.

Closes #48.
  • Loading branch information
jduo authored Jan 26, 2024
1 parent 5299ea0 commit 460937c
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 35 deletions.
6 changes: 6 additions & 0 deletions java/driver-manager/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,11 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.arrow</groupId>
<artifactId>arrow-memory-unsafe</artifactId>
<version>${dep.arrow.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.arrow.adbc.drivermanager;

import org.apache.arrow.adbc.core.AdbcDriver;
import org.apache.arrow.memory.BufferAllocator;

/** A factory for instantiating new AdbcDrivers. */
public interface AdbcDriverFactory {
/**
* Gets a new AdbcDriver.
*
* @param allocator The allocator to associate with the AdbcDriver.
* @return a new AdbcDriver.
*/
AdbcDriver getDriver(BufferAllocator allocator);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.arrow.adbc.drivermanager;

import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
Expand All @@ -27,55 +28,70 @@
import org.apache.arrow.adbc.core.AdbcStatusCode;
import org.apache.arrow.memory.BufferAllocator;

/**
* Load an ADBC driver based on name.
*
* <p>This class is EXPERIMENTAL. It will be rewritten before 1.0.0 (see <a
* href="https://github.com/apache/arrow-adbc/issues/48">issue #48</a>).
*/
/** Instantiate connections to ABDC databases generically based on driver name. */
public final class AdbcDriverManager {
private static final AdbcDriverManager INSTANCE = new AdbcDriverManager();

private final ConcurrentMap<String, Function<BufferAllocator, AdbcDriver>> drivers;
private final ConcurrentMap<String, Function<BufferAllocator, AdbcDriver>> driverFactoryFunctions;

public AdbcDriverManager() {
drivers = new ConcurrentHashMap<>();
private AdbcDriverManager() {
driverFactoryFunctions = new ConcurrentHashMap<>();
final ServiceLoader<AdbcDriverFactory> serviceLoader =
ServiceLoader.load(AdbcDriverFactory.class);
serviceLoader.forEach(
driverFactory ->
driverFactoryFunctions.putIfAbsent(
driverFactory.getClass().getCanonicalName(), driverFactory::getDriver));
}

/**
* Connect to a database.
*
* @param driverName The driver to use.
* @param driverFactoryName The driver to use.
* @param allocator The allocator to use.
* @param parameters Parameters for the driver.
* @return The AdbcDatabase instance.
* @throws AdbcException if the driver was not found or if connection fails.
*/
public AdbcDatabase connect(
String driverName, BufferAllocator allocator, Map<String, Object> parameters)
String driverFactoryName, BufferAllocator allocator, Map<String, Object> parameters)
throws AdbcException {
final Function<BufferAllocator, AdbcDriver> driver = lookupDriver(driverName);
if (driver == null) {
final Function<BufferAllocator, AdbcDriver> driverFactoryFunction =
lookupDriver(driverFactoryName);
if (driverFactoryFunction == null) {
throw new AdbcException(
"Driver not found for '" + driverName + "'", null, AdbcStatusCode.NOT_FOUND, null, 0);
"AdbcDriverFactory not found for '" + driverFactoryName + "'",
null,
AdbcStatusCode.NOT_FOUND,
null,
0);
}
return driver.apply(allocator).open(parameters);

return driverFactoryFunction.apply(allocator).open(parameters);
}

/**
* Lookup a registered driver.
*
* @param driverName The driver to lookup.
* @return The driver instance, or null if not found.
* @param driverFactoryName The driver factory function to lookup. This is usually the
* fully-qualified class name of an AdbcDriverFactory class.
* @return A function to construct an AdbcDriver from a BufferAllocator, or null if not found.
*/
public Function<BufferAllocator, AdbcDriver> lookupDriver(String driverName) {
return drivers.get(driverName);
Function<BufferAllocator, AdbcDriver> lookupDriver(String driverFactoryName) {
return driverFactoryFunctions.get(driverFactoryName);
}

public void registerDriver(String driverName, Function<BufferAllocator, AdbcDriver> driver) {
if (drivers.putIfAbsent(driverName, driver) != null) {
/**
* Register a driver manually.
*
* @param driverFactoryName The name of the driver to associate with the construction function.
* @param driverFactory The function to use to instantiate the driver.
*/
public void registerDriver(
String driverFactoryName, Function<BufferAllocator, AdbcDriver> driverFactory) {
if (driverFactoryFunctions.putIfAbsent(driverFactoryName, driverFactory) != null) {
throw new IllegalStateException(
"[DriverManager] Driver already registered for '" + driverName + "'");
"[DriverManager] Driver factory already registered for '" + driverFactoryName + "'");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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.arrow.adbc.drivermanager;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.function.Function;
import org.apache.arrow.adbc.core.AdbcDriver;
import org.apache.arrow.adbc.test.TestDriver;
import org.apache.arrow.adbc.test.TestDriverFactory;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
import org.junit.jupiter.api.Test;

/** Test cases for {@link AdbcDriverManager}. */
public class AdbcDriverManagerTest {
@Test
public void testDriverFromServiceLoader() {
final Function<BufferAllocator, AdbcDriver> driverFactoryFunction =
AdbcDriverManager.getInstance().lookupDriver(TestDriverFactory.class.getCanonicalName());

try (BufferAllocator allocator = new RootAllocator()) {
final AdbcDriver driverInstance = driverFactoryFunction.apply(allocator);
assertThat(driverInstance.getClass()).isEqualTo(TestDriver.class);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.arrow.adbc.test;

import java.util.Map;
import org.apache.arrow.adbc.core.AdbcDatabase;
import org.apache.arrow.adbc.core.AdbcDriver;

/** A dummy {@link AdbcDriver} implementation for testing purposes. */
public class TestDriver implements AdbcDriver {
public TestDriver() {}

@Override
public AdbcDatabase open(Map<String, Object> parameters) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.arrow.adbc.test;

import org.apache.arrow.adbc.core.AdbcDriver;
import org.apache.arrow.adbc.drivermanager.AdbcDriverFactory;
import org.apache.arrow.memory.BufferAllocator;

/** Dummy AdbcDriverFactory used for testing. */
public class TestDriverFactory implements AdbcDriverFactory {
@Override
public AdbcDriver getDriver(BufferAllocator allocator) {
// This dummy driver doesn't need to use its allocator.
return new TestDriver();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 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.

org.apache.arrow.adbc.test.TestDriverFactory
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,16 @@
import org.apache.arrow.adbc.core.AdbcDatabase;
import org.apache.arrow.adbc.core.AdbcDriver;
import org.apache.arrow.adbc.core.AdbcException;
import org.apache.arrow.adbc.drivermanager.AdbcDriverManager;
import org.apache.arrow.adbc.sql.SqlQuirks;
import org.apache.arrow.flight.Location;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.util.Preconditions;

/** An ADBC driver wrapping Arrow Flight SQL. */
public class FlightSqlDriver implements AdbcDriver {
static {
AdbcDriverManager.getInstance()
.registerDriver("org.apache.arrow.adbc.driver.flightsql", FlightSqlDriver::new);
}

private final BufferAllocator allocator;

FlightSqlDriver(BufferAllocator allocator) {
public FlightSqlDriver(BufferAllocator allocator) {
this.allocator = Objects.requireNonNull(allocator);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.arrow.adbc.driver.flightsql;

import org.apache.arrow.adbc.core.AdbcDriver;
import org.apache.arrow.adbc.drivermanager.AdbcDriverFactory;
import org.apache.arrow.memory.BufferAllocator;

/** Constructs new FlightSqlDriver instances. */
public class FlightSqlDriverFactory implements AdbcDriverFactory {
@Override
public AdbcDriver getDriver(BufferAllocator allocator) {
return new FlightSqlDriver(allocator);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 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.

org.apache.arrow.adbc.driver.flightsql.FlightSqlDriverFactory
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import org.apache.arrow.adbc.core.AdbcDatabase;
import org.apache.arrow.adbc.core.AdbcDriver;
import org.apache.arrow.adbc.core.AdbcException;
import org.apache.arrow.adbc.drivermanager.AdbcDriverManager;
import org.apache.arrow.adbc.sql.SqlQuirks;
import org.apache.arrow.memory.BufferAllocator;

Expand All @@ -40,11 +39,6 @@ public class JdbcDriver implements AdbcDriver {
*/
public static final String PARAM_URI = "uri";

static {
AdbcDriverManager.getInstance()
.registerDriver("org.apache.arrow.adbc.driver.jdbc", JdbcDriver::new);
}

private final BufferAllocator allocator;

public JdbcDriver(BufferAllocator allocator) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.arrow.adbc.driver.jdbc;

import org.apache.arrow.adbc.core.AdbcDriver;
import org.apache.arrow.adbc.drivermanager.AdbcDriverFactory;
import org.apache.arrow.memory.BufferAllocator;

/** Constructs new JdbcDriver instances. */
public class JdbcDriverFactory implements AdbcDriverFactory {
@Override
public AdbcDriver getDriver(BufferAllocator allocator) {
return new JdbcDriver(allocator);
}
}
Loading

0 comments on commit 460937c

Please sign in to comment.