@@ -112,6 +112,13 @@ public class MemoryRecordStore
112112
113113 public static final ListingHash <Class <? extends AbstractActionInput >, AbstractActionInput > actionInputs = new ListingHash <>();
114114
115+ //////////////////////////////////////////////////////////////////////
116+ // to slow-roll this change in capability, set it as a feature-flag //
117+ // on the MemoryRecordStore singleton instance. This should allow //
118+ // an individual test, for example, to toggle it, then reset it. //
119+ //////////////////////////////////////////////////////////////////////
120+ public static boolean BUILD_JOIN_CROSS_PRODUCT_FROM_JOIN_CONTEXT_DEFAULT = false ;
121+ private boolean buildJoinCrossProductFromJoinContext = BUILD_JOIN_CROSS_PRODUCT_FROM_JOIN_CONTEXT_DEFAULT ;
115122
116123
117124 /*******************************************************************************
@@ -206,9 +213,24 @@ public List<QRecord> query(QueryInput input) throws QException
206213
207214 QQueryFilter filter = clonedOrNewFilter (input .getFilter ());
208215 JoinsContext joinsContext = new JoinsContext (QContext .getQInstance (), input .getTableName (), input .getQueryJoins (), filter );
209- if (CollectionUtils .nullSafeHasContents (input .getQueryJoins ()))
216+
217+ /////////////////////////////////////////////////////////////////////////////////////////////////
218+ // if we every wanted or needed per-query control here, that could look like: //
219+ // || input.hasFlag(MemoryBackendQueryActionFlags.BUILD_JOIN_CROSS_PRODUCT_FROM_JOIN_CONTEXT)) //
220+ /////////////////////////////////////////////////////////////////////////////////////////////////
221+ if (buildJoinCrossProductFromJoinContext )
222+ {
223+ if (CollectionUtils .nullSafeHasContents (joinsContext .getQueryJoins ()))
224+ {
225+ tableData = buildJoinCrossProduct (input .getTable (), joinsContext .getQueryJoins ());
226+ }
227+ }
228+ else
210229 {
211- tableData = buildJoinCrossProduct (input );
230+ if (CollectionUtils .nullSafeHasContents (input .getQueryJoins ()))
231+ {
232+ tableData = buildJoinCrossProduct (input .getTable (), input .getQueryJoins ());
233+ }
212234 }
213235
214236 ///////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -273,20 +295,20 @@ public List<QRecord> query(QueryInput input) throws QException
273295 /*******************************************************************************
274296 **
275297 *******************************************************************************/
276- private Collection <QRecord > buildJoinCrossProduct (QueryInput input ) throws QException
298+ private Collection <QRecord > buildJoinCrossProduct (QTableMetaData table , List < QueryJoin > queryJoins ) throws QException
277299 {
278300 QInstance qInstance = QContext .getQInstance ();
279301
280302 List <QRecord > crossProduct = new ArrayList <>();
281- QTableMetaData leftTable = input . getTable () ;
303+ QTableMetaData leftTable = table ;
282304 for (QRecord record : getTableData (leftTable ).values ())
283305 {
284306 QRecord productRecord = new QRecord ();
285307 addRecordToProduct (productRecord , record , null );
286308 crossProduct .add (productRecord );
287309 }
288310
289- for (QueryJoin queryJoin : input . getQueryJoins () )
311+ for (QueryJoin queryJoin : queryJoins )
290312 {
291313 QTableMetaData nextTable = qInstance .getTable (queryJoin .getJoinTable ());
292314 Collection <QRecord > nextTableRecords = getTableData (nextTable ).values ();
@@ -309,7 +331,11 @@ private Collection<QRecord> buildJoinCrossProduct(QueryInput input) throws QExce
309331
310332 if (!matchFound )
311333 {
312- // todo - Left & Right joins
334+ if (QueryJoin .Type .LEFT .equals (queryJoin .getType ()))
335+ {
336+ QRecord joinRecord = new QRecord (productRecord );
337+ nextLevelProduct .add (joinRecord );
338+ }
313339 }
314340 }
315341
@@ -1186,4 +1212,53 @@ private record Variant(String type, Serializable id) implements BackendIdentifie
11861212 {
11871213 }
11881214
1215+
1216+
1217+ /*******************************************************************************
1218+ * Getter for buildJoinCrossProductFromJoinContext
1219+ * @see #withBuildJoinCrossProductFromJoinContext(boolean)
1220+ *******************************************************************************/
1221+ public boolean getBuildJoinCrossProductFromJoinContext ()
1222+ {
1223+ return (this .buildJoinCrossProductFromJoinContext );
1224+ }
1225+
1226+
1227+
1228+ /*******************************************************************************
1229+ * Setter for buildJoinCrossProductFromJoinContext
1230+ * @see #withBuildJoinCrossProductFromJoinContext(boolean)
1231+ *******************************************************************************/
1232+ public void setBuildJoinCrossProductFromJoinContext (boolean buildJoinCrossProductFromJoinContext )
1233+ {
1234+ this .buildJoinCrossProductFromJoinContext = buildJoinCrossProductFromJoinContext ;
1235+ }
1236+
1237+
1238+
1239+ /*******************************************************************************
1240+ * Fluent setter for buildJoinCrossProductFromJoinContext
1241+ *
1242+ * @param buildJoinCrossProductFromJoinContext
1243+ * The original implementation of this class only tried to build cross-products
1244+ * for joins based on QueryJoin objects directly added to the QueryInput.
1245+ * However, this meant that if a join was needed for the security key on a table,
1246+ * that this table wouldn't be included in the join cross product, thus incorrect
1247+ * query results could be returned.
1248+ *
1249+ * <p>This new behavior (to use joins from the JoinContext, which means, it includes
1250+ * joins needed for security) seems like the obviously more correct way to do it -
1251+ * but, it is a potentially breaking change, so we want to slow-roll it out.
1252+ * Thus, an application (or a unit test) can opt-in to the new behavior by
1253+ * setting this field to true on a MemoryRecordStore singleton instance.</p>
1254+ *
1255+ * @return this
1256+ *******************************************************************************/
1257+ public MemoryRecordStore withBuildJoinCrossProductFromJoinContext (boolean buildJoinCrossProductFromJoinContext )
1258+ {
1259+ this .buildJoinCrossProductFromJoinContext = buildJoinCrossProductFromJoinContext ;
1260+ return (this );
1261+ }
1262+
1263+
11891264}
0 commit comments