Add support for reactive streams with mongodb. (#1720)
Add support for reactive streams with mongodb. (fixes 1480) 1. Replace mongodb driver with reactive mongodb driver. Change APIs accordingly. 2. Use webflux + reactor framework to execute mongodb queries in event loop model. 3. Add test to test MongoPluginExecutor class' method "testDatasource".
This commit is contained in:
parent
fc3197b78f
commit
2c2aa06e32
|
|
@ -44,11 +44,6 @@
|
||||||
<version>1.18.8</version>
|
<version>1.18.8</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.mongodb</groupId>
|
|
||||||
<artifactId>mongo-java-driver</artifactId>
|
|
||||||
<version>3.11.1</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- Test Dependencies -->
|
<!-- Test Dependencies -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,16 @@ import com.appsmith.external.pluginExceptions.AppsmithPluginException;
|
||||||
import com.appsmith.external.pluginExceptions.StaleConnectionException;
|
import com.appsmith.external.pluginExceptions.StaleConnectionException;
|
||||||
import com.appsmith.external.plugins.BasePlugin;
|
import com.appsmith.external.plugins.BasePlugin;
|
||||||
import com.appsmith.external.plugins.PluginExecutor;
|
import com.appsmith.external.plugins.PluginExecutor;
|
||||||
import com.mongodb.MongoClient;
|
|
||||||
import com.mongodb.MongoClientURI;
|
|
||||||
import com.mongodb.MongoCommandException;
|
import com.mongodb.MongoCommandException;
|
||||||
import com.mongodb.MongoTimeoutException;
|
import com.mongodb.MongoTimeoutException;
|
||||||
import com.mongodb.client.ClientSession;
|
import com.mongodb.MongoClientURI;
|
||||||
import com.mongodb.client.MongoDatabase;
|
import com.mongodb.reactivestreams.client.MongoClient;
|
||||||
|
import com.mongodb.reactivestreams.client.MongoClients;
|
||||||
|
import com.mongodb.reactivestreams.client.ClientSession;
|
||||||
|
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||||
|
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||||
|
import com.mongodb.reactivestreams.client.Success;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bson.Document;
|
import org.bson.Document;
|
||||||
import org.bson.conversions.Bson;
|
import org.bson.conversions.Bson;
|
||||||
|
|
@ -32,6 +36,7 @@ import org.pf4j.PluginWrapper;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
@ -81,7 +86,7 @@ public class MongoPlugin extends BasePlugin {
|
||||||
* https://docs.huihoo.com/mongodb/3.4/reference/command/index.html
|
* https://docs.huihoo.com/mongodb/3.4/reference/command/index.html
|
||||||
*
|
*
|
||||||
* @param mongoClient : This is the connection that is established to the data source. This connection is according
|
* @param mongoClient : This is the connection that is established to the data source. This connection is according
|
||||||
* to the parameters in Datasource Configuration
|
* to the parameters in Datasource Configuration
|
||||||
* @param datasourceConfiguration : These are the configurations which have been used to create a Datasource from a Plugin
|
* @param datasourceConfiguration : These are the configurations which have been used to create a Datasource from a Plugin
|
||||||
* @param actionConfiguration : These are the configurations which have been used to create an Action from a Datasource.
|
* @param actionConfiguration : These are the configurations which have been used to create an Action from a Datasource.
|
||||||
* @return Result data from executing the action's query.
|
* @return Result data from executing the action's query.
|
||||||
|
|
@ -96,71 +101,79 @@ public class MongoPlugin extends BasePlugin {
|
||||||
throw new StaleConnectionException();
|
throw new StaleConnectionException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MongoDatabase database = mongoClient.getDatabase(getDatabaseName(datasourceConfiguration));
|
||||||
|
Bson command = Document.parse(actionConfiguration.getBody());
|
||||||
|
Mono<Document> mongoOutputMono = Mono.from(database.runCommand(command));
|
||||||
ActionExecutionResult result = new ActionExecutionResult();
|
ActionExecutionResult result = new ActionExecutionResult();
|
||||||
|
|
||||||
MongoDatabase database = mongoClient.getDatabase(getDatabaseName(datasourceConfiguration));
|
return mongoOutputMono
|
||||||
|
.flatMap(mongoOutput -> {
|
||||||
|
try {
|
||||||
|
JSONObject outputJson = new JSONObject(mongoOutput.toJson());
|
||||||
|
//The output json contains the key "ok". This is the status of the command
|
||||||
|
BigInteger status = outputJson.getBigInteger("ok");
|
||||||
|
JSONArray headerArray = new JSONArray();
|
||||||
|
|
||||||
Bson command = Document.parse(actionConfiguration.getBody());
|
if (BigInteger.ONE.equals(status)) {
|
||||||
|
result.setIsExecutionSuccess(true);
|
||||||
|
|
||||||
try {
|
/**
|
||||||
Document mongoOutput = database.runCommand(command);
|
* For the `findAndModify` command, we don't get the count of modifications made. Instead,
|
||||||
|
* we either get the modified new value or the pre-modified old value (depending on the
|
||||||
|
* `new` field in the command. Let's return that value to the user.
|
||||||
|
*/
|
||||||
|
if (outputJson.has(VALUE_STR)) {
|
||||||
|
result.setBody(objectMapper.readTree(
|
||||||
|
cleanUp(new JSONObject().put(VALUE_STR, outputJson.get(VALUE_STR))).toString()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
JSONObject outputJson = new JSONObject(mongoOutput.toJson());
|
/**
|
||||||
|
* The json contains key "cursor" when find command was issued and there are 1 or more
|
||||||
|
* results. In case there are no results for find, this key is not present in the result json.
|
||||||
|
*/
|
||||||
|
if (outputJson.has("cursor")) {
|
||||||
|
JSONArray outputResult = (JSONArray) cleanUp(
|
||||||
|
outputJson.getJSONObject("cursor").getJSONArray("firstBatch"));
|
||||||
|
result.setBody(objectMapper.readTree(outputResult.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
//The output json contains the key "ok". This is the status of the command
|
/**
|
||||||
BigInteger status = outputJson.getBigInteger("ok");
|
* The json contains key "n" when insert/update command is issued. "n" for update
|
||||||
JSONArray headerArray = new JSONArray();
|
* signifies the no of documents selected for update. "n" in case of insert signifies the
|
||||||
|
* number of documents inserted.
|
||||||
|
*/
|
||||||
|
if (outputJson.has("n")) {
|
||||||
|
JSONObject body = new JSONObject().put("n", outputJson.getBigInteger("n"));
|
||||||
|
result.setBody(body);
|
||||||
|
headerArray.put(body);
|
||||||
|
}
|
||||||
|
|
||||||
if (BigInteger.ONE.equals(status)) {
|
/**
|
||||||
result.setIsExecutionSuccess(true);
|
* The json key contains key "nModified" in case of update command. This signifies the no of
|
||||||
|
* documents updated.
|
||||||
|
*/
|
||||||
|
if (outputJson.has(N_MODIFIED)) {
|
||||||
|
JSONObject body = new JSONObject().put(N_MODIFIED, outputJson.getBigInteger(N_MODIFIED));
|
||||||
|
result.setBody(body);
|
||||||
|
headerArray.put(body);
|
||||||
|
}
|
||||||
|
|
||||||
// For the `findAndModify` command, we don't get the count of modifications made. Instead, we either
|
/** TODO
|
||||||
// get the modified new value or the pre-modified old value (depending on the `new` field in the
|
* Go through all the possible fields that are returned in the output JSON and add all the fields
|
||||||
// command. Let's return that value to the user.
|
* that are important to the headerArray.
|
||||||
if (outputJson.has(VALUE_STR)) {
|
*/
|
||||||
result.setBody(objectMapper.readTree(
|
}
|
||||||
cleanUp(new JSONObject().put(VALUE_STR, outputJson.get(VALUE_STR))).toString()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
//The json contains key "cursor" when find command was issued and there are 1 or more results. In case
|
JSONObject statusJson = new JSONObject().put("ok", status);
|
||||||
//there are no results for find, this key is not present in the result json.
|
headerArray.put(statusJson);
|
||||||
if (outputJson.has("cursor")) {
|
result.setHeaders(objectMapper.readTree(headerArray.toString()));
|
||||||
JSONArray outputResult = (JSONArray) cleanUp(
|
} catch (Exception e) {
|
||||||
outputJson.getJSONObject("cursor").getJSONArray("firstBatch"));
|
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||||
result.setBody(objectMapper.readTree(outputResult.toString()));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//The json contains key "n" when insert/update command is issued. "n" for update signifies the no of
|
return Mono.just(result);
|
||||||
//documents selected for update. "n" in case of insert signifies the number of documents inserted.
|
});
|
||||||
if (outputJson.has("n")) {
|
|
||||||
JSONObject body = new JSONObject().put("n", outputJson.getBigInteger("n"));
|
|
||||||
result.setBody(body);
|
|
||||||
headerArray.put(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
//The json key contains key "nModified" in case of update command. This signifies the no of
|
|
||||||
//documents updated.
|
|
||||||
if (outputJson.has(N_MODIFIED)) {
|
|
||||||
JSONObject body = new JSONObject().put(N_MODIFIED, outputJson.getBigInteger(N_MODIFIED));
|
|
||||||
result.setBody(body);
|
|
||||||
headerArray.put(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** TODO
|
|
||||||
* Go through all the possible fields that are returned in the output JSON and add all the fields
|
|
||||||
* that are important to the headerArray.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
JSONObject statusJson = new JSONObject().put("ok", status);
|
|
||||||
headerArray.put(statusJson);
|
|
||||||
result.setHeaders(objectMapper.readTree(headerArray.toString()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Mono.just(result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDatabaseName(DatasourceConfiguration datasourceConfiguration) {
|
private String getDatabaseName(DatasourceConfiguration datasourceConfiguration) {
|
||||||
|
|
@ -178,18 +191,20 @@ public class MongoPlugin extends BasePlugin {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<MongoClient> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
|
public Mono<MongoClient> datasourceCreate(DatasourceConfiguration datasourceConfiguration) {
|
||||||
// TODO: ReadOnly seems to be not supported at the driver level. The recommendation is to connect with a
|
/**
|
||||||
// user that doesn't have write permissions on the database.
|
* TODO: ReadOnly seems to be not supported at the driver level. The recommendation is to connect with a
|
||||||
// Ref: https://api.mongodb.com/java/2.13/com/mongodb/DB.html#setReadOnly-java.lang.Boolean-
|
* user that doesn't have write permissions on the database.
|
||||||
|
* Ref: https://api.mongodb.com/java/2.13/com/mongodb/DB.html#setReadOnly-java.lang.Boolean-
|
||||||
|
*/
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return Mono.just(new MongoClient(buildClientURI(datasourceConfiguration)));
|
return Mono.just(MongoClients.create(buildClientURI(datasourceConfiguration)));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MongoClientURI buildClientURI(DatasourceConfiguration datasourceConfiguration) {
|
public static String buildClientURI(DatasourceConfiguration datasourceConfiguration) {
|
||||||
StringBuilder builder = new StringBuilder();
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
final Connection connection = datasourceConfiguration.getConnection();
|
final Connection connection = datasourceConfiguration.getConnection();
|
||||||
|
|
@ -252,9 +267,7 @@ public class MongoPlugin extends BasePlugin {
|
||||||
builder.deleteCharAt(builder.length() - 1);
|
builder.deleteCharAt(builder.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String uri = builder.toString();
|
return builder.toString();
|
||||||
log.info("MongoPlugin URI: `{}`.", uri);
|
|
||||||
return new MongoClientURI(uri);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -313,56 +326,22 @@ public class MongoPlugin extends BasePlugin {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<DatasourceTestResult> testDatasource(DatasourceConfiguration datasourceConfiguration) {
|
public Mono<DatasourceTestResult> testDatasource(DatasourceConfiguration datasourceConfiguration) {
|
||||||
final Connection.Type connectionType = datasourceConfiguration.getConnection().getType();
|
|
||||||
return datasourceCreate(datasourceConfiguration)
|
return datasourceCreate(datasourceConfiguration)
|
||||||
.map(mongoClient -> {
|
.flatMap(mongoClient -> {
|
||||||
ClientSession clientSession = null;
|
return Mono.zip(Mono.just(mongoClient),
|
||||||
|
Mono.from(mongoClient.getDatabase("admin").runCommand(new Document(
|
||||||
try {
|
"listDatabases", 1))));
|
||||||
// Not using try-with-resources here since we want to close the *session* before closing the
|
|
||||||
// MongoClient instance.
|
|
||||||
if (Connection.Type.REPLICA_SET.equals(connectionType)) {
|
|
||||||
// For REPLICA_SET connections, we check by creating a session, as this is faster.
|
|
||||||
clientSession = mongoClient.startSession();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// For DIRECT connections, we check by running a DB command, as it's the only reliable
|
|
||||||
// method of checking if the connection is usable.
|
|
||||||
mongoClient
|
|
||||||
.getDatabase("admin")
|
|
||||||
.runCommand(new Document("listDatabases", 1));
|
|
||||||
return new DatasourceTestResult();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (MongoTimeoutException e) {
|
|
||||||
log.warn("Timeout connecting to MongoDB from MongoPlugin.", e);
|
|
||||||
return new DatasourceTestResult("Timed out trying to connect to MongoDB host.");
|
|
||||||
|
|
||||||
} catch (MongoCommandException e) {
|
|
||||||
// The fact that we got a response saying "Unauthorized" means that the connection to the
|
|
||||||
// MongoDB instance is valid. It also means we don't have access to the admin database, but
|
|
||||||
// that's okay for our purposes here.
|
|
||||||
return "Unauthorized".equals(e.getErrorCodeName())
|
|
||||||
? new DatasourceTestResult()
|
|
||||||
: new DatasourceTestResult(e.getMessage());
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new DatasourceTestResult(e.getMessage());
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
if (clientSession != null) {
|
|
||||||
clientSession.close();
|
|
||||||
}
|
|
||||||
if (mongoClient != null) {
|
|
||||||
mongoClient.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return new DatasourceTestResult();
|
|
||||||
})
|
})
|
||||||
.onErrorResume(error -> Mono.just(new DatasourceTestResult(error.getMessage())));
|
.doOnSuccess(tuple -> {
|
||||||
|
MongoClient mongoClient = tuple.getT1();
|
||||||
|
|
||||||
|
if(mongoClient != null) {
|
||||||
|
mongoClient.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(Mono.just(new DatasourceTestResult()))
|
||||||
|
.onErrorResume(error -> {
|
||||||
|
return Mono.just(new DatasourceTestResult(error.getMessage()));});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -370,166 +349,181 @@ public class MongoPlugin extends BasePlugin {
|
||||||
final DatasourceStructure structure = new DatasourceStructure();
|
final DatasourceStructure structure = new DatasourceStructure();
|
||||||
List<DatasourceStructure.Table> tables = new ArrayList<>();
|
List<DatasourceStructure.Table> tables = new ArrayList<>();
|
||||||
structure.setTables(tables);
|
structure.setTables(tables);
|
||||||
|
|
||||||
final MongoDatabase database = mongoClient.getDatabase(getDatabaseName(datasourceConfiguration));
|
final MongoDatabase database = mongoClient.getDatabase(getDatabaseName(datasourceConfiguration));
|
||||||
|
|
||||||
for (Document collection : database.listCollections()) {
|
return Flux.from(database.listCollectionNames()).
|
||||||
final String collectionName = collection.getString("name");
|
flatMap(collectionName -> {
|
||||||
|
final ArrayList<DatasourceStructure.Column> columns = new ArrayList<>();
|
||||||
|
final ArrayList<DatasourceStructure.Template> templates = new ArrayList<>();
|
||||||
|
tables.add(new DatasourceStructure.Table(
|
||||||
|
DatasourceStructure.TableType.COLLECTION,
|
||||||
|
collectionName,
|
||||||
|
columns,
|
||||||
|
new ArrayList<>(),
|
||||||
|
templates
|
||||||
|
));
|
||||||
|
|
||||||
final ArrayList<DatasourceStructure.Column> columns = new ArrayList<>();
|
return Mono.zip(
|
||||||
final ArrayList<DatasourceStructure.Template> templates = new ArrayList<>();
|
Mono.just(columns),
|
||||||
tables.add(new DatasourceStructure.Table(
|
Mono.just(templates),
|
||||||
DatasourceStructure.TableType.COLLECTION,
|
Mono.just(collectionName),
|
||||||
collectionName,
|
Mono.from(database.getCollection(collectionName).find().limit(1).first())
|
||||||
columns,
|
);
|
||||||
new ArrayList<>(),
|
}).
|
||||||
templates
|
flatMap(tuple -> {
|
||||||
));
|
final ArrayList<DatasourceStructure.Column> columns = tuple.getT1();
|
||||||
|
final ArrayList<DatasourceStructure.Template> templates = tuple.getT2();
|
||||||
|
String collectionName = tuple.getT3();
|
||||||
|
Document document = tuple.getT4();
|
||||||
|
|
||||||
final Document first = database.getCollection(collectionName).find().limit(1).first();
|
generateTemplatesAndStructureForACollection(collectionName, document, columns, templates);
|
||||||
if (first == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String filterFieldName = null;
|
return Mono.just(structure);
|
||||||
String filterFieldValue = null;
|
}).
|
||||||
Map<String, String> sampleInsertValues = new LinkedHashMap<>();
|
collectList().
|
||||||
|
flatMap(documentList -> {
|
||||||
for (Map.Entry<String, Object> entry : first.entrySet()) {
|
return Mono.just(structure);
|
||||||
final String name = entry.getKey();
|
});
|
||||||
final Object value = entry.getValue();
|
|
||||||
String type;
|
|
||||||
|
|
||||||
if (value instanceof Integer) {
|
|
||||||
type = "Integer";
|
|
||||||
sampleInsertValues.put(name, "1");
|
|
||||||
} else if (value instanceof Long) {
|
|
||||||
type = "Long";
|
|
||||||
sampleInsertValues.put(name, "NumberLong(\"1\")");
|
|
||||||
} else if (value instanceof Double) {
|
|
||||||
type = "Double";
|
|
||||||
sampleInsertValues.put(name, "1");
|
|
||||||
} else if (value instanceof Decimal128) {
|
|
||||||
type = "BigDecimal";
|
|
||||||
sampleInsertValues.put(name, "NumberDecimal(\"1\")");
|
|
||||||
} else if (value instanceof String) {
|
|
||||||
type = "String";
|
|
||||||
sampleInsertValues.put(name, "\"new value\"");
|
|
||||||
if (filterFieldName == null || filterFieldName.compareTo(name) > 0) {
|
|
||||||
filterFieldName = name;
|
|
||||||
filterFieldValue = (String) value;
|
|
||||||
}
|
|
||||||
} else if (value instanceof ObjectId) {
|
|
||||||
type = "ObjectId";
|
|
||||||
if (!value.equals("_id")) {
|
|
||||||
sampleInsertValues.put(name, "ObjectId(\"a_valid_object_id_hex\")");
|
|
||||||
}
|
|
||||||
} else if (value instanceof Collection) {
|
|
||||||
type = "Array";
|
|
||||||
sampleInsertValues.put(name, "[1, 2, 3]");
|
|
||||||
} else if (value instanceof Date) {
|
|
||||||
type = "Date";
|
|
||||||
sampleInsertValues.put(name, "new Date(\"2019-07-01\")");
|
|
||||||
} else {
|
|
||||||
type = "Object";
|
|
||||||
sampleInsertValues.put(name, "{}");
|
|
||||||
}
|
|
||||||
|
|
||||||
columns.add(new DatasourceStructure.Column(name, type, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
columns.sort(Comparator.naturalOrder());
|
|
||||||
|
|
||||||
templates.add(
|
|
||||||
new DatasourceStructure.Template(
|
|
||||||
"Find",
|
|
||||||
"{\n" +
|
|
||||||
" \"find\": \"" + collectionName + "\",\n" +
|
|
||||||
(
|
|
||||||
filterFieldName == null ? "" :
|
|
||||||
" \"filter\": {\n" +
|
|
||||||
" \"" + filterFieldName + "\": \"" + filterFieldValue + "\"\n" +
|
|
||||||
" },\n"
|
|
||||||
) +
|
|
||||||
" \"sort\": {\n" +
|
|
||||||
" \"_id\": 1\n" +
|
|
||||||
" },\n" +
|
|
||||||
" \"limit\": 10\n" +
|
|
||||||
"}\n"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
templates.add(
|
|
||||||
new DatasourceStructure.Template(
|
|
||||||
"Find by ID",
|
|
||||||
"{\n" +
|
|
||||||
" \"find\": \"" + collectionName + "\",\n" +
|
|
||||||
" \"filter\": {\n" +
|
|
||||||
" \"_id\": ObjectId(\"id_to_query_with\")\n" +
|
|
||||||
" }\n" +
|
|
||||||
"}\n"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
sampleInsertValues.entrySet().stream()
|
|
||||||
.map(entry -> " \"" + entry.getKey() + "\": " + entry.getValue() + ",\n")
|
|
||||||
.collect(Collectors.joining(""));
|
|
||||||
templates.add(
|
|
||||||
new DatasourceStructure.Template(
|
|
||||||
"Insert",
|
|
||||||
"{\n" +
|
|
||||||
" \"insert\": \"" + collectionName + "\",\n" +
|
|
||||||
" \"documents\": [\n" +
|
|
||||||
" {\n" +
|
|
||||||
sampleInsertValues.entrySet().stream()
|
|
||||||
.map(entry -> " \"" + entry.getKey() + "\": " + entry.getValue() + ",\n")
|
|
||||||
.sorted()
|
|
||||||
.collect(Collectors.joining("")) +
|
|
||||||
" }\n" +
|
|
||||||
" ]\n" +
|
|
||||||
"}\n"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
templates.add(
|
|
||||||
new DatasourceStructure.Template(
|
|
||||||
"Update",
|
|
||||||
"{\n" +
|
|
||||||
" \"update\": \"" + collectionName + "\",\n" +
|
|
||||||
" \"updates\": [\n" +
|
|
||||||
" {\n" +
|
|
||||||
" \"q\": {\n" +
|
|
||||||
" \"_id\": ObjectId(\"id_of_document_to_update\")\n" +
|
|
||||||
" },\n" +
|
|
||||||
" \"u\": { \"$set\": { \"" + filterFieldName + "\": \"new value\" } }\n" +
|
|
||||||
" }\n" +
|
|
||||||
" ]\n" +
|
|
||||||
"}\n"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
templates.add(
|
|
||||||
new DatasourceStructure.Template(
|
|
||||||
"Delete",
|
|
||||||
"{\n" +
|
|
||||||
" \"delete\": \"" + collectionName + "\",\n" +
|
|
||||||
" \"deletes\": [\n" +
|
|
||||||
" {\n" +
|
|
||||||
" \"q\": {\n" +
|
|
||||||
" \"_id\": \"id_of_document_to_delete\"\n" +
|
|
||||||
" },\n" +
|
|
||||||
" \"limit\": 1\n" +
|
|
||||||
" }\n" +
|
|
||||||
" ]\n" +
|
|
||||||
"}\n"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
tables.sort(Comparator.comparing(DatasourceStructure.Table::getName));
|
|
||||||
return Mono.just(structure);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void generateTemplatesAndStructureForACollection(String collectionName,
|
||||||
|
Document document,
|
||||||
|
ArrayList<DatasourceStructure.Column> columns,
|
||||||
|
ArrayList<DatasourceStructure.Template> templates) {
|
||||||
|
String filterFieldName = null;
|
||||||
|
String filterFieldValue = null;
|
||||||
|
Map<String, String> sampleInsertValues = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, Object> entry : document.entrySet()) {
|
||||||
|
final String name = entry.getKey();
|
||||||
|
final Object value = entry.getValue();
|
||||||
|
String type;
|
||||||
|
|
||||||
|
if (value instanceof Integer) {
|
||||||
|
type = "Integer";
|
||||||
|
sampleInsertValues.put(name, "1");
|
||||||
|
} else if (value instanceof Long) {
|
||||||
|
type = "Long";
|
||||||
|
sampleInsertValues.put(name, "NumberLong(\"1\")");
|
||||||
|
} else if (value instanceof Double) {
|
||||||
|
type = "Double";
|
||||||
|
sampleInsertValues.put(name, "1");
|
||||||
|
} else if (value instanceof Decimal128) {
|
||||||
|
type = "BigDecimal";
|
||||||
|
sampleInsertValues.put(name, "NumberDecimal(\"1\")");
|
||||||
|
} else if (value instanceof String) {
|
||||||
|
type = "String";
|
||||||
|
sampleInsertValues.put(name, "\"new value\"");
|
||||||
|
if (filterFieldName == null || filterFieldName.compareTo(name) > 0) {
|
||||||
|
filterFieldName = name;
|
||||||
|
filterFieldValue = (String) value;
|
||||||
|
}
|
||||||
|
} else if (value instanceof ObjectId) {
|
||||||
|
type = "ObjectId";
|
||||||
|
if (!value.equals("_id")) {
|
||||||
|
sampleInsertValues.put(name, "ObjectId(\"a_valid_object_id_hex\")");
|
||||||
|
}
|
||||||
|
} else if (value instanceof Collection) {
|
||||||
|
type = "Array";
|
||||||
|
sampleInsertValues.put(name, "[1, 2, 3]");
|
||||||
|
} else if (value instanceof Date) {
|
||||||
|
type = "Date";
|
||||||
|
sampleInsertValues.put(name, "new Date(\"2019-07-01\")");
|
||||||
|
} else {
|
||||||
|
type = "Object";
|
||||||
|
sampleInsertValues.put(name, "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.add(new DatasourceStructure.Column(name, type, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.sort(Comparator.naturalOrder());
|
||||||
|
|
||||||
|
templates.add(
|
||||||
|
new DatasourceStructure.Template(
|
||||||
|
"Find",
|
||||||
|
"{\n" +
|
||||||
|
" \"find\": \"" + collectionName + "\",\n" +
|
||||||
|
(
|
||||||
|
filterFieldName == null ? "" :
|
||||||
|
" \"filter\": {\n" +
|
||||||
|
" \"" + filterFieldName + "\": \"" + filterFieldValue + "\"\n" +
|
||||||
|
" },\n"
|
||||||
|
) +
|
||||||
|
" \"sort\": {\n" +
|
||||||
|
" \"_id\": 1\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"limit\": 10\n" +
|
||||||
|
"}\n"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
templates.add(
|
||||||
|
new DatasourceStructure.Template(
|
||||||
|
"Find by ID",
|
||||||
|
"{\n" +
|
||||||
|
" \"find\": \"" + collectionName + "\",\n" +
|
||||||
|
" \"filter\": {\n" +
|
||||||
|
" \"_id\": ObjectId(\"id_to_query_with\")\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}\n"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
sampleInsertValues.entrySet().stream()
|
||||||
|
.map(entry -> " \"" + entry.getKey() + "\": " + entry.getValue() + ",\n")
|
||||||
|
.collect(Collectors.joining(""));
|
||||||
|
templates.add(
|
||||||
|
new DatasourceStructure.Template(
|
||||||
|
"Insert",
|
||||||
|
"{\n" +
|
||||||
|
" \"insert\": \"" + collectionName + "\",\n" +
|
||||||
|
" \"documents\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
sampleInsertValues.entrySet().stream()
|
||||||
|
.map(entry -> " \"" + entry.getKey() + "\": " + entry.getValue() + ",\n")
|
||||||
|
.sorted()
|
||||||
|
.collect(Collectors.joining("")) +
|
||||||
|
" }\n" +
|
||||||
|
" ]\n" +
|
||||||
|
"}\n"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
templates.add(
|
||||||
|
new DatasourceStructure.Template(
|
||||||
|
"Update",
|
||||||
|
"{\n" +
|
||||||
|
" \"update\": \"" + collectionName + "\",\n" +
|
||||||
|
" \"updates\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"q\": {\n" +
|
||||||
|
" \"_id\": ObjectId(\"id_of_document_to_update\")\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"u\": { \"$set\": { \"" + filterFieldName + "\": \"new value\" } }\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ]\n" +
|
||||||
|
"}\n"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
templates.add(
|
||||||
|
new DatasourceStructure.Template(
|
||||||
|
"Delete",
|
||||||
|
"{\n" +
|
||||||
|
" \"delete\": \"" + collectionName + "\",\n" +
|
||||||
|
" \"deletes\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"q\": {\n" +
|
||||||
|
" \"_id\": \"id_of_document_to_delete\"\n" +
|
||||||
|
" },\n" +
|
||||||
|
" \"limit\": 1\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ]\n" +
|
||||||
|
"}\n"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String urlEncode(String text) {
|
private static String urlEncode(String text) {
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,24 @@ import com.appsmith.external.models.Endpoint;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.mongodb.MongoClient;
|
|
||||||
import com.mongodb.client.MongoCollection;
|
import com.mongodb.reactivestreams.client.MongoClient;
|
||||||
|
import com.mongodb.reactivestreams.client.MongoClients;
|
||||||
|
import com.mongodb.reactivestreams.client.ClientSession;
|
||||||
|
import com.mongodb.reactivestreams.client.MongoCollection;
|
||||||
|
import com.mongodb.reactivestreams.client.MongoDatabase;
|
||||||
|
import com.mongodb.reactivestreams.client.Success;
|
||||||
|
import org.reactivestreams.Publisher;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.bson.Document;
|
import org.bson.Document;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.testcontainers.containers.GenericContainer;
|
import org.testcontainers.containers.GenericContainer;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
@ -25,14 +35,12 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for MongoPlugin
|
* Unit tests for MongoPlugin
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class MongoPluginTest {
|
public class MongoPluginTest {
|
||||||
|
|
||||||
MongoPlugin.MongoPluginExecutor pluginExecutor = new MongoPlugin.MongoPluginExecutor();
|
MongoPlugin.MongoPluginExecutor pluginExecutor = new MongoPlugin.MongoPluginExecutor();
|
||||||
|
|
@ -51,23 +59,30 @@ public class MongoPluginTest {
|
||||||
public static void setUp() {
|
public static void setUp() {
|
||||||
address = mongoContainer.getContainerIpAddress();
|
address = mongoContainer.getContainerIpAddress();
|
||||||
port = mongoContainer.getFirstMappedPort();
|
port = mongoContainer.getFirstMappedPort();
|
||||||
|
String uri = "mongodb://" + address + ":" + Integer.toString(port);
|
||||||
|
final MongoClient mongoClient = MongoClients.create(uri);
|
||||||
|
|
||||||
final MongoClient mongoClient = new MongoClient(address, port);
|
Flux.from(mongoClient.getDatabase("test").listCollectionNames()).collectList().
|
||||||
if (!mongoClient.getDatabase("test").listCollectionNames().iterator().hasNext()) {
|
flatMap(collectionNamesList -> {
|
||||||
final MongoCollection<Document> usersCollection = mongoClient.getDatabase("test").getCollection("users");
|
final MongoCollection<Document> usersCollection = mongoClient.getDatabase("test").getCollection(
|
||||||
usersCollection.insertMany(List.of(
|
"users");
|
||||||
new Document(Map.of(
|
if(collectionNamesList.size() == 0) {
|
||||||
"name", "Cierra Vega",
|
Mono.from(usersCollection.insertMany(List.of(
|
||||||
"gender", "F",
|
new Document(Map.of(
|
||||||
"age", 20,
|
"name", "Cierra Vega",
|
||||||
"luckyNumber", 987654321L,
|
"gender", "F",
|
||||||
"dob", LocalDate.of(2018, 12, 31),
|
"age", 20,
|
||||||
"netWorth", new BigDecimal("123456.789012")
|
"luckyNumber", 987654321L,
|
||||||
)),
|
"dob", LocalDate.of(2018, 12, 31),
|
||||||
new Document(Map.of("name", "Alden Cantrell", "gender", "M", "age", 30)),
|
"netWorth", new BigDecimal("123456.789012")
|
||||||
new Document(Map.of("name", "Kierra Gentry", "gender", "F", "age", 40))
|
)),
|
||||||
));
|
new Document(Map.of("name", "Alden Cantrell", "gender", "M", "age", 30)),
|
||||||
}
|
new Document(Map.of("name", "Kierra Gentry", "gender", "F", "age", 40))
|
||||||
|
))).block();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Mono.just(usersCollection);
|
||||||
|
}).block();
|
||||||
}
|
}
|
||||||
|
|
||||||
private DatasourceConfiguration createDatasourceConfiguration() {
|
private DatasourceConfiguration createDatasourceConfiguration() {
|
||||||
|
|
@ -103,6 +118,25 @@ public class MongoPluginTest {
|
||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Test "testDatasource" method in MongoPluginExecutor class.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testDatasourceFail() {
|
||||||
|
System.out.println(mongoContainer.getContainerIpAddress());
|
||||||
|
System.out.println(mongoContainer.getFirstMappedPort());
|
||||||
|
DatasourceConfiguration dsConfig = createDatasourceConfiguration();
|
||||||
|
dsConfig.getEndpoints().get(0).setHost("badHost");
|
||||||
|
System.out.println(dsConfig);
|
||||||
|
|
||||||
|
StepVerifier.create(pluginExecutor.testDatasource(dsConfig))
|
||||||
|
.assertNext(datasourceTestResult -> {
|
||||||
|
assertNotNull(datasourceTestResult);
|
||||||
|
assertFalse(datasourceTestResult.isSuccess());
|
||||||
|
})
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExecuteReadQuery() {
|
public void testExecuteReadQuery() {
|
||||||
DatasourceConfiguration dsConfig = createDatasourceConfiguration();
|
DatasourceConfiguration dsConfig = createDatasourceConfiguration();
|
||||||
|
|
@ -311,5 +345,4 @@ public class MongoPluginTest {
|
||||||
})
|
})
|
||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user