Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -22,9 +22,14 @@
import com.arcadedb.exception.CommandParsingException;
import com.arcadedb.query.opencypher.ast.*;
import com.arcadedb.query.opencypher.grammar.Cypher25Parser;
import com.arcadedb.query.opencypher.temporal.CypherDate;
import com.arcadedb.query.opencypher.temporal.CypherLocalDateTime;
import com.arcadedb.query.opencypher.temporal.CypherTemporalValue;
import com.arcadedb.query.sql.executor.CommandContext;
import com.arcadedb.query.sql.executor.Result;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;

Expand Down Expand Up @@ -1662,6 +1667,17 @@ public Object evaluate(final Result result, final CommandContext context) {
return ((Result) baseValue).getProperty(propertyName);
}

// Handle temporal types (e.g., date().year, datetime().hour)
if (baseValue instanceof CypherTemporalValue) {
return ((CypherTemporalValue) baseValue).getTemporalProperty(propertyName);
}
if (baseValue instanceof LocalDate) {
return new CypherDate((LocalDate) baseValue).getTemporalProperty(propertyName);
}
if (baseValue instanceof LocalDateTime) {
return new CypherLocalDateTime((LocalDateTime) baseValue).getTemporalProperty(propertyName);
}
Comment on lines +1671 to +1679

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 temporal type checks can be simplified by using a single instanceof check or a pattern matching switch if the Java version allows, but at minimum, consider consolidating the logic to avoid repeated getTemporalProperty calls if possible, or ensure that the CypherTemporalValue interface is fully utilized to avoid explicit LocalDate and LocalDateTime checks here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The three instanceof checks are necessary because LocalDate and LocalDateTime are java.time types that do not implement CypherTemporalValue - they need to be wrapped in CypherDate/CypherLocalDateTime before the property can be resolved. This is the same pattern used in PropertyAccessExpression.evaluate() for the same reason.

Consolidating into a single check would require either making java.time types implement CypherTemporalValue (a larger structural change) or adding a wrapper method - both are beyond the scope of this targeted fix. The duplication between the two expression classes is noted in the Claude bot review as a future improvement.


return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;
import java.time.ZonedDateTime;

import static org.assertj.core.api.Assertions.assertThat;
import org.assertj.core.api.Assertions;

Expand Down Expand Up @@ -1102,4 +1105,91 @@ WITH datetime('2015-07-21T21:40:32.142+0100') AS dt \
final String formatted = (String) result.next().getProperty("result");
assertThat(formatted).isEqualTo("2015-07-21");
}

// ==================== Temporal component accessor tests (issue #4018) ====================

@Test
void dateYearComponentFromLiteral() {
final ResultSet result = database.command("opencypher", "RETURN date('2020-01-15').year AS y");
assertThat(result.hasNext()).isTrue();
assertThat(((Number) result.next().getProperty("y")).longValue()).isEqualTo(2020L);
}

@Test
void dateMonthComponentFromLiteral() {
final ResultSet result = database.command("opencypher", "RETURN date('2020-06-30').month AS m");
assertThat(result.hasNext()).isTrue();
assertThat(((Number) result.next().getProperty("m")).longValue()).isEqualTo(6L);
}

@Test
void dateDayComponentFromLiteral() {
final ResultSet result = database.command("opencypher", "RETURN date('2020-06-30').day AS d");
assertThat(result.hasNext()).isTrue();
assertThat(((Number) result.next().getProperty("d")).longValue()).isEqualTo(30L);
}

@Test
void dateComponentsFromCurrentDate() {
final LocalDate today = LocalDate.now();
final ResultSet result = database.command("opencypher", "RETURN date().year AS y, date().month AS m, date().day AS d");
assertThat(result.hasNext()).isTrue();
final var row = result.next();
assertThat(((Number) row.getProperty("y")).longValue()).isEqualTo((long) today.getYear());
assertThat(((Number) row.getProperty("m")).longValue()).isEqualTo((long) today.getMonthValue());
assertThat(((Number) row.getProperty("d")).longValue()).isEqualTo((long) today.getDayOfMonth());
}

@Test
void datetimeYearComponentFromLiteral() {
final ResultSet result = database.command("opencypher", "RETURN datetime('2021-03-14T15:09:26Z').year AS y");
assertThat(result.hasNext()).isTrue();
assertThat(((Number) result.next().getProperty("y")).longValue()).isEqualTo(2021L);
}

@Test
void datetimeHourComponentFromLiteral() {
final ResultSet result = database.command("opencypher", "RETURN datetime('2021-03-14T15:09:26Z').hour AS h");
assertThat(result.hasNext()).isTrue();
assertThat(((Number) result.next().getProperty("h")).longValue()).isEqualTo(15L);
}

@Test
void datetimeYearFromCurrentDatetime() {
final ZonedDateTime now = ZonedDateTime.now();
final ResultSet result = database.command("opencypher", "RETURN datetime().year AS y");
assertThat(result.hasNext()).isTrue();
assertThat(((Number) result.next().getProperty("y")).longValue()).isEqualTo((long) now.getYear());
}

@Test
void localdatetimeComponentsFromLiteral() {
final ResultSet result = database.command("opencypher",
"RETURN localdatetime('2022-11-05T08:30:00').year AS y, localdatetime('2022-11-05T08:30:00').month AS m, localdatetime('2022-11-05T08:30:00').hour AS h");
assertThat(result.hasNext()).isTrue();
final var row = result.next();
assertThat(((Number) row.getProperty("y")).longValue()).isEqualTo(2022L);
assertThat(((Number) row.getProperty("m")).longValue()).isEqualTo(11L);
assertThat(((Number) row.getProperty("h")).longValue()).isEqualTo(8L);
}

@Test
void localtimeComponentsFromLiteral() {
final ResultSet result = database.command("opencypher",
"RETURN localtime('10:35:00').hour AS h, localtime('10:35:00').minute AS m");
assertThat(result.hasNext()).isTrue();
final var row = result.next();
assertThat(((Number) row.getProperty("h")).longValue()).isEqualTo(10L);
assertThat(((Number) row.getProperty("m")).longValue()).isEqualTo(35L);
}

@Test
void timeComponentsFromLiteral() {
final ResultSet result = database.command("opencypher",
"RETURN time('12:00:00+01:00').hour AS h, time('12:00:00+01:00').minute AS m");
assertThat(result.hasNext()).isTrue();
final var row = result.next();
assertThat(((Number) row.getProperty("h")).longValue()).isEqualTo(12L);
assertThat(((Number) row.getProperty("m")).longValue()).isEqualTo(0L);
}
}
Loading