mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
more entity->QRecord abilities; add TIME & BOOLEAN types
This commit is contained in:
@ -47,7 +47,10 @@ import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEd
|
|||||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveFileStep;
|
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveFileStep;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertStoreRecordsStep;
|
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertStoreRecordsStep;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.general.LoadInitialRecordsStep;
|
import com.kingsrook.qqq.backend.core.processes.implementations.general.LoadInitialRecordsStep;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -57,6 +60,10 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QInstanceEnricher
|
public class QInstanceEnricher
|
||||||
{
|
{
|
||||||
|
private static final Logger LOG = LogManager.getLogger(QInstanceEnricher.class);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -182,6 +189,16 @@ public class QInstanceEnricher
|
|||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(name.length() == 0)
|
||||||
|
{
|
||||||
|
return ("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(name.length() == 1)
|
||||||
|
{
|
||||||
|
return (name.substring(0, 1).toUpperCase(Locale.ROOT));
|
||||||
|
}
|
||||||
|
|
||||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z])", " $1"));
|
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z])", " $1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,4 +420,96 @@ public class QInstanceEnricher
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** for all fields in a table, set their backendName, using the default "inference" logic
|
||||||
|
** see {@link #inferBackendName(String)}
|
||||||
|
*******************************************************************************/
|
||||||
|
public static void setInferredFieldBackendNames(QTableMetaData tableMetaData)
|
||||||
|
{
|
||||||
|
if(tableMetaData == null)
|
||||||
|
{
|
||||||
|
LOG.warn("Requested to infer field backend names with a null table as input. Returning with noop.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(CollectionUtils.nullSafeIsEmpty(tableMetaData.getFields()))
|
||||||
|
{
|
||||||
|
LOG.warn("Requested to infer field backend names on a table [" + tableMetaData.getName() + "] with no fields. Returning with noop.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(QFieldMetaData field : tableMetaData.getFields().values())
|
||||||
|
{
|
||||||
|
String fieldName = field.getName();
|
||||||
|
String fieldBackendName = field.getBackendName();
|
||||||
|
if(!StringUtils.hasContent(fieldBackendName))
|
||||||
|
{
|
||||||
|
String backendName = inferBackendName(fieldName);
|
||||||
|
field.setBackendName(backendName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Do a default mapping from a camelCase field name to an underscore_style
|
||||||
|
** name for a backend.
|
||||||
|
*******************************************************************************/
|
||||||
|
static String inferBackendName(String fieldName)
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// build a list of words in the name, then join them with _ and lower-case the result //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
List<String> words = new ArrayList<>();
|
||||||
|
StringBuilder currentWord = new StringBuilder();
|
||||||
|
for(int i = 0; i < fieldName.length(); i++)
|
||||||
|
{
|
||||||
|
Character thisChar = fieldName.charAt(i);
|
||||||
|
Character nextChar = i < (fieldName.length() - 1) ? fieldName.charAt(i + 1) : null;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// if we're at the end of the whole string, then we're at the end of the last word //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(nextChar == null)
|
||||||
|
{
|
||||||
|
currentWord.append(thisChar);
|
||||||
|
words.add(currentWord.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
// transitioning from a lower to an upper starts a word. //
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
else if(Character.isLowerCase(thisChar) && Character.isUpperCase(nextChar))
|
||||||
|
{
|
||||||
|
currentWord.append(thisChar);
|
||||||
|
words.add(currentWord.toString());
|
||||||
|
currentWord = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// transitioning from an upper to a lower - it starts a word, as long as there were already letters in the current word //
|
||||||
|
// e.g., on wordThenTLAInMiddle, when thisChar=I and nextChar=n. currentWord will be "TLA". So finish that word, and start a new one with the 'I' //
|
||||||
|
// but the normal single-upper condition, e.g., firstName, when thisChar=N and nextChar=a, current word will be empty string, so just append the 'a' to it //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
else if(Character.isUpperCase(thisChar) && Character.isLowerCase(nextChar) && currentWord.length() > 0)
|
||||||
|
{
|
||||||
|
words.add(currentWord.toString());
|
||||||
|
currentWord = new StringBuilder();
|
||||||
|
currentWord.append(thisChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
// by default, just add this character to the current word //
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentWord.append(thisChar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (String.join("_", words).toLowerCase(Locale.ROOT));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,12 +32,33 @@ import java.util.List;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class QFilterCriteria implements Serializable
|
public class QFilterCriteria implements Serializable
|
||||||
{
|
{
|
||||||
private String fieldName;
|
private String fieldName;
|
||||||
private QCriteriaOperator operator;
|
private QCriteriaOperator operator;
|
||||||
private List<Serializable> values;
|
private List<Serializable> values;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QFilterCriteria()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QFilterCriteria(String fieldName, QCriteriaOperator operator, List<Serializable> values)
|
||||||
|
{
|
||||||
|
this.fieldName = fieldName;
|
||||||
|
this.operator = operator;
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Getter for fieldName
|
** Getter for fieldName
|
||||||
**
|
**
|
||||||
@ -127,6 +148,7 @@ public class QFilterCriteria implements Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Setter for values
|
** Setter for values
|
||||||
**
|
**
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.model.data;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface QField
|
||||||
|
{
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
String backendName() default "";
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
boolean isRequired() default false;
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
boolean isEditable() default true;
|
||||||
|
}
|
@ -23,8 +23,12 @@ package com.kingsrook.qqq.backend.core.model.data;
|
|||||||
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -104,7 +108,7 @@ public abstract class QRecordEntity
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
private static List<QRecordEntityField> getFieldList(Class<? extends QRecordEntity> c)
|
public static List<QRecordEntityField> getFieldList(Class<? extends QRecordEntity> c)
|
||||||
{
|
{
|
||||||
if(!fieldMapping.containsKey(c))
|
if(!fieldMapping.containsKey(c))
|
||||||
{
|
{
|
||||||
@ -114,10 +118,12 @@ public abstract class QRecordEntity
|
|||||||
if(isGetter(possibleGetter))
|
if(isGetter(possibleGetter))
|
||||||
{
|
{
|
||||||
Optional<Method> setter = getSetterForGetter(c, possibleGetter);
|
Optional<Method> setter = getSetterForGetter(c, possibleGetter);
|
||||||
|
|
||||||
if(setter.isPresent())
|
if(setter.isPresent())
|
||||||
{
|
{
|
||||||
String name = getFieldNameFromGetter(possibleGetter);
|
String fieldName = getFieldNameFromGetter(possibleGetter);
|
||||||
fieldList.add(new QRecordEntityField(name, possibleGetter, setter.get(), possibleGetter.getReturnType()));
|
Optional<QField> fieldAnnotation = getQFieldAnnotation(c, fieldName);
|
||||||
|
fieldList.add(new QRecordEntityField(fieldName, possibleGetter, setter.get(), possibleGetter.getReturnType(), fieldAnnotation.orElse(null)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -132,6 +138,27 @@ public abstract class QRecordEntity
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static Optional<QField> getQFieldAnnotation(Class<? extends QRecordEntity> c, String fieldName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Field field = c.getDeclaredField(fieldName);
|
||||||
|
return (Optional.ofNullable(field.getAnnotation(QField.class)));
|
||||||
|
}
|
||||||
|
catch(NoSuchFieldException e)
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////
|
||||||
|
// ok, we just won't have an annotation //
|
||||||
|
//////////////////////////////////////////
|
||||||
|
}
|
||||||
|
return (Optional.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -207,7 +234,15 @@ public abstract class QRecordEntity
|
|||||||
|| returnType.equals(int.class)
|
|| returnType.equals(int.class)
|
||||||
|| returnType.equals(Boolean.class)
|
|| returnType.equals(Boolean.class)
|
||||||
|| returnType.equals(boolean.class)
|
|| returnType.equals(boolean.class)
|
||||||
|| returnType.equals(BigDecimal.class));
|
|| returnType.equals(BigDecimal.class)
|
||||||
|
|| returnType.equals(Instant.class)
|
||||||
|
|| returnType.equals(LocalDate.class)
|
||||||
|
|| returnType.equals(LocalTime.class));
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
// note - this list has implications upon: //
|
||||||
|
// - QFieldType.fromClass //
|
||||||
|
// - QRecordEntityField.convertValueType //
|
||||||
|
/////////////////////////////////////////////
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ package com.kingsrook.qqq.backend.core.model.data;
|
|||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
|
|
||||||
@ -38,18 +40,20 @@ public class QRecordEntityField
|
|||||||
private final Method getter;
|
private final Method getter;
|
||||||
private final Method setter;
|
private final Method setter;
|
||||||
private final Class<?> type;
|
private final Class<?> type;
|
||||||
|
private final QField fieldAnnotation;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Constructor.
|
** Constructor.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public QRecordEntityField(String fieldName, Method getter, Method setter, Class<?> type)
|
public QRecordEntityField(String fieldName, Method getter, Method setter, Class<?> type, QField fieldAnnotation)
|
||||||
{
|
{
|
||||||
this.fieldName = fieldName;
|
this.fieldName = fieldName;
|
||||||
this.getter = getter;
|
this.getter = getter;
|
||||||
this.setter = setter;
|
this.setter = setter;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.fieldAnnotation = fieldAnnotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -98,39 +102,67 @@ public class QRecordEntityField
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for fieldAnnotation
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QField getFieldAnnotation()
|
||||||
|
{
|
||||||
|
return fieldAnnotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public Object convertValueType(Serializable value)
|
public Object convertValueType(Serializable value)
|
||||||
{
|
{
|
||||||
if(value == null)
|
try
|
||||||
{
|
{
|
||||||
return (null);
|
if(value == null)
|
||||||
}
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
if(value.getClass().equals(type))
|
if(value.getClass().equals(type))
|
||||||
{
|
{
|
||||||
return (value);
|
return (value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type.equals(String.class))
|
if(type.equals(String.class))
|
||||||
{
|
{
|
||||||
return (ValueUtils.getValueAsString(value));
|
return (ValueUtils.getValueAsString(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type.equals(Integer.class) || type.equals(int.class))
|
if(type.equals(Integer.class) || type.equals(int.class))
|
||||||
{
|
{
|
||||||
return (ValueUtils.getValueAsInteger(value));
|
return (ValueUtils.getValueAsInteger(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type.equals(Boolean.class) || type.equals(boolean.class))
|
if(type.equals(Boolean.class) || type.equals(boolean.class))
|
||||||
{
|
{
|
||||||
return (ValueUtils.getValueAsBoolean(value));
|
return (ValueUtils.getValueAsBoolean(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(type.equals(BigDecimal.class))
|
if(type.equals(BigDecimal.class))
|
||||||
|
{
|
||||||
|
return (ValueUtils.getValueAsBigDecimal(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type.equals(LocalDate.class))
|
||||||
|
{
|
||||||
|
return (ValueUtils.getValueAsLocalDate(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type.equals(Instant.class))
|
||||||
|
{
|
||||||
|
return (ValueUtils.getValueAsInstant(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
return (ValueUtils.getValueAsBigDecimal(value));
|
throw (new QValueException("Exception converting value [" + value + "] for field [" + fieldName + "]", e));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw (new QValueException("Unhandled value type [" + type + "] for field [" + fieldName + "]"));
|
throw (new QValueException("Unhandled value type [" + type + "] for field [" + fieldName + "]"));
|
||||||
|
@ -24,9 +24,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Optional;
|
||||||
import com.github.hervian.reflection.Fun;
|
import com.github.hervian.reflection.Fun;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -77,12 +81,49 @@ public class QFieldMetaData
|
|||||||
** e.g., new QFieldMetaData(Order::getOrderNo).
|
** e.g., new QFieldMetaData(Order::getOrderNo).
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public <T> QFieldMetaData(Fun.With1ParamAndVoid<T> getterRef) throws QException
|
public <T> QFieldMetaData(Fun.With1ParamAndVoid<T> getterRef) throws QException
|
||||||
|
{
|
||||||
|
Method getter = Fun.toMethod(getterRef);
|
||||||
|
constructFromGetter(getter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Initialize a fieldMetaData from a getter method from an entity
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public <T> QFieldMetaData(Method getter) throws QException
|
||||||
|
{
|
||||||
|
constructFromGetter(getter);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** From a getter method, populate attributes in this field meta-data, including
|
||||||
|
** those from the @QField annotation on the field in the class, if present.
|
||||||
|
*******************************************************************************/
|
||||||
|
private void constructFromGetter(Method getter) throws QException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Method getter = Fun.toMethod(getterRef);
|
|
||||||
this.name = QRecordEntity.getFieldNameFromGetter(getter);
|
this.name = QRecordEntity.getFieldNameFromGetter(getter);
|
||||||
this.type = QFieldType.fromClass(getter.getReturnType());
|
this.type = QFieldType.fromClass(getter.getReturnType());
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Optional<QField> optionalFieldAnnotation = QRecordEntity.getQFieldAnnotation((Class<? extends QRecordEntity>) getter.getDeclaringClass(), this.name);
|
||||||
|
|
||||||
|
if(optionalFieldAnnotation.isPresent())
|
||||||
|
{
|
||||||
|
QField fieldAnnotation = optionalFieldAnnotation.get();
|
||||||
|
setIsRequired(fieldAnnotation.isRequired());
|
||||||
|
setIsEditable(fieldAnnotation.isEditable());
|
||||||
|
|
||||||
|
if(StringUtils.hasContent(fieldAnnotation.backendName()))
|
||||||
|
{
|
||||||
|
setBackendName(fieldAnnotation.backendName());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(QException qe)
|
catch(QException qe)
|
||||||
{
|
{
|
||||||
@ -90,7 +131,7 @@ public class QFieldMetaData
|
|||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
throw (new QException("Error constructing field from getterRef: " + getterRef, e));
|
throw (new QException("Error constructing field from getter method: " + getter.getName(), e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
|||||||
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalTime;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
|
||||||
|
|
||||||
@ -35,8 +38,9 @@ public enum QFieldType
|
|||||||
STRING,
|
STRING,
|
||||||
INTEGER,
|
INTEGER,
|
||||||
DECIMAL,
|
DECIMAL,
|
||||||
|
BOOLEAN,
|
||||||
DATE,
|
DATE,
|
||||||
// TIME,
|
TIME,
|
||||||
DATE_TIME,
|
DATE_TIME,
|
||||||
TEXT,
|
TEXT,
|
||||||
HTML,
|
HTML,
|
||||||
@ -65,6 +69,22 @@ public enum QFieldType
|
|||||||
{
|
{
|
||||||
return (DECIMAL);
|
return (DECIMAL);
|
||||||
}
|
}
|
||||||
|
if(c.equals(Instant.class))
|
||||||
|
{
|
||||||
|
return (DATE_TIME);
|
||||||
|
}
|
||||||
|
if(c.equals(LocalDate.class))
|
||||||
|
{
|
||||||
|
return (DATE);
|
||||||
|
}
|
||||||
|
if(c.equals(LocalTime.class))
|
||||||
|
{
|
||||||
|
return (TIME);
|
||||||
|
}
|
||||||
|
if(c.equals(Boolean.class))
|
||||||
|
{
|
||||||
|
return (BOOLEAN);
|
||||||
|
}
|
||||||
|
|
||||||
throw (new QException("Unrecognized class [" + c + "]"));
|
throw (new QException("Unrecognized class [" + c + "]"));
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,10 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
|
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||||
|
|
||||||
@ -91,6 +95,22 @@ public class QTableMetaData implements Serializable
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QTableMetaData withFieldsFromEntity(Class<? extends QRecordEntity> entityClass) throws QException
|
||||||
|
{
|
||||||
|
List<QRecordEntityField> recordEntityFieldList = QRecordEntity.getFieldList(entityClass);
|
||||||
|
for(QRecordEntityField recordEntityField : recordEntityFieldList)
|
||||||
|
{
|
||||||
|
QFieldMetaData field = new QFieldMetaData(recordEntityField.getGetter());
|
||||||
|
addField(field);
|
||||||
|
}
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@ -434,4 +454,15 @@ public class QTableMetaData implements Serializable
|
|||||||
return (this);
|
return (this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public QTableMetaData withInferredFieldBackendNames()
|
||||||
|
{
|
||||||
|
QInstanceEnricher.setInferredFieldBackendNames(this);
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ public class ListingHash<K, V> implements Map<K, List<V>>, Serializable
|
|||||||
{
|
{
|
||||||
public static final long serialVersionUID = 0L;
|
public static final long serialVersionUID = 0L;
|
||||||
|
|
||||||
private HashMap<K, List<V>> hashMap = null;
|
private Map<K, List<V>> hashMap = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -51,7 +51,19 @@ public class ListingHash<K, V> implements Map<K, List<V>>, Serializable
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public ListingHash()
|
public ListingHash()
|
||||||
{
|
{
|
||||||
this.hashMap = new HashMap<K, List<V>>();
|
this.hashMap = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Constructor where you can supply a source map (e.g., if you want a specific
|
||||||
|
** Map type (like LinkedHashMap), or with pre-values
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public ListingHash(Map<K, List<V>> sourceMap)
|
||||||
|
{
|
||||||
|
this.hashMap = sourceMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,11 +23,14 @@ package com.kingsrook.qqq.backend.core.utils;
|
|||||||
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||||
|
|
||||||
@ -37,7 +40,8 @@ import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class ValueUtils
|
public class ValueUtils
|
||||||
{
|
{
|
||||||
private static final DateTimeFormatter localDateDefaultFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
private static final DateTimeFormatter yyyyMMddWithDashesFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
private static final DateTimeFormatter MdyyyyWithSlashesFormatter = DateTimeFormatter.ofPattern("M/d/yyyy");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -174,9 +178,13 @@ public class ValueUtils
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw (new IllegalArgumentException("Unsupported class " + value.getClass().getName() + " for converting to Integer."));
|
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to Integer."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(QValueException qve)
|
||||||
|
{
|
||||||
|
throw (qve);
|
||||||
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
throw (new QValueException("Value [" + value + "] could not be converted to an Integer.", e));
|
throw (new QValueException("Value [" + value + "] could not be converted to an Integer.", e));
|
||||||
@ -212,8 +220,8 @@ public class ValueUtils
|
|||||||
}
|
}
|
||||||
else if(value instanceof Calendar c)
|
else if(value instanceof Calendar c)
|
||||||
{
|
{
|
||||||
TimeZone tz = c.getTimeZone();
|
TimeZone tz = c.getTimeZone();
|
||||||
ZoneId zid = (tz == null) ? ZoneId.systemDefault() : tz.toZoneId();
|
ZoneId zid = (tz == null) ? ZoneId.systemDefault() : tz.toZoneId();
|
||||||
return LocalDateTime.ofInstant(c.toInstant(), zid).toLocalDate();
|
return LocalDateTime.ofInstant(c.toInstant(), zid).toLocalDate();
|
||||||
}
|
}
|
||||||
else if(value instanceof LocalDateTime ldt)
|
else if(value instanceof LocalDateTime ldt)
|
||||||
@ -227,21 +235,47 @@ public class ValueUtils
|
|||||||
return (null);
|
return (null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalDate.parse(s, localDateDefaultFormatter);
|
return tryLocalDateParsers(s);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw (new IllegalArgumentException("Unsupported class " + value.getClass().getName() + " for converting to LocalDate."));
|
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to LocalDate."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(QValueException qve)
|
||||||
|
{
|
||||||
|
throw (qve);
|
||||||
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
throw (new QValueException("Value [" + value + "] could not be converted to an LocalDate.", e));
|
throw (new QValueException("Value [" + value + "] could not be converted to a LocalDate.", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static LocalDate tryLocalDateParsers(String s)
|
||||||
|
{
|
||||||
|
DateTimeParseException lastException = null;
|
||||||
|
for(DateTimeFormatter dateTimeFormatter : List.of(yyyyMMddWithDashesFormatter, MdyyyyWithSlashesFormatter))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return LocalDate.parse(s, dateTimeFormatter);
|
||||||
|
}
|
||||||
|
catch(DateTimeParseException dtpe)
|
||||||
|
{
|
||||||
|
lastException = dtpe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw (new QValueException("Could not parse value [" + s + "] to a local date", lastException));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Type-safely make a BigDecimal from any Object.
|
** Type-safely make a BigDecimal from any Object.
|
||||||
** null and empty-string inputs return null.
|
** null and empty-string inputs return null.
|
||||||
@ -305,13 +339,80 @@ public class ValueUtils
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw (new IllegalArgumentException("Unsupported class " + value.getClass().getName() + " for converting to BigDecimal."));
|
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to BigDecimal."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(QValueException qve)
|
||||||
|
{
|
||||||
|
throw (qve);
|
||||||
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
throw (new QValueException("Value [" + value + "] could not be converted to an BigDecimal.", e));
|
throw (new QValueException("Value [" + value + "] could not be converted to a BigDecimal.", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Type-safely make an Instant from any Object.
|
||||||
|
** null and empty-string inputs return null.
|
||||||
|
** We may throw if the input can't be converted to a Instant
|
||||||
|
*******************************************************************************/
|
||||||
|
public static Instant getValueAsInstant(Object value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(value == null)
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
else if(value instanceof Instant i)
|
||||||
|
{
|
||||||
|
return (i);
|
||||||
|
}
|
||||||
|
else if(value instanceof java.sql.Date d)
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// note - in the jdk, this method throws UnsupportedOperationException (because of the lack of time in sql Dates) //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
return d.toInstant();
|
||||||
|
}
|
||||||
|
else if(value instanceof java.util.Date d)
|
||||||
|
{
|
||||||
|
return d.toInstant();
|
||||||
|
}
|
||||||
|
else if(value instanceof Calendar c)
|
||||||
|
{
|
||||||
|
TimeZone tz = c.getTimeZone();
|
||||||
|
return (c.toInstant());
|
||||||
|
}
|
||||||
|
else if(value instanceof LocalDateTime ldt)
|
||||||
|
{
|
||||||
|
ZoneId zoneId = ZoneId.systemDefault();
|
||||||
|
return ldt.toInstant(zoneId.getRules().getOffset(ldt));
|
||||||
|
}
|
||||||
|
else if(value instanceof String s)
|
||||||
|
{
|
||||||
|
if(!StringUtils.hasContent(s))
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Instant.parse(s);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to Instant."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(QValueException qve)
|
||||||
|
{
|
||||||
|
throw (qve);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
throw (new QValueException("Value [" + value + "] could not be converted to a Instant.", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,15 @@
|
|||||||
package com.kingsrook.qqq.backend.core.actions.metadata;
|
package com.kingsrook.qqq.backend.core.actions.metadata;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -47,9 +50,18 @@ class MetaDataActionTest
|
|||||||
request.setSession(TestUtils.getMockSession());
|
request.setSession(TestUtils.getMockSession());
|
||||||
MetaDataOutput result = new MetaDataAction().execute(request);
|
MetaDataOutput result = new MetaDataAction().execute(request);
|
||||||
assertNotNull(result);
|
assertNotNull(result);
|
||||||
|
|
||||||
assertNotNull(result.getTables());
|
assertNotNull(result.getTables());
|
||||||
assertNotNull(result.getTables().get("person"));
|
assertNotNull(result.getTables().get("person"));
|
||||||
assertEquals("person", result.getTables().get("person").getName());
|
assertEquals("person", result.getTables().get("person").getName());
|
||||||
assertEquals("Person", result.getTables().get("person").getLabel());
|
assertEquals("Person", result.getTables().get("person").getLabel());
|
||||||
|
|
||||||
|
assertNotNull(result.getProcesses().get("greet"));
|
||||||
|
assertNotNull(result.getProcesses().get("greetInteractive"));
|
||||||
|
assertNotNull(result.getProcesses().get("etl.basic"));
|
||||||
|
assertNotNull(result.getProcesses().get("person.bulkInsert"));
|
||||||
|
assertNotNull(result.getProcesses().get("person.bulkEdit"));
|
||||||
|
assertNotNull(result.getProcesses().get("person.bulkDelete"));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,8 @@ public class RunBackendStepActionTest
|
|||||||
case STRING -> "ABC";
|
case STRING -> "ABC";
|
||||||
case INTEGER -> 42;
|
case INTEGER -> 42;
|
||||||
case DECIMAL -> new BigDecimal("47");
|
case DECIMAL -> new BigDecimal("47");
|
||||||
case DATE, DATE_TIME -> null;
|
case BOOLEAN -> true;
|
||||||
|
case DATE, TIME, DATE_TIME -> null;
|
||||||
case TEXT -> """
|
case TEXT -> """
|
||||||
ABC
|
ABC
|
||||||
XYZ""";
|
XYZ""";
|
||||||
|
@ -22,8 +22,11 @@
|
|||||||
package com.kingsrook.qqq.backend.core.instances;
|
package com.kingsrook.qqq.backend.core.instances;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||||
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.QInstance;
|
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
import com.kingsrook.qqq.backend.core.utils.TestUtils;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -45,7 +48,7 @@ class QInstanceEnricherTest
|
|||||||
@Test
|
@Test
|
||||||
public void test_nullTableLabelComesFromName()
|
public void test_nullTableLabelComesFromName()
|
||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
QTableMetaData personTable = qInstance.getTable("person");
|
QTableMetaData personTable = qInstance.getTable("person");
|
||||||
personTable.setLabel(null);
|
personTable.setLabel(null);
|
||||||
assertNull(personTable.getLabel());
|
assertNull(personTable.getLabel());
|
||||||
@ -54,6 +57,7 @@ class QInstanceEnricherTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Test that a table missing a label and a name doesn't NPE, but just keeps
|
** Test that a table missing a label and a name doesn't NPE, but just keeps
|
||||||
** the name & label both null.
|
** the name & label both null.
|
||||||
@ -62,7 +66,7 @@ class QInstanceEnricherTest
|
|||||||
@Test
|
@Test
|
||||||
public void test_nullNameGivesNullLabel()
|
public void test_nullNameGivesNullLabel()
|
||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
QTableMetaData personTable = qInstance.getTable("person");
|
QTableMetaData personTable = qInstance.getTable("person");
|
||||||
personTable.setLabel(null);
|
personTable.setLabel(null);
|
||||||
personTable.setName(null);
|
personTable.setName(null);
|
||||||
@ -74,6 +78,7 @@ class QInstanceEnricherTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Test that a field missing a label gets the default label applied (name w/ UC-first)
|
** Test that a field missing a label gets the default label applied (name w/ UC-first)
|
||||||
**
|
**
|
||||||
@ -81,12 +86,64 @@ class QInstanceEnricherTest
|
|||||||
@Test
|
@Test
|
||||||
public void test_nullFieldLabelComesFromName()
|
public void test_nullFieldLabelComesFromName()
|
||||||
{
|
{
|
||||||
QInstance qInstance = TestUtils.defineInstance();
|
QInstance qInstance = TestUtils.defineInstance();
|
||||||
QFieldMetaData idField = qInstance.getTable("person").getField("id");
|
QFieldMetaData idField = qInstance.getTable("person").getField("id");
|
||||||
idField.setLabel(null);
|
idField.setLabel(null);
|
||||||
assertNull(idField.getLabel());
|
assertNull(idField.getLabel());
|
||||||
new QInstanceEnricher().enrich(qInstance);
|
new QInstanceEnricher().enrich(qInstance);
|
||||||
assertEquals("Id", idField.getLabel());
|
assertEquals("Id", idField.getLabel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testSetInferredFieldBackendNames()
|
||||||
|
{
|
||||||
|
QTableMetaData table = new QTableMetaData()
|
||||||
|
.withField(new QFieldMetaData("id", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("firstName", QFieldType.INTEGER))
|
||||||
|
.withField(new QFieldMetaData("nonstandard", QFieldType.INTEGER).withBackendName("whateverImNon_standard"));
|
||||||
|
QInstanceEnricher.setInferredFieldBackendNames(table);
|
||||||
|
assertEquals("id", table.getField("id").getBackendName());
|
||||||
|
assertEquals("first_name", table.getField("firstName").getBackendName());
|
||||||
|
assertEquals("whateverImNon_standard", table.getField("nonstandard").getBackendName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testSetInferredFieldBackendNamesEdgeCases()
|
||||||
|
{
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// make sure none of these cases throw (but all should warn) //
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
QInstanceEnricher.setInferredFieldBackendNames(null);
|
||||||
|
QInstanceEnricher.setInferredFieldBackendNames(new QTableMetaData());
|
||||||
|
QInstanceEnricher.setInferredFieldBackendNames(new QTableMetaData().withFields(Collections.emptyMap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testInferBackendName()
|
||||||
|
{
|
||||||
|
assertEquals("id", QInstanceEnricher.inferBackendName("id"));
|
||||||
|
assertEquals("word_another_word_more_words", QInstanceEnricher.inferBackendName("wordAnotherWordMoreWords"));
|
||||||
|
assertEquals("l_ul_ul_ul", QInstanceEnricher.inferBackendName("lUlUlUl"));
|
||||||
|
assertEquals("starts_upper", QInstanceEnricher.inferBackendName("StartsUpper"));
|
||||||
|
assertEquals("tla_first", QInstanceEnricher.inferBackendName("TLAFirst"));
|
||||||
|
assertEquals("word_then_tla_in_middle", QInstanceEnricher.inferBackendName("wordThenTLAInMiddle"));
|
||||||
|
assertEquals("end_with_tla", QInstanceEnricher.inferBackendName("endWithTLA"));
|
||||||
|
assertEquals("tla_and_another_tla", QInstanceEnricher.inferBackendName("TLAAndAnotherTLA"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -162,15 +162,74 @@ class QRecordEntityTest
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
@Test
|
@Test
|
||||||
void testQTableConstructionFromEntity() throws QException
|
void testQTableConstructionFromEntityGetterReferences() throws QException
|
||||||
{
|
{
|
||||||
QTableMetaData qTableMetaData = new QTableMetaData()
|
QTableMetaData qTableMetaData = new QTableMetaData()
|
||||||
.withField(new QFieldMetaData(Item::getSku))
|
.withField(new QFieldMetaData(Item::getSku))
|
||||||
.withField(new QFieldMetaData(Item::getDescription))
|
.withField(new QFieldMetaData(Item::getDescription))
|
||||||
.withField(new QFieldMetaData(Item::getQuantity));
|
.withField(new QFieldMetaData(Item::getQuantity))
|
||||||
|
.withField(new QFieldMetaData(Item::getFeatured))
|
||||||
|
.withField(new QFieldMetaData(Item::getPrice));
|
||||||
|
|
||||||
assertEquals(QFieldType.STRING, qTableMetaData.getField("sku").getType());
|
assertEquals(QFieldType.STRING, qTableMetaData.getField("sku").getType());
|
||||||
assertEquals(QFieldType.INTEGER, qTableMetaData.getField("quantity").getType());
|
assertEquals(QFieldType.INTEGER, qTableMetaData.getField("quantity").getType());
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// assert about attributes that came from @QField annotation //
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
assertTrue(qTableMetaData.getField("sku").getIsRequired());
|
||||||
|
assertFalse(qTableMetaData.getField("quantity").getIsEditable());
|
||||||
|
assertEquals("is_featured", qTableMetaData.getField("featured").getBackendName());
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// assert about attributes that weren't specified in @QField annotation //
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
assertTrue(qTableMetaData.getField("sku").getIsEditable());
|
||||||
|
assertFalse(qTableMetaData.getField("quantity").getIsRequired());
|
||||||
|
assertNull(qTableMetaData.getField("sku").getBackendName());
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// assert about attributes for fields without a @QField annotation //
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
assertTrue(qTableMetaData.getField("price").getIsEditable());
|
||||||
|
assertFalse(qTableMetaData.getField("price").getIsRequired());
|
||||||
|
assertNull(qTableMetaData.getField("price").getBackendName());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testQTableConstructionFromEntity() throws QException
|
||||||
|
{
|
||||||
|
QTableMetaData qTableMetaData = new QTableMetaData()
|
||||||
|
.withFieldsFromEntity(Item.class);
|
||||||
|
|
||||||
|
assertEquals(QFieldType.STRING, qTableMetaData.getField("sku").getType());
|
||||||
|
assertEquals(QFieldType.INTEGER, qTableMetaData.getField("quantity").getType());
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
// assert about attributes that came from @QField annotation //
|
||||||
|
///////////////////////////////////////////////////////////////
|
||||||
|
assertTrue(qTableMetaData.getField("sku").getIsRequired());
|
||||||
|
assertFalse(qTableMetaData.getField("quantity").getIsEditable());
|
||||||
|
assertEquals("is_featured", qTableMetaData.getField("featured").getBackendName());
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// assert about attributes that weren't specified in @QField annotation //
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
assertTrue(qTableMetaData.getField("sku").getIsEditable());
|
||||||
|
assertFalse(qTableMetaData.getField("quantity").getIsRequired());
|
||||||
|
assertNull(qTableMetaData.getField("sku").getBackendName());
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
// assert about attributes for fields without a @QField annotation //
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
assertTrue(qTableMetaData.getField("price").getIsEditable());
|
||||||
|
assertFalse(qTableMetaData.getField("price").getIsRequired());
|
||||||
|
assertNull(qTableMetaData.getField("price").getBackendName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.data.testentities;
|
|||||||
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||||
|
|
||||||
|
|
||||||
@ -31,11 +32,19 @@ import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class Item extends QRecordEntity
|
public class Item extends QRecordEntity
|
||||||
{
|
{
|
||||||
private String sku;
|
@QField(isRequired = true)
|
||||||
private String description;
|
private String sku;
|
||||||
private Integer quantity;
|
|
||||||
|
@QField()
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@QField(isEditable = false)
|
||||||
|
private Integer quantity;
|
||||||
|
|
||||||
private BigDecimal price;
|
private BigDecimal price;
|
||||||
private Boolean featured;
|
|
||||||
|
@QField(backendName = "is_featured")
|
||||||
|
private Boolean featured;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,16 +32,13 @@ 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.data.QRecord;
|
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
|
|
||||||
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
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.code.QCodeReference;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||||
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.QInstance;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
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.possiblevalues.QPossibleValueSourceType;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||||
@ -50,10 +47,13 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMet
|
|||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionOutputMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.processes.QRecordListMetaData;
|
||||||
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||||
import com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule;
|
import com.kingsrook.qqq.backend.core.modules.authentication.MockAuthenticationModule;
|
||||||
|
import com.kingsrook.qqq.backend.core.modules.authentication.metadata.QAuthenticationMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockBackendModule;
|
import com.kingsrook.qqq.backend.core.modules.backend.implementations.mock.MockBackendModule;
|
||||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicETLProcess;
|
import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicETLProcess;
|
||||||
|
import com.kingsrook.qqq.backend.core.processes.implementations.mock.MockBackendStep;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -62,9 +62,15 @@ import com.kingsrook.qqq.backend.core.processes.implementations.etl.basic.BasicE
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class TestUtils
|
public class TestUtils
|
||||||
{
|
{
|
||||||
public static String DEFAULT_BACKEND_NAME = "default";
|
public static final String DEFAULT_BACKEND_NAME = "default";
|
||||||
public static String PROCESS_NAME_GREET_PEOPLE = "greet";
|
|
||||||
public static String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive";
|
public static final String TABLE_NAME_PERSON = "person";
|
||||||
|
|
||||||
|
public static final String PROCESS_NAME_GREET_PEOPLE = "greet";
|
||||||
|
public static final String PROCESS_NAME_GREET_PEOPLE_INTERACTIVE = "greetInteractive";
|
||||||
|
public static final String PROCESS_NAME_ADD_TO_PEOPLES_AGE = "addToPeoplesAge";
|
||||||
|
public static final String TABLE_NAME_PERSON_FILE = "personFile";
|
||||||
|
public static final String TABLE_NAME_ID_AND_NAME_ONLY = "idAndNameOnly";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -138,7 +144,7 @@ public class TestUtils
|
|||||||
public static QTableMetaData defineTablePerson()
|
public static QTableMetaData defineTablePerson()
|
||||||
{
|
{
|
||||||
return new QTableMetaData()
|
return new QTableMetaData()
|
||||||
.withName("person")
|
.withName(TABLE_NAME_PERSON)
|
||||||
.withLabel("Person")
|
.withLabel("Person")
|
||||||
.withBackendName(DEFAULT_BACKEND_NAME)
|
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||||
.withPrimaryKeyField("id")
|
.withPrimaryKeyField("id")
|
||||||
@ -160,7 +166,7 @@ public class TestUtils
|
|||||||
public static QTableMetaData definePersonFileTable()
|
public static QTableMetaData definePersonFileTable()
|
||||||
{
|
{
|
||||||
return (new QTableMetaData()
|
return (new QTableMetaData()
|
||||||
.withName("personFile")
|
.withName(TABLE_NAME_PERSON_FILE)
|
||||||
.withLabel("Person File")
|
.withLabel("Person File")
|
||||||
.withBackendName(DEFAULT_BACKEND_NAME)
|
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||||
.withPrimaryKeyField("id")
|
.withPrimaryKeyField("id")
|
||||||
@ -175,7 +181,7 @@ public class TestUtils
|
|||||||
public static QTableMetaData defineTableIdAndNameOnly()
|
public static QTableMetaData defineTableIdAndNameOnly()
|
||||||
{
|
{
|
||||||
return new QTableMetaData()
|
return new QTableMetaData()
|
||||||
.withName("idAndNameOnly")
|
.withName(TABLE_NAME_ID_AND_NAME_ONLY)
|
||||||
.withLabel("Id and Name Only")
|
.withLabel("Id and Name Only")
|
||||||
.withBackendName(DEFAULT_BACKEND_NAME)
|
.withBackendName(DEFAULT_BACKEND_NAME)
|
||||||
.withPrimaryKeyField("id")
|
.withPrimaryKeyField("id")
|
||||||
@ -192,7 +198,7 @@ public class TestUtils
|
|||||||
{
|
{
|
||||||
return new QProcessMetaData()
|
return new QProcessMetaData()
|
||||||
.withName(PROCESS_NAME_GREET_PEOPLE)
|
.withName(PROCESS_NAME_GREET_PEOPLE)
|
||||||
.withTableName("person")
|
.withTableName(TABLE_NAME_PERSON)
|
||||||
.addStep(new QBackendStepMetaData()
|
.addStep(new QBackendStepMetaData()
|
||||||
.withName("prepare")
|
.withName("prepare")
|
||||||
.withCode(new QCodeReference()
|
.withCode(new QCodeReference()
|
||||||
@ -200,14 +206,14 @@ public class TestUtils
|
|||||||
.withCodeType(QCodeType.JAVA)
|
.withCodeType(QCodeType.JAVA)
|
||||||
.withCodeUsage(QCodeUsage.BACKEND_STEP)) // todo - needed, or implied in this context?
|
.withCodeUsage(QCodeUsage.BACKEND_STEP)) // todo - needed, or implied in this context?
|
||||||
.withInputData(new QFunctionInputMetaData()
|
.withInputData(new QFunctionInputMetaData()
|
||||||
.withRecordListMetaData(new QRecordListMetaData().withTableName("person"))
|
.withRecordListMetaData(new QRecordListMetaData().withTableName(TABLE_NAME_PERSON))
|
||||||
.withFieldList(List.of(
|
.withFieldList(List.of(
|
||||||
new QFieldMetaData("greetingPrefix", QFieldType.STRING),
|
new QFieldMetaData("greetingPrefix", QFieldType.STRING),
|
||||||
new QFieldMetaData("greetingSuffix", QFieldType.STRING)
|
new QFieldMetaData("greetingSuffix", QFieldType.STRING)
|
||||||
)))
|
)))
|
||||||
.withOutputMetaData(new QFunctionOutputMetaData()
|
.withOutputMetaData(new QFunctionOutputMetaData()
|
||||||
.withRecordListMetaData(new QRecordListMetaData()
|
.withRecordListMetaData(new QRecordListMetaData()
|
||||||
.withTableName("person")
|
.withTableName(TABLE_NAME_PERSON)
|
||||||
.addField(new QFieldMetaData("fullGreeting", QFieldType.STRING))
|
.addField(new QFieldMetaData("fullGreeting", QFieldType.STRING))
|
||||||
)
|
)
|
||||||
.withFieldList(List.of(new QFieldMetaData("outputMessage", QFieldType.STRING))))
|
.withFieldList(List.of(new QFieldMetaData("outputMessage", QFieldType.STRING))))
|
||||||
@ -223,7 +229,7 @@ public class TestUtils
|
|||||||
{
|
{
|
||||||
return new QProcessMetaData()
|
return new QProcessMetaData()
|
||||||
.withName(PROCESS_NAME_GREET_PEOPLE_INTERACTIVE)
|
.withName(PROCESS_NAME_GREET_PEOPLE_INTERACTIVE)
|
||||||
.withTableName("person")
|
.withTableName(TABLE_NAME_PERSON)
|
||||||
|
|
||||||
.addStep(new QFrontendStepMetaData()
|
.addStep(new QFrontendStepMetaData()
|
||||||
.withName("setup")
|
.withName("setup")
|
||||||
@ -238,14 +244,14 @@ public class TestUtils
|
|||||||
.withCodeType(QCodeType.JAVA)
|
.withCodeType(QCodeType.JAVA)
|
||||||
.withCodeUsage(QCodeUsage.BACKEND_STEP)) // todo - needed, or implied in this context?
|
.withCodeUsage(QCodeUsage.BACKEND_STEP)) // todo - needed, or implied in this context?
|
||||||
.withInputData(new QFunctionInputMetaData()
|
.withInputData(new QFunctionInputMetaData()
|
||||||
.withRecordListMetaData(new QRecordListMetaData().withTableName("person"))
|
.withRecordListMetaData(new QRecordListMetaData().withTableName(TABLE_NAME_PERSON))
|
||||||
.withFieldList(List.of(
|
.withFieldList(List.of(
|
||||||
new QFieldMetaData("greetingPrefix", QFieldType.STRING),
|
new QFieldMetaData("greetingPrefix", QFieldType.STRING),
|
||||||
new QFieldMetaData("greetingSuffix", QFieldType.STRING)
|
new QFieldMetaData("greetingSuffix", QFieldType.STRING)
|
||||||
)))
|
)))
|
||||||
.withOutputMetaData(new QFunctionOutputMetaData()
|
.withOutputMetaData(new QFunctionOutputMetaData()
|
||||||
.withRecordListMetaData(new QRecordListMetaData()
|
.withRecordListMetaData(new QRecordListMetaData()
|
||||||
.withTableName("person")
|
.withTableName(TABLE_NAME_PERSON)
|
||||||
.addField(new QFieldMetaData("fullGreeting", QFieldType.STRING))
|
.addField(new QFieldMetaData("fullGreeting", QFieldType.STRING))
|
||||||
)
|
)
|
||||||
.withFieldList(List.of(new QFieldMetaData("outputMessage", QFieldType.STRING))))
|
.withFieldList(List.of(new QFieldMetaData("outputMessage", QFieldType.STRING))))
|
||||||
@ -270,8 +276,8 @@ public class TestUtils
|
|||||||
private static QProcessMetaData defineProcessAddToPeoplesAge()
|
private static QProcessMetaData defineProcessAddToPeoplesAge()
|
||||||
{
|
{
|
||||||
return new QProcessMetaData()
|
return new QProcessMetaData()
|
||||||
.withName("addToPeoplesAge")
|
.withName(PROCESS_NAME_ADD_TO_PEOPLES_AGE)
|
||||||
.withTableName("person")
|
.withTableName(TABLE_NAME_PERSON)
|
||||||
.addStep(new QBackendStepMetaData()
|
.addStep(new QBackendStepMetaData()
|
||||||
.withName("getAgeStatistics")
|
.withName("getAgeStatistics")
|
||||||
.withCode(new QCodeReference()
|
.withCode(new QCodeReference()
|
||||||
@ -279,10 +285,10 @@ public class TestUtils
|
|||||||
.withCodeType(QCodeType.JAVA)
|
.withCodeType(QCodeType.JAVA)
|
||||||
.withCodeUsage(QCodeUsage.BACKEND_STEP))
|
.withCodeUsage(QCodeUsage.BACKEND_STEP))
|
||||||
.withInputData(new QFunctionInputMetaData()
|
.withInputData(new QFunctionInputMetaData()
|
||||||
.withRecordListMetaData(new QRecordListMetaData().withTableName("person")))
|
.withRecordListMetaData(new QRecordListMetaData().withTableName(TABLE_NAME_PERSON)))
|
||||||
.withOutputMetaData(new QFunctionOutputMetaData()
|
.withOutputMetaData(new QFunctionOutputMetaData()
|
||||||
.withRecordListMetaData(new QRecordListMetaData()
|
.withRecordListMetaData(new QRecordListMetaData()
|
||||||
.withTableName("person")
|
.withTableName(TABLE_NAME_PERSON)
|
||||||
.addField(new QFieldMetaData("age", QFieldType.INTEGER)))
|
.addField(new QFieldMetaData("age", QFieldType.INTEGER)))
|
||||||
.withFieldList(List.of(
|
.withFieldList(List.of(
|
||||||
new QFieldMetaData("minAge", QFieldType.INTEGER),
|
new QFieldMetaData("minAge", QFieldType.INTEGER),
|
||||||
@ -297,7 +303,7 @@ public class TestUtils
|
|||||||
.withFieldList(List.of(new QFieldMetaData("yearsToAdd", QFieldType.INTEGER))))
|
.withFieldList(List.of(new QFieldMetaData("yearsToAdd", QFieldType.INTEGER))))
|
||||||
.withOutputMetaData(new QFunctionOutputMetaData()
|
.withOutputMetaData(new QFunctionOutputMetaData()
|
||||||
.withRecordListMetaData(new QRecordListMetaData()
|
.withRecordListMetaData(new QRecordListMetaData()
|
||||||
.withTableName("person")
|
.withTableName(TABLE_NAME_PERSON)
|
||||||
.addField(new QFieldMetaData("newAge", QFieldType.INTEGER)))));
|
.addField(new QFieldMetaData("newAge", QFieldType.INTEGER)))));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,16 @@ package com.kingsrook.qqq.backend.core.utils;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.Month;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
|
||||||
@ -41,6 +49,7 @@ class ValueUtilsTest
|
|||||||
@Test
|
@Test
|
||||||
void testGetValueAsString() throws QValueException
|
void testGetValueAsString() throws QValueException
|
||||||
{
|
{
|
||||||
|
//noinspection ConstantConditions
|
||||||
assertNull(ValueUtils.getValueAsString(null));
|
assertNull(ValueUtils.getValueAsString(null));
|
||||||
assertEquals("", ValueUtils.getValueAsString(""));
|
assertEquals("", ValueUtils.getValueAsString(""));
|
||||||
assertEquals(" ", ValueUtils.getValueAsString(" "));
|
assertEquals(" ", ValueUtils.getValueAsString(" "));
|
||||||
@ -132,7 +141,9 @@ class ValueUtilsTest
|
|||||||
assertEquals(new BigDecimal("1"), ValueUtils.getValueAsBigDecimal(1.0F));
|
assertEquals(new BigDecimal("1"), ValueUtils.getValueAsBigDecimal(1.0F));
|
||||||
assertEquals(new BigDecimal("1"), ValueUtils.getValueAsBigDecimal(1.0D));
|
assertEquals(new BigDecimal("1"), ValueUtils.getValueAsBigDecimal(1.0D));
|
||||||
assertEquals(new BigDecimal("1000000000000"), ValueUtils.getValueAsBigDecimal(1_000_000_000_000L));
|
assertEquals(new BigDecimal("1000000000000"), ValueUtils.getValueAsBigDecimal(1_000_000_000_000L));
|
||||||
|
//noinspection ConstantConditions
|
||||||
assertEquals(0, new BigDecimal("1.1").compareTo(ValueUtils.getValueAsBigDecimal(1.1F).round(MathContext.DECIMAL32)));
|
assertEquals(0, new BigDecimal("1.1").compareTo(ValueUtils.getValueAsBigDecimal(1.1F).round(MathContext.DECIMAL32)));
|
||||||
|
//noinspection ConstantConditions
|
||||||
assertEquals(0, new BigDecimal("1.1").compareTo(ValueUtils.getValueAsBigDecimal(1.1D).round(MathContext.DECIMAL64)));
|
assertEquals(0, new BigDecimal("1.1").compareTo(ValueUtils.getValueAsBigDecimal(1.1D).round(MathContext.DECIMAL64)));
|
||||||
|
|
||||||
assertThrows(QValueException.class, () -> ValueUtils.getValueAsBigDecimal("a"));
|
assertThrows(QValueException.class, () -> ValueUtils.getValueAsBigDecimal("a"));
|
||||||
@ -140,4 +151,79 @@ class ValueUtilsTest
|
|||||||
assertThrows(QValueException.class, () -> ValueUtils.getValueAsBigDecimal(new Object()));
|
assertThrows(QValueException.class, () -> ValueUtils.getValueAsBigDecimal(new Object()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Test
|
||||||
|
void testGetValueAsLocalDate() throws QValueException
|
||||||
|
{
|
||||||
|
assertNull(ValueUtils.getValueAsLocalDate(null));
|
||||||
|
assertNull(ValueUtils.getValueAsLocalDate(""));
|
||||||
|
assertNull(ValueUtils.getValueAsLocalDate(" "));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(LocalDate.of(1980, 5, 31)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.sql.Date(80, 4, 31)));
|
||||||
|
//noinspection MagicConstant
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.util.Date(80, 4, 31)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31, 12, 0)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31, 4, 0)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new java.util.Date(80, Calendar.MAY, 31, 22, 0)));
|
||||||
|
//noinspection MagicConstant
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new GregorianCalendar(1980, 4, 31)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(new GregorianCalendar(1980, Calendar.MAY, 31)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, 5, 31, 12, 0)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, 5, 31, 4, 0)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, 5, 31, 22, 0)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate(LocalDateTime.of(1980, Month.MAY, 31, 12, 0)));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate("1980-05-31"));
|
||||||
|
assertEquals(LocalDate.of(1980, Month.MAY, 31), ValueUtils.getValueAsLocalDate("05/31/1980"));
|
||||||
|
|
||||||
|
assertThrows(QValueException.class, () -> ValueUtils.getValueAsLocalDate("a"));
|
||||||
|
assertThrows(QValueException.class, () -> ValueUtils.getValueAsLocalDate("a,b"));
|
||||||
|
assertThat(assertThrows(QValueException.class, () -> ValueUtils.getValueAsLocalDate("1980/05/31")).getMessage()).contains("parse");
|
||||||
|
assertThat(assertThrows(QValueException.class, () -> ValueUtils.getValueAsLocalDate(new Object())).getMessage()).contains("Unsupported class");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Test
|
||||||
|
void testGetValueAsInstant() throws QValueException
|
||||||
|
{
|
||||||
|
Instant expected = Instant.parse("1980-05-31T12:30:00Z");
|
||||||
|
|
||||||
|
assertNull(ValueUtils.getValueAsInstant(null));
|
||||||
|
assertNull(ValueUtils.getValueAsInstant(""));
|
||||||
|
assertNull(ValueUtils.getValueAsInstant(" "));
|
||||||
|
assertEquals(expected, ValueUtils.getValueAsInstant(expected));
|
||||||
|
assertEquals(expected, ValueUtils.getValueAsInstant("1980-05-31T12:30:00Z"));
|
||||||
|
|
||||||
|
////////////////////////////
|
||||||
|
// todo - time zone logic //
|
||||||
|
////////////////////////////
|
||||||
|
// //noinspection MagicConstant
|
||||||
|
// assertEquals(expected, ValueUtils.getValueAsInstant(new java.util.Date(80, 4, 31, 7, 30)));
|
||||||
|
|
||||||
|
// //noinspection MagicConstant
|
||||||
|
// assertEquals(expected, ValueUtils.getValueAsInstant(new GregorianCalendar(1980, 4, 31)));
|
||||||
|
// assertEquals(expected, ValueUtils.getValueAsInstant(new GregorianCalendar(1980, Calendar.MAY, 31)));
|
||||||
|
// // assertEquals(expected, ValueUtils.getValueAsInstant(InstantTime.of(1980, 5, 31, 12, 0)));
|
||||||
|
// // assertEquals(expected, ValueUtils.getValueAsInstant(InstantTime.of(1980, 5, 31, 4, 0)));
|
||||||
|
// // assertEquals(expected, ValueUtils.getValueAsInstant(InstantTime.of(1980, 5, 31, 22, 0)));
|
||||||
|
// // assertEquals(expected, ValueUtils.getValueAsInstant(InstantTime.of(1980, Month.MAY, 31, 12, 0)));
|
||||||
|
|
||||||
|
assertThrows(QValueException.class, () -> ValueUtils.getValueAsInstant(new java.sql.Date(80, 4, 31)));
|
||||||
|
assertThrows(QValueException.class, () -> ValueUtils.getValueAsInstant("a"));
|
||||||
|
assertThrows(QValueException.class, () -> ValueUtils.getValueAsInstant("a,b"));
|
||||||
|
assertThrows(QValueException.class, () -> ValueUtils.getValueAsInstant("1980/05/31"));
|
||||||
|
assertThat(assertThrows(QValueException.class, () -> ValueUtils.getValueAsInstant(new Object())).getMessage()).contains("Unsupported class");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -36,6 +36,7 @@ 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.tables.QTableMetaData;
|
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
|
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDetails;
|
||||||
@ -94,7 +95,8 @@ public abstract class AbstractRDBMSAction
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Handle obvious problems with values - like empty string for integer should be null.
|
** Handle obvious problems with values - like empty string for integer should be null,
|
||||||
|
** and type conversions that we can do "better" than jdbc...
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
protected Serializable scrubValue(QFieldMetaData field, Serializable value, boolean isInsert)
|
protected Serializable scrubValue(QFieldMetaData field, Serializable value, boolean isInsert)
|
||||||
@ -108,6 +110,18 @@ public abstract class AbstractRDBMSAction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// value utils is good at making values from strings - jdbc, not as much... //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
if(field.getType().equals(QFieldType.DATE) && value instanceof String)
|
||||||
|
{
|
||||||
|
value = ValueUtils.getValueAsLocalDate(value);
|
||||||
|
}
|
||||||
|
else if(field.getType().equals(QFieldType.DECIMAL) && value instanceof String)
|
||||||
|
{
|
||||||
|
value = ValueUtils.getValueAsBigDecimal(value);
|
||||||
|
}
|
||||||
|
|
||||||
return (value);
|
return (value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,8 +189,13 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
|
|||||||
}
|
}
|
||||||
case DATE:
|
case DATE:
|
||||||
{
|
{
|
||||||
|
// todo - queryManager.getLocalDate?
|
||||||
return (QueryManager.getDate(resultSet, i));
|
return (QueryManager.getDate(resultSet, i));
|
||||||
}
|
}
|
||||||
|
case TIME:
|
||||||
|
{
|
||||||
|
return (QueryManager.getLocalTime(resultSet, i));
|
||||||
|
}
|
||||||
case DATE_TIME:
|
case DATE_TIME:
|
||||||
{
|
{
|
||||||
return (QueryManager.getLocalDateTime(resultSet, i));
|
return (QueryManager.getLocalDateTime(resultSet, i));
|
||||||
|
@ -35,6 +35,7 @@ import java.sql.Types;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
@ -47,6 +48,7 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
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 org.apache.commons.lang.NotImplementedException;
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
|
|
||||||
|
|
||||||
@ -121,7 +123,10 @@ public class QueryManager
|
|||||||
statement.execute();
|
statement.execute();
|
||||||
resultSet = statement.getResultSet();
|
resultSet = statement.getResultSet();
|
||||||
|
|
||||||
processor.processResultSet(resultSet);
|
if(processor != null)
|
||||||
|
{
|
||||||
|
processor.processResultSet(resultSet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -743,10 +748,14 @@ public class QueryManager
|
|||||||
}
|
}
|
||||||
else if(value instanceof LocalDate ld)
|
else if(value instanceof LocalDate ld)
|
||||||
{
|
{
|
||||||
ZoneOffset offset = OffsetDateTime.now().getOffset();
|
java.sql.Date date = new java.sql.Date(ld.getYear() - 1900, ld.getMonthValue() - 1, ld.getDayOfMonth());
|
||||||
long epochMillis = ld.atStartOfDay().toEpochSecond(offset) * MS_PER_SEC;
|
statement.setDate(index, date);
|
||||||
Timestamp timestamp = new Timestamp(epochMillis);
|
return (1);
|
||||||
statement.setTimestamp(index, timestamp);
|
}
|
||||||
|
else if(value instanceof LocalTime lt)
|
||||||
|
{
|
||||||
|
java.sql.Time time = new java.sql.Time(lt.getHour(), lt.getMinute(), lt.getSecond());
|
||||||
|
statement.setTime(index, time);
|
||||||
return (1);
|
return (1);
|
||||||
}
|
}
|
||||||
else if(value instanceof OffsetDateTime odt)
|
else if(value instanceof OffsetDateTime odt)
|
||||||
@ -1199,6 +1208,67 @@ public class QueryManager
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static LocalTime getLocalTime(ResultSet resultSet, int column) throws SQLException
|
||||||
|
{
|
||||||
|
String timeString = resultSet.getString(column);
|
||||||
|
if(resultSet.wasNull())
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
return stringToLocalTime(timeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public static LocalTime getLocalTime(ResultSet resultSet, String column) throws SQLException
|
||||||
|
{
|
||||||
|
String timeString = resultSet.getString(column);
|
||||||
|
if(resultSet.wasNull())
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
return stringToLocalTime(timeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
private static LocalTime stringToLocalTime(String timeString) throws SQLException
|
||||||
|
{
|
||||||
|
if(!StringUtils.hasContent(timeString))
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] parts = timeString.split(":");
|
||||||
|
if(parts.length == 1)
|
||||||
|
{
|
||||||
|
return LocalTime.of(Integer.parseInt(parts[0]), 0);
|
||||||
|
}
|
||||||
|
if(parts.length == 2)
|
||||||
|
{
|
||||||
|
return LocalTime.of(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
|
||||||
|
}
|
||||||
|
else if(parts.length == 3)
|
||||||
|
{
|
||||||
|
return LocalTime.of(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw (new SQLException("Unable to parse time value [" + timeString + "] to LocalTime"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@ -32,6 +32,7 @@ import java.sql.SQLException;
|
|||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
import java.time.Month;
|
import java.time.Month;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
@ -59,7 +60,16 @@ class QueryManagerTest
|
|||||||
void beforeEach() throws SQLException
|
void beforeEach() throws SQLException
|
||||||
{
|
{
|
||||||
Connection connection = getConnection();
|
Connection connection = getConnection();
|
||||||
QueryManager.executeUpdate(connection, "CREATE TABLE t (i INTEGER, dt DATETIME, c CHAR(1), d DATE)");
|
QueryManager.executeUpdate(connection, """
|
||||||
|
CREATE TABLE test_table
|
||||||
|
(
|
||||||
|
int_col INTEGER,
|
||||||
|
datetime_col DATETIME,
|
||||||
|
char_col CHAR(1),
|
||||||
|
date_col DATE,
|
||||||
|
time_col TIME
|
||||||
|
)
|
||||||
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -71,7 +81,7 @@ class QueryManagerTest
|
|||||||
void afterEach() throws SQLException
|
void afterEach() throws SQLException
|
||||||
{
|
{
|
||||||
Connection connection = getConnection();
|
Connection connection = getConnection();
|
||||||
QueryManager.executeUpdate(connection, "DROP TABLE t");
|
QueryManager.executeUpdate(connection, "DROP TABLE test_table");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -95,7 +105,7 @@ class QueryManagerTest
|
|||||||
{
|
{
|
||||||
long ctMillis = System.currentTimeMillis();
|
long ctMillis = System.currentTimeMillis();
|
||||||
Connection connection = getConnection();
|
Connection connection = getConnection();
|
||||||
PreparedStatement ps = connection.prepareStatement("UPDATE t SET i = ? WHERE i > 0");
|
PreparedStatement ps = connection.prepareStatement("UPDATE test_table SET int_col = ? WHERE int_col > 0");
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// these calls - we just want to assert that they don't throw any exceptions //
|
// these calls - we just want to assert that they don't throw any exceptions //
|
||||||
@ -149,37 +159,37 @@ class QueryManagerTest
|
|||||||
void testGetValueMethods() throws SQLException
|
void testGetValueMethods() throws SQLException
|
||||||
{
|
{
|
||||||
Connection connection = getConnection();
|
Connection connection = getConnection();
|
||||||
QueryManager.executeUpdate(connection, "INSERT INTO t (i, dt, c) VALUES (1, now(), 'A')");
|
QueryManager.executeUpdate(connection, "INSERT INTO test_table (int_col, datetime_col, char_col) VALUES (1, now(), 'A')");
|
||||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * from t");
|
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * from test_table");
|
||||||
preparedStatement.execute();
|
preparedStatement.execute();
|
||||||
ResultSet rs = preparedStatement.getResultSet();
|
ResultSet rs = preparedStatement.getResultSet();
|
||||||
rs.next();
|
rs.next();
|
||||||
|
|
||||||
assertEquals(1, QueryManager.getInteger(rs, "i"));
|
assertEquals(1, QueryManager.getInteger(rs, "int_col"));
|
||||||
assertEquals(1, QueryManager.getInteger(rs, 1));
|
assertEquals(1, QueryManager.getInteger(rs, 1));
|
||||||
assertEquals(1L, QueryManager.getLong(rs, "i"));
|
assertEquals(1L, QueryManager.getLong(rs, "int_col"));
|
||||||
assertEquals(1L, QueryManager.getLong(rs, 1));
|
assertEquals(1L, QueryManager.getLong(rs, 1));
|
||||||
assertArrayEquals(new byte[] { 0, 0, 0, 1 }, QueryManager.getByteArray(rs, "i"));
|
assertArrayEquals(new byte[] { 0, 0, 0, 1 }, QueryManager.getByteArray(rs, "int_col"));
|
||||||
assertArrayEquals(new byte[] { 0, 0, 0, 1 }, QueryManager.getByteArray(rs, 1));
|
assertArrayEquals(new byte[] { 0, 0, 0, 1 }, QueryManager.getByteArray(rs, 1));
|
||||||
assertEquals(1, QueryManager.getObject(rs, "i"));
|
assertEquals(1, QueryManager.getObject(rs, "int_col"));
|
||||||
assertEquals(1, QueryManager.getObject(rs, 1));
|
assertEquals(1, QueryManager.getObject(rs, 1));
|
||||||
assertEquals(BigDecimal.ONE, QueryManager.getBigDecimal(rs, "i"));
|
assertEquals(BigDecimal.ONE, QueryManager.getBigDecimal(rs, "int_col"));
|
||||||
assertEquals(BigDecimal.ONE, QueryManager.getBigDecimal(rs, 1));
|
assertEquals(BigDecimal.ONE, QueryManager.getBigDecimal(rs, 1));
|
||||||
assertEquals(true, QueryManager.getBoolean(rs, "i"));
|
assertEquals(true, QueryManager.getBoolean(rs, "int_col"));
|
||||||
assertEquals(true, QueryManager.getBoolean(rs, 1));
|
assertEquals(true, QueryManager.getBoolean(rs, 1));
|
||||||
assertNotNull(QueryManager.getDate(rs, "dt"));
|
assertNotNull(QueryManager.getDate(rs, "datetime_col"));
|
||||||
assertNotNull(QueryManager.getDate(rs, 2));
|
assertNotNull(QueryManager.getDate(rs, 2));
|
||||||
assertNotNull(QueryManager.getCalendar(rs, "dt"));
|
assertNotNull(QueryManager.getCalendar(rs, "datetime_col"));
|
||||||
assertNotNull(QueryManager.getCalendar(rs, 2));
|
assertNotNull(QueryManager.getCalendar(rs, 2));
|
||||||
assertNotNull(QueryManager.getLocalDate(rs, "dt"));
|
assertNotNull(QueryManager.getLocalDate(rs, "datetime_col"));
|
||||||
assertNotNull(QueryManager.getLocalDate(rs, 2));
|
assertNotNull(QueryManager.getLocalDate(rs, 2));
|
||||||
assertNotNull(QueryManager.getLocalDateTime(rs, "dt"));
|
assertNotNull(QueryManager.getLocalDateTime(rs, "datetime_col"));
|
||||||
assertNotNull(QueryManager.getLocalDateTime(rs, 2));
|
assertNotNull(QueryManager.getLocalDateTime(rs, 2));
|
||||||
assertNotNull(QueryManager.getOffsetDateTime(rs, "dt"));
|
assertNotNull(QueryManager.getOffsetDateTime(rs, "datetime_col"));
|
||||||
assertNotNull(QueryManager.getOffsetDateTime(rs, 2));
|
assertNotNull(QueryManager.getOffsetDateTime(rs, 2));
|
||||||
assertNotNull(QueryManager.getTimestamp(rs, "dt"));
|
assertNotNull(QueryManager.getTimestamp(rs, "datetime_col"));
|
||||||
assertNotNull(QueryManager.getTimestamp(rs, 2));
|
assertNotNull(QueryManager.getTimestamp(rs, 2));
|
||||||
assertEquals("A", QueryManager.getObject(rs, "c"));
|
assertEquals("A", QueryManager.getObject(rs, "char_col"));
|
||||||
assertEquals("A", QueryManager.getObject(rs, 3));
|
assertEquals("A", QueryManager.getObject(rs, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,37 +202,37 @@ class QueryManagerTest
|
|||||||
void testGetValueMethodsReturningNull() throws SQLException
|
void testGetValueMethodsReturningNull() throws SQLException
|
||||||
{
|
{
|
||||||
Connection connection = getConnection();
|
Connection connection = getConnection();
|
||||||
QueryManager.executeUpdate(connection, "INSERT INTO t (i, dt, c) VALUES (null, null, null)");
|
QueryManager.executeUpdate(connection, "INSERT INTO test_table (int_col, datetime_col, char_col) VALUES (null, null, null)");
|
||||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * from t");
|
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * from test_table");
|
||||||
preparedStatement.execute();
|
preparedStatement.execute();
|
||||||
ResultSet rs = preparedStatement.getResultSet();
|
ResultSet rs = preparedStatement.getResultSet();
|
||||||
rs.next();
|
rs.next();
|
||||||
|
|
||||||
assertNull(QueryManager.getInteger(rs, "i"));
|
assertNull(QueryManager.getInteger(rs, "int_col"));
|
||||||
assertNull(QueryManager.getInteger(rs, 1));
|
assertNull(QueryManager.getInteger(rs, 1));
|
||||||
assertNull(QueryManager.getLong(rs, "i"));
|
assertNull(QueryManager.getLong(rs, "int_col"));
|
||||||
assertNull(QueryManager.getLong(rs, 1));
|
assertNull(QueryManager.getLong(rs, 1));
|
||||||
assertNull(QueryManager.getByteArray(rs, "i"));
|
assertNull(QueryManager.getByteArray(rs, "int_col"));
|
||||||
assertNull(QueryManager.getByteArray(rs, 1));
|
assertNull(QueryManager.getByteArray(rs, 1));
|
||||||
assertNull(QueryManager.getObject(rs, "i"));
|
assertNull(QueryManager.getObject(rs, "int_col"));
|
||||||
assertNull(QueryManager.getObject(rs, 1));
|
assertNull(QueryManager.getObject(rs, 1));
|
||||||
assertNull(QueryManager.getBigDecimal(rs, "i"));
|
assertNull(QueryManager.getBigDecimal(rs, "int_col"));
|
||||||
assertNull(QueryManager.getBigDecimal(rs, 1));
|
assertNull(QueryManager.getBigDecimal(rs, 1));
|
||||||
assertNull(QueryManager.getBoolean(rs, "i"));
|
assertNull(QueryManager.getBoolean(rs, "int_col"));
|
||||||
assertNull(QueryManager.getBoolean(rs, 1));
|
assertNull(QueryManager.getBoolean(rs, 1));
|
||||||
assertNull(QueryManager.getDate(rs, "dt"));
|
assertNull(QueryManager.getDate(rs, "datetime_col"));
|
||||||
assertNull(QueryManager.getDate(rs, 2));
|
assertNull(QueryManager.getDate(rs, 2));
|
||||||
assertNull(QueryManager.getCalendar(rs, "dt"));
|
assertNull(QueryManager.getCalendar(rs, "datetime_col"));
|
||||||
assertNull(QueryManager.getCalendar(rs, 2));
|
assertNull(QueryManager.getCalendar(rs, 2));
|
||||||
assertNull(QueryManager.getLocalDate(rs, "dt"));
|
assertNull(QueryManager.getLocalDate(rs, "datetime_col"));
|
||||||
assertNull(QueryManager.getLocalDate(rs, 2));
|
assertNull(QueryManager.getLocalDate(rs, 2));
|
||||||
assertNull(QueryManager.getLocalDateTime(rs, "dt"));
|
assertNull(QueryManager.getLocalDateTime(rs, "datetime_col"));
|
||||||
assertNull(QueryManager.getLocalDateTime(rs, 2));
|
assertNull(QueryManager.getLocalDateTime(rs, 2));
|
||||||
assertNull(QueryManager.getOffsetDateTime(rs, "dt"));
|
assertNull(QueryManager.getOffsetDateTime(rs, "datetime_col"));
|
||||||
assertNull(QueryManager.getOffsetDateTime(rs, 2));
|
assertNull(QueryManager.getOffsetDateTime(rs, 2));
|
||||||
assertNull(QueryManager.getTimestamp(rs, "dt"));
|
assertNull(QueryManager.getTimestamp(rs, "datetime_col"));
|
||||||
assertNull(QueryManager.getTimestamp(rs, 2));
|
assertNull(QueryManager.getTimestamp(rs, 2));
|
||||||
assertNull(QueryManager.getObject(rs, "c"));
|
assertNull(QueryManager.getObject(rs, "char_col"));
|
||||||
assertNull(QueryManager.getObject(rs, 3));
|
assertNull(QueryManager.getObject(rs, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,9 +246,9 @@ class QueryManagerTest
|
|||||||
void testLocalDate() throws SQLException
|
void testLocalDate() throws SQLException
|
||||||
{
|
{
|
||||||
Connection connection = getConnection();
|
Connection connection = getConnection();
|
||||||
QueryManager.executeUpdate(connection, "INSERT INTO t (d) VALUES (?)", LocalDate.of(2013, Month.OCTOBER, 1));
|
QueryManager.executeUpdate(connection, "INSERT INTO test_table (date_col) VALUES (?)", LocalDate.of(2013, Month.OCTOBER, 1));
|
||||||
|
|
||||||
PreparedStatement preparedStatement = connection.prepareStatement("SELECT d from t");
|
PreparedStatement preparedStatement = connection.prepareStatement("SELECT date_col from test_table");
|
||||||
preparedStatement.execute();
|
preparedStatement.execute();
|
||||||
ResultSet rs = preparedStatement.getResultSet();
|
ResultSet rs = preparedStatement.getResultSet();
|
||||||
rs.next();
|
rs.next();
|
||||||
@ -268,4 +278,55 @@ class QueryManagerTest
|
|||||||
assertEquals(0, offsetDateTime.getMinute(), "Minute value");
|
assertEquals(0, offsetDateTime.getMinute(), "Minute value");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testLocalTime() throws SQLException
|
||||||
|
{
|
||||||
|
Connection connection = getConnection();
|
||||||
|
|
||||||
|
////////////////////////////////////
|
||||||
|
// insert one just hour & minutes //
|
||||||
|
////////////////////////////////////
|
||||||
|
QueryManager.executeUpdate(connection, "INSERT INTO test_table (int_col, time_col) VALUES (?, ?)", 1, LocalTime.of(10, 42));
|
||||||
|
|
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement("SELECT time_col from test_table where int_col=1");
|
||||||
|
preparedStatement.execute();
|
||||||
|
ResultSet rs = preparedStatement.getResultSet();
|
||||||
|
rs.next();
|
||||||
|
|
||||||
|
LocalTime localTime = QueryManager.getLocalTime(rs, 1);
|
||||||
|
assertEquals(10, localTime.getHour(), "Hour value");
|
||||||
|
assertEquals(42, localTime.getMinute(), "Minute value");
|
||||||
|
assertEquals(0, localTime.getSecond(), "Second value");
|
||||||
|
|
||||||
|
localTime = QueryManager.getLocalTime(rs, "time_col");
|
||||||
|
assertEquals(10, localTime.getHour(), "Hour value");
|
||||||
|
assertEquals(42, localTime.getMinute(), "Minute value");
|
||||||
|
assertEquals(0, localTime.getSecond(), "Second value");
|
||||||
|
|
||||||
|
/////////////////////////////////
|
||||||
|
// now insert one with seconds //
|
||||||
|
/////////////////////////////////
|
||||||
|
QueryManager.executeUpdate(connection, "INSERT INTO test_table (int_col, time_col) VALUES (?, ?)", 2, LocalTime.of(10, 42, 59));
|
||||||
|
|
||||||
|
preparedStatement = connection.prepareStatement("SELECT time_col from test_table where int_col=2");
|
||||||
|
preparedStatement.execute();
|
||||||
|
rs = preparedStatement.getResultSet();
|
||||||
|
rs.next();
|
||||||
|
|
||||||
|
localTime = QueryManager.getLocalTime(rs, 1);
|
||||||
|
assertEquals(10, localTime.getHour(), "Hour value");
|
||||||
|
assertEquals(42, localTime.getMinute(), "Minute value");
|
||||||
|
assertEquals(59, localTime.getSecond(), "Second value");
|
||||||
|
|
||||||
|
localTime = QueryManager.getLocalTime(rs, "time_col");
|
||||||
|
assertEquals(10, localTime.getHour(), "Hour value");
|
||||||
|
assertEquals(42, localTime.getMinute(), "Minute value");
|
||||||
|
assertEquals(59, localTime.getSecond(), "Second value");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.frontend.picocli;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.LocalTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -407,7 +408,8 @@ public class QCommandBuilder
|
|||||||
case INTEGER -> Integer.class;
|
case INTEGER -> Integer.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;
|
||||||
|
case BOOLEAN -> Boolean.class;
|
||||||
case DATE_TIME -> LocalDateTime.class;
|
case DATE_TIME -> LocalDateTime.class;
|
||||||
case BLOB -> byte[].class;
|
case BLOB -> byte[].class;
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user