Search in sources :

Example 1 with ElementContainerTable

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

the class QueryToSQLMapper method processFromClauseSubquery.

/**
 * Method to process a ClassExpression where it represents a subquery.
 * User defined the candidate of the subquery as an implied join to the outer query, for example "SELECT c FROM Customer c WHERE EXISTS (SELECT o FROM c.orders o ...)"
 * so this method will add the join(s) to the outer query.
 * @param clsExpr The ClassExpression
 * @param candSqlTbl Candidate SQL Table
 * @param mmgr MetaData Manager
 */
protected void processFromClauseSubquery(ClassExpression clsExpr, SQLTable candSqlTbl, MetaDataManager mmgr) {
    String[] tokens = StringUtils.split(clsExpr.getCandidateExpression(), ".");
    String leftAlias = tokens[0];
    SQLTableMapping outerSqlTblMapping = parentMapper.getSQLTableMappingForAlias(leftAlias);
    AbstractClassMetaData leftCmd = outerSqlTblMapping.cmd;
    // Get array of the left-right sides of this expression so we can work back from the subquery candidate
    AbstractMemberMetaData[] leftMmds = new AbstractMemberMetaData[tokens.length - 1];
    AbstractMemberMetaData[] rightMmds = new AbstractMemberMetaData[tokens.length - 1];
    for (int i = 0; i < tokens.length - 1; i++) {
        String joinedField = tokens[i + 1];
        AbstractMemberMetaData leftMmd = leftCmd.getMetaDataForMember(joinedField);
        AbstractMemberMetaData rightMmd = null;
        AbstractClassMetaData rightCmd = null;
        RelationType relationType = leftMmd.getRelationType(clr);
        if (RelationType.isBidirectional(relationType)) {
            // Take first possible
            rightMmd = leftMmd.getRelatedMemberMetaData(clr)[0];
            rightCmd = rightMmd.getAbstractClassMetaData();
        } else if (relationType == RelationType.ONE_TO_ONE_UNI) {
            rightCmd = mmgr.getMetaDataForClass(leftMmd.getType(), clr);
        } else if (relationType == RelationType.ONE_TO_MANY_UNI) {
            if (leftMmd.hasCollection()) {
                rightCmd = mmgr.getMetaDataForClass(leftMmd.getCollection().getElementType(), clr);
            } else if (leftMmd.hasMap()) {
                rightCmd = mmgr.getMetaDataForClass(leftMmd.getMap().getValueType(), clr);
            }
        } else {
            throw new NucleusUserException("Subquery has been specified with a candidate-expression that includes \"" + tokens[i] + "\" that isnt a relation field!!");
        }
        leftMmds[i] = leftMmd;
        rightMmds[i] = rightMmd;
        leftCmd = rightCmd;
    }
    // Work from subquery candidate back to outer query table, adding joins and where clause as appropriate
    SQLTable rSqlTbl = candSqlTbl;
    SQLTable outerSqlTbl = outerSqlTblMapping.table;
    JoinType joinType = JoinType.INNER_JOIN;
    for (int i = leftMmds.length - 1; i >= 0; i--) {
        AbstractMemberMetaData leftMmd = leftMmds[i];
        AbstractMemberMetaData rightMmd = rightMmds[i];
        DatastoreClass leftTbl = storeMgr.getDatastoreClass(leftMmd.getClassName(true), clr);
        SQLTable lSqlTbl = null;
        RelationType relationType = leftMmd.getRelationType(clr);
        if (relationType == RelationType.ONE_TO_ONE_UNI) {
            // 1-1 with FK in left table
            if (i == 0) {
                // Add where clause right table to outer table
                SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(), outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                SQLExpression rightExpr = exprFactory.newExpression(stmt, rSqlTbl, rSqlTbl.getTable().getIdMapping());
                stmt.whereAnd(outerExpr.eq(rightExpr), false);
            } else {
                // Join to left table
                JavaTypeMapping leftMapping = leftTbl.getMemberMapping(leftMmd);
                lSqlTbl = stmt.join(joinType, rSqlTbl, rSqlTbl.getTable().getIdMapping(), leftTbl, null, leftMapping, null, null, true);
            }
        } else if (relationType == RelationType.ONE_TO_ONE_BI) {
            if (leftMmd.getMappedBy() != null) {
                // 1-1 with FK in right table
                JavaTypeMapping rightMapping = rSqlTbl.getTable().getMemberMapping(rightMmd);
                if (i == 0) {
                    // Add where clause right table to outer table
                    SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(), outerSqlTbl, outerSqlTbl.getTable().getIdMapping());
                    SQLExpression rightExpr = exprFactory.newExpression(stmt, rSqlTbl, rightMapping);
                    stmt.whereAnd(outerExpr.eq(rightExpr), false);
                } else {
                    // Join to left table
                    lSqlTbl = stmt.join(joinType, rSqlTbl, rightMapping, leftTbl, null, leftTbl.getIdMapping(), null, null, true);
                }
            } else {
                // 1-1 with FK in left table
                if (i == 0) {
                    // Add where clause right table to outer table
                    SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(), outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                    SQLExpression rightExpr = exprFactory.newExpression(stmt, rSqlTbl, rSqlTbl.getTable().getIdMapping());
                    stmt.whereAnd(outerExpr.eq(rightExpr), false);
                } else {
                    // Join to left table
                    lSqlTbl = stmt.join(joinType, rSqlTbl, rSqlTbl.getTable().getIdMapping(), leftTbl, null, leftTbl.getMemberMapping(leftMmd), null, null, true);
                }
            }
        } else if (relationType == RelationType.ONE_TO_MANY_UNI) {
            if (leftMmd.getJoinMetaData() != null || rightMmd.getJoinMetaData() != null) {
                // 1-N with join table to right table, so join from right to join table
                JoinTable joinTbl = (JoinTable) storeMgr.getTable(leftMmd);
                SQLTable joinSqlTbl = null;
                if (leftMmd.hasCollection()) {
                    joinSqlTbl = stmt.join(joinType, rSqlTbl, rSqlTbl.getTable().getIdMapping(), joinTbl, null, ((ElementContainerTable) joinTbl).getElementMapping(), null, null, true);
                } else if (leftMmd.hasMap()) {
                    joinSqlTbl = stmt.join(joinType, rSqlTbl, rSqlTbl.getTable().getIdMapping(), joinTbl, null, ((MapTable) joinTbl).getValueMapping(), null, null, true);
                }
                if (i == 0) {
                    // Add where clause join table (owner) to outer table (id)
                    SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(), outerSqlTbl, outerSqlTbl.getTable().getIdMapping());
                    SQLExpression joinExpr = exprFactory.newExpression(stmt, joinSqlTbl, joinTbl.getOwnerMapping());
                    stmt.whereAnd(outerExpr.eq(joinExpr), false);
                } else {
                    // Join to left table
                    lSqlTbl = stmt.join(joinType, joinSqlTbl, joinTbl.getOwnerMapping(), leftTbl, null, leftTbl.getIdMapping(), null, null, true);
                }
            } else {
                // 1-N with FK in right table
                if (i == 0) {
                    // Add where clause right table to outer table
                    SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(), outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                    SQLExpression rightExpr = exprFactory.newExpression(stmt, rSqlTbl, rSqlTbl.getTable().getMemberMapping(rightMmd));
                    stmt.whereAnd(outerExpr.eq(rightExpr), false);
                } else {
                    // Join to left table
                    lSqlTbl = stmt.join(joinType, rSqlTbl, rSqlTbl.getTable().getMemberMapping(rightMmd), leftTbl, null, leftTbl.getIdMapping(), null, null, true);
                }
            }
        } else if (relationType == RelationType.ONE_TO_MANY_BI) {
            if (leftMmd.getJoinMetaData() != null || rightMmd.getJoinMetaData() != null) {
                // 1-N with join table to right table, so join from right to join table
                JoinTable joinTbl = (JoinTable) storeMgr.getTable(leftMmd);
                SQLTable joinSqlTbl = null;
                if (leftMmd.hasCollection()) {
                    joinSqlTbl = stmt.join(joinType, rSqlTbl, rSqlTbl.getTable().getIdMapping(), joinTbl, null, ((ElementContainerTable) joinTbl).getElementMapping(), null, null, true);
                } else if (leftMmd.hasMap()) {
                    joinSqlTbl = stmt.join(joinType, rSqlTbl, rSqlTbl.getTable().getIdMapping(), joinTbl, null, ((MapTable) joinTbl).getValueMapping(), null, null, true);
                }
                if (i == 0) {
                    // Add where clause join table (owner) to outer table (id)
                    SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(), outerSqlTbl, outerSqlTbl.getTable().getIdMapping());
                    SQLExpression joinExpr = exprFactory.newExpression(stmt, joinSqlTbl, joinTbl.getOwnerMapping());
                    stmt.whereAnd(outerExpr.eq(joinExpr), false);
                } else {
                    // Join to left table
                    lSqlTbl = stmt.join(joinType, joinSqlTbl, joinTbl.getOwnerMapping(), leftTbl, null, leftTbl.getIdMapping(), null, null, true);
                }
            } else {
                // 1-N with FK in right table
                if (i == 0) {
                    // Add where clause right table to outer table
                    SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(), outerSqlTbl, outerSqlTbl.getTable().getIdMapping());
                    SQLExpression rightExpr = exprFactory.newExpression(stmt, rSqlTbl, rSqlTbl.getTable().getMemberMapping(rightMmd));
                    stmt.whereAnd(outerExpr.eq(rightExpr), false);
                } else {
                    // Join to left table
                    lSqlTbl = stmt.join(joinType, rSqlTbl, rSqlTbl.getTable().getMemberMapping(rightMmd), leftTbl, null, leftTbl.getIdMapping(), null, null, true);
                }
            }
        } else if (relationType == RelationType.MANY_TO_ONE_BI) {
            if (leftMmd.getJoinMetaData() != null || rightMmd.getJoinMetaData() != null) {
                // 1-N with join table to right table, so join from right to join table
                JoinTable joinTbl = (JoinTable) storeMgr.getTable(leftMmd);
                SQLTable joinSqlTbl = stmt.join(joinType, rSqlTbl, rSqlTbl.getTable().getIdMapping(), joinTbl, null, joinTbl.getOwnerMapping(), null, null, true);
                if (leftMmd.hasCollection()) {
                    if (i == 0) {
                        // Add where clause join table (element) to outer table (id)
                        SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(), outerSqlTbl, outerSqlTbl.getTable().getIdMapping());
                        SQLExpression joinExpr = exprFactory.newExpression(stmt, joinSqlTbl, ((ElementContainerTable) joinTbl).getElementMapping());
                        stmt.whereAnd(outerExpr.eq(joinExpr), false);
                    } else {
                        // Join to left table
                        lSqlTbl = stmt.join(joinType, joinSqlTbl, ((ElementContainerTable) joinTbl).getElementMapping(), leftTbl, null, leftTbl.getIdMapping(), null, null, true);
                    }
                } else if (leftMmd.hasMap()) {
                    if (i == 0) {
                        // Add where clause join table (value) to outer table (id)
                        SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(), outerSqlTbl, outerSqlTbl.getTable().getIdMapping());
                        SQLExpression joinExpr = exprFactory.newExpression(stmt, joinSqlTbl, ((MapTable) joinTbl).getValueMapping());
                        stmt.whereAnd(outerExpr.eq(joinExpr), false);
                    } else {
                        // Join to left table
                        lSqlTbl = stmt.join(joinType, joinSqlTbl, ((MapTable) joinTbl).getValueMapping(), leftTbl, null, leftTbl.getIdMapping(), null, null, true);
                    }
                }
            } else {
                if (i == 0) {
                    // Add where clause right table to outer table
                    SQLExpression outerExpr = exprFactory.newExpression(outerSqlTbl.getSQLStatement(), outerSqlTbl, outerSqlTbl.getTable().getMemberMapping(leftMmd));
                    SQLExpression rightExpr = exprFactory.newExpression(stmt, rSqlTbl, rSqlTbl.getTable().getIdMapping());
                    stmt.whereAnd(outerExpr.eq(rightExpr), false);
                } else {
                    // Join to left table
                    lSqlTbl = stmt.join(joinType, rSqlTbl, rSqlTbl.getTable().getIdMapping(), leftTbl, null, leftTbl.getMemberMapping(leftMmd), null, null, true);
                }
            }
        }
        rSqlTbl = lSqlTbl;
    }
}
Also used : SQLExpression(org.datanucleus.store.rdbms.sql.expression.SQLExpression) JavaTypeMapping(org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping) NucleusUserException(org.datanucleus.exceptions.NucleusUserException) JoinType(org.datanucleus.store.rdbms.sql.SQLJoin.JoinType) AbstractClassMetaData(org.datanucleus.metadata.AbstractClassMetaData) MapTable(org.datanucleus.store.rdbms.table.MapTable) RelationType(org.datanucleus.metadata.RelationType) SQLTable(org.datanucleus.store.rdbms.sql.SQLTable) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) AbstractMemberMetaData(org.datanucleus.metadata.AbstractMemberMetaData) ElementContainerTable(org.datanucleus.store.rdbms.table.ElementContainerTable) JoinTable(org.datanucleus.store.rdbms.table.JoinTable)

Example 2 with ElementContainerTable

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

the class DynamicSchemaFieldManager method storeObjectField.

/**
 * Method to store an object field into the attached instance.
 * @param fieldNumber Number of the field to store
 * @param value the value in the detached instance
 */
public void storeObjectField(int fieldNumber, Object value) {
    if (value == null) {
        // No value so nothing to do
        return;
    }
    ExecutionContext ec = op.getExecutionContext();
    ClassLoaderResolver clr = ec.getClassLoaderResolver();
    AbstractMemberMetaData mmd = op.getClassMetaData().getMetaDataForManagedMemberAtAbsolutePosition(fieldNumber);
    if (mmd != null) {
        DatastoreClass table = rdbmsMgr.getDatastoreClass(op.getObject().getClass().getName(), clr);
        JavaTypeMapping fieldMapping = table.getMemberMapping(mmd);
        if (fieldMapping != null) {
            if (fieldMapping instanceof InterfaceMapping) {
                // 1-1 Interface field
                InterfaceMapping intfMapping = (InterfaceMapping) fieldMapping;
                if (mmd.getFieldTypes() != null || mmd.hasExtension(MetaData.EXTENSION_MEMBER_IMPLEMENTATION_CLASSES)) {
                    // Field is defined to not accept this type so just return
                    return;
                }
                processInterfaceMappingForValue(intfMapping, value, mmd, ec);
            } else if (mmd.hasCollection() || mmd.hasArray()) {
                boolean hasJoin = false;
                if (mmd.getJoinMetaData() != null) {
                    hasJoin = true;
                } else {
                    AbstractMemberMetaData[] relMmds = mmd.getRelatedMemberMetaData(clr);
                    if (relMmds != null && relMmds[0].getJoinMetaData() != null) {
                        hasJoin = true;
                    }
                }
                if (!hasJoin) {
                    // Not join table so no supported schema updates
                    return;
                }
                Table joinTbl = fieldMapping.getStoreManager().getTable(mmd);
                ElementContainerTable collTbl = (ElementContainerTable) joinTbl;
                JavaTypeMapping elemMapping = collTbl.getElementMapping();
                if (elemMapping instanceof InterfaceMapping) {
                    InterfaceMapping intfMapping = (InterfaceMapping) elemMapping;
                    if (mmd.hasCollection()) {
                        Collection coll = (Collection) value;
                        if (coll.isEmpty()) {
                            return;
                        }
                        // Update value mapping using first element. Maybe we should do the same for all elements?
                        Object elementValue = coll.iterator().next();
                        processInterfaceMappingForValue(intfMapping, elementValue, mmd, ec);
                    } else if (mmd.hasArray()) {
                        if (Array.getLength(value) == 0) {
                            return;
                        }
                        // Update value mapping using first element. Maybe we should do the same for all elements?
                        Object elementValue = Array.get(value, 0);
                        processInterfaceMappingForValue(intfMapping, elementValue, mmd, ec);
                    }
                }
            } else if (mmd.hasMap()) {
                boolean hasJoin = false;
                if (mmd.getJoinMetaData() != null) {
                    hasJoin = true;
                } else {
                    AbstractMemberMetaData[] relMmds = mmd.getRelatedMemberMetaData(clr);
                    if (relMmds != null && relMmds[0].getJoinMetaData() != null) {
                        hasJoin = true;
                    }
                }
                if (!hasJoin) {
                    // Not join table so no supported schema updates
                    return;
                }
                Map map = (Map) value;
                if (map.isEmpty()) {
                    return;
                }
                Table joinTbl = fieldMapping.getStoreManager().getTable(mmd);
                MapTable mapTbl = (MapTable) joinTbl;
                JavaTypeMapping keyMapping = mapTbl.getKeyMapping();
                if (keyMapping instanceof InterfaceMapping) {
                    // Update key mapping using first key. Maybe we should do the same for all keys?
                    InterfaceMapping intfMapping = (InterfaceMapping) keyMapping;
                    Object keyValue = map.keySet().iterator().next();
                    processInterfaceMappingForValue(intfMapping, keyValue, mmd, ec);
                }
                JavaTypeMapping valMapping = mapTbl.getValueMapping();
                if (valMapping instanceof InterfaceMapping) {
                    // Update value mapping using first value. Maybe we should do the same for all values?
                    InterfaceMapping intfMapping = (InterfaceMapping) valMapping;
                    Object valValue = map.values().iterator().next();
                    processInterfaceMappingForValue(intfMapping, valValue, mmd, ec);
                }
            }
        }
    }
}
Also used : ElementContainerTable(org.datanucleus.store.rdbms.table.ElementContainerTable) Table(org.datanucleus.store.rdbms.table.Table) MapTable(org.datanucleus.store.rdbms.table.MapTable) InterfaceMapping(org.datanucleus.store.rdbms.mapping.java.InterfaceMapping) JavaTypeMapping(org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping) ClassLoaderResolver(org.datanucleus.ClassLoaderResolver) MapTable(org.datanucleus.store.rdbms.table.MapTable) ExecutionContext(org.datanucleus.ExecutionContext) Collection(java.util.Collection) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) AbstractMemberMetaData(org.datanucleus.metadata.AbstractMemberMetaData) Map(java.util.Map) ElementContainerTable(org.datanucleus.store.rdbms.table.ElementContainerTable)

Example 3 with ElementContainerTable

use of org.datanucleus.store.rdbms.table.ElementContainerTable 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

AbstractMemberMetaData (org.datanucleus.metadata.AbstractMemberMetaData)3 JavaTypeMapping (org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping)3 NucleusUserException (org.datanucleus.exceptions.NucleusUserException)2 AbstractClassMetaData (org.datanucleus.metadata.AbstractClassMetaData)2 RelationType (org.datanucleus.metadata.RelationType)2 JoinType (org.datanucleus.store.rdbms.sql.SQLJoin.JoinType)2 SQLTable (org.datanucleus.store.rdbms.sql.SQLTable)2 DatastoreClass (org.datanucleus.store.rdbms.table.DatastoreClass)2 ElementContainerTable (org.datanucleus.store.rdbms.table.ElementContainerTable)2 MapTable (org.datanucleus.store.rdbms.table.MapTable)2 Collection (java.util.Collection)1 Map (java.util.Map)1 ClassLoaderResolver (org.datanucleus.ClassLoaderResolver)1 ExecutionContext (org.datanucleus.ExecutionContext)1 FetchGroup (org.datanucleus.FetchGroup)1 FetchGroupManager (org.datanucleus.FetchGroupManager)1 FetchPlanForClass (org.datanucleus.FetchPlanForClass)1 NucleusException (org.datanucleus.exceptions.NucleusException)1 MapMetaData (org.datanucleus.metadata.MapMetaData)1 MetaDataManager (org.datanucleus.metadata.MetaDataManager)1