org.neo4j.cypher.operations.CypherFunctions Maven / Gradle / Ivy
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.cypher.operations;
import static java.lang.Double.parseDouble;
import static java.lang.Long.parseLong;
import static java.lang.String.format;
import static org.neo4j.cypher.operations.CursorUtils.propertyKeys;
import static org.neo4j.values.storable.Values.EMPTY_STRING;
import static org.neo4j.values.storable.Values.FALSE;
import static org.neo4j.values.storable.Values.NO_VALUE;
import static org.neo4j.values.storable.Values.TRUE;
import static org.neo4j.values.storable.Values.booleanValue;
import static org.neo4j.values.storable.Values.doubleValue;
import static org.neo4j.values.storable.Values.longValue;
import static org.neo4j.values.storable.Values.stringValue;
import static org.neo4j.values.virtual.VirtualValues.EMPTY_LIST;
import static org.neo4j.values.virtual.VirtualValues.asList;
import static scala.jdk.javaapi.CollectionConverters.asJava;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.StreamSupport;
import org.eclipse.collections.api.set.primitive.IntSet;
import org.eclipse.collections.impl.factory.primitive.IntSets;
import org.neo4j.cypher.internal.expressions.NormalForm;
import org.neo4j.cypher.internal.runtime.DbAccess;
import org.neo4j.cypher.internal.runtime.ExpressionCursors;
import org.neo4j.cypher.internal.runtime.QueryContext;
import org.neo4j.cypher.internal.runtime.RuntimeNotifier;
import org.neo4j.cypher.internal.util.RuntimeUnsatisfiableRelationshipTypeExpression;
import org.neo4j.cypher.internal.util.symbols.AnyType;
import org.neo4j.cypher.internal.util.symbols.BooleanType;
import org.neo4j.cypher.internal.util.symbols.ClosedDynamicUnionType;
import org.neo4j.cypher.internal.util.symbols.CypherType;
import org.neo4j.cypher.internal.util.symbols.DateType;
import org.neo4j.cypher.internal.util.symbols.DurationType;
import org.neo4j.cypher.internal.util.symbols.FloatType;
import org.neo4j.cypher.internal.util.symbols.GeometryType;
import org.neo4j.cypher.internal.util.symbols.IntegerType;
import org.neo4j.cypher.internal.util.symbols.ListType;
import org.neo4j.cypher.internal.util.symbols.LocalDateTimeType;
import org.neo4j.cypher.internal.util.symbols.LocalTimeType;
import org.neo4j.cypher.internal.util.symbols.MapType;
import org.neo4j.cypher.internal.util.symbols.NodeType;
import org.neo4j.cypher.internal.util.symbols.NothingType;
import org.neo4j.cypher.internal.util.symbols.NullType;
import org.neo4j.cypher.internal.util.symbols.NumberType;
import org.neo4j.cypher.internal.util.symbols.PathType;
import org.neo4j.cypher.internal.util.symbols.PointType;
import org.neo4j.cypher.internal.util.symbols.PropertyValueType;
import org.neo4j.cypher.internal.util.symbols.RelationshipType;
import org.neo4j.cypher.internal.util.symbols.StringType;
import org.neo4j.cypher.internal.util.symbols.ZonedDateTimeType;
import org.neo4j.cypher.internal.util.symbols.ZonedTimeType;
import org.neo4j.exceptions.CypherTypeException;
import org.neo4j.exceptions.InvalidArgumentException;
import org.neo4j.exceptions.KernelException;
import org.neo4j.gqlstatus.ErrorGqlStatusObjectImplementation;
import org.neo4j.gqlstatus.GqlStatusInfoCodes;
import org.neo4j.internal.kernel.api.NodeCursor;
import org.neo4j.internal.kernel.api.PropertyCursor;
import org.neo4j.internal.kernel.api.Read;
import org.neo4j.internal.kernel.api.RelationshipScanCursor;
import org.neo4j.internal.kernel.api.TokenWrite;
import org.neo4j.internal.kernel.api.exceptions.schema.IllegalTokenNameException;
import org.neo4j.kernel.api.StatementConstants;
import org.neo4j.kernel.api.impl.schema.vector.VectorSimilarity;
import org.neo4j.kernel.api.vector.VectorCandidate;
import org.neo4j.kernel.api.vector.VectorSimilarityFunction;
import org.neo4j.kernel.impl.util.NodeEntityWrappingNodeValue;
import org.neo4j.storageengine.api.LongReference;
import org.neo4j.token.api.TokenConstants;
import org.neo4j.util.CalledFromGeneratedCode;
import org.neo4j.values.AnyValue;
import org.neo4j.values.ElementIdMapper;
import org.neo4j.values.SequenceValue;
import org.neo4j.values.storable.ArrayValue;
import org.neo4j.values.storable.BooleanValue;
import org.neo4j.values.storable.CoordinateReferenceSystem;
import org.neo4j.values.storable.DoubleValue;
import org.neo4j.values.storable.DurationValue;
import org.neo4j.values.storable.FloatingPointArray;
import org.neo4j.values.storable.FloatingPointValue;
import org.neo4j.values.storable.IntegralArray;
import org.neo4j.values.storable.IntegralValue;
import org.neo4j.values.storable.NoValue;
import org.neo4j.values.storable.NumberValue;
import org.neo4j.values.storable.PointValue;
import org.neo4j.values.storable.StringValue;
import org.neo4j.values.storable.TemporalValue;
import org.neo4j.values.storable.TextValue;
import org.neo4j.values.storable.Value;
import org.neo4j.values.storable.ValueGroup;
import org.neo4j.values.storable.ValueRepresentation;
import org.neo4j.values.storable.Values;
import org.neo4j.values.virtual.ListValue;
import org.neo4j.values.virtual.ListValueBuilder;
import org.neo4j.values.virtual.MapValue;
import org.neo4j.values.virtual.MapValueBuilder;
import org.neo4j.values.virtual.NodeValue;
import org.neo4j.values.virtual.PathValue;
import org.neo4j.values.virtual.RelationshipValue;
import org.neo4j.values.virtual.RelationshipVisitor;
import org.neo4j.values.virtual.VirtualNodeValue;
import org.neo4j.values.virtual.VirtualPathValue;
import org.neo4j.values.virtual.VirtualRelationshipValue;
import org.neo4j.values.virtual.VirtualValues;
/**
* This class contains static helper methods for the set of Cypher functions
*/
@SuppressWarnings({"ReferenceEquality"})
public final class CypherFunctions {
private static final BigDecimal MAX_LONG = BigDecimal.valueOf(Long.MAX_VALUE);
private static final BigDecimal MIN_LONG = BigDecimal.valueOf(Long.MIN_VALUE);
private static final String[] POINT_KEYS =
new String[] {"crs", "x", "y", "z", "longitude", "latitude", "height", "srid"};
private CypherFunctions() {
throw new UnsupportedOperationException("Do not instantiate");
}
public static AnyValue sin(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue) {
return doubleValue(Math.sin(((NumberValue) in).doubleValue()));
} else {
throw needsNumbers("sin()");
}
}
public static AnyValue asin(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.asin(number.doubleValue()));
} else {
throw needsNumbers("asin()");
}
}
public static AnyValue haversin(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue((1.0 - Math.cos(number.doubleValue())) / 2);
} else {
throw needsNumbers("haversin()");
}
}
public static AnyValue cos(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.cos(number.doubleValue()));
} else {
throw needsNumbers("cos()");
}
}
public static AnyValue cot(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(1.0 / Math.tan(number.doubleValue()));
} else {
throw needsNumbers("cot()");
}
}
public static AnyValue acos(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.acos(number.doubleValue()));
} else {
throw needsNumbers("acos()");
}
}
public static AnyValue tan(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.tan(number.doubleValue()));
} else {
throw needsNumbers("tan()");
}
}
public static AnyValue atan(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.atan(number.doubleValue()));
} else {
throw needsNumbers("atan()");
}
}
public static AnyValue atan2(AnyValue y, AnyValue x) {
if (y == NO_VALUE || x == NO_VALUE) {
return NO_VALUE;
} else if (y instanceof NumberValue yNumber && x instanceof NumberValue xNumber) {
return doubleValue(Math.atan2(yNumber.doubleValue(), xNumber.doubleValue()));
} else {
throw needsNumbers("atan2()");
}
}
public static AnyValue ceil(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.ceil(number.doubleValue()));
} else {
throw needsNumbers("ceil()");
}
}
public static AnyValue floor(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.floor(number.doubleValue()));
} else {
throw needsNumbers("floor()");
}
}
@CalledFromGeneratedCode
public static AnyValue round(AnyValue in) {
return round(in, Values.ZERO_INT, Values.stringValue("HALF_UP"), Values.booleanValue(false));
}
@CalledFromGeneratedCode
public static AnyValue round(AnyValue in, AnyValue precision) {
return round(in, precision, Values.stringValue("HALF_UP"), Values.booleanValue(false));
}
public static AnyValue round(AnyValue in, AnyValue precisionValue, AnyValue modeValue) {
return round(in, precisionValue, modeValue, Values.booleanValue(true));
}
public static AnyValue round(AnyValue in, AnyValue precisionValue, AnyValue modeValue, AnyValue explicitModeValue) {
if (in == NO_VALUE || precisionValue == NO_VALUE || modeValue == NO_VALUE) {
return NO_VALUE;
} else if (!(modeValue instanceof StringValue)) {
throw notAModeString("round", modeValue);
}
RoundingMode mode;
try {
mode = RoundingMode.valueOf(((StringValue) modeValue).stringValue());
} catch (IllegalArgumentException e) {
var gql = ErrorGqlStatusObjectImplementation.from(GqlStatusInfoCodes.STATUS_22000)
.withCause(ErrorGqlStatusObjectImplementation.from(GqlStatusInfoCodes.STATUS_22N26)
.build())
.build();
throw new InvalidArgumentException(
gql,
"Unknown rounding mode. Valid values are: CEILING, FLOOR, UP, DOWN, HALF_EVEN, HALF_UP, HALF_DOWN, UNNECESSARY.");
}
if (in instanceof NumberValue inNumber && precisionValue instanceof NumberValue) {
int precision = asIntExact(precisionValue, () -> "Invalid input for precision value in function 'round()'");
boolean explicitMode = ((BooleanValue) explicitModeValue).booleanValue();
if (precision < 0) {
throw InvalidArgumentException.negRoundPrecision(precision);
} else {
double value = inNumber.doubleValue();
if (Double.isInfinite(value) || Double.isNaN(value)) {
return doubleValue(value);
}
/*
* For precision zero and no explicit rounding mode, we want to fall back to Java Math.round().
* This rounds towards the nearest integer and if there is a tie, towards positive infinity,
* which doesn't correspond to any of the rounding modes.
*/
else if (precision == 0 && !explicitMode) {
return doubleValue(Math.round(value));
} else {
BigDecimal bigDecimal = BigDecimal.valueOf(value);
int newScale = Math.min(bigDecimal.scale(), precision);
return doubleValue(bigDecimal.setScale(newScale, mode).doubleValue());
}
}
} else {
throw needsNumbers("round()");
}
}
public static AnyValue abs(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
if (in instanceof IntegralValue) {
return longValue(Math.abs(number.longValue()));
} else {
return doubleValue(Math.abs(number.doubleValue()));
}
} else {
throw needsNumbers("abs()");
}
}
public static AnyValue isNaN(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof FloatingPointValue f) {
return booleanValue(f.isNaN());
} else if (in instanceof NumberValue) {
return BooleanValue.FALSE;
} else {
throw needsNumbers("isNaN()");
}
}
public static AnyValue toDegrees(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.toDegrees(number.doubleValue()));
} else {
throw needsNumbers("toDegrees()");
}
}
public static AnyValue exp(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.exp(number.doubleValue()));
} else {
throw needsNumbers("exp()");
}
}
public static AnyValue log(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.log(number.doubleValue()));
} else {
throw needsNumbers("log()");
}
}
public static AnyValue log10(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.log10(number.doubleValue()));
} else {
throw needsNumbers("log10()");
}
}
public static AnyValue toRadians(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.toRadians(number.doubleValue()));
} else {
throw needsNumbers("toRadians()");
}
}
@CalledFromGeneratedCode
public static ListValue range(AnyValue startValue, AnyValue endValue) {
return VirtualValues.range(
asLong(startValue, () -> "Invalid input for start value in function 'range()'"),
asLong(endValue, () -> "Invalid input for end value in function 'range()'"),
1L);
}
public static ListValue range(AnyValue startValue, AnyValue endValue, AnyValue stepValue) {
long step = asLong(stepValue, () -> "Invalid input for step value in function 'range()'");
if (step == 0L) {
throw InvalidArgumentException.zeroStepRange();
}
return VirtualValues.range(
asLong(startValue, () -> "Invalid input for start value in function 'range()'"),
asLong(endValue, () -> "Invalid input for end value in function 'range()'"),
step);
}
@CalledFromGeneratedCode
public static AnyValue signum(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return longValue((long) Math.signum(number.doubleValue()));
} else {
throw needsNumbers("signum()");
}
}
public static AnyValue sqrt(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof NumberValue number) {
return doubleValue(Math.sqrt(number.doubleValue()));
} else {
throw needsNumbers("sqrt()");
}
}
public static DoubleValue rand() {
return doubleValue(ThreadLocalRandom.current().nextDouble());
}
public static TextValue randomUuid() {
return stringValue(UUID.randomUUID().toString());
}
// TODO: Support better calculations, like https://en.wikipedia.org/wiki/Vincenty%27s_formulae
// TODO: Support more coordinate systems
public static Value distance(AnyValue lhs, AnyValue rhs) {
if (lhs instanceof PointValue && rhs instanceof PointValue) {
return calculateDistance((PointValue) lhs, (PointValue) rhs);
} else {
return NO_VALUE;
}
}
public static Value withinBBox(AnyValue point, AnyValue lowerLeft, AnyValue upperRight) {
if (point instanceof PointValue && lowerLeft instanceof PointValue && upperRight instanceof PointValue) {
return withinBBox((PointValue) point, (PointValue) lowerLeft, (PointValue) upperRight);
} else {
return NO_VALUE;
}
}
public static Value withinBBox(PointValue point, PointValue lowerLeft, PointValue upperRight) {
CoordinateReferenceSystem crs = point.getCoordinateReferenceSystem();
if (crs.equals(lowerLeft.getCoordinateReferenceSystem())
&& crs.equals(upperRight.getCoordinateReferenceSystem())) {
return Values.booleanValue(crs.getCalculator().withinBBox(point, lowerLeft, upperRight));
} else {
return NO_VALUE;
}
}
@CalledFromGeneratedCode
public static Value vectorSimilarityEuclidean(AnyValue lhs, AnyValue rhs) {
return vectorSimilarity(VectorSimilarity.EUCLIDEAN, lhs, rhs);
}
@CalledFromGeneratedCode
public static Value vectorSimilarityCosine(AnyValue lhs, AnyValue rhs) {
return vectorSimilarity(VectorSimilarity.COSINE, lhs, rhs);
}
public static Value vectorSimilarity(VectorSimilarity similarity, AnyValue lhs, AnyValue rhs) {
final var function = similarity.latestImplementation();
if (lhs == NO_VALUE || rhs == NO_VALUE) {
return NO_VALUE;
}
final var a = toFloatArrayVector(function, lhs, "a");
final var b = toFloatArrayVector(function, rhs, "b");
if (a.length != b.length) {
throw new InvalidArgumentException(invalidSimilarityFunctionInputErrorMessage(
function, "The supplied vectors do not have the same number of dimensions"));
}
return doubleValue(function.compare(a, b));
}
public static float[] toFloatArrayVector(VectorSimilarityFunction function, AnyValue arg, String argName) {
final VectorCandidate candidate = VectorCandidate.maybeFrom(arg);
if (candidate == null) {
throw new CypherTypeException(invalidSimilarityFunctionInputErrorMessage(
function, "Expected argument %s to be a LIST".formatted(argName)));
}
final float[] floatArray = function.maybeToValidVector(candidate);
if (floatArray == null) {
throw new InvalidArgumentException(invalidSimilarityFunctionInputErrorMessage(
function, "Argument %s is not a valid vector for this similarity function".formatted(argName)));
}
return floatArray;
}
private static String invalidSimilarityFunctionInputErrorMessage(VectorSimilarityFunction function, String reason) {
return "Invalid input for 'vector.similarity.%s()': %s."
.formatted(function.name().toLowerCase(), reason);
}
public static AnyValue startNode(AnyValue anyValue, DbAccess access, RelationshipScanCursor cursor) {
if (anyValue == NO_VALUE) {
return NO_VALUE;
} else if (anyValue instanceof VirtualRelationshipValue rel) {
return startNode(rel, access, cursor);
} else {
throw new CypherTypeException(format(
"Invalid input for function 'startNode()': Expected %s to be a RelationshipValue", anyValue));
}
}
public static VirtualNodeValue startNode(
VirtualRelationshipValue relationship, DbAccess access, RelationshipScanCursor cursor) {
return VirtualValues.node(relationship.startNodeId(consumer(access, cursor)));
}
public static AnyValue endNode(AnyValue anyValue, DbAccess access, RelationshipScanCursor cursor) {
if (anyValue == NO_VALUE) {
return NO_VALUE;
} else if (anyValue instanceof VirtualRelationshipValue rel) {
return endNode(rel, access, cursor);
} else {
throw new CypherTypeException(
format("Invalid input for function 'endNode()': Expected %s to be a RelationshipValue", anyValue));
}
}
public static VirtualNodeValue endNode(
VirtualRelationshipValue relationship, DbAccess access, RelationshipScanCursor cursor) {
return VirtualValues.node(relationship.endNodeId(consumer(access, cursor)));
}
@CalledFromGeneratedCode
public static VirtualNodeValue otherNode(
AnyValue anyValue, DbAccess access, VirtualNodeValue node, RelationshipScanCursor cursor) {
// This is not a function exposed to the user
assert anyValue != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (anyValue instanceof VirtualRelationshipValue rel) {
return otherNode(rel, access, node, cursor);
} else {
if (anyValue instanceof Value v)
throw CypherTypeException.expectedRelValue(
String.valueOf(v), v.prettyPrint(), CypherTypeValueMapper.valueType(anyValue));
else
throw CypherTypeException.expectedRelValue(
String.valueOf(anyValue), String.valueOf(anyValue), CypherTypeValueMapper.valueType(anyValue));
}
}
public static VirtualNodeValue otherNode(
VirtualRelationshipValue relationship,
DbAccess access,
VirtualNodeValue node,
RelationshipScanCursor cursor) {
return VirtualValues.node(relationship.otherNodeId(node.id(), consumer(access, cursor)));
}
@CalledFromGeneratedCode
public static AnyValue propertyGet(
String key,
AnyValue container,
DbAccess dbAccess,
NodeCursor nodeCursor,
RelationshipScanCursor relationshipScanCursor,
PropertyCursor propertyCursor) {
if (container == NO_VALUE) {
return NO_VALUE;
} else if (container instanceof VirtualNodeValue node) {
return dbAccess.nodeProperty(node.id(), dbAccess.propertyKey(key), nodeCursor, propertyCursor, true);
} else if (container instanceof VirtualRelationshipValue rel) {
return dbAccess.relationshipProperty(
rel, dbAccess.propertyKey(key), relationshipScanCursor, propertyCursor, true);
} else if (container instanceof MapValue map) {
return map.get(key);
} else if (container instanceof TemporalValue, ?> temporal) {
return temporal.get(key);
} else if (container instanceof DurationValue duration) {
return duration.get(key);
} else if (container instanceof PointValue point) {
return point.get(key);
} else {
if (container instanceof Value value)
throw CypherTypeException.expectedMap(
String.valueOf(value), value.prettyPrint(), CypherTypeValueMapper.valueType(container));
else
throw CypherTypeException.expectedMap(
String.valueOf(container),
String.valueOf(container),
CypherTypeValueMapper.valueType(container));
}
}
@CalledFromGeneratedCode
public static AnyValue[] propertiesGet(
String[] keys,
AnyValue container,
DbAccess dbAccess,
NodeCursor nodeCursor,
RelationshipScanCursor relationshipScanCursor,
PropertyCursor propertyCursor) {
if (container instanceof VirtualNodeValue node) {
return dbAccess.nodeProperties(node.id(), propertyKeys(keys, dbAccess), nodeCursor, propertyCursor);
} else if (container instanceof VirtualRelationshipValue rel) {
return dbAccess.relationshipProperties(
rel, propertyKeys(keys, dbAccess), relationshipScanCursor, propertyCursor);
} else {
return CursorUtils.propertiesGet(keys, container);
}
}
@CalledFromGeneratedCode
public static AnyValue containerIndex(
AnyValue container,
AnyValue index,
DbAccess dbAccess,
NodeCursor nodeCursor,
RelationshipScanCursor relationshipScanCursor,
PropertyCursor propertyCursor) {
if (container == NO_VALUE || index == NO_VALUE) {
return NO_VALUE;
} else if (container instanceof VirtualNodeValue node) {
return dbAccess.nodeProperty(node.id(), propertyKeyId(dbAccess, index), nodeCursor, propertyCursor, true);
} else if (container instanceof VirtualRelationshipValue rel) {
return dbAccess.relationshipProperty(
rel, propertyKeyId(dbAccess, index), relationshipScanCursor, propertyCursor, true);
}
if (container instanceof MapValue map) {
return mapAccess(map, index);
} else if (container instanceof SequenceValue seq) {
return listAccess(seq, index);
} else {
if (container instanceof Value v)
throw CypherTypeException.notCollectionOrMap(
String.valueOf(v), v.prettyPrint(), CypherTypeValueMapper.valueType(v), index);
else
throw CypherTypeException.notCollectionOrMap(
String.valueOf(container),
String.valueOf(container),
CypherTypeValueMapper.valueType(container),
index);
}
}
@CalledFromGeneratedCode
public static Value containerIndexExists(
AnyValue container,
AnyValue index,
DbAccess dbAccess,
NodeCursor nodeCursor,
RelationshipScanCursor relationshipScanCursor,
PropertyCursor propertyCursor) {
if (container == NO_VALUE || index == NO_VALUE) {
return NO_VALUE;
} else if (container instanceof VirtualNodeValue node) {
return booleanValue(
dbAccess.nodeHasProperty(node.id(), propertyKeyId(dbAccess, index), nodeCursor, propertyCursor));
} else if (container instanceof VirtualRelationshipValue rel) {
return booleanValue(dbAccess.relationshipHasProperty(
rel, propertyKeyId(dbAccess, index), relationshipScanCursor, propertyCursor));
}
if (container instanceof MapValue map) {
return booleanValue(map.containsKey(asString(
index,
() ->
// this string assumes that the asString method fails and gives context which
// operation went wrong
"Cannot use non string value as or in map keys. It was " + index.toString())));
} else {
if (container instanceof Value v)
throw CypherTypeException.notMap(
String.valueOf(v), v.prettyPrint(), CypherTypeValueMapper.valueType(v), index);
else
throw CypherTypeException.notMap(
String.valueOf(container),
String.valueOf(container),
CypherTypeValueMapper.valueType(container),
index);
}
}
@CalledFromGeneratedCode
public static AnyValue head(AnyValue container) {
if (container == NO_VALUE) {
return NO_VALUE;
} else if (container instanceof SequenceValue sequence) {
if (sequence.intSize() == 0) {
return NO_VALUE;
}
return sequence.value(0);
} else {
throw new CypherTypeException(
format("Invalid input for function 'head()': Expected %s to be a list", container));
}
}
@CalledFromGeneratedCode
public static AnyValue tail(AnyValue container) {
if (container == NO_VALUE) {
return NO_VALUE;
} else if (container instanceof ListValue) {
return ((ListValue) container).tail();
} else if (container instanceof ArrayValue) {
return VirtualValues.fromArray((ArrayValue) container).tail();
} else {
return EMPTY_LIST;
}
}
@CalledFromGeneratedCode
public static AnyValue last(AnyValue container) {
if (container == NO_VALUE) {
return NO_VALUE;
}
if (container instanceof SequenceValue sequence) {
int length = sequence.intSize();
if (length == 0) {
return NO_VALUE;
}
return sequence.value(length - 1);
} else {
throw new CypherTypeException(
format("Invalid input for function 'last()': Expected %s to be a list", container));
}
}
public static AnyValue left(AnyValue in, AnyValue endPos) {
if (in == NO_VALUE || endPos == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof TextValue text) {
final long len = asLong(endPos, () -> "Invalid input for length value in function 'left()'");
return text.substring(0, (int) Math.min(len, Integer.MAX_VALUE));
} else {
throw notAString("left", in);
}
}
public static AnyValue ltrim(AnyValue trimSource) {
if (trimSource == NO_VALUE) {
return NO_VALUE;
} else if (trimSource instanceof TextValue) {
return ((TextValue) trimSource).ltrim();
} else {
throw notAString("ltrim", trimSource);
}
}
public static AnyValue ltrim(AnyValue trimSource, AnyValue trimCharacterString) {
if (trimSource == NO_VALUE || trimCharacterString == NO_VALUE) {
return NO_VALUE;
} else if (trimSource instanceof TextValue trimSourceText) {
if (trimCharacterString instanceof TextValue trimCharacterStringText) {
return trimSourceText.ltrim(trimCharacterStringText);
} else {
throw notAString("ltrim", trimCharacterString);
}
} else {
throw notAString("ltrim", trimSource);
}
}
public static AnyValue rtrim(AnyValue trimSource) {
if (trimSource == NO_VALUE) {
return NO_VALUE;
} else if (trimSource instanceof TextValue) {
return ((TextValue) trimSource).rtrim();
} else {
throw notAString("rtrim", trimSource);
}
}
public static AnyValue rtrim(AnyValue trimSource, AnyValue trimCharacterString) {
if (trimSource == NO_VALUE || trimCharacterString == NO_VALUE) {
return NO_VALUE;
} else if (trimSource instanceof TextValue trimSourceText) {
if (trimCharacterString instanceof TextValue trimCharacterStringText) {
return trimSourceText.rtrim(trimCharacterStringText);
} else {
throw notAString("rtrim", trimCharacterString);
}
} else {
throw notAString("rtrim", trimSource);
}
}
public static AnyValue btrim(AnyValue trimSource) {
if (trimSource == NO_VALUE) {
return NO_VALUE;
} else if (trimSource instanceof TextValue) {
return ((TextValue) trimSource).trim();
} else {
throw notAString("btrim", trimSource);
}
}
public static AnyValue btrim(AnyValue trimSource, AnyValue trimCharacterString) {
if (trimSource == NO_VALUE || trimCharacterString == NO_VALUE) {
return NO_VALUE;
} else if (trimSource instanceof TextValue trimSourceText) {
if (trimCharacterString instanceof TextValue trimCharacterStringText) {
return trimSourceText.trim(trimCharacterStringText);
} else {
throw notAString("btrim", trimCharacterString);
}
} else {
throw notAString("btrim", trimSource);
}
}
public static AnyValue trim(AnyValue trimSpecification, AnyValue trimSource) {
if (trimSource == NO_VALUE) {
return NO_VALUE;
}
if (!(trimSource instanceof TextValue)) {
throw notAString("trim", trimSource);
}
if (trimSpecification instanceof TextValue trimSpec) {
return switch (trimSpec.stringValue()) {
case "LEADING" -> ltrim(trimSource);
case "TRAILING" -> rtrim(trimSource);
default -> btrim(trimSource);
};
} else {
throw notAString("trim", trimSpecification);
}
}
public static AnyValue trim(AnyValue trimSpecification, AnyValue trimSource, AnyValue trimCharacterString) {
if (trimSource == NO_VALUE) {
return NO_VALUE;
}
if (!(trimSource instanceof TextValue)) {
throw notAString("trim", trimSource);
}
if (trimSpecification instanceof TextValue trimSpec) {
if (trimCharacterString instanceof TextValue trimCharString) {
if (trimCharString.length() != 1) {
throw new InvalidArgumentException(
"The argument `trimCharacterString` in the `trim()` function must be of length 1.");
}
}
return switch (trimSpec.stringValue()) {
case "LEADING" -> ltrim(trimSource, trimCharacterString);
case "TRAILING" -> rtrim(trimSource, trimCharacterString);
default -> btrim(trimSource, trimCharacterString);
};
} else {
throw notAString("trim", trimSpecification);
}
}
public static AnyValue replace(AnyValue original, AnyValue search, AnyValue replaceWith) {
if (original == NO_VALUE || search == NO_VALUE || replaceWith == NO_VALUE) {
return NO_VALUE;
} else if (original instanceof TextValue) {
return ((TextValue) original).replace(asString(search), asString(replaceWith));
} else {
throw notAString("replace", original);
}
}
public static AnyValue reverse(AnyValue original) {
if (original == NO_VALUE) {
return NO_VALUE;
} else if (original instanceof TextValue text) {
return text.reverse();
} else if (original instanceof ListValue list) {
return list.reverse();
} else {
throw new CypherTypeException(
"Invalid input for function 'reverse()': "
+ "Expected a string or a list; consider converting the value to a string with toString() or creating a list.");
}
}
public static AnyValue right(AnyValue original, AnyValue length) {
if (original == NO_VALUE || length == NO_VALUE) {
return NO_VALUE;
} else if (original instanceof TextValue asText) {
final long len = asLong(length, () -> "Invalid input for length value in function 'right()'");
if (len < 0) {
throw new IndexOutOfBoundsException("negative length");
}
final long startVal = asText.length() - len;
return asText.substring((int) Math.max(0, startVal));
} else {
throw notAString("right", original);
}
}
public static AnyValue concatenate(AnyValue lhs, AnyValue rhs) {
if (lhs == NO_VALUE && rhs == NO_VALUE) {
return NO_VALUE;
}
// List Concatenation - Can only concatenate lists when both sides are lists
// arrays are same as lists when it comes to concatenation
if (lhs instanceof ArrayValue array) {
lhs = VirtualValues.fromArray(array);
}
if (rhs instanceof ArrayValue array) {
rhs = VirtualValues.fromArray(array);
}
// Null values only return null if the other side is either null (checked already) or a valid concatenation
// type.
if (lhs == NO_VALUE) {
if (rhs instanceof ListValue || rhs instanceof TextValue) {
return NO_VALUE;
}
}
if (rhs == NO_VALUE) {
if (lhs instanceof ListValue || lhs instanceof TextValue) {
return NO_VALUE;
}
}
boolean lhsIsListValue = lhs instanceof ListValue;
if (lhsIsListValue && rhs instanceof ListValue) {
return ((ListValue) lhs).appendAll((ListValue) rhs);
}
// String Concatenation - Can only concatenate strings when both sides are strings
if (lhs instanceof TextValue && rhs instanceof TextValue) {
return ((TextValue) lhs).plus((TextValue) rhs);
}
throw new CypherTypeException(
String.format("Cannot concatenate `%s` and `%s`", lhs.getTypeName(), rhs.getTypeName()));
}
public static AnyValue normalize(AnyValue input) {
return normalize(input, Values.stringValue("NFC"));
}
public static AnyValue normalize(AnyValue input, AnyValue normalForm) {
if (input == NO_VALUE || normalForm == NO_VALUE) {
return NO_VALUE;
}
Normalizer.Form form;
try {
form = Normalizer.Form.valueOf(asTextValue(normalForm).stringValue());
} catch (IllegalArgumentException e) {
throw InvalidArgumentException.unknownNormalForm(String.valueOf(normalForm));
}
String normalized = Normalizer.normalize(asTextValue(input).stringValue(), form);
return Values.stringValue(normalized);
}
public static AnyValue split(AnyValue original, AnyValue separator) {
if (original == NO_VALUE || separator == NO_VALUE) {
return NO_VALUE;
} else if (original instanceof TextValue asText) {
if (asText.length() == 0) {
return VirtualValues.list(EMPTY_STRING);
}
if (separator instanceof SequenceValue separatorList) {
var separators = new ArrayList();
for (var s : separatorList) {
if (s == NO_VALUE) {
return NO_VALUE;
}
separators.add(asString(s));
}
return asText.split(separators);
} else {
return asText.split(asString(separator));
}
} else {
throw notAString("split", original);
}
}
public static AnyValue substring(AnyValue original, AnyValue start) {
if (original == NO_VALUE || start == NO_VALUE) {
return NO_VALUE;
} else if (original instanceof TextValue asText) {
return asText.substring(asIntExact(start, () -> "Invalid input for start value in function 'substring()'"));
} else {
throw notAString("substring", original);
}
}
public static AnyValue substring(AnyValue original, AnyValue start, AnyValue length) {
if (original == NO_VALUE || start == NO_VALUE || length == NO_VALUE) {
return NO_VALUE;
} else if (original instanceof TextValue asText) {
return asText.substring(
asIntExact(start, () -> "Invalid input for start value in function 'substring()'"),
asIntExact(length, () -> "Invalid input for length value in function 'substring()'"));
} else {
throw notAString("substring", original);
}
}
public static AnyValue toLower(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof TextValue) {
return ((TextValue) in).toLower();
} else {
throw notAString("toLower", in);
}
}
public static AnyValue toUpper(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof TextValue) {
return ((TextValue) in).toUpper();
} else {
throw notAString("toUpper", in);
}
}
public static Value id(AnyValue item) {
if (item == NO_VALUE) {
return NO_VALUE;
} else if (item instanceof VirtualNodeValue) {
return longValue(((VirtualNodeValue) item).id());
} else if (item instanceof VirtualRelationshipValue) {
return longValue(((VirtualRelationshipValue) item).id());
} else {
throw new CypherTypeException(format(
"Invalid input for function 'id()': Expected %s to be a node or relationship, but it was `%s`",
item, item.getTypeName()));
}
}
public static AnyValue elementId(AnyValue entity, ElementIdMapper idMapper) {
if (entity == NO_VALUE) {
return NO_VALUE;
} else if (entity instanceof NodeValue node) {
// Needed to get correct ids in certain fabric queries.
return stringValue(node.elementId());
} else if (entity instanceof VirtualNodeValue node && node.id() < 0) {
// Don't format element ids for nodes with negative ids, such as db schema visualization
return stringValue(String.valueOf(node.id()));
} else if (entity instanceof VirtualNodeValue node) {
return stringValue(idMapper.nodeElementId(node.id()));
} else if (entity instanceof RelationshipValue relationship) {
// Needed to get correct ids in certain fabric queries.
return stringValue(relationship.elementId());
} else if (entity instanceof VirtualRelationshipValue relationship && relationship.id() < 0) {
// Don't format element ids for relationships with negative ids, such as db schema visualization
return stringValue(String.valueOf(relationship.id()));
} else if (entity instanceof VirtualRelationshipValue relationship) {
return stringValue(idMapper.relationshipElementId(relationship.id()));
}
throw new CypherTypeException(format(
"Invalid input for function 'elementId()': Expected %s to be a node or relationship, but it was `%s`",
entity, entity.getTypeName()));
}
public static AnyValue elementIdToNodeId(AnyValue elementId, ElementIdMapper idMapper) {
if (elementId == NO_VALUE) {
return NO_VALUE;
} else if (elementId instanceof TextValue str) {
try {
return longValue(idMapper.nodeId(str.stringValue()));
} catch (IllegalArgumentException e) {
return NO_VALUE;
}
}
return NO_VALUE;
}
public static AnyValue elementIdToRelationshipId(AnyValue elementId, ElementIdMapper idMapper) {
if (elementId == NO_VALUE) {
return NO_VALUE;
} else if (elementId instanceof TextValue str) {
try {
return longValue(idMapper.relationshipId(str.stringValue()));
} catch (IllegalArgumentException e) {
return NO_VALUE;
}
}
return NO_VALUE;
}
public static AnyValue elementIdListToNodeIdList(AnyValue collection, ElementIdMapper idMapper) {
if (collection == NO_VALUE) {
return NO_VALUE;
} else if (collection instanceof SequenceValue elementIds) {
var builder = ListValueBuilder.newListBuilder(elementIds.intSize());
for (var elementId : elementIds) {
AnyValue value = elementIdToNodeId(elementId, idMapper);
builder.add(value);
}
return builder.build();
}
return NO_VALUE;
}
public static AnyValue elementIdListToRelationshipIdList(AnyValue collection, ElementIdMapper idMapper) {
if (collection == NO_VALUE) {
return NO_VALUE;
} else if (collection instanceof SequenceValue elementIds) {
var builder = ListValueBuilder.newListBuilder(elementIds.intSize());
for (var elementId : elementIds) {
AnyValue value = elementIdToRelationshipId(elementId, idMapper);
builder.add(value);
}
return builder.build();
}
return NO_VALUE;
}
public static TextValue nodeElementId(long id, ElementIdMapper idMapper) {
assert id > LongReference.NULL;
return Values.stringValue(idMapper.nodeElementId(id));
}
public static TextValue relationshipElementId(long id, ElementIdMapper idMapper) {
assert id > LongReference.NULL;
return Values.stringValue(idMapper.relationshipElementId(id));
}
public static AnyValue labels(AnyValue item, DbAccess access, NodeCursor nodeCursor) {
if (item == NO_VALUE) {
return NO_VALUE;
} else if (item instanceof NodeEntityWrappingNodeValue node && node.id() < 0) {
// Labels for entities with negative id, such as db schema visualization, are already populated since
// the entity isn't a node in storage
var builder = ListValueBuilder.newListBuilder(node.labels().intSize());
node.labels().forEach(builder::add);
return builder.build();
} else if (item instanceof VirtualNodeValue node) {
return access.getLabelsForNode(node.id(), nodeCursor);
} else {
throw new CypherTypeException("Invalid input for function 'labels()': Expected a Node, got: " + item);
}
}
@CalledFromGeneratedCode
public static boolean hasLabel(AnyValue entity, int labelToken, DbAccess access, NodeCursor nodeCursor) {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualNodeValue node) {
return access.isLabelSetOnNode(labelToken, node.id(), nodeCursor);
} else {
if (entity instanceof Value v)
throw CypherTypeException.expectedNode(
String.valueOf(v), v.prettyPrint(), CypherTypeValueMapper.valueType(v));
else
throw CypherTypeException.expectedNode(
String.valueOf(entity), String.valueOf(entity), CypherTypeValueMapper.valueType(entity));
}
}
@CalledFromGeneratedCode
public static boolean hasLabels(AnyValue entity, int[] labelTokens, DbAccess access, NodeCursor nodeCursor) {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualNodeValue node) {
return access.areLabelsSetOnNode(labelTokens, node.id(), nodeCursor);
} else {
if (entity instanceof Value v)
throw CypherTypeException.expectedNode(
String.valueOf(v), v.prettyPrint(), CypherTypeValueMapper.valueType(v));
else
throw CypherTypeException.expectedNode(
String.valueOf(entity), String.valueOf(entity), CypherTypeValueMapper.valueType(entity));
}
}
private static boolean hasLabel(
VirtualNodeValue node, TextValue textLabel, NodeCursor nodeCursor, QueryContext queryContext)
throws IllegalTokenNameException {
var validName = TokenWrite.checkValidTokenName(textLabel.stringValue());
var tokenId = queryContext.nodeLabel(validName);
if (tokenId == TokenConstants.NO_TOKEN) {
return false;
}
return queryContext.isLabelSetOnNode(tokenId, node.id(), nodeCursor);
}
public static String evaluateSingleDynamicRelType(AnyValue value) throws IllegalTokenNameException {
TextValue singleValue = null;
if (value instanceof TextValue textValue) {
singleValue = textValue;
} else if (value instanceof SequenceValue sequenceValue) {
for (var t : sequenceValue) {
if (t instanceof TextValue textValue) {
if (singleValue == null) {
singleValue = textValue;
} else if (!singleValue.equals(textValue)) {
throw new IllegalArgumentException("Error - Exactly one relationship type must be specified.");
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'evaluateDynamicRelType()': Expected %s to be a string, but it was a `%s`",
t, t.getTypeName()));
}
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'evaluateDynamicRelType()': Expected %s to be a string or list of strings, but it was a `%s`",
value, value.getTypeName()));
}
if (singleValue == null) {
// can only reach here if value was an empty sequence
throw new IllegalArgumentException("Error - Exactly one relationship type must be specified.");
}
return singleValue.stringValue();
}
public static int getOrCreateDynamicRelType(AnyValue value, QueryContext queryContext)
throws IllegalTokenNameException {
return queryContext.getOrCreateRelTypeId(evaluateSingleDynamicRelType(value));
}
public static int getOrCreateDynamicRelType(AnyValue value, TokenWrite token) throws KernelException {
return token.relationshipTypeGetOrCreateForName(evaluateSingleDynamicRelType(value));
}
@CalledFromGeneratedCode
public static boolean hasDynamicLabels(
AnyValue entity, AnyValue[] labelNames, NodeCursor nodeCursor, QueryContext queryContext)
throws IllegalTokenNameException {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualNodeValue node) {
for (var labelName : labelNames) {
if (labelName instanceof TextValue textLabel) {
if (!hasLabel(node, textLabel, nodeCursor, queryContext)) {
return false;
}
} else if (labelName instanceof SequenceValue labelSequence) {
for (var l : labelSequence) {
if (l instanceof TextValue textLabel) {
if (!hasLabel(node, textLabel, nodeCursor, queryContext)) {
return false;
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasDynamicLabels()': Expected %s to be a string, but it was `%s`",
labelName, labelName.getTypeName()));
}
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasDynamicLabels()': Expected %s to be a string or list of strings, but it was `%s`",
labelName, labelName.getTypeName()));
}
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasDynamicLabels()': Expected %s to be a node, but it was `%s`",
entity, entity.getTypeName()));
}
return true;
}
@CalledFromGeneratedCode
public static boolean hasALabel(AnyValue entity, DbAccess access, NodeCursor nodeCursor) {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualNodeValue virtualNodeValue) {
return access.isALabelSetOnNode(virtualNodeValue.id(), nodeCursor);
} else {
if (entity instanceof Value v)
throw CypherTypeException.expectedNode(
String.valueOf(v), v.prettyPrint(), CypherTypeValueMapper.valueType(v));
else
throw CypherTypeException.expectedNode(
String.valueOf(entity), String.valueOf(entity), CypherTypeValueMapper.valueType(entity));
}
}
@CalledFromGeneratedCode
public static boolean hasALabelOrType(AnyValue entity, DbAccess access, NodeCursor nodeCursor) {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualNodeValue node) {
return access.isALabelSetOnNode(node.id(), nodeCursor);
} else if (entity instanceof VirtualRelationshipValue) {
return true;
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasALabelOrType()': Expected %s to be a node or relationship, but it was `%s`",
entity, entity.getTypeName()));
}
}
@CalledFromGeneratedCode
public static boolean hasLabelsOrTypes(
AnyValue entity,
DbAccess access,
int[] labels,
NodeCursor nodeCursor,
int[] types,
RelationshipScanCursor relationshipScanCursor) {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualNodeValue node) {
return access.areLabelsSetOnNode(labels, node.id(), nodeCursor);
} else if (entity instanceof VirtualRelationshipValue relationship) {
return access.areTypesSetOnRelationship(types, relationship, relationshipScanCursor);
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasALabelOrType()': Expected %s to be a node or relationship, but it was `%s`",
entity, entity.getTypeName()));
}
}
@CalledFromGeneratedCode
public static boolean hasAnyLabel(AnyValue entity, int[] labels, DbAccess access, NodeCursor nodeCursor) {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualNodeValue node) {
return access.isAnyLabelSetOnNode(labels, node.id(), nodeCursor);
} else {
if (entity instanceof Value v)
throw CypherTypeException.expectedNode(
String.valueOf(v), v.prettyPrint(), CypherTypeValueMapper.valueType(v));
else
throw CypherTypeException.expectedNode(
String.valueOf(entity), String.valueOf(entity), CypherTypeValueMapper.valueType(entity));
}
}
@CalledFromGeneratedCode
public static boolean hasAnyDynamicLabel(
AnyValue entity, AnyValue[] labels, NodeCursor nodeCursor, QueryContext queryContext)
throws IllegalTokenNameException {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualNodeValue node) {
for (var labelName : labels) {
if (labelName instanceof TextValue textLabel) {
if (hasLabel(node, textLabel, nodeCursor, queryContext)) {
return true;
}
} else if (labelName instanceof SequenceValue labelSequence) {
for (var l : labelSequence) {
if (l instanceof TextValue textLabel) {
if (hasLabel(node, textLabel, nodeCursor, queryContext)) {
return true;
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasAnyDynamicLabel()': Expected %s to be a string, but it was a `%s`",
l, l.getTypeName()));
}
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasAnyDynamicLabel()': Expected %s to be a string or list of strings, but it was a `%s`",
labelName, labelName.getTypeName()));
}
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasAnyDynamicLabel()': Expected %s to be a node, but it was a `%s`",
entity, entity.getTypeName()));
}
return false;
}
public static AnyValue type(AnyValue item, DbAccess access, RelationshipScanCursor relCursor, Read read) {
if (item == NO_VALUE) {
return NO_VALUE;
} else if (item instanceof RelationshipValue relationship) {
return relationship.type();
} else if (item instanceof VirtualRelationshipValue relationship) {
int typeToken = relationship.relationshipTypeId(relationshipVisitor -> {
long relationshipId = relationshipVisitor.id();
access.singleRelationship(relationshipId, relCursor);
if (relCursor.next() || read.relationshipDeletedInTransaction(relationshipId)) {
relationshipVisitor.visit(
relCursor.sourceNodeReference(), relCursor.targetNodeReference(), relCursor.type());
}
});
if (typeToken == TokenConstants.NO_TOKEN) {
return NO_VALUE;
} else {
return Values.stringValue(access.relationshipTypeName(typeToken));
}
} else {
throw new CypherTypeException("Invalid input for function 'type()': Expected a Relationship, got: " + item);
}
}
@CalledFromGeneratedCode
public static boolean hasType(AnyValue entity, int typeToken, DbAccess access, RelationshipScanCursor relCursor) {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualRelationshipValue relationship) {
if (typeToken == StatementConstants.NO_SUCH_RELATIONSHIP_TYPE) {
return false;
} else {
return typeToken == relationship.relationshipTypeId(consumer(access, relCursor));
}
} else {
if (entity instanceof Value v)
throw CypherTypeException.expectedRel(
String.valueOf(v), v.prettyPrint(), CypherTypeValueMapper.valueType(v));
else
throw CypherTypeException.expectedRel(
String.valueOf(entity), String.valueOf(entity), CypherTypeValueMapper.valueType(entity));
}
}
@CalledFromGeneratedCode
public static boolean hasTypes(
AnyValue entity, int[] typeTokens, DbAccess access, RelationshipScanCursor relCursor) {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualRelationshipValue relationship) {
return access.areTypesSetOnRelationship(typeTokens, relationship, relCursor);
} else {
if (entity instanceof Value v)
throw CypherTypeException.expectedRel(
String.valueOf(v), v.prettyPrint(), CypherTypeValueMapper.valueType(v));
else
throw CypherTypeException.expectedRel(
String.valueOf(entity), String.valueOf(entity), CypherTypeValueMapper.valueType(entity));
}
}
private static boolean hasType(
VirtualRelationshipValue relationship,
TextValue textValue,
RelationshipScanCursor relCursor,
DbAccess queryContext)
throws IllegalTokenNameException {
var validName = TokenWrite.checkValidTokenName(textValue.stringValue());
var tokenId = queryContext.relationshipType(validName);
return queryContext.isTypeSetOnRelationship(tokenId, relationship.id(), relCursor);
}
@CalledFromGeneratedCode
public static boolean hasDynamicType(
AnyValue entity,
AnyValue[] dynamicTypes,
RelationshipScanCursor relCursor,
DbAccess queryContext,
RuntimeNotifier notifier)
throws IllegalTokenNameException {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
TextValue singleValue = null;
ArrayList conflictingTypes = null;
if (entity instanceof VirtualRelationshipValue relationship) {
for (var value : dynamicTypes) {
if (value instanceof TextValue textValue) {
if (conflictingTypes != null) {
conflictingTypes.add(textValue.stringValue());
} else if (singleValue == null) {
singleValue = textValue;
} else if (!singleValue.equals(textValue)) {
conflictingTypes = new ArrayList<>();
conflictingTypes.add(singleValue.stringValue());
conflictingTypes.add(textValue.stringValue());
}
} else if (value instanceof SequenceValue sequenceValue) {
for (var t : sequenceValue) {
if (t instanceof TextValue textValue) {
if (conflictingTypes != null) {
conflictingTypes.add(textValue.stringValue());
} else if (singleValue == null) {
singleValue = textValue;
} else if (!singleValue.equals(textValue)) {
conflictingTypes = new ArrayList<>();
conflictingTypes.add(singleValue.stringValue());
conflictingTypes.add(textValue.stringValue());
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasDynamicType()': Expected %s to be a string, but it was a `%s`",
t, t.getTypeName()));
}
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasDynamicType()': Expected %s to be a string or list of strings, but it was a `%s`",
value, value.getTypeName()));
}
}
if (singleValue == null) {
// weird but if we have no value by this point then the list was empty and all(for n in []) == true
return true;
}
if (conflictingTypes != null) {
// detected more than one relationship type; the conjunction can never be satisfied
notifier.newRuntimeNotification(new RuntimeUnsatisfiableRelationshipTypeExpression(conflictingTypes));
return false;
}
return hasType(relationship, singleValue, relCursor, queryContext);
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasDynamicType()': Expected %s to be a relationship, but it was a `%s`",
entity, entity.getTypeName()));
}
}
@CalledFromGeneratedCode
public static boolean hasAnyDynamicType(
AnyValue entity, AnyValue[] dynamicTypes, RelationshipScanCursor relCursor, QueryContext queryContext)
throws IllegalTokenNameException {
assert entity != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (entity instanceof VirtualRelationshipValue relationship) {
for (var typ : dynamicTypes) {
if (typ instanceof TextValue textValue) {
if (hasType(relationship, textValue, relCursor, queryContext)) {
return true;
}
} else if (typ instanceof SequenceValue typeSeq) {
for (var t : typeSeq) {
if (t instanceof TextValue textValue) {
if (hasType(relationship, textValue, relCursor, queryContext)) {
return true;
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasAnyDynamicType()': Expected %s to be a string, but it was a `%s`",
t, t.getTypeName()));
}
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasAnyDynamicType()': Expected %s to be a string or list of strings, but it was a `%s`",
typ, typ.getTypeName()));
}
}
} else {
throw new CypherTypeException(format(
"Invalid input for function 'hasAnyDynamicType()': Expected %s to be a relationship, but it was a `%s`",
entity, entity.getTypeName()));
}
return false;
}
public static AnyValue nodes(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof PathValue) {
return VirtualValues.list(((PathValue) in).nodes());
} else if (in instanceof VirtualPathValue) {
long[] ids = ((VirtualPathValue) in).nodeIds();
ListValueBuilder builder = ListValueBuilder.newListBuilder(ids.length);
for (long id : ids) {
builder.add(VirtualValues.node(id));
}
return builder.build();
} else {
throw new CypherTypeException(format("Invalid input for function 'nodes()': Expected %s to be a path", in));
}
}
public static AnyValue relationships(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof VirtualPathValue path) {
return path.relationshipsAsList();
} else {
throw new CypherTypeException(
format("Invalid input for function 'relationships()': Expected %s to be a path", in));
}
}
public static Value point(AnyValue in, DbAccess access, ExpressionCursors cursors) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof VirtualNodeValue node) {
return asPoint(access, node, cursors.nodeCursor(), cursors.propertyCursor());
} else if (in instanceof VirtualRelationshipValue rel) {
return asPoint(access, rel, cursors.relationshipScanCursor(), cursors.propertyCursor());
} else if (in instanceof MapValue map) {
if (containsNull(map)) {
return NO_VALUE;
}
return PointValue.fromMap(map);
} else {
throw new CypherTypeException(
format("Invalid input for function 'point()': Expected a map but got %s", in));
}
}
public static AnyValue keys(
AnyValue in,
DbAccess access,
NodeCursor nodeCursor,
RelationshipScanCursor relationshipScanCursor,
PropertyCursor propertyCursor) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof VirtualNodeValue node) {
return extractKeys(access, access.nodePropertyIds(node.id(), nodeCursor, propertyCursor));
} else if (in instanceof VirtualRelationshipValue rel) {
return extractKeys(access, access.relationshipPropertyIds(rel, relationshipScanCursor, propertyCursor));
} else if (in instanceof MapValue) {
return ((MapValue) in).keys();
} else {
throw new CypherTypeException(format(
"Invalid input for function 'keys()': Expected a node, a relationship or a literal map but got %s",
in));
}
}
public static AnyValue properties(
AnyValue in,
DbAccess access,
NodeCursor nodeCursor,
RelationshipScanCursor relationshipCursor,
PropertyCursor propertyCursor) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof VirtualNodeValue node) {
return access.nodeAsMap(
node.id(), nodeCursor, propertyCursor, new MapValueBuilder(), IntSets.immutable.empty());
} else if (in instanceof VirtualRelationshipValue rel) {
return access.relationshipAsMap(
rel, relationshipCursor, propertyCursor, new MapValueBuilder(), IntSets.immutable.empty());
} else if (in instanceof MapValue) {
return in;
} else {
throw new CypherTypeException(format(
"Invalid input for function 'properties()': Expected a node, a relationship or a literal map but got %s",
in));
}
}
public static AnyValue properties(
AnyValue in,
DbAccess access,
NodeCursor nodeCursor,
RelationshipScanCursor relationshipCursor,
PropertyCursor propertyCursor,
MapValueBuilder alreadyReadProperties,
IntSet alreadyReadPropertyTokens) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof VirtualNodeValue node) {
return access.nodeAsMap(
node.id(), nodeCursor, propertyCursor, alreadyReadProperties, alreadyReadPropertyTokens);
} else if (in instanceof VirtualRelationshipValue rel) {
return access.relationshipAsMap(
rel.id(), relationshipCursor, propertyCursor, alreadyReadProperties, alreadyReadPropertyTokens);
} else if (in instanceof MapValue) {
return in;
} else {
throw new CypherTypeException(format(
"Invalid input for function 'properties()': Expected a node, a relationship or a literal map but got %s",
in));
}
}
public static AnyValue characterLength(AnyValue item) {
if (item == NO_VALUE) {
return NO_VALUE;
} else if (item instanceof TextValue) {
return size(item);
} else {
throw new CypherTypeException(
"Invalid input for function 'character_length()': Expected a String, got: " + item);
}
}
public static AnyValue size(AnyValue item) {
if (item == NO_VALUE) {
return NO_VALUE;
} else if (item instanceof TextValue) {
return longValue(((TextValue) item).length());
} else if (item instanceof SequenceValue) {
return longValue(((SequenceValue) item).actualSize());
} else {
throw new CypherTypeException(
"Invalid input for function 'size()': Expected a String or List, got: " + item);
}
}
public static AnyValue isEmpty(AnyValue item) {
if (item == NO_VALUE) {
return NO_VALUE;
} else if (item instanceof SequenceValue) {
return Values.booleanValue(((SequenceValue) item).isEmpty());
} else if (item instanceof MapValue) {
return Values.booleanValue(((MapValue) item).isEmpty());
} else if (item instanceof TextValue) {
return Values.booleanValue(((TextValue) item).isEmpty());
} else {
throw new CypherTypeException(
"Invalid input for function 'isEmpty()': Expected a List, Map, or String, got: " + item);
}
}
public static AnyValue length(AnyValue item) {
if (item == NO_VALUE) {
return NO_VALUE;
} else if (item instanceof VirtualPathValue) {
return longValue(((VirtualPathValue) item).size());
} else {
throw new CypherTypeException("Invalid input for function 'length()': Expected a Path, got: " + item);
}
}
public static Value toBoolean(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof BooleanValue) {
return (BooleanValue) in;
} else if (in instanceof TextValue) {
return switch (((TextValue) in).trim().stringValue().toLowerCase(Locale.ROOT)) {
case "true" -> TRUE;
case "false" -> FALSE;
default -> NO_VALUE;
};
} else if (in instanceof IntegralValue integer) {
return integer.longValue() == 0L ? FALSE : TRUE;
} else {
throw new CypherTypeException(
"Invalid input for function 'toBoolean()': Expected a Boolean, Integer or String, got: " + in);
}
}
public static Value toBooleanOrNull(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof BooleanValue || in instanceof TextValue || in instanceof IntegralValue) {
return toBoolean(in);
} else {
return NO_VALUE;
}
}
public static AnyValue toBooleanList(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof SequenceValue sv) {
return StreamSupport.stream(sv.spliterator(), false)
.map(entry -> entry == NO_VALUE ? NO_VALUE : toBooleanOrNull(entry))
.collect(ListValueBuilder.collector());
} else {
throw new CypherTypeException(
String.format("Invalid input for function 'toBooleanList()': Expected a List, got: %s", in));
}
}
public static Value toFloat(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof DoubleValue) {
return (DoubleValue) in;
} else if (in instanceof NumberValue number) {
return doubleValue(number.doubleValue());
} else if (in instanceof TextValue) {
try {
return doubleValue(parseDouble(((TextValue) in).stringValue()));
} catch (NumberFormatException ignore) {
return NO_VALUE;
}
} else {
throw new CypherTypeException(
"Invalid input for function 'toFloat()': Expected a String, Float or Integer, got: " + in);
}
}
public static Value toFloatOrNull(AnyValue in) {
if (in instanceof NumberValue || in instanceof TextValue) {
return toFloat(in);
} else {
return NO_VALUE;
}
}
public static AnyValue toFloatList(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof SequenceValue sv) {
return StreamSupport.stream(sv.spliterator(), false)
.map(entry -> entry == NO_VALUE ? NO_VALUE : toFloatOrNull(entry))
.collect(ListValueBuilder.collector());
} else {
throw new CypherTypeException(
String.format("Invalid input for function 'toFloatList()': Expected a List, got: %s", in));
}
}
public static Value toInteger(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof IntegralValue) {
return (IntegralValue) in;
} else if (in instanceof NumberValue number) {
return longValue(number.longValue());
} else if (in instanceof TextValue) {
return stringToLongValue((TextValue) in);
} else if (in instanceof BooleanValue) {
if (((BooleanValue) in).booleanValue()) {
return longValue(1L);
} else {
return longValue(0L);
}
} else {
throw new CypherTypeException(
"Invalid input for function 'toInteger()': Expected a String, Float, Integer or Boolean, got: "
+ in);
}
}
public static Value toIntegerOrNull(AnyValue in) {
if (in instanceof NumberValue || in instanceof BooleanValue) {
return toInteger(in);
} else if (in instanceof TextValue) {
try {
return stringToLongValue((TextValue) in);
} catch (CypherTypeException e) {
return NO_VALUE;
}
} else {
return NO_VALUE;
}
}
public static AnyValue toIntegerList(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof IntegralArray array) {
return VirtualValues.fromArray(array);
} else if (in instanceof FloatingPointArray array) {
return toIntegerList(array);
} else if (in instanceof SequenceValue sequence) {
return toIntegerList(sequence);
} else {
throw new CypherTypeException(
String.format("Invalid input for function 'toIntegerList()': Expected a List, got: %s", in));
}
}
public static Value toString(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof TextValue text) {
return text;
} else if (in instanceof NumberValue number) {
return stringValue(number.prettyPrint());
} else if (in instanceof BooleanValue b) {
return stringValue(b.prettyPrint());
} else if (in instanceof TemporalValue || in instanceof DurationValue || in instanceof PointValue) {
return stringValue(in.toString());
} else {
throw new CypherTypeException(
"Invalid input for function 'toString()': Expected a String, Float, Integer, Boolean, Temporal or Duration, got: "
+ in);
}
}
public static AnyValue toStringOrNull(AnyValue in) {
if (in instanceof TextValue
|| in instanceof NumberValue
|| in instanceof BooleanValue
|| in instanceof TemporalValue
|| in instanceof DurationValue
|| in instanceof PointValue) {
return toString(in);
} else {
return NO_VALUE;
}
}
public static AnyValue toStringList(AnyValue in) {
if (in == NO_VALUE) {
return NO_VALUE;
} else if (in instanceof SequenceValue sv) {
return StreamSupport.stream(sv.spliterator(), false)
.map(entry -> entry == NO_VALUE ? NO_VALUE : toStringOrNull(entry))
.collect(ListValueBuilder.collector());
} else {
throw new CypherTypeException(
String.format("Invalid input for function 'toStringList()': Expected a List, got: %s", in));
}
}
public static AnyValue fromSlice(AnyValue collection, AnyValue fromValue) {
if (collection == NO_VALUE || fromValue == NO_VALUE) {
return NO_VALUE;
}
int from = asIntExact(fromValue);
ListValue list = asList(collection);
if (from >= 0) {
return list.drop(from);
} else {
return list.drop(list.actualSize() + from);
}
}
public static AnyValue toSlice(AnyValue collection, AnyValue toValue) {
if (collection == NO_VALUE || toValue == NO_VALUE) {
return NO_VALUE;
}
int from = asIntExact(toValue);
ListValue list = asList(collection);
if (from >= 0) {
return list.take(from);
} else {
return list.take(list.intSize() + from);
}
}
public static AnyValue fullSlice(AnyValue collection, AnyValue fromValue, AnyValue toValue) {
if (collection == NO_VALUE || fromValue == NO_VALUE || toValue == NO_VALUE) {
return NO_VALUE;
}
int from = asIntExact(fromValue);
int to = asIntExact(toValue);
ListValue list = asList(collection);
int size = list.intSize();
if (from >= 0 && to >= 0) {
return list.slice(from, to);
} else if (from >= 0) {
return list.slice(from, size + to);
} else if (to >= 0) {
return list.slice(size + from, to);
} else {
return list.slice(size + from, size + to);
}
}
@CalledFromGeneratedCode
public static TextValue asTextValue(AnyValue value) {
return asTextValue(value, null);
}
public static TextValue asTextValue(AnyValue value, Supplier contextForErrorMessage) {
if (!(value instanceof TextValue)) {
String errorMessage;
if (contextForErrorMessage == null) {
errorMessage = format(
"Expected %s to be a %s, but it was a %s",
value, TextValue.class.getName(), value.getClass().getName());
} else {
errorMessage = format(
"%s: Expected %s to be a %s, but it was a %s",
contextForErrorMessage.get(),
value,
TextValue.class.getName(),
value.getClass().getName());
}
if (value instanceof Value v)
throw CypherTypeException.expectedString(
errorMessage, v.prettyPrint(), CypherTypeValueMapper.valueType(v));
else
throw CypherTypeException.expectedString(
errorMessage, String.valueOf(value), CypherTypeValueMapper.valueType(value));
}
return (TextValue) value;
}
private static Value stringToLongValue(TextValue in) {
try {
return longValue(parseLong(in.stringValue()));
} catch (Exception e) {
try {
BigDecimal bigDecimal = new BigDecimal(in.stringValue());
if (bigDecimal.compareTo(MAX_LONG) <= 0 && bigDecimal.compareTo(MIN_LONG) >= 0) {
return longValue(bigDecimal.longValue());
} else {
throw new CypherTypeException(format("integer, %s, is too large", in.stringValue()));
}
} catch (NumberFormatException ignore) {
return NO_VALUE;
}
}
}
private static ListValue extractKeys(DbAccess access, int[] keyIds) {
String[] keysNames = new String[keyIds.length];
for (int i = 0; i < keyIds.length; i++) {
keysNames[i] = access.getPropertyKeyName(keyIds[i]);
}
return VirtualValues.fromArray(Values.stringArray(keysNames));
}
private static Value asPoint(
DbAccess access, VirtualNodeValue nodeValue, NodeCursor nodeCursor, PropertyCursor propertyCursor) {
MapValueBuilder builder = new MapValueBuilder();
for (String key : POINT_KEYS) {
Value value =
access.nodeProperty(nodeValue.id(), access.propertyKey(key), nodeCursor, propertyCursor, true);
if (value == NO_VALUE) {
continue;
}
builder.add(key, value);
}
return PointValue.fromMap(builder.build());
}
private static Value asPoint(
DbAccess access,
VirtualRelationshipValue relationshipValue,
RelationshipScanCursor relationshipScanCursor,
PropertyCursor propertyCursor) {
MapValueBuilder builder = new MapValueBuilder();
for (String key : POINT_KEYS) {
Value value = access.relationshipProperty(
relationshipValue, access.propertyKey(key), relationshipScanCursor, propertyCursor, true);
if (value == NO_VALUE) {
continue;
}
builder.add(key, value);
}
return PointValue.fromMap(builder.build());
}
private static boolean containsNull(MapValue map) {
boolean[] hasNull = {false};
map.foreach((s, value) -> {
if (value == NO_VALUE) {
hasNull[0] = true;
}
});
return hasNull[0];
}
private static AnyValue listAccess(SequenceValue container, AnyValue index) {
NumberValue number = asNumberValue(
index,
() -> "Cannot access a list '" + container.toString() + "' using a non-number index, got "
+ index.toString());
if (!(number instanceof IntegralValue)) {
throw CypherTypeException.nonIntegerListIndex(
String.valueOf(number), number.prettyPrint(), CypherTypeValueMapper.valueType(number));
}
long idx = number.longValue();
if (idx < 0) {
idx = container.actualSize() + idx;
}
if (idx >= container.actualSize() || idx < 0) {
return NO_VALUE;
}
return container.value(idx);
}
private static int propertyKeyId(DbAccess dbAccess, AnyValue index) {
return dbAccess.propertyKey(asString(
index,
() ->
// this string assumes that the asString method fails and gives context which operation went
// wrong
"Cannot use a property key with non string name. It was " + index.toString()));
}
private static AnyValue mapAccess(MapValue container, AnyValue index) {
return container.get(asString(
index,
() ->
// this string assumes that the asString method fails and gives context which operation went
// wrong
"Cannot access a map '" + container.toString() + "' by key '" + index.toString() + "'"));
}
public static String asString(AnyValue value) {
return asTextValue(value).stringValue();
}
public static List asStringList(AnyValue value) {
if (value instanceof TextValue text) {
return Collections.singletonList(text.stringValue());
} else if (value instanceof SequenceValue sequenceValue) {
List result = new ArrayList<>();
sequenceValue.forEach(t -> result.add(asTextValue(t).stringValue()));
return result;
} else {
throw new CypherTypeException(String.format(
"Expected %s to be a %s or a %s, but it was a %s",
value, TextValue.class.getName(), SequenceValue.class.getName(), value.getTypeName()));
}
}
private static String asString(AnyValue value, Supplier contextForErrorMessage) {
return asTextValue(value, contextForErrorMessage).stringValue();
}
private static NumberValue asNumberValue(AnyValue value, Supplier contextForErrorMessage) {
if (!(value instanceof NumberValue)) {
var msg = format(
"%s: Expected %s to be a %s, but it was a %s",
contextForErrorMessage.get(),
value,
NumberValue.class.getName(),
value.getClass().getName());
if (value instanceof Value v)
throw CypherTypeException.expectedNumber(msg, v.prettyPrint(), CypherTypeValueMapper.valueType(v));
else
throw CypherTypeException.expectedNumber(
msg, String.valueOf(value), CypherTypeValueMapper.valueType(value));
}
return (NumberValue) value;
}
private static Value calculateDistance(PointValue p1, PointValue p2) {
if (p1.getCoordinateReferenceSystem().equals(p2.getCoordinateReferenceSystem())) {
return doubleValue(p1.getCoordinateReferenceSystem().getCalculator().distance(p1, p2));
} else {
return NO_VALUE;
}
}
private static long asLong(AnyValue value, Supplier contextForErrorMessage) {
if (value instanceof NumberValue) {
return ((NumberValue) value).longValue();
} else {
String errorMsg;
if (contextForErrorMessage == null) {
errorMsg = "Expected a numeric value but got: " + value;
} else {
errorMsg = contextForErrorMessage.get() + ": Expected a numeric value but got: " + value;
}
if (value instanceof Value v)
throw CypherTypeException.expectedNumber(errorMsg, v.prettyPrint(), CypherTypeValueMapper.valueType(v));
else
throw CypherTypeException.expectedNumber(
errorMsg, String.valueOf(value), CypherTypeValueMapper.valueType(value));
}
}
public static int asIntExact(AnyValue value) {
return asIntExact(value, null);
}
public static int asIntExact(AnyValue value, Supplier contextForErrorMessage) {
final long longValue = asLong(value, contextForErrorMessage);
final int intValue = (int) longValue;
if (intValue != longValue) {
String errorMsg = format(
"Expected an integer between %d and %d, but got: %d",
Integer.MIN_VALUE, Integer.MAX_VALUE, longValue);
if (contextForErrorMessage != null) {
errorMsg = contextForErrorMessage.get() + ": " + errorMsg;
}
throw new IllegalArgumentException(errorMsg);
}
return intValue;
}
public static long nodeId(AnyValue value) {
assert value != NO_VALUE : "NO_VALUE checks need to happen outside this call";
if (value instanceof VirtualNodeValue) {
return ((VirtualNodeValue) value).id();
} else {
if (value instanceof Value v)
throw CypherTypeException.expectedVirtualNode(
v.prettyPrint(), value.getClass().getName(), CypherTypeValueMapper.valueType(value));
else
throw CypherTypeException.expectedVirtualNode(
String.valueOf(value), value.getClass().getName(), CypherTypeValueMapper.valueType(value));
}
}
public static AnyValue isNormalized(AnyValue input, NormalForm normalForm) {
if (input == NO_VALUE) {
return NO_VALUE;
}
if (input instanceof TextValue asText) {
Normalizer.Form form;
try {
form = Normalizer.Form.valueOf(normalForm.description());
} catch (IllegalArgumentException e) {
throw InvalidArgumentException.unknownNormalForm(String.valueOf(normalForm));
}
boolean normalized = Normalizer.isNormalized(asText.stringValue(), form);
return Values.booleanValue(normalized);
} else {
return NO_VALUE;
}
}
public static BooleanValue isTyped(AnyValue item, CypherType typeName) {
boolean result;
if (typeName instanceof NothingType) {
result = false;
} else if (item instanceof NoValue) {
result = typeName instanceof NullType || typeName.isNullable();
} else if (typeName instanceof NullType) {
result = false;
} else if (typeName instanceof AnyType) {
result = true;
} else if (typeName instanceof ListType listType) {
result = (item instanceof SequenceValue list) && checkInnerListIsTyped(list, listType);
} else if (typeName.hasValueRepresentation()) {
result = possibleValueRepresentations(typeName).contains(item.valueRepresentation());
} else if (typeName instanceof NodeType) {
result = item instanceof VirtualNodeValue;
} else if (typeName instanceof RelationshipType) {
result = item instanceof VirtualRelationshipValue;
} else if (typeName instanceof MapType) {
result = item instanceof MapValue;
} else if (typeName instanceof PathType) {
result = item instanceof VirtualPathValue;
} else if (typeName instanceof PropertyValueType) {
result = hasPropertyValueRepresentation(item.valueRepresentation())
|| (item instanceof ListValue listValue
&& (listValue.isEmpty()
|| hasPropertyValueRepresentation(listValue.itemValueRepresentation())));
} else if (typeName instanceof ClosedDynamicUnionType unionType) {
result = false;
for (CypherType innerType : asJava(unionType.innerTypes())) {
if (isTyped(item, innerType) == TRUE) {
result = true;
break;
}
}
} else {
throw new IllegalArgumentException(String.format("Unexpected type: %s", typeName.toCypherTypeString()));
}
return Values.booleanValue(result);
}
public static final CypherTypeValueMapper CYPHER_TYPE_NAME_VALUE_MAPPER = new CypherTypeValueMapper();
public static Value valueType(AnyValue in) {
return Values.stringValue(
CypherType.normalizeTypes(in.map(CYPHER_TYPE_NAME_VALUE_MAPPER)).description());
}
private static boolean hasPropertyValueRepresentation(ValueRepresentation valueRepresentation) {
return !valueRepresentation.equals(ValueRepresentation.ANYTHING)
&& !valueRepresentation.equals(ValueRepresentation.UNKNOWN)
&& !valueRepresentation.equals(ValueRepresentation.NO_VALUE);
}
private static List possibleValueRepresentations(CypherType cypherType)
throws UnsupportedOperationException {
if (cypherType instanceof BooleanType) {
return List.of(ValueRepresentation.BOOLEAN);
} else if (cypherType instanceof StringType) {
return List.of(ValueRepresentation.UTF8_TEXT, ValueRepresentation.UTF16_TEXT);
} else if (cypherType instanceof IntegerType) {
return List.of(
ValueRepresentation.INT8,
ValueRepresentation.INT16,
ValueRepresentation.INT32,
ValueRepresentation.INT64);
} else if (cypherType instanceof FloatType) {
return List.of(ValueRepresentation.FLOAT32, ValueRepresentation.FLOAT64);
} else if (cypherType instanceof NumberType) {
return List.of(
ValueRepresentation.INT8,
ValueRepresentation.INT16,
ValueRepresentation.INT32,
ValueRepresentation.INT64,
ValueRepresentation.FLOAT32,
ValueRepresentation.FLOAT64);
} else if (cypherType instanceof DateType) {
return List.of(ValueRepresentation.DATE);
} else if (cypherType instanceof LocalTimeType) {
return List.of(ValueRepresentation.LOCAL_TIME);
} else if (cypherType instanceof ZonedTimeType) {
return List.of(ValueRepresentation.ZONED_TIME);
} else if (cypherType instanceof LocalDateTimeType) {
return List.of(ValueRepresentation.LOCAL_DATE_TIME);
} else if (cypherType instanceof ZonedDateTimeType) {
return List.of(ValueRepresentation.ZONED_DATE_TIME);
} else if (cypherType instanceof DurationType) {
return List.of(ValueRepresentation.DURATION);
} else if (cypherType instanceof GeometryType || cypherType instanceof PointType) {
return List.of(ValueRepresentation.GEOMETRY);
} else if (cypherType instanceof ListType listType) {
if (listType.innerType() instanceof BooleanType) {
return List.of(ValueRepresentation.BOOLEAN_ARRAY);
} else if (listType.innerType() instanceof StringType) {
return List.of(ValueRepresentation.TEXT_ARRAY);
} else if (listType.innerType() instanceof IntegerType) {
return List.of(
ValueRepresentation.INT8_ARRAY,
ValueRepresentation.INT16_ARRAY,
ValueRepresentation.INT32_ARRAY,
ValueRepresentation.INT64_ARRAY);
} else if (listType.innerType() instanceof FloatType) {
return List.of(ValueRepresentation.FLOAT32_ARRAY, ValueRepresentation.FLOAT64_ARRAY);
} else if (listType.innerType() instanceof NumberType) {
return List.of(
ValueRepresentation.INT8_ARRAY,
ValueRepresentation.INT16_ARRAY,
ValueRepresentation.INT32_ARRAY,
ValueRepresentation.INT64_ARRAY,
ValueRepresentation.FLOAT32_ARRAY,
ValueRepresentation.FLOAT64_ARRAY);
} else if (listType.innerType() instanceof DateType) {
return List.of(ValueRepresentation.DATE_ARRAY);
} else if (listType.innerType() instanceof LocalTimeType) {
return List.of(ValueRepresentation.LOCAL_TIME_ARRAY);
} else if (listType.innerType() instanceof ZonedTimeType) {
return List.of(ValueRepresentation.ZONED_TIME_ARRAY);
} else if (listType.innerType() instanceof LocalDateTimeType) {
return List.of(ValueRepresentation.LOCAL_DATE_TIME_ARRAY);
} else if (listType.innerType() instanceof ZonedDateTimeType) {
return List.of(ValueRepresentation.ZONED_DATE_TIME_ARRAY);
} else if (listType.innerType() instanceof DurationType) {
return List.of(ValueRepresentation.DURATION_ARRAY);
} else if (listType.innerType() instanceof PointType || listType.innerType() instanceof GeometryType) {
return List.of(ValueRepresentation.GEOMETRY_ARRAY);
} else {
return List.of();
}
} else {
throw new UnsupportedOperationException(String.format(
"possibleValueRepresentations not supported on %s",
cypherType.getClass().getName()));
}
}
private static boolean checkInnerListIsTyped(SequenceValue values, ListType typeName) {
final var itemType = typeName.innerType();
// An empty list can be a list of anything, even NOTHING, so don't check further
// A list of LIST can also be anything, so no need to check further
if (values.isEmpty() || (!itemType.isNullable() && itemType instanceof AnyType)) return true;
// A non-empty list of NOTHING is always false
if (itemType instanceof NothingType) return false;
if (values instanceof ArrayValue array) {
// An ArrayValue can only hold storable types (not null)
// So a LIST, LIST are true
// else check that the specific array type matches
return itemType instanceof AnyType
|| itemType instanceof PropertyValueType
|| (typeName.hasValueRepresentation()
&& possibleValueRepresentations(typeName).contains(array.valueRepresentation()));
} else if (values instanceof ListValue list) {
// For a simple LIST we can quickly check the list type
// without needing to iterate over the list
// Lists that are mixed ints and floats will return as a list of float here, so don't allow the shortcut for
// that
if (itemType.hasValueRepresentation()
&& !itemType.isNullable()
&& list.itemValueRepresentation().valueGroup() != ValueGroup.NUMBER
&& possibleValueRepresentations(itemType).contains(list.itemValueRepresentation())) {
return true;
} else {
// The list is either mixed, or may contain nulls, must check all values
for (AnyValue value : values) {
if (isTyped(value, itemType) == FALSE) {
return false;
}
}
}
}
return true;
}
public static AnyValue assertIsNode(AnyValue item) {
if (item == NO_VALUE) {
return NO_VALUE;
} else if (item instanceof VirtualNodeValue) {
return TRUE;
} else {
if (item instanceof Value v)
throw CypherTypeException.expectedNode(
String.valueOf(v), v.prettyPrint(), CypherTypeValueMapper.valueType(v));
else
throw CypherTypeException.expectedNode(
String.valueOf(item), String.valueOf(item), CypherTypeValueMapper.valueType(item));
}
}
private static CypherTypeException needsNumbers(String method) {
return new CypherTypeException(format("%s requires numbers", method));
}
private static CypherTypeException notAString(String method, AnyValue in) {
return new CypherTypeException(format(
"Expected a string value for `%s`, but got: %s; consider converting it to a string with "
+ "toString().",
method, in));
}
private static CypherTypeException notAModeString(String method, AnyValue mode) {
return new CypherTypeException(format("Expected a string value for `%s`, but got: %s.", method, mode));
}
private static ListValue toIntegerList(FloatingPointArray array) {
var converted = ListValueBuilder.newListBuilder(array.intSize());
for (int i = 0; i < array.intSize(); i++) {
converted.add(longValue((long) array.doubleValue(i)));
}
return converted.build();
}
private static ListValue toIntegerList(SequenceValue sequenceValue) {
var converted = ListValueBuilder.newListBuilder();
for (AnyValue value : sequenceValue) {
converted.add(value != NO_VALUE ? toIntegerOrNull(value) : NO_VALUE);
}
return converted.build();
}
private static Consumer consumer(DbAccess access, RelationshipScanCursor cursor) {
return relationshipVisitor -> {
access.singleRelationship(relationshipVisitor.id(), cursor);
if (cursor.next()) {
relationshipVisitor.visit(cursor.sourceNodeReference(), cursor.targetNodeReference(), cursor.type());
}
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy