Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
f363428
added neo4j functions tests
Feb 15, 2026
2376022
fixed factory
Feb 15, 2026
cdec6bb
Merge branch 'ArcadeData:main' into more_tests
ExtReMLapin Feb 15, 2026
44d0c39
Merge branch 'ArcadeData:main' into more_tests
ExtReMLapin Feb 15, 2026
be84308
added elementid() (uses ID())
Feb 15, 2026
1ba12c7
Revert "added elementid() (uses ID())"
Feb 15, 2026
1d0342e
Merge branch 'ArcadeData:main' into more_tests
ExtReMLapin Feb 15, 2026
6488c38
fixed some tests (expect 64bit, not 32)
Feb 15, 2026
e8375db
more 64bit fixes
Feb 15, 2026
a41ff64
fixed some errors
Feb 15, 2026
ca38e62
more errors fixes
Feb 15, 2026
703eba0
Merge branch 'ArcadeData:main' into more_tests
ExtReMLapin Feb 16, 2026
a8f9dcd
Update engine/src/test/java/com/arcadedb/query/opencypher/functions/O…
ExtReMLapin Feb 16, 2026
ddc6dbf
Update engine/src/test/java/com/arcadedb/query/opencypher/functions/O…
ExtReMLapin Feb 16, 2026
99aa587
Merge branch 'ArcadeData:main' into more_tests
ExtReMLapin Feb 16, 2026
c454c1c
fixed compilation error
Feb 18, 2026
c826fe5
Merge branch 'ArcadeData:main' into more_tests
ExtReMLapin Mar 19, 2026
16af5f2
fixed exception not being detected
Mar 19, 2026
00afff9
fixed missing exceptions
Mar 19, 2026
64e2abd
updated tests
Mar 19, 2026
8280281
updated tests
Mar 20, 2026
3b6fd79
Coll insert/remove force second arg
Mar 20, 2026
6a23fdf
point.distance cypher
Mar 20, 2026
9dff309
cypher point use map like cypher, not like sql
Mar 20, 2026
0762c40
geo utils support map xy or longitude latitude
Mar 20, 2026
91407e5
left support two args error and neg len error
Mar 20, 2026
2c5f0f0
two args and neg len
Mar 20, 2026
6275cb2
force 3 args for Replace function
Mar 20, 2026
69e265d
lTrim support two args
Mar 20, 2026
50ec604
rtrim two args
Mar 20, 2026
28f6a6e
substring better errors handling
Mar 20, 2026
87e67e6
sync
Mar 20, 2026
2f591d6
Round added precision and mode
Mar 20, 2026
3266b9b
added timezones
Mar 20, 2026
7e9e0c6
updated vectors
Mar 20, 2026
ce445f6
sync
Mar 20, 2026
f7b0fb0
SQLFunctionStandardDeviation now returns 0.0 instead of null
Mar 20, 2026
34c5150
Update engine/src/main/java/com/arcadedb/function/geo/CypherPointFunc…
ExtReMLapin Mar 20, 2026
ef55e43
updated old test
Mar 20, 2026
14fa415
Merge branch 'ArcadeData:main' into more_tests
ExtReMLapin Apr 18, 2026
8e79172
sync
Apr 18, 2026
f8f942c
sync
Apr 18, 2026
cbc1491
fixed codacy being a crybaby
Apr 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ public Object execute(final Object[] args, final CommandContext context) {
final List<Object> list = asList(args[0]);
if (list == null)
return null;
if (args[1] == null)
return null;
final int index = ((Number) args[1]).intValue();
if (index < 0)
throw new CommandExecutionException("coll.insert() does not support negative index: " + index);
if (index > list.size())
throw new CommandExecutionException("coll.insert() index " + index + " is out of range for list of size " + list.size());
final List<Object> result = new ArrayList<>(list);
result.add(index, args[2]);
return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,13 @@ public Object execute(final Object[] args, final CommandContext context) {
final List<Object> list = asList(args[0]);
if (list == null)
return null;
if (args[1] == null)
return null;

final int index = ((Number) args[1]).intValue();
if (index < 0 || index >= list.size())
if (index < 0)
throw new CommandExecutionException("coll.remove() does not support negative index: " + index);
if (index >= list.size())
throw new CommandExecutionException("coll.remove() index " + index + " is out of range for list of size " + list.size());
final int count = args.length > 2 ? ((Number) args[2]).intValue() : 1;
final List<Object> result = new ArrayList<>(list);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright © 2021-present Arcade Data Ltd ([email protected])
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-FileCopyrightText: 2021-present Arcade Data Ltd ([email protected])
* SPDX-License-Identifier: Apache-2.0
*/
package com.arcadedb.function.geo;

import com.arcadedb.exception.CommandExecutionException;
import com.arcadedb.function.StatelessFunction;
import com.arcadedb.query.sql.executor.CommandContext;

import java.util.Map;

/**
* Cypher {@code point.distance(point1, point2)} function.
*
* <p>Computes the distance between two points. Uses Haversine formula for WGS-84
* geographic points (result in meters), and Euclidean distance for Cartesian points.</p>
*/
public class CypherPointDistanceFunction implements StatelessFunction {
private static final double EARTH_RADIUS_M = 6371000.0;

@Override
public String getName() {
return "point.distance";
}

@Override
public Object execute(final Object[] args, final CommandContext context) {
if (args == null || args.length != 2)
throw new CommandExecutionException("point.distance() requires exactly 2 arguments");
if (args[0] == null || args[1] == null)
return null;
if (!(args[0] instanceof Map) || !(args[1] instanceof Map))
throw new CommandExecutionException("point.distance() arguments must be point values (maps)");
final Map<?, ?> p1 = (Map<?, ?>) args[0];
final Map<?, ?> p2 = (Map<?, ?>) args[1];

// WGS-84: use Haversine formula
if (p1.containsKey("longitude") && p1.containsKey("latitude") &&
p2.containsKey("longitude") && p2.containsKey("latitude"))
return haversineDistance(
((Number) p1.get("latitude")).doubleValue(), ((Number) p1.get("longitude")).doubleValue(),
((Number) p2.get("latitude")).doubleValue(), ((Number) p2.get("longitude")).doubleValue());

// Cartesian: use Euclidean distance
final Number x1n = (Number) p1.get("x");
final Number y1n = (Number) p1.get("y");
final Number x2n = (Number) p2.get("x");
final Number y2n = (Number) p2.get("y");
if (x1n == null || y1n == null || x2n == null || y2n == null)
return null;
final double dx = x2n.doubleValue() - x1n.doubleValue();
final double dy = y2n.doubleValue() - y1n.doubleValue();
double sumSq = dx * dx + dy * dy;
final Number z1n = (Number) p1.get("z");
final Number z2n = (Number) p2.get("z");
if (z1n != null && z2n != null) {
final double dz = z2n.doubleValue() - z1n.doubleValue();
sumSq += dz * dz;
}
return Math.sqrt(sumSq);
}

private double haversineDistance(final double lat1, final double lon1, final double lat2, final double lon2) {
final double dLat = Math.toRadians(lat2 - lat1);
final double dLon = Math.toRadians(lon2 - lon1);
final double a = Math.pow(Math.sin(dLat / 2), 2)
+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
* Math.pow(Math.sin(dLon / 2), 2);
return 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) * EARTH_RADIUS_M;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,23 @@

import com.arcadedb.exception.CommandExecutionException;
import com.arcadedb.function.StatelessFunction;
import com.arcadedb.function.sql.geo.GeoUtils;
import com.arcadedb.function.sql.geo.LightweightPoint;
import com.arcadedb.query.sql.executor.CommandContext;

import java.util.LinkedHashMap;
import java.util.Map;

/**
* Cypher {@code point(lat, lon)} function.
*
* <p>Constructs a spatial point from latitude and longitude. Following Cypher/Neo4j convention,
* the first argument is latitude and the second is longitude. The point is stored internally
* using the spatial4j convention (x=longitude, y=latitude) so that spatial distance functions
* such as {@code geo.distance} operate correctly.</p>
* Cypher {@code point(map)} function.
*
* <p>Usage: {@code point(<latitude>, <longitude>)}</p>
* <p>Constructs a point from a map of coordinate properties. Supports:</p>
* <ul>
* <li>WGS-84 2D: {@code point({longitude: x, latitude: y})}</li>
* <li>WGS-84 3D: {@code point({longitude: x, latitude: y, height: z})}</li>
* <li>Cartesian 2D: {@code point({x: a, y: b})}</li>
* <li>Cartesian 3D: {@code point({x: a, y: b, z: c})}</li>
* </ul>
* <p>The returned map contains the coordinate keys and a {@code crs} field indicating
* the coordinate reference system.</p>
*/
public class CypherPointFunction implements StatelessFunction {
@Override
Expand All @@ -42,13 +46,84 @@ public String getName() {

@Override
public Object execute(final Object[] args, final CommandContext context) {
if (args == null || args.length < 2)
throw new CommandExecutionException("point() requires latitude and longitude as parameters");
if (args[0] == null || args[1] == null)
if (args == null || args.length == 0 || args.length > 2)
throw new CommandExecutionException("point() requires either one map argument (point({...})) or two numeric arguments (point(latitude, longitude))");

// 2-arg positional form: point(latitude, longitude) → WGS-84 2D
if (args.length == 2) {
if (args[0] == null || args[1] == null)
return null;
if (!(args[0] instanceof Number) || !(args[1] instanceof Number))
throw new CommandExecutionException("point() with two arguments requires numeric latitude and longitude");
final double lat = ((Number) args[0]).doubleValue();
final double lon = ((Number) args[1]).doubleValue();
final Map<String, Object> result = new LinkedHashMap<>();
result.put("latitude", lat);
result.put("longitude", lon);
result.put("x", lon);
result.put("y", lat);
result.put("crs", "WGS-84");
result.put("srid", 4326);
return result;
}

if (args[0] == null)
return null;
final double lat = GeoUtils.getDoubleValue(args[0]);
final double lon = GeoUtils.getDoubleValue(args[1]);
// Store as LightweightPoint(x=longitude, y=latitude) per spatial4j convention
return new LightweightPoint(lon, lat);
if (!(args[0] instanceof Map))
throw new CommandExecutionException("point() argument must be a map with coordinate properties");
final Map<?, ?> map = (Map<?, ?>) args[0];

final Map<String, Object> result = new LinkedHashMap<>();

if (map.containsKey("longitude") || map.containsKey("latitude")) {
// WGS-84 coordinate system
final Object lon = map.get("longitude");
final Object lat = map.get("latitude");
if (lon == null || lat == null)
return null;
final double x = ((Number) lon).doubleValue();
final double y = ((Number) lat).doubleValue();
result.put("longitude", x);
result.put("latitude", y);
result.put("x", x);
result.put("y", y);
addOptionalZ(result, map);
result.put("crs", result.containsKey("z") ? "WGS-84-3D" : "WGS-84");
result.put("srid", result.containsKey("z") ? 4979 : 4326);
} else if (map.containsKey("x") || map.containsKey("y")) {
// Cartesian coordinate system
final Object xv = map.get("x");
final Object yv = map.get("y");
if (xv == null || yv == null)
return null;
final double x = ((Number) xv).doubleValue();
final double y = ((Number) yv).doubleValue();
result.put("x", x);
result.put("y", y);
addOptionalZ(result, map);
final Object crsObj = map.get("crs");
if (crsObj != null)
result.put("crs", crsObj.toString());
else
result.put("crs", result.containsKey("z") ? "cartesian-3D" : "cartesian");
if (map.containsKey("srid"))
result.put("srid", ((Number) map.get("srid")).intValue());
} else {
throw new CommandExecutionException("point() map must contain x/y or longitude/latitude properties");
}

return result;
}

private void addOptionalZ(final Map<String, Object> result, final Map<?, ?> map) {
if (map.containsKey("z")) {
final Object zv = map.get("z");
if (zv != null)
result.put("z", ((Number) zv).doubleValue());
} else if (map.containsKey("height")) {
final Object hv = map.get("height");
if (hv != null)
result.put("z", ((Number) hv).doubleValue());
}
}
}
21 changes: 18 additions & 3 deletions engine/src/main/java/com/arcadedb/function/math/RoundFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public String getName() {

@Override
public Object execute(final Object[] args, final CommandContext context) {
if (args.length < 1 || args.length > 2)
if (args.length < 1 || args.length > 3)
throw new CommandExecutionException("round() requires one or two arguments");
Comment on lines +45 to 46
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error message round() requires one or two arguments is now incorrect, as the function has been updated to support three arguments (value, precision, mode). Please update the message to reflect the new signature.

Suggested change
if (args.length < 1 || args.length > 3)
throw new CommandExecutionException("round() requires one or two arguments");
if (args.length < 1 || args.length > 3)
throw new CommandExecutionException("round() requires one, two, or three arguments");


if (args[0] == null)
Expand All @@ -61,7 +61,7 @@ public Object execute(final Object[] args, final CommandContext context) {
return (double) Math.round(value);
}

// round(value, precision)
// round(value, precision) or round(value, precision, mode)
if (args[1] == null)
return null;

Expand All @@ -70,7 +70,22 @@ public Object execute(final Object[] args, final CommandContext context) {

final int precision = ((Number) args[1]).intValue();

final BigDecimal bd = BigDecimal.valueOf(value).setScale(precision, RoundingMode.HALF_UP);
RoundingMode mode = RoundingMode.HALF_UP;
if (args.length == 3 && args[2] != null) {
final String modeStr = args[2].toString().toUpperCase().replace(" ", "_");
mode = switch (modeStr) {
case "UP" -> RoundingMode.UP;
case "DOWN" -> RoundingMode.DOWN;
case "CEILING" -> RoundingMode.CEILING;
case "FLOOR" -> RoundingMode.FLOOR;
case "HALF_UP" -> RoundingMode.HALF_UP;
case "HALF_DOWN" -> RoundingMode.HALF_DOWN;
case "HALF_EVEN" -> RoundingMode.HALF_EVEN;
default -> throw new CommandExecutionException("round() unknown rounding mode: " + args[2]);
};
}

final BigDecimal bd = BigDecimal.valueOf(value).setScale(precision, mode);
return bd.doubleValue();
}
}
16 changes: 16 additions & 0 deletions engine/src/main/java/com/arcadedb/function/sql/geo/GeoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.locationtech.spatial4j.shape.jts.JtsGeometry;

import java.util.Locale;
import java.util.Map;

/**
* Geospatial utility class.
Expand Down Expand Up @@ -69,6 +70,21 @@ public static Shape parseGeometry(final Object value) {
return null;
if (value instanceof Shape shape)
return shape;
// Cypher point() returns a Map with x/y or longitude/latitude keys
if (value instanceof Map<?, ?> map) {
double x;
double y;
if (map.containsKey("x") && map.containsKey("y")) {
x = ((Number) map.get("x")).doubleValue();
y = ((Number) map.get("y")).doubleValue();
} else if (map.containsKey("longitude") && map.containsKey("latitude")) {
x = ((Number) map.get("longitude")).doubleValue();
y = ((Number) map.get("latitude")).doubleValue();
} else {
throw new IllegalArgumentException("Cannot parse geometry from map: missing x/y or longitude/latitude keys");
}
return SPATIAL_CONTEXT.getShapeFactory().pointXY(x, y);
}
final String wkt = value.toString().trim();
if (wkt.isEmpty())
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ public Object getResult() {
final Object variance = super.getResult();
if (variance != null)
return Math.sqrt((Double) variance);
return null;
return 0.0;
Comment thread
ExtReMLapin marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;

/**
Expand All @@ -48,8 +49,18 @@ public Object execute(final Object[] args, final CommandContext context) {
return CypherFunctionHelper.getStatementTime(context).get("date");
if (args[0] == null)
return null;
if (args[0] instanceof String)
return CypherDate.parse((String) args[0]);
if (args[0] instanceof String) {
final String str = (String) args[0];
try {
return CypherDate.parse(str);
} catch (final Exception e) {
try {
return new CypherDate(LocalDate.now(ZoneId.of(str)));
} catch (final Exception e2) {
throw new CommandExecutionException("date() cannot parse '" + str + "' as a date or timezone");
}
}
}
if (args[0] instanceof Map)
return CypherDate.fromMap((Map<String, Object>) args[0]);
if (args[0] instanceof CypherDate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Map;

/**
Expand All @@ -48,8 +50,18 @@ public Object execute(final Object[] args, final CommandContext context) {
return CypherFunctionHelper.getStatementTime(context).get("datetime");
if (args[0] == null)
return null;
if (args[0] instanceof String)
return CypherDateTime.parse((String) args[0]);
if (args[0] instanceof String) {
final String str = (String) args[0];
try {
return CypherDateTime.parse(str);
} catch (final Exception e) {
try {
return new CypherDateTime(ZonedDateTime.now(ZoneId.of(str)));
} catch (final Exception e2) {
throw new CommandExecutionException("datetime() cannot parse '" + str + "' as a datetime or timezone");
}
}
}
if (args[0] instanceof Map)
return CypherDateTime.fromMap((Map<String, Object>) args[0]);
if (args[0] instanceof CypherDateTime)
Expand Down
Loading
Loading