16
16
use Doctrine \DBAL \Query \Expression \ExpressionBuilder ;
17
17
use Doctrine \DBAL \Query \QueryBuilder ;
18
18
use Ibexa \Contracts \CorePersistence \Exception \RuntimeMappingException ;
19
- use Ibexa \Contracts \CorePersistence \Gateway \DoctrineOneToManyRelationship ;
20
19
use Ibexa \Contracts \CorePersistence \Gateway \DoctrineRelationship ;
21
20
use Ibexa \Contracts \CorePersistence \Gateway \DoctrineRelationshipInterface ;
22
21
use Ibexa \Contracts \CorePersistence \Gateway \DoctrineSchemaMetadataInterface ;
23
22
use Ibexa \Contracts \CorePersistence \Gateway \DoctrineSchemaMetadataRegistryInterface ;
23
+ use LogicException ;
24
24
use RuntimeException ;
25
25
26
26
/**
@@ -48,19 +48,23 @@ final class ExpressionVisitor extends BaseExpressionVisitor
48
48
49
49
private DoctrineSchemaMetadataRegistryInterface $ registry ;
50
50
51
+ private RelationshipTypeStrategyRegistryInterface $ relationshipTypeStrategyRegistry ;
52
+
51
53
/**
52
54
* @param non-empty-string $tableName
53
55
*/
54
56
public function __construct (
55
57
QueryBuilder $ queryBuilder ,
56
58
DoctrineSchemaMetadataRegistryInterface $ registry ,
57
59
string $ tableName ,
58
- string $ tableAlias
60
+ string $ tableAlias ,
61
+ RelationshipTypeStrategyRegistryInterface $ relationshipTypeStrategyRegistry
59
62
) {
60
63
$ this ->queryBuilder = $ queryBuilder ;
61
64
$ this ->schemaMetadata = $ registry ->getMetadataForTable ($ tableName );
62
65
$ this ->tableAlias = $ tableAlias ;
63
66
$ this ->registry = $ registry ;
67
+ $ this ->relationshipTypeStrategyRegistry = $ relationshipTypeStrategyRegistry ;
64
68
}
65
69
66
70
/**
@@ -102,7 +106,7 @@ public function walkComparison(Comparison $comparison)
102
106
}
103
107
104
108
$ parameterName = $ column . '_ ' . count ($ this ->parameters );
105
- $ placeholder = ' : ' . $ parameterName ;
109
+ $ placeholder = $ this -> getPlaceholder ( $ parameterName) ;
106
110
$ value = $ this ->walkValue ($ comparison ->getValue ());
107
111
$ type = $ this ->schemaMetadata ->getBindingTypeForColumn ($ column );
108
112
if (is_array ($ value )) {
@@ -162,6 +166,10 @@ private function containsRelationshipDelimiter(string $column): bool
162
166
private function handleRelationshipComparison (string $ column , Comparison $ comparison ): string
163
167
{
164
168
$ metadata = $ this ->schemaMetadata ;
169
+ $ fromTable = $ this ->tableAlias ;
170
+ $ toTable = null ;
171
+ $ subQuery = null ;
172
+
165
173
do {
166
174
[
167
175
$ foreignProperty ,
@@ -170,6 +178,26 @@ private function handleRelationshipComparison(string $column, Comparison $compar
170
178
171
179
$ relationship = $ metadata ->getRelationshipByForeignProperty ($ foreignProperty );
172
180
$ metadata = $ this ->registry ->getMetadata ($ relationship ->getRelationshipClass ());
181
+ $ parentMetadata = $ metadata ->getParentMetadata ();
182
+ if (null !== $ parentMetadata ) {
183
+ $ fromTable = $ parentMetadata ->getTableName ();
184
+ } else {
185
+ $ fromTable = $ toTable ?? $ fromTable ;
186
+ }
187
+
188
+ $ toTable = $ metadata ->getTableName ();
189
+
190
+ if (DoctrineRelationship::JOIN_TYPE_SUB_SELECT === $ relationship ->getJoinType ()) {
191
+ $ subQuery ??= $ this ->queryBuilder ->getConnection ()->createQueryBuilder ();
192
+ }
193
+
194
+ $ this ->relationshipTypeStrategyRegistry ->handleRelationshipType (
195
+ $ subQuery ?? $ this ->queryBuilder ,
196
+ $ relationship ,
197
+ $ this ->tableAlias ,
198
+ $ fromTable ,
199
+ $ toTable
200
+ );
173
201
} while ($ this ->containsRelationshipDelimiter ($ column ));
174
202
175
203
if (!$ metadata ->hasColumn ($ column )) {
@@ -186,32 +214,7 @@ private function handleRelationshipComparison(string $column, Comparison $compar
186
214
));
187
215
}
188
216
189
- $ relationshipType = get_class ($ relationship );
190
-
191
- switch (true ) {
192
- case $ relationship ->getJoinType () === DoctrineRelationship::JOIN_TYPE_SUB_SELECT :
193
- return $ this ->handleSubSelectQuery (
194
- $ metadata ,
195
- $ relationship ->getForeignKeyColumn (),
196
- $ column ,
197
- $ comparison ,
198
- );
199
- case $ relationship ->getJoinType () === DoctrineRelationship::JOIN_TYPE_JOINED :
200
- return $ this ->handleJoinQuery (
201
- $ metadata ,
202
- $ column ,
203
- $ comparison ,
204
- );
205
- default :
206
- throw new RuntimeMappingException (sprintf (
207
- 'Unhandled relationship metadata. Expected one of "%s". Received "%s". ' ,
208
- implode ('", " ' , [
209
- DoctrineRelationship::class,
210
- DoctrineOneToManyRelationship::class,
211
- ]),
212
- $ relationshipType ,
213
- ));
214
- }
217
+ return $ this ->handleRelationshipQuery ($ metadata , $ relationship , $ column , $ comparison , $ subQuery );
215
218
}
216
219
217
220
private function escapeSpecialSQLValues (Parameter $ parameter ): string
@@ -226,63 +229,106 @@ private function expr(): ExpressionBuilder
226
229
return $ this ->queryBuilder ->expr ();
227
230
}
228
231
232
+ private function handleRelationshipQuery (
233
+ DoctrineSchemaMetadataInterface $ metadata ,
234
+ DoctrineRelationshipInterface $ relationship ,
235
+ string $ column ,
236
+ Comparison $ comparison ,
237
+ ?QueryBuilder $ subQuery
238
+ ): string {
239
+ $ isSubSelectJoin = $ relationship ->getJoinType () === DoctrineRelationship::JOIN_TYPE_SUB_SELECT ;
240
+ if ($ isSubSelectJoin ) {
241
+ if ($ subQuery === null ) {
242
+ throw new LogicException (
243
+ 'Sub-select query is not initialized. ' ,
244
+ );
245
+ }
246
+ }
247
+
248
+ $ parameterName = $ column . '_ ' . count ($ this ->parameters );
249
+ $ fullColumnName = $ metadata ->getTableName () . '. ' . $ column ;
250
+ $ relationshipQuery = $ this ->relationshipTypeStrategyRegistry ->handleRelationshipTypeQuery (
251
+ $ relationship ,
252
+ $ subQuery ?? $ this ->queryBuilder ,
253
+ $ fullColumnName ,
254
+ $ this ->getPlaceholder ($ parameterName )
255
+ );
256
+
257
+ if ($ isSubSelectJoin ) {
258
+ return $ this ->handleSubSelectQuery (
259
+ $ relationship ,
260
+ $ metadata ,
261
+ $ comparison ,
262
+ $ column ,
263
+ $ parameterName ,
264
+ $ relationshipQuery
265
+ );
266
+ }
267
+
268
+ return $ this ->handleJoinQuery (
269
+ $ comparison ,
270
+ $ metadata ,
271
+ $ column ,
272
+ $ parameterName ,
273
+ $ fullColumnName ,
274
+ $ relationshipQuery
275
+ );
276
+ }
277
+
229
278
private function handleJoinQuery (
279
+ Comparison $ comparison ,
230
280
DoctrineSchemaMetadataInterface $ relationshipMetadata ,
231
281
string $ field ,
232
- Comparison $ comparison
282
+ string $ parameterName ,
283
+ string $ fullColumnName ,
284
+ QueryBuilder $ relationshipQuery
233
285
): string {
234
- $ tableName = $ relationshipMetadata ->getTableName ();
235
- $ parameterName = $ field . '_ ' . count ($ this ->parameters );
236
- $ placeholder = ': ' . $ parameterName ;
237
-
238
286
$ value = $ this ->walkValue ($ comparison ->getValue ());
239
287
$ type = $ relationshipMetadata ->getBindingTypeForColumn ($ field );
288
+
240
289
if (is_array ($ value )) {
241
290
$ type += Connection::ARRAY_PARAM_OFFSET ;
242
291
}
243
292
244
293
$ parameter = new Parameter ($ parameterName , $ value , $ type );
294
+ $ placeholder = $ this ->getPlaceholder ($ parameterName );
245
295
246
296
if (is_array ($ value )) {
247
297
$ this ->parameters [] = $ parameter ;
248
298
249
- return $ this ->expr ()->in ($ tableName . '. ' . $ field , $ placeholder );
299
+ return $ relationshipQuery ->expr ()->in (
300
+ $ fullColumnName ,
301
+ $ placeholder
302
+ );
250
303
}
251
304
252
- return $ this ->handleComparison ($ comparison , $ parameter , $ tableName . '. ' . $ field , $ placeholder );
305
+ return $ this ->handleComparison (
306
+ $ comparison ,
307
+ $ parameter ,
308
+ $ fullColumnName ,
309
+ $ placeholder
310
+ );
253
311
}
254
312
255
313
private function handleSubSelectQuery (
314
+ DoctrineRelationshipInterface $ relationship ,
256
315
DoctrineSchemaMetadataInterface $ relationshipMetadata ,
257
- string $ foreignField ,
316
+ Comparison $ comparison ,
258
317
string $ field ,
259
- Comparison $ comparison
318
+ string $ parameterName ,
319
+ QueryBuilder $ relationshipQuery
260
320
): string {
261
- $ tableName = $ relationshipMetadata ->getTableName ();
262
- $ parameterName = $ field . '_ ' . count ($ this ->parameters );
263
- $ placeholder = ': ' . $ parameterName ;
264
-
265
- $ subquery = $ this ->queryBuilder ->getConnection ()->createQueryBuilder ();
266
- $ subquery ->from ($ tableName );
267
- $ subquery ->select ($ tableName . '. ' . $ relationshipMetadata ->getIdentifierColumn ());
268
- $ subquery ->where (
269
- $ subquery ->expr ()->in (
270
- $ tableName . '. ' . $ field ,
271
- $ placeholder ,
272
- ),
273
- );
274
-
275
321
$ value = $ this ->walkValue ($ comparison ->getValue ());
276
322
$ type = $ relationshipMetadata ->getBindingTypeForColumn ($ field );
277
323
if (is_array ($ value )) {
278
324
$ type += Connection::ARRAY_PARAM_OFFSET ;
279
325
}
280
- $ parameter = new Parameter ( $ parameterName , $ value , $ type );
281
- $ this ->parameters [] = $ parameter ;
326
+
327
+ $ this ->parameters [] = new Parameter ( $ parameterName , $ value , $ type ) ;
282
328
283
329
return $ this ->expr ()->in (
284
- $ this ->tableAlias . '. ' . $ foreignField ,
285
- $ subquery ->getSQL (),
330
+ $ this ->tableAlias . '. ' . $ relationship -> getForeignKeyColumn () ,
331
+ $ relationshipQuery ->getSQL (),
286
332
);
287
333
}
288
334
@@ -304,6 +350,7 @@ private function handleTranslation(Comparison $comparison): string
304
350
$ this ->registry ,
305
351
$ translationMetadata ->getTableName (),
306
352
self ::TRANSLATION_TABLE_ALIAS ,
353
+ $ this ->relationshipTypeStrategyRegistry
307
354
);
308
355
$ innerCondition = $ innerExpressionVisitor ->walkComparison ($ comparison );
309
356
$ subquery ->andWhere ($ innerCondition );
@@ -317,6 +364,11 @@ private function handleTranslation(Comparison $comparison): string
317
364
);
318
365
}
319
366
367
+ private function getPlaceholder (string $ parameterName ): string
368
+ {
369
+ return ': ' . $ parameterName ;
370
+ }
371
+
320
372
private function isInheritedColumn (string $ column ): bool
321
373
{
322
374
return $ this ->schemaMetadata ->getIdentifierColumn () !== $ column
0 commit comments