mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
CE-781 Add logQuery, queryStats, actionTimeouts to MongoDB; fix many query operators while adding test coverage
This commit is contained in:
@ -33,6 +33,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
@ -40,14 +41,17 @@ 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.QCriteriaOperator;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
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.actions.tables.query.QQueryFilter;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
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.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
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.fields.QFieldType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
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.RecordSecurityLock;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLockFilters;
|
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.metadata.tables.QTableMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
@ -63,6 +67,7 @@ import com.mongodb.client.model.Filters;
|
|||||||
import org.bson.Document;
|
import org.bson.Document;
|
||||||
import org.bson.conversions.Bson;
|
import org.bson.conversions.Bson;
|
||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
|
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -72,6 +77,8 @@ public class AbstractMongoDBAction
|
|||||||
{
|
{
|
||||||
private static final QLogger LOG = QLogger.getLogger(AbstractMongoDBAction.class);
|
private static final QLogger LOG = QLogger.getLogger(AbstractMongoDBAction.class);
|
||||||
|
|
||||||
|
protected QueryStat queryStat;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -137,6 +144,11 @@ public class AbstractMongoDBAction
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
protected String getBackendTableName(QTableMetaData table)
|
protected String getBackendTableName(QTableMetaData table)
|
||||||
{
|
{
|
||||||
|
if(table == null)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
if(table.getBackendDetails() != null)
|
if(table.getBackendDetails() != null)
|
||||||
{
|
{
|
||||||
String backendTableName = ((MongoDBTableBackendDetails) table.getBackendDetails()).getTableName();
|
String backendTableName = ((MongoDBTableBackendDetails) table.getBackendDetails()).getTableName();
|
||||||
@ -368,8 +380,16 @@ public class AbstractMongoDBAction
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bson searchQueryForSecurity = makeSearchQueryDocumentWithoutSecurity(table, securityFilter);
|
Bson searchQueryForSecurity = makeSearchQueryDocumentWithoutSecurity(table, securityFilter);
|
||||||
|
|
||||||
|
if(searchQueryWithoutSecurity.toBsonDocument().isEmpty())
|
||||||
|
{
|
||||||
|
return (searchQueryForSecurity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return (Filters.and(searchQueryWithoutSecurity, searchQueryForSecurity));
|
return (Filters.and(searchQueryWithoutSecurity, searchQueryForSecurity));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -524,6 +544,31 @@ public class AbstractMongoDBAction
|
|||||||
QFieldMetaData field = table.getField(criteria.getFieldName());
|
QFieldMetaData field = table.getField(criteria.getFieldName());
|
||||||
String fieldBackendName = getFieldBackendName(field);
|
String fieldBackendName = getFieldBackendName(field);
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// replace any expression-type values with their evaluation //
|
||||||
|
// also, "scrub" non-expression values, which type-converts them (e.g., strings in various supported date formats become LocalDate) //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
ListIterator<Serializable> valueListIterator = values.listIterator();
|
||||||
|
while(valueListIterator.hasNext())
|
||||||
|
{
|
||||||
|
Serializable value = valueListIterator.next();
|
||||||
|
if(value instanceof AbstractFilterExpression<?> expression)
|
||||||
|
{
|
||||||
|
valueListIterator.set(expression.evaluate());
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
todo - is this needed??
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serializable scrubbedValue = scrubValue(field, value);
|
||||||
|
valueListIterator.set(scrubbedValue);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// make sure any values we're going to run against the primary key (_id) are ObjectIds //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(field.getName().equals(table.getPrimaryKeyField()))
|
if(field.getName().equals(table.getPrimaryKeyField()))
|
||||||
{
|
{
|
||||||
ListIterator<Serializable> iterator = values.listIterator();
|
ListIterator<Serializable> iterator = values.listIterator();
|
||||||
@ -534,37 +579,53 @@ public class AbstractMongoDBAction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Serializable value0 = values.get(0);
|
////////
|
||||||
|
// :( //
|
||||||
|
////////
|
||||||
|
if(StringUtils.hasContent(criteria.getOtherFieldName()))
|
||||||
|
{
|
||||||
|
throw (new IllegalArgumentException("A mongodb query with an 'otherFieldName' specified is not currently supported."));
|
||||||
|
}
|
||||||
|
|
||||||
criteriaFilters.add(switch(criteria.getOperator())
|
criteriaFilters.add(switch(criteria.getOperator())
|
||||||
{
|
{
|
||||||
case EQUALS -> Filters.eq(fieldBackendName, value0);
|
case EQUALS -> Filters.eq(fieldBackendName, getValue(values, 0));
|
||||||
case NOT_EQUALS -> Filters.ne(fieldBackendName, value0);
|
|
||||||
|
case NOT_EQUALS -> Filters.and(
|
||||||
|
Filters.ne(fieldBackendName, getValue(values, 0)),
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// to match RDBMS and other QQQ backends, consider a null to not match a not-equals query //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
Filters.not(Filters.eq(fieldBackendName, null))
|
||||||
|
);
|
||||||
|
|
||||||
case NOT_EQUALS_OR_IS_NULL -> Filters.or(
|
case NOT_EQUALS_OR_IS_NULL -> Filters.or(
|
||||||
Filters.eq(fieldBackendName, null),
|
Filters.eq(fieldBackendName, null),
|
||||||
Filters.ne(fieldBackendName, value0)
|
Filters.ne(fieldBackendName, getValue(values, 0))
|
||||||
);
|
);
|
||||||
case IN -> filterIn(fieldBackendName, values);
|
case IN -> filterIn(fieldBackendName, values);
|
||||||
case NOT_IN -> Filters.not(filterIn(fieldBackendName, values));
|
case NOT_IN -> Filters.nor(filterIn(fieldBackendName, values));
|
||||||
case IS_NULL_OR_IN -> Filters.or(
|
case IS_NULL_OR_IN -> Filters.or(
|
||||||
Filters.eq(fieldBackendName, null),
|
Filters.eq(fieldBackendName, null),
|
||||||
filterIn(fieldBackendName, values)
|
filterIn(fieldBackendName, values)
|
||||||
);
|
);
|
||||||
case LIKE -> filterRegex(fieldBackendName, null, ValueUtils.getValueAsString(value0).replaceAll("%", ".*"), null);
|
case LIKE -> filterRegex(fieldBackendName, null, ValueUtils.getValueAsString(getValue(values, 0)).replaceAll("%", ".*"), null);
|
||||||
case NOT_LIKE -> Filters.not(filterRegex(fieldBackendName, null, ValueUtils.getValueAsString(value0).replaceAll("%", ".*"), null));
|
case NOT_LIKE -> Filters.nor(filterRegex(fieldBackendName, null, ValueUtils.getValueAsString(getValue(values, 0)).replaceAll("%", ".*"), null));
|
||||||
case STARTS_WITH -> filterRegex(fieldBackendName, null, value0, ".*");
|
case STARTS_WITH -> filterRegex(fieldBackendName, null, getValue(values, 0), ".*");
|
||||||
case ENDS_WITH -> filterRegex(fieldBackendName, ".*", value0, null);
|
case ENDS_WITH -> filterRegex(fieldBackendName, ".*", getValue(values, 0), null);
|
||||||
case CONTAINS -> filterRegex(fieldBackendName, ".*", value0, ".*");
|
case CONTAINS -> filterRegex(fieldBackendName, ".*", getValue(values, 0), ".*");
|
||||||
case NOT_STARTS_WITH -> Filters.not(filterRegex(fieldBackendName, null, value0, ".*"));
|
case NOT_STARTS_WITH -> Filters.nor(filterRegex(fieldBackendName, null, getValue(values, 0), ".*"));
|
||||||
case NOT_ENDS_WITH -> Filters.not(filterRegex(fieldBackendName, ".*", value0, null));
|
case NOT_ENDS_WITH -> Filters.nor(filterRegex(fieldBackendName, ".*", getValue(values, 0), null));
|
||||||
case NOT_CONTAINS -> Filters.not(filterRegex(fieldBackendName, ".*", value0, ".*"));
|
case NOT_CONTAINS -> Filters.nor(filterRegex(fieldBackendName, ".*", getValue(values, 0), ".*"));
|
||||||
case LESS_THAN -> Filters.lt(fieldBackendName, value0);
|
case LESS_THAN -> Filters.lt(fieldBackendName, getValue(values, 0));
|
||||||
case LESS_THAN_OR_EQUALS -> Filters.lte(fieldBackendName, value0);
|
case LESS_THAN_OR_EQUALS -> Filters.lte(fieldBackendName, getValue(values, 0));
|
||||||
case GREATER_THAN -> Filters.gt(fieldBackendName, value0);
|
case GREATER_THAN -> Filters.gt(fieldBackendName, getValue(values, 0));
|
||||||
case GREATER_THAN_OR_EQUALS -> Filters.gte(fieldBackendName, value0);
|
case GREATER_THAN_OR_EQUALS -> Filters.gte(fieldBackendName, getValue(values, 0));
|
||||||
case IS_BLANK -> filterIsBlank(fieldBackendName);
|
case IS_BLANK -> filterIsBlank(fieldBackendName);
|
||||||
case IS_NOT_BLANK -> Filters.not(filterIsBlank(fieldBackendName));
|
case IS_NOT_BLANK -> Filters.nor(filterIsBlank(fieldBackendName));
|
||||||
case BETWEEN -> filterBetween(fieldBackendName, values);
|
case BETWEEN -> filterBetween(fieldBackendName, values);
|
||||||
case NOT_BETWEEN -> Filters.not(filterBetween(fieldBackendName, values));
|
case NOT_BETWEEN -> Filters.nor(filterBetween(fieldBackendName, values));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,6 +646,21 @@ public class AbstractMongoDBAction
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static Serializable getValue(List<Serializable> values, int i)
|
||||||
|
{
|
||||||
|
if(values == null || values.size() <= i)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Incorrect number of values given for criteria");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (values.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** build a bson filter doing a regex (e.g., for LIKE, STARTS_WITH, etc)
|
** build a bson filter doing a regex (e.g., for LIKE, STARTS_WITH, etc)
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -600,7 +676,7 @@ public class AbstractMongoDBAction
|
|||||||
suffix = "";
|
suffix = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
String fullRegex = prefix + Pattern.quote(ValueUtils.getValueAsString(mainRegex) + suffix);
|
String fullRegex = prefix + ValueUtils.getValueAsString(mainRegex + suffix);
|
||||||
return (Filters.regex(fieldBackendName, Pattern.compile(fullRegex)));
|
return (Filters.regex(fieldBackendName, Pattern.compile(fullRegex)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -622,8 +698,8 @@ public class AbstractMongoDBAction
|
|||||||
private static Bson filterBetween(String fieldBackendName, List<Serializable> values)
|
private static Bson filterBetween(String fieldBackendName, List<Serializable> values)
|
||||||
{
|
{
|
||||||
return Filters.and(
|
return Filters.and(
|
||||||
Filters.gte(fieldBackendName, values.get(0)),
|
Filters.gte(fieldBackendName, getValue(values, 0)),
|
||||||
Filters.lte(fieldBackendName, values.get(1))
|
Filters.lte(fieldBackendName, getValue(values, 1))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -639,4 +715,75 @@ public class AbstractMongoDBAction
|
|||||||
Filters.eq(fieldBackendName, "")
|
Filters.eq(fieldBackendName, "")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for queryStat
|
||||||
|
*******************************************************************************/
|
||||||
|
public QueryStat getQueryStat()
|
||||||
|
{
|
||||||
|
return (this.queryStat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for queryStat
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setQueryStat(QueryStat queryStat)
|
||||||
|
{
|
||||||
|
this.queryStat = queryStat;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
protected void setQueryInQueryStat(Bson query)
|
||||||
|
{
|
||||||
|
if(queryStat != null && query != null)
|
||||||
|
{
|
||||||
|
queryStat.setQueryText(query.toString());
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
// todo - if we support joins in the future, do them here too //
|
||||||
|
////////////////////////////////////////////////////////////////
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
protected void logQuery(String tableName, String actionName, List<Bson> query, Long queryStartTime)
|
||||||
|
{
|
||||||
|
|
||||||
|
if(System.getProperty("qqq.mongodb.logQueries", "false").equals("true"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(System.getProperty("qqq.mongodb.logQueries.output", "logger").equalsIgnoreCase("system.out"))
|
||||||
|
{
|
||||||
|
System.out.println("Table: " + tableName + ", Action: " + actionName + ", Query: " + query);
|
||||||
|
|
||||||
|
if(queryStartTime != null)
|
||||||
|
{
|
||||||
|
System.out.println("Query Took [" + QValueFormatter.formatValue(DisplayFormat.COMMAS, (System.currentTimeMillis() - queryStartTime)) + "] ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG.debug("Running Query", logPair("table", tableName), logPair("action", actionName), logPair("query", query), logPair("millis", queryStartTime == null ? null : (System.currentTimeMillis() - queryStartTime)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.debug("Error logging query...", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,11 @@ package com.kingsrook.qqq.backend.module.mongodb.actions;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ActionTimeoutHelper;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
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.Aggregate;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateInput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.aggregate.AggregateInput;
|
||||||
@ -61,7 +64,7 @@ public class MongoDBAggregateAction extends AbstractMongoDBAction implements Agg
|
|||||||
{
|
{
|
||||||
private static final QLogger LOG = QLogger.getLogger(MongoDBBackendModule.class);
|
private static final QLogger LOG = QLogger.getLogger(MongoDBBackendModule.class);
|
||||||
|
|
||||||
// todo? private ActionTimeoutHelper actionTimeoutHelper;
|
private ActionTimeoutHelper actionTimeoutHelper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -73,6 +76,9 @@ public class MongoDBAggregateAction extends AbstractMongoDBAction implements Agg
|
|||||||
{
|
{
|
||||||
MongoClientContainer mongoClientContainer = null;
|
MongoClientContainer mongoClientContainer = null;
|
||||||
|
|
||||||
|
Long queryStartTime = System.currentTimeMillis();
|
||||||
|
List<Bson> queryToLog = new ArrayList<>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
AggregateOutput aggregateOutput = new AggregateOutput();
|
AggregateOutput aggregateOutput = new AggregateOutput();
|
||||||
@ -87,6 +93,12 @@ public class MongoDBAggregateAction extends AbstractMongoDBAction implements Agg
|
|||||||
QQueryFilter filter = aggregateInput.getFilter();
|
QQueryFilter filter = aggregateInput.getFilter();
|
||||||
Bson searchQuery = makeSearchQueryDocument(table, filter);
|
Bson searchQuery = makeSearchQueryDocument(table, filter);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// set up & start an actionTimeoutHelper (note, internally it'll deal with the time being null or negative as meaning not to timeout) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper = new ActionTimeoutHelper(aggregateInput.getTimeoutSeconds(), TimeUnit.SECONDS, new TimeoutCanceller(mongoClientContainer));
|
||||||
|
actionTimeoutHelper.start();
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
// we have to submit a list of BSON objects to the aggregate function. //
|
// we have to submit a list of BSON objects to the aggregate function. //
|
||||||
// the first one is the search query //
|
// the first one is the search query //
|
||||||
@ -94,6 +106,8 @@ public class MongoDBAggregateAction extends AbstractMongoDBAction implements Agg
|
|||||||
/////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////
|
||||||
List<Bson> bsonList = new ArrayList<>();
|
List<Bson> bsonList = new ArrayList<>();
|
||||||
bsonList.add(Aggregates.match(searchQuery));
|
bsonList.add(Aggregates.match(searchQuery));
|
||||||
|
setQueryInQueryStat(searchQuery);
|
||||||
|
queryToLog = bsonList;
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////////////////
|
||||||
// if there are group-by fields, then we need to build a document with those fields //
|
// if there are group-by fields, then we need to build a document with those fields //
|
||||||
@ -184,6 +198,12 @@ public class MongoDBAggregateAction extends AbstractMongoDBAction implements Agg
|
|||||||
/////////////////////
|
/////////////////////
|
||||||
for(Document document : aggregates)
|
for(Document document : aggregates)
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// once we've started getting results, go ahead and cancel the timeout //
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper.cancel();
|
||||||
|
setQueryStatFirstResultTime();
|
||||||
|
|
||||||
AggregateResult result = new AggregateResult();
|
AggregateResult result = new AggregateResult();
|
||||||
results.add(result);
|
results.add(result);
|
||||||
|
|
||||||
@ -222,13 +242,16 @@ public class MongoDBAggregateAction extends AbstractMongoDBAction implements Agg
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
||||||
{
|
{
|
||||||
setCountStatFirstResultTime();
|
setQueryStatFirstResultTime();
|
||||||
throw (new QUserFacingException("Aggregate timed out."));
|
throw (new QUserFacingException("Aggregate timed out."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this was copied from RDBMS - not sure where/how/if it's being used there though //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(isCancelled)
|
if(isCancelled)
|
||||||
{
|
{
|
||||||
throw (new QUserFacingException("Aggregate was cancelled."));
|
throw (new QUserFacingException("Aggregate was cancelled."));
|
||||||
@ -239,8 +262,9 @@ public class MongoDBAggregateAction extends AbstractMongoDBAction implements Agg
|
|||||||
throw new QException("Error executing aggregate", e);
|
throw new QException("Error executing aggregate", e);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
||||||
{
|
{
|
||||||
|
logQuery(getBackendTableName(aggregateInput.getTable()), "aggregate", queryToLog, queryStartTime);
|
||||||
|
|
||||||
if(mongoClientContainer != null)
|
if(mongoClientContainer != null)
|
||||||
{
|
{
|
||||||
mongoClientContainer.closeIfNeeded();
|
mongoClientContainer.closeIfNeeded();
|
||||||
|
@ -22,9 +22,13 @@
|
|||||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ActionTimeoutHelper;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
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.CountInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||||
@ -48,7 +52,7 @@ public class MongoDBCountAction extends AbstractMongoDBAction implements CountIn
|
|||||||
{
|
{
|
||||||
private static final QLogger LOG = QLogger.getLogger(MongoDBBackendModule.class);
|
private static final QLogger LOG = QLogger.getLogger(MongoDBBackendModule.class);
|
||||||
|
|
||||||
// todo? private ActionTimeoutHelper actionTimeoutHelper;
|
private ActionTimeoutHelper actionTimeoutHelper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -59,6 +63,9 @@ public class MongoDBCountAction extends AbstractMongoDBAction implements CountIn
|
|||||||
{
|
{
|
||||||
MongoClientContainer mongoClientContainer = null;
|
MongoClientContainer mongoClientContainer = null;
|
||||||
|
|
||||||
|
Long queryStartTime = System.currentTimeMillis();
|
||||||
|
List<Bson> queryToLog = new ArrayList<>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
CountOutput countOutput = new CountOutput();
|
CountOutput countOutput = new CountOutput();
|
||||||
@ -70,34 +77,43 @@ public class MongoDBCountAction extends AbstractMongoDBAction implements CountIn
|
|||||||
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
||||||
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// set up & start an actionTimeoutHelper (note, internally it'll deal with the time being null or negative as meaning not to timeout) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper = new ActionTimeoutHelper(countInput.getTimeoutSeconds(), TimeUnit.SECONDS, new TimeoutCanceller(mongoClientContainer));
|
||||||
|
actionTimeoutHelper.start();
|
||||||
|
|
||||||
QQueryFilter filter = countInput.getFilter();
|
QQueryFilter filter = countInput.getFilter();
|
||||||
Bson searchQuery = makeSearchQueryDocument(table, filter);
|
Bson searchQuery = makeSearchQueryDocument(table, filter);
|
||||||
|
queryToLog.add(searchQuery);
|
||||||
|
setQueryInQueryStat(searchQuery);
|
||||||
|
|
||||||
List<Bson> bsonList = List.of(
|
List<Bson> bsonList = List.of(
|
||||||
Aggregates.match(searchQuery),
|
Aggregates.match(searchQuery),
|
||||||
Aggregates.group("_id", Accumulators.sum("count", 1)));
|
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);
|
AggregateIterable<Document> aggregate = collection.aggregate(mongoClientContainer.getMongoSession(), bsonList);
|
||||||
|
|
||||||
Document document = aggregate.first();
|
Document document = aggregate.first();
|
||||||
countOutput.setCount(document == null ? 0 : document.get("count", Integer.class));
|
countOutput.setCount(document == null ? 0 : document.get("count", Integer.class));
|
||||||
|
|
||||||
|
actionTimeoutHelper.cancel();
|
||||||
|
setQueryStatFirstResultTime();
|
||||||
|
|
||||||
return (countOutput);
|
return (countOutput);
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
||||||
{
|
{
|
||||||
setCountStatFirstResultTime();
|
setQueryStatFirstResultTime();
|
||||||
throw (new QUserFacingException("Count timed out."));
|
throw (new QUserFacingException("Count timed out."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this was copied from RDBMS - not sure where/how/if it's being used there though //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(isCancelled)
|
if(isCancelled)
|
||||||
{
|
{
|
||||||
throw (new QUserFacingException("Count was cancelled."));
|
throw (new QUserFacingException("Count was cancelled."));
|
||||||
@ -109,6 +125,8 @@ public class MongoDBCountAction extends AbstractMongoDBAction implements CountIn
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
logQuery(getBackendTableName(countInput.getTable()), "count", queryToLog, queryStartTime);
|
||||||
|
|
||||||
if(mongoClientContainer != null)
|
if(mongoClientContainer != null)
|
||||||
{
|
{
|
||||||
mongoClientContainer.closeIfNeeded();
|
mongoClientContainer.closeIfNeeded();
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
@ -70,6 +72,9 @@ public class MongoDBDeleteAction extends AbstractMongoDBAction implements Delete
|
|||||||
{
|
{
|
||||||
MongoClientContainer mongoClientContainer = null;
|
MongoClientContainer mongoClientContainer = null;
|
||||||
|
|
||||||
|
Long queryStartTime = System.currentTimeMillis();
|
||||||
|
List<Bson> queryToLog = new ArrayList<>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DeleteOutput deleteOutput = new DeleteOutput();
|
DeleteOutput deleteOutput = new DeleteOutput();
|
||||||
@ -98,6 +103,8 @@ public class MongoDBDeleteAction extends AbstractMongoDBAction implements Delete
|
|||||||
return (deleteOutput);
|
return (deleteOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
queryToLog.add(searchQuery);
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
// todo - system property to control (like print-sql) //
|
// todo - system property to control (like print-sql) //
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
@ -119,6 +126,8 @@ public class MongoDBDeleteAction extends AbstractMongoDBAction implements Delete
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
logQuery(getBackendTableName(deleteInput.getTable()), "delete", queryToLog, queryStartTime);
|
||||||
|
|
||||||
if(mongoClientContainer != null)
|
if(mongoClientContainer != null)
|
||||||
{
|
{
|
||||||
mongoClientContainer.closeIfNeeded();
|
mongoClientContainer.closeIfNeeded();
|
||||||
|
@ -38,6 +38,7 @@ import com.mongodb.client.MongoDatabase;
|
|||||||
import com.mongodb.client.result.InsertManyResult;
|
import com.mongodb.client.result.InsertManyResult;
|
||||||
import org.bson.BsonValue;
|
import org.bson.BsonValue;
|
||||||
import org.bson.Document;
|
import org.bson.Document;
|
||||||
|
import org.bson.conversions.Bson;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -59,6 +60,9 @@ public class MongoDBInsertAction extends AbstractMongoDBAction implements Insert
|
|||||||
List<QRecord> outputRecords = new ArrayList<>();
|
List<QRecord> outputRecords = new ArrayList<>();
|
||||||
rs.setRecords(outputRecords);
|
rs.setRecords(outputRecords);
|
||||||
|
|
||||||
|
Long queryStartTime = System.currentTimeMillis();
|
||||||
|
List<Bson> queryToLog = new ArrayList<>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
QTableMetaData table = insertInput.getTable();
|
QTableMetaData table = insertInput.getTable();
|
||||||
@ -69,10 +73,6 @@ public class MongoDBInsertAction extends AbstractMongoDBAction implements Insert
|
|||||||
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
||||||
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
||||||
|
|
||||||
//////////////////////////
|
|
||||||
// todo - transaction?! //
|
|
||||||
//////////////////////////
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// page over input record list (assuming some size of batch is too big?) //
|
// page over input record list (assuming some size of batch is too big?) //
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
@ -88,7 +88,10 @@ public class MongoDBInsertAction extends AbstractMongoDBAction implements Insert
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
documentList.add(recordToDocument(table, record));
|
|
||||||
|
Document document = recordToDocument(table, record);
|
||||||
|
documentList.add(document);
|
||||||
|
queryToLog.add(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
@ -99,11 +102,6 @@ public class MongoDBInsertAction extends AbstractMongoDBAction implements Insert
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
|
||||||
// todo - system property to control (like print-sql) //
|
|
||||||
////////////////////////////////////////////////////////
|
|
||||||
// LOG.debug(documentList);
|
|
||||||
|
|
||||||
///////////////////////////////////////////////
|
///////////////////////////////////////////////
|
||||||
// actually do the insert //
|
// actually do the insert //
|
||||||
// todo - how are errors returned by mongo?? //
|
// todo - how are errors returned by mongo?? //
|
||||||
@ -134,6 +132,8 @@ public class MongoDBInsertAction extends AbstractMongoDBAction implements Insert
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
logQuery(getBackendTableName(insertInput.getTable()), "insert", queryToLog, queryStartTime);
|
||||||
|
|
||||||
if(mongoClientContainer != null)
|
if(mongoClientContainer != null)
|
||||||
{
|
{
|
||||||
mongoClientContainer.closeIfNeeded();
|
mongoClientContainer.closeIfNeeded();
|
||||||
|
@ -22,8 +22,13 @@
|
|||||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ActionTimeoutHelper;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
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.QFilterOrderBy;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||||
@ -48,7 +53,7 @@ public class MongoDBQueryAction extends AbstractMongoDBAction implements QueryIn
|
|||||||
{
|
{
|
||||||
private static final QLogger LOG = QLogger.getLogger(MongoDBBackendModule.class);
|
private static final QLogger LOG = QLogger.getLogger(MongoDBBackendModule.class);
|
||||||
|
|
||||||
// todo? private ActionTimeoutHelper actionTimeoutHelper;
|
private ActionTimeoutHelper actionTimeoutHelper;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -59,6 +64,9 @@ public class MongoDBQueryAction extends AbstractMongoDBAction implements QueryIn
|
|||||||
{
|
{
|
||||||
MongoClientContainer mongoClientContainer = null;
|
MongoClientContainer mongoClientContainer = null;
|
||||||
|
|
||||||
|
Long queryStartTime = System.currentTimeMillis();
|
||||||
|
List<Bson> queryToLog = new ArrayList<>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
QueryOutput queryOutput = new QueryOutput(queryInput);
|
QueryOutput queryOutput = new QueryOutput(queryInput);
|
||||||
@ -70,16 +78,19 @@ public class MongoDBQueryAction extends AbstractMongoDBAction implements QueryIn
|
|||||||
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
MongoDatabase database = mongoClientContainer.getMongoClient().getDatabase(backend.getDatabaseName());
|
||||||
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
MongoCollection<Document> collection = database.getCollection(backendTableName);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// set up & start an actionTimeoutHelper (note, internally it'll deal with the time being null or negative as meaning not to timeout) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper = new ActionTimeoutHelper(queryInput.getTimeoutSeconds(), TimeUnit.SECONDS, new TimeoutCanceller(mongoClientContainer));
|
||||||
|
actionTimeoutHelper.start();
|
||||||
|
|
||||||
/////////////////////////
|
/////////////////////////
|
||||||
// set up filter/query //
|
// set up filter/query //
|
||||||
/////////////////////////
|
/////////////////////////
|
||||||
QQueryFilter filter = queryInput.getFilter();
|
QQueryFilter filter = queryInput.getFilter();
|
||||||
Bson searchQuery = makeSearchQueryDocument(table, filter);
|
Bson searchQuery = makeSearchQueryDocument(table, filter);
|
||||||
|
queryToLog.add(searchQuery);
|
||||||
////////////////////////////////////////////////////////
|
setQueryInQueryStat(searchQuery);
|
||||||
// todo - system property to control (like print-sql) //
|
|
||||||
////////////////////////////////////////////////////////
|
|
||||||
// LOG.debug(searchQuery);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
// create cursor - further adjustments to it still follow //
|
// create cursor - further adjustments to it still follow //
|
||||||
@ -92,6 +103,7 @@ public class MongoDBQueryAction extends AbstractMongoDBAction implements QueryIn
|
|||||||
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getOrderBys()))
|
if(filter != null && CollectionUtils.nullSafeHasContents(filter.getOrderBys()))
|
||||||
{
|
{
|
||||||
Document sortDocument = new Document();
|
Document sortDocument = new Document();
|
||||||
|
queryToLog.add(sortDocument);
|
||||||
for(QFilterOrderBy orderBy : filter.getOrderBys())
|
for(QFilterOrderBy orderBy : filter.getOrderBys())
|
||||||
{
|
{
|
||||||
String fieldBackendName = getFieldBackendName(table.getField(orderBy.getFieldName()));
|
String fieldBackendName = getFieldBackendName(table.getField(orderBy.getFieldName()));
|
||||||
@ -121,6 +133,12 @@ public class MongoDBQueryAction extends AbstractMongoDBAction implements QueryIn
|
|||||||
////////////////////////////////////////////
|
////////////////////////////////////////////
|
||||||
for(Document document : cursor)
|
for(Document document : cursor)
|
||||||
{
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
// once we've started getting results, go ahead and cancel the timeout //
|
||||||
|
/////////////////////////////////////////////////////////////////////////
|
||||||
|
actionTimeoutHelper.cancel();
|
||||||
|
setQueryStatFirstResultTime();
|
||||||
|
|
||||||
QRecord record = documentToRecord(table, document);
|
QRecord record = documentToRecord(table, document);
|
||||||
queryOutput.addRecord(record);
|
queryOutput.addRecord(record);
|
||||||
|
|
||||||
@ -135,13 +153,16 @@ public class MongoDBQueryAction extends AbstractMongoDBAction implements QueryIn
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
/*
|
|
||||||
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
if(actionTimeoutHelper != null && actionTimeoutHelper.getDidTimeout())
|
||||||
{
|
{
|
||||||
setQueryStatFirstResultTime();
|
setQueryStatFirstResultTime();
|
||||||
throw (new QUserFacingException("Query timed out."));
|
throw (new QUserFacingException("Query timed out."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// this was copied from RDBMS - not sure where/how/if it's being used there though //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
if(isCancelled)
|
if(isCancelled)
|
||||||
{
|
{
|
||||||
throw (new QUserFacingException("Query was cancelled."));
|
throw (new QUserFacingException("Query was cancelled."));
|
||||||
@ -153,6 +174,8 @@ public class MongoDBQueryAction extends AbstractMongoDBAction implements QueryIn
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
logQuery(getBackendTableName(queryInput.getTable()), "query", queryToLog, queryStartTime);
|
||||||
|
|
||||||
if(mongoClientContainer != null)
|
if(mongoClientContainer != null)
|
||||||
{
|
{
|
||||||
mongoClientContainer.closeIfNeeded();
|
mongoClientContainer.closeIfNeeded();
|
||||||
|
@ -141,26 +141,29 @@ public class MongoDBUpdateAction extends AbstractMongoDBAction implements Update
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private void updateRecordsWithMatchingValuesAndFields(MongoClientContainer mongoClientContainer, MongoCollection<Document> collection, QTableMetaData table, List<QRecord> recordList, List<String> fieldsBeingUpdated)
|
private void updateRecordsWithMatchingValuesAndFields(MongoClientContainer mongoClientContainer, MongoCollection<Document> collection, QTableMetaData table, List<QRecord> recordList, List<String> fieldsBeingUpdated)
|
||||||
{
|
{
|
||||||
|
Long queryStartTime = System.currentTimeMillis();
|
||||||
|
List<Bson> queryToLog = new ArrayList<>();
|
||||||
|
|
||||||
QRecord firstRecord = recordList.get(0);
|
QRecord firstRecord = recordList.get(0);
|
||||||
List<ObjectId> ids = recordList.stream().map(r -> new ObjectId(r.getValueString("id"))).toList();
|
List<ObjectId> ids = recordList.stream().map(r -> new ObjectId(r.getValueString("id"))).toList();
|
||||||
Bson filter = Filters.in("_id", ids);
|
Bson filter = Filters.in("_id", ids);
|
||||||
|
queryToLog.add(filter);
|
||||||
|
|
||||||
List<Bson> updates = new ArrayList<>();
|
List<Bson> updates = new ArrayList<>();
|
||||||
for(String fieldName : fieldsBeingUpdated)
|
for(String fieldName : fieldsBeingUpdated)
|
||||||
{
|
{
|
||||||
QFieldMetaData field = table.getField(fieldName);
|
QFieldMetaData field = table.getField(fieldName);
|
||||||
String fieldBackendName = getFieldBackendName(field);
|
String fieldBackendName = getFieldBackendName(field);
|
||||||
updates.add(Updates.set(fieldBackendName, firstRecord.getValue(fieldName)));
|
Bson set = Updates.set(fieldBackendName, firstRecord.getValue(fieldName));
|
||||||
|
updates.add(set);
|
||||||
|
queryToLog.add(set);
|
||||||
}
|
}
|
||||||
Bson changes = Updates.combine(updates);
|
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);
|
UpdateResult updateResult = collection.updateMany(mongoClientContainer.getMongoSession(), filter, changes);
|
||||||
// todo - anything with the output??
|
// todo - anything with the output??
|
||||||
|
|
||||||
|
logQuery(getBackendTableName(table), "update", queryToLog, queryStartTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||||
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Helper to cancel statements that timeout.
|
||||||
|
*******************************************************************************/
|
||||||
|
public class TimeoutCanceller implements Runnable
|
||||||
|
{
|
||||||
|
private static final QLogger LOG = QLogger.getLogger(TimeoutCanceller.class);
|
||||||
|
private final MongoClientContainer mongoClientContainer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public TimeoutCanceller(MongoClientContainer mongoClientContainer)
|
||||||
|
{
|
||||||
|
this.mongoClientContainer = mongoClientContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mongoClientContainer.closeIfNeeded();
|
||||||
|
LOG.info("Cancelled timed out query");
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Error trying to cancel statement after timeout", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw (new QRuntimeException("Statement timed out and was cancelled."));
|
||||||
|
}
|
||||||
|
}
|
@ -58,6 +58,8 @@ public class BaseTest
|
|||||||
@BeforeAll
|
@BeforeAll
|
||||||
static void beforeAll()
|
static void beforeAll()
|
||||||
{
|
{
|
||||||
|
System.setProperty("qqq.mongodb.logQueries", "true");
|
||||||
|
|
||||||
mongoDBContainer = new GenericContainer<>(DockerImageName.parse(MONGO_IMAGE))
|
mongoDBContainer = new GenericContainer<>(DockerImageName.parse(MONGO_IMAGE))
|
||||||
.withEnv("MONGO_INITDB_ROOT_USERNAME", TestUtils.MONGO_USERNAME)
|
.withEnv("MONGO_INITDB_ROOT_USERNAME", TestUtils.MONGO_USERNAME)
|
||||||
.withEnv("MONGO_INITDB_ROOT_PASSWORD", TestUtils.MONGO_PASSWORD)
|
.withEnv("MONGO_INITDB_ROOT_PASSWORD", TestUtils.MONGO_PASSWORD)
|
||||||
@ -92,6 +94,18 @@ public class BaseTest
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@AfterEach
|
@AfterEach
|
||||||
void baseAfterEach()
|
void baseAfterEach()
|
||||||
|
{
|
||||||
|
clearDatabase();
|
||||||
|
|
||||||
|
QContext.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
protected static void clearDatabase()
|
||||||
{
|
{
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
// clear test database between tests //
|
// clear test database between tests //
|
||||||
@ -99,8 +113,6 @@ public class BaseTest
|
|||||||
MongoClient mongoClient = getMongoClient();
|
MongoClient mongoClient = getMongoClient();
|
||||||
MongoDatabase database = mongoClient.getDatabase(TestUtils.MONGO_DATABASE);
|
MongoDatabase database = mongoClient.getDatabase(TestUtils.MONGO_DATABASE);
|
||||||
database.drop();
|
database.drop();
|
||||||
|
|
||||||
QContext.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,11 +22,22 @@
|
|||||||
package com.kingsrook.qqq.backend.module.mongodb;
|
package com.kingsrook.qqq.backend.module.mongodb;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
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.QInstance;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
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.QFieldMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PVSValueFormatAndFields;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||||
|
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.tables.Association;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
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.MongoDBBackendMetaData;
|
||||||
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBTableBackendDetails;
|
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBTableBackendDetails;
|
||||||
@ -34,6 +45,8 @@ import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBTableBacke
|
|||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Test Utils class for this module
|
** Test Utils class for this module
|
||||||
|
**
|
||||||
|
** Note - tons of copying from RDMBS... wouldn't it be nice to share??
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class TestUtils
|
public class TestUtils
|
||||||
{
|
{
|
||||||
@ -41,6 +54,15 @@ public class TestUtils
|
|||||||
|
|
||||||
public static final String TABLE_NAME_PERSON = "personTable";
|
public static final String TABLE_NAME_PERSON = "personTable";
|
||||||
|
|
||||||
|
public static final String TABLE_NAME_STORE = "store";
|
||||||
|
public static final String TABLE_NAME_ORDER = "order";
|
||||||
|
public static final String TABLE_NAME_ORDER_INSTRUCTIONS = "orderInstructions";
|
||||||
|
public static final String TABLE_NAME_ITEM = "item";
|
||||||
|
public static final String TABLE_NAME_ORDER_LINE = "orderLine";
|
||||||
|
public static final String TABLE_NAME_LINE_ITEM_EXTRINSIC = "orderLineExtrinsic";
|
||||||
|
public static final String TABLE_NAME_WAREHOUSE = "warehouse";
|
||||||
|
public static final String TABLE_NAME_WAREHOUSE_STORE_INT = "warehouseStoreInt";
|
||||||
|
|
||||||
public static final String SECURITY_KEY_STORE_ALL_ACCESS = "storeAllAccess";
|
public static final String SECURITY_KEY_STORE_ALL_ACCESS = "storeAllAccess";
|
||||||
|
|
||||||
public static final String MONGO_USERNAME = "mongoUser";
|
public static final String MONGO_USERNAME = "mongoUser";
|
||||||
@ -48,33 +70,6 @@ public class TestUtils
|
|||||||
public static final Integer MONGO_PORT = 27017;
|
public static final Integer MONGO_PORT = 27017;
|
||||||
public static final String MONGO_DATABASE = "testDatabase";
|
public static final String MONGO_DATABASE = "testDatabase";
|
||||||
|
|
||||||
public static final String TEST_COLLECTION = "testTable";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
|
||||||
**
|
|
||||||
*******************************************************************************/
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -85,6 +80,8 @@ public class TestUtils
|
|||||||
QInstance qInstance = new QInstance();
|
QInstance qInstance = new QInstance();
|
||||||
qInstance.addBackend(defineBackend());
|
qInstance.addBackend(defineBackend());
|
||||||
qInstance.addTable(defineTablePerson());
|
qInstance.addTable(defineTablePerson());
|
||||||
|
qInstance.addPossibleValueSource(definePvsPerson());
|
||||||
|
addOmsTablesAndJoins(qInstance);
|
||||||
qInstance.setAuthentication(defineAuthentication());
|
qInstance.setAuthentication(defineAuthentication());
|
||||||
return (qInstance);
|
return (qInstance);
|
||||||
}
|
}
|
||||||
@ -116,7 +113,8 @@ public class TestUtils
|
|||||||
.withUsername(TestUtils.MONGO_USERNAME)
|
.withUsername(TestUtils.MONGO_USERNAME)
|
||||||
.withPassword(TestUtils.MONGO_PASSWORD)
|
.withPassword(TestUtils.MONGO_PASSWORD)
|
||||||
.withAuthSourceDatabase("admin")
|
.withAuthSourceDatabase("admin")
|
||||||
.withDatabaseName(TestUtils.MONGO_DATABASE));
|
.withDatabaseName(TestUtils.MONGO_DATABASE)
|
||||||
|
.withTransactionsSupported(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -136,6 +134,7 @@ public class TestUtils
|
|||||||
.withField(new QFieldMetaData("id", QFieldType.STRING).withBackendName("_id"))
|
.withField(new QFieldMetaData("id", QFieldType.STRING).withBackendName("_id"))
|
||||||
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("metaData.createDate"))
|
.withField(new QFieldMetaData("createDate", QFieldType.DATE_TIME).withBackendName("metaData.createDate"))
|
||||||
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("metaData.modifyDate"))
|
.withField(new QFieldMetaData("modifyDate", QFieldType.DATE_TIME).withBackendName("metaData.modifyDate"))
|
||||||
|
.withField(new QFieldMetaData("seqNo", QFieldType.INTEGER))
|
||||||
.withField(new QFieldMetaData("firstName", QFieldType.STRING))
|
.withField(new QFieldMetaData("firstName", QFieldType.STRING))
|
||||||
.withField(new QFieldMetaData("lastName", QFieldType.STRING))
|
.withField(new QFieldMetaData("lastName", QFieldType.STRING))
|
||||||
.withField(new QFieldMetaData("birthDate", QFieldType.DATE))
|
.withField(new QFieldMetaData("birthDate", QFieldType.DATE))
|
||||||
@ -145,7 +144,210 @@ public class TestUtils
|
|||||||
.withField(new QFieldMetaData("daysWorked", QFieldType.INTEGER))
|
.withField(new QFieldMetaData("daysWorked", QFieldType.INTEGER))
|
||||||
.withField(new QFieldMetaData("homeTown", QFieldType.STRING))
|
.withField(new QFieldMetaData("homeTown", QFieldType.STRING))
|
||||||
.withBackendDetails(new MongoDBTableBackendDetails()
|
.withBackendDetails(new MongoDBTableBackendDetails()
|
||||||
.withTableName(TEST_COLLECTION));
|
.withTableName(TABLE_NAME_PERSON));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QPossibleValueSource definePvsPerson()
|
||||||
|
{
|
||||||
|
return (new QPossibleValueSource()
|
||||||
|
.withName(TABLE_NAME_PERSON)
|
||||||
|
.withType(QPossibleValueSourceType.TABLE)
|
||||||
|
.withTableName(TABLE_NAME_PERSON)
|
||||||
|
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static void addOmsTablesAndJoins(QInstance qInstance)
|
||||||
|
{
|
||||||
|
qInstance.addTable(defineBaseTable(TABLE_NAME_STORE, "store")
|
||||||
|
.withRecordLabelFormat("%s")
|
||||||
|
.withRecordLabelFields("name")
|
||||||
|
.withRecordSecurityLock(new RecordSecurityLock().withSecurityKeyType(TABLE_NAME_STORE).withFieldName("key"))
|
||||||
|
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER, "order")
|
||||||
|
.withRecordSecurityLock(new RecordSecurityLock().withSecurityKeyType(TABLE_NAME_STORE).withFieldName("storeKey"))
|
||||||
|
.withAssociation(new Association().withName("orderLine").withAssociatedTableName(TABLE_NAME_ORDER_LINE).withJoinName("orderJoinOrderLine"))
|
||||||
|
.withExposedJoin(new ExposedJoin().withJoinTable(TABLE_NAME_ITEM).withJoinPath(List.of("orderJoinOrderLine", "orderLineJoinItem")))
|
||||||
|
.withField(new QFieldMetaData("storeKey", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_STORE))
|
||||||
|
.withField(new QFieldMetaData("billToPersonId", QFieldType.STRING).withPossibleValueSourceName(TABLE_NAME_PERSON))
|
||||||
|
.withField(new QFieldMetaData("shipToPersonId", QFieldType.STRING).withPossibleValueSourceName(TABLE_NAME_PERSON))
|
||||||
|
.withField(new QFieldMetaData("currentOrderInstructionsId", QFieldType.STRING).withPossibleValueSourceName(TABLE_NAME_PERSON))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER_INSTRUCTIONS, "order_instructions")
|
||||||
|
.withRecordSecurityLock(new RecordSecurityLock()
|
||||||
|
.withSecurityKeyType(TABLE_NAME_STORE)
|
||||||
|
.withFieldName("order.storeKey")
|
||||||
|
.withJoinNameChain(List.of("orderInstructionsJoinOrder")))
|
||||||
|
.withField(new QFieldMetaData("orderId", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("instructions", QFieldType.STRING))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addTable(defineBaseTable(TABLE_NAME_ITEM, "item")
|
||||||
|
.withRecordSecurityLock(new RecordSecurityLock().withSecurityKeyType(TABLE_NAME_STORE).withFieldName("storeKey"))
|
||||||
|
.withExposedJoin(new ExposedJoin().withJoinTable(TABLE_NAME_ORDER).withJoinPath(List.of("orderLineJoinItem", "orderJoinOrderLine")))
|
||||||
|
.withField(new QFieldMetaData("sku", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("description", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("storeKey", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_STORE))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addTable(defineBaseTable(TABLE_NAME_ORDER_LINE, "order_line")
|
||||||
|
.withRecordSecurityLock(new RecordSecurityLock()
|
||||||
|
.withSecurityKeyType(TABLE_NAME_STORE)
|
||||||
|
.withFieldName("order.storeKey")
|
||||||
|
.withJoinNameChain(List.of("orderJoinOrderLine")))
|
||||||
|
.withAssociation(new Association().withName("extrinsics").withAssociatedTableName(TABLE_NAME_LINE_ITEM_EXTRINSIC).withJoinName("orderLineJoinLineItemExtrinsic"))
|
||||||
|
.withField(new QFieldMetaData("orderId", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("sku", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("storeKey", QFieldType.INTEGER).withPossibleValueSourceName(TABLE_NAME_STORE))
|
||||||
|
.withField(new QFieldMetaData("quantity", QFieldType.INTEGER))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addTable(defineBaseTable(TABLE_NAME_LINE_ITEM_EXTRINSIC, "line_item_extrinsic")
|
||||||
|
.withRecordSecurityLock(new RecordSecurityLock()
|
||||||
|
.withSecurityKeyType(TABLE_NAME_STORE)
|
||||||
|
.withFieldName("order.storeKey")
|
||||||
|
.withJoinNameChain(List.of("orderJoinOrderLine", "orderLineJoinLineItemExtrinsic")))
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
|
||||||
|
.withField(new QFieldMetaData("orderLineId", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("key", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("value", QFieldType.STRING))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addTable(defineBaseTable(TABLE_NAME_WAREHOUSE_STORE_INT, "warehouse_store_int")
|
||||||
|
.withField(new QFieldMetaData("warehouseId", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("storeKey", QFieldType.INTEGER))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addTable(defineBaseTable(TABLE_NAME_WAREHOUSE, "warehouse")
|
||||||
|
.withRecordSecurityLock(new RecordSecurityLock()
|
||||||
|
.withSecurityKeyType(TABLE_NAME_STORE)
|
||||||
|
.withFieldName(TABLE_NAME_WAREHOUSE_STORE_INT + ".storeKey")
|
||||||
|
.withJoinNameChain(List.of(QJoinMetaData.makeInferredJoinName(TestUtils.TABLE_NAME_WAREHOUSE, TestUtils.TABLE_NAME_WAREHOUSE_STORE_INT)))
|
||||||
|
)
|
||||||
|
.withField(new QFieldMetaData("name", QFieldType.STRING))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
|
.withLeftTable(TestUtils.TABLE_NAME_WAREHOUSE)
|
||||||
|
.withRightTable(TestUtils.TABLE_NAME_WAREHOUSE_STORE_INT)
|
||||||
|
.withInferredName()
|
||||||
|
.withJoinOn(new JoinOn("id", "warehouseId"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderJoinStore")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_STORE)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("storeKey", "key"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderJoinBillToPerson")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_PERSON)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("billToPersonId", "id"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderJoinShipToPerson")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_PERSON)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("shipToPersonId", "id"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("itemJoinStore")
|
||||||
|
.withLeftTable(TABLE_NAME_ITEM)
|
||||||
|
.withRightTable(TABLE_NAME_STORE)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("storeKey", "key"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderJoinOrderLine")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_ORDER_LINE)
|
||||||
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
|
.withJoinOn(new JoinOn("id", "orderId"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderLineJoinItem")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER_LINE)
|
||||||
|
.withRightTable(TABLE_NAME_ITEM)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("sku", "sku"))
|
||||||
|
.withJoinOn(new JoinOn("storeKey", "storeKey"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderLineJoinLineItemExtrinsic")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER_LINE)
|
||||||
|
.withRightTable(TABLE_NAME_LINE_ITEM_EXTRINSIC)
|
||||||
|
.withType(JoinType.ONE_TO_MANY)
|
||||||
|
.withJoinOn(new JoinOn("id", "orderLineId"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderJoinCurrentOrderInstructions")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER)
|
||||||
|
.withRightTable(TABLE_NAME_ORDER_INSTRUCTIONS)
|
||||||
|
.withType(JoinType.ONE_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("currentOrderInstructionsId", "id"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addJoin(new QJoinMetaData()
|
||||||
|
.withName("orderInstructionsJoinOrder")
|
||||||
|
.withLeftTable(TABLE_NAME_ORDER_INSTRUCTIONS)
|
||||||
|
.withRightTable(TABLE_NAME_ORDER)
|
||||||
|
.withType(JoinType.MANY_TO_ONE)
|
||||||
|
.withJoinOn(new JoinOn("orderId", "id"))
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addPossibleValueSource(new QPossibleValueSource()
|
||||||
|
.withName("store")
|
||||||
|
.withType(QPossibleValueSourceType.TABLE)
|
||||||
|
.withTableName(TABLE_NAME_STORE)
|
||||||
|
.withValueFormatAndFields(PVSValueFormatAndFields.LABEL_ONLY)
|
||||||
|
);
|
||||||
|
|
||||||
|
qInstance.addSecurityKeyType(new QSecurityKeyType()
|
||||||
|
.withName(TABLE_NAME_STORE)
|
||||||
|
.withAllAccessKeyName(SECURITY_KEY_STORE_ALL_ACCESS)
|
||||||
|
.withPossibleValueSourceName(TABLE_NAME_STORE));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static QTableMetaData defineBaseTable(String tableName, String backendTableName)
|
||||||
|
{
|
||||||
|
return new QTableMetaData()
|
||||||
|
.withName(tableName)
|
||||||
|
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||||
|
.withBackendDetails(new MongoDBTableBackendDetails().withTableName(backendTableName))
|
||||||
|
.withPrimaryKeyField("id")
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.STRING))
|
||||||
|
.withField(new QFieldMetaData("key", QFieldType.INTEGER));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ class MongoDBCountActionTest extends BaseTest
|
|||||||
// directly insert some mongo records //
|
// directly insert some mongo records //
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
MongoDatabase database = getMongoClient().getDatabase(TestUtils.MONGO_DATABASE);
|
MongoDatabase database = getMongoClient().getDatabase(TestUtils.MONGO_DATABASE);
|
||||||
MongoCollection<Document> collection = database.getCollection(TestUtils.TEST_COLLECTION);
|
MongoCollection<Document> collection = database.getCollection(TestUtils.TABLE_NAME_PERSON);
|
||||||
collection.insertMany(List.of(
|
collection.insertMany(List.of(
|
||||||
Document.parse("""
|
Document.parse("""
|
||||||
{"firstName": "Darin", "lastName": "Kelkhoff"}"""),
|
{"firstName": "Darin", "lastName": "Kelkhoff"}"""),
|
||||||
|
@ -57,7 +57,7 @@ class MongoDBDeleteActionTest extends BaseTest
|
|||||||
// directly insert some mongo records //
|
// directly insert some mongo records //
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
MongoDatabase database = getMongoClient().getDatabase(TestUtils.MONGO_DATABASE);
|
MongoDatabase database = getMongoClient().getDatabase(TestUtils.MONGO_DATABASE);
|
||||||
MongoCollection<Document> collection = database.getCollection(TestUtils.TEST_COLLECTION);
|
MongoCollection<Document> collection = database.getCollection(TestUtils.TABLE_NAME_PERSON);
|
||||||
collection.insertMany(List.of(
|
collection.insertMany(List.of(
|
||||||
Document.parse("""
|
Document.parse("""
|
||||||
{"firstName": "Darin", "lastName": "Kelkhoff"}"""),
|
{"firstName": "Darin", "lastName": "Kelkhoff"}"""),
|
||||||
|
@ -78,7 +78,7 @@ class MongoDBInsertActionTest extends BaseTest
|
|||||||
// directly query mongo for the inserted records //
|
// directly query mongo for the inserted records //
|
||||||
///////////////////////////////////////////////////
|
///////////////////////////////////////////////////
|
||||||
MongoDatabase database = getMongoClient().getDatabase(TestUtils.MONGO_DATABASE);
|
MongoDatabase database = getMongoClient().getDatabase(TestUtils.MONGO_DATABASE);
|
||||||
MongoCollection<Document> collection = database.getCollection(TestUtils.TEST_COLLECTION);
|
MongoCollection<Document> collection = database.getCollection(TestUtils.TABLE_NAME_PERSON);
|
||||||
assertEquals(3, collection.countDocuments());
|
assertEquals(3, collection.countDocuments());
|
||||||
for(Document document : collection.find())
|
for(Document document : collection.find())
|
||||||
{
|
{
|
||||||
|
@ -23,18 +23,33 @@ package com.kingsrook.qqq.backend.module.mongodb.actions;
|
|||||||
|
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
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.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.actions.tables.query.QueryInput;
|
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.actions.tables.query.QueryOutput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.Now;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.NowWithOffset;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.module.mongodb.BaseTest;
|
import com.kingsrook.qqq.backend.module.mongodb.BaseTest;
|
||||||
import com.kingsrook.qqq.backend.module.mongodb.TestUtils;
|
import com.kingsrook.qqq.backend.module.mongodb.TestUtils;
|
||||||
import com.mongodb.client.MongoCollection;
|
import com.mongodb.client.MongoCollection;
|
||||||
import com.mongodb.client.MongoDatabase;
|
import com.mongodb.client.MongoDatabase;
|
||||||
import org.bson.Document;
|
import org.bson.Document;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@ -51,9 +66,60 @@ class MongoDBQueryActionTest extends BaseTest
|
|||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void beforeEach()
|
void beforeEach() throws QException
|
||||||
{
|
{
|
||||||
|
primeTestDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
protected void primeTestDatabase() throws QException
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
insertInput.setRecords(List.of(
|
||||||
|
new QRecord().withValue("seqNo", 1).withValue("firstName", "Darin").withValue("lastName", "Kelkhoff").withValue("birthDate", LocalDate.parse("1980-05-31")).withValue("email", "darin.kelkhoff@gmail.com").withValue("isEmployed", true).withValue("annualSalary", 25000).withValue("daysWorked", 27).withValue("homeTown", "Chester"),
|
||||||
|
new QRecord().withValue("seqNo", 2).withValue("firstName", "James").withValue("lastName", "Maes").withValue("birthDate", LocalDate.parse("1980-05-15")).withValue("email", "jmaes@mmltholdings.com").withValue("isEmployed", true).withValue("annualSalary", 26000).withValue("daysWorked", 124).withValue("homeTown", "Chester"),
|
||||||
|
new QRecord().withValue("seqNo", 3).withValue("firstName", "Tim").withValue("lastName", "Chamberlain").withValue("birthDate", LocalDate.parse("1976-05-28")).withValue("email", "tchamberlain@mmltholdings.com").withValue("isEmployed", false).withValue("annualSalary", null).withValue("daysWorked", 0).withValue("homeTown", "Decatur"),
|
||||||
|
new QRecord().withValue("seqNo", 4).withValue("firstName", "Tyler").withValue("lastName", "Samples").withValue("birthDate", null).withValue("email", "tsamples@mmltholdings.com").withValue("isEmployed", true).withValue("annualSalary", 30000).withValue("daysWorked", 99).withValue("homeTown", "Texas"),
|
||||||
|
new QRecord().withValue("seqNo", 5).withValue("firstName", "Garret").withValue("lastName", "Richardson").withValue("birthDate", LocalDate.parse("1981-01-01")).withValue("email", "grichardson@mmltholdings.com").withValue("isEmployed", true).withValue("annualSalary", 1000000).withValue("daysWorked", 232).withValue("homeTown", null)
|
||||||
|
));
|
||||||
|
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||||
|
|
||||||
|
MongoDatabase database = getMongoClient().getDatabase(TestUtils.MONGO_DATABASE);
|
||||||
|
|
||||||
|
MongoCollection<Document> storeCollection = database.getCollection(TestUtils.TABLE_NAME_STORE);
|
||||||
|
storeCollection.insertMany(List.of(
|
||||||
|
Document.parse("""
|
||||||
|
{"key":1, "name": "Q-Mart"}"""),
|
||||||
|
Document.parse("""
|
||||||
|
{"key":2, "name": "QQQ 'R' Us"}"""),
|
||||||
|
Document.parse("""
|
||||||
|
{"key":3, "name": "QDepot"}""")
|
||||||
|
));
|
||||||
|
|
||||||
|
MongoCollection<Document> orderCollection = database.getCollection(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
orderCollection.insertMany(List.of(
|
||||||
|
Document.parse("""
|
||||||
|
{"key": 1, "storeKey":1, "billToPersonId": 1, "shipToPersonId": 1}}"""),
|
||||||
|
Document.parse("""
|
||||||
|
{"key": 2, "storeKey":1, "billToPersonId": 1, "shipToPersonId": 2}}"""),
|
||||||
|
Document.parse("""
|
||||||
|
{"key": 3, "storeKey":1, "billToPersonId": 2, "shipToPersonId": 3}}"""),
|
||||||
|
Document.parse("""
|
||||||
|
{"key": 4, "storeKey":2, "billToPersonId": 4, "shipToPersonId": 5}}"""),
|
||||||
|
Document.parse("""
|
||||||
|
{"key": 5, "storeKey":2, "billToPersonId": 5, "shipToPersonId": 4}}"""),
|
||||||
|
Document.parse("""
|
||||||
|
{"key": 6, "storeKey":3, "billToPersonId": 5, "shipToPersonId": null}}"""),
|
||||||
|
Document.parse("""
|
||||||
|
{"key": 7, "storeKey":3, "billToPersonId": null, "shipToPersonId": 5}"""),
|
||||||
|
Document.parse("""
|
||||||
|
{"key": 8, "storeKey":3, "billToPersonId": null, "shipToPersonId": 5}""")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -64,11 +130,16 @@ class MongoDBQueryActionTest extends BaseTest
|
|||||||
@Test
|
@Test
|
||||||
void test() throws QException
|
void test() throws QException
|
||||||
{
|
{
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
// let's not use the primed-database rows for this test //
|
||||||
|
//////////////////////////////////////////////////////////
|
||||||
|
clearDatabase();
|
||||||
|
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
// directly insert some mongo records //
|
// directly insert some mongo records //
|
||||||
////////////////////////////////////////
|
////////////////////////////////////////
|
||||||
MongoDatabase database = getMongoClient().getDatabase(TestUtils.MONGO_DATABASE);
|
MongoDatabase database = getMongoClient().getDatabase(TestUtils.MONGO_DATABASE);
|
||||||
MongoCollection<Document> collection = database.getCollection(TestUtils.TEST_COLLECTION);
|
MongoCollection<Document> collection = database.getCollection(TestUtils.TABLE_NAME_PERSON);
|
||||||
collection.insertMany(List.of(
|
collection.insertMany(List.of(
|
||||||
Document.parse("""
|
Document.parse("""
|
||||||
{ "metaData": {"createDate": "2023-01-09T01:01:01.123Z", "modifyDate": "2023-01-09T02:02:02.123Z", "oops": "All Crunchberries"},
|
{ "metaData": {"createDate": "2023-01-09T01:01:01.123Z", "modifyDate": "2023-01-09T02:02:02.123Z", "oops": "All Crunchberries"},
|
||||||
@ -116,4 +187,784 @@ class MongoDBQueryActionTest extends BaseTest
|
|||||||
assertEquals("Sample", record.getValueString("lastName"));
|
assertEquals("Sample", record.getValueString("lastName"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private QueryInput initQueryRequest()
|
||||||
|
{
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
return queryInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testUnfilteredQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(5, queryOutput.getRecords().size(), "Unfiltered query should find all rows");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testEqualsQuery() throws QException
|
||||||
|
{
|
||||||
|
String email = "darin.kelkhoff@gmail.com";
|
||||||
|
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("email")
|
||||||
|
.withOperator(QCriteriaOperator.EQUALS)
|
||||||
|
.withValues(List.of(email)))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
assertEquals(email, queryOutput.getRecords().get(0).getValueString("email"), "Should find expected email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testNotEqualsQuery() throws QException
|
||||||
|
{
|
||||||
|
String email = "darin.kelkhoff@gmail.com";
|
||||||
|
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("email")
|
||||||
|
.withOperator(QCriteriaOperator.NOT_EQUALS)
|
||||||
|
.withValues(List.of(email)))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").equals(email)), "Should NOT find expected email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testNotEqualsOrIsNullQuery() throws QException
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// 5 rows, 1 has a null salary, 1 has 1,000,000. //
|
||||||
|
// first confirm that query for != returns 3 (the null does NOT come back) //
|
||||||
|
// then, confirm that != or is null gives the (more humanly expected) 4. //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("annualSalary")
|
||||||
|
.withOperator(QCriteriaOperator.NOT_EQUALS)
|
||||||
|
.withValues(List.of(1_000_000))));
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(3, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
|
||||||
|
queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("annualSalary")
|
||||||
|
.withOperator(QCriteriaOperator.NOT_EQUALS_OR_IS_NULL)
|
||||||
|
.withValues(List.of(1_000_000))));
|
||||||
|
queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> Objects.equals(1_000_000, r.getValueInteger("annualSalary"))), "Should NOT find expected salary");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testInQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("seqNo")
|
||||||
|
.withOperator(QCriteriaOperator.IN)
|
||||||
|
.withValues(List.of(2, 4)))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("seqNo").equals(2) || r.getValueInteger("seqNo").equals(4)), "Should find expected ids");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testNotInQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("seqNo")
|
||||||
|
.withOperator(QCriteriaOperator.NOT_IN)
|
||||||
|
.withValues(List.of(2, 3, 4)))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("seqNo").equals(1) || r.getValueInteger("seqNo").equals(5)), "Should find expected ids");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testStartsWith() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("email")
|
||||||
|
.withOperator(QCriteriaOperator.STARTS_WITH)
|
||||||
|
.withValues(List.of("darin")))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches("darin.*")), "Should find matching email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testContains() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("email")
|
||||||
|
.withOperator(QCriteriaOperator.CONTAINS)
|
||||||
|
.withValues(List.of("kelkhoff")))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testLike() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("email")
|
||||||
|
.withOperator(QCriteriaOperator.LIKE)
|
||||||
|
.withValues(List.of("%kelk%")))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testNotLike() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("email")
|
||||||
|
.withOperator(QCriteriaOperator.NOT_LIKE)
|
||||||
|
.withValues(List.of("%kelk%")))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testEndsWith() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("email")
|
||||||
|
.withOperator(QCriteriaOperator.ENDS_WITH)
|
||||||
|
.withValues(List.of("gmail.com")))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueString("email").matches(".*gmail.com")), "Should find matching email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testNotStartsWith() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("email")
|
||||||
|
.withOperator(QCriteriaOperator.NOT_STARTS_WITH)
|
||||||
|
.withValues(List.of("darin")))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches("darin.*")), "Should find matching email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testNotContains() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("email")
|
||||||
|
.withOperator(QCriteriaOperator.NOT_CONTAINS)
|
||||||
|
.withValues(List.of("kelkhoff")))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*kelkhoff.*")), "Should find matching email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testNotEndsWith() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("email")
|
||||||
|
.withOperator(QCriteriaOperator.NOT_ENDS_WITH)
|
||||||
|
.withValues(List.of("gmail.com")))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(4, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().noneMatch(r -> r.getValueString("email").matches(".*gmail.com")), "Should find matching email address");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testLessThanQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("seqNo")
|
||||||
|
.withOperator(QCriteriaOperator.LESS_THAN)
|
||||||
|
.withValues(List.of(3)))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("seqNo").equals(1) || r.getValueInteger("seqNo").equals(2)), "Should find expected ids");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testLessThanOrEqualsQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("seqNo")
|
||||||
|
.withOperator(QCriteriaOperator.LESS_THAN_OR_EQUALS)
|
||||||
|
.withValues(List.of(2)))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("seqNo").equals(1) || r.getValueInteger("seqNo").equals(2)), "Should find expected ids");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testGreaterThanQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("seqNo")
|
||||||
|
.withOperator(QCriteriaOperator.GREATER_THAN)
|
||||||
|
.withValues(List.of(3)))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("seqNo").equals(4) || r.getValueInteger("seqNo").equals(5)), "Should find expected ids");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testGreaterThanOrEqualsQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("seqNo")
|
||||||
|
.withOperator(QCriteriaOperator.GREATER_THAN_OR_EQUALS)
|
||||||
|
.withValues(List.of(4)))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("seqNo").equals(4) || r.getValueInteger("seqNo").equals(5)), "Should find expected ids");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testIsBlankQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("birthDate")
|
||||||
|
.withOperator(QCriteriaOperator.IS_BLANK)
|
||||||
|
));
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("birthDate") == null), "Should find expected row");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testIsNotBlankQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("firstName")
|
||||||
|
.withOperator(QCriteriaOperator.IS_NOT_BLANK)
|
||||||
|
));
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(5, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValue("firstName") != null), "Should find expected rows");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testBetweenQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("seqNo")
|
||||||
|
.withOperator(QCriteriaOperator.BETWEEN)
|
||||||
|
.withValues(List.of(2, 4))
|
||||||
|
));
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(3, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("seqNo").equals(2) || r.getValueInteger("seqNo").equals(3) || r.getValueInteger("seqNo").equals(4)), "Should find expected ids");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
* [
|
||||||
|
* And Filter
|
||||||
|
* {
|
||||||
|
* filters=
|
||||||
|
* [
|
||||||
|
* Not Filter
|
||||||
|
* {
|
||||||
|
* filter=And Filter
|
||||||
|
* {
|
||||||
|
* filters=
|
||||||
|
* [
|
||||||
|
* Operator Filter
|
||||||
|
* {
|
||||||
|
* fieldName='seqNo', operator='$gte', value=2
|
||||||
|
* },
|
||||||
|
* Operator Filter
|
||||||
|
* {
|
||||||
|
* fieldName='seqNo', operator='$lte', value=4
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testNotBetweenQuery() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria()
|
||||||
|
.withFieldName("seqNo")
|
||||||
|
.withOperator(QCriteriaOperator.NOT_BETWEEN)
|
||||||
|
.withValues(List.of(2, 4))
|
||||||
|
));
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().allMatch(r -> r.getValueInteger("seqNo").equals(1) || r.getValueInteger("seqNo").equals(5)), "Should find expected ids");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
public void testFilterExpressions() throws QException
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
insertInput.setRecords(List.of(
|
||||||
|
new QRecord().withValue("email", "-").withValue("firstName", "past").withValue("lastName", "ExpressionTest").withValue("birthDate", Instant.now().minus(3, ChronoUnit.DAYS)),
|
||||||
|
new QRecord().withValue("email", "-").withValue("firstName", "future").withValue("lastName", "ExpressionTest").withValue("birthDate", Instant.now().plus(3, ChronoUnit.DAYS))
|
||||||
|
));
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("lastName").withOperator(QCriteriaOperator.EQUALS).withValues(List.of("ExpressionTest")))
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("birthDate").withOperator(QCriteriaOperator.LESS_THAN).withValues(List.of(new Now()))));
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("past")), "Should find expected row");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("lastName").withOperator(QCriteriaOperator.EQUALS).withValues(List.of("ExpressionTest")))
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("birthDate").withOperator(QCriteriaOperator.LESS_THAN).withValues(List.of(NowWithOffset.plus(2, ChronoUnit.DAYS)))));
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("past")), "Should find expected row");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("lastName").withOperator(QCriteriaOperator.EQUALS).withValues(List.of("ExpressionTest")))
|
||||||
|
.withCriteria(new QFilterCriteria().withFieldName("birthDate").withOperator(QCriteriaOperator.GREATER_THAN).withValues(List.of(NowWithOffset.minus(5, ChronoUnit.DAYS)))));
|
||||||
|
QueryOutput queryOutput = new MongoDBQueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size(), "Expected # of rows");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("past")), "Should find expected row");
|
||||||
|
Assertions.assertTrue(queryOutput.getRecords().stream().anyMatch(r -> r.getValue("firstName").equals("future")), "Should find expected row");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testEmptyInList() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.IN, List.of())));
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(0, queryOutput.getRecords().size(), "IN empty list should find nothing.");
|
||||||
|
|
||||||
|
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.NOT_IN, List.of())));
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(5, queryOutput.getRecords().size(), "NOT_IN empty list should find everything.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testOr() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
|
||||||
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Darin")))
|
||||||
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Tim")))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size(), "OR should find 2 rows");
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testNestedFilterAndOrOr() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
|
||||||
|
.withSubFilters(List.of(
|
||||||
|
new QQueryFilter()
|
||||||
|
.withBooleanOperator(QQueryFilter.BooleanOperator.AND)
|
||||||
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("James")))
|
||||||
|
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, List.of("Maes"))),
|
||||||
|
new QQueryFilter()
|
||||||
|
.withBooleanOperator(QQueryFilter.BooleanOperator.AND)
|
||||||
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Darin")))
|
||||||
|
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, List.of("Kelkhoff")))
|
||||||
|
))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(2, queryOutput.getRecords().size(), "Complex query should find 2 rows");
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("James") && r.getValueString("lastName").equals("Maes"));
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Darin") && r.getValueString("lastName").equals("Kelkhoff"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testNestedFilterOrAndAnd() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withBooleanOperator(QQueryFilter.BooleanOperator.AND)
|
||||||
|
.withSubFilters(List.of(
|
||||||
|
new QQueryFilter()
|
||||||
|
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
|
||||||
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("James")))
|
||||||
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Tim"))),
|
||||||
|
new QQueryFilter()
|
||||||
|
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
|
||||||
|
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, List.of("Kelkhoff")))
|
||||||
|
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, List.of("Chamberlain")))
|
||||||
|
))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "Complex query should find 1 row");
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueString("firstName").equals("Tim") && r.getValueString("lastName").equals("Chamberlain"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testNestedFilterAndTopLevelFilter() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = initQueryRequest();
|
||||||
|
queryInput.setFilter(new QQueryFilter()
|
||||||
|
.withCriteria(new QFilterCriteria("seqNo", QCriteriaOperator.EQUALS, 3))
|
||||||
|
.withBooleanOperator(QQueryFilter.BooleanOperator.AND)
|
||||||
|
.withSubFilters(List.of(
|
||||||
|
new QQueryFilter()
|
||||||
|
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
|
||||||
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("James")))
|
||||||
|
.withCriteria(new QFilterCriteria("firstName", QCriteriaOperator.EQUALS, List.of("Tim"))),
|
||||||
|
new QQueryFilter()
|
||||||
|
.withBooleanOperator(QQueryFilter.BooleanOperator.OR)
|
||||||
|
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, List.of("Kelkhoff")))
|
||||||
|
.withCriteria(new QFilterCriteria("lastName", QCriteriaOperator.EQUALS, List.of("Chamberlain")))
|
||||||
|
))
|
||||||
|
);
|
||||||
|
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(1, queryOutput.getRecords().size(), "Complex query should find 1 row");
|
||||||
|
assertThat(queryOutput.getRecords()).anyMatch(r -> r.getValueInteger("seqNo").equals(3) && r.getValueString("firstName").equals("Tim") && r.getValueString("lastName").equals("Chamberlain"));
|
||||||
|
|
||||||
|
queryInput.getFilter().setCriteria(List.of(new QFilterCriteria("seqNo", QCriteriaOperator.NOT_EQUALS, 3)));
|
||||||
|
queryOutput = new QueryAction().execute(queryInput);
|
||||||
|
assertEquals(0, queryOutput.getRecords().size(), "Next complex query should find 0 rows");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** queries on the store table, where the primary key (id) is the security field
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testRecordSecurityPrimaryKeyFieldNoFilters() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_STORE);
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_STORE_ALL_ACCESS, true));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).hasSize(3);
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 1));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords())
|
||||||
|
.hasSize(1)
|
||||||
|
.anyMatch(r -> r.getValueInteger("key").equals(1));
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 2));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords())
|
||||||
|
.hasSize(1)
|
||||||
|
.anyMatch(r -> r.getValueInteger("key").equals(2));
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 5));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).isEmpty();
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession());
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).isEmpty();
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValues(TestUtils.TABLE_NAME_STORE, null));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).isEmpty();
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValues(TestUtils.TABLE_NAME_STORE, Collections.emptyList()));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).isEmpty();
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValues(TestUtils.TABLE_NAME_STORE, List.of(1, 3)));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords())
|
||||||
|
.hasSize(2)
|
||||||
|
.anyMatch(r -> r.getValueInteger("key").equals(1))
|
||||||
|
.anyMatch(r -> r.getValueInteger("key").equals(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** not really expected to be any different from where we filter on the primary key,
|
||||||
|
** but just good to make sure
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testRecordSecurityForeignKeyFieldNoFilters() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_STORE_ALL_ACCESS, true));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).hasSize(8);
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 1));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords())
|
||||||
|
.hasSize(3)
|
||||||
|
.allMatch(r -> r.getValueInteger("storeKey").equals(1));
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 2));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords())
|
||||||
|
.hasSize(2)
|
||||||
|
.allMatch(r -> r.getValueInteger("storeKey").equals(2));
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 5));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).isEmpty();
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession());
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).isEmpty();
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValues(TestUtils.TABLE_NAME_STORE, null));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).isEmpty();
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValues(TestUtils.TABLE_NAME_STORE, Collections.emptyList()));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).isEmpty();
|
||||||
|
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValues(TestUtils.TABLE_NAME_STORE, List.of(1, 3)));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords())
|
||||||
|
.hasSize(6)
|
||||||
|
.allMatch(r -> r.getValueInteger("storeKey").equals(1) || r.getValueInteger("storeKey").equals(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testRecordSecurityWithFilters() throws QException
|
||||||
|
{
|
||||||
|
QueryInput queryInput = new QueryInput();
|
||||||
|
queryInput.setTableName(TestUtils.TABLE_NAME_ORDER);
|
||||||
|
|
||||||
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("key", QCriteriaOperator.BETWEEN, List.of(2, 7))));
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.SECURITY_KEY_STORE_ALL_ACCESS, true));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).hasSize(6);
|
||||||
|
|
||||||
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("key", QCriteriaOperator.BETWEEN, List.of(2, 7))));
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 1));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords())
|
||||||
|
.hasSize(2)
|
||||||
|
.allMatch(r -> r.getValueInteger("storeKey").equals(1));
|
||||||
|
|
||||||
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("key", QCriteriaOperator.BETWEEN, List.of(2, 7))));
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValue(TestUtils.TABLE_NAME_STORE, 5));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).isEmpty();
|
||||||
|
|
||||||
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("key", QCriteriaOperator.BETWEEN, List.of(2, 7))));
|
||||||
|
QContext.setQSession(new QSession());
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords()).isEmpty();
|
||||||
|
|
||||||
|
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("storeKey", QCriteriaOperator.IN, List.of(1, 2))));
|
||||||
|
QContext.setQSession(new QSession().withSecurityKeyValues(TestUtils.TABLE_NAME_STORE, List.of(1, 3)));
|
||||||
|
assertThat(new QueryAction().execute(queryInput).getRecords())
|
||||||
|
.hasSize(3)
|
||||||
|
.allMatch(r -> r.getValueInteger("storeKey").equals(1));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* 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.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||||
|
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
|
import com.kingsrook.qqq.backend.module.mongodb.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.module.mongodb.TestUtils;
|
||||||
|
import com.kingsrook.qqq.backend.module.mongodb.model.metadata.MongoDBBackendMetaData;
|
||||||
|
import com.mongodb.MongoCommandException;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Unit test for MongoDBTransaction
|
||||||
|
*******************************************************************************/
|
||||||
|
class MongoDBTransactionTest extends BaseTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Our testcontainer only runs a single mongo, so it doesn't support transactions.
|
||||||
|
** The Backend built by TestUtils is configured to with transactionsSupported = false
|
||||||
|
** make sure things all work like this.
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testWithTransactionsDisabled() throws QException
|
||||||
|
{
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withValue("firstName", "Darin")));
|
||||||
|
|
||||||
|
QBackendTransaction transaction = QBackendTransaction.openFor(insertInput);
|
||||||
|
assertNotNull(transaction);
|
||||||
|
assertThat(transaction).isInstanceOf(MongoDBTransaction.class);
|
||||||
|
MongoDBTransaction mongoDBTransaction = (MongoDBTransaction) transaction;
|
||||||
|
assertNotNull(mongoDBTransaction.getMongoClient());
|
||||||
|
assertNotNull(mongoDBTransaction.getClientSession());
|
||||||
|
|
||||||
|
insertInput.setTransaction(transaction);
|
||||||
|
new InsertAction().execute(insertInput);
|
||||||
|
transaction.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** make sure we throw an error if we do turn on transaction support, but our
|
||||||
|
** mongo backend can't handle them
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testWithTransactionsEnabled() throws QException
|
||||||
|
{
|
||||||
|
MongoDBBackendMetaData backend = (MongoDBBackendMetaData) QContext.getQInstance().getBackend(TestUtils.DEFAULT_BACKEND_NAME);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
backend.setTransactionsSupported(true);
|
||||||
|
|
||||||
|
InsertInput insertInput = new InsertInput();
|
||||||
|
insertInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
insertInput.setRecords(List.of(new QRecord().withValue("firstName", "Darin")));
|
||||||
|
|
||||||
|
QBackendTransaction transaction = QBackendTransaction.openFor(insertInput);
|
||||||
|
assertNotNull(transaction);
|
||||||
|
assertThat(transaction).isInstanceOf(MongoDBTransaction.class);
|
||||||
|
MongoDBTransaction mongoDBTransaction = (MongoDBTransaction) transaction;
|
||||||
|
assertNotNull(mongoDBTransaction.getMongoClient());
|
||||||
|
assertNotNull(mongoDBTransaction.getClientSession());
|
||||||
|
|
||||||
|
insertInput.setTransaction(transaction);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> new InsertAction().execute(insertInput))
|
||||||
|
.isInstanceOf(QException.class)
|
||||||
|
.hasRootCauseInstanceOf(MongoCommandException.class);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> transaction.commit())
|
||||||
|
.isInstanceOf(QException.class)
|
||||||
|
.hasRootCauseInstanceOf(MongoCommandException.class);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
backend.setTransactionsSupported(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,9 +22,24 @@
|
|||||||
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
package com.kingsrook.qqq.backend.module.mongodb.actions;
|
||||||
|
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
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.module.mongodb.BaseTest;
|
import com.kingsrook.qqq.backend.module.mongodb.BaseTest;
|
||||||
|
import com.kingsrook.qqq.backend.module.mongodb.TestUtils;
|
||||||
|
import com.mongodb.client.MongoCollection;
|
||||||
|
import com.mongodb.client.MongoDatabase;
|
||||||
|
import com.mongodb.client.result.InsertManyResult;
|
||||||
|
import org.bson.BsonValue;
|
||||||
|
import org.bson.Document;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -39,7 +54,34 @@ class MongoDBUpdateActionTest extends BaseTest
|
|||||||
@Test
|
@Test
|
||||||
void test() throws QException
|
void test() throws QException
|
||||||
{
|
{
|
||||||
// todo - test!!
|
////////////////////////////////////////
|
||||||
|
// directly insert some mongo records //
|
||||||
|
////////////////////////////////////////
|
||||||
|
MongoDatabase database = getMongoClient().getDatabase(TestUtils.MONGO_DATABASE);
|
||||||
|
MongoCollection<Document> collection = database.getCollection(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
InsertManyResult insertManyResult = collection.insertMany(List.of(
|
||||||
|
Document.parse("""
|
||||||
|
{"metaData": {"createDate": "2023-01-09T03:03:03.123Z", "modifyDate": "2023-01-09T04:04:04.123Z"}, "firstName": "Tylers", "lastName": "Sample"}""")
|
||||||
|
));
|
||||||
|
BsonValue insertedId = insertManyResult.getInsertedIds().values().iterator().next();
|
||||||
|
|
||||||
|
////////////////////////////////////
|
||||||
|
// update using qqq update action //
|
||||||
|
////////////////////////////////////
|
||||||
|
UpdateInput updateInput = new UpdateInput();
|
||||||
|
updateInput.setTableName(TestUtils.TABLE_NAME_PERSON);
|
||||||
|
updateInput.setRecords(List.of(
|
||||||
|
new QRecord().withValue("id", insertedId.asObjectId().getValue().toString()).withValue("firstName", "Tyler").withValue("lastName", "Sample")
|
||||||
|
));
|
||||||
|
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
// directly query mongo for the updated record //
|
||||||
|
/////////////////////////////////////////////////
|
||||||
|
Document document = collection.find(new Document("firstName", "Tyler")).first();
|
||||||
|
assertNotNull(document);
|
||||||
|
assertEquals("Tyler", document.get("firstName"));
|
||||||
|
assertNotEquals(Instant.parse("2023-01-09T04:04:04.123Z"), ((Document) document.get("metaData")).get("modifyDate"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user