Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 5 additions & 5 deletions presto-docs/src/main/sphinx/connector/mongodb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ This connector allows the use of MongoDB collections as tables in Presto.

.. note::

MongoDB 2.6+ is supported although it is highly recommend to use 3.0 or later.
This connector uses MongoDB Java Sync Driver version 5.6.5, for which MongoDB 4.2 or above is recommended. Supported MongoDB server versions should follow the driver compatibility matrix in the MongoDB documentation:
https://www.mongodb.com/docs/drivers/compatibility/?driver-language=java&java-driver-framework=java-sync#mongodb-server-compatibility-5

Configuration
-------------
Expand Down Expand Up @@ -39,7 +40,7 @@ Property Name Description
===================================== ============================================================== ===========
``mongodb.seeds`` List of all MongoDB servers
``mongodb.schema-collection`` A collection which contains schema information ``_schema``
``mongodb.credentials`` List of credentials
``mongodb.credentials`` Authentication credential
``mongodb.min-connections-per-host`` The minimum size of the connection pool per host ``0``
``mongodb.connections-per-host`` The maximum size of the connection pool per host ``100``
``mongodb.max-wait-time`` The maximum wait time ``120000ms``
Expand Down Expand Up @@ -81,7 +82,7 @@ This property is optional; the default is ``_schema``.
``mongodb.credentials``
^^^^^^^^^^^^^^^^^^^^^^^

A comma separated list of ``username:password@collection`` credentials
Authentication credential in the format ``username:password@authenticationDatabase``.

This property is optional; no default value.

Expand Down Expand Up @@ -180,8 +181,7 @@ This property is optional; the default is ``PRIMARY``.
^^^^^^^^^^^^^^^^^^^^^^^^^

The write concern to use. The available values are
``ACKNOWLEDGED``, ``FSYNC_SAFE``, ``FSYNCED``, ``JOURNAL_SAFE``, ``JOURNALED``, ``MAJORITY``,
``NORMAL``, ``REPLICA_ACKNOWLEDGED``, ``REPLICAS_SAFE`` and ``UNACKNOWLEDGED``.
``ACKNOWLEDGED``, ``JOURNALED``, ``MAJORITY``, ``UNACKNOWLEDGED``, ``W1``, ``W2`` and ``W3``.

This property is optional; the default is ``ACKNOWLEDGED``.

Expand Down
16 changes: 14 additions & 2 deletions presto-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<properties>
<air.main.basedir>${project.parent.basedir}</air.main.basedir>
<mongo-java.version>3.12.14</mongo-java.version>
<mongo-java.version>5.6.5</mongo-java.version>
<mongo-server.version>1.47.0</mongo-server.version>
<air.check.skip-modernizer>true</air.check.skip-modernizer>
</properties>
Expand All @@ -28,7 +28,7 @@

<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<artifactId>mongodb-driver-sync</artifactId>
<version>${mongo-java.version}</version>
<exclusions>
<exclusion>
Expand All @@ -38,6 +38,18 @@
</exclusions>
</dependency>

<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-core</artifactId>
<version>${mongo-java.version}</version>
</dependency>

<dependency>
<groupId>org.mongodb</groupId>
<artifactId>bson</artifactId>
<version>${mongo-java.version}</version>
</dependency>

<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class MongoClientConfig
private static final Splitter TAG_SPLITTER = Splitter.on(':').trimResults().omitEmptyStrings();
private String schemaCollection = "_schema";
private List<ServerAddress> seeds = ImmutableList.of();
private List<MongoCredential> credentials = ImmutableList.of();
private MongoCredential credential;

private int minConnectionsPerHost;
private int connectionsPerHost = 100;
Expand Down Expand Up @@ -181,23 +181,16 @@ public MongoClientConfig setSeeds(String... seeds)
return this;
}

@NotNull
@Size(min = 0)
public List<MongoCredential> getCredentials()
public Optional<MongoCredential> getCredentials()
{
return credentials;
return Optional.ofNullable(credential);
}

@Config("mongodb.credentials")
public MongoClientConfig setCredentials(String credentials)
{
this.credentials = buildCredentials(SPLITTER.split(credentials));
return this;
}

public MongoClientConfig setCredentials(String... credentials)
@ConfigSecuritySensitive
public MongoClientConfig setCredentials(String credential)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, we can annotate this with @ConfigSecuritySensitive

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ConfigSecuritySensitive added

{
this.credentials = buildCredentials(Arrays.asList(credentials));
this.credential = credential == null ? null : buildCredential(credential);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Connection works fine even with a null credential? Or this should throw an error?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, connections work fine with null credentials. This is intentional and correct behavior because MongoDB supports unauthenticated connections. No exception will throw if authentication is disabled.

if authentication is enabled and doesn't provide any credentials, the query will fail with authentication error like this

com.mongodb.MongoQueryException: Command execution failed on MongoDB server with error 13 (Unauthorized): 'Command find requires authentication

return this;
}

Expand All @@ -217,23 +210,21 @@ private List<ServerAddress> buildSeeds(Iterable<String> hostPorts)
return builder.build();
}

private List<MongoCredential> buildCredentials(Iterable<String> userPasses)
private MongoCredential buildCredential(String userPass)
{
ImmutableList.Builder<MongoCredential> builder = ImmutableList.builder();
for (String userPass : userPasses) {
int atPos = userPass.lastIndexOf('@');
checkArgument(atPos > 0, "Invalid credential format. Required user:password@collection");
String userAndPassword = userPass.substring(0, atPos);
String collection = userPass.substring(atPos + 1);
checkArgument(!userPass.contains(","), "Multiple credentials are not supported. Configure a single credential in the format user:password@authenticationDatabase");

int colonPos = userAndPassword.indexOf(':');
checkArgument(colonPos > 0, "Invalid credential format. Required user:password@collection");
String user = userAndPassword.substring(0, colonPos);
String password = userAndPassword.substring(colonPos + 1);
int atPos = userPass.lastIndexOf('@');
checkArgument(atPos > 0, "Invalid credential format. Required user:password@authenticationDatabase");
String userAndPassword = userPass.substring(0, atPos);
String authenticationDatabase = userPass.substring(atPos + 1);

builder.add(createCredential(user, collection, password.toCharArray()));
}
return builder.build();
int colonPos = userAndPassword.indexOf(':');
checkArgument(colonPos > 0, "Invalid credential format. Required user:password@authenticationDatabase");
String user = userAndPassword.substring(0, colonPos);
String password = userAndPassword.substring(colonPos + 1);

return createCredential(user, authenticationDatabase, password.toCharArray());
}

@Min(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.Scopes;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.ReadPreference;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import jakarta.inject.Singleton;

import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.facebook.airlift.configuration.ConfigBinder.configBinder;
import static java.util.Objects.requireNonNull;

Expand All @@ -46,38 +52,67 @@ public static MongoSession createMongoSession(TypeManager typeManager, MongoClie
{
requireNonNull(config, "config is null");

MongoClientOptions.Builder options = MongoClientOptions.builder()
.connectionsPerHost(config.getConnectionsPerHost())
.connectTimeout(config.getConnectionTimeout())
.socketTimeout(config.getSocketTimeout())
.socketKeepAlive(config.getSocketKeepAlive())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

socketKeepAlive is removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. The socketKeepAlive() method has been removed from the driver API

  • Socket keep-alive is now always enabled by default and controlled at the OS level

.maxWaitTime(config.getMaxWaitTime())
.minConnectionsPerHost(config.getMinConnectionsPerHost())
.writeConcern(config.getWriteConcern().getWriteConcern());
MongoClientSettings.Builder settingsBuilder = MongoClientSettings.builder();

String connectionString = buildConnectionString(config);
settingsBuilder.applyConnectionString(new ConnectionString(connectionString));

config.getCredentials().ifPresent(settingsBuilder::credential);

settingsBuilder.applyToConnectionPoolSettings(builder -> builder
.maxSize(config.getConnectionsPerHost())
.minSize(config.getMinConnectionsPerHost())
.maxWaitTime(config.getMaxWaitTime(), TimeUnit.MILLISECONDS));

settingsBuilder.applyToSocketSettings(builder -> builder
.connectTimeout(config.getConnectionTimeout(), TimeUnit.MILLISECONDS)
.readTimeout(config.getSocketTimeout(), TimeUnit.MILLISECONDS));

settingsBuilder.writeConcern(config.getWriteConcern().getWriteConcern());

settingsBuilder.readPreference(configureReadPreference(config));

if (config.getRequiredReplicaSetName() != null) {
options.requiredReplicaSetName(config.getRequiredReplicaSetName());
settingsBuilder.applyToClusterSettings(builder ->
builder.requiredReplicaSetName(config.getRequiredReplicaSetName()));
}

configureReadPreference(options, config);
configureSsl(options, config);
configureSsl(settingsBuilder, config);

MongoClient client = new MongoClient(config.getSeeds(), config.getCredentials(), options.build());
MongoClient client = MongoClients.create(settingsBuilder.build());

return new MongoSession(typeManager, client, config);
}

private static void configureReadPreference(MongoClientOptions.Builder options, MongoClientConfig config)
private static String buildConnectionString(MongoClientConfig config)
{
StringBuilder connectionString = new StringBuilder("mongodb://");

connectionString.append(config.getSeeds().stream()
.map(addr -> addr.getHost() + ":" + addr.getPort())
.collect(Collectors.joining(",")));

config.getCredentials().ifPresent(credential ->
connectionString.append("/")
.append(credential.getSource()));

// Enable replica set discovery when replica set name is configured or multiple seeds are provided
if (config.getRequiredReplicaSetName() != null || config.getSeeds().size() > 1) {
connectionString.append("?directConnection=false");
}
return connectionString.toString();
}
private static ReadPreference configureReadPreference(MongoClientConfig config)
{
if (config.getReadPreferenceTags().isEmpty()) {
options.readPreference(config.getReadPreference().getReadPreference());
return config.getReadPreference().getReadPreference();
}
else {
options.readPreference(config.getReadPreference().getReadPreferenceWithTags(config.getReadPreferenceTags()));
return config.getReadPreference().getReadPreferenceWithTags(config.getReadPreferenceTags());
}
}

private static void configureSsl(MongoClientOptions.Builder options, MongoClientConfig config)
private static void configureSsl(MongoClientSettings.Builder settings, MongoClientConfig config)
{
if (config.isTlsEnabled()) {
SslContextProvider sslContextProvider = new SslContextProvider(
Expand All @@ -88,8 +123,9 @@ private static void configureSsl(MongoClientOptions.Builder options, MongoClient

sslContextProvider.buildSslContext()
.ifPresent(sslContext -> {
options.sslContext(sslContext);
options.sslEnabled(true);
settings.applyToSslSettings(builder -> builder
.enabled(true)
.context(sslContext));
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.mongodb.MongoClient;
import com.mongodb.MongoNamespace;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,12 @@
public enum WriteConcernType
{
ACKNOWLEDGED(WriteConcern.ACKNOWLEDGED),
FSYNC_SAFE(WriteConcern.FSYNC_SAFE),
FSYNCED(WriteConcern.FSYNCED),
JOURNAL_SAFE(WriteConcern.JOURNAL_SAFE),
JOURNALED(WriteConcern.JOURNALED),
MAJORITY(WriteConcern.MAJORITY),
NORMAL(WriteConcern.NORMAL),
REPLICA_ACKNOWLEDGED(WriteConcern.REPLICA_ACKNOWLEDGED),
REPLICAS_SAFE(WriteConcern.REPLICAS_SAFE),
SAFE(WriteConcern.SAFE),
UNACKNOWLEDGED(WriteConcern.UNACKNOWLEDGED);
UNACKNOWLEDGED(WriteConcern.UNACKNOWLEDGED),
W1(WriteConcern.W1),
W2(WriteConcern.W2),
W3(WriteConcern.W3);

private final WriteConcern writeConcern;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import com.facebook.presto.tpch.TpchPlugin;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.mongodb.MongoClient;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import de.bwaldvogel.mongo.MongoServer;
import io.airlift.tpch.TpchTable;

Expand Down Expand Up @@ -48,7 +48,7 @@ private MongoQueryRunner(Session session, int workers)

server = new MongoServer(new SyncMemoryBackend());
address = server.bind();
client = new MongoClient(new ServerAddress(address));
client = MongoClients.create("mongodb://" + address.getHostString() + ":" + address.getPort());
}

public static MongoQueryRunner createMongoQueryRunner(TpchTable<?>... tables)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public void testDefaults()
ConfigAssertions.assertRecordedDefaults(ConfigAssertions.recordDefaults(MongoClientConfig.class)
.setSchemaCollection("_schema")
.setSeeds("")
.setCredentials("")
.setCredentials(null)
.setMinConnectionsPerHost(0)
.setConnectionsPerHost(100)
.setMaxWaitTime(120_000)
Expand Down Expand Up @@ -152,7 +152,8 @@ public void testSpecialCharacterCredential()
MongoClientConfig config = new MongoClientConfig()
.setCredentials("username:P@ss:w0rd@database");

MongoCredential credential = config.getCredentials().get(0);
assertTrue(config.getCredentials().isPresent());
MongoCredential credential = config.getCredentials().get();
MongoCredential expected = MongoCredential.createCredential("username", "database", "P@ss:w0rd".toCharArray());
assertEquals(credential, expected);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import com.facebook.presto.testing.QueryRunner;
import com.facebook.presto.tests.AbstractTestQueryFramework;
import com.google.common.collect.ImmutableMap;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoClient;
import io.airlift.tpch.TpchTable;
import org.bson.Document;
import org.testng.annotations.AfterClass;
Expand Down
Loading
Loading