|
16 | 16 | */
|
17 | 17 | package org.neo4j.driver;
|
18 | 18 |
|
| 19 | +import java.time.LocalDate; |
| 20 | +import java.time.LocalDateTime; |
| 21 | +import java.time.LocalTime; |
| 22 | +import java.time.OffsetDateTime; |
| 23 | +import java.time.OffsetTime; |
| 24 | +import java.time.ZonedDateTime; |
19 | 25 | import java.util.List;
|
| 26 | +import java.util.Map; |
20 | 27 | import org.neo4j.driver.exceptions.ClientException;
|
21 | 28 | import org.neo4j.driver.exceptions.NoSuchRecordException;
|
| 29 | +import org.neo4j.driver.exceptions.value.LossyCoercion; |
| 30 | +import org.neo4j.driver.exceptions.value.Uncoercible; |
| 31 | +import org.neo4j.driver.mapping.Property; |
| 32 | +import org.neo4j.driver.types.IsoDuration; |
22 | 33 | import org.neo4j.driver.types.MapAccessorWithDefaultValue;
|
| 34 | +import org.neo4j.driver.types.Node; |
| 35 | +import org.neo4j.driver.types.Path; |
| 36 | +import org.neo4j.driver.types.Point; |
| 37 | +import org.neo4j.driver.types.Relationship; |
| 38 | +import org.neo4j.driver.types.TypeSystem; |
23 | 39 | import org.neo4j.driver.util.Immutable;
|
24 | 40 | import org.neo4j.driver.util.Pair;
|
| 41 | +import org.neo4j.driver.util.Preview; |
25 | 42 |
|
26 | 43 | /**
|
27 | 44 | * Container for Cypher result values.
|
@@ -78,4 +95,206 @@ public interface Record extends MapAccessorWithDefaultValue {
|
78 | 95 | * @throws NoSuchRecordException if the associated underlying record is not available
|
79 | 96 | */
|
80 | 97 | List<Pair<String, Value>> fields();
|
| 98 | + |
| 99 | + /** |
| 100 | + * Maps this value to the given type providing that is it supported. |
| 101 | + * <p> |
| 102 | + * <b>Basic Mapping</b> |
| 103 | + * <p> |
| 104 | + * Supported destination types depend on value type, please see the table below for more details. |
| 105 | + * <table> |
| 106 | + * <caption>Supported Mappings</caption> |
| 107 | + * <thead> |
| 108 | + * <tr> |
| 109 | + * <td>Value Type</td> |
| 110 | + * <td>Supported Target Types</td> |
| 111 | + * </tr> |
| 112 | + * </thead> |
| 113 | + * <tbody> |
| 114 | + * <tr> |
| 115 | + * <td>{@link TypeSystem#BOOLEAN}</td> |
| 116 | + * <td>{@code boolean}, {@link Boolean}</td> |
| 117 | + * </tr> |
| 118 | + * <tr> |
| 119 | + * <td>{@link TypeSystem#BYTES}</td> |
| 120 | + * <td>{@code byte[]}</td> |
| 121 | + * </tr> |
| 122 | + * <tr> |
| 123 | + * <td>{@link TypeSystem#STRING}</td> |
| 124 | + * <td>{@link String}</td> |
| 125 | + * </tr> |
| 126 | + * <tr> |
| 127 | + * <td>{@link TypeSystem#INTEGER}</td> |
| 128 | + * <td>{@code long}, {@link Long}, {@code int}, {@link Integer}, {@code double}, {@link Double}, {@code float}, {@link Float}</td> |
| 129 | + * </tr> |
| 130 | + * <tr> |
| 131 | + * <td>{@link TypeSystem#FLOAT}</td> |
| 132 | + * <td>{@code long}, {@link Long}, {@code int}, {@link Integer}, {@code double}, {@link Double}, {@code float}, {@link Float}</td> |
| 133 | + * </tr> |
| 134 | + * <tr> |
| 135 | + * <td>{@link TypeSystem#PATH}</td> |
| 136 | + * <td>{@link Path}</td> |
| 137 | + * </tr> |
| 138 | + * <tr> |
| 139 | + * <td>{@link TypeSystem#POINT}</td> |
| 140 | + * <td>{@link Point}</td> |
| 141 | + * </tr> |
| 142 | + * <tr> |
| 143 | + * <td>{@link TypeSystem#DATE}</td> |
| 144 | + * <td>{@link LocalDate}</td> |
| 145 | + * </tr> |
| 146 | + * <tr> |
| 147 | + * <td>{@link TypeSystem#TIME}</td> |
| 148 | + * <td>{@link OffsetTime}</td> |
| 149 | + * </tr> |
| 150 | + * <tr> |
| 151 | + * <td>{@link TypeSystem#LOCAL_TIME}</td> |
| 152 | + * <td>{@link LocalTime}</td> |
| 153 | + * </tr> |
| 154 | + * <tr> |
| 155 | + * <td>{@link TypeSystem#LOCAL_DATE_TIME}</td> |
| 156 | + * <td>{@link LocalDateTime}</td> |
| 157 | + * </tr> |
| 158 | + * <tr> |
| 159 | + * <td>{@link TypeSystem#DATE_TIME}</td> |
| 160 | + * <td>{@link ZonedDateTime}, {@link OffsetDateTime}</td> |
| 161 | + * </tr> |
| 162 | + * <tr> |
| 163 | + * <td>{@link TypeSystem#DURATION}</td> |
| 164 | + * <td>{@link IsoDuration}, {@link java.time.Period} (only when {@code seconds = 0} and {@code nanoseconds = 0} and no overflow happens), |
| 165 | + * {@link java.time.Duration} (only when {@code months = 0} and {@code days = 0} and no overflow happens)</td> |
| 166 | + * </tr> |
| 167 | + * <tr> |
| 168 | + * <td>{@link TypeSystem#NULL}</td> |
| 169 | + * <td>{@code null}</td> |
| 170 | + * </tr> |
| 171 | + * <tr> |
| 172 | + * <td>{@link TypeSystem#LIST}</td> |
| 173 | + * <td>{@link List}</td> |
| 174 | + * </tr> |
| 175 | + * <tr> |
| 176 | + * <td>{@link TypeSystem#MAP}</td> |
| 177 | + * <td>{@link Map}</td> |
| 178 | + * </tr> |
| 179 | + * <tr> |
| 180 | + * <td>{@link TypeSystem#NODE}</td> |
| 181 | + * <td>{@link Node}</td> |
| 182 | + * </tr> |
| 183 | + * <tr> |
| 184 | + * <td>{@link TypeSystem#RELATIONSHIP}</td> |
| 185 | + * <td>{@link Relationship}</td> |
| 186 | + * </tr> |
| 187 | + * </tbody> |
| 188 | + * </table> |
| 189 | + * |
| 190 | + * <p> |
| 191 | + * <b>Object Mapping</b> |
| 192 | + * <p> |
| 193 | + * Mapping of user-defined properties to user-defined types is supported for the following value types: |
| 194 | + * <ul> |
| 195 | + * <li>{@link TypeSystem#NODE()}</li> |
| 196 | + * <li>{@link TypeSystem#RELATIONSHIP()}</li> |
| 197 | + * <li>{@link TypeSystem#MAP()}</li> |
| 198 | + * </ul> |
| 199 | + * <p> |
| 200 | + * Example (using the <a href=https://github.com/neo4j-graph-examples/movies>Neo4j Movies Database</a>): |
| 201 | + * <pre> |
| 202 | + * {@code |
| 203 | + * // assuming the following Java record |
| 204 | + * public record Movie(String title, String tagline, long released) {} |
| 205 | + * // the nodes may be mapped to Movie instances |
| 206 | + * var movies = driver.executableQuery("MATCH (movie:Movie) RETURN movie") |
| 207 | + * .execute() |
| 208 | + * .records() |
| 209 | + * .stream() |
| 210 | + * .map(record -> record.get("movie").as(Movie.class)) |
| 211 | + * .toList(); |
| 212 | + * } |
| 213 | + * </pre> |
| 214 | + * <p> |
| 215 | + * Note that Object Mapping is an alternative to accessing the user-defined values in a map-like data structure. |
| 216 | + * If Object Graph Mapping (OGM) is needed, please use a higher level solution built on top of the driver, like |
| 217 | + * <a href="https://neo4j.com/docs/getting-started/languages-guides/java/spring-data-neo4j/">Spring Data Neo4j</a>. |
| 218 | + * <p> |
| 219 | + * The mapping is done by matching user-defined property names to target type constructor arguments. Therefore, the |
| 220 | + * constructor arguments must either have {@link Property} annotation or have a matching name that is available |
| 221 | + * at runtime (note that the constructor names are typically changed by the compiler unless either the compiler |
| 222 | + * {@code -parameters} option is used or they belong to the cannonical constructor of |
| 223 | + * {@link java.lang.Record java.lang.Record}). The name matching is case-sensitive. |
| 224 | + * <p> |
| 225 | + * Additionally, the {@link Property} annotation may be used when mapping a property with a different name to |
| 226 | + * {@link java.lang.Record java.lang.Record} cannonical constructor argument. |
| 227 | + * <p> |
| 228 | + * The constructor selection criteria is the following (top priority first): |
| 229 | + * <ol> |
| 230 | + * <li>Maximum matching properties.</li> |
| 231 | + * <li>Minimum mismatching properties.</li> |
| 232 | + * </ol> |
| 233 | + * The constructor search is done in the order defined by the {@link Class#getDeclaredConstructors()} and is |
| 234 | + * finished either when a full match is found with no mismatches or once all constructors have been visited. |
| 235 | + * <p> |
| 236 | + * At least 1 property match must be present for mapping to work. |
| 237 | + * <p> |
| 238 | + * A {@code null} value is used for arguments that don't have a matching property. If the argument does not accept |
| 239 | + * {@code null} value (this includes primitive types), an alternative constructor that excludes it must be |
| 240 | + * available. |
| 241 | + * <p> |
| 242 | + * Example with optional property (using the <a href=https://github.com/neo4j-graph-examples/movies>Neo4j Movies Database</a>): |
| 243 | + * <pre> |
| 244 | + * {@code |
| 245 | + * // assuming the following Java record |
| 246 | + * public record Person(String name, long born) { |
| 247 | + * // alternative constructor for values that don't have 'born' property available |
| 248 | + * public Person(@Property("name") String name) { |
| 249 | + * this(name, -1); |
| 250 | + * } |
| 251 | + * } |
| 252 | + * // the nodes may be mapped to Person instances |
| 253 | + * var persons = driver.executableQuery("MATCH (person:Person) RETURN person") |
| 254 | + * .execute() |
| 255 | + * .records() |
| 256 | + * .stream() |
| 257 | + * .map(record -> record.get("person").as(Person.class)) |
| 258 | + * .toList(); |
| 259 | + * } |
| 260 | + * </pre> |
| 261 | + * <p> |
| 262 | + * Types with generic parameters defined at the class level are not supported. However, constructor arguments with |
| 263 | + * specific types are permitted. |
| 264 | + * <p> |
| 265 | + * Example (using the <a href=https://github.com/neo4j-graph-examples/movies>Neo4j Movies Database</a>): |
| 266 | + * <pre> |
| 267 | + * {@code |
| 268 | + * // assuming the following Java record |
| 269 | + * public record Acted(List<String> roles) {} |
| 270 | + * // the relationships may be mapped to Acted instances |
| 271 | + * var actedList = driver.executableQuery("MATCH ()-[acted:ACTED_IN]-() RETURN acted") |
| 272 | + * .execute() |
| 273 | + * .records() |
| 274 | + * .stream() |
| 275 | + * .map(record -> record.get("acted").as(Acted.class)) |
| 276 | + * .toList(); |
| 277 | + * } |
| 278 | + * </pre> |
| 279 | + * On the contrary, the following record would not be mapped because type information is insufficient: |
| 280 | + * <pre> |
| 281 | + * {@code |
| 282 | + * public record Acted<T>(List<T> roles) {} |
| 283 | + * } |
| 284 | + * </pre> |
| 285 | + * Wildcard type value is not supported. |
| 286 | + * |
| 287 | + * @param targetClass the target class to map to |
| 288 | + * @param <T> the target type to map to |
| 289 | + * @return the mapped value |
| 290 | + * @throws Uncoercible when mapping to the target type is not possible |
| 291 | + * @throws LossyCoercion when mapping cannot be achieved without losing precision |
| 292 | + * @see Property |
| 293 | + * @since 5.28.5 |
| 294 | + */ |
| 295 | + @Preview(name = "Object mapping") |
| 296 | + default <T> T as(Class<T> targetClass) { |
| 297 | + // for backwards compatibility only |
| 298 | + throw new UnsupportedOperationException("not supported"); |
| 299 | + } |
81 | 300 | }
|
0 commit comments