Adding QFieldType.LONG

This commit is contained in:
2023-12-22 18:59:08 -06:00
parent a8c30b1bed
commit 455ab69104
13 changed files with 309 additions and 7 deletions

View File

@ -73,6 +73,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.aggregates.AggregatesInterface; import com.kingsrook.qqq.backend.core.utils.aggregates.AggregatesInterface;
import com.kingsrook.qqq.backend.core.utils.aggregates.BigDecimalAggregates; import com.kingsrook.qqq.backend.core.utils.aggregates.BigDecimalAggregates;
import com.kingsrook.qqq.backend.core.utils.aggregates.IntegerAggregates; import com.kingsrook.qqq.backend.core.utils.aggregates.IntegerAggregates;
import com.kingsrook.qqq.backend.core.utils.aggregates.LongAggregates;
/******************************************************************************* /*******************************************************************************
@ -553,6 +554,12 @@ public class GenerateReportAction
AggregatesInterface<Integer> fieldAggregates = (AggregatesInterface<Integer>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new IntegerAggregates()); AggregatesInterface<Integer> fieldAggregates = (AggregatesInterface<Integer>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new IntegerAggregates());
fieldAggregates.add(record.getValueInteger(field.getName())); fieldAggregates.add(record.getValueInteger(field.getName()));
} }
else if(field.getType().equals(QFieldType.LONG))
{
@SuppressWarnings("unchecked")
AggregatesInterface<Long> fieldAggregates = (AggregatesInterface<Long>) aggregatesMap.computeIfAbsent(field.getName(), (name) -> new LongAggregates());
fieldAggregates.add(record.getValueLong(field.getName()));
}
else if(field.getType().equals(QFieldType.DECIMAL)) else if(field.getType().equals(QFieldType.DECIMAL))
{ {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View File

@ -270,6 +270,10 @@ public class QPossibleValueTranslator
{ {
value = ValueUtils.getValueAsInteger(value); value = ValueUtils.getValueAsInteger(value);
} }
if(field.getType().equals(QFieldType.LONG) && !(value instanceof Long))
{
value = ValueUtils.getValueAsLong(value);
}
} }
catch(QValueException e) catch(QValueException e)
{ {

View File

@ -444,6 +444,16 @@ public class QRecord implements Serializable
} }
/*******************************************************************************
** Getter for a single field's value
**
*******************************************************************************/
public Long getValueLong(String fieldName)
{
return (ValueUtils.getValueAsLong(values.get(fieldName)));
}
/******************************************************************************* /*******************************************************************************
** **

View File

@ -37,6 +37,7 @@ public enum QFieldType
{ {
STRING, STRING,
INTEGER, INTEGER,
LONG,
DECIMAL, DECIMAL,
BOOLEAN, BOOLEAN,
DATE, DATE,
@ -65,6 +66,10 @@ public enum QFieldType
{ {
return (INTEGER); return (INTEGER);
} }
if(c.equals(Long.class) || c.equals(long.class))
{
return (LONG);
}
if(c.equals(BigDecimal.class)) if(c.equals(BigDecimal.class))
{ {
return (DECIMAL); return (DECIMAL);
@ -110,7 +115,7 @@ public enum QFieldType
*******************************************************************************/ *******************************************************************************/
public boolean isNumeric() public boolean isNumeric()
{ {
return this == QFieldType.INTEGER || this == QFieldType.DECIMAL; return this == QFieldType.INTEGER || this == QFieldType.LONG || this == QFieldType.DECIMAL;
} }

View File

@ -366,7 +366,7 @@ public class MemoryRecordStore
///////////////////////////////////////////////// /////////////////////////////////////////////////
// set the next serial in the record if needed // // set the next serial in the record if needed //
///////////////////////////////////////////////// /////////////////////////////////////////////////
if(recordToInsert.getValue(primaryKeyField.getName()) == null && primaryKeyField.getType().equals(QFieldType.INTEGER)) if(recordToInsert.getValue(primaryKeyField.getName()) == null && (primaryKeyField.getType().equals(QFieldType.INTEGER) || primaryKeyField.getType().equals(QFieldType.LONG)))
{ {
recordToInsert.setValue(primaryKeyField.getName(), nextSerial++); recordToInsert.setValue(primaryKeyField.getName(), nextSerial++);
} }
@ -378,6 +378,13 @@ public class MemoryRecordStore
{ {
nextSerial = recordToInsert.getValueInteger(primaryKeyField.getName()) + 1; nextSerial = recordToInsert.getValueInteger(primaryKeyField.getName()) + 1;
} }
else if(primaryKeyField.getType().equals(QFieldType.LONG) && recordToInsert.getValueLong(primaryKeyField.getName()) > nextSerial)
{
//////////////////////////////////////
// todo - mmm, could overflow here? //
//////////////////////////////////////
nextSerial = recordToInsert.getValueInteger(primaryKeyField.getName()) + 1;
}
tableData.put(recordToInsert.getValue(primaryKeyField.getName()), recordToInsert); tableData.put(recordToInsert.getValue(primaryKeyField.getName()), recordToInsert);
if(returnInsertedRecords) if(returnInsertedRecords)
@ -709,7 +716,7 @@ public class MemoryRecordStore
{ {
// todo - joins probably? // todo - joins probably?
QFieldMetaData field = table.getField(fieldName); QFieldMetaData field = table.getField(fieldName);
if(field.getType().equals(QFieldType.INTEGER) && (operator.equals(AggregateOperator.AVG))) if((field.getType().equals(QFieldType.INTEGER) || field.getType().equals(QFieldType.LONG)) && (operator.equals(AggregateOperator.AVG)))
{ {
fieldType = QFieldType.DECIMAL; fieldType = QFieldType.DECIMAL;
} }
@ -745,6 +752,10 @@ public class MemoryRecordStore
.filter(r -> r.getValue(fieldName) != null) .filter(r -> r.getValue(fieldName) != null)
.mapToInt(r -> r.getValueInteger(fieldName)) .mapToInt(r -> r.getValueInteger(fieldName))
.sum(); .sum();
case LONG -> records.stream()
.filter(r -> r.getValue(fieldName) != null)
.mapToLong(r -> r.getValueLong(fieldName))
.sum();
case DECIMAL -> records.stream() case DECIMAL -> records.stream()
.filter(r -> r.getValue(fieldName) != null) .filter(r -> r.getValue(fieldName) != null)
.map(r -> r.getValueBigDecimal(fieldName)) .map(r -> r.getValueBigDecimal(fieldName))
@ -759,6 +770,11 @@ public class MemoryRecordStore
.mapToInt(r -> r.getValueInteger(fieldName)) .mapToInt(r -> r.getValueInteger(fieldName))
.min() .min()
.stream().boxed().findFirst().orElse(null); .stream().boxed().findFirst().orElse(null);
case LONG -> records.stream()
.filter(r -> r.getValue(fieldName) != null)
.mapToLong(r -> r.getValueLong(fieldName))
.min()
.stream().boxed().findFirst().orElse(null);
case DECIMAL, STRING, DATE, DATE_TIME -> case DECIMAL, STRING, DATE, DATE_TIME ->
{ {
Optional<Serializable> serializable = records.stream() Optional<Serializable> serializable = records.stream()
@ -775,7 +791,12 @@ public class MemoryRecordStore
{ {
case INTEGER -> records.stream() case INTEGER -> records.stream()
.filter(r -> r.getValue(fieldName) != null) .filter(r -> r.getValue(fieldName) != null)
.mapToInt(r -> r.getValueInteger(fieldName)) .mapToLong(r -> r.getValueInteger(fieldName))
.max()
.stream().boxed().findFirst().orElse(null);
case LONG -> records.stream()
.filter(r -> r.getValue(fieldName) != null)
.mapToLong(r -> r.getValueLong(fieldName))
.max() .max()
.stream().boxed().findFirst().orElse(null); .stream().boxed().findFirst().orElse(null);
case DECIMAL, STRING, DATE, DATE_TIME -> case DECIMAL, STRING, DATE, DATE_TIME ->
@ -797,6 +818,11 @@ public class MemoryRecordStore
.mapToInt(r -> r.getValueInteger(fieldName)) .mapToInt(r -> r.getValueInteger(fieldName))
.average() .average()
.stream().boxed().findFirst().orElse(null); .stream().boxed().findFirst().orElse(null);
case LONG -> records.stream()
.filter(r -> r.getValue(fieldName) != null)
.mapToLong(r -> r.getValueLong(fieldName))
.average()
.stream().boxed().findFirst().orElse(null);
case DECIMAL -> records.stream() case DECIMAL -> records.stream()
.filter(r -> r.getValue(fieldName) != null) .filter(r -> r.getValue(fieldName) != null)
.mapToDouble(r -> r.getValueBigDecimal(fieldName).doubleValue()) .mapToDouble(r -> r.getValueBigDecimal(fieldName).doubleValue())

View File

@ -103,6 +103,7 @@ public class MockQueryAction implements QueryInterface
{ {
case STRING -> UUID.randomUUID().toString(); case STRING -> UUID.randomUUID().toString();
case INTEGER -> 42; case INTEGER -> 42;
case LONG -> 42L;
case DECIMAL -> new BigDecimal("3.14159"); case DECIMAL -> new BigDecimal("3.14159");
case DATE -> LocalDate.of(1970, Month.JANUARY, 1); case DATE -> LocalDate.of(1970, Month.JANUARY, 1);
case DATE_TIME -> LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0); case DATE_TIME -> LocalDateTime.of(1970, Month.JANUARY, 1, 0, 0);

View File

@ -362,6 +362,7 @@ public class JsonUtils
switch(metaData.getType()) switch(metaData.getType())
{ {
case INTEGER -> record.setValue(fieldName, jsonObjectToUse.optInt(backendName)); case INTEGER -> record.setValue(fieldName, jsonObjectToUse.optInt(backendName));
case LONG -> record.setValue(fieldName, jsonObjectToUse.optLong(backendName));
case DECIMAL -> record.setValue(fieldName, jsonObjectToUse.optBigDecimal(backendName, null)); case DECIMAL -> record.setValue(fieldName, jsonObjectToUse.optBigDecimal(backendName, null));
case BOOLEAN -> record.setValue(fieldName, jsonObjectToUse.optBoolean(backendName)); case BOOLEAN -> record.setValue(fieldName, jsonObjectToUse.optBoolean(backendName));
case DATE_TIME -> case DATE_TIME ->

View File

@ -114,6 +114,113 @@ public class ValueUtils
/*******************************************************************************
** Type-safely make an Long from any Object.
** null and empty-string inputs return null.
** We try to strip away commas and decimals (as long as they are exactly equal to the int value)
** We may throw if the input can't be converted to an integer.
*******************************************************************************/
public static Long getValueAsLong(Object value) throws QValueException
{
try
{
if(value == null)
{
return (null);
}
else if(value instanceof Integer i)
{
return Long.valueOf((i));
}
else if(value instanceof Long l)
{
return (l);
}
else if(value instanceof BigInteger b)
{
return (b.longValue());
}
else if(value instanceof Float f)
{
if(f.longValue() != f)
{
throw (new QValueException(f + " does not have an exact integer representation."));
}
return (f.longValue());
}
else if(value instanceof Double d)
{
if(d.longValue() != d)
{
throw (new QValueException(d + " does not have an exact integer representation."));
}
return (d.longValue());
}
else if(value instanceof BigDecimal bd)
{
return bd.longValueExact();
}
else if(value instanceof PossibleValueEnum<?> pve)
{
return getValueAsLong(pve.getPossibleValueId());
}
else if(value instanceof String s)
{
if(!StringUtils.hasContent(s))
{
return (null);
}
try
{
return (Long.parseLong(s));
}
catch(NumberFormatException nfe)
{
if(s.contains(","))
{
String sWithoutCommas = s.replaceAll(",", "");
try
{
return (getValueAsLong(sWithoutCommas));
}
catch(Exception ignore)
{
throw (nfe);
}
}
if(s.matches(".*\\.\\d+$"))
{
String sWithoutDecimal = s.replaceAll("\\.\\d+$", "");
try
{
return (getValueAsLong(sWithoutDecimal));
}
catch(Exception ignore)
{
throw (nfe);
}
}
throw (nfe);
}
}
else
{
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to Long."));
}
}
catch(QValueException qve)
{
throw (qve);
}
catch(Exception e)
{
throw (new QValueException("Value [" + value + "] could not be converted to a Long.", e));
}
}
/******************************************************************************* /*******************************************************************************
** Type-safely make an Integer from any Object. ** Type-safely make an Integer from any Object.
** null and empty-string inputs return null. ** null and empty-string inputs return null.
@ -693,6 +800,7 @@ public class ValueUtils
{ {
case STRING, TEXT, HTML, PASSWORD -> getValueAsString(value); case STRING, TEXT, HTML, PASSWORD -> getValueAsString(value);
case INTEGER -> getValueAsInteger(value); case INTEGER -> getValueAsInteger(value);
case LONG -> getValueAsLong(value);
case DECIMAL -> getValueAsBigDecimal(value); case DECIMAL -> getValueAsBigDecimal(value);
case BOOLEAN -> getValueAsBoolean(value); case BOOLEAN -> getValueAsBoolean(value);
case DATE -> getValueAsLocalDate(value); case DATE -> getValueAsLocalDate(value);

View File

@ -0,0 +1,135 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.utils.aggregates;
import java.math.BigDecimal;
/*******************************************************************************
** Long version of data aggregator
*******************************************************************************/
public class LongAggregates implements AggregatesInterface<Long>
{
private int count = 0;
// private Long countDistinct;
private Long sum;
private Long min;
private Long max;
/*******************************************************************************
** Add a new value to this aggregate set
*******************************************************************************/
public void add(Long input)
{
if(input == null)
{
return;
}
count++;
if(sum == null)
{
sum = input;
}
else
{
sum = sum + input;
}
if(min == null || input < min)
{
min = input;
}
if(max == null || input > max)
{
max = input;
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public int getCount()
{
return (count);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Long getSum()
{
return (sum);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Long getMin()
{
return (min);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Long getMax()
{
return (max);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public BigDecimal getAverage()
{
if(this.count > 0)
{
return (BigDecimal.valueOf(this.sum.doubleValue() / (double) this.count));
}
else
{
return (null);
}
}
}

View File

@ -154,7 +154,7 @@ public abstract class AbstractRDBMSAction implements QActionInterface
if("".equals(value)) if("".equals(value))
{ {
QFieldType type = field.getType(); QFieldType type = field.getType();
if(type.equals(QFieldType.INTEGER) || type.equals(QFieldType.DECIMAL) || type.equals(QFieldType.DATE) || type.equals(QFieldType.DATE_TIME) || type.equals(QFieldType.BOOLEAN)) if(type.equals(QFieldType.INTEGER) || type.equals(QFieldType.LONG) || type.equals(QFieldType.DECIMAL) || type.equals(QFieldType.DATE) || type.equals(QFieldType.DATE_TIME) || type.equals(QFieldType.BOOLEAN))
{ {
value = null; value = null;
} }
@ -875,6 +875,10 @@ public abstract class AbstractRDBMSAction implements QActionInterface
{ {
return (QueryManager.getInteger(resultSet, i)); return (QueryManager.getInteger(resultSet, i));
} }
case LONG:
{
return (QueryManager.getLong(resultSet, i));
}
case DECIMAL: case DECIMAL:
{ {
return (QueryManager.getBigDecimal(resultSet, i)); return (QueryManager.getBigDecimal(resultSet, i));

View File

@ -143,7 +143,7 @@ public class RDBMSAggregateAction extends AbstractRDBMSAction implements Aggrega
QFieldType fieldType = aggregate.getFieldType(); QFieldType fieldType = aggregate.getFieldType();
if(fieldType == null) if(fieldType == null)
{ {
if(field.getType().equals(QFieldType.INTEGER) && (aggregate.getOperator().equals(AggregateOperator.AVG))) if((field.getType().equals(QFieldType.INTEGER) || field.getType().equals(QFieldType.LONG)) && (aggregate.getOperator().equals(AggregateOperator.AVG)))
{ {
fieldType = QFieldType.DECIMAL; fieldType = QFieldType.DECIMAL;
} }

View File

@ -1680,7 +1680,7 @@ public class GenerateOpenApiSpecAction extends AbstractQActionFunction<GenerateO
return switch(type) return switch(type)
{ {
case STRING, DATE, TIME, DATE_TIME, TEXT, HTML, PASSWORD, BLOB -> "string"; case STRING, DATE, TIME, DATE_TIME, TEXT, HTML, PASSWORD, BLOB -> "string";
case INTEGER -> "integer"; case INTEGER, LONG -> "integer"; // todo - we could give 'format' w/ int32 & int64 to further specify
case DECIMAL -> "number"; case DECIMAL -> "number";
case BOOLEAN -> "boolean"; case BOOLEAN -> "boolean";
}; };

View File

@ -415,6 +415,7 @@ public class QCommandBuilder
{ {
case STRING, TEXT, HTML, PASSWORD -> String.class; case STRING, TEXT, HTML, PASSWORD -> String.class;
case INTEGER -> Integer.class; case INTEGER -> Integer.class;
case LONG -> Long.class;
case DECIMAL -> BigDecimal.class; case DECIMAL -> BigDecimal.class;
case DATE -> LocalDate.class; case DATE -> LocalDate.class;
case TIME -> LocalTime.class; case TIME -> LocalTime.class;