From 64d96f1fae7c1038fcb4d7d848144b043cc57e81 Mon Sep 17 00:00:00 2001 From: Tobias Hellmund <42065061+T-Hellmund@users.noreply.github.com> Date: Thu, 10 Oct 2024 08:16:18 +0200 Subject: [PATCH] Distinguish spatial queries for MariaDB and Postgres as the specific geometric functions differ. The ExpressionHandler class contains the functions similar for all database dialects. Specific differences in the dialects are implemented in PgExpressionHandler and MariaDbExpressionHandler. (#2036) --- CHANGELOG.md | 1 + .../persistence/pgjooq/ExpressionHandler.java | 1204 ++++++++++++++++ .../pgjooq/JooqPersistenceManager.java | 2 + .../pgjooq/MariaDBExpressionHandler.java | 72 + .../pgjooq/MariadbPersistenceManager.java | 5 + .../pgjooq/PgExpressionHandler.java | 1249 +---------------- .../pgjooq/PostgresPersistenceManager.java | 5 + .../persistence/pgjooq/QueryBuilder.java | 4 +- 8 files changed, 1296 insertions(+), 1246 deletions(-) create mode 100644 FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/ExpressionHandler.java create mode 100644 FROST-Server.SQLjooq/src/main/java/de/fraunhofer/iosb/ilt/frostserver/persistence/pgjooq/MariaDBExpressionHandler.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 10263ade1..965a470c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Development version 2.4.3 **Internal changes & Bugfixes** +* Added support for spatial queries on MariaDB. ## Release version 2.4.2 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())); }