/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.query.metadata; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.teiid.adminapi.impl.ModelMetaData; import org.teiid.adminapi.impl.ModelMetaData.Message.Severity; import org.teiid.adminapi.impl.VDBMetaData; import org.teiid.api.exception.query.QueryResolverException; import org.teiid.core.types.DataTypeManagerService; import org.teiid.designer.query.metadata.IQueryMetadataInterface; import org.teiid.designer.query.sql.lang.ICommand; import org.teiid.designer.query.sql.symbol.IGroupSymbol; import org.teiid.designer.runtime.version.spi.ITeiidServerVersion; import org.teiid.designer.runtime.version.spi.TeiidServerVersion.Version; import org.teiid.designer.validator.IValidator.IValidatorFailure; import org.teiid.language.SQLConstants; import org.teiid.metadata.AbstractMetadataRecord; import org.teiid.metadata.BaseColumn.NullType; import org.teiid.metadata.Column; import org.teiid.metadata.Datatype; import org.teiid.metadata.ForeignKey; import org.teiid.metadata.FunctionMethod; import org.teiid.metadata.FunctionMethod.Determinism; import org.teiid.metadata.FunctionParameter; import org.teiid.metadata.KeyRecord; import org.teiid.metadata.MetadataFactory; import org.teiid.metadata.MetadataStore; import org.teiid.metadata.Procedure; import org.teiid.metadata.ProcedureParameter; import org.teiid.metadata.ProcedureParameter.Type; import org.teiid.metadata.Schema; import org.teiid.metadata.Table; import org.teiid.query.function.metadata.FunctionMetadataValidator; import org.teiid.query.mapping.relational.QueryNode; import org.teiid.query.parser.QueryParser; import org.teiid.query.parser.TeiidNodeFactory; import org.teiid.query.parser.TeiidNodeFactory.ASTNodes; import org.teiid.query.report.ActivityReport; import org.teiid.query.report.ReportItem; import org.teiid.query.resolver.QueryResolver; import org.teiid.query.resolver.util.ResolverUtil; import org.teiid.query.resolver.util.ResolverVisitor; import org.teiid.query.sql.lang.CacheHint; import org.teiid.query.sql.lang.Command; import org.teiid.query.sql.lang.LanguageObject; import org.teiid.query.sql.lang.QueryCommand; import org.teiid.query.sql.navigator.PostOrderNavigator; import org.teiid.query.sql.navigator.PreOrPostOrderNavigator; import org.teiid.query.sql.symbol.ElementSymbol; import org.teiid.query.sql.symbol.Expression; import org.teiid.query.sql.symbol.GroupSymbol; import org.teiid.query.sql.symbol.Symbol; import org.teiid.query.sql.visitor.ElementCollectorVisitor; import org.teiid.query.sql.visitor.EvaluatableVisitor; import org.teiid.query.sql.visitor.GroupCollectorVisitor; import org.teiid.query.sql.visitor.ValueIteratorProviderCollectorVisitor; import org.teiid.query.validator.Validator; import org.teiid.query.validator.ValidatorFailure; import org.teiid.query.validator.ValidatorReport; import org.teiid.runtime.client.Messages; import org.teiid.runtime.client.query.SyntaxFactory; public class MetadataValidator { /** * MATVIEW_TTL property name */ public static final String MATVIEW_TTL = AbstractMetadataRecord.RELATIONAL_URI + "MATVIEW_TTL"; //$NON-NLS-1$ private final ITeiidServerVersion teiidVersion; private final QueryParser queryParser; private Map<String, Datatype> typeMap; interface MetadataRule { void execute(VDBMetaData vdb, MetadataStore vdbStore, ValidatorReport report, MetadataValidator metadataValidator); } public MetadataValidator(ITeiidServerVersion teiidVersion, Map<String, Datatype> typeMap) { this.teiidVersion = teiidVersion; this.typeMap = typeMap; this.queryParser = new QueryParser(teiidVersion); } public MetadataValidator(ITeiidServerVersion teiidVersion) { this(teiidVersion, SystemMetadata.getInstance(teiidVersion).getRuntimeTypeMap()); } private boolean isTeiidOrGreater(Version version) { return teiidVersion.isGreaterThanOrEqualTo(version.get()); } private <T extends LanguageObject> T createASTNode(ASTNodes nodeType) { return TeiidNodeFactory.createASTNode(teiidVersion, nodeType); } public ValidatorReport validate(VDBMetaData vdb, MetadataStore store) { ValidatorReport report = new ValidatorReport(); if (store != null && !store.getSchemaList().isEmpty()) { new SourceModelArtifacts().execute(vdb, store, report, this); new CrossSchemaResolver().execute(vdb, store, report, this); new ResolveQueryPlans().execute(vdb, store, report, this); new MinimalMetadata().execute(vdb, store, report, this); } return report; } // At minimum the model must have table/view, procedure or function class MinimalMetadata implements MetadataRule { @Override public void execute(VDBMetaData vdb, MetadataStore store, ValidatorReport report, MetadataValidator metadataValidator) { for (Schema schema:store.getSchemaList()) { if (vdb.getImportedModels().contains(schema.getName())) { continue; } ModelMetaData model = vdb.getModel(schema.getName()); if (schema.getTables().isEmpty() && schema.getProcedures().isEmpty() && schema.getFunctions().isEmpty()) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31070, model.getName())); } for (Table t:schema.getTables().values()) { if (t.getColumns() == null || t.getColumns().size() == 0) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31071, t.getFullName())); } Set<String> names = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); validateConstraintNames(metadataValidator, report, model, t.getAllKeys(), names); validateConstraintNames(metadataValidator, report, model, t.getFunctionBasedIndexes(), names); } // procedure validation is handled in parsing routines. if (!schema.getFunctions().isEmpty()) { ActivityReport<ReportItem> funcReport = new ActivityReport<ReportItem>("Translator metadata load " + model.getName()); //$NON-NLS-1$ FunctionMetadataValidator.validateFunctionMethods(teiidVersion, schema.getFunctions().values(),report); if(report.hasItems()) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31073, funcReport)); } } } } private void validateConstraintNames(MetadataValidator metadataValidator, ValidatorReport report, ModelMetaData model, Collection<KeyRecord> keys, Set<String> names) { for (KeyRecord record : keys) { if (record.getName() == null) { continue; } if (!names.add(record.getName())) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31152, record.getFullName())); } } } } // do not allow foreign tables, source functions in view model and vice versa class SourceModelArtifacts implements MetadataRule { @Override public void execute(VDBMetaData vdb, MetadataStore store, ValidatorReport report, MetadataValidator metadataValidator) { for (Schema schema:store.getSchemaList()) { if (vdb.getImportedModels().contains(schema.getName())) { continue; } ModelMetaData model = vdb.getModel(schema.getName()); for (Table t:schema.getTables().values()) { if (t.isPhysical() && !model.isSource()) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31075, t.getFullName(), model.getName())); } } Set<String> names = new HashSet<String>(); for (Procedure p:schema.getProcedures().values()) { boolean hasReturn = false; names.clear(); for (int i = 0; i < p.getParameters().size(); i++) { ProcedureParameter param = p.getParameters().get(i); if (param.isVarArg() && param != p.getParameters().get(p.getParameters().size() -1)) { //check that the rest of the parameters are optional //this accommodates variadic multi-source procedures //effective this and the resolving logic ensure that you can used named parameters for everything, //or call the vararg procedure as normal if(isTeiidOrGreater(Version.TEIID_8_10)) { for (int j = i+1; j < p.getParameters().size(); j++) { ProcedureParameter param1 = p.getParameters().get(j); if ((param1.getType() == Type.In || param1.getType() == Type.InOut) && (param1.isVarArg() || (param1.getNullType() != NullType.Nullable && param1.getDefaultValue() == null))) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31112, p.getFullName())); } } } else { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31112, p.getFullName())); } } if (param.getType() == ProcedureParameter.Type.ReturnValue) { if (hasReturn) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31107, p.getFullName())); } hasReturn = true; } else if (p.isFunction() && param.getType() != ProcedureParameter.Type.In && teiidVersion.isGreaterThanOrEqualTo(Version.TEIID_8_11)) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31165, p.getFullName(), param.getFullName())); } if (!names.add(param.getName())) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31106, p.getFullName(), param.getFullName())); } } if (!p.isVirtual() && !model.isSource()) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31077, p.getFullName(), model.getName())); } if (p.isFunction() && teiidVersion.isGreaterThanOrEqualTo(Version.TEIID_8_11)) { if (!hasReturn) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31166, p.getFullName())); } if (p.isVirtual() && p.getQueryPlan() == null) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31167, p.getFullName())); } } } for (FunctionMethod func:schema.getFunctions().values()) { for (FunctionParameter param : func.getInputParameters()) { if (param.isVarArg() && param != func.getInputParameters().get(func.getInputParameterCount() -1)) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31112, func.getFullName())); } } if (func.getPushdown().equals(FunctionMethod.PushDown.MUST_PUSHDOWN) && !model.isSource()) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31078, func.getFullName(), model.getName())); } } } } } // Resolves metadata query plans to make sure they are accurate class ResolveQueryPlans implements MetadataRule { @Override public void execute(VDBMetaData vdb, MetadataStore store, ValidatorReport report, MetadataValidator metadataValidator) { IQueryMetadataInterface metadata = vdb.getAttachment(IQueryMetadataInterface.class); metadata = new TempMetadataAdapter(metadata, new TempMetadataStore()); for (Schema schema:store.getSchemaList()) { if (vdb.getImportedModels().contains(schema.getName())) { continue; } ModelMetaData model = vdb.getModel(schema.getName()); MetadataFactory mf = new MetadataFactory(teiidVersion, vdb.getName(), vdb.getVersion(), metadataValidator.typeMap, model) { @Override protected void setUUID(AbstractMetadataRecord record) { if (count >= 0) { count = Integer.MIN_VALUE; } super.setUUID(record); } }; mf.setBuiltinDataTypes(store.getDatatypes()); for (AbstractMetadataRecord record : schema.getResolvingOrder()) { if (record instanceof Table) { Table t = (Table)record; // no need to verify the transformation of the xml mapping document, // as this is very specific and designer already validates it. if (t.getTableType() == Table.Type.Document || t.getTableType() == Table.Type.XmlMappingClass || t.getTableType() == Table.Type.XmlStagingTable) { continue; } if (t.isVirtual() && t.getTableType() != Table.Type.TemporaryTable) { if (t.getSelectTransformation() == null) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31079, t.getFullName(), model.getName())); } else { metadataValidator.validate(vdb, model, t, report, metadata, mf); } } } else if (record instanceof Procedure) { Procedure p = (Procedure)record; boolean test = p.isVirtual(); if (teiidVersion.isLessThan(Version.TEIID_8_11)) test = test && !p.isFunction(); if (test) { if (p.getQueryPlan() == null) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31081, p.getFullName(), model.getName())); } else { metadataValidator.validate(vdb, model, p, report, metadata, mf); } } } } } } } public void log(ValidatorReport report, ModelMetaData model, String msg) { log(report, model, Severity.ERROR, msg); } public void log(ValidatorReport report, ModelMetaData model, Severity severity, String msg) { model.addRuntimeMessage(severity, msg); if (severity == Severity.ERROR) { report.handleValidationError(msg); } else { report.handleValidationWarning(msg); } } private void validate(VDBMetaData vdb, ModelMetaData model, AbstractMetadataRecord record, ValidatorReport report, IQueryMetadataInterface metadata, MetadataFactory mf) { ValidatorReport resolverReport = null; try { if (record instanceof Procedure) { Procedure p = (Procedure)record; Command command = queryParser.parseProcedure(p.getQueryPlan(), false); GroupSymbol gs = createASTNode(ASTNodes.GROUP_SYMBOL); gs.setName(p.getFullName()); QueryResolver resolver = new QueryResolver(queryParser); resolver.resolveCommand(command, gs, ICommand.TYPE_STORED_PROCEDURE, metadata, false); Validator validator = new Validator(); resolverReport = validator.validate(command, metadata); determineDependencies(p, command); } else if (record instanceof Table) { Table t = (Table)record; GroupSymbol symbol = createASTNode(ASTNodes.GROUP_SYMBOL); symbol.setName(t.getFullName()); ResolverUtil.resolveGroup(symbol, metadata); String selectTransformation = t.getSelectTransformation(); boolean columnsIsEmpty = t.getColumns() == null || t.getColumns().isEmpty(); // Consider columns if teid 8.11 or lower boolean considerColumns_811 = isTeiidOrGreater(Version.TEIID_8_12_4) ? true : columnsIsEmpty; // Consider columns if teiid 8.12.4+ boolean considerColumns_8124 = isTeiidOrGreater(Version.TEIID_8_12_4) ? columnsIsEmpty : true; if (t.isVirtual() && considerColumns_811) { QueryCommand command = (QueryCommand) queryParser.parseCommand(selectTransformation); QueryResolver resolver = new QueryResolver(queryParser); resolver.resolveCommand(command, metadata); Validator validator = new Validator(); resolverReport = validator.validate(command, metadata); if (!resolverReport.hasItems() && considerColumns_8124) { List<Expression> symbols = command.getProjectedSymbols(); for (Expression column:symbols) { try { addColumn(Symbol.getShortName(column), column.getType(), t, mf); } catch (Exception e) { log(report, model, e.getMessage()); } } } if (considerColumns_8124) { determineDependencies(t, command); if (t.getInsertPlan() != null && t.isInsertPlanEnabled()) { validateUpdatePlan(model, report, metadata, t, t.getInsertPlan(), Command.TYPE_INSERT); } if (t.getUpdatePlan() != null && t.isUpdatePlanEnabled()) { validateUpdatePlan(model, report, metadata, t, t.getUpdatePlan(), Command.TYPE_UPDATE); } if (t.getDeletePlan() != null && t.isDeletePlanEnabled()) { validateUpdatePlan(model, report, metadata, t, t.getDeletePlan(), Command.TYPE_DELETE); } } } boolean addCacheHint = false; if (t.isMaterialized() && t.getMaterializedTable() == null) { List<KeyRecord> fbis = t.getFunctionBasedIndexes(); List<GroupSymbol> groups = Arrays.asList(symbol); if (fbis != null && !fbis.isEmpty()) { for (KeyRecord fbi : fbis) { for (int j = 0; j < fbi.getColumns().size(); j++) { Column c = fbi.getColumns().get(j); if (c.getParent() != fbi) { continue; } String exprString = c.getNameInSource(); try { Expression ex = queryParser.parseExpression(exprString); ResolverVisitor resolverVisitor = new ResolverVisitor(teiidVersion); resolverVisitor.resolveLanguageObject(ex, groups, metadata); if (!ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(ex).isEmpty()) { log(report, model, Messages.gs(Messages.TEIID.TEIID31114, exprString, fbi.getFullName())); } EvaluatableVisitor ev = new EvaluatableVisitor(teiidVersion); PreOrPostOrderNavigator.doVisit(ex, ev, PostOrderNavigator.PRE_ORDER); if (ev.getDeterminismLevel().compareTo(Determinism.VDB_DETERMINISTIC) < 0) { log(report, model, Messages.gs(Messages.TEIID.TEIID31115, exprString, fbi.getFullName())); } } catch (QueryResolverException e) { log(report, model, Messages.gs(Messages.TEIID.TEIID31116, exprString, fbi.getFullName(), e.getMessage())); } } } } } else { addCacheHint = true; } // this seems to parse, resolve and validate. QueryResolver resolver = new QueryResolver(queryParser); QueryNode node = resolver.resolveView(symbol, new QueryNode(t.getSelectTransformation()), SQLConstants.Reserved.SELECT, metadata); CacheHint cacheHint = node.getCommand().getCacheHint(); Long ttl = -1L; if (cacheHint != null && cacheHint.getTtl() != null && addCacheHint && t.getProperty(MATVIEW_TTL, false) == null) { ttl = cacheHint.getTtl(); t.setProperty(MATVIEW_TTL, String.valueOf(ttl)); } } if(resolverReport != null && resolverReport.hasItems()) { for (ValidatorFailure v:resolverReport.getItems()) { log(report, model, v.getStatus() == IValidatorFailure.VFStatus.ERROR?Severity.ERROR:Severity.WARNING, v.getMessage()); } } processReport(model, record, report, resolverReport); } catch (Exception e) { log(report, model, Messages.gs(Messages.TEIID.TEIID31080, record.getFullName(), e.getMessage())); } } public static void determineDependencies(AbstractMetadataRecord p, Command command) { Collection<GroupSymbol> groups = GroupCollectorVisitor.getGroupsIgnoreInlineViewsAndEvaluatableSubqueries(command, true); LinkedHashSet<AbstractMetadataRecord> values = new LinkedHashSet<AbstractMetadataRecord>(); for (GroupSymbol group : groups) { Object mid = group.getMetadataID(); if (mid instanceof TempMetadataAdapter) { mid = ((TempMetadataID)mid).getOriginalMetadataID(); } if (mid instanceof AbstractMetadataRecord) { values.add((AbstractMetadataRecord)mid); } } Collection<ElementSymbol> elems = ElementCollectorVisitor.getElements(command, true, true); for (ElementSymbol elem : elems) { Object mid = elem.getMetadataID(); if (mid instanceof TempMetadataAdapter) { mid = ((TempMetadataID)mid).getOriginalMetadataID(); } if (mid instanceof AbstractMetadataRecord) { values.add((AbstractMetadataRecord)mid); } } p.setIncomingObjects(new ArrayList<AbstractMetadataRecord>(values)); } private void validateUpdatePlan(ModelMetaData model, ValidatorReport report, IQueryMetadataInterface metadata, Table t, String plan, int type) throws Exception { Command command = null; QueryResolver queryResolver = new QueryResolver(queryParser); if (isTeiidOrGreater(Version.TEIID_8_12_4)) { command = queryParser.parseProcedure(plan, true); SyntaxFactory factory = new SyntaxFactory(queryParser.getTeiidVersion()); IGroupSymbol groupSymbol = factory.createGroupSymbol(t.getFullName()); queryResolver.resolveCommand(command, (GroupSymbol) groupSymbol, type, metadata, false); } else { command = queryParser.parseCommand(plan); queryResolver.resolveCommand(command, metadata); } //determineDependencies(t, command); -- these should be tracked against triggers ValidatorReport resolverReport = new Validator().validate(command, metadata); processReport(model, t, report, resolverReport); } private void processReport(ModelMetaData model, AbstractMetadataRecord record, ValidatorReport report, ValidatorReport resolverReport) { if(resolverReport != null && resolverReport.hasItems()) { for (ValidatorFailure v:resolverReport.getItems()) { log(report, model, v.getStatus() == IValidatorFailure.VFStatus.ERROR?Severity.ERROR:Severity.WARNING, v.getMessage()); } } } private Column addColumn(String name, Class<?> type, Table table, MetadataFactory mf) throws Exception { if (type == null) { throw new Exception(Messages.gs(Messages.TEIID.TEIID31086, name, table.getFullName())); } Column column = mf.addColumn(name, DataTypeManagerService.getInstance(teiidVersion).getDataTypeName(type), table); column.setUpdatable(table.supportsUpdate()); return column; } // this class resolves the artifacts that are dependent upon objects from other schemas // materialization sources, fk and data types (coming soon..) // ensures that even if cached metadata is used that we resolve to a single instance static class CrossSchemaResolver implements MetadataRule { private boolean keyMatches(List<String> names, KeyRecord record) { if (names.size() != record.getColumns().size()) { return false; } Set<String> keyNames = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); for (Column c: record.getColumns()) { keyNames.add(c.getName()); } for (int i = 0; i < names.size(); i++) { if (!keyNames.contains(names.get(i))) { return false; } } return true; } @Override public void execute(VDBMetaData vdb, MetadataStore store, ValidatorReport report, MetadataValidator metadataValidator) { for (Schema schema:store.getSchemaList()) { if (vdb.getImportedModels().contains(schema.getName())) { continue; } ModelMetaData model = vdb.getModel(schema.getName()); for (Table t:schema.getTables().values()) { if (t.isVirtual()) { if (t.isMaterialized() && t.getMaterializedTable() != null) { String matTableName = t.getMaterializedTable().getFullName(); int index = matTableName.indexOf(Table.NAME_DELIM_CHAR); if (index == -1) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31088, matTableName, t.getFullName())); } else { String schemaName = matTableName.substring(0, index); Schema matSchema = store.getSchema(schemaName); if (matSchema == null) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31089, schemaName, matTableName, t.getFullName())); } else { Table matTable = matSchema.getTable(matTableName.substring(index+1)); if (matTable == null) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31090, matTableName.substring(index+1), schemaName, t.getFullName())); } else { t.setMaterializedTable(matTable); } } } } } for (KeyRecord record : t.getAllKeys()) { if (record.getColumns() == null || record.getColumns().isEmpty()) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31149, t.getFullName(), record.getName())); } } List<ForeignKey> fks = t.getForeignKeys(); if (fks == null || fks.isEmpty()) { continue; } for (ForeignKey fk:fks) { // Only applicable to older teiid releases than 8.9 if (fk.getReferenceKey() != null && !metadataValidator.isTeiidOrGreater(Version.TEIID_8_9)) { //ensure derived fields are set fk.setReferenceKey(fk.getReferenceKey()); continue; } String referenceTableName = fk.getReferenceTableName(); Table referenceTable = null; if (fk.getReferenceKey() == null) { if (referenceTableName == null){ metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31091, t.getFullName())); continue; } //TODO there is an ambiguity here because we don't properly track the name parts //so we have to first check for a table name that may contain . referenceTable = schema.getTable(referenceTableName); } else { referenceTableName = fk.getReferenceKey().getParent().getFullName(); } String referenceSchemaName = schema.getName(); int index = referenceTableName.indexOf(Table.NAME_DELIM_CHAR); if (referenceTable == null) { if (index != -1) { referenceSchemaName = referenceTableName.substring(0, index); Schema referenceSchema = store.getSchema(referenceSchemaName); if (referenceSchema == null) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31093, referenceSchemaName, t.getFullName())); continue; } referenceTable = referenceSchema.getTable(referenceTableName.substring(index+1)); } if (referenceTable == null) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31092, t.getFullName(), referenceTableName.substring(index+1), referenceSchemaName)); continue; } } KeyRecord uniqueKey = null; List<String> referenceColumns = fk.getReferenceColumns(); if (fk.getReferenceKey() != null) { //index metadata logic sets the key prior to having the column names List<Column> cols = fk.getReferenceKey().getColumns(); referenceColumns = new ArrayList<String>(); for (Column col : cols) { referenceColumns.add(col.getName()); } } if (referenceColumns == null || referenceColumns.isEmpty()) { if (referenceTable.getPrimaryKey() == null) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31094, t.getFullName(), referenceTableName.substring(index+1), referenceSchemaName)); } else { uniqueKey = referenceTable.getPrimaryKey(); } } else { for (KeyRecord record : referenceTable.getUniqueKeys()) { if (keyMatches(fk.getReferenceColumns(), record)) { uniqueKey = record; break; } } if (uniqueKey == null && referenceTable.getPrimaryKey() != null && keyMatches(fk.getReferenceColumns(), referenceTable.getPrimaryKey())) { uniqueKey = referenceTable.getPrimaryKey(); } } if (uniqueKey == null) { metadataValidator.log(report, model, Messages.gs(Messages.TEIID.TEIID31095, t.getFullName(), referenceTableName.substring(index+1), referenceSchemaName, fk.getReferenceColumns())); } else { fk.setReferenceKey(uniqueKey); } } } } } } }