mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Add LIKE criteria operator; more api endpoints to understand versions, get swagger json; more field name mapping
This commit is contained in:
@ -33,6 +33,8 @@ public enum QCriteriaOperator
|
||||
IN,
|
||||
NOT_IN,
|
||||
IS_NULL_OR_IN,
|
||||
LIKE,
|
||||
NOT_LIKE,
|
||||
STARTS_WITH,
|
||||
ENDS_WITH,
|
||||
CONTAINS,
|
||||
|
@ -123,6 +123,8 @@ public class BackendQueryFilterUtils
|
||||
case CONTAINS -> testContains(criterion, fieldName, value);
|
||||
case NOT_CONTAINS -> !testContains(criterion, fieldName, value);
|
||||
case IS_NULL_OR_IN -> testBlank(criterion, value) || testIn(criterion, value);
|
||||
case LIKE -> testLike(criterion, fieldName, value);
|
||||
case NOT_LIKE -> !testLike(criterion, fieldName, value);
|
||||
case STARTS_WITH -> testStartsWith(criterion, fieldName, value);
|
||||
case NOT_STARTS_WITH -> !testStartsWith(criterion, fieldName, value);
|
||||
case ENDS_WITH -> testEndsWith(criterion, fieldName, value);
|
||||
@ -152,6 +154,21 @@ public class BackendQueryFilterUtils
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static boolean testLike(QFilterCriteria criterion, String fieldName, Serializable value)
|
||||
{
|
||||
String stringValue = getStringFieldValue(value, fieldName, criterion);
|
||||
String criterionValue = getFirstStringCriterionValue(criterion);
|
||||
|
||||
String regex = sqlLikeToRegex(criterionValue);
|
||||
|
||||
return (stringValue.matches(regex));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Based on an incoming boolean value (accumulator), a new value, and a boolean
|
||||
** operator, update the accumulator, and if we can then short-circuit remaining
|
||||
@ -514,4 +531,38 @@ public class BackendQueryFilterUtils
|
||||
return recordList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** ... written by ChatGPT
|
||||
*******************************************************************************/
|
||||
static String sqlLikeToRegex(String sqlLikeExpression)
|
||||
{
|
||||
StringBuilder regex = new StringBuilder("^");
|
||||
|
||||
for(int i = 0; i < sqlLikeExpression.length(); i++)
|
||||
{
|
||||
char c = sqlLikeExpression.charAt(i);
|
||||
|
||||
if(c == '%')
|
||||
{
|
||||
regex.append(".*");
|
||||
}
|
||||
else if(c == '_')
|
||||
{
|
||||
regex.append(".");
|
||||
}
|
||||
else if("[]^$|\\(){}.*+?".indexOf(c) >= 0)
|
||||
{
|
||||
regex.append("\\").append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
regex.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
regex.append("$");
|
||||
return regex.toString();
|
||||
}
|
||||
}
|
||||
|
@ -87,6 +87,78 @@ class BackendQueryFilterUtilsTest
|
||||
assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_BETWEEN, List.of(1, 3)), "f", 2));
|
||||
assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_BETWEEN, List.of(1, 3)), "f", 3));
|
||||
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_BETWEEN, List.of(1, 3)), "f", 4));
|
||||
|
||||
////////////////
|
||||
// like & not //
|
||||
////////////////
|
||||
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LIKE, "Test"), "f", "Test"));
|
||||
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LIKE, "T%"), "f", "Test"));
|
||||
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LIKE, "T_st"), "f", "Test"));
|
||||
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_LIKE, "Test"), "f", "Tst"));
|
||||
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_LIKE, "T%"), "f", "Rest"));
|
||||
assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_LIKE, "T_st"), "f", "Toast"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testLikeDarPercent()
|
||||
{
|
||||
String pattern = BackendQueryFilterUtils.sqlLikeToRegex("Dar%");
|
||||
assertTrue("Darin".matches(pattern));
|
||||
assertTrue("Dar".matches(pattern));
|
||||
assertFalse("Not Darin".matches(pattern));
|
||||
assertFalse("David".matches(pattern));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testLikeDPercentIn()
|
||||
{
|
||||
String pattern = BackendQueryFilterUtils.sqlLikeToRegex("D%in");
|
||||
assertTrue("Darin".matches(pattern));
|
||||
assertFalse("Dar".matches(pattern));
|
||||
assertFalse("Not Darin".matches(pattern));
|
||||
assertTrue("Davin".matches(pattern));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testLikeDPercentUnderscoreN()
|
||||
{
|
||||
String pattern = BackendQueryFilterUtils.sqlLikeToRegex("D%_n");
|
||||
assertTrue("Darin".matches(pattern));
|
||||
assertTrue("Daron".matches(pattern));
|
||||
assertTrue("Dan".matches(pattern));
|
||||
assertFalse("Dar".matches(pattern));
|
||||
assertFalse("Not Darin".matches(pattern));
|
||||
assertTrue("Davin".matches(pattern));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testLikeDarUnderscore()
|
||||
{
|
||||
String pattern = BackendQueryFilterUtils.sqlLikeToRegex("Dar_");
|
||||
assertFalse("Darin".matches(pattern));
|
||||
assertFalse("Dar".matches(pattern));
|
||||
assertTrue("Dart".matches(pattern));
|
||||
assertFalse("Not Darin".matches(pattern));
|
||||
assertFalse("David".matches(pattern));
|
||||
}
|
||||
}
|
@ -601,6 +601,18 @@ public abstract class AbstractRDBMSAction implements QActionInterface
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LIKE:
|
||||
{
|
||||
clause += " LIKE ?";
|
||||
expectedNoOfParams = 1;
|
||||
break;
|
||||
}
|
||||
case NOT_LIKE:
|
||||
{
|
||||
clause += " NOT LIKE ?";
|
||||
expectedNoOfParams = 1;
|
||||
break;
|
||||
}
|
||||
case STARTS_WITH:
|
||||
{
|
||||
clause += " LIKE ?";
|
||||
|
@ -213,6 +213,46 @@ public class RDBMSQueryActionTest extends RDBMSActionTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@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 RDBMSQueryAction().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 RDBMSQueryAction().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");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -63,6 +63,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
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.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
@ -106,7 +109,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
)
|
||||
.withServers(ListBuilder.of(new Server()
|
||||
.withDescription("Localhost development")
|
||||
.withUrl("http://localhost:8000/api")
|
||||
.withUrl("http://localhost:8000/api/" + version)
|
||||
));
|
||||
|
||||
openAPI.setTags(new ArrayList<>());
|
||||
@ -163,6 +166,12 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
{
|
||||
String tableName = table.getName();
|
||||
|
||||
if(input.getTableName() != null && !input.getTableName().equals(tableName))
|
||||
{
|
||||
LOG.debug("Omitting table [" + tableName + "] because it is not the requested table [" + input.getTableName() + "]");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(table.getIsHidden())
|
||||
{
|
||||
LOG.debug("Omitting table [" + tableName + "] because it is marked as hidden");
|
||||
@ -209,11 +218,9 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
String primaryKeyName = table.getPrimaryKeyField();
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
String primaryKeyLabel = primaryKeyField.getLabel();
|
||||
String primaryKeyApiName = ApiFieldMetaData.getEffectiveApiFieldName(primaryKeyField);
|
||||
List<QFieldMetaData> tableApiFields = new GetTableApiFieldsAction().execute(new GetTableApiFieldsInput().withTableName(tableName).withVersion(version)).getFields();
|
||||
|
||||
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(primaryKeyField);
|
||||
String primaryKeyApiName = (apiFieldMetaData != null && StringUtils.hasContent(apiFieldMetaData.getApiFieldName())) ? apiFieldMetaData.getApiFieldName() : primaryKeyName;
|
||||
|
||||
String tableReadPermissionName = PermissionsHelper.getTablePermissionName(tableName, TablePermissionSubType.READ);
|
||||
if(StringUtils.hasContent(tableReadPermissionName))
|
||||
{
|
||||
@ -257,18 +264,40 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
|
||||
.withType("object")
|
||||
.withProperties(tableFieldsWithoutPrimaryKey));
|
||||
|
||||
for(QFieldMetaData tableApiField : tableApiFields)
|
||||
for(QFieldMetaData field : tableApiFields)
|
||||
{
|
||||
if(primaryKeyName.equals(tableApiField.getName()))
|
||||
if(primaryKeyName.equals(field.getName()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
tableFieldsWithoutPrimaryKey.put(tableApiField.getName(), new Schema()
|
||||
.withType(getFieldType(table.getField(tableApiField.getName())))
|
||||
.withFormat(getFieldFormat(table.getField(tableApiField.getName())))
|
||||
.withDescription(tableApiField.getLabel() + " for the " + tableLabel + ".")
|
||||
);
|
||||
String apiFieldName = ApiFieldMetaData.getEffectiveApiFieldName(field);
|
||||
|
||||
Schema fieldSchema = new Schema()
|
||||
.withType(getFieldType(table.getField(field.getName())))
|
||||
.withFormat(getFieldFormat(table.getField(field.getName())))
|
||||
.withDescription(field.getLabel() + " for the " + tableLabel + ".");
|
||||
|
||||
if(StringUtils.hasContent(field.getPossibleValueSourceName()))
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
if(QPossibleValueSourceType.ENUM.equals(possibleValueSource.getType()))
|
||||
{
|
||||
List<String> enumValues = new ArrayList<>();
|
||||
for(QPossibleValue<?> enumValue : possibleValueSource.getEnumValues())
|
||||
{
|
||||
enumValues.add(enumValue.getId() + "=" + enumValue.getLabel());
|
||||
}
|
||||
fieldSchema.setEnumValues(enumValues);
|
||||
}
|
||||
else if(QPossibleValueSourceType.TABLE.equals(possibleValueSource.getType()))
|
||||
{
|
||||
QTableMetaData sourceTable = qInstance.getTable(possibleValueSource.getTableName());
|
||||
fieldSchema.setDescription(fieldSchema.getDescription() + " Values in this field come from the primary key of the " + sourceTable.getLabel() + " table");
|
||||
}
|
||||
}
|
||||
|
||||
tableFieldsWithoutPrimaryKey.put(apiFieldName, fieldSchema);
|
||||
}
|
||||
|
||||
componentSchemas.put(tableApiName, new Schema()
|
||||
|
@ -67,12 +67,7 @@ public class QRecordApiAdapter
|
||||
|
||||
// todo - what about display values / possible values?
|
||||
|
||||
String apiFieldName = apiFieldMetaData.getApiFieldName();
|
||||
if(!StringUtils.hasContent(apiFieldName))
|
||||
{
|
||||
apiFieldName = field.getName();
|
||||
}
|
||||
|
||||
String apiFieldName = ApiFieldMetaData.getEffectiveApiFieldName(field);
|
||||
if(StringUtils.hasContent(apiFieldMetaData.getReplacedByFieldName()))
|
||||
{
|
||||
outputRecord.put(apiFieldName, record.getValue(apiFieldMetaData.getReplacedByFieldName()));
|
||||
@ -149,16 +144,7 @@ public class QRecordApiAdapter
|
||||
Pair<String, String> key = new Pair<>(tableName, apiVersion);
|
||||
if(!fieldMapCache.containsKey(key))
|
||||
{
|
||||
Map<String, QFieldMetaData> map = getTableApiFieldList(tableName, apiVersion).stream().collect(Collectors.toMap(f ->
|
||||
{
|
||||
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(f);
|
||||
String apiFieldName = apiFieldMetaData.getApiFieldName();
|
||||
if(!StringUtils.hasContent(apiFieldName))
|
||||
{
|
||||
apiFieldName = f.getName();
|
||||
}
|
||||
return (apiFieldName);
|
||||
}, f -> f));
|
||||
Map<String, QFieldMetaData> map = getTableApiFieldList(tableName, apiVersion).stream().collect(Collectors.toMap(f -> (ApiFieldMetaData.getEffectiveApiFieldName(f)), f -> f));
|
||||
fieldMapCache.put(key, map);
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.api.actions.GenerateOpenApiSpecAction;
|
||||
import com.kingsrook.qqq.api.actions.QRecordApiAdapter;
|
||||
import com.kingsrook.qqq.api.model.APIVersion;
|
||||
@ -129,10 +130,14 @@ public class QJavalinApiHandler
|
||||
{
|
||||
ApiBuilder.path("/api/{version}", () -> // todo - configurable, that /api/ bit?
|
||||
{
|
||||
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpec);
|
||||
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpecYaml);
|
||||
ApiBuilder.get("/openapi.json", QJavalinApiHandler::doSpecJson);
|
||||
|
||||
ApiBuilder.path("/{tableName}", () ->
|
||||
{
|
||||
ApiBuilder.get("/openapi.yaml", QJavalinApiHandler::doSpecYaml);
|
||||
ApiBuilder.get("/openapi.json", QJavalinApiHandler::doSpecJson);
|
||||
|
||||
ApiBuilder.post("/", QJavalinApiHandler::doInsert);
|
||||
|
||||
ApiBuilder.get("/query", QJavalinApiHandler::doQuery);
|
||||
@ -151,6 +156,10 @@ public class QJavalinApiHandler
|
||||
});
|
||||
});
|
||||
|
||||
ApiBuilder.get("/api/versions.json", QJavalinApiHandler::doVersions);
|
||||
|
||||
ApiBuilder.before("/*", QJavalinApiHandler::setupCORS);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// default all other /api/ requests (for the methods we support) to a standard 404 response //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -163,6 +172,45 @@ public class QJavalinApiHandler
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void doVersions(Context context)
|
||||
{
|
||||
ApiInstanceMetaData apiInstanceMetaData = ApiInstanceMetaData.of(qInstance);
|
||||
|
||||
Map<String, Object> rs = new HashMap<>();
|
||||
rs.put("supportedVersions", apiInstanceMetaData.getSupportedVersions().stream().map(String::valueOf).collect(Collectors.toList()));
|
||||
rs.put("currentVersion", apiInstanceMetaData.getCurrentVersion().toString());
|
||||
|
||||
context.contentType(ContentType.APPLICATION_JSON);
|
||||
context.result(JsonUtils.toJson(rs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void setupCORS(Context context)
|
||||
{
|
||||
if(StringUtils.hasContent(context.header("Origin")))
|
||||
{
|
||||
context.res().setHeader("Access-Control-Allow-Origin", context.header("Origin"));
|
||||
context.res().setHeader("Vary", "Origin");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.res().setHeader("Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
|
||||
context.header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, OPTIONS");
|
||||
context.header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Authorization, Accept, content-type, authorization, accept");
|
||||
context.header("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -176,7 +224,7 @@ public class QJavalinApiHandler
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void doSpec(Context context)
|
||||
private static void doSpecYaml(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -194,6 +242,34 @@ public class QJavalinApiHandler
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void doSpecJson(Context context)
|
||||
{
|
||||
try
|
||||
{
|
||||
QContext.init(qInstance, null);
|
||||
String version = context.pathParam("version");
|
||||
GenerateOpenApiSpecInput input = new GenerateOpenApiSpecInput().withVersion(version);
|
||||
|
||||
if(context.pathParam("tableName") != null)
|
||||
{
|
||||
input.setTableName(context.pathParam("tableName"));
|
||||
}
|
||||
|
||||
GenerateOpenApiSpecOutput output = new GenerateOpenApiSpecAction().execute(input);
|
||||
context.contentType(ContentType.JSON);
|
||||
context.result(output.getJson());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
handleException(context, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -576,22 +652,20 @@ public class QJavalinApiHandler
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// order of these is important (e.g., because some are a sub-string of others!!) //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
EQ("=", QCriteriaOperator.EQUALS, QCriteriaOperator.NOT_EQUALS, true, 1),
|
||||
LTE("<=", QCriteriaOperator.LESS_THAN_OR_EQUALS, QCriteriaOperator.GREATER_THAN, false, 1),
|
||||
GTE(">=", QCriteriaOperator.GREATER_THAN_OR_EQUALS, QCriteriaOperator.LESS_THAN, false, 1),
|
||||
LT("<", QCriteriaOperator.LESS_THAN, QCriteriaOperator.GREATER_THAN_OR_EQUALS, false, 1),
|
||||
GT(">", QCriteriaOperator.GREATER_THAN, QCriteriaOperator.LESS_THAN_OR_EQUALS, false, 1),
|
||||
EMPTY("EMPTY", QCriteriaOperator.IS_BLANK, QCriteriaOperator.IS_NOT_BLANK, true, 0),
|
||||
BETWEEN("BETWEEN ", QCriteriaOperator.BETWEEN, QCriteriaOperator.NOT_BETWEEN, true, 2),
|
||||
IN("IN ", QCriteriaOperator.IN, QCriteriaOperator.NOT_IN, true, null),
|
||||
// todo MATCHES
|
||||
;
|
||||
EQ("=", QCriteriaOperator.EQUALS, QCriteriaOperator.NOT_EQUALS, 1),
|
||||
LTE("<=", QCriteriaOperator.LESS_THAN_OR_EQUALS, null, 1),
|
||||
GTE(">=", QCriteriaOperator.GREATER_THAN_OR_EQUALS, null, 1),
|
||||
LT("<", QCriteriaOperator.LESS_THAN, null, 1),
|
||||
GT(">", QCriteriaOperator.GREATER_THAN, null, 1),
|
||||
EMPTY("EMPTY", QCriteriaOperator.IS_BLANK, QCriteriaOperator.IS_NOT_BLANK, 0),
|
||||
BETWEEN("BETWEEN ", QCriteriaOperator.BETWEEN, QCriteriaOperator.NOT_BETWEEN, 2),
|
||||
IN("IN ", QCriteriaOperator.IN, QCriteriaOperator.NOT_IN, null),
|
||||
LIKE("LIKE ", QCriteriaOperator.LIKE, QCriteriaOperator.NOT_LIKE, 1);
|
||||
|
||||
|
||||
private final String prefix;
|
||||
private final QCriteriaOperator positiveOperator;
|
||||
private final QCriteriaOperator negativeOperator;
|
||||
private final boolean supportsNot;
|
||||
private final Integer noOfValues; // null means many (IN)
|
||||
|
||||
|
||||
@ -599,12 +673,11 @@ public class QJavalinApiHandler
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
Operator(String prefix, QCriteriaOperator positiveOperator, QCriteriaOperator negativeOperator, boolean supportsNot, Integer noOfValues)
|
||||
Operator(String prefix, QCriteriaOperator positiveOperator, QCriteriaOperator negativeOperator, Integer noOfValues)
|
||||
{
|
||||
this.prefix = prefix;
|
||||
this.positiveOperator = positiveOperator;
|
||||
this.negativeOperator = negativeOperator;
|
||||
this.supportsNot = supportsNot;
|
||||
this.noOfValues = noOfValues;
|
||||
}
|
||||
}
|
||||
@ -635,7 +708,7 @@ public class QJavalinApiHandler
|
||||
if(value.startsWith(op.prefix))
|
||||
{
|
||||
selectedOperator = op;
|
||||
if(!selectedOperator.supportsNot && isNot)
|
||||
if(selectedOperator.negativeOperator == null && isNot)
|
||||
{
|
||||
throw (new QBadRequestException("Unsupported operator: !" + selectedOperator.prefix));
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
public class GenerateOpenApiSpecInput extends AbstractActionInput
|
||||
{
|
||||
private String version;
|
||||
private String tableName;
|
||||
|
||||
|
||||
|
||||
@ -63,4 +64,35 @@ public class GenerateOpenApiSpecInput extends AbstractActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return (this.tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
*******************************************************************************/
|
||||
public GenerateOpenApiSpecInput withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.api.model.metadata.fields;
|
||||
import com.kingsrook.qqq.api.ApiMiddlewareType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QMiddlewareFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -63,6 +64,22 @@ public class ApiFieldMetaData extends QMiddlewareFieldMetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String getEffectiveApiFieldName(QFieldMetaData field)
|
||||
{
|
||||
ApiFieldMetaData apiFieldMetaData = ApiFieldMetaData.of(field);
|
||||
if(apiFieldMetaData != null && StringUtils.hasContent(apiFieldMetaData.apiFieldName))
|
||||
{
|
||||
return (apiFieldMetaData.apiFieldName);
|
||||
}
|
||||
|
||||
return (field.getName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for initialVersion
|
||||
*******************************************************************************/
|
||||
|
@ -292,6 +292,23 @@ class QJavalinApiHandlerTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testQuery200LikeAndNotLike() throws QException
|
||||
{
|
||||
insertSimpsons();
|
||||
String PERCENT = "%25";
|
||||
assertPersonQueryFindsFirstNames(List.of("Homer"), "firstName=LIKE Ho" + PERCENT);
|
||||
assertPersonQueryFindsFirstNames(List.of("Homer"), "firstName=LIKE Ho_er");
|
||||
|
||||
assertPersonQueryFindsFirstNames(List.of("Marge", "Bart", "Lisa", "Maggie"), "firstName=!LIKE Homer&orderBy=id");
|
||||
assertPersonQueryFindsFirstNames(List.of("Homer"), "firstName=!LIKE " + PERCENT + "a" + PERCENT + "&orderBy=id");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
Reference in New Issue
Block a user