/*
* 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 static org.teiid.query.metadata.MaterializationMetadataRepository.*;
import java.util.*;
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.QueryParserException;
import org.teiid.api.exception.query.QueryResolverException;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidException;
import org.teiid.core.types.DataTypeManager;
import org.teiid.language.SQLConstants;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
import org.teiid.metadata.*;
import org.teiid.metadata.BaseColumn.NullType;
import org.teiid.metadata.FunctionMethod.Determinism;
import org.teiid.metadata.ProcedureParameter.Type;
import org.teiid.metadata.Table.TriggerEvent;
import org.teiid.query.QueryPlugin;
import org.teiid.query.function.metadata.FunctionMetadataValidator;
import org.teiid.query.mapping.relational.QueryNode;
import org.teiid.query.metadata.MaterializationMetadataRepository.Scope;
import org.teiid.query.parser.QueryParser;
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.Query;
import org.teiid.query.sql.lang.QueryCommand;
import org.teiid.query.sql.lang.SetQuery;
import org.teiid.query.sql.navigator.PreOrPostOrderNavigator;
import org.teiid.query.sql.proc.CreateProcedureCommand;
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.AbstractValidationVisitor;
import org.teiid.query.validator.ValidationVisitor;
import org.teiid.query.validator.Validator;
import org.teiid.query.validator.ValidatorFailure;
import org.teiid.query.validator.ValidatorReport;
import org.teiid.translator.TranslatorException;
public class MetadataValidator {
private Map<String, Datatype> typeMap;
private QueryParser parser;
interface MetadataRule {
void execute(VDBMetaData vdb, MetadataStore vdbStore, ValidatorReport report, MetadataValidator metadataValidator);
}
public MetadataValidator(Map<String, Datatype> typeMap, QueryParser parser) {
this.typeMap = typeMap;
this.parser = parser;
}
public MetadataValidator() {
this.typeMap = SystemMetadata.getInstance().getRuntimeTypeMap();
this.parser = QueryParser.getQueryParser();
}
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);
new MatViewPropertiesValidator().execute(vdb, store, report, this);
}
return report;
}
// At minimum the model must have table/view, procedure or function
static 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, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31070, model.getName()));
}
for (Table t:schema.getTables().values()) {
if (t.getColumns() == null || t.getColumns().size() == 0) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.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(schema.getFunctions().values(),report, store.getDatatypes());
if(report.hasItems()) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.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, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31152, record.getFullName()));
}
}
}
}
// do not allow foreign tables, source functions in view model and vice versa
static 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, QueryPlugin.Util.gs(QueryPlugin.Event.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
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, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31112, p.getFullName()));
}
}
}
if (param.getType() == ProcedureParameter.Type.ReturnValue) {
if (hasReturn) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31107, p.getFullName()));
}
hasReturn = true;
} else if (p.isFunction() && param.getType() != ProcedureParameter.Type.In) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31165, p.getFullName(), param.getFullName()));
}
if (!names.add(param.getName())) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31106, p.getFullName(), param.getFullName()));
}
}
if (!p.isVirtual() && !model.isSource()) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31077, p.getFullName(), model.getName()));
}
if (p.isFunction()) {
if (!hasReturn) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31166, p.getFullName()));
}
if (p.isVirtual() && p.getQueryPlan() == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.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, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31112, func.getFullName()));
}
}
if (func.getPushdown().equals(FunctionMethod.PushDown.MUST_PUSHDOWN) && !model.isSource()) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31078, func.getFullName(), model.getName()));
}
}
}
}
}
// Resolves metadata query plans to make sure they are accurate
static class ResolveQueryPlans implements MetadataRule {
@Override
public void execute(VDBMetaData vdb, MetadataStore store, ValidatorReport report, MetadataValidator metadataValidator) {
QueryMetadataInterface metadata = vdb.getAttachment(QueryMetadataInterface.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(vdb.getName(), vdb.getVersion(), metadataValidator.typeMap, model) {
@Override
protected void setUUID(AbstractMetadataRecord record) {
if (count >= 0) {
count = Integer.MIN_VALUE;
}
super.setUUID(record);
}
};
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.getTableType() == Table.Type.TemporaryTable) {
continue;
}
if (t.isVirtual()) {
if (t.getSelectTransformation() == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31079, t.getFullName(), model.getName()));
}
else {
metadataValidator.validate(vdb, model, t, report, metadata, mf);
}
} else {
for (Trigger tr : t.getTriggers().values()) {
int commandType = Command.TYPE_INSERT;
if (tr.getEvent() == TriggerEvent.DELETE) {
commandType = Command.TYPE_DELETE;
} else if (tr.getEvent() == TriggerEvent.UPDATE) {
commandType = Command.TYPE_UPDATE;
}
try {
metadataValidator.validateUpdatePlan(model, report, metadata, t, tr.getPlan(), commandType);
} catch (TeiidException e) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31080, record.getFullName(), e.getMessage()));
}
}
}
} else if (record instanceof Procedure) {
Procedure p = (Procedure)record;
if (p.isVirtual()) {
if (p.getQueryPlan() == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31081, p.getFullName(), model.getName()));
}
else {
metadataValidator.validate(vdb, model, p, report, metadata, mf);
}
}
}
}
}
}
}
static class MatViewPropertiesValidator implements MetadataRule {
@Override
public void execute(final 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 (final Table t:schema.getTables().values()) {
if (t.isVirtual() && t.isMaterialized() && t.getMaterializedTable() != null) {
Table matTable = t.getMaterializedTable();
Table stageTable = t.getMaterializedStageTable();
// set the original names of the VDB with view, incase VDB is imported
// into another VDB
t.setProperty(MATVIEW_OWNER_VDB_NAME, vdb.getName());
t.setProperty(MATVIEW_OWNER_VDB_VERSION, vdb.getVersion());
String beforeScript = t.getProperty(MATVIEW_BEFORE_LOAD_SCRIPT, false);
String afterScript = t.getProperty(MATVIEW_AFTER_LOAD_SCRIPT, false);
if (beforeScript == null || afterScript == null) {
metadataValidator.log(report, model, Severity.WARNING, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31155, t.getFullName()));
}
String matViewLoadNumberColumn = t.getProperty(MATVIEW_LOADNUMBER_COLUMN, false);
verifyTableColumns(model, report, metadataValidator, t, matTable, matViewLoadNumberColumn);
if(stageTable != null) {
verifyTableColumns(model, report, metadataValidator, t, stageTable, matViewLoadNumberColumn);
}
if (matViewLoadNumberColumn != null) {
Column column = matTable.getColumnByName(matViewLoadNumberColumn);
if (column == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31218, t.getFullName(), matTable.getFullName(), matViewLoadNumberColumn));
continue;
} else if (!column.getRuntimeType().equalsIgnoreCase(DataTypeManager.DefaultDataTypes.LONG)) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31215, t.getFullName(), matTable.getFullName(), matViewLoadNumberColumn, column.getRuntimeType()));
continue;
}
}
String status = t.getProperty(MATVIEW_STATUS_TABLE, false);
String loadScript = t.getProperty(MATVIEW_LOAD_SCRIPT, false);
if (status == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31154, t.getFullName()));
continue;
}
if (matViewLoadNumberColumn == null && stageTable == null && loadScript == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31216, t.getFullName()));
continue;
}
String scope = t.getProperty(MATVIEW_SHARE_SCOPE, false);
if (scope != null && !scope.equalsIgnoreCase(Scope.IMPORTED.name())
&& !scope.equalsIgnoreCase(Scope.FULL.name())) {
metadataValidator.log(report, model, Severity.WARNING,QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31253, t.getFullName(), scope));
t.setProperty(MATVIEW_SHARE_SCOPE, Scope.IMPORTED.name());
}
String stalenessString = t.getProperty(MaterializationMetadataRepository.MATVIEW_MAX_STALENESS_PCT, false);
if (stalenessString != null) {
final HashSet<Table> ids = new HashSet<Table>();
listPhysicalTables(t.getIncomingObjects(), new TableFilter() {
@Override
public void accept(Table physicalTable) {
ids.add(physicalTable);
}
});
for (Table physicalTable : ids) {
addLazyMatViewTrigger(vdb, t, physicalTable, Table.TriggerEvent.INSERT);
addLazyMatViewTrigger(vdb, t, physicalTable, Table.TriggerEvent.UPDATE);
addLazyMatViewTrigger(vdb, t, physicalTable, Table.TriggerEvent.DELETE);
}
}
Table statusTable = findTableByName(store, status);
if (statusTable == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31197, t.getFullName(), status));
continue;
}
Map<String, Class<?>> statusTypeMap = new TreeMap<String, Class<?>>(String.CASE_INSENSITIVE_ORDER);
statusTypeMap.put("VDBNAME", DataTypeManager.DefaultDataClasses.STRING); //$NON-NLS-1$
statusTypeMap.put("VDBVERSION", DataTypeManager.DefaultDataClasses.STRING); //$NON-NLS-1$
statusTypeMap.put("SCHEMANAME", DataTypeManager.DefaultDataClasses.STRING); //$NON-NLS-1$
statusTypeMap.put("NAME", DataTypeManager.DefaultDataClasses.STRING); //$NON-NLS-1$
statusTypeMap.put("TARGETSCHEMANAME", DataTypeManager.DefaultDataClasses.STRING); //$NON-NLS-1$
statusTypeMap.put("TARGETNAME", DataTypeManager.DefaultDataClasses.STRING); //$NON-NLS-1$
statusTypeMap.put("VALID", DataTypeManager.DefaultDataClasses.BOOLEAN); //$NON-NLS-1$
statusTypeMap.put("LOADSTATE", DataTypeManager.DefaultDataClasses.STRING); //$NON-NLS-1$
statusTypeMap.put("CARDINALITY", DataTypeManager.DefaultDataClasses.LONG); //$NON-NLS-1$
statusTypeMap.put("UPDATED", DataTypeManager.DefaultDataClasses.TIMESTAMP); //$NON-NLS-1$
statusTypeMap.put("LOADNUMBER", DataTypeManager.DefaultDataClasses.LONG); //$NON-NLS-1$
statusTypeMap.put("NODENAME", DataTypeManager.DefaultDataClasses.STRING); //$NON-NLS-1$
statusTypeMap.put("STALECOUNT", DataTypeManager.DefaultDataClasses.LONG); //$NON-NLS-1$
List<Column> statusColumns = statusTable.getColumns();
for(int i = 0 ; i < statusColumns.size() ; i ++) {
String name = statusColumns.get(i).getName();
Class<?> expectedType = statusTypeMap.remove(name);
if (expectedType == null) {
continue; //unknown column
}
Class<?> type = statusColumns.get(i).getJavaType();
if(type != expectedType) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31195, t.getName(), statusTable.getFullName(), name, type, expectedType));
}
}
if (!statusTypeMap.isEmpty()) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31196, t.getName(), statusTable.getFullName(), statusTypeMap.keySet()));
}
// validate the load scripts
String manage = t.getProperty(ALLOW_MATVIEW_MANAGEMENT, false);
if (Boolean.valueOf(manage)) {
loadScriptsValidation(vdb, report, metadataValidator, model, t, t.getProperty(ON_VDB_START_SCRIPT, false), "ON_VDB_START_SCRIPT");//$NON-NLS-1$
loadScriptsValidation(vdb, report, metadataValidator, model, t, t.getProperty(ON_VDB_DROP_SCRIPT, false), "ON_VDB_DROP_SCRIPT");//$NON-NLS-1$
}
loadScriptsValidation(vdb, report, metadataValidator, model, t, t.getProperty(MATVIEW_BEFORE_LOAD_SCRIPT, false), "MATVIEW_BEFORE_LOAD_SCRIPT");//$NON-NLS-1$
loadScriptsValidation(vdb, report, metadataValidator, model, t, t.getProperty(MATVIEW_LOAD_SCRIPT, false), "MATVIEW_LOAD_SCRIPT");//$NON-NLS-1$
loadScriptsValidation(vdb, report, metadataValidator, model, t, t.getProperty(MATVIEW_AFTER_LOAD_SCRIPT, false), "MATVIEW_AFTER_LOAD_SCRIPT");//$NON-NLS-1$
} else if (t.isVirtual() && t.isMaterialized() && t.getMaterializedTable() == null) {
// internal materialization
String manage = t.getProperty(ALLOW_MATVIEW_MANAGEMENT, false);
if (!Boolean.valueOf(manage)) {
continue;
}
String updatable = t.getProperty(MATVIEW_UPDATABLE, false);
if (updatable == null || !Boolean.parseBoolean(updatable)) {
metadataValidator.log(report, model, Severity.WARNING, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31217, t.getFullName()));
}
}
}
}
}
interface TableFilter {
void accept(Table table);
}
private void listPhysicalTables(Collection<AbstractMetadataRecord> records, TableFilter tableFilter) {
for (AbstractMetadataRecord record : records) {
if (record instanceof Table) {
Table table = (Table)record;
if (table.isPhysical()) {
tableFilter.accept(table);
} else {
listPhysicalTables(table.getIncomingObjects(), tableFilter);
}
} else if (record instanceof Procedure) {
Procedure proc = (Procedure)record;
if (proc.isVirtual()) {
listPhysicalTables(proc.getIncomingObjects(), tableFilter);
}
}
}
}
private void addLazyMatViewTrigger(VDBMetaData vdb, Table t, Table st, Table.TriggerEvent event) {
String name = "ON_"+st.getName()+"_"+event.name()+"_FOR_"+t.getName()+"_FOR_LAZY_SNAPSHOT";
String plan = "FOR EACH ROW\n"
+ "BEGIN ATOMIC\n"
+ "EXECUTE SYSADMIN.updateStaleCount(schemaName=>'"+t.getParent().getName()+"', viewName=>'"+t.getName()+"');\n"
+ "END\n";
Trigger trigger = new Trigger();
trigger.setName(name);
trigger.setEvent(event);
trigger.setPlan(plan);
trigger.setAfter(true);
trigger.setProperty(DDLStringVisitor.GENERATED, "true");
st.getTriggers().put(name, trigger);
LogManager.logDetail(LogConstants.CTX_MATVIEWS, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31256, st.getName(), t.getName()));
}
private void loadScriptsValidation(VDBMetaData vdb, ValidatorReport report, MetadataValidator metadataValidator, ModelMetaData model, Table matView, String script, String option) {
if(script == null) {
return;
}
QueryMetadataInterface metadata = vdb.getAttachment(QueryMetadataInterface.class);
QueryParser queryParser = QueryParser.getQueryParser();
try {
Command command = queryParser.parseCommand(script);
if (command instanceof CreateProcedureCommand) {
((CreateProcedureCommand)command).setResultSetColumns(Collections.EMPTY_LIST);
}
QueryResolver.resolveCommand(command, metadata);
AbstractValidationVisitor visitor = new ValidationVisitor();
ValidatorReport subReport = Validator.validate(command, metadata, visitor);
metadataValidator.processReport(model, matView, report, subReport);
} catch (QueryParserException | QueryResolverException | TeiidComponentException e) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31198, matView.getFullName(), option, script, e));
}
}
private void verifyTableColumns(ModelMetaData model, ValidatorReport report,
MetadataValidator metadataValidator, Table view, Table matView, String ignoreColumnOnMatView) {
List<Column> columns = view.getColumns();
for(int i = 0 ; i < columns.size() ; i ++) {
Column column = columns.get(i);
Column matViewColumn = matView.getColumnByName(column.getName());
if (matViewColumn == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31193, column.getName(), matView.getFullName(), view.getFullName()));
} else if(!column.getDatatypeUUID().equals(matViewColumn.getDatatypeUUID())){
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31194, matViewColumn.getName(), matView.getFullName(), column.getName(), view.getFullName()));
}
}
}
}
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);
int messageLevel = MessageLevel.WARNING;
if (severity == Severity.ERROR) {
report.handleValidationError(msg);
} else {
messageLevel = MessageLevel.INFO;
}
LogManager.log(messageLevel, LogConstants.CTX_QUERY_RESOLVER, msg);
}
private void validate(VDBMetaData vdb, ModelMetaData model, AbstractMetadataRecord record, ValidatorReport report, QueryMetadataInterface metadata, MetadataFactory mf) {
ValidatorReport resolverReport = null;
try {
if (record instanceof Procedure) {
Procedure p = (Procedure)record;
Command command = parser.parseProcedure(p.getQueryPlan(), false);
QueryResolver.resolveCommand(command, new GroupSymbol(p.getFullName()), Command.TYPE_STORED_PROCEDURE, metadata, false);
resolverReport = Validator.validate(command, metadata);
determineDependencies(p, command);
} else if (record instanceof Table) {
Table t = (Table)record;
GroupSymbol symbol = new GroupSymbol(t.getFullName());
ResolverUtil.resolveGroup(symbol, metadata);
String selectTransformation = t.getSelectTransformation();
if (t.isVirtual()) {
QueryCommand command = (QueryCommand)parser.parseCommand(selectTransformation);
QueryResolver.resolveCommand(command, metadata);
resolverReport = Validator.validate(command, metadata);
if (!resolverReport.hasItems() && (t.getColumns() == null || t.getColumns().isEmpty())) {
List<Expression> symbols = command.getProjectedSymbols();
for (Expression column:symbols) {
try {
addColumn(Symbol.getShortName(column), column.getType(), t, mf);
} catch (TranslatorException e) {
log(report, model, e.getMessage());
}
}
}
if (t.getColumns() != null && !t.getColumns().isEmpty()) {
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 = parser.parseExpression(exprString);
ResolverVisitor.resolveLanguageObject(ex, groups, metadata);
if (!ValueIteratorProviderCollectorVisitor.getValueIteratorProviders(ex).isEmpty()) {
log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31114, exprString, fbi.getFullName()));
}
EvaluatableVisitor ev = new EvaluatableVisitor();
PreOrPostOrderNavigator.doVisit(ex, ev, PreOrPostOrderNavigator.PRE_ORDER);
if (ev.getDeterminismLevel().compareTo(Determinism.VDB_DETERMINISTIC) < 0) {
log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31115, exprString, fbi.getFullName()));
}
} catch (QueryResolverException e) {
log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31116, exprString, fbi.getFullName(), e.getMessage()));
}
}
}
}
}
else {
addCacheHint = true;
}
if (addCacheHint && t.isMaterialized()) {
// this seems to parse, resolve and validate.
QueryNode node = QueryResolver.resolveView(symbol, new QueryNode(selectTransformation), SQLConstants.Reserved.SELECT, metadata, true);
CacheHint cacheHint = node.getCommand().getCacheHint();
Long ttl = -1L;
if (cacheHint != null) {
if (cacheHint.getTtl() != null && t.getProperty(MaterializationMetadataRepository.MATVIEW_TTL, false) == null) {
ttl = cacheHint.getTtl();
t.setProperty(MaterializationMetadataRepository.MATVIEW_TTL, String.valueOf(ttl));
}
if (cacheHint.getUpdatable() != null && t.getProperty(MaterializationMetadataRepository.MATVIEW_UPDATABLE, false) == null) {
t.setProperty(MaterializationMetadataRepository.MATVIEW_UPDATABLE, String.valueOf(cacheHint.getUpdatable()));
}
if (cacheHint.getPrefersMemory() != null && t.getProperty(MaterializationMetadataRepository.MATVIEW_PREFER_MEMORY, false) == null) {
t.setProperty(MaterializationMetadataRepository.MATVIEW_PREFER_MEMORY, String.valueOf(cacheHint.getPrefersMemory()));
}
if (cacheHint.getScope() != null && t.getProperty(MaterializationMetadataRepository.MATVIEW_SHARE_SCOPE, false) == null) {
log(report, model, Severity.WARNING, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31252, t.getName(), cacheHint.getScope().name()));
t.setProperty(MaterializationMetadataRepository.MATVIEW_SHARE_SCOPE, MaterializationMetadataRepository.Scope.IMPORTED.name());
}
}
}
}
processReport(model, record, report, resolverReport);
} catch (TeiidException e) {
log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31080, record.getFullName(), e.getMessage()));
}
}
public static void determineDependencies(AbstractMetadataRecord p, Command command) {
LinkedHashSet<AbstractMetadataRecord> values = new LinkedHashSet<AbstractMetadataRecord>();
collectDependencies(command, values);
p.setIncomingObjects(new ArrayList<AbstractMetadataRecord>(values));
if (p instanceof Table) {
Table t = (Table)p;
for (int i = 0; i < t.getColumns().size(); i++) {
LinkedHashSet<AbstractMetadataRecord> columnValues = new LinkedHashSet<AbstractMetadataRecord>();
Column c = t.getColumns().get(i);
c.setIncomingObjects(columnValues);
determineDependencies(command, c, i, columnValues);
}
}
}
private static void collectDependencies(org.teiid.query.sql.LanguageObject lo,
LinkedHashSet<AbstractMetadataRecord> values) {
Collection<GroupSymbol> groups = GroupCollectorVisitor.getGroupsIgnoreInlineViews(lo, true);
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(lo, 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);
}
}
}
private static void determineDependencies(Command command, Column c, int index, LinkedHashSet<AbstractMetadataRecord> columnValues) {
if (command instanceof Query) {
Expression ex = command.getProjectedSymbols().get(index);
collectDependencies(ex, columnValues);
} else if (command instanceof SetQuery) {
determineDependencies(((SetQuery)command).getLeftQuery(), c, index, columnValues);
determineDependencies(((SetQuery)command).getRightQuery(), c, index, columnValues);
}
}
private static Table findTableByName(MetadataStore store, String name) {
Table table = null;
int index = name.indexOf(Table.NAME_DELIM_CHAR);
if(index == -1) {
for(Schema schema : store.getSchemaList()) {
table = schema.getTable(name);
if(table != null) {
break;
}
}
} else {
String schemaName = name.substring(0, index);
Schema schema = store.getSchema(schemaName);
if(schema != null) {
table = schema.getTable(name.substring(index+1));
}
}
return table;
}
private void validateUpdatePlan(ModelMetaData model,
ValidatorReport report,
QueryMetadataInterface metadata,
Table t, String plan, int type) throws QueryParserException, QueryResolverException,
TeiidComponentException {
Command command = parser.parseProcedure(plan, true);
QueryResolver.resolveCommand(command, new GroupSymbol(t.getFullName()), type, metadata, false);
//determineDependencies(t, command); -- these should be tracked against triggers
ValidatorReport resolverReport = 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() == ValidatorFailure.Status.ERROR?Severity.ERROR:Severity.WARNING, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31080, record.getFullName(), v.getMessage()));
}
}
}
private Column addColumn(String name, Class<?> type, Table table, MetadataFactory mf) throws TranslatorException {
if (type == null) {
throw new TranslatorException(QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31086, name, table.getFullName()));
}
Column column = mf.addColumn(name, DataTypeManager.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, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31088, matTableName, t.getFullName()));
}
else {
String schemaName = matTableName.substring(0, index);
Schema matSchema = store.getSchema(schemaName);
if (matSchema == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31089, schemaName, matTableName, t.getFullName()));
}
else {
Table matTable = matSchema.getTable(matTableName.substring(index+1));
if (matTable == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31090, matTableName.substring(index+1), schemaName, t.getFullName()));
}
else {
t.setMaterializedTable(matTable);
}
}
}
String stageTable = t.getProperty(MATVIEW_STAGE_TABLE, false);
if(stageTable != null){
Table materializedStageTable = findTableByName(store, stageTable);
if(materializedStageTable == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31192, t.getFullName(), MATVIEW_STAGE_TABLE, stageTable));
} else {
t.setMaterializedStageTable(materializedStageTable);
}
}
}
}
for (KeyRecord record : t.getAllKeys()) {
if (record.getColumns() == null || record.getColumns().isEmpty()) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31149, t.getFullName(), record.getName()));
}
}
List<ForeignKey> fks = t.getForeignKeys();
if (fks == null || fks.isEmpty()) {
continue;
}
for (ForeignKey fk:fks) {
String referenceTableName = fk.getReferenceTableName();
Table referenceTable = null;
if (fk.getReferenceKey() == null) {
if (referenceTableName == null){
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.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, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31093, referenceSchemaName, t.getFullName()));
continue;
}
referenceTable = referenceSchema.getTable(referenceTableName.substring(index+1));
}
if (referenceTable == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.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, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31094, t.getFullName(), referenceTableName.substring(index+1), referenceSchemaName));
}
else {
uniqueKey = referenceTable.getPrimaryKey();
}
} else {
for (KeyRecord record : referenceTable.getUniqueKeys()) {
if (keyMatches(referenceColumns, record)) {
uniqueKey = record;
break;
}
}
if (uniqueKey == null && referenceTable.getPrimaryKey() != null && keyMatches(referenceColumns, referenceTable.getPrimaryKey())) {
uniqueKey = referenceTable.getPrimaryKey();
}
}
if (uniqueKey == null) {
metadataValidator.log(report, model, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31095, t.getFullName(), referenceTableName.substring(index+1), referenceSchemaName, referenceColumns));
}
else {
fk.setReferenceKey(uniqueKey);
}
}
}
}
}
}
}