Search in sources :

Example 1 with ArrayTable

use of org.datanucleus.store.rdbms.table.ArrayTable in project datanucleus-rdbms by datanucleus.

the class ArrayContainsMethod method containsAsSubquery.

/**
 * Method to return an expression for Collection.contains using a subquery "EXISTS".
 * This is for use when there are "!contains" or "OR" operations in the filter.
 * Creates the following SQL,
 * <ul>
 * <li><b>Collection of NonPC using join table</b>
 * <pre>
 * SELECT 1 FROM JOIN_TBL A0_SUB
 * WHERE A0_SUB.JOIN_OWN_ID = A0.ID AND A0_SUB.JOIN_ELEM_ID = {elemExpr}
 * </pre>
 * </li>
 * <li><b>Collection of PC using join table</b>
 * <pre>
 * SELECT 1 FROM ELEM_TABLE A0_SUB INNER JOIN JOIN_TBL B0 ON ...
 * WHERE B0.JOIN_OWN_ID = A0.ID AND A0_SUB.ID = {elemExpr}
 * </pre>
 * </li>
 * </ul>
 * and returns a BooleanSubqueryExpression ("EXISTS (subquery)")
 * @param stmt SQLStatement
 * @param arrExpr Collection expression
 * @param elemExpr Expression for the element
 * @return Contains expression
 */
protected SQLExpression containsAsSubquery(SQLStatement stmt, ArrayExpression arrExpr, SQLExpression elemExpr) {
    boolean elemIsUnbound = (elemExpr instanceof UnboundExpression);
    String varName = null;
    if (elemIsUnbound) {
        varName = ((UnboundExpression) elemExpr).getVariableName();
        NucleusLogger.QUERY.debug(">> Array.contains binding unbound variable " + varName + " using SUBQUERY");
    }
    RDBMSStoreManager storeMgr = stmt.getRDBMSManager();
    SQLExpressionFactory exprFactory = stmt.getSQLExpressionFactory();
    ClassLoaderResolver clr = stmt.getQueryGenerator().getClassLoaderResolver();
    AbstractMemberMetaData mmd = arrExpr.getJavaTypeMapping().getMemberMetaData();
    AbstractClassMetaData elemCmd = mmd.getArray().getElementClassMetaData(clr);
    ArrayTable joinTbl = (ArrayTable) storeMgr.getTable(mmd);
    SelectStatement subStmt = null;
    if (joinTbl != null) {
        // JoinTable array
        if (elemCmd == null) {
            // Array<Non-PC>
            subStmt = new SelectStatement(stmt, storeMgr, joinTbl, null, null);
            subStmt.setClassLoaderResolver(clr);
            JavaTypeMapping oneMapping = storeMgr.getMappingManager().getMapping(Integer.class);
            subStmt.select(exprFactory.newLiteral(subStmt, oneMapping, 1), null);
            // Restrict to array owner
            JavaTypeMapping ownerMapping = ((JoinTable) joinTbl).getOwnerMapping();
            SQLExpression ownerExpr = exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), ownerMapping);
            SQLExpression ownerIdExpr = exprFactory.newExpression(stmt, arrExpr.getSQLTable(), arrExpr.getSQLTable().getTable().getIdMapping());
            subStmt.whereAnd(ownerExpr.eq(ownerIdExpr), true);
            SQLExpression elemIdExpr = exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), joinTbl.getElementMapping());
            if (elemIsUnbound) {
                // Bind the variable in the QueryGenerator
                stmt.getQueryGenerator().bindVariable(varName, null, elemIdExpr.getSQLTable(), elemIdExpr.getJavaTypeMapping());
            } else {
                // Add restrict to element
                subStmt.whereAnd(elemIdExpr.eq(elemExpr), true);
            }
        } else {
            // Array<PC>
            DatastoreClass elemTbl = storeMgr.getDatastoreClass(mmd.getArray().getElementType(), clr);
            subStmt = new SelectStatement(stmt, storeMgr, elemTbl, null, null);
            subStmt.setClassLoaderResolver(clr);
            JavaTypeMapping oneMapping = storeMgr.getMappingManager().getMapping(Integer.class);
            subStmt.select(exprFactory.newLiteral(subStmt, oneMapping, 1), null);
            // Join to join table
            SQLTable joinSqlTbl = subStmt.join(JoinType.INNER_JOIN, subStmt.getPrimaryTable(), elemTbl.getIdMapping(), null, joinTbl, null, joinTbl.getElementMapping(), null, null, null, true, null);
            // Restrict to array owner
            JavaTypeMapping ownerMapping = ((JoinTable) joinTbl).getOwnerMapping();
            SQLExpression ownerExpr = exprFactory.newExpression(subStmt, joinSqlTbl, ownerMapping);
            SQLExpression ownerIdExpr = exprFactory.newExpression(stmt, arrExpr.getSQLTable(), arrExpr.getSQLTable().getTable().getIdMapping());
            subStmt.whereAnd(ownerExpr.eq(ownerIdExpr), true);
            SQLExpression elemIdExpr = exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), elemTbl.getIdMapping());
            if (elemIsUnbound) {
                // Bind the variable in the QueryGenerator
                stmt.getQueryGenerator().bindVariable(varName, elemCmd, elemIdExpr.getSQLTable(), elemIdExpr.getJavaTypeMapping());
            } else {
                // Add restrict to element
                subStmt.whereAnd(elemIdExpr.eq(elemExpr), true);
            }
        }
    } else {
        // TODO Support FK array ?
        throw new NucleusException("Dont support evaluation of ARRAY.contains when no join table is used");
    }
    return new BooleanSubqueryExpression(stmt, "EXISTS", subStmt);
}
Also used : SQLExpressionFactory(org.datanucleus.store.rdbms.sql.expression.SQLExpressionFactory) SQLExpression(org.datanucleus.store.rdbms.sql.expression.SQLExpression) JavaTypeMapping(org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping) ClassLoaderResolver(org.datanucleus.ClassLoaderResolver) UnboundExpression(org.datanucleus.store.rdbms.sql.expression.UnboundExpression) AbstractClassMetaData(org.datanucleus.metadata.AbstractClassMetaData) RDBMSStoreManager(org.datanucleus.store.rdbms.RDBMSStoreManager) SelectStatement(org.datanucleus.store.rdbms.sql.SelectStatement) BooleanSubqueryExpression(org.datanucleus.store.rdbms.sql.expression.BooleanSubqueryExpression) SQLTable(org.datanucleus.store.rdbms.sql.SQLTable) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) ArrayTable(org.datanucleus.store.rdbms.table.ArrayTable) NucleusException(org.datanucleus.exceptions.NucleusException) AbstractMemberMetaData(org.datanucleus.metadata.AbstractMemberMetaData) JoinTable(org.datanucleus.store.rdbms.table.JoinTable)

Example 2 with ArrayTable

use of org.datanucleus.store.rdbms.table.ArrayTable in project datanucleus-rdbms by datanucleus.

the class RDBMSStoreManager method getBackingStoreForField.

/**
 * Accessor for the backing store for the specified member.
 * Note : if we have an embedded object that is embedded into some other type and the object has a member that requires a join table (backing store), this method
 * will not cater for the different places that can be embedded.
 * @param clr The ClassLoaderResolver
 * @param mmd metadata for the member to be persisted by this Store
 * @param type instantiated type or prefered type
 * @return The backing store
 */
public Store getBackingStoreForField(ClassLoaderResolver clr, AbstractMemberMetaData mmd, Class type) {
    if (mmd == null || mmd.isSerialized()) {
        return null;
    }
    Store store = backingStoreByMemberName.get(mmd.getFullFieldName());
    if (store != null) {
        return store;
    }
    synchronized (backingStoreByMemberName) {
        // Just in case we synced just after someone added since our previous lookup above
        store = backingStoreByMemberName.get(mmd.getFullFieldName());
        if (store != null) {
            return store;
        }
        Class expectedMappingType = null;
        if (mmd.getMap() != null) {
            expectedMappingType = MapMapping.class;
        } else if (mmd.getArray() != null) {
            expectedMappingType = ArrayMapping.class;
        } else if (mmd.getCollection() != null) {
            expectedMappingType = CollectionMapping.class;
        } else {
            expectedMappingType = PersistableMapping.class;
        }
        // Validate the mapping type matches the table
        try {
            DatastoreClass ownerTable = getDatastoreClass(mmd.getClassName(), clr);
            if (ownerTable == null) {
                // Class doesn't manage its own table (uses subclass-table, or superclass-table?)
                AbstractClassMetaData fieldTypeCmd = getMetaDataManager().getMetaDataForClass(mmd.getClassName(), clr);
                AbstractClassMetaData[] tableOwnerCmds = getClassesManagingTableForClass(fieldTypeCmd, clr);
                if (tableOwnerCmds != null && tableOwnerCmds.length == 1) {
                    ownerTable = getDatastoreClass(tableOwnerCmds[0].getFullClassName(), clr);
                }
            }
            if (ownerTable != null) {
                JavaTypeMapping m = ownerTable.getMemberMapping(mmd);
                if (!expectedMappingType.isAssignableFrom(m.getClass())) {
                    String requiredType = type != null ? type.getName() : mmd.getTypeName();
                    NucleusLogger.PERSISTENCE.warn("Member " + mmd.getFullFieldName() + " in table=" + ownerTable + " has mapping=" + m + " but expected mapping type=" + expectedMappingType);
                    throw new IncompatibleFieldTypeException(mmd.getFullFieldName(), requiredType, m.getType());
                }
            }
        } catch (NoTableManagedException ntme) {
        // Embedded, so just pass through
        }
        if (mmd.getMap() != null) {
            Table datastoreTable = getTable(mmd);
            if (datastoreTable == null) {
                store = new FKMapStore(mmd, this, clr);
            } else {
                store = new JoinMapStore((MapTable) datastoreTable, clr);
            }
        } else if (mmd.getArray() != null) {
            Table datastoreTable = getTable(mmd);
            if (datastoreTable != null) {
                store = new JoinArrayStore(mmd, (ArrayTable) datastoreTable, clr);
            } else {
                store = new FKArrayStore(mmd, this, clr);
            }
        } else if (mmd.getCollection() != null) {
            Table datastoreTable = getTable(mmd);
            if (type == null) {
                // No type to base it on so create it based on the field declared type
                if (datastoreTable == null) {
                    // We need a "FK" relation
                    if (Set.class.isAssignableFrom(mmd.getType())) {
                        store = new FKSetStore(mmd, this, clr);
                    } else if (List.class.isAssignableFrom(mmd.getType()) || Queue.class.isAssignableFrom(mmd.getType())) {
                        store = new FKListStore(mmd, this, clr);
                    } else if (mmd.getOrderMetaData() != null) {
                        // User has requested ordering
                        store = new FKListStore(mmd, this, clr);
                    } else {
                        store = new FKSetStore(mmd, this, clr);
                    }
                } else {
                    // We need a "JoinTable" relation.
                    if (Set.class.isAssignableFrom(mmd.getType())) {
                        store = new JoinSetStore(mmd, (CollectionTable) datastoreTable, clr);
                    } else if (List.class.isAssignableFrom(mmd.getType()) || Queue.class.isAssignableFrom(mmd.getType())) {
                        store = new JoinListStore(mmd, (CollectionTable) datastoreTable, clr);
                    } else if (mmd.getOrderMetaData() != null) {
                        // User has requested ordering
                        store = new JoinListStore(mmd, (CollectionTable) datastoreTable, clr);
                    } else {
                        store = new JoinSetStore(mmd, (CollectionTable) datastoreTable, clr);
                    }
                }
            } else {
                // Instantiated type specified, so use it to pick the associated backing store
                if (datastoreTable == null) {
                    if (SCOUtils.isListBased(type)) {
                        // List required
                        store = new FKListStore(mmd, this, clr);
                    } else {
                        // Set required
                        store = new FKSetStore(mmd, this, clr);
                    }
                } else {
                    if (SCOUtils.isListBased(type)) {
                        // List required
                        store = new JoinListStore(mmd, (CollectionTable) datastoreTable, clr);
                    } else {
                        // Set required
                        store = new JoinSetStore(mmd, (CollectionTable) datastoreTable, clr);
                    }
                }
            }
        } else {
            store = new JoinPersistableRelationStore(mmd, (PersistableJoinTable) getTable(mmd), clr);
        }
        backingStoreByMemberName.put(mmd.getFullFieldName(), store);
        return store;
    }
}
Also used : Table(org.datanucleus.store.rdbms.table.Table) ProbeTable(org.datanucleus.store.rdbms.table.ProbeTable) JoinTable(org.datanucleus.store.rdbms.table.JoinTable) ClassTable(org.datanucleus.store.rdbms.table.ClassTable) MapTable(org.datanucleus.store.rdbms.table.MapTable) PersistableJoinTable(org.datanucleus.store.rdbms.table.PersistableJoinTable) ArrayTable(org.datanucleus.store.rdbms.table.ArrayTable) CollectionTable(org.datanucleus.store.rdbms.table.CollectionTable) SequenceTable(org.datanucleus.store.rdbms.valuegenerator.SequenceTable) JoinArrayStore(org.datanucleus.store.rdbms.scostore.JoinArrayStore) JavaTypeMapping(org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping) JoinMapStore(org.datanucleus.store.rdbms.scostore.JoinMapStore) JoinSetStore(org.datanucleus.store.rdbms.scostore.JoinSetStore) JoinListStore(org.datanucleus.store.rdbms.scostore.JoinListStore) Store(org.datanucleus.store.types.scostore.Store) JoinArrayStore(org.datanucleus.store.rdbms.scostore.JoinArrayStore) JoinPersistableRelationStore(org.datanucleus.store.rdbms.scostore.JoinPersistableRelationStore) FKSetStore(org.datanucleus.store.rdbms.scostore.FKSetStore) FKListStore(org.datanucleus.store.rdbms.scostore.FKListStore) FKMapStore(org.datanucleus.store.rdbms.scostore.FKMapStore) FKArrayStore(org.datanucleus.store.rdbms.scostore.FKArrayStore) JoinMapStore(org.datanucleus.store.rdbms.scostore.JoinMapStore) IncompatibleFieldTypeException(org.datanucleus.store.types.IncompatibleFieldTypeException) MacroString(org.datanucleus.util.MacroString) JoinListStore(org.datanucleus.store.rdbms.scostore.JoinListStore) AbstractClassMetaData(org.datanucleus.metadata.AbstractClassMetaData) MapTable(org.datanucleus.store.rdbms.table.MapTable) ArrayMapping(org.datanucleus.store.rdbms.mapping.java.ArrayMapping) JoinSetStore(org.datanucleus.store.rdbms.scostore.JoinSetStore) CollectionTable(org.datanucleus.store.rdbms.table.CollectionTable) FKMapStore(org.datanucleus.store.rdbms.scostore.FKMapStore) FKSetStore(org.datanucleus.store.rdbms.scostore.FKSetStore) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) FKArrayStore(org.datanucleus.store.rdbms.scostore.FKArrayStore) PersistableJoinTable(org.datanucleus.store.rdbms.table.PersistableJoinTable) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) NoTableManagedException(org.datanucleus.store.rdbms.exceptions.NoTableManagedException) FKListStore(org.datanucleus.store.rdbms.scostore.FKListStore) JoinPersistableRelationStore(org.datanucleus.store.rdbms.scostore.JoinPersistableRelationStore)

Example 3 with ArrayTable

use of org.datanucleus.store.rdbms.table.ArrayTable in project datanucleus-rdbms by datanucleus.

the class QueryToSQLMapper method compileFromClassExpression.

/**
 * Method to take a ClassExpression (in a FROM clause) and process the candidate and any
 * linked JoinExpression(s), adding joins to the SQLStatement as required.
 * @param clsExpr The ClassExpression
 */
protected void compileFromClassExpression(ClassExpression clsExpr) {
    Symbol clsExprSym = clsExpr.getSymbol();
    Class baseCls = (clsExprSym != null ? clsExprSym.getValueType() : null);
    SQLTable candSqlTbl = stmt.getPrimaryTable();
    MetaDataManager mmgr = storeMgr.getMetaDataManager();
    AbstractClassMetaData cmd = mmgr.getMetaDataForClass(baseCls, clr);
    if (baseCls != null && !candidateAlias.equals(clsExpr.getAlias())) {
        // Not candidate class so must be cross join (JPA spec 4.4.5)
        DatastoreClass candTbl = storeMgr.getDatastoreClass(baseCls.getName(), clr);
        candSqlTbl = stmt.join(JoinType.CROSS_JOIN, null, null, null, candTbl, clsExpr.getAlias(), null, null, null, null, true, null);
        SQLTableMapping tblMapping = new SQLTableMapping(candSqlTbl, cmd, candTbl.getIdMapping());
        setSQLTableMappingForAlias(clsExpr.getAlias(), tblMapping);
    }
    if (clsExpr.getCandidateExpression() != null && parentMapper != null) {
        // User defined the candidate of the subquery as an implied join to the outer query
        // e.g SELECT c FROM Customer c WHERE EXISTS (SELECT o FROM c.orders o ...)
        // so add the join(s) to the outer query
        processFromClauseSubquery(clsExpr, candSqlTbl, mmgr);
    }
    // Process all linked JoinExpression(s) for this ClassExpression
    Expression rightExpr = clsExpr.getRight();
    SQLTable sqlTbl = candSqlTbl;
    JavaTypeMapping previousMapping = null;
    while (rightExpr != null) {
        if (rightExpr instanceof JoinExpression) {
            JoinExpression joinExpr = (JoinExpression) rightExpr;
            JoinExpression.JoinType exprJoinType = joinExpr.getType();
            JoinType joinType = org.datanucleus.store.rdbms.sql.SQLJoin.getJoinTypeForJoinExpressionType(exprJoinType);
            Expression joinedExpr = joinExpr.getJoinedExpression();
            Expression joinOnExpr = joinExpr.getOnExpression();
            String joinAlias = joinExpr.getAlias();
            PrimaryExpression joinPrimExpr = null;
            Class castCls = null;
            if (joinedExpr instanceof PrimaryExpression) {
                joinPrimExpr = (PrimaryExpression) joinedExpr;
            } else if (joinedExpr instanceof DyadicExpression && joinedExpr.getOperator() == Expression.OP_CAST) {
                // TREAT this join as a particular type. Cast type is processed below where we add the joins
                joinPrimExpr = (PrimaryExpression) joinedExpr.getLeft();
                String castClassName = (String) ((Literal) joinedExpr.getRight()).getLiteral();
                castCls = clr.classForName(castClassName);
            } else {
                throw new NucleusException("We do not currently support JOIN to " + joinedExpr);
            }
            Iterator<String> iter = joinPrimExpr.getTuples().iterator();
            String rootId = iter.next();
            if (joinPrimExpr.getTuples().size() == 1 && !rootId.endsWith("#KEY") && !rootId.endsWith("#VALUE")) {
                // DN Extension : Join to (new) root element? We need an ON expression to be supplied in this case
                if (joinOnExpr == null) {
                    throw new NucleusUserException("Query has join to " + joinPrimExpr.getId() + " yet this is a root component and there is no ON expression");
                }
                // Add the basic join first with no condition since this root will be referenced in the "on" condition
                baseCls = resolveClass(joinPrimExpr.getId());
                DatastoreClass baseTbl = storeMgr.getDatastoreClass(baseCls.getName(), clr);
                sqlTbl = stmt.join(joinType, candSqlTbl, baseTbl, joinAlias, null, null, true);
                cmd = mmgr.getMetaDataForClass(baseCls, clr);
                SQLTableMapping tblMapping = new SQLTableMapping(sqlTbl, cmd, baseTbl.getIdMapping());
                setSQLTableMappingForAlias(joinAlias, tblMapping);
                // Convert the ON expression to a BooleanExpression and add to the join
                processingOnClause = true;
                joinOnExpr.evaluate(this);
                BooleanExpression joinOnSqlExpr = (BooleanExpression) stack.pop();
                processingOnClause = false;
                stmt.addAndConditionToJoinForTable(sqlTbl, joinOnSqlExpr, true);
                // Move on to next join in the chain
                rightExpr = rightExpr.getRight();
                continue;
            }
            String joinTableGroupName = null;
            SQLTable tblMappingSqlTbl = null;
            JavaTypeMapping tblIdMapping = null;
            AbstractMemberMetaData tblMmd = null;
            boolean mapKey = false;
            boolean mapValue = false;
            String rootComponent = rootId;
            if (rootComponent.endsWith("#KEY")) {
                mapKey = true;
                rootComponent = rootComponent.substring(0, rootComponent.length() - 4);
            } else if (rootComponent.endsWith("#VALUE")) {
                mapValue = true;
                rootComponent = rootComponent.substring(0, rootComponent.length() - 6);
            }
            if (rootComponent.equalsIgnoreCase(candidateAlias)) {
                // Join relative to the candidate
                // Name table group of joined-to as per the relation
                // Note : this will only work for one level out from the candidate TODO Extend this
                cmd = candidateCmd;
                joinTableGroupName = joinPrimExpr.getId();
                sqlTbl = candSqlTbl;
            } else {
                // Join relative to some other alias
                SQLTableMapping sqlTblMapping = getSQLTableMappingForAlias(rootComponent);
                if (sqlTblMapping != null) {
                    if (sqlTblMapping.mmd != null && (mapKey || mapValue)) {
                        // First component is Map-related (i.e m#KEY, m#VALUE), so add any necessary join(s)
                        MapMetaData mapmd = sqlTblMapping.mmd.getMap();
                        cmd = mapKey ? mapmd.getKeyClassMetaData(clr) : mapmd.getValueClassMetaData(clr);
                        // Find the table forming the Map. This may be a join table, or the key or value depending on the type
                        // TODO Use OPTION_CASE_INSENSITIVE
                        sqlTbl = stmt.getTable(rootComponent + "_MAP");
                        if (sqlTbl == null) {
                            sqlTbl = stmt.getTable((rootComponent + "_MAP").toUpperCase());
                            if (sqlTbl == null) {
                                sqlTbl = stmt.getTable((rootComponent + "_MAP").toLowerCase());
                            }
                        }
                        String aliasForJoin = (iter.hasNext()) ? null : joinAlias;
                        boolean embedded = mapKey ? (mapmd.isEmbeddedKey() || mapmd.isSerializedKey()) : (mapmd.isEmbeddedValue() || mapmd.isSerializedValue());
                        if (mapmd.getMapType() == MapType.MAP_TYPE_JOIN) {
                            // Join from join table to KEY/VALUE as required
                            if (!embedded) {
                                if (mapKey) {
                                    DatastoreClass keyTable = storeMgr.getDatastoreClass(mapmd.getKeyType(), clr);
                                    sqlTbl = stmt.join(joinType, sqlTbl, ((MapTable) sqlTbl.getTable()).getKeyMapping(), keyTable, aliasForJoin, keyTable.getIdMapping(), null, joinTableGroupName, true);
                                } else {
                                    DatastoreClass valueTable = storeMgr.getDatastoreClass(mapmd.getValueType(), clr);
                                    sqlTbl = stmt.join(joinType, sqlTbl, ((MapTable) sqlTbl.getTable()).getValueMapping(), valueTable, aliasForJoin, valueTable.getIdMapping(), null, joinTableGroupName, true);
                                }
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                            }
                        } else if (mapmd.getMapType() == MapType.MAP_TYPE_KEY_IN_VALUE) {
                        // TODO Cater for this type
                        } else if (mapmd.getMapType() == MapType.MAP_TYPE_VALUE_IN_KEY) {
                        // TODO Cater for this type
                        }
                    } else {
                        cmd = sqlTblMapping.cmd;
                        sqlTbl = sqlTblMapping.table;
                    }
                    joinTableGroupName = sqlTbl.getGroupName() + joinPrimExpr.getId().substring(rootComponent.length());
                } else {
                    throw new NucleusUserException("Query has " + joinPrimExpr.getId() + " yet the first component " + rootComponent + " is unknown!");
                }
            }
            while (iter.hasNext()) {
                String id = iter.next();
                String[] ids = id.contains(".") ? StringUtils.split(id, ".") : new String[] { id };
                for (int k = 0; k < ids.length; k++) {
                    if (cmd == null) {
                        throw new NucleusUserException("Error in JOIN clause. id=" + id + " but component prior to " + ids[k] + " has no metadata");
                    }
                    boolean lastComponent = (k == ids.length - 1);
                    String thisComponent = ids[k];
                    mapKey = false;
                    mapValue = false;
                    if (thisComponent.endsWith("#KEY")) {
                        thisComponent = thisComponent.substring(0, thisComponent.length() - 4);
                        mapKey = true;
                    } else if (thisComponent.endsWith("#VALUE")) {
                        thisComponent = thisComponent.substring(0, thisComponent.length() - 6);
                        mapValue = true;
                    }
                    AbstractMemberMetaData mmd = cmd.getMetaDataForMember(thisComponent);
                    if (mmd == null) {
                        if (exprJoinType == JoinExpression.JoinType.JOIN_LEFT_OUTER || exprJoinType == JoinExpression.JoinType.JOIN_LEFT_OUTER_FETCH) {
                            // Polymorphic join, where the field exists in a subclass (doable since we have outer join)
                            String[] subclasses = mmgr.getSubclassesForClass(cmd.getFullClassName(), true);
                            for (int l = 0; l < subclasses.length; l++) {
                                AbstractClassMetaData subCmd = mmgr.getMetaDataForClass(subclasses[l], clr);
                                if (subCmd != null) {
                                    mmd = subCmd.getMetaDataForMember(thisComponent);
                                    if (mmd != null) {
                                        cmd = subCmd;
                                        break;
                                    }
                                }
                            }
                        }
                        if (mmd == null) {
                            throw new NucleusUserException("Query has " + joinPrimExpr.getId() + " yet " + thisComponent + " is not found. Fix your input");
                        }
                    }
                    tblMmd = null;
                    String aliasForJoin = null;
                    if (k == (ids.length - 1) && !iter.hasNext()) {
                        aliasForJoin = joinAlias;
                    }
                    RelationType relationType = mmd.getRelationType(clr);
                    DatastoreClass relTable = null;
                    AbstractMemberMetaData relMmd = null;
                    if (relationType != RelationType.NONE) {
                        if (JoinExpression.JoinType.isFetch(exprJoinType)) {
                            // Add field to FetchPlan since marked for FETCH
                            String fgName = "QUERY_FETCH_" + mmd.getFullFieldName();
                            FetchGroupManager fetchGrpMgr = storeMgr.getNucleusContext().getFetchGroupManager();
                            if (fetchGrpMgr.getFetchGroupsWithName(fgName) == null) {
                                FetchGroup grp = new FetchGroup(storeMgr.getNucleusContext(), fgName, clr.classForName(cmd.getFullClassName()));
                                grp.addMember(mmd.getName());
                                fetchGrpMgr.addFetchGroup(grp);
                            }
                            fetchPlan.addGroup(fgName);
                        }
                    }
                    if (relationType == RelationType.ONE_TO_ONE_UNI) {
                        JavaTypeMapping otherMapping = null;
                        Object[] castDiscrimValues = null;
                        if (castCls != null && lastComponent) {
                            cmd = mmgr.getMetaDataForClass(castCls, clr);
                            if (cmd.hasDiscriminatorStrategy()) {
                                // Restrict discriminator on cast type to be the type+subclasses
                                castDiscrimValues = getDiscriminatorValuesForCastClass(cmd);
                            }
                        } else {
                            cmd = mmgr.getMetaDataForClass(mmd.getType(), clr);
                        }
                        if (mmd.isEmbedded()) {
                            // Embedded into the same table as before, so no join needed
                            otherMapping = sqlTbl.getTable().getMemberMapping(mmd);
                        } else {
                            if (sqlTbl.getTable() instanceof CollectionTable) {
                                // Currently in a join table, so work from the element and this being an embedded member
                                CollectionTable collTbl = (CollectionTable) sqlTbl.getTable();
                                JavaTypeMapping elemMapping = collTbl.getElementMapping();
                                if (elemMapping instanceof EmbeddedMapping) {
                                    otherMapping = ((EmbeddedMapping) elemMapping).getJavaTypeMapping(mmd.getName());
                                }
                            } else {
                                otherMapping = sqlTbl.getTable().getMemberMapping(mmd);
                            }
                            relTable = storeMgr.getDatastoreClass(mmd.getTypeName(), clr);
                            if (otherMapping == null && previousMapping != null) {
                                if (previousMapping instanceof EmbeddedMapping) {
                                    // Part of an embedded 1-1 object, so find the relevant member mapping
                                    EmbeddedMapping embMapping = (EmbeddedMapping) previousMapping;
                                    otherMapping = embMapping.getJavaTypeMapping(mmd.getName());
                                }
                            }
                            if (otherMapping == null) {
                                // Polymorphic join? : cannot find this member in the candidate of the main statement, so need to pick which UNION
                                String tblGroupName = sqlTbl.getGroupName();
                                SQLTableGroup grp = stmt.getTableGroup(tblGroupName);
                                SQLTable nextSqlTbl = null;
                                // Try to find subtable in the same group that has a mapping for this member (and join from that)
                                SQLTable[] grpTbls = grp.getTables();
                                for (SQLTable grpTbl : grpTbls) {
                                    if (grpTbl.getTable().getMemberMapping(mmd) != null) {
                                        otherMapping = grpTbl.getTable().getMemberMapping(mmd);
                                        break;
                                    }
                                }
                                SQLTable newSqlTbl = stmt.join(joinType, sqlTbl, otherMapping, relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, false);
                                if (newSqlTbl != null) {
                                    nextSqlTbl = newSqlTbl;
                                }
                                if (stmt instanceof SelectStatement) {
                                    List<SelectStatement> unionStmts = ((SelectStatement) stmt).getUnions();
                                    if (unionStmts != null) {
                                        for (SQLStatement unionStmt : unionStmts) {
                                            // Repeat the process for any unioned statements, find a subtable in the same group (and join from that)
                                            otherMapping = null;
                                            grp = unionStmt.getTableGroup(tblGroupName);
                                            SQLTable[] unionGrpTbls = grp.getTables();
                                            for (SQLTable grpTbl : unionGrpTbls) {
                                                if (grpTbl.getTable().getMemberMapping(mmd) != null) {
                                                    otherMapping = grpTbl.getTable().getMemberMapping(mmd);
                                                    break;
                                                }
                                            }
                                            newSqlTbl = unionStmt.join(joinType, sqlTbl, otherMapping, relTable, aliasForJoin, relTable.getIdMapping(), castDiscrimValues, joinTableGroupName, false);
                                            if (newSqlTbl != null) {
                                                nextSqlTbl = newSqlTbl;
                                            }
                                        }
                                    }
                                }
                                sqlTbl = nextSqlTbl;
                            } else {
                                sqlTbl = stmt.join(joinType, sqlTbl, otherMapping, relTable, aliasForJoin, relTable.getIdMapping(), castDiscrimValues, joinTableGroupName, true);
                            }
                        }
                        previousMapping = otherMapping;
                        tblIdMapping = sqlTbl.getTable().getIdMapping();
                        tblMappingSqlTbl = sqlTbl;
                    } else if (relationType == RelationType.ONE_TO_ONE_BI) {
                        JavaTypeMapping otherMapping = null;
                        Object[] castDiscrimValues = null;
                        if (castCls != null && lastComponent) {
                            cmd = mmgr.getMetaDataForClass(castCls, clr);
                            if (cmd.hasDiscriminatorStrategy()) {
                                // Restrict discriminator on cast type to be the type+subclasses
                                castDiscrimValues = getDiscriminatorValuesForCastClass(cmd);
                            }
                        } else {
                            cmd = mmgr.getMetaDataForClass(mmd.getType(), clr);
                        }
                        if (mmd.isEmbedded()) {
                            // Embedded into the same table as before, so no join needed
                            otherMapping = sqlTbl.getTable().getMemberMapping(mmd);
                        } else {
                            relTable = storeMgr.getDatastoreClass(mmd.getTypeName(), clr);
                            if (mmd.getMappedBy() != null) {
                                relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                                JavaTypeMapping relMapping = relTable.getMemberMapping(relMmd);
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), relTable, aliasForJoin, relMapping, castDiscrimValues, joinTableGroupName, true);
                            } else {
                                if (sqlTbl.getTable() instanceof CollectionTable) {
                                    // Currently in a join table, so work from the element and this being an embedded member
                                    CollectionTable collTbl = (CollectionTable) sqlTbl.getTable();
                                    JavaTypeMapping elemMapping = collTbl.getElementMapping();
                                    if (elemMapping instanceof EmbeddedMapping) {
                                        otherMapping = ((EmbeddedMapping) elemMapping).getJavaTypeMapping(mmd.getName());
                                    }
                                } else {
                                    otherMapping = sqlTbl.getTable().getMemberMapping(mmd);
                                }
                                if (otherMapping == null && previousMapping != null) {
                                    if (previousMapping instanceof EmbeddedMapping) {
                                        // Part of an embedded 1-1 object, so find the relevant member mapping
                                        EmbeddedMapping embMapping = (EmbeddedMapping) previousMapping;
                                        otherMapping = embMapping.getJavaTypeMapping(mmd.getName());
                                    }
                                }
                                sqlTbl = stmt.join(joinType, sqlTbl, otherMapping, relTable, aliasForJoin, relTable.getIdMapping(), castDiscrimValues, joinTableGroupName, true);
                            }
                        }
                        previousMapping = otherMapping;
                        tblIdMapping = sqlTbl.getTable().getIdMapping();
                        tblMappingSqlTbl = sqlTbl;
                    } else if (relationType == RelationType.ONE_TO_MANY_BI) {
                        previousMapping = null;
                        if (mmd.hasCollection()) {
                            // Join across COLLECTION relation
                            cmd = mmd.getCollection().getElementClassMetaData(clr);
                            if (mmd.getCollection().isEmbeddedElement() && mmd.getJoinMetaData() != null) {
                                // Embedded element stored in (collection) join table
                                CollectionTable relEmbTable = (CollectionTable) storeMgr.getTable(mmd);
                                JavaTypeMapping relOwnerMapping = relEmbTable.getOwnerMapping();
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), relEmbTable, aliasForJoin, relOwnerMapping, null, joinTableGroupName, true);
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = relEmbTable.getElementMapping();
                            } else {
                                relTable = storeMgr.getDatastoreClass(mmd.getCollection().getElementType(), clr);
                                relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                                if (mmd.getJoinMetaData() != null || relMmd.getJoinMetaData() != null) {
                                    // Join to join table, then to related table
                                    ElementContainerTable joinTbl = (ElementContainerTable) storeMgr.getTable(mmd);
                                    SQLTable joinSqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, null, joinTbl.getOwnerMapping(), null, null, true);
                                    sqlTbl = stmt.join(joinType, joinSqlTbl, joinTbl.getElementMapping(), relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                } else {
                                    // Join to related table FK
                                    sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), relTable, aliasForJoin, relTable.getMemberMapping(relMmd), null, joinTableGroupName, true);
                                }
                                tblIdMapping = sqlTbl.getTable().getIdMapping();
                                tblMappingSqlTbl = sqlTbl;
                            }
                        } else if (mmd.hasMap()) {
                            // Join across MAP relation
                            MapMetaData mapmd = mmd.getMap();
                            cmd = mapmd.getValueClassMetaData(clr);
                            tblMmd = mmd;
                            boolean embedded = mapKey ? (mapmd.isEmbeddedKey() || mapmd.isSerializedKey()) : (mapmd.isEmbeddedValue() || mapmd.isSerializedValue());
                            if (mapmd.getMapType() == MapType.MAP_TYPE_JOIN) {
                                // Add join to join table, then to related table (value)
                                MapTable joinTbl = (MapTable) storeMgr.getTable(mmd);
                                String aliasForMap = embedded ? aliasForJoin : (aliasForJoin + "_MAP");
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, aliasForMap, joinTbl.getOwnerMapping(), null, null, true);
                                if (embedded) {
                                    tblMappingSqlTbl = sqlTbl;
                                    tblIdMapping = mapKey ? joinTbl.getKeyMapping() : joinTbl.getValueMapping();
                                } else {
                                    if (mapKey) {
                                        // Join to key table and use that
                                        relTable = storeMgr.getDatastoreClass(mapmd.getKeyType(), clr);
                                        sqlTbl = stmt.join(joinType, sqlTbl, joinTbl.getKeyMapping(), relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                    // TODO if there is an ON clause it needs to go on the correct join See [rdbms-177]
                                    } else {
                                        // Join to value table and use that
                                        relTable = storeMgr.getDatastoreClass(mapmd.getValueType(), clr);
                                        sqlTbl = stmt.join(joinType, sqlTbl, joinTbl.getValueMapping(), relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                    // TODO if there is an ON clause it needs to go on the correct join See [rdbms-177]
                                    }
                                    tblMappingSqlTbl = sqlTbl;
                                    tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                                }
                            } else if (mapmd.getMapType() == MapType.MAP_TYPE_KEY_IN_VALUE) {
                                // Join to value table
                                DatastoreClass valTable = storeMgr.getDatastoreClass(mapmd.getValueType(), clr);
                                JavaTypeMapping mapTblOwnerMapping;
                                if (mmd.getMappedBy() != null) {
                                    mapTblOwnerMapping = valTable.getMemberMapping(mapmd.getValueClassMetaData(clr).getMetaDataForMember(mmd.getMappedBy()));
                                } else {
                                    mapTblOwnerMapping = valTable.getExternalMapping(mmd, MappingType.EXTERNAL_FK);
                                }
                                String aliasForMap = (embedded || !mapKey) ? aliasForJoin : (aliasForJoin + "_MAP");
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), valTable, aliasForMap, mapTblOwnerMapping, null, null, true);
                                if (!embedded) {
                                    if (mapKey) {
                                        // Join to key table
                                        JavaTypeMapping keyMapping = valTable.getMemberMapping(mmd.getKeyMetaData().getMappedBy());
                                        relTable = storeMgr.getDatastoreClass(mapmd.getKeyType(), clr);
                                        sqlTbl = stmt.join(joinType, sqlTbl, keyMapping, relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                    // TODO if there is an ON clause it needs to go on the correct join See [rdbms-177]
                                    }
                                }
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                            } else if (mapmd.getMapType() == MapType.MAP_TYPE_VALUE_IN_KEY) {
                                // Join to key table, and then to value table
                                DatastoreClass keyTable = storeMgr.getDatastoreClass(mapmd.getKeyType(), clr);
                                JavaTypeMapping mapTblOwnerMapping;
                                if (mmd.getMappedBy() != null) {
                                    mapTblOwnerMapping = keyTable.getMemberMapping(mapmd.getKeyClassMetaData(clr).getMetaDataForMember(mmd.getMappedBy()));
                                } else {
                                    mapTblOwnerMapping = keyTable.getExternalMapping(mmd, MappingType.EXTERNAL_FK);
                                }
                                String aliasForMap = (embedded || mapKey) ? aliasForJoin : (aliasForJoin + "_MAP");
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), keyTable, aliasForMap, mapTblOwnerMapping, null, null, true);
                                if (!embedded) {
                                    if (!mapKey) {
                                        // Join to value table
                                        JavaTypeMapping valueMapping = keyTable.getMemberMapping(mmd.getValueMetaData().getMappedBy());
                                        relTable = storeMgr.getDatastoreClass(mapmd.getValueType(), clr);
                                        sqlTbl = stmt.join(joinType, sqlTbl, valueMapping, relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                    // TODO if there is an ON clause it needs to go on the correct join See [rdbms-177]
                                    }
                                }
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                            }
                        } else if (mmd.hasArray()) {
                            // Join across ARRAY relation
                            cmd = mmd.getArray().getElementClassMetaData(clr);
                            if (mmd.getArray().isEmbeddedElement() && mmd.getJoinMetaData() != null) {
                                // Embedded element stored in (array) join table
                                ArrayTable relEmbTable = (ArrayTable) storeMgr.getTable(mmd);
                                JavaTypeMapping relOwnerMapping = relEmbTable.getOwnerMapping();
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), relEmbTable, aliasForJoin, relOwnerMapping, null, joinTableGroupName, true);
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = relEmbTable.getElementMapping();
                            } else {
                                relTable = storeMgr.getDatastoreClass(mmd.getArray().getElementType(), clr);
                                relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                                if (mmd.getJoinMetaData() != null || relMmd.getJoinMetaData() != null) {
                                    // Join to join table, then to related table
                                    ElementContainerTable joinTbl = (ElementContainerTable) storeMgr.getTable(mmd);
                                    SQLTable joinSqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, null, joinTbl.getOwnerMapping(), null, null, true);
                                    sqlTbl = stmt.join(joinType, joinSqlTbl, joinTbl.getElementMapping(), relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                // TODO if there is an ON clause it needs to go on the correct join See [rdbms-177]
                                } else {
                                    // Join to related table FK
                                    sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), relTable, aliasForJoin, relTable.getMemberMapping(relMmd), null, joinTableGroupName, true);
                                }
                                tblIdMapping = sqlTbl.getTable().getIdMapping();
                                tblMappingSqlTbl = sqlTbl;
                            }
                        }
                    } else if (relationType == RelationType.ONE_TO_MANY_UNI) {
                        previousMapping = null;
                        if (mmd.hasCollection()) {
                            // Join across COLLECTION relation
                            cmd = mmd.getCollection().getElementClassMetaData(clr);
                            if (mmd.getCollection().isEmbeddedElement() && mmd.getJoinMetaData() != null) {
                                // Embedded element stored in (collection) join table
                                CollectionTable relEmbTable = (CollectionTable) storeMgr.getTable(mmd);
                                JavaTypeMapping relOwnerMapping = relEmbTable.getOwnerMapping();
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), relEmbTable, aliasForJoin, relOwnerMapping, null, joinTableGroupName, true);
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = relEmbTable.getElementMapping();
                            } else {
                                relTable = storeMgr.getDatastoreClass(mmd.getCollection().getElementType(), clr);
                                if (mmd.getJoinMetaData() != null) {
                                    // Join to join table, then to related table
                                    ElementContainerTable joinTbl = (ElementContainerTable) storeMgr.getTable(mmd);
                                    SQLTable joinSqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, null, joinTbl.getOwnerMapping(), null, null, true);
                                    sqlTbl = stmt.join(joinType, joinSqlTbl, joinTbl.getElementMapping(), relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                // TODO if there is an ON clause it needs to go on the correct join See [rdbms-177]
                                } else {
                                    // Join to related table FK
                                    JavaTypeMapping relMapping = relTable.getExternalMapping(mmd, MappingType.EXTERNAL_FK);
                                    sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), relTable, aliasForJoin, relMapping, null, joinTableGroupName, true);
                                }
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                            }
                        } else if (mmd.hasMap()) {
                            // Join across MAP relation
                            MapMetaData mapmd = mmd.getMap();
                            cmd = mapmd.getValueClassMetaData(clr);
                            tblMmd = mmd;
                            boolean embedded = mapKey ? (mapmd.isEmbeddedKey() || mapmd.isSerializedKey()) : (mapmd.isEmbeddedValue() || mapmd.isSerializedValue());
                            if (mapmd.getMapType() == MapType.MAP_TYPE_JOIN) {
                                // Add join to join table, then to related table (value)
                                MapTable joinTbl = (MapTable) storeMgr.getTable(mmd);
                                String aliasForMap = (embedded || mapKey) ? aliasForJoin : (aliasForJoin + "_MAP");
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, aliasForMap, joinTbl.getOwnerMapping(), null, null, true);
                                if (embedded) {
                                    tblMappingSqlTbl = sqlTbl;
                                    tblIdMapping = mapKey ? joinTbl.getKeyMapping() : joinTbl.getValueMapping();
                                } else {
                                    if (mapKey) {
                                        // Join to key table and use that
                                        relTable = storeMgr.getDatastoreClass(mapmd.getKeyType(), clr);
                                        sqlTbl = stmt.join(joinType, sqlTbl, joinTbl.getKeyMapping(), relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                    // TODO if there is an ON clause it needs to go on the correct join See [rdbms-177]
                                    } else {
                                        // Join to value table and use that
                                        relTable = storeMgr.getDatastoreClass(mapmd.getValueType(), clr);
                                        sqlTbl = stmt.join(joinType, sqlTbl, joinTbl.getValueMapping(), relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                    // TODO if there is an ON clause it needs to go on the correct join See [rdbms-177]
                                    }
                                    tblMappingSqlTbl = sqlTbl;
                                    tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                                }
                            } else if (mapmd.getMapType() == MapType.MAP_TYPE_KEY_IN_VALUE) {
                                // Join to value table
                                DatastoreClass valTable = storeMgr.getDatastoreClass(mapmd.getValueType(), clr);
                                JavaTypeMapping mapTblOwnerMapping;
                                if (mmd.getMappedBy() != null) {
                                    mapTblOwnerMapping = valTable.getMemberMapping(mapmd.getValueClassMetaData(clr).getMetaDataForMember(mmd.getMappedBy()));
                                } else {
                                    mapTblOwnerMapping = valTable.getExternalMapping(mmd, MappingType.EXTERNAL_FK);
                                }
                                String aliasForMap = (embedded || !mapKey) ? aliasForJoin : (aliasForJoin + "_MAP");
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), valTable, aliasForMap, mapTblOwnerMapping, null, null, true);
                                if (!embedded) {
                                    if (mapKey) {
                                        // Join to key table
                                        JavaTypeMapping keyMapping = valTable.getMemberMapping(mmd.getKeyMetaData().getMappedBy());
                                        relTable = storeMgr.getDatastoreClass(mapmd.getKeyType(), clr);
                                        sqlTbl = stmt.join(joinType, sqlTbl, keyMapping, relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                    // TODO if there is an ON clause it needs to go on the correct join See [rdbms-177]
                                    }
                                }
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                            } else if (mapmd.getMapType() == MapType.MAP_TYPE_VALUE_IN_KEY) {
                                // Join to key table, and then to value table
                                DatastoreClass keyTable = storeMgr.getDatastoreClass(mapmd.getKeyType(), clr);
                                JavaTypeMapping mapTblOwnerMapping;
                                if (mmd.getMappedBy() != null) {
                                    mapTblOwnerMapping = keyTable.getMemberMapping(mapmd.getKeyClassMetaData(clr).getMetaDataForMember(mmd.getMappedBy()));
                                } else {
                                    mapTblOwnerMapping = keyTable.getExternalMapping(mmd, MappingType.EXTERNAL_FK);
                                }
                                String aliasForMap = (embedded || mapKey) ? aliasForJoin : (aliasForJoin + "_MAP");
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), keyTable, aliasForMap, mapTblOwnerMapping, null, null, true);
                                if (!embedded) {
                                    if (!mapKey) {
                                        // Join to value table
                                        JavaTypeMapping valueMapping = keyTable.getMemberMapping(mmd.getValueMetaData().getMappedBy());
                                        relTable = storeMgr.getDatastoreClass(mapmd.getValueType(), clr);
                                        sqlTbl = stmt.join(joinType, sqlTbl, valueMapping, relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                    // TODO if there is an ON clause it needs to go on the correct join See [rdbms-177]
                                    }
                                }
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                            }
                        } else if (mmd.hasArray()) {
                            // Join across ARRAY relation
                            cmd = mmd.getArray().getElementClassMetaData(clr);
                            if (mmd.getArray().isEmbeddedElement() && mmd.getJoinMetaData() != null) {
                                // Embedded element stored in (array) join table
                                ArrayTable relEmbTable = (ArrayTable) storeMgr.getTable(mmd);
                                JavaTypeMapping relOwnerMapping = relEmbTable.getOwnerMapping();
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), relEmbTable, aliasForJoin, relOwnerMapping, null, joinTableGroupName, true);
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = relEmbTable.getElementMapping();
                            } else {
                                relTable = storeMgr.getDatastoreClass(mmd.getArray().getElementType(), clr);
                                if (mmd.getJoinMetaData() != null) {
                                    // Join to join table, then to related table
                                    ElementContainerTable joinTbl = (ElementContainerTable) storeMgr.getTable(mmd);
                                    SQLTable joinSqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, null, joinTbl.getOwnerMapping(), null, null, true);
                                    sqlTbl = stmt.join(joinType, joinSqlTbl, joinTbl.getElementMapping(), relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                                } else {
                                    // Join to related table FK
                                    JavaTypeMapping relMapping = relTable.getExternalMapping(mmd, MappingType.EXTERNAL_FK);
                                    sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), relTable, aliasForJoin, relMapping, null, joinTableGroupName, true);
                                }
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                            }
                        }
                    } else if (relationType == RelationType.MANY_TO_MANY_BI) {
                        previousMapping = null;
                        relTable = storeMgr.getDatastoreClass(mmd.getCollection().getElementType(), clr);
                        cmd = mmd.getCollection().getElementClassMetaData(clr);
                        relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                        // Join to join table, then to related table
                        if (mmd.hasCollection()) {
                            CollectionTable joinTbl = (CollectionTable) storeMgr.getTable(mmd);
                            SQLTable joinSqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, null, joinTbl.getOwnerMapping(), null, null, true);
                            sqlTbl = stmt.join(joinType, joinSqlTbl, joinTbl.getElementMapping(), relTable, aliasForJoin, relTable.getIdMapping(), null, joinTableGroupName, true);
                        } else if (mmd.hasMap()) {
                            NucleusLogger.QUERY.warn("We do not support joining across a M-N MAP field : " + mmd.getFullFieldName());
                        } else if (mmd.hasArray()) {
                            NucleusLogger.QUERY.warn("We do not support joining across a M-N ARRAY field : " + mmd.getFullFieldName());
                        }
                        tblMappingSqlTbl = sqlTbl;
                        tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                    } else if (relationType == RelationType.MANY_TO_ONE_BI) {
                        previousMapping = null;
                        relTable = storeMgr.getDatastoreClass(mmd.getTypeName(), clr);
                        Object[] castDiscrimValues = null;
                        if (castCls != null && lastComponent) {
                            cmd = mmgr.getMetaDataForClass(castCls, clr);
                            if (cmd.hasDiscriminatorStrategy()) {
                                // Restrict discriminator on cast type to be the type+subclasses
                                castDiscrimValues = getDiscriminatorValuesForCastClass(cmd);
                            }
                        } else {
                            cmd = mmgr.getMetaDataForClass(mmd.getType(), clr);
                        }
                        relMmd = mmd.getRelatedMemberMetaData(clr)[0];
                        if (mmd.getJoinMetaData() != null || relMmd.getJoinMetaData() != null) {
                            // Join to join table, then to related table
                            if (mmd.hasCollection()) {
                                CollectionTable joinTbl = (CollectionTable) storeMgr.getTable(relMmd);
                                SQLTable joinSqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, null, joinTbl.getElementMapping(), null, null, true);
                                sqlTbl = stmt.join(joinType, joinSqlTbl, joinTbl.getOwnerMapping(), relTable, aliasForJoin, relTable.getIdMapping(), castDiscrimValues, joinTableGroupName, true);
                            } else if (mmd.hasMap()) {
                                NucleusLogger.QUERY.warn("We do not support joining across a N-1 MAP field : " + mmd.getFullFieldName());
                            } else if (mmd.hasArray()) {
                                NucleusLogger.QUERY.warn("We do not support joining across a N-1 ARRAY field : " + mmd.getFullFieldName());
                            }
                        } else {
                            // Join to owner table
                            JavaTypeMapping fkMapping = sqlTbl.getTable().getMemberMapping(mmd);
                            sqlTbl = stmt.join(joinType, sqlTbl, fkMapping, relTable, aliasForJoin, relTable.getIdMapping(), castDiscrimValues, joinTableGroupName, true);
                        }
                        tblMappingSqlTbl = sqlTbl;
                        tblIdMapping = tblMappingSqlTbl.getTable().getIdMapping();
                    } else {
                        // NO RELATION, but cater for join table cases
                        previousMapping = null;
                        if (mmd.hasCollection()) {
                            cmd = null;
                            if (mmd.getJoinMetaData() != null) {
                                // Join to join table
                                CollectionTable joinTbl = (CollectionTable) storeMgr.getTable(mmd);
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, aliasForJoin, joinTbl.getOwnerMapping(), null, null, true);
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = joinTbl.getElementMapping();
                            } else {
                                throw new NucleusUserException("FROM clause contains join to Collection field at " + mmd.getFullFieldName() + " yet this has no join table");
                            }
                        } else if (mmd.hasMap()) {
                            MapMetaData mapmd = mmd.getMap();
                            cmd = mapmd.getValueClassMetaData(clr);
                            tblMmd = mmd;
                            if (// Should be the only type when relationType is NONE
                            mapmd.getMapType() == MapType.MAP_TYPE_JOIN) {
                                // Add join to join table
                                MapTable joinTbl = (MapTable) storeMgr.getTable(mmd);
                                String aliasForMap = aliasForJoin;
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, aliasForMap, joinTbl.getOwnerMapping(), null, null, true);
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = joinTbl.getValueMapping();
                            } else {
                                throw new NucleusUserException("FROM clause contains join to Map field at " + mmd.getFullFieldName() + " yet this has no join table");
                            }
                        } else if (mmd.hasArray()) {
                            cmd = null;
                            if (mmd.getJoinMetaData() != null) {
                                // Join to join table
                                ArrayTable joinTbl = (ArrayTable) storeMgr.getTable(mmd);
                                sqlTbl = stmt.join(joinType, sqlTbl, sqlTbl.getTable().getIdMapping(), joinTbl, aliasForJoin, joinTbl.getOwnerMapping(), null, null, true);
                                tblMappingSqlTbl = sqlTbl;
                                tblIdMapping = joinTbl.getElementMapping();
                            } else {
                                throw new NucleusUserException("FROM clause contains join to array field at " + mmd.getFullFieldName() + " yet this has no join table");
                            }
                        }
                    }
                }
            }
            if (joinAlias != null) {
                if (explicitJoinPrimaryByAlias == null) {
                    explicitJoinPrimaryByAlias = new HashMap<>();
                }
                explicitJoinPrimaryByAlias.put(joinAlias, joinPrimExpr.getId());
                SQLTableMapping tblMapping = null;
                if (tblMmd != null) {
                    // Maps store the member so we can more easily navigate to the key/value
                    tblMapping = new SQLTableMapping(tblMappingSqlTbl, cmd, tblMmd, tblIdMapping);
                } else {
                    tblMapping = new SQLTableMapping(tblMappingSqlTbl, cmd, tblIdMapping);
                }
                setSQLTableMappingForAlias(joinAlias, tblMapping);
            }
            if (joinOnExpr != null) {
                // Convert the ON expression to a BooleanExpression
                processingOnClause = true;
                joinOnExpr.evaluate(this);
                BooleanExpression joinOnSqlExpr = (BooleanExpression) stack.pop();
                processingOnClause = false;
                // Add the ON expression to the most recent SQLTable at the end of this chain
                // TODO Allow for SQL JOIN "grouping" [rdbms-177]. This applies to all cases where we join to a join table then to an element/value table and
                // need to apply the ON clause across both
                SQLJoin join = stmt.getJoinForTable(sqlTbl);
                join.addAndCondition(joinOnSqlExpr);
            }
        } else {
            previousMapping = null;
        }
        // Move on to next join in the chain
        rightExpr = rightExpr.getRight();
    }
}
Also used : FetchGroupManager(org.datanucleus.FetchGroupManager) PrimaryExpression(org.datanucleus.query.expression.PrimaryExpression) JavaTypeMapping(org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping) Symbol(org.datanucleus.query.compiler.Symbol) SQLJoin(org.datanucleus.store.rdbms.sql.SQLJoin) SQLStatement(org.datanucleus.store.rdbms.sql.SQLStatement) AbstractClassMetaData(org.datanucleus.metadata.AbstractClassMetaData) MapTable(org.datanucleus.store.rdbms.table.MapTable) SelectStatement(org.datanucleus.store.rdbms.sql.SelectStatement) BooleanExpression(org.datanucleus.store.rdbms.sql.expression.BooleanExpression) CollectionTable(org.datanucleus.store.rdbms.table.CollectionTable) SQLTable(org.datanucleus.store.rdbms.sql.SQLTable) TemporalLiteral(org.datanucleus.store.rdbms.sql.expression.TemporalLiteral) SQLLiteral(org.datanucleus.store.rdbms.sql.expression.SQLLiteral) ParameterLiteral(org.datanucleus.store.rdbms.sql.expression.ParameterLiteral) BooleanLiteral(org.datanucleus.store.rdbms.sql.expression.BooleanLiteral) IntegerLiteral(org.datanucleus.store.rdbms.sql.expression.IntegerLiteral) Literal(org.datanucleus.query.expression.Literal) NullLiteral(org.datanucleus.store.rdbms.sql.expression.NullLiteral) RelationType(org.datanucleus.metadata.RelationType) JoinExpression(org.datanucleus.query.expression.JoinExpression) ElementContainerTable(org.datanucleus.store.rdbms.table.ElementContainerTable) NucleusUserException(org.datanucleus.exceptions.NucleusUserException) MetaDataManager(org.datanucleus.metadata.MetaDataManager) JoinType(org.datanucleus.store.rdbms.sql.SQLJoin.JoinType) MapMetaData(org.datanucleus.metadata.MapMetaData) DyadicExpression(org.datanucleus.query.expression.DyadicExpression) CaseExpression(org.datanucleus.query.expression.CaseExpression) BooleanSubqueryExpression(org.datanucleus.store.rdbms.sql.expression.BooleanSubqueryExpression) StringExpression(org.datanucleus.store.rdbms.sql.expression.StringExpression) JoinExpression(org.datanucleus.query.expression.JoinExpression) NumericSubqueryExpression(org.datanucleus.store.rdbms.sql.expression.NumericSubqueryExpression) StringSubqueryExpression(org.datanucleus.store.rdbms.sql.expression.StringSubqueryExpression) ClassExpression(org.datanucleus.query.expression.ClassExpression) InvokeExpression(org.datanucleus.query.expression.InvokeExpression) MapExpression(org.datanucleus.store.rdbms.sql.expression.MapExpression) SubqueryExpression(org.datanucleus.query.expression.SubqueryExpression) NewObjectExpression(org.datanucleus.store.rdbms.sql.expression.NewObjectExpression) TemporalSubqueryExpression(org.datanucleus.store.rdbms.sql.expression.TemporalSubqueryExpression) BooleanExpression(org.datanucleus.store.rdbms.sql.expression.BooleanExpression) OrderExpression(org.datanucleus.query.expression.OrderExpression) PrimaryExpression(org.datanucleus.query.expression.PrimaryExpression) SQLExpression(org.datanucleus.store.rdbms.sql.expression.SQLExpression) UnboundExpression(org.datanucleus.store.rdbms.sql.expression.UnboundExpression) TemporalExpression(org.datanucleus.store.rdbms.sql.expression.TemporalExpression) ArrayExpression(org.datanucleus.query.expression.ArrayExpression) ResultAliasExpression(org.datanucleus.store.rdbms.sql.expression.ResultAliasExpression) CreatorExpression(org.datanucleus.query.expression.CreatorExpression) Expression(org.datanucleus.query.expression.Expression) TypeExpression(org.datanucleus.query.expression.TypeExpression) NumericExpression(org.datanucleus.store.rdbms.sql.expression.NumericExpression) CollectionExpression(org.datanucleus.store.rdbms.sql.expression.CollectionExpression) DyadicExpression(org.datanucleus.query.expression.DyadicExpression) ParameterExpression(org.datanucleus.query.expression.ParameterExpression) ColumnExpression(org.datanucleus.store.rdbms.sql.expression.ColumnExpression) VariableExpression(org.datanucleus.query.expression.VariableExpression) EmbeddedMapping(org.datanucleus.store.rdbms.mapping.java.EmbeddedMapping) SQLTableGroup(org.datanucleus.store.rdbms.sql.SQLTableGroup) FetchGroup(org.datanucleus.FetchGroup) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) FetchPlanForClass(org.datanucleus.FetchPlanForClass) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) NucleusException(org.datanucleus.exceptions.NucleusException) ArrayTable(org.datanucleus.store.rdbms.table.ArrayTable) AbstractMemberMetaData(org.datanucleus.metadata.AbstractMemberMetaData)

Aggregations

AbstractClassMetaData (org.datanucleus.metadata.AbstractClassMetaData)3 JavaTypeMapping (org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping)3 NucleusException (org.datanucleus.exceptions.NucleusException)2 AbstractMemberMetaData (org.datanucleus.metadata.AbstractMemberMetaData)2 SQLTable (org.datanucleus.store.rdbms.sql.SQLTable)2 SelectStatement (org.datanucleus.store.rdbms.sql.SelectStatement)2 BooleanSubqueryExpression (org.datanucleus.store.rdbms.sql.expression.BooleanSubqueryExpression)2 ArrayTable (org.datanucleus.store.rdbms.table.ArrayTable)2 DatastoreClass (org.datanucleus.store.rdbms.table.DatastoreClass)2 JoinTable (org.datanucleus.store.rdbms.table.JoinTable)2 ClassLoaderResolver (org.datanucleus.ClassLoaderResolver)1 FetchGroup (org.datanucleus.FetchGroup)1 FetchGroupManager (org.datanucleus.FetchGroupManager)1 FetchPlanForClass (org.datanucleus.FetchPlanForClass)1 NucleusUserException (org.datanucleus.exceptions.NucleusUserException)1 MapMetaData (org.datanucleus.metadata.MapMetaData)1 MetaDataManager (org.datanucleus.metadata.MetaDataManager)1 RelationType (org.datanucleus.metadata.RelationType)1 Symbol (org.datanucleus.query.compiler.Symbol)1 ArrayExpression (org.datanucleus.query.expression.ArrayExpression)1