/* * Copyright (C) 2016 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 <http://www.gnu.org/licenses/>. */ package de.fraunhofer.iosb.ilt.frostserver.persistence.pgjooq; import de.fraunhofer.iosb.ilt.frostserver.model.EntityType; 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.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.property.CustomProperty; import de.fraunhofer.iosb.ilt.frostserver.property.CustomPropertyLink; import de.fraunhofer.iosb.ilt.frostserver.property.EntityProperty; import de.fraunhofer.iosb.ilt.frostserver.property.NavigationPropertyMain; import de.fraunhofer.iosb.ilt.frostserver.property.Property; import static de.fraunhofer.iosb.ilt.frostserver.property.SpecialNames.AT_IOT_ID; 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.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.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.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.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.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 static de.fraunhofer.iosb.ilt.frostserver.util.Constants.UTC; import java.time.Instant; import java.time.OffsetDateTime; import java.util.Calendar; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import org.geojson.GeoJsonObject; import org.geolatte.geom.Geometry; import org.geolatte.geom.codec.Wkt; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Interval; import org.joda.time.LocalDate; import org.joda.time.LocalTime; 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 * @param <J> The type of the ID fields. */ public class PgExpressionHandler<J extends Comparable> implements ExpressionVisitor<FieldWrapper> { /** * 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<J> queryBuilder; /** * The table reference for the main table of the request. */ private final TableRef<J> tableRef; private int maxCustomLinkDepth = -1; public PgExpressionHandler(CoreSettings settings, QueryBuilder<J> queryBuilder, TableRef<J> tableRef) { this.queryBuilder = queryBuilder; this.tableRef = tableRef; final Settings experimentalSettings = settings.getExperimentalSettings(); if (experimentalSettings.getBoolean(CoreSettings.TAG_ENABLE_CUSTOM_LINKS, 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()); } else if (filterField instanceof FieldListWrapper) { FieldListWrapper listExpression = (FieldListWrapper) filterField; for (Field expression : listExpression.getExpressions().values()) { if (Boolean.class.isAssignableFrom(expression.getType())) { Field<Boolean> 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) { StaTimeIntervalWrapper ti = (StaTimeIntervalWrapper) resultExpression; addToQuery(orderBy, ti.getStart(), orderFields); addToQuery(orderBy, ti.getEnd(), orderFields); return; } if (resultExpression instanceof StaDurationWrapper) { StaDurationWrapper duration = (StaDurationWrapper) resultExpression; addToQuery(orderBy, duration.getDuration(), orderFields); return; } if (resultExpression instanceof StaDateTimeWrapper) { StaDateTimeWrapper dateTime = (StaDateTimeWrapper) resultExpression; addToQuery(orderBy, dateTime.getDateTime(), orderFields); return; } if (resultExpression instanceof FieldListWrapper) { for (Field sqlExpression : ((FieldListWrapper) resultExpression).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<J> state = new PathState<>(); state.pathTableRef = tableRef; state.elements = path.getElements(); for (state.curIndex = 0; state.curIndex < state.elements.size() && !state.finished; state.curIndex++) { Property element = state.elements.get(state.curIndex); if (element instanceof CustomProperty) { handleCustomProperty(state, path); } else if (element instanceof CustomPropertyLink) { handleCustomProperty(state, path); } else if (element instanceof EntityProperty) { handleEntityProperty(state, path, element); } else if (element instanceof NavigationPropertyMain) { handleNavigationProperty(state, path, element); } } if (state.finalExpression == null) { throw new IllegalArgumentException("Path does not end in an EntityProperty: " + path); } if (state.finalExpression instanceof Field) { Field field = (Field) state.finalExpression; if (OffsetDateTime.class.isAssignableFrom(field.getType())) { Field<OffsetDateTime> dateTimePath = (Field<OffsetDateTime>) state.finalExpression; state.finalExpression = new StaDateTimeWrapper(dateTimePath); } } return state.finalExpression; } private void handleCustomProperty(PathState<J> state, Path path) { if (state.finalExpression == null) { throw new IllegalArgumentException("CustomProperty must follow an EntityProperty: " + path); } // generate finalExpression::jsonb#>>'{x,y,z}' JsonFieldFactory jsonFactory = new JsonFieldFactory(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 CustomPropertyLink) { int maxDepth = state.curIndex + maxCustomLinkDepth; if (state.curIndex <= maxDepth) { handleCustomLink(property, jsonFactory, name, state); return; } else { jsonFactory.addToPath(name); } } else { jsonFactory.addToPath(name); } } state.finalExpression = jsonFactory.build(); state.finished = true; } private void handleCustomLink(final Property property, JsonFieldFactory jsonFactory, String name, PathState<J> state) { EntityType targetEntityType = ((CustomPropertyLink) property).getTargetEntityType(); JsonFieldFactory.JsonFieldWrapper sourceIdFieldWrapper = jsonFactory.addToPath(name + AT_IOT_ID).build(); Field<Number> sourceIdField = sourceIdFieldWrapper.getFieldAsType(Number.class, true); state.pathTableRef = queryBuilder.queryEntityType(targetEntityType, state.pathTableRef, sourceIdField); state.finalExpression = null; } private void handleEntityProperty(PathState<J> state, Path path, Property element) { if (state.finalExpression != null) { throw new IllegalArgumentException("EntityProperty can not follow an other EntityProperty: " + path); } EntityProperty entityProperty = (EntityProperty) element; Map<String, Field> pathExpressions = queryBuilder .getPropertyResolver() .getAllFieldsForProperty(entityProperty, state.pathTableRef.getTable(), new LinkedHashMap<>()); if (pathExpressions.size() == 1) { state.finalExpression = PropertyResolver.wrapField(pathExpressions.values().iterator().next()); } else { state.finalExpression = getSubExpression(state, pathExpressions); } } private void handleNavigationProperty(PathState<J> state, Path path, Property element) { if (state.finalExpression != null) { throw new IllegalArgumentException("NavigationProperty can not follow an EntityProperty: " + path); } NavigationPropertyMain navigationProperty = (NavigationPropertyMain) element; state.pathTableRef = queryBuilder.queryEntityType(navigationProperty.getType(), null, state.pathTableRef); } private FieldWrapper getSubExpression(PathState<J> state, Map<String, Field> 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 new SimpleFieldWrapper(pathExpressions.get(subProperty.getName())); } else { if (pathExpressions.containsKey(StaTimeIntervalWrapper.KEY_TIME_INTERVAL_START) && pathExpressions.containsKey(StaTimeIntervalWrapper.KEY_TIME_INTERVAL_END)) { return new StaTimeIntervalWrapper(pathExpressions); } return new FieldListWrapper(pathExpressions); } } 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(BooleanConstant node) { return new SimpleFieldWrapper(Boolean.TRUE.equals(node.getValue()) ? DSL.condition("TRUE") : DSL.condition("FALSE")); } @Override public FieldWrapper visit(DateConstant node) { LocalDate date = node.getValue(); Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("GMT")); instance.set(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth()); return new SimpleFieldWrapper(DSL.inline(new java.sql.Date(instance.getTimeInMillis()))); } @Override public FieldWrapper visit(DateTimeConstant node) { DateTime value = node.getValue(); DateTimeZone zone = value.getZone(); return new StaDateTimeWrapper(OffsetDateTime.ofInstant(Instant.ofEpochMilli(value.getMillis()), UTC), zone == DateTimeZone.UTC); } @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) { Interval value = node.getValue(); return new StaTimeIntervalWrapper( OffsetDateTime.ofInstant(Instant.ofEpochMilli(value.getStartMillis()), UTC), OffsetDateTime.ofInstant(Instant.ofEpochMilli(value.getEndMillis()), UTC) ); } @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, Geometry.class, geom.asText())); } @Override public FieldWrapper visit(PointConstant node) { Geometry geom = fromGeoJsonConstant(node); return new SimpleFieldWrapper(DSL.field(ST_GEOM_FROM_EWKT, Geometry.class, geom.asText())); } @Override public FieldWrapper visit(PolygonConstant node) { Geometry geom = fromGeoJsonConstant(node); return new SimpleFieldWrapper(DSL.field(ST_GEOM_FROM_EWKT, Geometry.class, geom.asText())); } private Geometry fromGeoJsonConstant(GeoJsonConstant<? extends GeoJsonObject> 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) { LocalTime time = node.getValue(); Calendar instance = Calendar.getInstance(TimeZone.getTimeZone("GMT")); instance.set(1970, 1, 1, time.getHourOfDay(), time.getMinuteOfHour(), time.getSecondOfMinute()); return new SimpleFieldWrapper(DSL.inline(new java.sql.Time(instance.getTimeInMillis()))); } @Override public FieldWrapper visit(Before node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) p1; 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<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) p1; 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<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) p1; 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<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p2 instanceof StaTimeIntervalWrapper) { StaTimeIntervalWrapper ti2 = (StaTimeIntervalWrapper) p2; return ti2.contains(p1); } else { throw new IllegalArgumentException("Second parameter of 'during' has to be an interval."); } } @Override public FieldWrapper visit(Overlaps node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) p1; 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<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) p1; 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<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) p1; 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<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper ti1 = (TimeFieldWrapper) p1; return ti1.add(p2); } if (p2 instanceof TimeFieldWrapper) { TimeFieldWrapper ti2 = (TimeFieldWrapper) p2; return ti2.add(p1); } Field<Number> n1 = p1.getFieldAsType(Number.class, true); Field<Number> n2 = p2.getFieldAsType(Number.class, true); return new SimpleFieldWrapper(n1.add(n2)); } @Override public FieldWrapper visit(Divide node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper ti1 = (TimeFieldWrapper) p1; return ti1.div(p2); } if (p2 instanceof TimeFieldWrapper) { throw new IllegalArgumentException("Can not devide by a TimeExpression."); } Field<Number> n1 = p1.getFieldAsType(Number.class, true); Field<Number> n2 = p2.getFieldAsType(Number.class, true); return new SimpleFieldWrapper(n1.divide(n2).coerce(SQLDataType.DOUBLE)); } @Override public FieldWrapper visit(Modulo node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); Field<? extends Number> n1 = p1.getFieldAsType(Number.class, true); Field<? extends Number> 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<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper ti1 = (TimeFieldWrapper) p1; return ti1.mul(p2); } if (p2 instanceof TimeFieldWrapper) { TimeFieldWrapper ti2 = (TimeFieldWrapper) p2; return ti2.mul(p1); } Field<Number> n1 = p1.getFieldAsType(Number.class, true); Field<Number> n2 = p2.getFieldAsType(Number.class, true); return new SimpleFieldWrapper(n1.multiply(n2)); } @Override public FieldWrapper visit(Subtract node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper ti1 = (TimeFieldWrapper) p1; return ti1.sub(p2); } if (p2 instanceof TimeFieldWrapper) { throw new IllegalArgumentException("Can not sub a time expression from a " + p1.getClass().getName()); } Field<Number> n1 = p1.getFieldAsType(Number.class, true); Field<Number> n2 = p2.getFieldAsType(Number.class, true); return new SimpleFieldWrapper(n1.subtract(n2)); } @Override public FieldWrapper visit(Equal node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper ti1 = (TimeFieldWrapper) p1; return ti1.eq(p2); } if (p2 instanceof TimeFieldWrapper) { TimeFieldWrapper ti2 = (TimeFieldWrapper) p2; return ti2.eq(p1); } if (p1 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l1 = (JsonFieldFactory.JsonFieldWrapper) p1; return l1.eq(p2); } if (p2 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l2 = (JsonFieldFactory.JsonFieldWrapper) p2; return l2.eq(p1); } Field[] pair = findPair(p1, p2); return new SimpleFieldWrapper(pair[0].eq(pair[1])); } @Override public FieldWrapper visit(GreaterEqual node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper ti1 = (TimeFieldWrapper) p1; return ti1.goe(p2); } if (p2 instanceof TimeFieldWrapper) { TimeFieldWrapper ti2 = (TimeFieldWrapper) p2; return ti2.loe(p1); } if (p1 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l1 = (JsonFieldFactory.JsonFieldWrapper) p1; return l1.goe(p2); } if (p2 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l2 = (JsonFieldFactory.JsonFieldWrapper) p2; return l2.loe(p1); } Field[] pair = findPair(p1, p2); return new SimpleFieldWrapper(pair[0].greaterOrEqual(pair[1])); } @Override public FieldWrapper visit(GreaterThan node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper ti1 = (TimeFieldWrapper) p1; return ti1.gt(p2); } if (p2 instanceof TimeFieldWrapper) { TimeFieldWrapper ti2 = (TimeFieldWrapper) p2; return ti2.lt(p1); } if (p1 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l1 = (JsonFieldFactory.JsonFieldWrapper) p1; return l1.gt(p2); } if (p2 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l2 = (JsonFieldFactory.JsonFieldWrapper) p2; return l2.lt(p1); } Field[] pair = findPair(p1, p2); return new SimpleFieldWrapper(pair[0].greaterThan(pair[1])); } @Override public FieldWrapper visit(LessEqual node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper ti1 = (TimeFieldWrapper) p1; return ti1.loe(p2); } if (p2 instanceof TimeFieldWrapper) { TimeFieldWrapper ti2 = (TimeFieldWrapper) p2; return ti2.goe(p1); } if (p1 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l1 = (JsonFieldFactory.JsonFieldWrapper) p1; return l1.loe(p2); } if (p2 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l2 = (JsonFieldFactory.JsonFieldWrapper) p2; return l2.goe(p1); } Field[] pair = findPair(p1, p2); return new SimpleFieldWrapper(pair[0].lessOrEqual(pair[1])); } @Override public FieldWrapper visit(LessThan node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper ti1 = (TimeFieldWrapper) p1; return ti1.lt(p2); } if (p2 instanceof TimeFieldWrapper) { TimeFieldWrapper ti2 = (TimeFieldWrapper) p2; return ti2.gt(p1); } if (p1 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l1 = (JsonFieldFactory.JsonFieldWrapper) p1; return l1.lt(p2); } if (p2 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l2 = (JsonFieldFactory.JsonFieldWrapper) p2; return l2.gt(p1); } Field[] pair = findPair(p1, p2); return new SimpleFieldWrapper(pair[0].lt(pair[1])); } @Override public FieldWrapper visit(NotEqual node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); FieldWrapper p2 = params.get(1).accept(this); if (p1 instanceof TimeFieldWrapper) { TimeFieldWrapper ti1 = (TimeFieldWrapper) p1; return ti1.neq(p2); } if (p2 instanceof TimeFieldWrapper) { TimeFieldWrapper ti2 = (TimeFieldWrapper) p2; return ti2.neq(p1); } if (p1 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l1 = (JsonFieldFactory.JsonFieldWrapper) p1; return l1.ne(p2); } if (p2 instanceof JsonFieldFactory.JsonFieldWrapper) { JsonFieldFactory.JsonFieldWrapper l2 = (JsonFieldFactory.JsonFieldWrapper) p2; return l2.ne(p1); } Field[] pair = findPair(p1, p2); return new SimpleFieldWrapper(pair[0].ne(pair[1])); } @Override public FieldWrapper visit(Date node) { Expression param = node.getParameters().get(0); FieldWrapper input = param.accept(this); if (input instanceof TimeFieldWrapper) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) input; return new SimpleFieldWrapper(DSL.function("date", Date.class, timeExpression.getDateTime())); } 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) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) input; 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) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) input; 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) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) input; 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) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) input; 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) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) input; 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.currentOffsetDateTime()); } @Override public FieldWrapper visit(Second node) { Expression param = node.getParameters().get(0); FieldWrapper input = param.accept(this); if (input instanceof TimeFieldWrapper) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) input; 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) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) input; if (!timeExpression.isUtc()) { throw new IllegalArgumentException("Constants passed to the time() function have to be in UTC."); } 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) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) input; 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) { TimeFieldWrapper timeExpression = (TimeFieldWrapper) input; 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<Geometry> g1 = e1.getFieldAsType(Geometry.class, true); Field<Geometry> 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<Geometry> 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<Expression> 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<Expression> 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<Expression> 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<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); Field<Number> n1 = p1.getFieldAsType(Number.class, true); return new SimpleFieldWrapper(DSL.ceil(n1)); } @Override public FieldWrapper visit(Floor node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); Field<Number> n1 = p1.getFieldAsType(Number.class, true); return new SimpleFieldWrapper(DSL.floor(n1)); } @Override public FieldWrapper visit(Round node) { List<Expression> params = node.getParameters(); FieldWrapper p1 = params.get(0).accept(this); Field<Number> 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<Geometry> g1 = e1.getFieldAsType(Geometry.class, true); Field<Geometry> 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<Geometry> g1 = e1.getFieldAsType(Geometry.class, true); Field<Geometry> g2 = e2.getFieldAsType(Geometry.class, true); Field<String> 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<String> s1 = e1.getFieldAsType(String.class, true); Field<String> 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<String> s1 = e1.getFieldAsType(String.class, true); Field<String> 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<String> s1 = e1.getFieldAsType(String.class, true); Field<String> 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<String> 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<String> s1 = e1.getFieldAsType(String.class, true); Field<String> s2 = e2.getFieldAsType(String.class, true); return new SimpleFieldWrapper(s1.startsWith(s2)); } @Override public FieldWrapper visit(Substring node) { List<Expression> 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<String> s1 = e1.getFieldAsType(String.class, true); Field<Number> n2 = e2.getFieldAsType(Number.class, true); if (params.size() > 2) { Expression p3 = node.getParameters().get(2); FieldWrapper e3 = p3.accept(this); Field<Number> n3 = e3.getFieldAsType(Number.class, true); return new SimpleFieldWrapper(DSL.substring(s1, n2, n3)); } return new SimpleFieldWrapper(DSL.substring(s1, n2)); } @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<String> s1 = e1.getFieldAsType(String.class, true); Field<String> 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<String> 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<String> 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<String> field = input.getFieldAsType(String.class, true); return new SimpleFieldWrapper(DSL.trim(field)); } private static class PathState<J extends Comparable> { private TableRef<J> pathTableRef; private List<Property> elements; private FieldWrapper finalExpression = null; private int curIndex; private boolean finished = false; } }