/* * Copyright (C) 2010 eXo Platform SAS. * * This 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 software 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 software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xcmis.search.query.validate; import org.xcmis.search.InvalidQueryException; import org.xcmis.search.VisitException; import org.xcmis.search.Visitors.AbstractModelVisitor; import org.xcmis.search.content.Schema; import org.xcmis.search.content.TableDoesntExistException; import org.xcmis.search.content.Schema.Table; import org.xcmis.search.model.Query; import org.xcmis.search.model.column.Column; import org.xcmis.search.model.constraint.ChildNode; import org.xcmis.search.model.constraint.Comparison; import org.xcmis.search.model.constraint.DescendantNode; import org.xcmis.search.model.constraint.FullTextSearch; import org.xcmis.search.model.constraint.Operator; import org.xcmis.search.model.constraint.PropertyExistence; import org.xcmis.search.model.constraint.SameNode; import org.xcmis.search.model.operand.DynamicOperand; import org.xcmis.search.model.operand.FullTextSearchScore; import org.xcmis.search.model.operand.Length; import org.xcmis.search.model.operand.LowerCase; import org.xcmis.search.model.operand.NodeDepth; import org.xcmis.search.model.operand.NodeLocalName; import org.xcmis.search.model.operand.NodeName; import org.xcmis.search.model.operand.PropertyValue; import org.xcmis.search.model.operand.UpperCase; import org.xcmis.search.model.ordering.Ordering; import org.xcmis.search.model.source.SelectorName; import org.xcmis.search.model.source.join.ChildNodeJoinCondition; import org.xcmis.search.model.source.join.DescendantNodeJoinCondition; import org.xcmis.search.model.source.join.EquiJoinCondition; import org.xcmis.search.model.source.join.SameNodeJoinCondition; import org.xcmis.search.query.QueryExecutionContext; import org.xcmis.search.query.QueryExecutionExceptions; import java.util.HashMap; import java.util.Map; /** * A {@link Visitor} implementation that validates a query's used of a {@link Schema} and records any problems as errors. */ public class Validator extends AbstractModelVisitor { private final QueryExecutionContext context; private final QueryExecutionExceptions problems; private final Map<SelectorName, Table> selectorsByNameOrAlias; private final Map<SelectorName, Table> selectorsByName; private final Map<String, Schema.Column> columnsByAlias; private final boolean validateColumnExistence; /** * @param context the query context * @param selectorsByName the {@link Table tables} by their name or alias, as defined by the selectors */ public Validator(QueryExecutionContext context, Map<SelectorName, Table> selectorsByName) { this.context = context; this.problems = this.context.getExecutionExceptions(); this.selectorsByNameOrAlias = selectorsByName; this.selectorsByName = new HashMap<SelectorName, Table>(); for (Table table : selectorsByName.values()) { this.selectorsByName.put(table.getName(), table); } this.columnsByAlias = new HashMap<String, Schema.Column>(); this.validateColumnExistence = true; } /** * Check if selector exists in list of selectors. * @param selectorName * @return */ public Table checkSelectorExistance(SelectorName selectorName) { Table table = selectorsByNameOrAlias.get(selectorName); if (table == null) { // Try looking up the table by it's real name (if an alias were used) ... table = selectorsByName.get(selectorName); } if (table == null) { problems.addException(new TableDoesntExistException("Table " + selectorName.getName() + " doesnt exist")); } return table; } /** * Check if selector exists in list of selectors. * @param selectorName * @param propertyName * @param columnIsRequired */ public Schema.Column checkTableAndColumnExistance(SelectorName selectorName, String propertyName, boolean columnIsRequired) { Table table = checkSelectorExistance(selectorName); //no need to check select ALL properties. if (table != null && !propertyName.equals("*")) { Schema.Column column = table.getColumn(propertyName); if (column == null) { // Maybe the supplied property name is really an alias ... column = this.columnsByAlias.get(propertyName); if (column == null && columnIsRequired) { problems.addException(new InvalidQueryException("Column " + propertyName + " doesnt exist on table " + selectorName.getName() + " or not allowed for search")); } } return column; } return null; } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.constraint.ChildNode) */ @Override public void visit(ChildNode node) throws VisitException { checkSelectorExistance(node.getSelectorName()); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.source.join.ChildNodeJoinCondition) */ @Override public void visit(ChildNodeJoinCondition node) throws VisitException { checkSelectorExistance(node.getChildSelectorName()); checkSelectorExistance(node.getParentSelectorName()); if (node.getChildSelectorName().equals(node.getParentSelectorName())) { problems.addException(new InvalidQueryException(node.getChildSelectorName() + " is the same as " + node.getParentSelectorName())); } } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.column.Column) */ @Override public void visit(Column node) throws VisitException { if (!node.isFunction()) { //column should exist's checkTableAndColumnExistance(node.getSelectorName(), node.getPropertyName(), true); } } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.constraint.Comparison) */ @Override public void visit(Comparison node) throws VisitException { super.visit(node); DynamicOperand dynamicOperand = node.getOperand1(); if (dynamicOperand instanceof PropertyValue) { PropertyValue propertyValueOperand = (PropertyValue)dynamicOperand; Table table = checkSelectorExistance(propertyValueOperand.getSelectorName()); //no need to check select ALL properties. String propertyName = propertyValueOperand.getPropertyName(); if (table != null && !propertyName.equals("*")) { Schema.Column column = table.getColumn(propertyName); if (column != null) { boolean isAvaileable = false; for (Operator operator : column.getAvailableQueryOperators()) { if (node.getOperator().equals(operator)) { isAvaileable = true; break; } } if (!isAvaileable) { throw new VisitException("Operator :" + node.getOperator() + " is not availeble query operator for property '" + propertyValueOperand.getPropertyName() + "' of the table '" + propertyValueOperand.getSelectorName() + "'"); } } } } else if (dynamicOperand instanceof Length) { //TODO Length is not accepteable for boolean other? } else if (dynamicOperand instanceof NodeName) { //TODO NodeName is not accepteable for long and boolean } else if (dynamicOperand instanceof NodeLocalName) { //TODO NodeName is not accepteable for long and boolean } } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.constraint.DescendantNode) */ @Override public void visit(DescendantNode node) throws VisitException { checkSelectorExistance(node.getSelectorName()); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.source.join.DescendantNodeJoinCondition) */ @Override public void visit(DescendantNodeJoinCondition node) throws VisitException { checkSelectorExistance(node.getAncestorSelectorName()); checkSelectorExistance(node.getDescendantSelectorName()); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.source.join.EquiJoinCondition) */ @Override public void visit(EquiJoinCondition node) throws VisitException { checkTableAndColumnExistance(node.getSelector1Name(), node.getProperty1Name(), true); checkTableAndColumnExistance(node.getSelector2Name(), node.getProperty2Name(), true); if (node.getSelector1Name().equals(node.getSelector2Name())) { problems.addException(new InvalidQueryException(node.getSelector1Name() + " is the same as " + node.getSelector2Name())); } } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.constraint.FullTextSearch) */ @Override public void visit(FullTextSearch node) throws VisitException { SelectorName selectorName = node.getSelectorName(); if (node.getPropertyName() != null) { Schema.Column column = checkTableAndColumnExistance(selectorName, node.getPropertyName(), this.validateColumnExistence); if (column != null) { // Make sure the column is full-text searchable ... if (!column.isFullTextSearchable()) { problems.addException(new InvalidQueryException("Column " + column.getName() + " on table " + selectorName + " does not support full-text searching")); } } } else { Table table = checkSelectorExistance(selectorName); if (table != null) { // Make sure there is at least one column on the table that is full-text searchable ... boolean searchable = false; for (Schema.Column column : table.getColumns()) { if (column.isFullTextSearchable()) { searchable = true; break; } } if (!searchable) { problems.addException(new InvalidQueryException("Table '" + selectorName + "' has no columns that support full-text searching")); } } } } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.operand.FullTextSearchScore) */ @Override public void visit(FullTextSearchScore node) throws VisitException { checkSelectorExistance(node.getSelectorName()); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.operand.Length) */ @Override public void visit(Length node) throws VisitException { checkSelectorExistance(node.getSelectorName()); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.operand.LowerCase) */ @Override public void visit(LowerCase node) throws VisitException { checkSelectorExistance(node.getSelectorName()); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.operand.NodeDepth) */ @Override public void visit(NodeDepth depth) throws VisitException { checkSelectorExistance(depth.getSelectorName()); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.operand.NodeLocalName) */ @Override public void visit(NodeLocalName node) throws VisitException { checkSelectorExistance(node.getSelectorName()); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.operand.NodeName) */ @Override public void visit(NodeName node) throws VisitException { checkSelectorExistance(node.getSelectorName()); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.constraint.PropertyExistence) */ @Override public void visit(PropertyExistence node) throws VisitException { checkTableAndColumnExistance(node.getSelectorName(), node.getPropertyName(), true); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.operand.PropertyValue) */ @Override public void visit(PropertyValue node) throws VisitException { checkTableAndColumnExistance(node.getSelectorName(), node.getPropertyName(), true); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.Query) */ @Override public void visit(Query node) throws VisitException { // Collect the map of columns by alias for this query ... this.columnsByAlias.clear(); for (Column column : node.getColumns()) { if (!column.isFunction()) { // Find the schemata column ... Table table = checkSelectorExistance(column.getSelectorName()); if (table != null) { Schema.Column tableColumn = table.getColumn(column.getPropertyName()); if (tableColumn != null) { this.columnsByAlias.put(column.getColumnName(), tableColumn); } } } } super.visit(node); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.ordering.Ordering) */ @Override public void visit(Ordering node) throws VisitException { //TODO check query ordereable } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.constraint.SameNode) */ @Override public void visit(SameNode node) throws VisitException { checkSelectorExistance(node.getSelectorName()); } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.source.join.SameNodeJoinCondition) */ @Override public void visit(SameNodeJoinCondition node) throws VisitException { checkSelectorExistance(node.getSelector1Name()); checkSelectorExistance(node.getSelector2Name()); if (node.getSelector1Name().equals(node.getSelector2Name())) { problems.addException(new InvalidQueryException(node.getSelector1Name() + " is the same as " + node.getSelector2Name())); } } /** * @see org.xcmis.search.Visitors.AbstractModelVisitor#visit(org.xcmis.search.model.operand.UpperCase) */ @Override public void visit(UpperCase node) throws VisitException { checkSelectorExistance(node.getSelectorName()); } }