|
24 | 24 | import org.springframework.data.mapping.PersistentPropertyPath;
|
25 | 25 | import org.springframework.data.relational.core.sql.SqlIdentifier;
|
26 | 26 | import org.springframework.lang.Nullable;
|
| 27 | +import org.springframework.util.Assert; |
27 | 28 |
|
28 | 29 | /**
|
29 | 30 | * Represents a path within an aggregate starting from the aggregate root. The path can be iterated from the leaf to its
|
@@ -81,7 +82,9 @@ default int getLength() {
|
81 | 82 |
|
82 | 83 | /**
|
83 | 84 | * Returns {@literal true} if there are multiple values for this path, i.e. if the path contains at least one element
|
84 |
| - * that is a collection and array or a map. |
| 85 | + * that is a collection and array or a map. // TODO: why does this return true if the parent entity is a collection? |
| 86 | + * This seems to mix some concepts that belong to somewhere else. // TODO: Multi-valued could be understood for |
| 87 | + * embeddables with more than one column (i.e. composite primary keys) |
85 | 88 | *
|
86 | 89 | * @return {@literal true} if the path contains a multivalued element.
|
87 | 90 | */
|
@@ -179,9 +182,13 @@ default RelationalPersistentEntity<?> getRequiredLeafEntity() {
|
179 | 182 | // TODO: Conceptually, AggregatePath works with properties. The mapping into columns and tables should reside in a
|
180 | 183 | // utility that can distinguish whether a property maps to one or many columns (e.g. embedded) and the same for
|
181 | 184 | // identifier columns.
|
182 |
| - TableInfo getTableInfo(); |
| 185 | + default TableInfo getTableInfo() { |
| 186 | + return TableInfo.of(this); |
| 187 | + } |
183 | 188 |
|
184 |
| - ColumnInfo getColumnInfo(); |
| 189 | + default ColumnInfo getColumnInfo() { |
| 190 | + return ColumnInfo.of(this); |
| 191 | + } |
185 | 192 |
|
186 | 193 | /**
|
187 | 194 | * Filter the AggregatePath returning the first item matching the given {@link Predicate}.
|
@@ -222,65 +229,114 @@ default Stream<AggregatePath> stream() {
|
222 | 229 |
|
223 | 230 | record TableInfo(
|
224 | 231 |
|
225 |
| - /** |
| 232 | + /* |
226 | 233 | * The fully qualified name of the table this path is tied to or of the longest ancestor path that is actually
|
227 | 234 | * tied to a table.
|
228 |
| - * |
229 |
| - * @return the name of the table. Guaranteed to be not {@literal null}. |
230 |
| - * @since 3.0 |
231 | 235 | */
|
232 | 236 | SqlIdentifier qualifiedTableName,
|
233 | 237 |
|
234 |
| - /** |
| 238 | + /* |
235 | 239 | * The alias used for the table on which this path is based.
|
236 |
| - * |
237 |
| - * @return a table alias, {@literal null} if the table owning path is the empty path. |
238 | 240 | */
|
239 | 241 | @Nullable SqlIdentifier tableAlias,
|
240 | 242 |
|
241 | 243 | ColumnInfo reverseColumnInfo,
|
242 | 244 |
|
243 |
| - /** |
| 245 | + /* |
244 | 246 | * The column used for the list index or map key of the leaf property of this path.
|
245 |
| - * |
246 |
| - * @return May be {@literal null}. |
247 | 247 | */
|
248 | 248 | @Nullable ColumnInfo qualifierColumnInfo,
|
249 | 249 |
|
250 |
| - /** |
| 250 | + /* |
251 | 251 | * The type of the qualifier column of the leaf property of this path or {@literal null} if this is not
|
252 | 252 | * applicable.
|
253 |
| - * |
254 |
| - * @return may be {@literal null}. |
255 | 253 | */
|
256 | 254 | @Nullable Class<?> qualifierColumnType,
|
257 | 255 |
|
258 |
| - /** |
| 256 | + /* |
259 | 257 | * The column name of the id column of the ancestor path that represents an actual table.
|
260 | 258 | */
|
261 | 259 | SqlIdentifier idColumnName,
|
262 | 260 |
|
263 |
| - /** |
| 261 | + /* |
264 | 262 | * If the table owning ancestor has an id the column name of that id property is returned. Otherwise the reverse
|
265 | 263 | * column is returned.
|
266 | 264 | */
|
267 | 265 | SqlIdentifier effectiveIdColumnName) {
|
268 | 266 |
|
| 267 | + static TableInfo of(AggregatePath path) { |
| 268 | + |
| 269 | + AggregatePath tableOwner = AggregatePathTraversal.getTableOwningPath(path); |
| 270 | + |
| 271 | + RelationalPersistentEntity<?> leafEntity = tableOwner.getRequiredLeafEntity(); |
| 272 | + SqlIdentifier qualifiedTableName = leafEntity.getQualifiedTableName(); |
| 273 | + |
| 274 | + SqlIdentifier tableAlias = tableOwner.isRoot() ? null : AggregatePathTableUtils.constructTableAlias(tableOwner); |
| 275 | + |
| 276 | + ColumnInfo reverseColumnInfo = null; |
| 277 | + if (!tableOwner.isRoot()) { |
| 278 | + |
| 279 | + AggregatePath idDefiningParentPath = tableOwner.getIdDefiningParentPath(); |
| 280 | + RelationalPersistentProperty leafProperty = tableOwner.getRequiredLeafProperty(); |
| 281 | + |
| 282 | + SqlIdentifier reverseColumnName = leafProperty |
| 283 | + .getReverseColumnName(idDefiningParentPath.getRequiredLeafEntity()); |
| 284 | + |
| 285 | + reverseColumnInfo = new ColumnInfo(reverseColumnName, |
| 286 | + AggregatePathTableUtils.prefixWithTableAlias(path, reverseColumnName)); |
| 287 | + } |
| 288 | + |
| 289 | + ColumnInfo qualifierColumnInfo = null; |
| 290 | + if (!path.isRoot()) { |
| 291 | + |
| 292 | + SqlIdentifier keyColumn = path.getRequiredLeafProperty().getKeyColumn(); |
| 293 | + if (keyColumn != null) { |
| 294 | + qualifierColumnInfo = new ColumnInfo(keyColumn, keyColumn); |
| 295 | + } |
| 296 | + } |
| 297 | + |
| 298 | + Class<?> qualifierColumnType = null; |
| 299 | + if (!path.isRoot() && path.getRequiredLeafProperty().isQualified()) { |
| 300 | + qualifierColumnType = path.getRequiredLeafProperty().getQualifierColumnType(); |
| 301 | + } |
| 302 | + |
| 303 | + SqlIdentifier idColumnName = leafEntity.hasIdProperty() ? leafEntity.getIdColumn() : null; |
| 304 | + |
| 305 | + SqlIdentifier effectiveIdColumnName = tableOwner.isRoot() ? idColumnName : reverseColumnInfo.name(); |
| 306 | + |
| 307 | + return new TableInfo(qualifiedTableName, tableAlias, reverseColumnInfo, qualifierColumnInfo, qualifierColumnType, |
| 308 | + idColumnName, effectiveIdColumnName); |
| 309 | + |
| 310 | + } |
269 | 311 | }
|
270 | 312 |
|
271 | 313 | record ColumnInfo(
|
272 | 314 |
|
273 |
| - /** |
274 |
| - * The name of the column used to represent this property in the database. |
275 |
| - * |
276 |
| - * @throws IllegalStateException when called on an empty path. |
277 |
| - */ |
278 |
| - SqlIdentifier name, |
279 |
| - /** |
280 |
| - * The alias for the column used to represent this property in the database. |
281 |
| - * |
282 |
| - * @throws IllegalStateException when called on an empty path. |
283 |
| - */ |
| 315 | + /* The name of the column used to represent this property in the database. */ |
| 316 | + SqlIdentifier name, /* The alias for the column used to represent this property in the database. */ |
284 | 317 | SqlIdentifier alias) {
|
| 318 | + |
| 319 | + /** |
| 320 | + * Create a {@link ColumnInfo} from an aggregate path. ColumnInfo can be created for simple type single-value |
| 321 | + * properties only. |
| 322 | + * |
| 323 | + * @param path |
| 324 | + * @return the {@link ColumnInfo}. |
| 325 | + * @throws IllegalArgumentException if the path is {@link #isRoot()}, {@link #isEmbedded()} or |
| 326 | + * {@link #isMultiValued()}. |
| 327 | + */ |
| 328 | + static ColumnInfo of(AggregatePath path) { |
| 329 | + |
| 330 | + Assert.notNull(path, "AggregatePath must not be null"); |
| 331 | + Assert.isTrue(!path.isRoot(), () -> "Cannot obtain ColumnInfo for root path"); |
| 332 | + Assert.isTrue(!path.isEmbedded(), () -> "Cannot obtain ColumnInfo for embedded path"); |
| 333 | + |
| 334 | + // TODO: Multi-valued paths cannot be represented with a single column |
| 335 | + // Assert.isTrue(!path.isMultiValued(), () -> "Cannot obtain ColumnInfo for multi-valued path"); |
| 336 | + |
| 337 | + SqlIdentifier name = AggregatePathTableUtils.assembleColumnName(path, |
| 338 | + path.getRequiredLeafProperty().getColumnName()); |
| 339 | + return new ColumnInfo(name, AggregatePathTableUtils.prefixWithTableAlias(path, name)); |
| 340 | + } |
285 | 341 | }
|
286 | 342 | }
|
0 commit comments