mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-17 20:50:44 +00:00
CE-781 Initial build of mongodb backend module
This commit is contained in:
1
pom.xml
1
pom.xml
@ -33,6 +33,7 @@
|
||||
<module>qqq-backend-module-api</module>
|
||||
<module>qqq-backend-module-filesystem</module>
|
||||
<module>qqq-backend-module-rdbms</module>
|
||||
<module>qqq-backend-module-mongodb</module>
|
||||
<module>qqq-language-support-javascript</module>
|
||||
<module>qqq-middleware-picocli</module>
|
||||
<module>qqq-middleware-javalin</module>
|
||||
|
120
qqq-backend-module-mongodb/pom.xml
Normal file
120
qqq-backend-module-mongodb/pom.xml
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ QQQ - Low-code Application Framework for Engineers.
|
||||
~ Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
~ 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
~ contact@kingsrook.com
|
||||
~ https://github.com/Kingsrook/
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU Affero General Public License as
|
||||
~ published by the Free Software Foundation, either version 3 of the
|
||||
~ License, or (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU Affero General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>qqq-backend-module-mongodb</artifactId>
|
||||
|
||||
<parent>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-parent-project</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<!-- props specifically to this module -->
|
||||
<!-- none at this time -->
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- other qqq modules deps -->
|
||||
<dependency>
|
||||
<groupId>com.kingsrook.qqq</groupId>
|
||||
<artifactId>qqq-backend-core</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 3rd party deps specifically for this module -->
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongodb-driver-sync</artifactId>
|
||||
<version>4.11.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j-impl</artifactId>
|
||||
<version>2.17.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mongodb</artifactId>
|
||||
<version>1.19.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Common deps for all qqq modules -->
|
||||
<dependency>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.4.3</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>${plugin.shade.phase}</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.actions.AbstractMongoDBAction;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.actions.MongoClientContainer;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.actions.MongoDBAggregateAction;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.actions.MongoDBCountAction;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.actions.MongoDBDeleteAction;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.actions.MongoDBInsertAction;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.actions.MongoDBQueryAction;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.actions.MongoDBTransaction;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.actions.MongoDBUpdateAction;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBTableBackendDetails;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** QQQ Backend module for working with MongoDB
|
||||
*******************************************************************************/
|
||||
public class MongoDBBackendModule implements QBackendModuleInterface
|
||||
{
|
||||
static
|
||||
{
|
||||
QBackendModuleDispatcher.registerBackendModule(new MongoDBBackendModule());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Method where a backend module must be able to provide its type (name).
|
||||
*******************************************************************************/
|
||||
public String getBackendType()
|
||||
{
|
||||
return ("mongodb");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Method to identify the class used for backend meta data for this module.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Class<? extends QBackendMetaData> getBackendMetaDataClass()
|
||||
{
|
||||
return (MongoDBBackendMetaData.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Method to identify the class used for table-backend details for this module.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Class<? extends QTableBackendDetails> getTableBackendDetailsClass()
|
||||
{
|
||||
return (MongoDBTableBackendDetails.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public CountInterface getCountInterface()
|
||||
{
|
||||
return (new MongoDBCountAction());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QueryInterface getQueryInterface()
|
||||
{
|
||||
return (new MongoDBQueryAction());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public InsertInterface getInsertInterface()
|
||||
{
|
||||
return (new MongoDBInsertAction());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public UpdateInterface getUpdateInterface()
|
||||
{
|
||||
return (new MongoDBUpdateAction());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public DeleteInterface getDeleteInterface()
|
||||
{
|
||||
return (new MongoDBDeleteAction());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public AggregateInterface getAggregateInterface()
|
||||
{
|
||||
return (new MongoDBAggregateAction());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QBackendTransaction openTransaction(AbstractTableActionInput input)
|
||||
{
|
||||
MongoDBBackendMetaData backend = (MongoDBBackendMetaData) input.getBackend();
|
||||
MongoClientContainer mongoClientContainer = new AbstractMongoDBAction().openClient(backend, null);
|
||||
return (new MongoDBTransaction(backend, mongoClientContainer.getMongoClient()));
|
||||
}
|
||||
}
|
@ -0,0 +1,541 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLockFilters;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBTableBackendDetails;
|
||||
import com.mongodb.ConnectionString;
|
||||
import com.mongodb.MongoClientSettings;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.client.MongoClient;
|
||||
import com.mongodb.client.MongoClients;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base class for all mongoDB module actions.
|
||||
*******************************************************************************/
|
||||
public class AbstractMongoDBAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AbstractMongoDBAction.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Open a MongoDB Client / session -- re-using the one in the input transaction
|
||||
** if it is present.
|
||||
*******************************************************************************/
|
||||
public MongoClientContainer openClient(MongoDBBackendMetaData backend, QBackendTransaction transaction)
|
||||
{
|
||||
if(transaction instanceof MongoDBTransaction mongoDBTransaction)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// re-use the connection from the transaction (indicating false in last parameter here) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (new MongoClientContainer(mongoDBTransaction.getMongoClient(), mongoDBTransaction.getClientSession(), false));
|
||||
}
|
||||
|
||||
ConnectionString connectionString = new ConnectionString("mongodb://" + backend.getHost() + ":" + backend.getPort() + "/");
|
||||
|
||||
MongoCredential credential = MongoCredential.createCredential(backend.getUsername(), backend.getAuthSourceDatabase(), backend.getPassword().toCharArray());
|
||||
|
||||
MongoClientSettings settings = MongoClientSettings.builder()
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// is this needed, what, for a cluster maybe? //
|
||||
////////////////////////////////////////////////
|
||||
// .applyToClusterSettings(builder -> builder.hosts(seeds))
|
||||
|
||||
.applyConnectionString(connectionString)
|
||||
.credential(credential)
|
||||
.build();
|
||||
|
||||
MongoClient mongoClient = MongoClients.create(settings);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// indicate that this connection was newly opened via the true param here //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
return (new MongoClientContainer(mongoClient, mongoClient.startSession(), true));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get the name to use for a field in the mongoDB, from the fieldMetaData.
|
||||
**
|
||||
** That is, field.backendName if set -- else, field.name
|
||||
*******************************************************************************/
|
||||
protected String getFieldBackendName(QFieldMetaData field)
|
||||
{
|
||||
if(field.getBackendName() != null)
|
||||
{
|
||||
return (field.getBackendName());
|
||||
}
|
||||
return (field.getName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get the name to use for a table in the mongoDB, from the table's backendDetails.
|
||||
**
|
||||
** else, the table's name.
|
||||
*******************************************************************************/
|
||||
protected String getBackendTableName(QTableMetaData table)
|
||||
{
|
||||
if(table.getBackendDetails() != null)
|
||||
{
|
||||
String backendTableName = ((MongoDBTableBackendDetails) table.getBackendDetails()).getTableName();
|
||||
if(StringUtils.hasContent(backendTableName))
|
||||
{
|
||||
return (backendTableName);
|
||||
}
|
||||
}
|
||||
return table.getName();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected int getPageSize()
|
||||
{
|
||||
return (1000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Convert a mongodb document to a QRecord.
|
||||
*******************************************************************************/
|
||||
protected QRecord documentToRecord(QTableMetaData table, Document document)
|
||||
{
|
||||
QRecord record = new QRecord();
|
||||
record.setTableName(table.getName());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// todo - this - or iterate over the values in the document?? //
|
||||
// seems like, maybe, this is an attribute in the table-backend-details? //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Map<String, Serializable> values = record.getValues();
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
String fieldBackendName = getFieldBackendName(field);
|
||||
Object value = document.get(fieldBackendName);
|
||||
String fieldName = field.getName();
|
||||
|
||||
setValue(values, fieldName, value);
|
||||
}
|
||||
return (record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Recursive helper method to put a value in a map - where mongodb documents
|
||||
** are recursively expanded, and types are mapped to QQQ expectations.
|
||||
*******************************************************************************/
|
||||
private void setValue(Map<String, Serializable> values, String fieldName, Object value)
|
||||
{
|
||||
if(value instanceof ObjectId objectId)
|
||||
{
|
||||
values.put(fieldName, objectId.toString());
|
||||
}
|
||||
else if(value instanceof java.util.Date date)
|
||||
{
|
||||
values.put(fieldName, date.toInstant());
|
||||
}
|
||||
else if(value instanceof Document document)
|
||||
{
|
||||
LinkedHashMap<String, Serializable> subValues = new LinkedHashMap<>();
|
||||
values.put(fieldName, subValues);
|
||||
|
||||
for(String subFieldName : document.keySet())
|
||||
{
|
||||
Object subValue = document.get(subFieldName);
|
||||
setValue(subValues, subFieldName, subValue);
|
||||
}
|
||||
}
|
||||
else if(value instanceof Serializable s)
|
||||
{
|
||||
values.put(fieldName, s);
|
||||
}
|
||||
else if(value != null)
|
||||
{
|
||||
values.put(fieldName, String.valueOf(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
values.put(fieldName, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Convert a QRecord to a mongodb document.
|
||||
*******************************************************************************/
|
||||
protected Document recordToDocument(QTableMetaData table, QRecord record)
|
||||
{
|
||||
Document document = new Document();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// todo - this - or iterate over the values in the record?? //
|
||||
// seems like, maybe, this is an attribute in the table-backend-details? //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
if(field.getName().equals(table.getPrimaryKeyField()) && record.getValue(field.getName()) == null)
|
||||
{
|
||||
////////////////////////////////////
|
||||
// let mongodb client generate id //
|
||||
////////////////////////////////////
|
||||
continue;
|
||||
}
|
||||
|
||||
String fieldBackendName = getFieldBackendName(field);
|
||||
document.append(fieldBackendName, record.getValue(field.getName()));
|
||||
}
|
||||
return (document);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Convert QQueryFilter to Bson search query document - including security
|
||||
** for the table if needed.
|
||||
*******************************************************************************/
|
||||
protected Bson makeSearchQueryDocument(QTableMetaData table, QQueryFilter filter) throws QException
|
||||
{
|
||||
Bson searchQueryWithoutSecurity = makeSearchQueryDocumentWithoutSecurity(table, filter);
|
||||
QQueryFilter securityFilter = makeSecurityQueryFilter(table);
|
||||
if(!securityFilter.hasAnyCriteria())
|
||||
{
|
||||
return (searchQueryWithoutSecurity);
|
||||
}
|
||||
|
||||
Bson searchQueryForSecurity = makeSearchQueryDocumentWithoutSecurity(table, securityFilter);
|
||||
return (Filters.and(searchQueryWithoutSecurity, searchQueryForSecurity));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Build a QQueryFilter to apply record-level security to the query.
|
||||
** Note, it may be empty, if there are no lock fields, or all are all-access.
|
||||
**
|
||||
** Originally copied from RDBMS module... should this be shared?
|
||||
** and/or, how big of a re-write did that get in the joins-enhancements branch...
|
||||
*******************************************************************************/
|
||||
private QQueryFilter makeSecurityQueryFilter(QTableMetaData table) throws QException
|
||||
{
|
||||
QQueryFilter securityFilter = new QQueryFilter();
|
||||
securityFilter.setBooleanOperator(QQueryFilter.BooleanOperator.AND);
|
||||
|
||||
for(RecordSecurityLock recordSecurityLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(table.getRecordSecurityLocks())))
|
||||
{
|
||||
addSubFilterForRecordSecurityLock(QContext.getQInstance(), QContext.getQSession(), table, securityFilter, recordSecurityLock, null, table.getName(), false);
|
||||
}
|
||||
|
||||
return (securityFilter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Helper for makeSecuritySearchQuery.
|
||||
**
|
||||
** Originally copied from RDBMS module... should this be shared?
|
||||
** and/or, how big of a re-write did that get in the joins-enhancements branch...
|
||||
*******************************************************************************/
|
||||
private static void addSubFilterForRecordSecurityLock(QInstance instance, QSession session, QTableMetaData table, QQueryFilter securityFilter, RecordSecurityLock recordSecurityLock, JoinsContext joinsContext, String tableNameOrAlias, boolean isOuter) throws QException
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// check if the key type has an all-access key, and if so, if it's set to true for the current user/session //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QSecurityKeyType securityKeyType = instance.getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
|
||||
{
|
||||
if(session.hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// if we have all-access on this key, then we don't need a criterion for it. //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// some differences from RDBMS here, due to not yet having joins support in mongo... //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// String fieldName = tableNameOrAlias + "." + recordSecurityLock.getFieldName();
|
||||
String fieldName = recordSecurityLock.getFieldName();
|
||||
if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()))
|
||||
{
|
||||
throw (new QException("Security locks in mongodb with joinNameChain is not yet supported"));
|
||||
// fieldName = recordSecurityLock.getFieldName();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// else - get the key values from the session and decide what kind of criterion to build //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter lockFilter = new QQueryFilter();
|
||||
List<QFilterCriteria> lockCriteria = new ArrayList<>();
|
||||
lockFilter.setCriteria(lockCriteria);
|
||||
|
||||
QFieldType type = QFieldType.INTEGER;
|
||||
try
|
||||
{
|
||||
if(joinsContext == null)
|
||||
{
|
||||
type = table.getField(fieldName).getType();
|
||||
}
|
||||
else
|
||||
{
|
||||
JoinsContext.FieldAndTableNameOrAlias fieldAndTableNameOrAlias = joinsContext.getFieldAndTableNameOrAlias(fieldName);
|
||||
type = fieldAndTableNameOrAlias.field().getType();
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.debug("Error getting field type... Trying Integer", e);
|
||||
}
|
||||
|
||||
List<Serializable> securityKeyValues = session.getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), type);
|
||||
if(CollectionUtils.nullSafeIsEmpty(securityKeyValues))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// handle user with no values -- they can only see null values, and only iff the lock's null-value behavior is ALLOW //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(RecordSecurityLock.NullValueBehavior.ALLOW.equals(recordSecurityLock.getNullValueBehavior()))
|
||||
{
|
||||
lockCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IS_BLANK));
|
||||
}
|
||||
else
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// else, if no user/session values, and null-value behavior is deny, then setup a FALSE condition, to allow no rows. //
|
||||
// todo - make some explicit contradiction here - maybe even avoid running the whole query - as you're not allowed ANY records //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
lockCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IN, Collections.emptyList()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// else, if user/session has some values, build an IN rule - //
|
||||
// noting that if the lock's null-value behavior is ALLOW, then we actually want IS_NULL_OR_IN, not just IN //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(RecordSecurityLock.NullValueBehavior.ALLOW.equals(recordSecurityLock.getNullValueBehavior()))
|
||||
{
|
||||
lockCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IS_NULL_OR_IN, securityKeyValues));
|
||||
}
|
||||
else
|
||||
{
|
||||
lockCriteria.add(new QFilterCriteria(fieldName, QCriteriaOperator.IN, securityKeyValues));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if this field is on the outer side of an outer join, then if we do a straight filter on it, then we're basically //
|
||||
// nullifying the outer join... so for an outer join use-case, OR the security field criteria with a primary-key IS NULL //
|
||||
// which will make missing rows from the join be found. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(isOuter)
|
||||
{
|
||||
lockFilter.setBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
||||
lockFilter.addCriteria(new QFilterCriteria(tableNameOrAlias + "." + table.getPrimaryKeyField(), QCriteriaOperator.IS_BLANK));
|
||||
}
|
||||
|
||||
securityFilter.addSubFilter(lockFilter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** w/o considering security, just map a QQueryFilter to a Bson searchQuery.
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:Indentation")
|
||||
private Bson makeSearchQueryDocumentWithoutSecurity(QTableMetaData table, QQueryFilter filter)
|
||||
{
|
||||
if(filter == null || !filter.hasAnyCriteria())
|
||||
{
|
||||
return (new Document());
|
||||
}
|
||||
|
||||
List<Bson> criteriaFilters = new ArrayList<>();
|
||||
|
||||
for(QFilterCriteria criteria : CollectionUtils.nonNullList(filter.getCriteria()))
|
||||
{
|
||||
List<Serializable> values = criteria.getValues() == null ? new ArrayList<>() : new ArrayList<>(criteria.getValues());
|
||||
QFieldMetaData field = table.getField(criteria.getFieldName());
|
||||
String fieldBackendName = getFieldBackendName(field);
|
||||
|
||||
if(field.getName().equals(table.getPrimaryKeyField()))
|
||||
{
|
||||
ListIterator<Serializable> iterator = values.listIterator();
|
||||
while(iterator.hasNext())
|
||||
{
|
||||
Serializable value = iterator.next();
|
||||
iterator.set(new ObjectId(String.valueOf(value)));
|
||||
}
|
||||
}
|
||||
|
||||
Serializable value0 = values.get(0);
|
||||
criteriaFilters.add(switch(criteria.getOperator())
|
||||
{
|
||||
case EQUALS -> Filters.eq(fieldBackendName, value0);
|
||||
case NOT_EQUALS -> Filters.ne(fieldBackendName, value0);
|
||||
case NOT_EQUALS_OR_IS_NULL -> Filters.or(
|
||||
Filters.eq(fieldBackendName, null),
|
||||
Filters.ne(fieldBackendName, value0)
|
||||
);
|
||||
case IN -> filterIn(fieldBackendName, values);
|
||||
case NOT_IN -> Filters.not(filterIn(fieldBackendName, values));
|
||||
case IS_NULL_OR_IN -> Filters.or(
|
||||
Filters.eq(fieldBackendName, null),
|
||||
filterIn(fieldBackendName, values)
|
||||
);
|
||||
case LIKE -> filterRegex(fieldBackendName, null, ValueUtils.getValueAsString(value0).replaceAll("%", ".*"), null);
|
||||
case NOT_LIKE -> Filters.not(filterRegex(fieldBackendName, null, ValueUtils.getValueAsString(value0).replaceAll("%", ".*"), null));
|
||||
case STARTS_WITH -> filterRegex(fieldBackendName, null, value0, ".*");
|
||||
case ENDS_WITH -> filterRegex(fieldBackendName, ".*", value0, null);
|
||||
case CONTAINS -> filterRegex(fieldBackendName, ".*", value0, ".*");
|
||||
case NOT_STARTS_WITH -> Filters.not(filterRegex(fieldBackendName, null, value0, ".*"));
|
||||
case NOT_ENDS_WITH -> Filters.not(filterRegex(fieldBackendName, ".*", value0, null));
|
||||
case NOT_CONTAINS -> Filters.not(filterRegex(fieldBackendName, ".*", value0, ".*"));
|
||||
case LESS_THAN -> Filters.lt(fieldBackendName, value0);
|
||||
case LESS_THAN_OR_EQUALS -> Filters.lte(fieldBackendName, value0);
|
||||
case GREATER_THAN -> Filters.gt(fieldBackendName, value0);
|
||||
case GREATER_THAN_OR_EQUALS -> Filters.gte(fieldBackendName, value0);
|
||||
case IS_BLANK -> filterIsBlank(fieldBackendName);
|
||||
case IS_NOT_BLANK -> Filters.not(filterIsBlank(fieldBackendName));
|
||||
case BETWEEN -> filterBetween(fieldBackendName, values);
|
||||
case NOT_BETWEEN -> Filters.not(filterBetween(fieldBackendName, values));
|
||||
});
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// recursively process sub-filters //
|
||||
/////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(filter.getSubFilters()))
|
||||
{
|
||||
for(QQueryFilter subFilter : filter.getSubFilters())
|
||||
{
|
||||
criteriaFilters.add(makeSearchQueryDocumentWithoutSecurity(table, subFilter));
|
||||
}
|
||||
}
|
||||
|
||||
Bson bson = QQueryFilter.BooleanOperator.AND.equals(filter.getBooleanOperator()) ? Filters.and(criteriaFilters) : Filters.or(criteriaFilters);
|
||||
return bson;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** build a bson filter doing a regex (e.g., for LIKE, STARTS_WITH, etc)
|
||||
*******************************************************************************/
|
||||
private Bson filterRegex(String fieldBackendName, String prefix, Serializable mainRegex, String suffix)
|
||||
{
|
||||
if(prefix == null)
|
||||
{
|
||||
prefix = "";
|
||||
}
|
||||
|
||||
if(suffix == null)
|
||||
{
|
||||
suffix = "";
|
||||
}
|
||||
|
||||
String fullRegex = prefix + Pattern.quote(ValueUtils.getValueAsString(mainRegex) + suffix);
|
||||
return (Filters.regex(fieldBackendName, Pattern.compile(fullRegex)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** build a bson filter doing IN
|
||||
*******************************************************************************/
|
||||
private static Bson filterIn(String fieldBackendName, List<Serializable> values)
|
||||
{
|
||||
return Filters.in(fieldBackendName, values);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** build a bson filter doing BETWEEN
|
||||
*******************************************************************************/
|
||||
private static Bson filterBetween(String fieldBackendName, List<Serializable> values)
|
||||
{
|
||||
return Filters.and(
|
||||
Filters.gte(fieldBackendName, values.get(0)),
|
||||
Filters.lte(fieldBackendName, values.get(1))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** build a bson filter doing BLANK (null or == "")
|
||||
*******************************************************************************/
|
||||
private static Bson filterIsBlank(String fieldBackendName)
|
||||
{
|
||||
return Filters.or(
|
||||
Filters.eq(fieldBackendName, null),
|
||||
Filters.eq(fieldBackendName, "")
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||
|
||||
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoClient;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Wrapper around a MongoClient, ClientSession, and a boolean to help signal
|
||||
** where it was opened (e.g., so you know if you need to close it yourself, or
|
||||
** if it came from someone else (e.g., via an input transaction)).
|
||||
*******************************************************************************/
|
||||
public class MongoClientContainer
|
||||
{
|
||||
private MongoClient mongoClient;
|
||||
private ClientSession mongoSession;
|
||||
private boolean needToClose;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MongoClientContainer(MongoClient mongoClient, ClientSession mongoSession, boolean needToClose)
|
||||
{
|
||||
this.mongoClient = mongoClient;
|
||||
this.mongoSession = mongoSession;
|
||||
this.needToClose = needToClose;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for mongoClient
|
||||
*******************************************************************************/
|
||||
public MongoClient getMongoClient()
|
||||
{
|
||||
return (this.mongoClient);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for mongoClient
|
||||
*******************************************************************************/
|
||||
public void setMongoClient(MongoClient mongoClient)
|
||||
{
|
||||
this.mongoClient = mongoClient;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for mongoClient
|
||||
*******************************************************************************/
|
||||
public MongoClientContainer withMongoClient(MongoClient mongoClient)
|
||||
{
|
||||
this.mongoClient = mongoClient;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for mongoSession
|
||||
*******************************************************************************/
|
||||
public ClientSession getMongoSession()
|
||||
{
|
||||
return (this.mongoSession);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for mongoSession
|
||||
*******************************************************************************/
|
||||
public void setMongoSession(ClientSession mongoSession)
|
||||
{
|
||||
this.mongoSession = mongoSession;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for mongoSession
|
||||
*******************************************************************************/
|
||||
public MongoClientContainer withMongoSession(ClientSession mongoSession)
|
||||
{
|
||||
this.mongoSession = mongoSession;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for needToClose
|
||||
*******************************************************************************/
|
||||
public boolean getNeedToClose()
|
||||
{
|
||||
return (this.needToClose);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for needToClose
|
||||
*******************************************************************************/
|
||||
public void setNeedToClose(boolean needToClose)
|
||||
{
|
||||
this.needToClose = needToClose;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for needToClose
|
||||
*******************************************************************************/
|
||||
public MongoClientContainer withNeedToClose(boolean needToClose)
|
||||
{
|
||||
this.needToClose = needToClose;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void closeIfNeeded()
|
||||
{
|
||||
if(needToClose)
|
||||
{
|
||||
mongoSession.close();
|
||||
mongoClient.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,251 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.Aggregate;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateResult;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.GroupBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByAggregate;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.QFilterOrderByGroupBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.MongoDBBackendModule;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||
import com.mongodb.client.AggregateIterable;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.Accumulators;
|
||||
import com.mongodb.client.model.Aggregates;
|
||||
import com.mongodb.client.model.BsonField;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MongoDBAggregateAction extends AbstractMongoDBAction implements AggregateInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MongoDBBackendModule.class);
|
||||
|
||||
// todo? private ActionTimeoutHelper actionTimeoutHelper;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:indentation")
|
||||
public AggregateOutput execute(AggregateInput aggregateInput) throws QException
|
||||
{
|
||||
MongoClientContainer mongoClientContainer = null;
|
||||
|
||||
try
|
||||
{
|
||||
AggregateOutput aggregateOutput = new AggregateOutput();
|
||||
QTableMetaData table = aggregateInput.getTable();
|
||||
String backendTableName = getBackendTableName(table);
|
||||
MongoDBBackendMetaData backend = (MongoDBBackendMetaData) aggregateInput.getBackend();
|
||||
|
||||
mongoClientContainer = openClient(backend, null); // todo - aggregate input has no transaction!?
|
||||
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
||||
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
||||
|
||||
QQueryFilter filter = aggregateInput.getFilter();
|
||||
Bson searchQuery = makeSearchQueryDocument(table, filter);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// we have to submit a list of BSON objects to the aggregate function. //
|
||||
// the first one is the search query //
|
||||
// second is the group-by stuff, which we'll explain as we build it //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
List<Bson> bsonList = new ArrayList<>();
|
||||
bsonList.add(Aggregates.match(searchQuery));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// if there are group-by fields, then we need to build a document with those fields //
|
||||
// not sure what the whole name, $name is, but, go go mongo //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
Document groupValueDocument = new Document();
|
||||
if(CollectionUtils.nullSafeHasContents(aggregateInput.getGroupBys()))
|
||||
{
|
||||
for(GroupBy groupBy : aggregateInput.getGroupBys())
|
||||
{
|
||||
String name = getFieldBackendName(table.getField(groupBy.getFieldName()));
|
||||
groupValueDocument.append(name, "$" + name);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// next build a list of accumulator fields - for aggregate values //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
List<BsonField> bsonFields = new ArrayList<>();
|
||||
for(Aggregate aggregate : aggregateInput.getAggregates())
|
||||
{
|
||||
String fieldName = aggregate.getFieldName() + "_" + aggregate.getOperator().toString().toLowerCase();
|
||||
String expression = "$" + getFieldBackendName(table.getField(aggregate.getFieldName()));
|
||||
|
||||
bsonFields.add(switch(aggregate.getOperator())
|
||||
{
|
||||
case COUNT -> Accumulators.sum(fieldName, 1); // count... do a sum of 1's
|
||||
case COUNT_DISTINCT -> throw new QException("Count Distinct is not supported for MongoDB tables at this time.");
|
||||
case SUM -> Accumulators.sum(fieldName, expression);
|
||||
case MIN -> Accumulators.min(fieldName, expression);
|
||||
case MAX -> Accumulators.max(fieldName, expression);
|
||||
case AVG -> Accumulators.avg(fieldName, expression);
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// add the group-by fields and the aggregates in the group stage of the pipeline //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
bsonList.add(Aggregates.group(groupValueDocument, bsonFields));
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// if there are any order-bys, add them too //
|
||||
//////////////////////////////////////////////
|
||||
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getOrderBys()))
|
||||
{
|
||||
Document sortValue = new Document();
|
||||
for(QFilterOrderBy orderBy : filter.getOrderBys())
|
||||
{
|
||||
String fieldName;
|
||||
if(orderBy instanceof QFilterOrderByAggregate orderByAggregate)
|
||||
{
|
||||
Aggregate aggregate = orderByAggregate.getAggregate();
|
||||
fieldName = aggregate.getFieldName() + "_" + aggregate.getOperator().toString().toLowerCase();
|
||||
}
|
||||
else if(orderBy instanceof QFilterOrderByGroupBy orderByGroupBy)
|
||||
{
|
||||
fieldName = "_id." + getFieldBackendName(table.getField(orderByGroupBy.getGroupBy().getFieldName()));
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////
|
||||
// does this happen? should it be "_id." if so? //
|
||||
///////////////////////////////////////////////////
|
||||
fieldName = getFieldBackendName(table.getField(orderBy.getFieldName()));
|
||||
}
|
||||
|
||||
sortValue.append(fieldName, orderBy.getIsAscending() ? 1 : -1);
|
||||
}
|
||||
|
||||
bsonList.add(new Document("$sort", sortValue));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// todo - system property to control (like print-sql) //
|
||||
////////////////////////////////////////////////////////
|
||||
// LOG.debug(bsonList.toString());
|
||||
|
||||
///////////////////////////
|
||||
// execute the aggregate //
|
||||
///////////////////////////
|
||||
AggregateIterable<Document> aggregates = collection.aggregate(mongoClientContainer.getMongoSession(), bsonList);
|
||||
|
||||
List<AggregateResult> results = new ArrayList<>();
|
||||
aggregateOutput.setResults(results);
|
||||
|
||||
/////////////////////
|
||||
// process results //
|
||||
/////////////////////
|
||||
for(Document document : aggregates)
|
||||
{
|
||||
AggregateResult result = new AggregateResult();
|
||||
results.add(result);
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// get group by values (if there are any) out of the document //
|
||||
////////////////////////////////////////////////////////////////
|
||||
for(GroupBy groupBy : CollectionUtils.nonNullList(aggregateInput.getGroupBys()))
|
||||
{
|
||||
Document idDocument = (Document) document.get("_id");
|
||||
Object value = idDocument.get(groupBy.getFieldName());
|
||||
result.withGroupByValue(groupBy, ValueUtils.getValueAsFieldType(groupBy.getType(), value));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// get aggregate values out of document //
|
||||
//////////////////////////////////////////
|
||||
for(Aggregate aggregate : aggregateInput.getAggregates())
|
||||
{
|
||||
QFieldMetaData field = table.getField(aggregate.getFieldName());
|
||||
QFieldType fieldType = aggregate.getFieldType();
|
||||
if(fieldType == null)
|
||||
{
|
||||
fieldType = field.getType();
|
||||
}
|
||||
if(fieldType.equals(QFieldType.INTEGER) && (aggregate.getOperator().equals(AggregateOperator.AVG)))
|
||||
{
|
||||
fieldType = QFieldType.DECIMAL;
|
||||
}
|
||||
|
||||
Object value = document.get(aggregate.getFieldName() + "_" + aggregate.getOperator().toString().toLowerCase());
|
||||
result.withAggregateValue(aggregate, ValueUtils.getValueAsFieldType(fieldType, value));
|
||||
}
|
||||
}
|
||||
|
||||
return (aggregateOutput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
/*
|
||||
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
||||
{
|
||||
setCountStatFirstResultTime();
|
||||
throw (new QUserFacingException("Aggregate timed out."));
|
||||
}
|
||||
|
||||
if(isCancelled)
|
||||
{
|
||||
throw (new QUserFacingException("Aggregate was cancelled."));
|
||||
}
|
||||
*/
|
||||
|
||||
LOG.warn("Error executing aggregate", e);
|
||||
throw new QException("Error executing aggregate", e);
|
||||
}
|
||||
finally
|
||||
|
||||
{
|
||||
if(mongoClientContainer != null)
|
||||
{
|
||||
mongoClientContainer.closeIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.MongoDBBackendModule;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||
import com.mongodb.client.AggregateIterable;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.Accumulators;
|
||||
import com.mongodb.client.model.Aggregates;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MongoDBCountAction extends AbstractMongoDBAction implements CountInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MongoDBBackendModule.class);
|
||||
|
||||
// todo? private ActionTimeoutHelper actionTimeoutHelper;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CountOutput execute(CountInput countInput) throws QException
|
||||
{
|
||||
MongoClientContainer mongoClientContainer = null;
|
||||
|
||||
try
|
||||
{
|
||||
CountOutput countOutput = new CountOutput();
|
||||
QTableMetaData table = countInput.getTable();
|
||||
String backendTableName = getBackendTableName(table);
|
||||
MongoDBBackendMetaData backend = (MongoDBBackendMetaData) countInput.getBackend();
|
||||
|
||||
mongoClientContainer = openClient(backend, null); // todo - count input has no transaction!?
|
||||
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
||||
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
||||
|
||||
QQueryFilter filter = countInput.getFilter();
|
||||
Bson searchQuery = makeSearchQueryDocument(table, filter);
|
||||
|
||||
List<Bson> bsonList = List.of(
|
||||
Aggregates.match(searchQuery),
|
||||
Aggregates.group("_id", Accumulators.sum("count", 1)));
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// todo - system property to control (like print-sql) //
|
||||
////////////////////////////////////////////////////////
|
||||
// LOG.debug(bsonList.toString());
|
||||
|
||||
AggregateIterable<Document> aggregate = collection.aggregate(mongoClientContainer.getMongoSession(), bsonList);
|
||||
|
||||
Document document = aggregate.first();
|
||||
countOutput.setCount(document == null ? 0 : document.get("count", Integer.class));
|
||||
|
||||
return (countOutput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
/*
|
||||
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
||||
{
|
||||
setCountStatFirstResultTime();
|
||||
throw (new QUserFacingException("Count timed out."));
|
||||
}
|
||||
|
||||
if(isCancelled)
|
||||
{
|
||||
throw (new QUserFacingException("Count was cancelled."));
|
||||
}
|
||||
*/
|
||||
|
||||
LOG.warn("Error executing count", e);
|
||||
throw new QException("Error executing count", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(mongoClientContainer != null)
|
||||
{
|
||||
mongoClientContainer.closeIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.MongoDBBackendModule;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import com.mongodb.client.result.DeleteResult;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MongoDBDeleteAction extends AbstractMongoDBAction implements DeleteInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MongoDBBackendModule.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean supportsQueryFilterInput()
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public DeleteOutput execute(DeleteInput deleteInput) throws QException
|
||||
{
|
||||
MongoClientContainer mongoClientContainer = null;
|
||||
|
||||
try
|
||||
{
|
||||
DeleteOutput deleteOutput = new DeleteOutput();
|
||||
QTableMetaData table = deleteInput.getTable();
|
||||
String backendTableName = getBackendTableName(table);
|
||||
MongoDBBackendMetaData backend = (MongoDBBackendMetaData) deleteInput.getBackend();
|
||||
|
||||
mongoClientContainer = openClient(backend, deleteInput.getTransaction());
|
||||
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
||||
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
||||
|
||||
QQueryFilter queryFilter = deleteInput.getQueryFilter();
|
||||
Bson searchQuery;
|
||||
if(CollectionUtils.nullSafeHasContents(deleteInput.getPrimaryKeys()))
|
||||
{
|
||||
searchQuery = Filters.in("_id", deleteInput.getPrimaryKeys().stream().map(id -> new ObjectId(ValueUtils.getValueAsString(id))).toList());
|
||||
}
|
||||
else if(queryFilter != null && queryFilter.hasAnyCriteria())
|
||||
{
|
||||
QQueryFilter filter = queryFilter;
|
||||
searchQuery = makeSearchQueryDocument(table, filter);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("Missing both primary keys and a search filter in delete request - exiting with noop", logPair("tableName", table.getName()));
|
||||
return (deleteOutput);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// todo - system property to control (like print-sql) //
|
||||
////////////////////////////////////////////////////////
|
||||
// LOG.debug(searchQuery);
|
||||
|
||||
DeleteResult deleteResult = collection.deleteMany(mongoClientContainer.getMongoSession(), searchQuery);
|
||||
deleteOutput.setDeletedRecordCount((int) deleteResult.getDeletedCount());
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// todo any way to get records with errors or warnings for deleteOutput //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
return (deleteOutput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error executing delete", e);
|
||||
throw new QException("Error executing delete", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(mongoClientContainer != null)
|
||||
{
|
||||
mongoClientContainer.closeIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,266 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.InsertInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.result.InsertManyResult;
|
||||
import org.bson.BsonValue;
|
||||
import org.bson.Document;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MongoDBInsertAction extends AbstractMongoDBAction implements InsertInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MongoDBInsertAction.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public InsertOutput execute(InsertInput insertInput) throws QException
|
||||
{
|
||||
MongoClientContainer mongoClientContainer = null;
|
||||
InsertOutput rs = new InsertOutput();
|
||||
List<QRecord> outputRecords = new ArrayList<>();
|
||||
rs.setRecords(outputRecords);
|
||||
|
||||
try
|
||||
{
|
||||
QTableMetaData table = insertInput.getTable();
|
||||
String backendTableName = getBackendTableName(table);
|
||||
MongoDBBackendMetaData backend = (MongoDBBackendMetaData) insertInput.getBackend();
|
||||
|
||||
mongoClientContainer = openClient(backend, insertInput.getTransaction());
|
||||
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
||||
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
||||
|
||||
//////////////////////////
|
||||
// todo - transaction?! //
|
||||
//////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// page over input record list (assuming some size of batch is too big?) //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
for(List<QRecord> page : CollectionUtils.getPages(insertInput.getRecords(), getPageSize()))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// build list of documents from records w/o errors in this page //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
List<Document> documentList = new ArrayList<>();
|
||||
for(QRecord record : page)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
documentList.add(recordToDocument(table, record));
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// skip pages that were all errors //
|
||||
/////////////////////////////////////
|
||||
if(documentList.isEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// todo - system property to control (like print-sql) //
|
||||
////////////////////////////////////////////////////////
|
||||
// LOG.debug(documentList);
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// actually do the insert //
|
||||
// todo - how are errors returned by mongo?? //
|
||||
///////////////////////////////////////////////
|
||||
InsertManyResult insertManyResult = collection.insertMany(mongoClientContainer.getMongoSession(), documentList);
|
||||
|
||||
/////////////////////////////////
|
||||
// put ids on inserted records //
|
||||
/////////////////////////////////
|
||||
int index = 0;
|
||||
for(QRecord record : page)
|
||||
{
|
||||
QRecord outputRecord = new QRecord(record);
|
||||
rs.addRecord(outputRecord);
|
||||
|
||||
if(CollectionUtils.nullSafeIsEmpty(record.getErrors()))
|
||||
{
|
||||
BsonValue insertedId = insertManyResult.getInsertedIds().get(index++);
|
||||
String idString = insertedId.asObjectId().getValue().toString();
|
||||
outputRecord.setValue(table.getPrimaryKeyField(), idString);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QException("Error executing insert: " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(mongoClientContainer != null)
|
||||
{
|
||||
mongoClientContainer.closeIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
return (rs);
|
||||
|
||||
/*
|
||||
try
|
||||
{
|
||||
List<QFieldMetaData> insertableFields = table.getFields().values().stream()
|
||||
.filter(field -> !field.getName().equals("id")) // todo - intent here is to avoid non-insertable fields.
|
||||
.toList();
|
||||
|
||||
String columns = insertableFields.stream()
|
||||
.map(f -> "`" + getColumnName(f) + "`")
|
||||
.collect(Collectors.joining(", "));
|
||||
String questionMarks = insertableFields.stream()
|
||||
.map(x -> "?")
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
List<QRecord> outputRecords = new ArrayList<>();
|
||||
rs.setRecords(outputRecords);
|
||||
|
||||
Connection connection;
|
||||
boolean needToCloseConnection = false;
|
||||
if(insertInput.getTransaction() != null && insertInput.getTransaction() instanceof RDBMSTransaction rdbmsTransaction)
|
||||
{
|
||||
connection = rdbmsTransaction.getConnection();
|
||||
}
|
||||
else
|
||||
{
|
||||
connection = getConnection(insertInput);
|
||||
needToCloseConnection = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
for(List<QRecord> page : CollectionUtils.getPages(insertInput.getRecords(), QueryManager.PAGE_SIZE))
|
||||
{
|
||||
String tableName = escapeIdentifier(getTableName(table));
|
||||
StringBuilder sql = new StringBuilder("INSERT INTO ").append(tableName).append("(").append(columns).append(") VALUES");
|
||||
List<Object> params = new ArrayList<>();
|
||||
int recordIndex = 0;
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// for each record in the page: //
|
||||
// - if it has errors, skip it //
|
||||
// - else add a "(?,?,...,?)," clause to the INSERT //
|
||||
// - then add all fields into the params list //
|
||||
//////////////////////////////////////////////////////
|
||||
for(QRecord record : page)
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(recordIndex++ > 0)
|
||||
{
|
||||
sql.append(",");
|
||||
}
|
||||
sql.append("(").append(questionMarks).append(")");
|
||||
|
||||
for(QFieldMetaData field : insertableFields)
|
||||
{
|
||||
Serializable value = record.getValue(field.getName());
|
||||
value = scrubValue(field, value);
|
||||
params.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if all records had errors, copy them to the output, and continue w/o running query //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(recordIndex == 0)
|
||||
{
|
||||
for(QRecord record : page)
|
||||
{
|
||||
QRecord outputRecord = new QRecord(record);
|
||||
outputRecords.add(outputRecord);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Long mark = System.currentTimeMillis();
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// execute the insert, then foreach record in the input, //
|
||||
// add it to the output, and set its generated id too. //
|
||||
///////////////////////////////////////////////////////////
|
||||
// todo sql customization - can edit sql and/or param list
|
||||
// todo - non-serial-id style tables
|
||||
// todo - other generated values, e.g., createDate... maybe need to re-select?
|
||||
List<Integer> idList = QueryManager.executeInsertForGeneratedIds(connection, sql.toString(), params);
|
||||
int index = 0;
|
||||
for(QRecord record : page)
|
||||
{
|
||||
QRecord outputRecord = new QRecord(record);
|
||||
outputRecords.add(outputRecord);
|
||||
|
||||
if(CollectionUtils.nullSafeIsEmpty(record.getErrors()))
|
||||
{
|
||||
Integer id = idList.get(index++);
|
||||
outputRecord.setValue(table.getPrimaryKeyField(), id);
|
||||
}
|
||||
}
|
||||
|
||||
logSQL(sql, params, mark);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(needToCloseConnection)
|
||||
{
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QException("Error executing insert: " + e.getMessage(), e);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.MongoDBBackendModule;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||
import com.mongodb.client.FindIterable;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MongoDBQueryAction extends AbstractMongoDBAction implements QueryInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MongoDBBackendModule.class);
|
||||
|
||||
// todo? private ActionTimeoutHelper actionTimeoutHelper;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QueryOutput execute(QueryInput queryInput) throws QException
|
||||
{
|
||||
MongoClientContainer mongoClientContainer = null;
|
||||
|
||||
try
|
||||
{
|
||||
QueryOutput queryOutput = new QueryOutput(queryInput);
|
||||
QTableMetaData table = queryInput.getTable();
|
||||
String backendTableName = getBackendTableName(table);
|
||||
MongoDBBackendMetaData backend = (MongoDBBackendMetaData) queryInput.getBackend();
|
||||
|
||||
mongoClientContainer = openClient(backend, queryInput.getTransaction());
|
||||
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
||||
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
||||
|
||||
/////////////////////////
|
||||
// set up filter/query //
|
||||
/////////////////////////
|
||||
QQueryFilter filter = queryInput.getFilter();
|
||||
Bson searchQuery = makeSearchQueryDocument(table, filter);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// todo - system property to control (like print-sql) //
|
||||
////////////////////////////////////////////////////////
|
||||
// LOG.debug(searchQuery);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// create cursor - further adjustments to it still follow //
|
||||
////////////////////////////////////////////////////////////
|
||||
FindIterable<Document> cursor = collection.find(mongoClientContainer.getMongoSession(), searchQuery);
|
||||
|
||||
///////////////////////////////////
|
||||
// add a sort operator if needed //
|
||||
///////////////////////////////////
|
||||
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getOrderBys()))
|
||||
{
|
||||
Document sortDocument = new Document();
|
||||
for(QFilterOrderBy orderBy : filter.getOrderBys())
|
||||
{
|
||||
String fieldBackendName = getFieldBackendName(table.getField(orderBy.getFieldName()));
|
||||
sortDocument.put(fieldBackendName, orderBy.getIsAscending() ? 1 : -1);
|
||||
}
|
||||
cursor.sort(sortDocument);
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
// apply skip & limit //
|
||||
////////////////////////
|
||||
if(filter != null)
|
||||
{
|
||||
if(filter.getSkip() != null)
|
||||
{
|
||||
cursor.skip(filter.getSkip());
|
||||
}
|
||||
|
||||
if(filter.getLimit() != null)
|
||||
{
|
||||
cursor.limit(filter.getLimit());
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// iterate over results, building records //
|
||||
////////////////////////////////////////////
|
||||
for(Document document : cursor)
|
||||
{
|
||||
QRecord record = documentToRecord(table, document);
|
||||
queryOutput.addRecord(record);
|
||||
|
||||
if(queryInput.getAsyncJobCallback().wasCancelRequested())
|
||||
{
|
||||
LOG.info("Breaking query job, as requested.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (queryOutput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
/*
|
||||
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
||||
{
|
||||
setQueryStatFirstResultTime();
|
||||
throw (new QUserFacingException("Query timed out."));
|
||||
}
|
||||
|
||||
if(isCancelled)
|
||||
{
|
||||
throw (new QUserFacingException("Query was cancelled."));
|
||||
}
|
||||
*/
|
||||
|
||||
LOG.warn("Error executing query", e);
|
||||
throw new QException("Error executing query", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(mongoClientContainer != null)
|
||||
{
|
||||
mongoClientContainer.closeIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||
import com.mongodb.client.ClientSession;
|
||||
import com.mongodb.client.MongoClient;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** MongoDB implementation of backend transaction.
|
||||
**
|
||||
** Stores a mongoClient and clientSession.
|
||||
**
|
||||
** Also keeps track of if the specific mongo backend being used supports transactions,
|
||||
** as, it appears that single-node instances do not, and they throw errors if
|
||||
** you try to do transaction operations in them... This is configured by the
|
||||
** corresponding field in the backend metaData.
|
||||
*******************************************************************************/
|
||||
public class MongoDBTransaction extends QBackendTransaction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MongoDBTransaction.class);
|
||||
|
||||
private boolean transactionsSupported;
|
||||
private MongoClient mongoClient;
|
||||
private ClientSession clientSession;
|
||||
|
||||
private Instant openedAt = Instant.now();
|
||||
private Integer logSlowTransactionSeconds = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MongoDBTransaction(MongoDBBackendMetaData backend, MongoClient mongoClient)
|
||||
{
|
||||
this.transactionsSupported = backend.getTransactionsSupported();
|
||||
ClientSession clientSession = mongoClient.startSession();
|
||||
|
||||
if(transactionsSupported)
|
||||
{
|
||||
clientSession.startTransaction();
|
||||
}
|
||||
|
||||
String propertyName = "qqq.mongodb.logSlowTransactionSeconds";
|
||||
try
|
||||
{
|
||||
logSlowTransactionSeconds = Integer.parseInt(System.getProperty(propertyName, "10"));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.debug("Error reading property [" + propertyName + "] value as integer", e);
|
||||
}
|
||||
|
||||
this.mongoClient = mongoClient;
|
||||
this.clientSession = clientSession;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void commit() throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
Instant commitAt = Instant.now();
|
||||
|
||||
Duration duration = Duration.between(openedAt, commitAt);
|
||||
if(logSlowTransactionSeconds != null && duration.compareTo(Duration.ofSeconds(logSlowTransactionSeconds)) > 0)
|
||||
{
|
||||
LOG.info("Committing long-running transaction", logPair("durationSeconds", duration.getSeconds()));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Committing transaction");
|
||||
}
|
||||
|
||||
if(transactionsSupported)
|
||||
{
|
||||
this.clientSession.commitTransaction();
|
||||
LOG.debug("Commit complete");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Request to commit, but transactions not supported in this mongodb backend");
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error committing transaction", e);
|
||||
throw new QException("Error committing transaction: " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// reset this - as after one commit, the transaction is essentially re-opened for any future statements that run on it //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
openedAt = Instant.now();
|
||||
if(transactionsSupported)
|
||||
{
|
||||
this.clientSession.startTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void rollback() throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
if(transactionsSupported)
|
||||
{
|
||||
LOG.info("Rolling back transaction");
|
||||
this.clientSession.abortTransaction();
|
||||
LOG.info("Rollback complete");
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Request to rollback, but transactions not supported in this mongodb backend");
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error rolling back transaction", e);
|
||||
throw new QException("Error rolling back transaction: " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// reset this - as after one commit, the transaction is essentially re-opened for any future statements that run on it //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
openedAt = Instant.now();
|
||||
if(transactionsSupported)
|
||||
{
|
||||
this.clientSession.startTransaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.clientSession.close();
|
||||
this.mongoClient.close();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error closing connection - possible mongo connection leak", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for mongoClient
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MongoClient getMongoClient()
|
||||
{
|
||||
return mongoClient;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for clientSession
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ClientSession getClientSession()
|
||||
{
|
||||
return clientSession;
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.UpdateInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UpdateActionRecordSplitHelper;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||
import com.mongodb.client.MongoCollection;
|
||||
import com.mongodb.client.MongoDatabase;
|
||||
import com.mongodb.client.model.Filters;
|
||||
import com.mongodb.client.model.Updates;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import org.bson.Document;
|
||||
import org.bson.conversions.Bson;
|
||||
import org.bson.types.ObjectId;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MongoDBUpdateAction extends AbstractMongoDBAction implements UpdateInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MongoDBUpdateAction.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public UpdateOutput execute(UpdateInput updateInput) throws QException
|
||||
{
|
||||
MongoClientContainer mongoClientContainer = null;
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
String backendTableName = getBackendTableName(table);
|
||||
MongoDBBackendMetaData backend = (MongoDBBackendMetaData) updateInput.getBackend();
|
||||
|
||||
UpdateActionRecordSplitHelper updateActionRecordSplitHelper = new UpdateActionRecordSplitHelper();
|
||||
updateActionRecordSplitHelper.init(updateInput);
|
||||
|
||||
UpdateOutput rs = new UpdateOutput();
|
||||
rs.setRecords(updateActionRecordSplitHelper.getOutputRecords());
|
||||
|
||||
if(!updateActionRecordSplitHelper.getHaveAnyWithoutErrors())
|
||||
{
|
||||
LOG.info("Exiting early - all records have some error.");
|
||||
return (rs);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mongoClientContainer = openClient(backend, updateInput.getTransaction());
|
||||
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
||||
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// process each distinct list of fields being updated (e.g., each different SQL statement) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
ListingHash<List<String>, QRecord> recordsByFieldBeingUpdated = updateActionRecordSplitHelper.getRecordsByFieldBeingUpdated();
|
||||
for(Map.Entry<List<String>, List<QRecord>> entry : recordsByFieldBeingUpdated.entrySet())
|
||||
{
|
||||
updateRecordsWithMatchingListOfFields(updateInput, mongoClientContainer, collection, table, entry.getValue(), entry.getKey());
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QException("Error executing update: " + e.getMessage(), e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if(mongoClientContainer != null)
|
||||
{
|
||||
mongoClientContainer.closeIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void updateRecordsWithMatchingListOfFields(UpdateInput updateInput, MongoClientContainer mongoClientContainer, MongoCollection<Document> collection, QTableMetaData table, List<QRecord> recordList, List<String> fieldsBeingUpdated)
|
||||
{
|
||||
boolean allAreTheSame = UpdateActionRecordSplitHelper.areAllValuesBeingUpdatedTheSame(updateInput, recordList, fieldsBeingUpdated);
|
||||
if(allAreTheSame)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if all records w/ this set of fields have the same values, we can do 1 big updateMany on the whole list //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
updateRecordsWithMatchingValuesAndFields(mongoClientContainer, collection, table, recordList, fieldsBeingUpdated);
|
||||
}
|
||||
else
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// else, if not all are being updated the same, then update one-by-one //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord record : recordList)
|
||||
{
|
||||
updateRecordsWithMatchingValuesAndFields(mongoClientContainer, collection, table, List.of(record), fieldsBeingUpdated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void updateRecordsWithMatchingValuesAndFields(MongoClientContainer mongoClientContainer, MongoCollection<Document> collection, QTableMetaData table, List<QRecord> recordList, List<String> fieldsBeingUpdated)
|
||||
{
|
||||
QRecord firstRecord = recordList.get(0);
|
||||
List<ObjectId> ids = recordList.stream().map(r -> new ObjectId(r.getValueString("id"))).toList();
|
||||
Bson filter = Filters.in("_id", ids);
|
||||
|
||||
List<Bson> updates = new ArrayList<>();
|
||||
for(String fieldName : fieldsBeingUpdated)
|
||||
{
|
||||
QFieldMetaData field = table.getField(fieldName);
|
||||
String fieldBackendName = getFieldBackendName(field);
|
||||
updates.add(Updates.set(fieldBackendName, firstRecord.getValue(fieldName)));
|
||||
}
|
||||
Bson changes = Updates.combine(updates);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// todo - system property to control (like print-sql) //
|
||||
////////////////////////////////////////////////////////
|
||||
// LOG.debug(filter, changes);
|
||||
|
||||
UpdateResult updateResult = collection.updateMany(mongoClientContainer.getMongoSession(), filter, changes);
|
||||
// todo - anything with the output??
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,343 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.model.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.MongoDBBackendModule;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data to provide details of a MongoDB backend (e.g., connection params)
|
||||
*******************************************************************************/
|
||||
public class MongoDBBackendMetaData extends QBackendMetaData
|
||||
{
|
||||
private String host;
|
||||
private Integer port;
|
||||
private String databaseName;
|
||||
private String username;
|
||||
private String password;
|
||||
private String authSourceDatabase;
|
||||
private String urlSuffix;
|
||||
|
||||
private boolean transactionsSupported = true;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default Constructor.
|
||||
*******************************************************************************/
|
||||
public MongoDBBackendMetaData()
|
||||
{
|
||||
super();
|
||||
setBackendType(MongoDBBackendModule.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter, override to help fluent flows
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public MongoDBBackendMetaData withName(String name)
|
||||
{
|
||||
setName(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for host
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getHost()
|
||||
{
|
||||
return host;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for host
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setHost(String host)
|
||||
{
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent Setter for host
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MongoDBBackendMetaData withHost(String host)
|
||||
{
|
||||
this.host = host;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for port
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getPort()
|
||||
{
|
||||
return port;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for port
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPort(Integer port)
|
||||
{
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent Setter for port
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MongoDBBackendMetaData withPort(Integer port)
|
||||
{
|
||||
this.port = port;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for username
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getUsername()
|
||||
{
|
||||
return username;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for username
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setUsername(String username)
|
||||
{
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent Setter for username
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MongoDBBackendMetaData withUsername(String username)
|
||||
{
|
||||
this.username = username;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for password
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getPassword()
|
||||
{
|
||||
return password;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for password
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setPassword(String password)
|
||||
{
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent Setter for password
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MongoDBBackendMetaData withPassword(String password)
|
||||
{
|
||||
this.password = password;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Called by the QInstanceEnricher - to do backend-type-specific enrichments.
|
||||
** Original use case is: reading secrets into fields (e.g., passwords).
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void enrich()
|
||||
{
|
||||
super.enrich();
|
||||
QMetaDataVariableInterpreter interpreter = new QMetaDataVariableInterpreter();
|
||||
username = interpreter.interpret(username);
|
||||
password = interpreter.interpret(password);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for urlSuffix
|
||||
*******************************************************************************/
|
||||
public String getUrlSuffix()
|
||||
{
|
||||
return (this.urlSuffix);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for urlSuffix
|
||||
*******************************************************************************/
|
||||
public void setUrlSuffix(String urlSuffix)
|
||||
{
|
||||
this.urlSuffix = urlSuffix;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for urlSuffix
|
||||
*******************************************************************************/
|
||||
public MongoDBBackendMetaData withUrlSuffix(String urlSuffix)
|
||||
{
|
||||
this.urlSuffix = urlSuffix;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for databaseName
|
||||
*******************************************************************************/
|
||||
public String getDatabaseName()
|
||||
{
|
||||
return (this.databaseName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for databaseName
|
||||
*******************************************************************************/
|
||||
public void setDatabaseName(String databaseName)
|
||||
{
|
||||
this.databaseName = databaseName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for databaseName
|
||||
*******************************************************************************/
|
||||
public MongoDBBackendMetaData withDatabaseName(String databaseName)
|
||||
{
|
||||
this.databaseName = databaseName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for transactionsSupported
|
||||
*******************************************************************************/
|
||||
public boolean getTransactionsSupported()
|
||||
{
|
||||
return (this.transactionsSupported);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for transactionsSupported
|
||||
*******************************************************************************/
|
||||
public void setTransactionsSupported(boolean transactionsSupported)
|
||||
{
|
||||
this.transactionsSupported = transactionsSupported;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for transactionsSupported
|
||||
*******************************************************************************/
|
||||
public MongoDBBackendMetaData withTransactionsSupported(boolean transactionsSupported)
|
||||
{
|
||||
this.transactionsSupported = transactionsSupported;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for authSourceDatabase
|
||||
*******************************************************************************/
|
||||
public String getAuthSourceDatabase()
|
||||
{
|
||||
return (this.authSourceDatabase);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for authSourceDatabase
|
||||
*******************************************************************************/
|
||||
public void setAuthSourceDatabase(String authSourceDatabase)
|
||||
{
|
||||
this.authSourceDatabase = authSourceDatabase;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for authSourceDatabase
|
||||
*******************************************************************************/
|
||||
public MongoDBBackendMetaData withAuthSourceDatabase(String authSourceDatabase)
|
||||
{
|
||||
this.authSourceDatabase = authSourceDatabase;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb.model.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableBackendDetails;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.MongoDBBackendModule;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Extension of QTableBackendDetails, with details specific to a MongoDB table.
|
||||
*******************************************************************************/
|
||||
public class MongoDBTableBackendDetails extends QTableBackendDetails
|
||||
{
|
||||
private String tableName;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default Constructor.
|
||||
*******************************************************************************/
|
||||
public MongoDBTableBackendDetails()
|
||||
{
|
||||
super();
|
||||
setBackendType(MongoDBBackendModule.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent Setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MongoDBTableBackendDetails withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base for all tests in this module
|
||||
*******************************************************************************/
|
||||
public class BaseTest
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(BaseTest.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** init the QContext with the instance from TestUtils and a new session
|
||||
*******************************************************************************/
|
||||
@BeforeEach
|
||||
void baseBeforeEach()
|
||||
{
|
||||
QContext.init(TestUtils.defineInstance(), new QSession());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** clear the QContext
|
||||
*******************************************************************************/
|
||||
@AfterEach
|
||||
void baseAfterEach()
|
||||
{
|
||||
QContext.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** if needed, re-initialize the QInstance in context.
|
||||
*******************************************************************************/
|
||||
protected static void reInitInstanceInContext(QInstance qInstance)
|
||||
{
|
||||
if(qInstance.equals(QContext.getQInstance()))
|
||||
{
|
||||
LOG.warn("Unexpected condition - the same qInstance that is already in the QContext was passed into reInit. You probably want a new QInstance object instance.");
|
||||
}
|
||||
QContext.init(qInstance, new QSession());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.module.mongodb;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBTableBackendDetails;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Test Utils class for this module
|
||||
*******************************************************************************/
|
||||
public class TestUtils
|
||||
{
|
||||
public static final String DEFAULT_BACKEND_NAME = "default";
|
||||
|
||||
public static final String TABLE_NAME_PERSON = "personTable";
|
||||
|
||||
public static final String SECURITY_KEY_STORE_ALL_ACCESS = "storeAllAccess";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void primeTestDatabase(String sqlFileName) throws Exception
|
||||
{
|
||||
/*
|
||||
ConnectionManager connectionManager = new ConnectionManager();
|
||||
try(Connection connection = connectionManager.getConnection(TestUtils.defineBackend()))
|
||||
{
|
||||
InputStream primeTestDatabaseSqlStream = RDBMSActionTest.class.getResourceAsStream("/" + sqlFileName);
|
||||
assertNotNull(primeTestDatabaseSqlStream);
|
||||
List<String> lines = (List<String>) IOUtils.readLines(primeTestDatabaseSqlStream);
|
||||
lines = lines.stream().filter(line -> !line.startsWith("-- ")).toList();
|
||||
String joinedSQL = String.join("\n", lines);
|
||||
for(String sql : joinedSQL.split(";"))
|
||||
{
|
||||
QueryManager.executeUpdate(connection, sql);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QInstance defineInstance()
|
||||
{
|
||||
QInstance qInstance = new QInstance();
|
||||
qInstance.addBackend(defineBackend());
|
||||
qInstance.addTable(defineTablePerson());
|
||||
qInstance.setAuthentication(defineAuthentication());
|
||||
return (qInstance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Define the authentication used in standard tests - using 'mock' type.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QAuthenticationMetaData defineAuthentication()
|
||||
{
|
||||
return new QAuthenticationMetaData()
|
||||
.withName("mock")
|
||||
.withType(QAuthenticationType.MOCK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static MongoDBBackendMetaData defineBackend()
|
||||
{
|
||||
return (new MongoDBBackendMetaData()
|
||||
.withName(DEFAULT_BACKEND_NAME)
|
||||
.withHost("localhost")
|
||||
.withPort(27017)
|
||||
.withUsername("ctliveuser")
|
||||
.withPassword("uoaKOIjfk23h8lozK983L")
|
||||
.withAuthSourceDatabase("admin")
|
||||
.withDatabaseName("testDatabase")
|
||||
/*.withUrlSuffix("?tls=true&tlsCAFile=global-bundle.pem&retryWrites=false")*/);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QTableMetaData defineTablePerson()
|
||||
{
|
||||
return new QTableMetaData()
|
||||
.withName(TABLE_NAME_PERSON)
|
||||
.withLabel("Person")
|
||||
.withRecordLabelFormat("%s %s")
|
||||
.withRecordLabelFields("firstName", "lastName")
|
||||
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||
.withPrimaryKeyField("id")
|
||||
.withField(new QFieldMetaData("id", QFieldType.STRING).withBackendName("_id"))
|
||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME))
|
||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME))
|
||||
.withField(new QFieldMetaData("firstName", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("lastName", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("birthDate", QFieldType.DATE))
|
||||
.withField(new QFieldMetaData("email", QFieldType.STRING))
|
||||
.withField(new QFieldMetaData("isEmployed", QFieldType.BOOLEAN))
|
||||
.withField(new QFieldMetaData("annualSalary", QFieldType.DECIMAL))
|
||||
.withField(new QFieldMetaData("daysWorked", QFieldType.INTEGER))
|
||||
.withField(new QFieldMetaData("homeTown", QFieldType.STRING))
|
||||
.withBackendDetails(new MongoDBTableBackendDetails()
|
||||
.withTableName("testTable"));
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user