All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq.PgExpressionHandler Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
/*
 * 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.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;

/**
 *
 * @author Hylke van der Schaaf
 */
public class PgExpressionHandler implements ExpressionVisitor {

    /**
     * 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;

    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()));
    }

    @Override
    public FieldWrapper visit(LineStringConstant node) {
        Geometry geom = fromGeoJsonConstant(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);
        return new SimpleFieldWrapper(DSL.field(ST_GEOM_FROM_EWKT, PostGisGeometryBinding.dataType(), geom.asText()));
    }

    @Override
    public FieldWrapper visit(PolygonConstant node) {
        Geometry geom = fromGeoJsonConstant(node);
        return new SimpleFieldWrapper(DSL.field(ST_GEOM_FROM_EWKT, PostGisGeometryBinding.dataType(), geom.asText()));
    }

    private static 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;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy