diff --git a/CHANGELOG.md b/CHANGELOG.md index 687ac6779..460c7a5eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ FROST-Server version 2.5 and higher requires Java 21. This is because some libra **New Features** * Updated to jakarta EE 10: FROST-Server is now runs on Tomcat 10 and later. * Added a validator that sets a time to now(), unless otherwise set. +* Added support for spatial queries on MariaDB. **Internal changes & Bugfixes** * Set limit to 1 for entity-by-primary-key requests. diff --git a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/ExpressionHandler.java b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/ExpressionHandler.java new file mode 100644 index 000000000..dc77aaa00 --- /dev/null +++ b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/ExpressionHandler.java @@ -0,0 +1,1204 @@ +/* + * Copyright (C) 2024 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131 + * Karlsruhe, Germany. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq; + +import static de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.QueryState.ALIAS_ROOT; +import static de.fraunhofer.iosb.ilt.frostserver.property.SpecialNames.AT_IOT_ID; +import static de.fraunhofer.iosb.ilt.frostserver.util.Constants.NOT_IMPLEMENTED_MULTI_VALUE_PK; + +import de.fraunhofer.iosb.ilt.frostserver.model.EntityType; +import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.*; +import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.StaMainTable; +import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.TableCollection; +import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.QueryState; +import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.TableRef; +import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.Utils; +import de.fraunhofer.iosb.ilt.frostserver.property.*; +import de.fraunhofer.iosb.ilt.frostserver.query.OrderBy; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.Expression; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.ExpressionVisitor; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.Path; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.*; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.Function; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.arithmetic.*; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.comparison.*; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.context.ContextEntityProperty; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.context.PrincipalName; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.*; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.Date; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.logical.And; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.logical.Any; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.logical.Not; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.logical.Or; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.math.Ceiling; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.math.Floor; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.math.Round; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.*; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.*; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.temporal.*; +import de.fraunhofer.iosb.ilt.frostserver.settings.CoreSettings; +import de.fraunhofer.iosb.ilt.frostserver.settings.Settings; +import java.util.*; +import net.time4j.Moment; +import net.time4j.PlainDate; +import net.time4j.PlainTime; +import net.time4j.ZonalDateTime; +import net.time4j.range.MomentInterval; +import org.apache.commons.lang3.NotImplementedException; +import org.geojson.GeoJsonObject; +import org.geolatte.geom.Geometry; +import org.jooq.Condition; +import org.jooq.DatePart; +import org.jooq.Field; +import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Superclass for the specific implementations to form the database-dialect specific SQL-Queries. + * + * @author Tobias Hellmund (IOSB) + */ +public abstract class ExpressionHandler implements ExpressionVisitor { + + /** + * The logger for this class. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(ExpressionHandler.class); + + private final QueryBuilder queryBuilder; + private QueryState queryState; + private int maxCustomLinkDepth = -1; + + public ExpressionHandler(CoreSettings settings, QueryBuilder queryBuilder) { + this.queryBuilder = queryBuilder; + this.queryState = queryBuilder.getQueryState(); + final Settings experimentalSettings = settings.getExtensionSettings(); + if (experimentalSettings.getBoolean(CoreSettings.TAG_CUSTOM_LINKS_ENABLE, CoreSettings.class)) { + maxCustomLinkDepth = experimentalSettings.getInt(CoreSettings.TAG_CUSTOM_LINKS_RECURSE_DEPTH, CoreSettings.class); + } + } + + public Condition addFilterToWhere(Expression filter, Condition sqlWhere) { + FieldWrapper filterField = filter.accept(this); + if (filterField.isCondition()) { + return sqlWhere.and(filterField.getCondition()); + + } + if (filterField instanceof FieldListWrapper listExpression) { + for (Field expression : listExpression.getExpressions().values()) { + if (Boolean.class.isAssignableFrom(expression.getType())) { + Field predicate = expression; + return sqlWhere.and(predicate); + } + } + } + LOGGER.error("Filter is not a predicate but a {}.", filterField.getClass().getName()); + throw new IllegalArgumentException("Filter is not a predicate but a " + filterField.getClass().getName()); + } + + public void addOrderbyToQuery(OrderBy orderBy, Utils.SortSelectFields orderFields) { + FieldWrapper resultExpression = orderBy.getExpression().accept(this); + if (resultExpression instanceof StaTimeIntervalWrapper timeInterval) { + addToQuery(orderBy, timeInterval.getStart(), orderFields); + addToQuery(orderBy, timeInterval.getEnd(), orderFields); + return; + } + if (resultExpression instanceof StaDurationWrapper duration) { + addToQuery(orderBy, duration.getDuration(), orderFields); + return; + } + if (resultExpression instanceof StaDateTimeWrapper dateTime) { + addToQuery(orderBy, dateTime.getDateTime(), orderFields); + return; + } + if (resultExpression instanceof FieldListWrapper fieldListWrapper) { + for (Field sqlExpression : fieldListWrapper.getExpressionsForOrder().values()) { + addToQuery(orderBy, sqlExpression, orderFields); + } + return; + } + Field field = resultExpression.getDefaultField(); + addToQuery(orderBy, field, orderFields); + } + + public void addToQuery(OrderBy orderBy, Field field, Utils.SortSelectFields orderFields) { + orderFields.add(field, orderBy.getType()); + } + + @Override + public FieldWrapper visit(Path path) { + PathState state = new PathState(); + state.elements = path.getElements(); + var storedQueryState = queryState; + try { + Property firstElement = state.elements.get(0); + int startIdx = 0; + if (firstElement instanceof PropertyReference pr) { + startIdx = 1; + queryState = queryState.findStateForAlias(pr.getName()); + } else { + queryState = queryState.findStateForAlias(ALIAS_ROOT); + } + state.pathTableRef = queryState.getTableRef(); + walkPath(state, startIdx, path); + } finally { + queryState = storedQueryState; + } + return state.finalExpression; + } + + private void walkPath(PathState state, int startIdx, Path path) throws IllegalArgumentException { + for (state.curIndex = startIdx; state.curIndex < state.elements.size() && !state.finished; state.curIndex++) { + Property element = state.elements.get(state.curIndex); + if (element instanceof EntityPropertyCustom) { + handleCustomProperty(state, path); + + } else if (element instanceof EntityPropertyCustomLink) { + handleCustomProperty(state, path); + + } else if (element instanceof EntityPropertyMain entityPropertyMain) { + handleEntityProperty(state, path, entityPropertyMain); + + } else if (element instanceof NavigationPropertyMain navigationPropertyMain) { + handleNavigationProperty(state, path, navigationPropertyMain); + } + } + if (state.finalExpression == null) { + throw new IllegalArgumentException("Path does not end in an EntityProperty: " + path); + } + if (state.finalExpression instanceof Field field && Moment.class.isAssignableFrom(field.getType())) { + Field dateTimePath = (Field) state.finalExpression; + state.finalExpression = new StaDateTimeWrapper(dateTimePath); + } + } + + private void handleCustomProperty(PathState state, Path path) { + if (state.finalExpression == null) { + throw new IllegalArgumentException("CustomProperty must follow an EntityProperty: " + path); + } + // generate finalExpression::jsonb#>>'{x,y,z}' + JsonFieldFactory.JsonFieldWrapper jsonFactory; + if (state.finalExpression instanceof JsonFieldFactory.JsonFieldWrapper jsonFieldWrapper) { + jsonFactory = jsonFieldWrapper; + } else { + jsonFactory = new JsonFieldFactory.JsonFieldWrapper(state.finalExpression); + } + for (; state.curIndex < state.elements.size(); state.curIndex++) { + final Property property = state.elements.get(state.curIndex); + String name = property.getName(); + if (property instanceof EntityPropertyCustomLink epcl) { + int maxDepth = state.curIndex + maxCustomLinkDepth; + if (state.curIndex <= maxDepth) { + handleCustomLink(epcl, jsonFactory, name, state); + return; + } else { + jsonFactory.addToPath(name); + } + } else { + jsonFactory.addToPath(name); + } + } + state.finalExpression = jsonFactory.materialise(); + state.finished = true; + } + + private void handleCustomLink(final EntityPropertyCustomLink epcl, JsonFieldFactory.JsonFieldWrapper jsonFactory, String name, PathState state) { + JsonFieldFactory.JsonFieldWrapper sourceIdFieldWrapper = jsonFactory.addToPath(name + AT_IOT_ID).materialise(); + Field sourceIdField = sourceIdFieldWrapper.getFieldAsType(Number.class, true); + state.pathTableRef = queryEntityType(epcl, state.pathTableRef, sourceIdField); + state.finalExpression = null; + } + + private void handleEntityProperty(PathState state, Path path, EntityPropertyMain element) { + if (state.finalExpression != null) { + throw new IllegalArgumentException("EntityProperty can not follow an other EntityProperty: " + path); + } + Map pathExpressions = state.pathTableRef.getTable() + .getPropertyFieldRegistry() + .getAllFieldsForProperty(element, new LinkedHashMap<>()); + if (pathExpressions.size() == 1) { + final Field field = pathExpressions.values().stream().iterator().next(); + Field optimisedField = state.pathTableRef.getJoinEqual(field); + state.finalExpression = WrapperHelper.wrapField(optimisedField); + } else { + state.finalExpression = getSubExpression(state, pathExpressions); + } + } + + private void handleNavigationProperty(PathState state, Path path, NavigationPropertyMain np) { + if (state.finalExpression != null) { + throw new IllegalArgumentException("NavigationProperty can not follow an EntityProperty: " + path); + } + state.pathTableRef = queryEntityType(np, state.pathTableRef); + } + + private FieldWrapper getSubExpression(PathState state, Map pathExpressions) { + int nextIdx = state.curIndex + 1; + if (state.elements.size() > nextIdx) { + Property subProperty = state.elements.get(nextIdx); + // If the subProperty is unknown, and the expression can be of type JSON, + // then we assume JSON. + if (!pathExpressions.containsKey(subProperty.getName()) && pathExpressions.containsKey("j")) { + return new SimpleFieldWrapper(pathExpressions.get("j")); + } + // We can not accept json, so the subProperty must be a known direction. + state.finished = true; + return WrapperHelper.wrapField(pathExpressions.get(subProperty.getName())); + } + if (pathExpressions.containsKey(StaTimeIntervalWrapper.KEY_TIME_INTERVAL_START) + && pathExpressions.containsKey(StaTimeIntervalWrapper.KEY_TIME_INTERVAL_END)) { + return new StaTimeIntervalWrapper(pathExpressions); + } + return new FieldListWrapper(pathExpressions); + } + + /** + * Queries the given entity type, as relation to the given table reference + * and returns a new table reference. Effectively, this generates a join. + * + * @param np The NavigationProperty to query + * @param last The table the requested entity is related to. + * @return The table reference of the requested entity. + */ + public TableRef queryEntityType(NavigationProperty np, TableRef last) { + if (queryState == null) { + throw new IllegalStateException("QueryState should not be null"); + } + if (last == null) { + throw new IllegalStateException("last result should not be null"); + } + + TableRef existingJoin = last.getJoin(np); + if (existingJoin != null) { + return existingJoin; + } + + return last.createJoin(np.getName(), queryState); + } + + /** + * Directly query an entity type. Used for custom linking. + * + * @param epcl the custom link. + * @param sourceRef The source table ref. + * @param sourceIdField The source ID field. + * @return A new table ref with the target entity type table joined. + */ + public TableRef queryEntityType(EntityPropertyCustomLink epcl, TableRef sourceRef, Field sourceIdField) { + final EntityType targetEntityType = epcl.getEntityType(); + final StaMainTable target = queryBuilder.getTableCollection().getTablesByType().get(targetEntityType); + final StaMainTable targetAliased = target.asSecure(queryState.getNextAlias(), queryBuilder.getPersistenceManager()); + final List targetField = targetAliased.getPkFields(); + if (targetField.size() > 1) { + throw new NotImplementedException(NOT_IMPLEMENTED_MULTI_VALUE_PK); + } + queryState.setSqlFrom(queryState.getSqlFrom().leftJoin(targetAliased).on(targetField.get(0).eq(sourceIdField))); + TableRef newRef = new TableRef(targetAliased); + sourceRef.addJoin(epcl, newRef); + return newRef; + } + + public Field[] findPair(FieldWrapper p1, FieldWrapper p2) { + Field[] result = new Field[2]; + + result[0] = p1.getFieldAsType(Number.class, true); + result[1] = p2.getFieldAsType(Number.class, true); + if (result[0] != null && result[1] != null) { + return result; + } + + result[0] = p1.getFieldAsType(Boolean.class, true); + result[1] = p2.getFieldAsType(Boolean.class, true); + if (result[0] != null && result[1] != null) { + return result; + } + + // If both are strings, use strings. + result[0] = p1.getFieldAsType(String.class, true); + result[1] = p2.getFieldAsType(String.class, true); + if (result[0] != null && result[1] != null) { + return result; + } + + LOGGER.warn("Could not match types for {} and {}", p1, p2); + result[0] = p1.getDefaultField(); + result[1] = p2.getDefaultField(); + return result; + } + + @Override + public FieldWrapper visit(Any node) { + final TableCollection tc = queryBuilder.getTableCollection(); + final QueryState parentQueryState = queryState; + + QueryState existsQueryState = walkAnyPath(parentQueryState, node, tc); + if (existsQueryState == null) { + throw new IllegalStateException("Failed to parse any()."); + } + + // Set our subQuery state to be the active one. + queryState = existsQueryState; + try { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + if (!p1.isCondition()) { + throw new IllegalArgumentException("Any() requires a condition, got " + p1); + } + Condition exists = DSL.exists(DSL.selectOne().from(existsQueryState.getSqlFrom()).where(existsQueryState.getSqlWhere().and(p1.getCondition()))); + return new SimpleFieldWrapper(exists); + } finally { + // Set the query state back to what it was. + queryState = parentQueryState; + } + } + + private QueryState walkAnyPath(final QueryState parentQueryState, Any node, final TableCollection tc) throws IllegalArgumentException { + final StaMainTable parentMainTable = parentQueryState.getMainTable(); + final EntityType parentEntityType = parentMainTable.getEntityType(); + final Path path = node.getCollection(); + final List elements = path.getElements(); + QueryState existsQueryState = null; + TableRef lastJoin = null; + Property firstElement = elements.get(0); + int startIdx = 0; + if (firstElement instanceof PropertyReference) { + startIdx = 1; + } + for (int idx = elements.size() - 1; idx >= startIdx; idx--) { + Property element = elements.get(idx); + if ((lastJoin == null)) { + if (element instanceof NavigationPropertyMain.NavigationPropertyEntitySet npes) { + // Last entry in the path: the target collection. + EntityType finalType = npes.getEntityType(); + final StaMainTable tableForType = tc.getTableForType(finalType).asSecure(parentQueryState.getNextAlias(), parentQueryState.getPersistenceManager()); + existsQueryState = new QueryState(tableForType, parentQueryState, node.getLambdaName()); + lastJoin = existsQueryState.getTableRef(); + } else { + throw new IllegalArgumentException("Path before any() MUST end in an EntitySet. Found: " + element); + } + } + if (element instanceof NavigationPropertyMain npm) { + var inverse = npm.getInverse(); + if (idx == startIdx) { + // First entry in the path: Link to the main table! + if (inverse.getEntityType() != parentEntityType) { + throw new IllegalArgumentException("path of any() did not track back to main entity type. Expected " + parentEntityType + " got " + inverse.getEntityType()); + } + lastJoin.createSemiJoin(inverse.getName(), parentMainTable, existsQueryState); + + } else { + TableRef existingJoin = lastJoin.getJoin(inverse); + if (existingJoin != null) { + lastJoin = existingJoin; + } + lastJoin = lastJoin.createJoin(inverse.getName(), existsQueryState); + } + + } else if (element instanceof EntityPropertyCustomLink) { + throw new IllegalArgumentException("Path before any() should not contain Custom Links. Found: " + element); + } else if (element instanceof EntityPropertyCustom) { + throw new IllegalArgumentException("Path before any() should not contain EntityProperties. Found: " + element); + } else if (element instanceof EntityPropertyMain) { + throw new IllegalArgumentException("Path before any() should not contain EntityProperties. Found: " + element); + } else { + throw new IllegalArgumentException("Path before any() contains unknown element. Found: " + element); + } + } + return existsQueryState; + } + + @Override + public FieldWrapper visit(BooleanConstant node) { + return new SimpleFieldWrapper(Boolean.TRUE.equals(node.getValue()) ? DSL.condition("TRUE") : DSL.condition("FALSE")); + } + + @Override + public FieldWrapper visit(DateConstant node) { + PlainDate date = node.getValue(); + Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + instance.set(date.getYear(), date.getMonth() - 1, date.getDayOfMonth()); + return new SimpleFieldWrapper(DSL.inline(new java.sql.Date(instance.getTimeInMillis()))); + } + + @Override + public FieldWrapper visit(DateTimeConstant node) { + ZonalDateTime value = node.getValue(); + return new StaDateTimeWrapper(value.toMoment(), true); + } + + @Override + public FieldWrapper visit(DoubleConstant node) { + return new SimpleFieldWrapper(DSL.val(node.getValue())); + } + + @Override + public FieldWrapper visit(DurationConstant node) { + return new StaDurationWrapper(node); + } + + @Override + public FieldWrapper visit(IntervalConstant node) { + MomentInterval value = node.getValue(); + return new StaTimeIntervalWrapper( + value.getStartAsMoment(), + value.getEndAsMoment()); + } + + @Override + public FieldWrapper visit(IntegerConstant node) { + return new SimpleFieldWrapper(DSL.val(node.getValue())); + } + + @Override + public FieldWrapper visit(NullConstant node) { + return new NullWrapper(); + } + + @Override + public FieldWrapper visit(StringConstant node) { + return new SimpleFieldWrapper(DSL.value(node.getValue())); + } + + @Override + public FieldWrapper visit(TimeConstant node) { + PlainTime time = node.getValue(); + Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + instance.set(1970, 1, 1, time.getHour(), time.getMinute(), time.getSecond()); + return new SimpleFieldWrapper(DSL.inline(new java.sql.Time(instance.getTimeInMillis()))); + } + + @Override + public FieldWrapper visit(ConstantList node) { + return new ArrayConstandFieldWrapper(node); + } + + @Override + public FieldWrapper visit(Before node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper timeExpression) { + return timeExpression.before(p2); + } + throw new IllegalArgumentException("Before can only be used on times, not on " + p1.getClass().getName()); + } + + @Override + public FieldWrapper visit(After node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper timeExpression) { + return timeExpression.after(p2); + } + throw new IllegalArgumentException("After can only be used on times, not on " + p1.getClass().getName()); + } + + @Override + public FieldWrapper visit(Meets node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper timeExpression) { + return timeExpression.meets(p2); + } + throw new IllegalArgumentException("Meets can only be used on times, not on " + p1.getClass().getName()); + } + + @Override + public FieldWrapper visit(During node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p2 instanceof StaTimeIntervalWrapper ti2) { + return ti2.contains(p1); + } + throw new IllegalArgumentException("Second parameter of 'during' has to be an interval."); + } + + @Override + public FieldWrapper visit(Overlaps node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper timeExpression) { + return timeExpression.overlaps(p2); + } + throw new IllegalArgumentException("Overlaps can only be used on times, not on " + p1.getClass().getName()); + } + + @Override + public FieldWrapper visit(Starts node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper timeExpression) { + return timeExpression.starts(p2); + } + throw new IllegalArgumentException("Starts can only be used on times, not on " + p1.getClass().getName()); + } + + @Override + public FieldWrapper visit(Finishes node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper timeExpression) { + return timeExpression.finishes(p2); + } + throw new IllegalArgumentException("Finishes can only be used on times, not on " + p1.getClass().getName()); + } + + @Override + public FieldWrapper visit(Add node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper ti1) { + return ti1.add(p2); + } + if (p2 instanceof TimeFieldWrapper ti2) { + return ti2.add(p1); + } + Field n1 = p1.getFieldAsType(Number.class, true); + Field n2 = p2.getFieldAsType(Number.class, true); + return new SimpleFieldWrapper(n1.add(n2)); + } + + @Override + public FieldWrapper visit(Divide node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper ti1) { + return ti1.div(p2); + } + if (p2 instanceof TimeFieldWrapper) { + throw new IllegalArgumentException("Can not devide by a TimeExpression."); + } + Field n1 = p1.getFieldAsType(Number.class, true); + Field n2 = p2.getFieldAsType(Number.class, true); + return new SimpleFieldWrapper(n1.divide(n2).coerce(SQLDataType.DOUBLE)); + } + + @Override + public FieldWrapper visit(Modulo node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + Field n1 = p1.getFieldAsType(Number.class, true); + Field n2 = p2.getFieldAsType(Number.class, true); + if (n1.getType().equals(Double.class)) { + n1 = n1.cast(SQLDataType.NUMERIC); + } + if (n2.getType().equals(Double.class)) { + n2 = n2.cast(SQLDataType.NUMERIC); + } + return new SimpleFieldWrapper(n1.mod(n2)); + } + + @Override + public FieldWrapper visit(Multiply node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper ti1) { + return ti1.mul(p2); + } + if (p2 instanceof TimeFieldWrapper ti2) { + return ti2.mul(p1); + } + Field n1 = p1.getFieldAsType(Number.class, true); + Field n2 = p2.getFieldAsType(Number.class, true); + return new SimpleFieldWrapper(n1.multiply(n2)); + } + + @Override + public FieldWrapper visit(Subtract node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper ti1) { + return ti1.sub(p2); + } + if (p2 instanceof TimeFieldWrapper) { + throw new IllegalArgumentException("Can not sub a time expression from a " + p1.getClass().getName()); + } + Field n1 = p1.getFieldAsType(Number.class, true); + Field n2 = p2.getFieldAsType(Number.class, true); + return new SimpleFieldWrapper(n1.subtract(n2)); + } + + @Override + public FieldWrapper visit(Equal node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof NullWrapper) { + return new SimpleFieldWrapper(p2.getDefaultField().isNull()); + } + if (p2 instanceof NullWrapper) { + return new SimpleFieldWrapper(p1.getDefaultField().isNull()); + } + if (p1 instanceof TimeFieldWrapper ti1) { + return ti1.eq(p2); + } + if (p2 instanceof TimeFieldWrapper ti2) { + return ti2.eq(p1); + } + if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { + return l1.eq(p2); + } + if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { + return l2.eq(p1); + } + + Field[] pair = findPair(p1, p2); + return new SimpleFieldWrapper(pair[0].eq(pair[1])); + } + + @Override + public FieldWrapper visit(GreaterEqual node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper ti1) { + return ti1.goe(p2); + } + if (p2 instanceof TimeFieldWrapper ti2) { + return ti2.loe(p1); + } + if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { + return l1.goe(p2); + } + if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { + return l2.loe(p1); + } + Field[] pair = findPair(p1, p2); + return new SimpleFieldWrapper(pair[0].greaterOrEqual(pair[1])); + } + + @Override + public FieldWrapper visit(GreaterThan node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper ti1) { + return ti1.gt(p2); + } + if (p2 instanceof TimeFieldWrapper ti2) { + return ti2.lt(p1); + } + if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { + return l1.gt(p2); + } + if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { + return l2.lt(p1); + } + Field[] pair = findPair(p1, p2); + return new SimpleFieldWrapper(pair[0].greaterThan(pair[1])); + } + + @Override + public FieldWrapper visit(LessEqual node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper ti1) { + return ti1.loe(p2); + } + if (p2 instanceof TimeFieldWrapper ti2) { + return ti2.goe(p1); + } + if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { + return l1.loe(p2); + } + if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { + return l2.goe(p1); + } + Field[] pair = findPair(p1, p2); + return new SimpleFieldWrapper(pair[0].lessOrEqual(pair[1])); + } + + @Override + public FieldWrapper visit(LessThan node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof TimeFieldWrapper ti1) { + return ti1.lt(p2); + } + if (p2 instanceof TimeFieldWrapper ti2) { + return ti2.gt(p1); + } + if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { + return l1.lt(p2); + } + if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { + return l2.gt(p1); + } + Field[] pair = findPair(p1, p2); + return new SimpleFieldWrapper(pair[0].lt(pair[1])); + } + + @Override + public FieldWrapper visit(NotEqual node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1 instanceof NullWrapper) { + return new SimpleFieldWrapper(p2.getDefaultField().isNotNull()); + } + if (p2 instanceof NullWrapper) { + return new SimpleFieldWrapper(p1.getDefaultField().isNotNull()); + } + if (p1 instanceof TimeFieldWrapper ti1) { + return ti1.neq(p2); + } + if (p2 instanceof TimeFieldWrapper ti2) { + return ti2.neq(p1); + } + if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { + return l1.ne(p2); + } + if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { + return l2.ne(p1); + } + Field[] pair = findPair(p1, p2); + return new SimpleFieldWrapper(pair[0].ne(pair[1])); + } + + @Override + public FieldWrapper visit(In node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p2 instanceof ArrayConstandFieldWrapper clP2) { + return new SimpleFieldWrapper(p1.getDefaultField().in(clP2.getValueList())); + } + Field[] pair = findPair(p1, p2); + if (p2 instanceof JsonFieldFactory.JsonFieldWrapper jP2) { + return jP2.contains(pair[0]); + } else { + return new SimpleFieldWrapper(pair[0].in(pair[1])); + } + } + + @Override + public FieldWrapper visit(Date node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + if (input instanceof TimeFieldWrapper timeExpression) { + return new SimpleFieldWrapper(DSL.function("date", java.sql.Date.class, timeExpression.getDateTime())); + } + Field fieldAsDate = input.getFieldAsType(java.sql.Date.class, true); + if (fieldAsDate != null) { + return new SimpleFieldWrapper(fieldAsDate); + } + throw new IllegalArgumentException("Date can only be used on times, not on " + input.getClass().getName()); + } + + @Override + public FieldWrapper visit(Day node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + if (input instanceof TimeFieldWrapper timeExpression) { + return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.DAY)); + } + throw new IllegalArgumentException("Day can only be used on times, not on " + input.getClass().getName()); + } + + @Override + public FieldWrapper visit(FractionalSeconds node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + if (input instanceof TimeFieldWrapper timeExpression) { + return new SimpleFieldWrapper(DSL.field("(date_part('SECONDS', TIMESTAMPTZ ?) - floor(date_part('SECONDS', TIMESTAMPTZ ?)))", Double.class, timeExpression.getDateTime(), timeExpression.getDateTime())); + } + throw new IllegalArgumentException("FractionalSeconds can only be used on times, not on " + input.getClass().getName()); + } + + @Override + public FieldWrapper visit(Hour node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + if (input instanceof TimeFieldWrapper timeExpression) { + return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.HOUR)); + } + throw new IllegalArgumentException("Hour can only be used on times, not on " + input.getClass().getName()); + } + + @Override + public FieldWrapper visit(MaxDateTime node) { + return new StaDateTimeWrapper(PostgresPersistenceManager.DATETIME_MAX, true); + } + + @Override + public FieldWrapper visit(MinDateTime node) { + return new StaDateTimeWrapper(PostgresPersistenceManager.DATETIME_MIN, true); + } + + @Override + public FieldWrapper visit(Minute node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + if (input instanceof TimeFieldWrapper timeExpression) { + return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.MINUTE)); + } + throw new IllegalArgumentException("Minute can only be used on times, not on " + input.getClass().getName()); + } + + @Override + public FieldWrapper visit(Month node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + if (input instanceof TimeFieldWrapper timeExpression) { + return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.MONTH)); + } + throw new IllegalArgumentException("Month can only be used on times, not on " + input.getClass().getName()); + } + + @Override + public FieldWrapper visit(Now node) { + return new StaDateTimeWrapper(DSL.field("now()", Moment.class)); + } + + @Override + public FieldWrapper visit(Second node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + if (input instanceof TimeFieldWrapper timeExpression) { + return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.SECOND)); + } + throw new IllegalArgumentException("Second can only be used on times, not on " + input.getClass().getName()); + } + + @Override + public FieldWrapper visit(Time node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + if (input instanceof TimeFieldWrapper timeExpression) { + return new SimpleFieldWrapper(timeExpression.getDateTime().cast(SQLDataType.TIME)); + } + throw new IllegalArgumentException("Time can only be used on times, not on " + input.getClass().getName()); + } + + @Override + public FieldWrapper visit(TotalOffsetMinutes node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + if (input instanceof TimeFieldWrapper timeExpression) { + return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.TIMEZONE).div(60)); + } + throw new IllegalArgumentException("TotalOffsetMinutes can only be used on times, not on " + input.getClass().getName()); + } + + @Override + public FieldWrapper visit(Year node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + if (input instanceof TimeFieldWrapper timeExpression) { + return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.YEAR)); + } + throw new IllegalArgumentException("Year can only be used on times, not on " + input.getClass().getName()); + } + + @Override + public FieldWrapper visit(GeoDistance node) { + Expression p1 = node.getParameters().get(0); + Expression p2 = node.getParameters().get(1); + FieldWrapper e1 = p1.accept(this); + FieldWrapper e2 = p2.accept(this); + Field g1 = e1.getFieldAsType(Geometry.class, true); + Field g2 = e2.getFieldAsType(Geometry.class, true); + if (g1 == null || g2 == null) { + throw new IllegalArgumentException("GeoDistance requires two geometries, got " + e1 + " & " + e2); + } + return new SimpleFieldWrapper(DSL.function("ST_Distance", SQLDataType.NUMERIC, g1, g2)); + } + + @Override + public FieldWrapper visit(GeoIntersects node) { + return stCompare(node, "ST_Intersects"); + } + + @Override + public FieldWrapper visit(GeoLength node) { + Expression p1 = node.getParameters().get(0); + FieldWrapper e1 = p1.accept(this); + Field g1 = e1.getFieldAsType(Geometry.class, true); + if (g1 == null) { + throw new IllegalArgumentException("GeoLength requires a geometry, got " + e1); + } + return new SimpleFieldWrapper(DSL.function("ST_Length", SQLDataType.NUMERIC, g1)); + } + + @Override + public FieldWrapper visit(And node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1.isCondition() && p2.isCondition()) { + return new SimpleFieldWrapper(p1.getCondition().and(p2.getCondition())); + } + throw new IllegalArgumentException("And requires two conditions, got " + p1 + " & " + p2); + } + + @Override + public FieldWrapper visit(Not node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + if (p1.isCondition()) { + return new SimpleFieldWrapper(p1.getCondition().not()); + } + throw new IllegalArgumentException("Not requires a condition, got " + p1); + } + + @Override + public FieldWrapper visit(Or node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + FieldWrapper p2 = params.get(1).accept(this); + if (p1.isCondition() && p2.isCondition()) { + return new SimpleFieldWrapper(p1.getCondition().or(p2.getCondition())); + } + throw new IllegalArgumentException("Or requires two conditions, got " + p1 + " & " + p2); + } + + @Override + public FieldWrapper visit(Ceiling node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + Field n1 = p1.getFieldAsType(Number.class, true); + return new SimpleFieldWrapper(DSL.ceil(n1)); + } + + @Override + public FieldWrapper visit(Floor node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + Field n1 = p1.getFieldAsType(Number.class, true); + return new SimpleFieldWrapper(DSL.floor(n1)); + } + + @Override + public FieldWrapper visit(Round node) { + List params = node.getParameters(); + FieldWrapper p1 = params.get(0).accept(this); + Field n1 = p1.getFieldAsType(Number.class, true); + return new SimpleFieldWrapper(DSL.round(n1)); + } + + private FieldWrapper stCompare(Function node, String functionName) { + Expression p1 = node.getParameters().get(0); + Expression p2 = node.getParameters().get(1); + FieldWrapper e1 = p1.accept(this); + FieldWrapper e2 = p2.accept(this); + Field g1 = e1.getFieldAsType(Geometry.class, true); + Field g2 = e2.getFieldAsType(Geometry.class, true); + if (g1 == null || g2 == null) { + throw new IllegalArgumentException(functionName + " requires two geometries, got " + e1 + " & " + e2); + } + return new SimpleFieldWrapper(DSL.condition(DSL.function(functionName, SQLDataType.BOOLEAN, g1, g2))); + } + + @Override + public FieldWrapper visit(STContains node) { + return stCompare(node, "ST_Contains"); + } + + @Override + public FieldWrapper visit(STCrosses node) { + return stCompare(node, "ST_Crosses"); + } + + @Override + public FieldWrapper visit(STDisjoint node) { + return stCompare(node, "ST_Disjoint"); + } + + @Override + public FieldWrapper visit(STEquals node) { + return stCompare(node, "ST_Equals"); + } + + @Override + public FieldWrapper visit(STIntersects node) { + return stCompare(node, "ST_Intersects"); + } + + @Override + public FieldWrapper visit(STOverlaps node) { + return stCompare(node, "ST_Overlaps"); + } + + @Override + public FieldWrapper visit(STRelate node) { + Expression p1 = node.getParameters().get(0); + Expression p2 = node.getParameters().get(1); + Expression p3 = node.getParameters().get(2); + FieldWrapper e1 = p1.accept(this); + FieldWrapper e2 = p2.accept(this); + FieldWrapper e3 = p3.accept(this); + Field g1 = e1.getFieldAsType(Geometry.class, true); + Field g2 = e2.getFieldAsType(Geometry.class, true); + Field g3 = e3.getFieldAsType(String.class, true); + if (g1 == null || g2 == null || g3 == null) { + throw new IllegalArgumentException("STRelate requires two geometries and a string, got " + e1 + ", " + e2 + " & " + e3); + } + return new SimpleFieldWrapper(DSL.condition(DSL.function("ST_Relate", SQLDataType.BOOLEAN, g1, g2, g3))); + } + + @Override + public FieldWrapper visit(STTouches node) { + return stCompare(node, "ST_Touches"); + } + + @Override + public FieldWrapper visit(STWithin node) { + return stCompare(node, "ST_Within"); + } + + @Override + public FieldWrapper visit(Concat node) { + Expression p1 = node.getParameters().get(0); + Expression p2 = node.getParameters().get(1); + FieldWrapper e1 = p1.accept(this); + FieldWrapper e2 = p2.accept(this); + Field s1 = e1.getFieldAsType(String.class, true); + Field s2 = e2.getFieldAsType(String.class, true); + return new SimpleFieldWrapper(s1.concat(s2)); + } + + @Override + public FieldWrapper visit(EndsWith node) { + Expression p1 = node.getParameters().get(0); + Expression p2 = node.getParameters().get(1); + FieldWrapper e1 = p1.accept(this); + FieldWrapper e2 = p2.accept(this); + Field s1 = e1.getFieldAsType(String.class, true); + Field s2 = e2.getFieldAsType(String.class, true); + return new SimpleFieldWrapper(s1.endsWith(s2)); + } + + @Override + public FieldWrapper visit(IndexOf node) { + Expression p1 = node.getParameters().get(0); + Expression p2 = node.getParameters().get(1); + FieldWrapper e1 = p1.accept(this); + FieldWrapper e2 = p2.accept(this); + Field s1 = e1.getFieldAsType(String.class, true); + Field s2 = e2.getFieldAsType(String.class, true); + return new SimpleFieldWrapper(DSL.position(s1, s2)); + } + + @Override + public FieldWrapper visit(Length node) { + Expression param = node.getParameters().get(0); + FieldWrapper e1 = param.accept(this); + Field s1 = e1.getFieldAsType(String.class, true); + return new SimpleFieldWrapper(DSL.length(s1)); + } + + @Override + public FieldWrapper visit(StartsWith node) { + Expression p1 = node.getParameters().get(0); + Expression p2 = node.getParameters().get(1); + FieldWrapper e1 = p1.accept(this); + FieldWrapper e2 = p2.accept(this); + Field s1 = e1.getFieldAsType(String.class, true); + Field s2 = e2.getFieldAsType(String.class, true); + return new SimpleFieldWrapper(s1.startsWith(s2)); + } + + @Override + public FieldWrapper visit(Substring node) { + List params = node.getParameters(); + Expression p1 = node.getParameters().get(0); + Expression p2 = node.getParameters().get(1); + FieldWrapper e1 = p1.accept(this); + FieldWrapper e2 = p2.accept(this); + Field s1 = e1.getFieldAsType(String.class, true); + Field n2 = e2.getFieldAsType(Integer.class, true); + if (params.size() > 2) { + Expression p3 = node.getParameters().get(2); + FieldWrapper e3 = p3.accept(this); + Field n3 = e3.getFieldAsType(Integer.class, true); + return new SimpleFieldWrapper(DSL.substring(s1, n2.add(1), n3)); + } + return new SimpleFieldWrapper(DSL.substring(s1, n2.add(1))); + } + + @Override + public FieldWrapper visit(SubstringOf node) { + Expression p1 = node.getParameters().get(0); + Expression p2 = node.getParameters().get(1); + FieldWrapper e1 = p1.accept(this); + FieldWrapper e2 = p2.accept(this); + Field s1 = e1.getFieldAsType(String.class, true); + Field s2 = e2.getFieldAsType(String.class, true); + return new SimpleFieldWrapper(s2.contains(s1)); + } + + @Override + public FieldWrapper visit(ToLower node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + Field field = input.getFieldAsType(String.class, true); + return new SimpleFieldWrapper(DSL.lower(field)); + } + + @Override + public FieldWrapper visit(ToUpper node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + Field field = input.getFieldAsType(String.class, true); + return new SimpleFieldWrapper(DSL.upper(field)); + } + + @Override + public FieldWrapper visit(Trim node) { + Expression param = node.getParameters().get(0); + FieldWrapper input = param.accept(this); + Field field = input.getFieldAsType(String.class, true); + return new SimpleFieldWrapper(DSL.trim(field)); + } + + @Override + public FieldWrapper visit(PrincipalName node) { + return new SimpleFieldWrapper(DSL.value(node.getValue())); + } + + @Override + public FieldWrapper visit(ContextEntityProperty node) { + return new SimpleFieldWrapper(DSL.value(node.getValue())); + } + + public abstract Geometry fromGeoJsonConstant(GeoJsonConstant node); + + private static class PathState { + + private TableRef pathTableRef; + private List elements; + private FieldWrapper finalExpression = null; + private int curIndex; + private boolean finished = false; + } +} diff --git a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/JooqPersistenceManager.java b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/JooqPersistenceManager.java index 6b88699c2..0223e4c89 100644 --- a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/JooqPersistenceManager.java +++ b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/JooqPersistenceManager.java @@ -65,6 +65,8 @@ public interface JooqPersistenceManager extends LiquibaseUser, PersistenceManage void generateLiquibaseVariables(Map target, String entity, String type); + ExpressionHandler createExpressionHandler(QueryBuilder queryBuilder); + Entity get(EntityType entityType, PkValue id, Query query); ConnectionUtils.ConnectionWrapper getConnectionProvider(); diff --git a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/MariaDBExpressionHandler.java b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/MariaDBExpressionHandler.java new file mode 100644 index 000000000..44553657d --- /dev/null +++ b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/MariaDBExpressionHandler.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 Fraunhofer Institut IOSB, Fraunhoferstr. 1, D 76131 + * Karlsruhe, Germany. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq; + +import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.bindings.PostGisGeometryBinding; +import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.FieldWrapper; +import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.SimpleFieldWrapper; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.GeoJsonConstant; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.LineStringConstant; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.PointConstant; +import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.PolygonConstant; +import de.fraunhofer.iosb.ilt.frostserver.settings.CoreSettings; +import org.geojson.GeoJsonObject; +import org.geolatte.geom.Geometry; +import org.geolatte.geom.codec.Wkt; +import org.jooq.impl.DSL; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Hylke van der Schaaf + */ +public class MariaDBExpressionHandler extends ExpressionHandler { + + private static final String ST_GeomFromText = "ST_GeomFromText(?)"; + + private static final Logger LOGGER = LoggerFactory.getLogger(MariaDBExpressionHandler.class); + + public MariaDBExpressionHandler(CoreSettings settings, QueryBuilder queryBuilder) { + super(settings, queryBuilder); + } + + @Override + public FieldWrapper visit(LineStringConstant node) { + Geometry geom = fromGeoJsonConstant(node); + return new SimpleFieldWrapper(DSL.field(ST_GeomFromText, PostGisGeometryBinding.dataType(), geom.asText())); + } + + @Override + public FieldWrapper visit(PointConstant node) { + Geometry geom = fromGeoJsonConstant(node); + return new SimpleFieldWrapper(DSL.field(ST_GeomFromText, PostGisGeometryBinding.dataType(), geom.asText())); + } + + @Override + public FieldWrapper visit(PolygonConstant node) { + Geometry geom = fromGeoJsonConstant(node); + return new SimpleFieldWrapper(DSL.field(ST_GeomFromText, PostGisGeometryBinding.dataType(), geom.asText())); + } + + public Geometry fromGeoJsonConstant(GeoJsonConstant node) { + if (node.getValue().getCrs() == null) { + return Wkt.fromWkt(node.getSource()); + } + return Wkt.fromWkt(node.getSource()); + } +} diff --git a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/MariadbPersistenceManager.java b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/MariadbPersistenceManager.java index 17c8eb94c..b1865f158 100644 --- a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/MariadbPersistenceManager.java +++ b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/MariadbPersistenceManager.java @@ -860,6 +860,11 @@ public void generateLiquibaseVariables(Map target, String entity } } + @Override + public ExpressionHandler createExpressionHandler(QueryBuilder queryBuilder) { + return new MariaDBExpressionHandler(settings, queryBuilder); + } + @Override public String checkForUpgrades() { Map props = CollectionsHelper.LinkedHashMapBuilder() diff --git a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/PgExpressionHandler.java b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/PgExpressionHandler.java index 78fa80654..9c4045f3b 100644 --- a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/PgExpressionHandler.java +++ b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/PgExpressionHandler.java @@ -17,141 +17,18 @@ */ package de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq; -import static de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.QueryState.ALIAS_ROOT; -import static de.fraunhofer.iosb.ilt.frostserver.property.SpecialNames.AT_IOT_ID; -import static de.fraunhofer.iosb.ilt.frostserver.util.Constants.NOT_IMPLEMENTED_MULTI_VALUE_PK; - -import de.fraunhofer.iosb.ilt.frostserver.model.EntityType; import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.bindings.PostGisGeometryBinding; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.ArrayConstandFieldWrapper; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.FieldListWrapper; import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.FieldWrapper; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.JsonFieldFactory; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.JsonFieldFactory.JsonFieldWrapper; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.NullWrapper; import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.SimpleFieldWrapper; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.StaDateTimeWrapper; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.StaDurationWrapper; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.StaTimeIntervalWrapper; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.TimeFieldWrapper; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.fieldwrapper.WrapperHelper; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.StaMainTable; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.tables.TableCollection; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.QueryState; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.TableRef; -import de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.utils.Utils; -import de.fraunhofer.iosb.ilt.frostserver.property.EntityPropertyCustom; -import de.fraunhofer.iosb.ilt.frostserver.property.EntityPropertyCustomLink; -import de.fraunhofer.iosb.ilt.frostserver.property.EntityPropertyMain; -import de.fraunhofer.iosb.ilt.frostserver.property.NavigationProperty; -import de.fraunhofer.iosb.ilt.frostserver.property.NavigationPropertyMain; -import de.fraunhofer.iosb.ilt.frostserver.property.NavigationPropertyMain.NavigationPropertyEntitySet; -import de.fraunhofer.iosb.ilt.frostserver.property.Property; -import de.fraunhofer.iosb.ilt.frostserver.property.PropertyReference; -import de.fraunhofer.iosb.ilt.frostserver.query.OrderBy; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.Expression; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.ExpressionVisitor; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.Path; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.BooleanConstant; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.ConstantList; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.DateConstant; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.DateTimeConstant; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.DoubleConstant; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.DurationConstant; import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.GeoJsonConstant; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.IntegerConstant; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.IntervalConstant; import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.LineStringConstant; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.NullConstant; import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.PointConstant; import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.PolygonConstant; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.StringConstant; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.constant.TimeConstant; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.Function; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.arithmetic.Add; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.arithmetic.Divide; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.arithmetic.Modulo; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.arithmetic.Multiply; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.arithmetic.Subtract; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.comparison.Equal; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.comparison.GreaterEqual; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.comparison.GreaterThan; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.comparison.In; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.comparison.LessEqual; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.comparison.LessThan; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.comparison.NotEqual; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.context.ContextEntityProperty; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.context.PrincipalName; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.Date; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.Day; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.FractionalSeconds; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.Hour; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.MaxDateTime; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.MinDateTime; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.Minute; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.Month; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.Now; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.Second; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.Time; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.TotalOffsetMinutes; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.date.Year; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.logical.And; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.logical.Any; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.logical.Not; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.logical.Or; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.math.Ceiling; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.math.Floor; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.math.Round; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.GeoDistance; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.GeoIntersects; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.GeoLength; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.STContains; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.STCrosses; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.STDisjoint; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.STEquals; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.STIntersects; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.STOverlaps; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.STRelate; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.STTouches; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.spatialrelation.STWithin; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.Concat; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.EndsWith; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.IndexOf; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.Length; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.StartsWith; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.Substring; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.SubstringOf; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.ToLower; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.ToUpper; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.string.Trim; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.temporal.After; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.temporal.Before; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.temporal.During; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.temporal.Finishes; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.temporal.Meets; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.temporal.Overlaps; -import de.fraunhofer.iosb.ilt.frostserver.query.expression.function.temporal.Starts; import de.fraunhofer.iosb.ilt.frostserver.settings.CoreSettings; -import de.fraunhofer.iosb.ilt.frostserver.settings.Settings; -import java.util.Calendar; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; -import net.time4j.Moment; -import net.time4j.PlainDate; -import net.time4j.PlainTime; -import net.time4j.ZonalDateTime; -import net.time4j.range.MomentInterval; -import org.apache.commons.lang3.NotImplementedException; import org.geojson.GeoJsonObject; import org.geolatte.geom.Geometry; import org.geolatte.geom.codec.Wkt; -import org.jooq.Condition; -import org.jooq.DatePart; -import org.jooq.Field; import org.jooq.impl.DSL; -import org.jooq.impl.SQLDataType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -159,397 +36,14 @@ * * @author Hylke van der Schaaf */ -public class PgExpressionHandler implements ExpressionVisitor { +public class PgExpressionHandler extends ExpressionHandler { - /** - * The logger for this class. - */ - private static final Logger LOGGER = LoggerFactory.getLogger(PgExpressionHandler.class); private static final String ST_GEOM_FROM_EWKT = "ST_GeomFromEWKT(?)"; - private final QueryBuilder queryBuilder; - private QueryState queryState; - - private int maxCustomLinkDepth = -1; + private static final Logger LOGGER = LoggerFactory.getLogger(PgExpressionHandler.class); public PgExpressionHandler(CoreSettings settings, QueryBuilder queryBuilder) { - this.queryBuilder = queryBuilder; - this.queryState = queryBuilder.getQueryState(); - final Settings experimentalSettings = settings.getExtensionSettings(); - if (experimentalSettings.getBoolean(CoreSettings.TAG_CUSTOM_LINKS_ENABLE, CoreSettings.class)) { - maxCustomLinkDepth = experimentalSettings.getInt(CoreSettings.TAG_CUSTOM_LINKS_RECURSE_DEPTH, CoreSettings.class); - } - } - - public Condition addFilterToWhere(Expression filter, Condition sqlWhere) { - FieldWrapper filterField = filter.accept(this); - if (filterField.isCondition()) { - return sqlWhere.and(filterField.getCondition()); - - } - if (filterField instanceof FieldListWrapper listExpression) { - for (Field expression : listExpression.getExpressions().values()) { - if (Boolean.class.isAssignableFrom(expression.getType())) { - Field predicate = expression; - return sqlWhere.and(predicate); - } - } - } - LOGGER.error("Filter is not a predicate but a {}.", filterField.getClass().getName()); - throw new IllegalArgumentException("Filter is not a predicate but a " + filterField.getClass().getName()); - } - - public void addOrderbyToQuery(OrderBy orderBy, Utils.SortSelectFields orderFields) { - FieldWrapper resultExpression = orderBy.getExpression().accept(this); - if (resultExpression instanceof StaTimeIntervalWrapper timeInterval) { - addToQuery(orderBy, timeInterval.getStart(), orderFields); - addToQuery(orderBy, timeInterval.getEnd(), orderFields); - return; - } - if (resultExpression instanceof StaDurationWrapper duration) { - addToQuery(orderBy, duration.getDuration(), orderFields); - return; - } - if (resultExpression instanceof StaDateTimeWrapper dateTime) { - addToQuery(orderBy, dateTime.getDateTime(), orderFields); - return; - } - if (resultExpression instanceof FieldListWrapper fieldListWrapper) { - for (Field sqlExpression : fieldListWrapper.getExpressionsForOrder().values()) { - addToQuery(orderBy, sqlExpression, orderFields); - } - return; - } - Field field = resultExpression.getDefaultField(); - addToQuery(orderBy, field, orderFields); - } - - public void addToQuery(OrderBy orderBy, Field field, Utils.SortSelectFields orderFields) { - orderFields.add(field, orderBy.getType()); - } - - @Override - public FieldWrapper visit(Path path) { - PathState state = new PathState(); - state.elements = path.getElements(); - var storedQueryState = queryState; - try { - Property firstElement = state.elements.get(0); - int startIdx = 0; - if (firstElement instanceof PropertyReference pr) { - startIdx = 1; - queryState = queryState.findStateForAlias(pr.getName()); - } else { - queryState = queryState.findStateForAlias(ALIAS_ROOT); - } - state.pathTableRef = queryState.getTableRef(); - walkPath(state, startIdx, path); - } finally { - queryState = storedQueryState; - } - return state.finalExpression; - } - - private void walkPath(PathState state, int startIdx, Path path) throws IllegalArgumentException { - for (state.curIndex = startIdx; state.curIndex < state.elements.size() && !state.finished; state.curIndex++) { - Property element = state.elements.get(state.curIndex); - if (element instanceof EntityPropertyCustom) { - handleCustomProperty(state, path); - - } else if (element instanceof EntityPropertyCustomLink) { - handleCustomProperty(state, path); - - } else if (element instanceof EntityPropertyMain entityPropertyMain) { - handleEntityProperty(state, path, entityPropertyMain); - - } else if (element instanceof NavigationPropertyMain navigationPropertyMain) { - handleNavigationProperty(state, path, navigationPropertyMain); - } - } - if (state.finalExpression == null) { - throw new IllegalArgumentException("Path does not end in an EntityProperty: " + path); - } - if (state.finalExpression instanceof Field field && Moment.class.isAssignableFrom(field.getType())) { - Field dateTimePath = (Field) state.finalExpression; - state.finalExpression = new StaDateTimeWrapper(dateTimePath); - } - } - - private void handleCustomProperty(PathState state, Path path) { - if (state.finalExpression == null) { - throw new IllegalArgumentException("CustomProperty must follow an EntityProperty: " + path); - } - // generate finalExpression::jsonb#>>'{x,y,z}' - JsonFieldWrapper jsonFactory; - if (state.finalExpression instanceof JsonFieldWrapper jsonFieldWrapper) { - jsonFactory = jsonFieldWrapper; - } else { - jsonFactory = new JsonFieldWrapper(state.finalExpression); - } - for (; state.curIndex < state.elements.size(); state.curIndex++) { - final Property property = state.elements.get(state.curIndex); - String name = property.getName(); - if (property instanceof EntityPropertyCustomLink epcl) { - int maxDepth = state.curIndex + maxCustomLinkDepth; - if (state.curIndex <= maxDepth) { - handleCustomLink(epcl, jsonFactory, name, state); - return; - } else { - jsonFactory.addToPath(name); - } - } else { - jsonFactory.addToPath(name); - } - } - state.finalExpression = jsonFactory.materialise(); - state.finished = true; - } - - private void handleCustomLink(final EntityPropertyCustomLink epcl, JsonFieldWrapper jsonFactory, String name, PathState state) { - JsonFieldFactory.JsonFieldWrapper sourceIdFieldWrapper = jsonFactory.addToPath(name + AT_IOT_ID).materialise(); - Field sourceIdField = sourceIdFieldWrapper.getFieldAsType(Number.class, true); - state.pathTableRef = queryEntityType(epcl, state.pathTableRef, sourceIdField); - state.finalExpression = null; - } - - private void handleEntityProperty(PathState state, Path path, EntityPropertyMain element) { - if (state.finalExpression != null) { - throw new IllegalArgumentException("EntityProperty can not follow an other EntityProperty: " + path); - } - Map pathExpressions = state.pathTableRef.getTable() - .getPropertyFieldRegistry() - .getAllFieldsForProperty(element, new LinkedHashMap<>()); - if (pathExpressions.size() == 1) { - final Field field = pathExpressions.values().stream().iterator().next(); - Field optimisedField = state.pathTableRef.getJoinEqual(field); - state.finalExpression = WrapperHelper.wrapField(optimisedField); - } else { - state.finalExpression = getSubExpression(state, pathExpressions); - } - } - - private void handleNavigationProperty(PathState state, Path path, NavigationPropertyMain np) { - if (state.finalExpression != null) { - throw new IllegalArgumentException("NavigationProperty can not follow an EntityProperty: " + path); - } - state.pathTableRef = queryEntityType(np, state.pathTableRef); - } - - private FieldWrapper getSubExpression(PathState state, Map pathExpressions) { - int nextIdx = state.curIndex + 1; - if (state.elements.size() > nextIdx) { - Property subProperty = state.elements.get(nextIdx); - // If the subProperty is unknown, and the expression can be of type JSON, - // then we assume JSON. - if (!pathExpressions.containsKey(subProperty.getName()) && pathExpressions.containsKey("j")) { - return new SimpleFieldWrapper(pathExpressions.get("j")); - } - // We can not accept json, so the subProperty must be a known direction. - state.finished = true; - return WrapperHelper.wrapField(pathExpressions.get(subProperty.getName())); - } - if (pathExpressions.containsKey(StaTimeIntervalWrapper.KEY_TIME_INTERVAL_START) - && pathExpressions.containsKey(StaTimeIntervalWrapper.KEY_TIME_INTERVAL_END)) { - return new StaTimeIntervalWrapper(pathExpressions); - } - return new FieldListWrapper(pathExpressions); - } - - /** - * Queries the given entity type, as relation to the given table reference - * and returns a new table reference. Effectively, this generates a join. - * - * @param np The NavigationProperty to query - * @param last The table the requested entity is related to. - * @return The table reference of the requested entity. - */ - public TableRef queryEntityType(NavigationProperty np, TableRef last) { - if (queryState == null) { - throw new IllegalStateException("QueryState should not be null"); - } - if (last == null) { - throw new IllegalStateException("last result should not be null"); - } - - TableRef existingJoin = last.getJoin(np); - if (existingJoin != null) { - return existingJoin; - } - - return last.createJoin(np.getName(), queryState); - } - - /** - * Directly query an entity type. Used for custom linking. - * - * @param epcl the custom link. - * @param sourceRef The source table ref. - * @param sourceIdField The source ID field. - * @return A new table ref with the target entity type table joined. - */ - public TableRef queryEntityType(EntityPropertyCustomLink epcl, TableRef sourceRef, Field sourceIdField) { - final EntityType targetEntityType = epcl.getEntityType(); - final StaMainTable target = queryBuilder.getTableCollection().getTablesByType().get(targetEntityType); - final StaMainTable targetAliased = target.asSecure(queryState.getNextAlias(), queryBuilder.getPersistenceManager()); - final List targetField = targetAliased.getPkFields(); - if (targetField.size() > 1) { - throw new NotImplementedException(NOT_IMPLEMENTED_MULTI_VALUE_PK); - } - queryState.setSqlFrom(queryState.getSqlFrom().leftJoin(targetAliased).on(targetField.get(0).eq(sourceIdField))); - TableRef newRef = new TableRef(targetAliased); - sourceRef.addJoin(epcl, newRef); - return newRef; - } - - public Field[] findPair(FieldWrapper p1, FieldWrapper p2) { - Field[] result = new Field[2]; - - result[0] = p1.getFieldAsType(Number.class, true); - result[1] = p2.getFieldAsType(Number.class, true); - if (result[0] != null && result[1] != null) { - return result; - } - - result[0] = p1.getFieldAsType(Boolean.class, true); - result[1] = p2.getFieldAsType(Boolean.class, true); - if (result[0] != null && result[1] != null) { - return result; - } - - // If both are strings, use strings. - result[0] = p1.getFieldAsType(String.class, true); - result[1] = p2.getFieldAsType(String.class, true); - if (result[0] != null && result[1] != null) { - return result; - } - - LOGGER.warn("Could not match types for {} and {}", p1, p2); - result[0] = p1.getDefaultField(); - result[1] = p2.getDefaultField(); - return result; - } - - @Override - public FieldWrapper visit(Any node) { - final TableCollection tc = queryBuilder.getTableCollection(); - final QueryState parentQueryState = queryState; - - QueryState existsQueryState = walkAnyPath(parentQueryState, node, tc); - if (existsQueryState == null) { - throw new IllegalStateException("Failed to parse any()."); - } - - // Set our subQuery state to be the active one. - queryState = existsQueryState; - try { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - if (!p1.isCondition()) { - throw new IllegalArgumentException("Any() requires a condition, got " + p1); - } - Condition exists = DSL.exists(DSL.selectOne().from(existsQueryState.getSqlFrom()).where(existsQueryState.getSqlWhere().and(p1.getCondition()))); - return new SimpleFieldWrapper(exists); - } finally { - // Set the query state back to what it was. - queryState = parentQueryState; - } - } - - private QueryState walkAnyPath(final QueryState parentQueryState, Any node, final TableCollection tc) throws IllegalArgumentException { - final StaMainTable parentMainTable = parentQueryState.getMainTable(); - final EntityType parentEntityType = parentMainTable.getEntityType(); - final Path path = node.getCollection(); - final List elements = path.getElements(); - QueryState existsQueryState = null; - TableRef lastJoin = null; - Property firstElement = elements.get(0); - int startIdx = 0; - if (firstElement instanceof PropertyReference) { - startIdx = 1; - } - for (int idx = elements.size() - 1; idx >= startIdx; idx--) { - Property element = elements.get(idx); - if ((lastJoin == null)) { - if (element instanceof NavigationPropertyEntitySet npes) { - // Last entry in the path: the target collection. - EntityType finalType = npes.getEntityType(); - final StaMainTable tableForType = tc.getTableForType(finalType).asSecure(parentQueryState.getNextAlias(), parentQueryState.getPersistenceManager()); - existsQueryState = new QueryState(tableForType, parentQueryState, node.getLambdaName()); - lastJoin = existsQueryState.getTableRef(); - } else { - throw new IllegalArgumentException("Path before any() MUST end in an EntitySet. Found: " + element); - } - } - if (element instanceof NavigationPropertyMain npm) { - var inverse = npm.getInverse(); - if (idx == startIdx) { - // First entry in the path: Link to the main table! - if (inverse.getEntityType() != parentEntityType) { - throw new IllegalArgumentException("path of any() did not track back to main entity type. Expected " + parentEntityType + " got " + inverse.getEntityType()); - } - lastJoin.createSemiJoin(inverse.getName(), parentMainTable, existsQueryState); - - } else { - TableRef existingJoin = lastJoin.getJoin(inverse); - if (existingJoin != null) { - lastJoin = existingJoin; - } - lastJoin = lastJoin.createJoin(inverse.getName(), existsQueryState); - } - - } else if (element instanceof EntityPropertyCustomLink) { - throw new IllegalArgumentException("Path before any() should not contain Custom Links. Found: " + element); - } else if (element instanceof EntityPropertyCustom) { - throw new IllegalArgumentException("Path before any() should not contain EntityProperties. Found: " + element); - } else if (element instanceof EntityPropertyMain) { - throw new IllegalArgumentException("Path before any() should not contain EntityProperties. Found: " + element); - } else { - throw new IllegalArgumentException("Path before any() contains unknown element. Found: " + element); - } - } - return existsQueryState; - } - - @Override - public FieldWrapper visit(BooleanConstant node) { - return new SimpleFieldWrapper(Boolean.TRUE.equals(node.getValue()) ? DSL.condition("TRUE") : DSL.condition("FALSE")); - } - - @Override - public FieldWrapper visit(DateConstant node) { - PlainDate date = node.getValue(); - Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - instance.set(date.getYear(), date.getMonth() - 1, date.getDayOfMonth()); - return new SimpleFieldWrapper(DSL.inline(new java.sql.Date(instance.getTimeInMillis()))); - } - - @Override - public FieldWrapper visit(DateTimeConstant node) { - ZonalDateTime value = node.getValue(); - return new StaDateTimeWrapper(value.toMoment(), true); - } - - @Override - public FieldWrapper visit(DoubleConstant node) { - return new SimpleFieldWrapper(DSL.val(node.getValue())); - } - - @Override - public FieldWrapper visit(DurationConstant node) { - return new StaDurationWrapper(node); - } - - @Override - public FieldWrapper visit(IntervalConstant node) { - MomentInterval value = node.getValue(); - return new StaTimeIntervalWrapper( - value.getStartAsMoment(), - value.getEndAsMoment()); - } - - @Override - public FieldWrapper visit(IntegerConstant node) { - return new SimpleFieldWrapper(DSL.val(node.getValue())); + super(settings, queryBuilder); } @Override @@ -558,11 +52,6 @@ public FieldWrapper visit(LineStringConstant node) { return new SimpleFieldWrapper(DSL.field(ST_GEOM_FROM_EWKT, PostGisGeometryBinding.dataType(), geom.asText())); } - @Override - public FieldWrapper visit(NullConstant node) { - return new NullWrapper(); - } - @Override public FieldWrapper visit(PointConstant node) { Geometry geom = fromGeoJsonConstant(node); @@ -575,739 +64,11 @@ public FieldWrapper visit(PolygonConstant node) { return new SimpleFieldWrapper(DSL.field(ST_GEOM_FROM_EWKT, PostGisGeometryBinding.dataType(), geom.asText())); } - private static Geometry fromGeoJsonConstant(GeoJsonConstant node) { + @Override + public Geometry fromGeoJsonConstant(GeoJsonConstant node) { if (node.getValue().getCrs() == null) { return Wkt.fromWkt("SRID=4326;" + node.getSource()); } return Wkt.fromWkt(node.getSource()); } - - @Override - public FieldWrapper visit(StringConstant node) { - return new SimpleFieldWrapper(DSL.value(node.getValue())); - } - - @Override - public FieldWrapper visit(TimeConstant node) { - PlainTime time = node.getValue(); - Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - instance.set(1970, 1, 1, time.getHour(), time.getMinute(), time.getSecond()); - return new SimpleFieldWrapper(DSL.inline(new java.sql.Time(instance.getTimeInMillis()))); - } - - @Override - public FieldWrapper visit(ConstantList node) { - return new ArrayConstandFieldWrapper(node); - } - - @Override - public FieldWrapper visit(Before node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper timeExpression) { - return timeExpression.before(p2); - } - throw new IllegalArgumentException("Before can only be used on times, not on " + p1.getClass().getName()); - } - - @Override - public FieldWrapper visit(After node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper timeExpression) { - return timeExpression.after(p2); - } - throw new IllegalArgumentException("After can only be used on times, not on " + p1.getClass().getName()); - } - - @Override - public FieldWrapper visit(Meets node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper timeExpression) { - return timeExpression.meets(p2); - } - throw new IllegalArgumentException("Meets can only be used on times, not on " + p1.getClass().getName()); - } - - @Override - public FieldWrapper visit(During node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p2 instanceof StaTimeIntervalWrapper ti2) { - return ti2.contains(p1); - } - throw new IllegalArgumentException("Second parameter of 'during' has to be an interval."); - } - - @Override - public FieldWrapper visit(Overlaps node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper timeExpression) { - return timeExpression.overlaps(p2); - } - throw new IllegalArgumentException("Overlaps can only be used on times, not on " + p1.getClass().getName()); - } - - @Override - public FieldWrapper visit(Starts node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper timeExpression) { - return timeExpression.starts(p2); - } - throw new IllegalArgumentException("Starts can only be used on times, not on " + p1.getClass().getName()); - } - - @Override - public FieldWrapper visit(Finishes node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper timeExpression) { - return timeExpression.finishes(p2); - } - throw new IllegalArgumentException("Finishes can only be used on times, not on " + p1.getClass().getName()); - } - - @Override - public FieldWrapper visit(Add node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper ti1) { - return ti1.add(p2); - } - if (p2 instanceof TimeFieldWrapper ti2) { - return ti2.add(p1); - } - Field n1 = p1.getFieldAsType(Number.class, true); - Field n2 = p2.getFieldAsType(Number.class, true); - return new SimpleFieldWrapper(n1.add(n2)); - } - - @Override - public FieldWrapper visit(Divide node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper ti1) { - return ti1.div(p2); - } - if (p2 instanceof TimeFieldWrapper) { - throw new IllegalArgumentException("Can not devide by a TimeExpression."); - } - Field n1 = p1.getFieldAsType(Number.class, true); - Field n2 = p2.getFieldAsType(Number.class, true); - return new SimpleFieldWrapper(n1.divide(n2).coerce(SQLDataType.DOUBLE)); - } - - @Override - public FieldWrapper visit(Modulo node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - Field n1 = p1.getFieldAsType(Number.class, true); - Field n2 = p2.getFieldAsType(Number.class, true); - if (n1.getType().equals(Double.class)) { - n1 = n1.cast(SQLDataType.NUMERIC); - } - if (n2.getType().equals(Double.class)) { - n2 = n2.cast(SQLDataType.NUMERIC); - } - return new SimpleFieldWrapper(n1.mod(n2)); - } - - @Override - public FieldWrapper visit(Multiply node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper ti1) { - return ti1.mul(p2); - } - if (p2 instanceof TimeFieldWrapper ti2) { - return ti2.mul(p1); - } - Field n1 = p1.getFieldAsType(Number.class, true); - Field n2 = p2.getFieldAsType(Number.class, true); - return new SimpleFieldWrapper(n1.multiply(n2)); - } - - @Override - public FieldWrapper visit(Subtract node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper ti1) { - return ti1.sub(p2); - } - if (p2 instanceof TimeFieldWrapper) { - throw new IllegalArgumentException("Can not sub a time expression from a " + p1.getClass().getName()); - } - Field n1 = p1.getFieldAsType(Number.class, true); - Field n2 = p2.getFieldAsType(Number.class, true); - return new SimpleFieldWrapper(n1.subtract(n2)); - } - - @Override - public FieldWrapper visit(Equal node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof NullWrapper) { - return new SimpleFieldWrapper(p2.getDefaultField().isNull()); - } - if (p2 instanceof NullWrapper) { - return new SimpleFieldWrapper(p1.getDefaultField().isNull()); - } - if (p1 instanceof TimeFieldWrapper ti1) { - return ti1.eq(p2); - } - if (p2 instanceof TimeFieldWrapper ti2) { - return ti2.eq(p1); - } - if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { - return l1.eq(p2); - } - if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { - return l2.eq(p1); - } - - Field[] pair = findPair(p1, p2); - return new SimpleFieldWrapper(pair[0].eq(pair[1])); - } - - @Override - public FieldWrapper visit(GreaterEqual node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper ti1) { - return ti1.goe(p2); - } - if (p2 instanceof TimeFieldWrapper ti2) { - return ti2.loe(p1); - } - if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { - return l1.goe(p2); - } - if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { - return l2.loe(p1); - } - Field[] pair = findPair(p1, p2); - return new SimpleFieldWrapper(pair[0].greaterOrEqual(pair[1])); - } - - @Override - public FieldWrapper visit(GreaterThan node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper ti1) { - return ti1.gt(p2); - } - if (p2 instanceof TimeFieldWrapper ti2) { - return ti2.lt(p1); - } - if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { - return l1.gt(p2); - } - if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { - return l2.lt(p1); - } - Field[] pair = findPair(p1, p2); - return new SimpleFieldWrapper(pair[0].greaterThan(pair[1])); - } - - @Override - public FieldWrapper visit(LessEqual node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper ti1) { - return ti1.loe(p2); - } - if (p2 instanceof TimeFieldWrapper ti2) { - return ti2.goe(p1); - } - if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { - return l1.loe(p2); - } - if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { - return l2.goe(p1); - } - Field[] pair = findPair(p1, p2); - return new SimpleFieldWrapper(pair[0].lessOrEqual(pair[1])); - } - - @Override - public FieldWrapper visit(LessThan node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof TimeFieldWrapper ti1) { - return ti1.lt(p2); - } - if (p2 instanceof TimeFieldWrapper ti2) { - return ti2.gt(p1); - } - if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { - return l1.lt(p2); - } - if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { - return l2.gt(p1); - } - Field[] pair = findPair(p1, p2); - return new SimpleFieldWrapper(pair[0].lt(pair[1])); - } - - @Override - public FieldWrapper visit(NotEqual node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1 instanceof NullWrapper) { - return new SimpleFieldWrapper(p2.getDefaultField().isNotNull()); - } - if (p2 instanceof NullWrapper) { - return new SimpleFieldWrapper(p1.getDefaultField().isNotNull()); - } - if (p1 instanceof TimeFieldWrapper ti1) { - return ti1.neq(p2); - } - if (p2 instanceof TimeFieldWrapper ti2) { - return ti2.neq(p1); - } - if (p1 instanceof JsonFieldFactory.JsonFieldWrapper l1) { - return l1.ne(p2); - } - if (p2 instanceof JsonFieldFactory.JsonFieldWrapper l2) { - return l2.ne(p1); - } - Field[] pair = findPair(p1, p2); - return new SimpleFieldWrapper(pair[0].ne(pair[1])); - } - - @Override - public FieldWrapper visit(In node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p2 instanceof ArrayConstandFieldWrapper clP2) { - return new SimpleFieldWrapper(p1.getDefaultField().in(clP2.getValueList())); - } - Field[] pair = findPair(p1, p2); - if (p2 instanceof JsonFieldWrapper jP2) { - return jP2.contains(pair[0]); - } else { - return new SimpleFieldWrapper(pair[0].in(pair[1])); - } - } - - @Override - public FieldWrapper visit(Date node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - if (input instanceof TimeFieldWrapper timeExpression) { - return new SimpleFieldWrapper(DSL.function("date", java.sql.Date.class, timeExpression.getDateTime())); - } - Field fieldAsDate = input.getFieldAsType(java.sql.Date.class, true); - if (fieldAsDate != null) { - return new SimpleFieldWrapper(fieldAsDate); - } - throw new IllegalArgumentException("Date can only be used on times, not on " + input.getClass().getName()); - } - - @Override - public FieldWrapper visit(Day node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - if (input instanceof TimeFieldWrapper timeExpression) { - return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.DAY)); - } - throw new IllegalArgumentException("Day can only be used on times, not on " + input.getClass().getName()); - } - - @Override - public FieldWrapper visit(FractionalSeconds node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - if (input instanceof TimeFieldWrapper timeExpression) { - return new SimpleFieldWrapper(DSL.field("(date_part('SECONDS', TIMESTAMPTZ ?) - floor(date_part('SECONDS', TIMESTAMPTZ ?)))", Double.class, timeExpression.getDateTime(), timeExpression.getDateTime())); - } - throw new IllegalArgumentException("FractionalSeconds can only be used on times, not on " + input.getClass().getName()); - } - - @Override - public FieldWrapper visit(Hour node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - if (input instanceof TimeFieldWrapper timeExpression) { - return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.HOUR)); - } - throw new IllegalArgumentException("Hour can only be used on times, not on " + input.getClass().getName()); - } - - @Override - public FieldWrapper visit(MaxDateTime node) { - return new StaDateTimeWrapper(PostgresPersistenceManager.DATETIME_MAX, true); - } - - @Override - public FieldWrapper visit(MinDateTime node) { - return new StaDateTimeWrapper(PostgresPersistenceManager.DATETIME_MIN, true); - } - - @Override - public FieldWrapper visit(Minute node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - if (input instanceof TimeFieldWrapper timeExpression) { - return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.MINUTE)); - } - throw new IllegalArgumentException("Minute can only be used on times, not on " + input.getClass().getName()); - } - - @Override - public FieldWrapper visit(Month node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - if (input instanceof TimeFieldWrapper timeExpression) { - return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.MONTH)); - } - throw new IllegalArgumentException("Month can only be used on times, not on " + input.getClass().getName()); - } - - @Override - public FieldWrapper visit(Now node) { - return new StaDateTimeWrapper(DSL.field("now()", Moment.class)); - } - - @Override - public FieldWrapper visit(Second node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - if (input instanceof TimeFieldWrapper timeExpression) { - return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.SECOND)); - } - throw new IllegalArgumentException("Second can only be used on times, not on " + input.getClass().getName()); - } - - @Override - public FieldWrapper visit(Time node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - if (input instanceof TimeFieldWrapper timeExpression) { - return new SimpleFieldWrapper(timeExpression.getDateTime().cast(SQLDataType.TIME)); - } - throw new IllegalArgumentException("Time can only be used on times, not on " + input.getClass().getName()); - } - - @Override - public FieldWrapper visit(TotalOffsetMinutes node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - if (input instanceof TimeFieldWrapper timeExpression) { - return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.TIMEZONE).div(60)); - } - throw new IllegalArgumentException("TotalOffsetMinutes can only be used on times, not on " + input.getClass().getName()); - } - - @Override - public FieldWrapper visit(Year node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - if (input instanceof TimeFieldWrapper timeExpression) { - return new SimpleFieldWrapper(DSL.extract(timeExpression.getDateTime(), DatePart.YEAR)); - } - throw new IllegalArgumentException("Year can only be used on times, not on " + input.getClass().getName()); - } - - @Override - public FieldWrapper visit(GeoDistance node) { - Expression p1 = node.getParameters().get(0); - Expression p2 = node.getParameters().get(1); - FieldWrapper e1 = p1.accept(this); - FieldWrapper e2 = p2.accept(this); - Field g1 = e1.getFieldAsType(Geometry.class, true); - Field g2 = e2.getFieldAsType(Geometry.class, true); - if (g1 == null || g2 == null) { - throw new IllegalArgumentException("GeoDistance requires two geometries, got " + e1 + " & " + e2); - } - return new SimpleFieldWrapper(DSL.function("ST_Distance", SQLDataType.NUMERIC, g1, g2)); - } - - @Override - public FieldWrapper visit(GeoIntersects node) { - return stCompare(node, "ST_Intersects"); - } - - @Override - public FieldWrapper visit(GeoLength node) { - Expression p1 = node.getParameters().get(0); - FieldWrapper e1 = p1.accept(this); - Field g1 = e1.getFieldAsType(Geometry.class, true); - if (g1 == null) { - throw new IllegalArgumentException("GeoLength requires a geometry, got " + e1); - } - return new SimpleFieldWrapper(DSL.function("ST_Length", SQLDataType.NUMERIC, g1)); - } - - @Override - public FieldWrapper visit(And node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1.isCondition() && p2.isCondition()) { - return new SimpleFieldWrapper(p1.getCondition().and(p2.getCondition())); - } - throw new IllegalArgumentException("And requires two conditions, got " + p1 + " & " + p2); - } - - @Override - public FieldWrapper visit(Not node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - if (p1.isCondition()) { - return new SimpleFieldWrapper(p1.getCondition().not()); - } - throw new IllegalArgumentException("Not requires a condition, got " + p1); - } - - @Override - public FieldWrapper visit(Or node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - FieldWrapper p2 = params.get(1).accept(this); - if (p1.isCondition() && p2.isCondition()) { - return new SimpleFieldWrapper(p1.getCondition().or(p2.getCondition())); - } - throw new IllegalArgumentException("Or requires two conditions, got " + p1 + " & " + p2); - } - - @Override - public FieldWrapper visit(Ceiling node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - Field n1 = p1.getFieldAsType(Number.class, true); - return new SimpleFieldWrapper(DSL.ceil(n1)); - } - - @Override - public FieldWrapper visit(Floor node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - Field n1 = p1.getFieldAsType(Number.class, true); - return new SimpleFieldWrapper(DSL.floor(n1)); - } - - @Override - public FieldWrapper visit(Round node) { - List params = node.getParameters(); - FieldWrapper p1 = params.get(0).accept(this); - Field n1 = p1.getFieldAsType(Number.class, true); - return new SimpleFieldWrapper(DSL.round(n1)); - } - - private FieldWrapper stCompare(Function node, String functionName) { - Expression p1 = node.getParameters().get(0); - Expression p2 = node.getParameters().get(1); - FieldWrapper e1 = p1.accept(this); - FieldWrapper e2 = p2.accept(this); - Field g1 = e1.getFieldAsType(Geometry.class, true); - Field g2 = e2.getFieldAsType(Geometry.class, true); - if (g1 == null || g2 == null) { - throw new IllegalArgumentException(functionName + " requires two geometries, got " + e1 + " & " + e2); - } - return new SimpleFieldWrapper(DSL.condition(DSL.function(functionName, SQLDataType.BOOLEAN, g1, g2))); - } - - @Override - public FieldWrapper visit(STContains node) { - return stCompare(node, "ST_Contains"); - } - - @Override - public FieldWrapper visit(STCrosses node) { - return stCompare(node, "ST_Crosses"); - } - - @Override - public FieldWrapper visit(STDisjoint node) { - return stCompare(node, "ST_Disjoint"); - } - - @Override - public FieldWrapper visit(STEquals node) { - return stCompare(node, "ST_Equals"); - } - - @Override - public FieldWrapper visit(STIntersects node) { - return stCompare(node, "ST_Intersects"); - } - - @Override - public FieldWrapper visit(STOverlaps node) { - return stCompare(node, "ST_Overlaps"); - } - - @Override - public FieldWrapper visit(STRelate node) { - Expression p1 = node.getParameters().get(0); - Expression p2 = node.getParameters().get(1); - Expression p3 = node.getParameters().get(2); - FieldWrapper e1 = p1.accept(this); - FieldWrapper e2 = p2.accept(this); - FieldWrapper e3 = p3.accept(this); - Field g1 = e1.getFieldAsType(Geometry.class, true); - Field g2 = e2.getFieldAsType(Geometry.class, true); - Field g3 = e3.getFieldAsType(String.class, true); - if (g1 == null || g2 == null || g3 == null) { - throw new IllegalArgumentException("STRelate requires two geometries and a string, got " + e1 + ", " + e2 + " & " + e3); - } - return new SimpleFieldWrapper(DSL.condition(DSL.function("ST_Relate", SQLDataType.BOOLEAN, g1, g2, g3))); - } - - @Override - public FieldWrapper visit(STTouches node) { - return stCompare(node, "ST_Touches"); - } - - @Override - public FieldWrapper visit(STWithin node) { - return stCompare(node, "ST_Within"); - } - - @Override - public FieldWrapper visit(Concat node) { - Expression p1 = node.getParameters().get(0); - Expression p2 = node.getParameters().get(1); - FieldWrapper e1 = p1.accept(this); - FieldWrapper e2 = p2.accept(this); - Field s1 = e1.getFieldAsType(String.class, true); - Field s2 = e2.getFieldAsType(String.class, true); - return new SimpleFieldWrapper(s1.concat(s2)); - } - - @Override - public FieldWrapper visit(EndsWith node) { - Expression p1 = node.getParameters().get(0); - Expression p2 = node.getParameters().get(1); - FieldWrapper e1 = p1.accept(this); - FieldWrapper e2 = p2.accept(this); - Field s1 = e1.getFieldAsType(String.class, true); - Field s2 = e2.getFieldAsType(String.class, true); - return new SimpleFieldWrapper(s1.endsWith(s2)); - } - - @Override - public FieldWrapper visit(IndexOf node) { - Expression p1 = node.getParameters().get(0); - Expression p2 = node.getParameters().get(1); - FieldWrapper e1 = p1.accept(this); - FieldWrapper e2 = p2.accept(this); - Field s1 = e1.getFieldAsType(String.class, true); - Field s2 = e2.getFieldAsType(String.class, true); - return new SimpleFieldWrapper(DSL.position(s1, s2)); - } - - @Override - public FieldWrapper visit(Length node) { - Expression param = node.getParameters().get(0); - FieldWrapper e1 = param.accept(this); - Field s1 = e1.getFieldAsType(String.class, true); - return new SimpleFieldWrapper(DSL.length(s1)); - } - - @Override - public FieldWrapper visit(StartsWith node) { - Expression p1 = node.getParameters().get(0); - Expression p2 = node.getParameters().get(1); - FieldWrapper e1 = p1.accept(this); - FieldWrapper e2 = p2.accept(this); - Field s1 = e1.getFieldAsType(String.class, true); - Field s2 = e2.getFieldAsType(String.class, true); - return new SimpleFieldWrapper(s1.startsWith(s2)); - } - - @Override - public FieldWrapper visit(Substring node) { - List params = node.getParameters(); - Expression p1 = node.getParameters().get(0); - Expression p2 = node.getParameters().get(1); - FieldWrapper e1 = p1.accept(this); - FieldWrapper e2 = p2.accept(this); - Field s1 = e1.getFieldAsType(String.class, true); - Field n2 = e2.getFieldAsType(Integer.class, true); - if (params.size() > 2) { - Expression p3 = node.getParameters().get(2); - FieldWrapper e3 = p3.accept(this); - Field n3 = e3.getFieldAsType(Integer.class, true); - return new SimpleFieldWrapper(DSL.substring(s1, n2.add(1), n3)); - } - return new SimpleFieldWrapper(DSL.substring(s1, n2.add(1))); - } - - @Override - public FieldWrapper visit(SubstringOf node) { - Expression p1 = node.getParameters().get(0); - Expression p2 = node.getParameters().get(1); - FieldWrapper e1 = p1.accept(this); - FieldWrapper e2 = p2.accept(this); - Field s1 = e1.getFieldAsType(String.class, true); - Field s2 = e2.getFieldAsType(String.class, true); - return new SimpleFieldWrapper(s2.contains(s1)); - } - - @Override - public FieldWrapper visit(ToLower node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - Field field = input.getFieldAsType(String.class, true); - return new SimpleFieldWrapper(DSL.lower(field)); - } - - @Override - public FieldWrapper visit(ToUpper node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - Field field = input.getFieldAsType(String.class, true); - return new SimpleFieldWrapper(DSL.upper(field)); - } - - @Override - public FieldWrapper visit(Trim node) { - Expression param = node.getParameters().get(0); - FieldWrapper input = param.accept(this); - Field field = input.getFieldAsType(String.class, true); - return new SimpleFieldWrapper(DSL.trim(field)); - } - - @Override - public FieldWrapper visit(PrincipalName node) { - return new SimpleFieldWrapper(DSL.value(node.getValue())); - } - - @Override - public FieldWrapper visit(ContextEntityProperty node) { - return new SimpleFieldWrapper(DSL.value(node.getValue())); - } - - private static class PathState { - - private TableRef pathTableRef; - private List elements; - private FieldWrapper finalExpression = null; - private int curIndex; - private boolean finished = false; - } } diff --git a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/PostgresPersistenceManager.java b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/PostgresPersistenceManager.java index b18fe2be1..71eaff407 100644 --- a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/PostgresPersistenceManager.java +++ b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/PostgresPersistenceManager.java @@ -855,6 +855,11 @@ public void generateLiquibaseVariables(Map target, String entity } } + @Override + public ExpressionHandler createExpressionHandler(QueryBuilder queryBuilder) { + return new PgExpressionHandler(settings, queryBuilder); + } + @Override public String checkForUpgrades() { Map props = CollectionsHelper.LinkedHashMapBuilder() diff --git a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/QueryBuilder.java b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/QueryBuilder.java index e41cefc00..4a0825123 100644 --- a/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/QueryBuilder.java +++ b/FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/QueryBuilder.java @@ -428,7 +428,7 @@ private void handleEntityExpand(QueryState sqlState, Expand expand, NavigationPr private void parseOrder(Query query) { if (query != null) { - PgExpressionHandler handler = new PgExpressionHandler(coreSettings, this); + ExpressionHandler handler = getPersistenceManager().createExpressionHandler(this); for (OrderBy ob : query.getOrderBy()) { handler.addOrderbyToQuery(ob, queryState.getSqlSortFields()); } @@ -440,7 +440,7 @@ public void parseFilter(Query query) { queryState.setFilter(true); final Expression filter = query.getFilter(); final Expression skipFilter = query.getSkipFilter(); - PgExpressionHandler handler = new PgExpressionHandler(coreSettings, this); + ExpressionHandler handler = getPersistenceManager().createExpressionHandler(this); if (filter != null) { queryState.setSqlWhere(handler.addFilterToWhere(filter, queryState.getSqlWhere())); }