Search in sources :

Example 66 with DatastoreClass

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

the class UnionStatementGenerator method getSelectStatementForCandidate.

/**
 * Convenience method to return the SelectStatement for a particular class.
 * Returns a SelectStatement with primaryTable of the "candidateTable", and which joins to the table of the class (if different).
 * @param className The class name to generate the statement for
 * @param ec ExecutionContext
 * @return The SelectStatement
 */
protected SelectStatement getSelectStatementForCandidate(String className, ExecutionContext ec) {
    DatastoreClass table = storeMgr.getDatastoreClass(className, clr);
    if (table == null) {
        // Subclass-table, so persisted into table(s) of subclasses
        NucleusLogger.GENERAL.info("Generation of statement to retrieve objects of type " + candidateType.getName() + (includeSubclasses ? " including subclasses " : "") + " attempted to include " + className + " but this has no table of its own; ignored");
        // TODO Cater for use of single subclass-table
        return null;
    }
    // Start from an SQL SELECT of the candidate table
    SelectStatement stmt = new SelectStatement(parentStmt, storeMgr, candidateTable, candidateTableAlias, candidateTableGroupName);
    stmt.setClassLoaderResolver(clr);
    stmt.setCandidateClassName(className);
    String tblGroupName = stmt.getPrimaryTable().getGroupName();
    if (table != candidateTable) {
        // INNER JOIN from the root candidate table to this candidates table
        JavaTypeMapping candidateIdMapping = candidateTable.getIdMapping();
        JavaTypeMapping tableIdMapping = table.getIdMapping();
        SQLTable tableSqlTbl = stmt.join(JoinType.INNER_JOIN, null, candidateIdMapping, table, null, tableIdMapping, null, stmt.getPrimaryTable().getGroupName(), true);
        tblGroupName = tableSqlTbl.getGroupName();
    }
    // Add any discriminator restriction in this table for the specified class
    // Caters for the case where we have more than 1 class stored in this table
    SQLExpressionFactory factory = storeMgr.getSQLExpressionFactory();
    JavaTypeMapping discriminatorMapping = table.getSurrogateMapping(SurrogateColumnType.DISCRIMINATOR, false);
    DiscriminatorMetaData discriminatorMetaData = table.getDiscriminatorMetaData();
    if (discriminatorMapping != null && discriminatorMetaData.getStrategy() != DiscriminatorStrategy.NONE) {
        // Restrict to valid discriminator values where we have a discriminator specified on this table
        AbstractClassMetaData targetCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(className, clr);
        SQLExpression discExpr = factory.newExpression(stmt, stmt.getPrimaryTable(), discriminatorMapping);
        SQLExpression discValExpr = factory.newLiteral(stmt, discriminatorMapping, targetCmd.getDiscriminatorValue());
        stmt.whereAnd(discExpr.eq(discValExpr), false);
    }
    JavaTypeMapping multitenancyMapping = table.getSurrogateMapping(SurrogateColumnType.MULTITENANCY, false);
    if (multitenancyMapping != null) {
        // Multi-tenancy restriction
        AbstractClassMetaData cmd = table.getClassMetaData();
        SQLTable tenantSqlTbl = stmt.getTable(multitenancyMapping.getTable(), tblGroupName);
        SQLExpression tenantExpr = stmt.getSQLExpressionFactory().newExpression(stmt, tenantSqlTbl, multitenancyMapping);
        SQLExpression tenantVal = stmt.getSQLExpressionFactory().newLiteral(stmt, multitenancyMapping, ec.getNucleusContext().getMultiTenancyId(ec, cmd));
        stmt.whereAnd(tenantExpr.eq(tenantVal), true);
    }
    JavaTypeMapping softDeleteMapping = table.getSurrogateMapping(SurrogateColumnType.SOFTDELETE, false);
    if (softDeleteMapping != null && !hasOption(OPTION_INCLUDE_SOFT_DELETES)) {
        // Soft-delete restriction
        SQLTable softDeleteSqlTbl = stmt.getTable(softDeleteMapping.getTable(), tblGroupName);
        SQLExpression softDeleteExpr = stmt.getSQLExpressionFactory().newExpression(stmt, softDeleteSqlTbl, softDeleteMapping);
        SQLExpression softDeleteVal = stmt.getSQLExpressionFactory().newLiteral(stmt, softDeleteMapping, Boolean.FALSE);
        stmt.whereAnd(softDeleteExpr.eq(softDeleteVal), true);
    }
    // Eliminate any subclasses (catered for in separate UNION statement)
    Iterator<String> subIter = storeMgr.getSubClassesForClass(className, false, clr).iterator();
    while (subIter.hasNext()) {
        String subclassName = subIter.next();
        DatastoreClass[] subclassTables = null;
        DatastoreClass subclassTable = storeMgr.getDatastoreClass(subclassName, clr);
        if (subclassTable == null) {
            AbstractClassMetaData targetSubCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(subclassName, clr);
            AbstractClassMetaData[] targetSubCmds = storeMgr.getClassesManagingTableForClass(targetSubCmd, clr);
            subclassTables = new DatastoreClass[targetSubCmds.length];
            for (int i = 0; i < targetSubCmds.length; i++) {
                subclassTables[i] = storeMgr.getDatastoreClass(targetSubCmds[i].getFullClassName(), clr);
            }
        } else {
            subclassTables = new DatastoreClass[1];
            subclassTables[0] = subclassTable;
        }
        for (int i = 0; i < subclassTables.length; i++) {
            if (subclassTables[i] != table) {
                // Subclass of our class is stored in different table to the candidate so exclude it
                // Adds FROM clause of "LEFT OUTER JOIN {subTable} ON ..."
                // and WHERE clause of "{subTable}.ID = NULL"
                JavaTypeMapping tableIdMapping = table.getIdMapping();
                JavaTypeMapping subclassIdMapping = subclassTables[i].getIdMapping();
                SQLTable sqlTableSubclass = stmt.join(JoinType.LEFT_OUTER_JOIN, null, tableIdMapping, subclassTables[i], null, subclassIdMapping, null, stmt.getPrimaryTable().getGroupName(), true);
                SQLExpression subclassIdExpr = factory.newExpression(stmt, sqlTableSubclass, subclassIdMapping);
                SQLExpression nullExpr = new NullLiteral(stmt, null, null, null);
                stmt.whereAnd(subclassIdExpr.eq(nullExpr), false);
            }
        }
    }
    if (hasOption(OPTION_SELECT_DN_TYPE)) {
        // Add SELECT of dummy metadata for this class ("'mydomain.MyClass' AS DN_TYPE")
        addTypeSelectForClass(stmt, className);
    }
    return stmt;
}
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) AbstractClassMetaData(org.datanucleus.metadata.AbstractClassMetaData) DiscriminatorMetaData(org.datanucleus.metadata.DiscriminatorMetaData) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) NullLiteral(org.datanucleus.store.rdbms.sql.expression.NullLiteral)

Example 67 with DatastoreClass

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

the class UnionStatementGenerator method getSQLStatementForCandidateViaJoin.

/**
 * Convenience method to return the SQLStatement for a particular class selecting a join table.
 * Returns a SQLStatement with primaryTable of the "joinTable", and which joins to the table of the class.
 * @param className The class name to generate the statement for
 * @return The SQLStatement
 */
protected SelectStatement getSQLStatementForCandidateViaJoin(String className) {
    DatastoreClass table = storeMgr.getDatastoreClass(className, clr);
    if (table == null) {
        // TODO Cater for use of single subclass-table
        throw new NucleusException("We do not currently support a UNION statement for class=" + className + " since it has no table of its own");
    }
    // Start from an SQL SELECT of the join table
    SelectStatement stmt = new SelectStatement(parentStmt, storeMgr, joinTable, joinTableAlias, candidateTableGroupName);
    stmt.setClassLoaderResolver(clr);
    stmt.setCandidateClassName(className);
    // INNER/LEFT OUTER JOIN from the join table to the root candidate table
    // If we allow nulls we do a left outer join here, otherwise an inner join
    SQLTable candidateSQLTable = null;
    if (candidateTable != null) {
        // We have a root candidate table, so join to that
        JavaTypeMapping candidateIdMapping = candidateTable.getIdMapping();
        if (hasOption(OPTION_ALLOW_NULLS)) {
            // Put element table in same table group since all relates to the elements
            candidateSQLTable = stmt.join(JoinType.LEFT_OUTER_JOIN, null, joinElementMapping, candidateTable, null, candidateIdMapping, null, stmt.getPrimaryTable().getGroupName(), true);
        } else {
            // Put element table in same table group since all relates to the elements
            candidateSQLTable = stmt.join(JoinType.INNER_JOIN, null, joinElementMapping, candidateTable, null, candidateIdMapping, null, stmt.getPrimaryTable().getGroupName(), true);
        }
        // Join the root candidate table to this particular candidate table
        if (table != candidateTable) {
            // INNER JOIN from the root candidate table to this candidates table
            stmt.join(JoinType.INNER_JOIN, candidateSQLTable, candidateIdMapping, table, null, table.getIdMapping(), null, stmt.getPrimaryTable().getGroupName(), true);
        }
    } else {
        // No root candidate table, so join direct to this candidate
        JavaTypeMapping candidateIdMapping = table.getIdMapping();
        if (hasOption(OPTION_ALLOW_NULLS)) {
            // Put element table in same table group since all relates to the elements
            candidateSQLTable = stmt.join(JoinType.LEFT_OUTER_JOIN, null, joinElementMapping, table, null, candidateIdMapping, null, stmt.getPrimaryTable().getGroupName(), true);
        } else {
            // Put element table in same table group since all relates to the elements
            candidateSQLTable = stmt.join(JoinType.INNER_JOIN, null, joinElementMapping, table, null, candidateIdMapping, null, stmt.getPrimaryTable().getGroupName(), true);
        }
    }
    // Add any discriminator restriction in the table for the specified class
    // Caters for the case where we have more than 1 class stored in this table
    SQLExpressionFactory factory = storeMgr.getSQLExpressionFactory();
    JavaTypeMapping discriminatorMapping = table.getSurrogateMapping(SurrogateColumnType.DISCRIMINATOR, false);
    DiscriminatorMetaData discriminatorMetaData = table.getDiscriminatorMetaData();
    if (discriminatorMapping != null && discriminatorMetaData.getStrategy() != DiscriminatorStrategy.NONE) {
        // Restrict to valid discriminator value where we have a discriminator specified on this table
        BooleanExpression discExpr = SQLStatementHelper.getExpressionForDiscriminatorForClass(stmt, className, discriminatorMetaData, discriminatorMapping, stmt.getPrimaryTable(), clr);
        stmt.whereAnd(discExpr, false);
    }
    // Eliminate any subclasses (catered for in separate UNION statement)
    Iterator<String> subIter = storeMgr.getSubClassesForClass(className, false, clr).iterator();
    while (subIter.hasNext()) {
        String subclassName = subIter.next();
        DatastoreClass[] subclassTables = null;
        DatastoreClass subclassTable = storeMgr.getDatastoreClass(subclassName, clr);
        if (subclassTable == null) {
            AbstractClassMetaData targetSubCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(subclassName, clr);
            AbstractClassMetaData[] targetSubCmds = storeMgr.getClassesManagingTableForClass(targetSubCmd, clr);
            subclassTables = new DatastoreClass[targetSubCmds.length];
            for (int i = 0; i < targetSubCmds.length; i++) {
                subclassTables[i] = storeMgr.getDatastoreClass(targetSubCmds[i].getFullClassName(), clr);
            }
        } else {
            subclassTables = new DatastoreClass[1];
            subclassTables[0] = subclassTable;
        }
        for (int i = 0; i < subclassTables.length; i++) {
            if (subclassTables[i] != table) {
                // Subclass of our class is stored in different table to the candidate so exclude it
                // Adds FROM clause of "LEFT OUTER JOIN {subTable} ON ..."
                // and WHERE clause of "{subTable}.ID = NULL"
                JavaTypeMapping subclassIdMapping = subclassTables[i].getIdMapping();
                SQLTable sqlTableSubclass = stmt.join(JoinType.LEFT_OUTER_JOIN, null, joinElementMapping, subclassTables[i], null, subclassIdMapping, null, stmt.getPrimaryTable().getGroupName(), true);
                SQLExpression subclassIdExpr = factory.newExpression(stmt, sqlTableSubclass, subclassIdMapping);
                SQLExpression nullExpr = new NullLiteral(stmt, null, null, null);
                stmt.whereAnd(subclassIdExpr.eq(nullExpr), false);
            }
        }
    }
    if (hasOption(OPTION_SELECT_DN_TYPE)) {
        // Add SELECT of dummy metadata for this class ("'mydomain.MyClass' AS DN_TYPE")
        addTypeSelectForClass(stmt, className);
    }
    return stmt;
}
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) AbstractClassMetaData(org.datanucleus.metadata.AbstractClassMetaData) DiscriminatorMetaData(org.datanucleus.metadata.DiscriminatorMetaData) BooleanExpression(org.datanucleus.store.rdbms.sql.expression.BooleanExpression) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) NucleusException(org.datanucleus.exceptions.NucleusException) NullLiteral(org.datanucleus.store.rdbms.sql.expression.NullLiteral)

Example 68 with DatastoreClass

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

the class JoinMapStore method getSQLStatementForGet.

/**
 * Method to return an SQLStatement for retrieving the value for a key.
 * Selects the join table and optionally joins to the value table if it has its own table.
 * @param ownerOP ObjectProvider for the owning object
 * @return The SQLStatement
 */
protected SelectStatement getSQLStatementForGet(ObjectProvider ownerOP) {
    SelectStatement sqlStmt = null;
    ExecutionContext ec = ownerOP.getExecutionContext();
    final ClassLoaderResolver clr = ownerOP.getExecutionContext().getClassLoaderResolver();
    Class valueCls = clr.classForName(this.valueType);
    if (valuesAreEmbedded || valuesAreSerialised) {
        // Value is stored in join table
        sqlStmt = new SelectStatement(storeMgr, mapTable, null, null);
        sqlStmt.setClassLoaderResolver(clr);
        sqlStmt.select(sqlStmt.getPrimaryTable(), valueMapping, null);
    } else {
        // Value is stored in own table
        getMappingDef = new StatementClassMapping();
        if (!valueCmd.getFullClassName().equals(valueCls.getName())) {
            valueCls = clr.classForName(valueCmd.getFullClassName());
        }
        UnionStatementGenerator stmtGen = new UnionStatementGenerator(storeMgr, clr, valueCls, true, null, null, mapTable, null, valueMapping);
        stmtGen.setOption(SelectStatementGenerator.OPTION_SELECT_DN_TYPE);
        getMappingDef.setNucleusTypeColumnName(UnionStatementGenerator.DN_TYPE_COLUMN);
        sqlStmt = stmtGen.getStatement(ec);
        // Select the value field(s)
        SQLTable valueSqlTbl = sqlStmt.getTable(valueTable, sqlStmt.getPrimaryTable().getGroupName());
        if (valueSqlTbl == null) {
            // Root value candidate has no table, so try to find a value candidate with a table that exists in this statement
            Collection<String> valueSubclassNames = storeMgr.getSubClassesForClass(valueType, true, clr);
            if (valueSubclassNames != null && !valueSubclassNames.isEmpty()) {
                for (String valueSubclassName : valueSubclassNames) {
                    DatastoreClass valueTbl = storeMgr.getDatastoreClass(valueSubclassName, clr);
                    if (valueTbl != null) {
                        valueSqlTbl = sqlStmt.getTable(valueTbl, sqlStmt.getPrimaryTable().getGroupName());
                        if (valueSqlTbl != null) {
                            break;
                        }
                    }
                }
            }
        }
        SQLStatementHelper.selectFetchPlanOfSourceClassInStatement(sqlStmt, getMappingDef, ec.getFetchPlan(), valueSqlTbl, valueCmd, ec.getFetchPlan().getMaxFetchDepth());
    }
    // Apply condition on owner field to filter by owner
    SQLExpressionFactory exprFactory = storeMgr.getSQLExpressionFactory();
    SQLTable ownerSqlTbl = SQLStatementHelper.getSQLTableForMappingOfTable(sqlStmt, sqlStmt.getPrimaryTable(), ownerMapping);
    SQLExpression ownerExpr = exprFactory.newExpression(sqlStmt, ownerSqlTbl, ownerMapping);
    SQLExpression ownerVal = exprFactory.newLiteralParameter(sqlStmt, ownerMapping, null, "OWNER");
    sqlStmt.whereAnd(ownerExpr.eq(ownerVal), true);
    // Apply condition on key
    if (keyMapping instanceof SerialisedMapping) {
        // if the keyMapping contains a BLOB column (or any other column not supported by the database
        // as primary key), uses like instead of the operator OP_EQ (=)
        // in future do not check if the keyMapping is of ObjectMapping, but use the database
        // adapter to check the data types not supported as primary key
        // if object mapping (BLOB) use like
        SQLExpression keyExpr = exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), keyMapping);
        SQLExpression keyVal = exprFactory.newLiteralParameter(sqlStmt, keyMapping, null, "KEY");
        sqlStmt.whereAnd(new org.datanucleus.store.rdbms.sql.expression.BooleanExpression(keyExpr, Expression.OP_LIKE, keyVal), true);
    } else {
        SQLExpression keyExpr = exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), keyMapping);
        SQLExpression keyVal = exprFactory.newLiteralParameter(sqlStmt, keyMapping, null, "KEY");
        sqlStmt.whereAnd(keyExpr.eq(keyVal), true);
    }
    // Input parameter(s) - owner, key
    int inputParamNum = 1;
    StatementMappingIndex ownerIdx = new StatementMappingIndex(ownerMapping);
    StatementMappingIndex keyIdx = new StatementMappingIndex(keyMapping);
    if (sqlStmt.getNumberOfUnions() > 0) {
        // Add parameter occurrence for each union of statement
        for (int j = 0; j < sqlStmt.getNumberOfUnions() + 1; j++) {
            int[] ownerPositions = new int[ownerMapping.getNumberOfDatastoreMappings()];
            for (int k = 0; k < ownerPositions.length; k++) {
                ownerPositions[k] = inputParamNum++;
            }
            ownerIdx.addParameterOccurrence(ownerPositions);
            int[] keyPositions = new int[keyMapping.getNumberOfDatastoreMappings()];
            for (int k = 0; k < keyPositions.length; k++) {
                keyPositions[k] = inputParamNum++;
            }
            keyIdx.addParameterOccurrence(keyPositions);
        }
    } else {
        int[] ownerPositions = new int[ownerMapping.getNumberOfDatastoreMappings()];
        for (int k = 0; k < ownerPositions.length; k++) {
            ownerPositions[k] = inputParamNum++;
        }
        ownerIdx.addParameterOccurrence(ownerPositions);
        int[] keyPositions = new int[keyMapping.getNumberOfDatastoreMappings()];
        for (int k = 0; k < keyPositions.length; k++) {
            keyPositions[k] = inputParamNum++;
        }
        keyIdx.addParameterOccurrence(keyPositions);
    }
    getMappingParams = new StatementParameterMapping();
    getMappingParams.addMappingForParameter("owner", ownerIdx);
    getMappingParams.addMappingForParameter("key", keyIdx);
    return sqlStmt;
}
Also used : StatementParameterMapping(org.datanucleus.store.rdbms.query.StatementParameterMapping) SQLExpressionFactory(org.datanucleus.store.rdbms.sql.expression.SQLExpressionFactory) SQLExpression(org.datanucleus.store.rdbms.sql.expression.SQLExpression) ClassLoaderResolver(org.datanucleus.ClassLoaderResolver) StatementMappingIndex(org.datanucleus.store.rdbms.query.StatementMappingIndex) StatementClassMapping(org.datanucleus.store.rdbms.query.StatementClassMapping) UnionStatementGenerator(org.datanucleus.store.rdbms.sql.UnionStatementGenerator) SelectStatement(org.datanucleus.store.rdbms.sql.SelectStatement) ExecutionContext(org.datanucleus.ExecutionContext) SQLTable(org.datanucleus.store.rdbms.sql.SQLTable) SerialisedMapping(org.datanucleus.store.rdbms.mapping.java.SerialisedMapping) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass)

Example 69 with DatastoreClass

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

Example 70 with DatastoreClass

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

the class PersistableMapping method prepareDatastoreMapping.

/**
 * Method to prepare the PC mapping and add its associated datastore mappings.
 * @param clr The ClassLoaderResolver
 */
protected void prepareDatastoreMapping(ClassLoaderResolver clr) {
    if (roleForMember == FieldRole.ROLE_COLLECTION_ELEMENT) {
    // TODO Handle creation of columns in join table for collection of PCs
    } else if (roleForMember == FieldRole.ROLE_ARRAY_ELEMENT) {
    // TODO Handle creation of columns in join table for array of PCs
    } else if (roleForMember == FieldRole.ROLE_MAP_KEY) {
    // TODO Handle creation of columns in join table for map of PCs as keys
    } else if (roleForMember == FieldRole.ROLE_MAP_VALUE) {
    // TODO Handle creation of columns in join table for map of PCs as values
    } else {
        // Either one end of a 1-1 relation, or the N end of a N-1
        AbstractClassMetaData refCmd = storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(mmd.getType(), clr);
        JavaTypeMapping referenceMapping = null;
        if (refCmd == null) {
            // User stupidity
            throw new NucleusUserException("You have a field " + mmd.getFullFieldName() + " that has type " + mmd.getTypeName() + " but this type has no known metadata. Your mapping is incorrect");
        }
        if (refCmd.getInheritanceMetaData() != null && refCmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.SUBCLASS_TABLE) {
            // Find the actual tables storing the other end (can be multiple subclasses)
            AbstractClassMetaData[] cmds = storeMgr.getClassesManagingTableForClass(refCmd, clr);
            if (cmds != null && cmds.length > 0) {
                if (cmds.length > 1) {
                    // TODO Only log this when it is really necessary. In some situations it is fine
                    NucleusLogger.PERSISTENCE.warn("Field " + mmd.getFullFieldName() + " represents either a 1-1 relation, " + "or a N-1 relation where the other end uses \"subclass-table\" inheritance strategy and more " + "than 1 subclasses with a table. This is not fully supported");
                }
            } else {
                // TODO Throw an exception ?
                return;
            }
            // TODO We need a mapping for each of the possible subclass tables
            referenceMapping = storeMgr.getDatastoreClass(cmds[0].getFullClassName(), clr).getIdMapping();
        } else if (refCmd.getInheritanceMetaData() != null && refCmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.COMPLETE_TABLE) {
            // Find the other side of the relation
            DatastoreClass refTable = null;
            if (refCmd instanceof ClassMetaData && !((ClassMetaData) refCmd).isAbstract()) {
                refTable = storeMgr.getDatastoreClass(refCmd.getFullClassName(), clr);
            } else {
                Collection<String> refSubclasses = storeMgr.getSubClassesForClass(refCmd.getFullClassName(), true, clr);
                if (refSubclasses != null && !refSubclasses.isEmpty()) {
                    // if only 1 subclass then use that
                    String refSubclassName = refSubclasses.iterator().next();
                    refTable = storeMgr.getDatastoreClass(refSubclassName, clr);
                    if (refSubclasses.size() > 1) {
                        NucleusLogger.DATASTORE_SCHEMA.info("Field " + mmd.getFullFieldName() + " is a 1-1/N-1 relation and the other side had multiple possible classes " + "to which to create a foreign-key. Using first possible (" + refSubclassName + ")");
                    }
                }
            }
            if (refTable != null) {
                referenceMapping = refTable.getIdMapping();
            } else {
                throw new NucleusUserException("Field " + mmd.getFullFieldName() + " represents either a 1-1 relation, " + "or a N-1 relation where the other end uses \"complete-table\" inheritance strategy and either no table was found, or multiple possible tables!");
            }
        } else {
            // Default is to use the ID of the related object
            // TODO Add option to use a natural-id in the other class. Find the mapping using the targetColumnName
            referenceMapping = storeMgr.getDatastoreClass(mmd.getType().getName(), clr).getIdMapping();
        }
        // Generate a mapping from the columns of the referenced object to this mapping's ColumnMetaData
        CorrespondentColumnsMapper correspondentColumnsMapping = new CorrespondentColumnsMapper(mmd, table, referenceMapping, true);
        // Find any related field where this is part of a bidirectional relation
        RelationType relationType = mmd.getRelationType(clr);
        boolean createDatastoreMappings = true;
        if (relationType == RelationType.MANY_TO_ONE_BI) {
            AbstractMemberMetaData[] relatedMmds = mmd.getRelatedMemberMetaData(clr);
            // TODO Cater for more than 1 related field
            createDatastoreMappings = (relatedMmds[0].getJoinMetaData() == null);
        } else if (// TODO If join table then don't need this
        relationType == RelationType.ONE_TO_ONE_BI) {
            // Put the FK at the end without "mapped-by"
            createDatastoreMappings = (mmd.getMappedBy() == null);
        }
        if (mmd.getJoinMetaData() != null && (relationType == RelationType.MANY_TO_ONE_UNI || relationType == RelationType.ONE_TO_ONE_UNI || relationType == RelationType.ONE_TO_ONE_BI)) {
            if (relationType == RelationType.ONE_TO_ONE_UNI || relationType == RelationType.ONE_TO_ONE_BI) {
                throw new NucleusUserException("We do not currently support 1-1 relations via join table : " + mmd.getFullFieldName());
            }
            // create join table
            storeMgr.newJoinTable(table, mmd, clr);
        } else {
            // Loop through the datastore fields in the referenced class and create a datastore field for each
            for (int i = 0; i < referenceMapping.getNumberOfDatastoreMappings(); i++) {
                DatastoreMapping refDatastoreMapping = referenceMapping.getDatastoreMapping(i);
                JavaTypeMapping mapping = storeMgr.getMappingManager().getMapping(refDatastoreMapping.getJavaTypeMapping().getJavaType());
                this.addJavaTypeMapping(mapping);
                // Create physical datastore columns where we require a FK link to the related table.
                if (createDatastoreMappings) {
                    // Find the Column MetaData that maps to the referenced datastore field
                    ColumnMetaData colmd = correspondentColumnsMapping.getColumnMetaDataByIdentifier(refDatastoreMapping.getColumn().getIdentifier());
                    if (colmd == null) {
                        throw new NucleusUserException(Localiser.msg("041038", refDatastoreMapping.getColumn().getIdentifier(), toString())).setFatal();
                    }
                    // Create a Datastore field to equate to the referenced classes datastore field
                    MappingManager mmgr = storeMgr.getMappingManager();
                    Column col = mmgr.createColumn(mmd, table, mapping, colmd, refDatastoreMapping.getColumn(), clr);
                    // Add its datastore mapping
                    DatastoreMapping datastoreMapping = mmgr.createDatastoreMapping(mapping, col, refDatastoreMapping.getJavaTypeMapping().getJavaTypeForDatastoreMapping(i));
                    this.addDatastoreMapping(datastoreMapping);
                } else {
                    mapping.setReferenceMapping(referenceMapping);
                }
            }
        }
    }
}
Also used : NucleusUserException(org.datanucleus.exceptions.NucleusUserException) AbstractClassMetaData(org.datanucleus.metadata.AbstractClassMetaData) DatastoreMapping(org.datanucleus.store.rdbms.mapping.datastore.DatastoreMapping) Column(org.datanucleus.store.rdbms.table.Column) RelationType(org.datanucleus.metadata.RelationType) DatastoreClass(org.datanucleus.store.rdbms.table.DatastoreClass) ColumnMetaData(org.datanucleus.metadata.ColumnMetaData) MappingManager(org.datanucleus.store.rdbms.mapping.MappingManager) AbstractClassMetaData(org.datanucleus.metadata.AbstractClassMetaData) ClassMetaData(org.datanucleus.metadata.ClassMetaData) CorrespondentColumnsMapper(org.datanucleus.store.rdbms.mapping.CorrespondentColumnsMapper)

Aggregations

DatastoreClass (org.datanucleus.store.rdbms.table.DatastoreClass)87 JavaTypeMapping (org.datanucleus.store.rdbms.mapping.java.JavaTypeMapping)60 AbstractClassMetaData (org.datanucleus.metadata.AbstractClassMetaData)49 AbstractMemberMetaData (org.datanucleus.metadata.AbstractMemberMetaData)48 ClassLoaderResolver (org.datanucleus.ClassLoaderResolver)44 RDBMSStoreManager (org.datanucleus.store.rdbms.RDBMSStoreManager)41 SQLExpression (org.datanucleus.store.rdbms.sql.expression.SQLExpression)35 SQLTable (org.datanucleus.store.rdbms.sql.SQLTable)32 SQLExpressionFactory (org.datanucleus.store.rdbms.sql.expression.SQLExpressionFactory)28 NucleusUserException (org.datanucleus.exceptions.NucleusUserException)26 SelectStatement (org.datanucleus.store.rdbms.sql.SelectStatement)21 MapTable (org.datanucleus.store.rdbms.table.MapTable)19 NucleusException (org.datanucleus.exceptions.NucleusException)18 SecondaryDatastoreClass (org.datanucleus.store.rdbms.table.SecondaryDatastoreClass)15 ArrayList (java.util.ArrayList)14 ExecutionContext (org.datanucleus.ExecutionContext)13 JoinTable (org.datanucleus.store.rdbms.table.JoinTable)13 Table (org.datanucleus.store.rdbms.table.Table)13 NucleusDataStoreException (org.datanucleus.exceptions.NucleusDataStoreException)11 UnboundExpression (org.datanucleus.store.rdbms.sql.expression.UnboundExpression)11