0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.modules.sql.framework.model.visitors;
0042:
0043: import java.sql.Types;
0044: import java.util.ArrayList;
0045: import java.util.Collection;
0046: import java.util.Collections;
0047: import java.util.HashSet;
0048: import java.util.Iterator;
0049: import java.util.List;
0050: import java.util.Map;
0051: import org.axiondb.AxionException;
0052: import org.axiondb.parser.AxionDateTimeFormatParser;
0053: import org.netbeans.modules.sql.framework.model.DBColumn;
0054: import org.netbeans.modules.sql.framework.common.jdbc.SQLUtils;
0055: import org.netbeans.modules.sql.framework.common.utils.PhysicalTable;
0056: import org.netbeans.modules.sql.framework.model.ColumnRef;
0057: import org.netbeans.modules.sql.framework.model.RuntimeInput;
0058: import org.netbeans.modules.sql.framework.model.SQLCaseOperator;
0059: import org.netbeans.modules.sql.framework.model.SQLCastOperator;
0060: import org.netbeans.modules.sql.framework.model.SQLCondition;
0061: import org.netbeans.modules.sql.framework.model.SQLConnectableObject;
0062: import org.netbeans.modules.sql.framework.model.SQLConstants;
0063: import org.netbeans.modules.sql.framework.model.SQLDBColumn;
0064: import org.netbeans.modules.sql.framework.model.SQLDefinition;
0065: import org.netbeans.modules.sql.framework.model.SQLFilter;
0066: import org.netbeans.modules.sql.framework.model.SQLGenericOperator;
0067: import org.netbeans.modules.sql.framework.model.SQLGroupBy;
0068: import org.netbeans.modules.sql.framework.model.SQLInputObject;
0069: import org.netbeans.modules.sql.framework.model.SQLJoinOperator;
0070: import org.netbeans.modules.sql.framework.model.SQLJoinView;
0071: import org.netbeans.modules.sql.framework.model.SQLLiteral;
0072: import org.netbeans.modules.sql.framework.model.SQLObject;
0073: import org.netbeans.modules.sql.framework.model.SQLPredicate;
0074: import org.netbeans.modules.sql.framework.model.SQLWhen;
0075: import org.netbeans.modules.sql.framework.model.SourceColumn;
0076: import org.netbeans.modules.sql.framework.model.SourceTable;
0077: import org.netbeans.modules.sql.framework.model.TargetColumn;
0078: import org.netbeans.modules.sql.framework.model.TargetTable;
0079: import org.netbeans.modules.sql.framework.model.ValidationInfo;
0080: import org.netbeans.modules.sql.framework.model.impl.SQLCustomOperatorImpl;
0081: import org.netbeans.modules.sql.framework.model.impl.ValidationInfoImpl;
0082: import org.netbeans.modules.sql.framework.model.utils.SQLObjectUtil;
0083: import org.openide.util.NbBundle;
0084: import com.sun.sql.framework.exception.BaseException;
0085: import com.sun.sql.framework.jdbc.DBConstants;
0086: import net.java.hulp.i18n.Logger;
0087: import com.sun.sql.framework.utils.StringUtil;
0088: import org.netbeans.modules.etl.logger.Localizer;
0089: import org.netbeans.modules.etl.logger.LogUtil;
0090: import org.netbeans.modules.sql.framework.model.Index;
0091:
0092: /**
0093: * @author Ritesh Adval
0094: * @version $Revision$
0095: */
0096: public class SQLValidationVisitor implements SQLVisitor {
0097:
0098: private static transient final Logger mLogger = LogUtil
0099: .getLogger(SQLValidationVisitor.class.getName());
0100: private static transient final Localizer mLoc = Localizer.get();
0101: private static final HashSet<String> DATE_FORMAT_OPS = new HashSet<String>();
0102:
0103: static {
0104: DATE_FORMAT_OPS.add("isvaliddatetime");
0105: DATE_FORMAT_OPS.add("chartodate");
0106: DATE_FORMAT_OPS.add("datetochar");
0107: }
0108: private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
0109: private static final String LOG_CATEGORY = SQLValidationVisitor.class
0110: .getName();
0111: private List<ValidationInfo> validationInfoList = new ArrayList<ValidationInfo>();
0112:
0113: public List<ValidationInfo> getValidationInfoList() {
0114: return this .validationInfoList;
0115: }
0116:
0117: /**
0118: * Indicates whether the given List of ValidationInfo instances contains at least one
0119: * validation error.
0120: *
0121: * @param valInfoList Collection of ValidationInfo instances to be evaluated.
0122: * @return true if at least one item in <code>valInfoList</code> is an error.
0123: */
0124: public boolean hasErrors(Collection valInfoList) {
0125: boolean ret = false;
0126:
0127: if (valInfoList != null) {
0128: Iterator itr = valInfoList.iterator();
0129: ValidationInfo vInfo = null;
0130:
0131: while (itr.hasNext()) {
0132: vInfo = (ValidationInfo) itr.next();
0133: if (vInfo.getValidationType() == ValidationInfo.VALIDATION_ERROR) {
0134: ret = true;
0135: break;
0136: }
0137: }
0138: }
0139: return ret;
0140: }
0141:
0142: public void visit(SourceTable sourceTable) {
0143: // validate extraction condition
0144: SQLCondition extCondition = sourceTable
0145: .getExtractionCondition();
0146: if (extCondition != null) {
0147: if (!extCondition.isValid()) {
0148: String desc = buildErrorMessageWithObjectIdentifier(
0149: sourceTable.getQualifiedName(),
0150: "ERROR_extraction_condition_invalid");
0151: ValidationInfo validationInfo = new ValidationInfoImpl(
0152: extCondition, desc,
0153: ValidationInfo.VALIDATION_ERROR);
0154: validationInfoList.add(validationInfo);
0155: } else {
0156: visit(extCondition);
0157: }
0158: }
0159:
0160: // FIXME: This condition when reported and user tries to
0161: // edit it by double clicking then extraction condition content is shown
0162: // validate data validation condition
0163: SQLCondition dataValidationCondition = sourceTable
0164: .getDataValidationCondition();
0165: if (dataValidationCondition != null) {
0166: if (!dataValidationCondition.isValid()) {
0167: String desc = buildErrorMessageWithObjectIdentifier(
0168: sourceTable.getQualifiedName(),
0169: "ERROR_datavalidation_condition_invalid");
0170: ValidationInfo validationInfo = new ValidationInfoImpl(
0171: dataValidationCondition, desc,
0172: ValidationInfo.VALIDATION_ERROR);
0173: validationInfoList.add(validationInfo);
0174: } else {
0175: visit(dataValidationCondition);
0176: }
0177: }
0178: }
0179:
0180: public void visit(SQLCaseOperator operator) {
0181: visitExpression(operator, true);
0182: }
0183:
0184: public void visit(SQLCondition condition) {
0185: // If there is no condition then do not validate
0186: if (!condition.isConditionDefined()) {
0187: return;
0188: }
0189:
0190: // If the resulting expression does not form a valid condition, then raise an
0191: // error.
0192: if (!condition.isValid()) {
0193: String nbBundle1 = mLoc
0194: .t("PRSR001: Condition is not valid.");
0195: String error = Localizer.parse(nbBundle1);
0196: ValidationInfo info = new ValidationInfoImpl(null, error,
0197: ValidationInfo.VALIDATION_ERROR);
0198: validationInfoList.add(info);
0199: }
0200:
0201: Collection allObjects = condition.getAllObjects();
0202: List expObjects = SQLObjectUtil
0203: .getAllExpressionObjects(allObjects);
0204: // Validate all other objects in objectMap
0205: validate(allObjects, expObjects);
0206: }
0207:
0208: public void visit(SQLDefinition definition) {
0209: Iterator it = definition.getTargetTables().iterator();
0210: boolean oracleTableGroupByUse = false;
0211: final int execStrategy = definition.getExecutionStrategyCode()
0212: .intValue();
0213: if ((execStrategy == SQLDefinition.EXECUTION_STRATEGY_STAGING)
0214: && definition.requiresPipelineProcess()) {
0215: String nbBundle2 = mLoc
0216: .t("PRSR001: Cannot execute in Staging mode, choose Best-fit or Pipeline.");
0217: String desc = Localizer.parse(nbBundle2);
0218: ValidationInfo validationInfo = new ValidationInfoImpl(
0219: definition, desc, ValidationInfo.VALIDATION_ERROR);
0220: validationInfoList.add(validationInfo);
0221: }
0222:
0223: List<PhysicalTable> srcTables = new ArrayList<PhysicalTable>();
0224: List<PhysicalTable> trgtTables = new ArrayList<PhysicalTable>();
0225: while (it.hasNext()) {
0226: TargetTable targetTable = (TargetTable) it.next();
0227: final String identifier = targetTable.getQualifiedName();
0228: final int stmtType = targetTable.getStatementType();
0229: String dbType = targetTable.getParent()
0230: .getConnectionDefinition().getDBType();
0231: final String jdbcUrl = targetTable.getParent()
0232: .getConnectionDefinition().getConnectionURL();
0233: boolean jdbcEway = isJdbcOrUnknownDB(dbType, jdbcUrl);
0234:
0235: boolean oracleTargetTableWithCondition = false;
0236: boolean oracleSourceTable = false;
0237:
0238: // Check for Unknown DB and UPDATE and MERGE statement usage.
0239: if (jdbcEway) {
0240: if ((stmtType == SQLConstants.INSERT_UPDATE_STATEMENT)
0241: && ((execStrategy == SQLDefinition.EXECUTION_STRATEGY_BEST_FIT) || (execStrategy == SQLDefinition.EXECUTION_STRATEGY_STAGING))) {
0242: String desc = buildErrorMessageWithObjectIdentifier(
0243: identifier,
0244: "WARN_merge_may_not_be_supported");
0245: ValidationInfo validationInfo = new ValidationInfoImpl(
0246: targetTable, desc,
0247: ValidationInfo.VALIDATION_WARNING);
0248: validationInfoList.add(validationInfo);
0249: }
0250: }
0251:
0252: targetTable.visit(this );
0253:
0254: trgtTables.add(PhysicalTable.getPhysicalTable(targetTable));
0255: try {
0256: srcTables.addAll(PhysicalTable
0257: .getPhysicalTableList(targetTable
0258: .getSourceTableList()));
0259: } catch (BaseException e) {
0260: String nbBundle3 = mLoc
0261: .t("PRSR001: Must have at least one column mapped to a literal, source column or operator.");
0262: String desc = Localizer.parse(nbBundle3);
0263: ValidationInfo validationInfo = new ValidationInfoImpl(
0264: targetTable, desc,
0265: ValidationInfo.VALIDATION_ERROR);
0266: validationInfoList.add(validationInfo);
0267: }
0268:
0269: if ((targetTable.getJoinCondition() != null)
0270: && (targetTable.getJoinCondition()
0271: .isConditionDefined())) {
0272: dbType = targetTable.getParent()
0273: .getConnectionDefinition().getDBType();
0274: dbType = (dbType != null) ? dbType.toUpperCase() : "";
0275: if (dbType.indexOf(DBConstants.ORACLE_STR) >= 0) {
0276: oracleTargetTableWithCondition = true;
0277: }
0278: }
0279:
0280: try {
0281: Iterator srcItr = targetTable.getSourceTableList()
0282: .iterator();
0283: SourceTable srcTbl = null;
0284: while (srcItr.hasNext()) {
0285: srcTbl = (SourceTable) srcItr.next();
0286: dbType = srcTbl.getParent()
0287: .getConnectionDefinition().getDBType();
0288: dbType = (dbType != null) ? dbType.toUpperCase()
0289: : "";
0290: if (dbType.indexOf(DBConstants.ORACLE_STR) >= 0) {
0291: oracleSourceTable = true;
0292: break;
0293: }
0294: }
0295: } catch (BaseException ex) {
0296: // ignore
0297: }
0298:
0299: if ((targetTable.getSQLGroupBy() != null)
0300: && (targetTable.getSQLGroupBy().getColumns().size() > 0)) {
0301: if (oracleTargetTableWithCondition || oracleSourceTable) {
0302: oracleTableGroupByUse = true;
0303: }
0304: }
0305: }
0306:
0307: for (it = trgtTables.iterator(); it.hasNext();) {
0308: PhysicalTable pt = (PhysicalTable) it.next();
0309: if (srcTables.contains(pt)) {
0310: String nbBundle4 = mLoc.t(
0311: "PRSR001: {0} used as both source and target.",
0312: pt.getName());
0313: String desc = Localizer.parse(nbBundle4);
0314: ValidationInfo validationInfo = new ValidationInfoImpl(
0315: definition, desc,
0316: ValidationInfo.VALIDATION_WARNING);
0317: validationInfoList.add(validationInfo);
0318: }
0319: }
0320:
0321: // Flag error when one of the source tables is Oracle, Group By clause
0322: // is used and Pipeline is required.
0323: // Since we use Oracle forward only ResultSet to avoid OutOfMemoryError,
0324: // as its scroll-able result set caches data at the client VM.
0325: // Pipeline GROUP BY processing needs scroll-able result set.
0326: if ((definition.requiresPipelineProcess() || (definition
0327: .getExecutionStrategyCode().intValue() == SQLDefinition.EXECUTION_STRATEGY_PIPELINE))
0328: && (oracleTableGroupByUse)) {
0329: String nbBundle5 = mLoc
0330: .t("PRSR001: Can not use Oracle table as Source with Group By clause in pipeline/validation mode.");
0331: String desc = Localizer.parse(nbBundle5);
0332: ValidationInfo validationInfo = new ValidationInfoImpl(
0333: definition, desc, ValidationInfo.VALIDATION_ERROR);
0334: validationInfoList.add(validationInfo);
0335: }
0336: }
0337:
0338: public void visit(SQLFilter filter) {
0339: visitExpression(filter, true);
0340:
0341: if (!filter.isValid()) {
0342: String nbBundle6 = mLoc.t("PRSR001: {0} {1}", filter
0343: .getDisplayName(), filter.toString());
0344: String descriptor = Localizer.parse(nbBundle6);
0345: String message = buildErrorMessageWithObjectIdentifier(
0346: descriptor, "ERROR_predicate_invalid");
0347: ValidationInfo expValidationInfo = new ValidationInfoImpl(
0348: filter, message, ValidationInfo.VALIDATION_ERROR);
0349: validationInfoList.add(expValidationInfo);
0350: }
0351:
0352: // Check if the next SQLFilter clause, if any, has a prefix.
0353: if (filter.getNextFilter() != null
0354: && StringUtil.isNullString(filter.getNextFilter()
0355: .getPrefix())) {
0356: String nbBundle7 = mLoc.t("PRSR001: {0} {1}", filter
0357: .getNextFilter().getDisplayName(), filter
0358: .toString());
0359: String descriptor = Localizer.parse(nbBundle7);
0360: String message = buildErrorMessageWithObjectIdentifier(
0361: descriptor, "ERROR_predicate_missing_prefix");
0362:
0363: ValidationInfo expValidationInfo = new ValidationInfoImpl(
0364: filter, message, ValidationInfo.VALIDATION_ERROR);
0365: validationInfoList.add(expValidationInfo);
0366: }
0367: }
0368:
0369: public void visit(SQLGenericOperator operator) {
0370: if (operator.hasVariableArgs()
0371: && operator.getInputObjectMap().size() == 0) {
0372: String desc = buildErrorMessageWithObjectIdentifier(
0373: operator.getDisplayName(), "ERROR_input_not_linked");
0374: ValidationInfo expValidationInfo = new ValidationInfoImpl(
0375: operator, desc, ValidationInfo.VALIDATION_ERROR);
0376: validationInfoList.add(expValidationInfo);
0377: } else {
0378: visitExpression(operator, true);
0379: }
0380: }
0381:
0382: public void visit(SQLJoinOperator operator) {
0383: doJoinConditionValidation(operator);
0384: visitExpression(operator, true);
0385: }
0386:
0387: public void visit(SQLJoinView joinView) {
0388: SQLJoinOperator rJoin = joinView.getRootJoin();
0389: if (rJoin != null) {
0390: rJoin.visit(this );
0391: }
0392: }
0393:
0394: public void visit(SQLPredicate operator) {
0395: visitExpression(operator, true);
0396: }
0397:
0398: public void visit(SQLWhen when) {
0399: }
0400:
0401: private boolean isJdbcOrUnknownDB(String dbType, String jdbcUrl) {
0402: boolean unknownDB = true;
0403: if (dbType != null) {
0404: dbType = dbType.toUpperCase();
0405: for (int i = 0; i < DBConstants.SUPPORTED_DB_TYPE_STRINGS.length; i++) {
0406: if ((dbType
0407: .indexOf(DBConstants.SUPPORTED_DB_TYPE_STRINGS[i])) > -1) {
0408: unknownDB = false;
0409: break;
0410: }
0411: }
0412:
0413: // Now check whether connecting to known DB thru JDBC eWay
0414: if ((unknownDB) && (jdbcUrl != null)) {
0415: jdbcUrl = jdbcUrl.toLowerCase();
0416: for (int i = 0; i < DBConstants.SUPPORTED_DB_URL_PREFIXES.length; i++) {
0417: if ((jdbcUrl
0418: .indexOf(DBConstants.SUPPORTED_DB_URL_PREFIXES[i])) > -1) {
0419: unknownDB = false;
0420: break;
0421: }
0422: }
0423: }
0424: }
0425: return unknownDB;
0426: }
0427:
0428: public void visit(TargetTable targetTable) {
0429: final String identifier = targetTable.getQualifiedName();
0430: final int stmtType = targetTable.getStatementType();
0431:
0432: Collection<String> uniqueIndexColumns = new HashSet<String>();
0433: List<Index> indexes = targetTable.getIndexes();
0434: Index index = null;
0435:
0436: if (indexes != null) {
0437: Iterator<Index> indexItr = indexes.iterator();
0438: while (indexItr.hasNext()) {
0439: index = indexItr.next();
0440: if (index.isUnique()) {
0441: uniqueIndexColumns.addAll(index.getColumnNames());
0442: }
0443: }
0444: }
0445:
0446: int nonNullColumns = findColumnErrors(targetTable, identifier,
0447: stmtType, uniqueIndexColumns);
0448: validateGroupBy(targetTable, stmtType);
0449: validateTargetTableCondition(targetTable, identifier, stmtType);
0450:
0451: // check for column maps for non-delete statements
0452: if (stmtType != SQLConstants.DELETE_STATEMENT) {
0453: if (nonNullColumns == 0) {
0454: String desc = buildErrorMessageWithObjectIdentifier(
0455: identifier, "ERROR_targettable_not_mapped");
0456: ValidationInfo validationInfo = new ValidationInfoImpl(
0457: targetTable, desc,
0458: ValidationInfo.VALIDATION_ERROR);
0459: validationInfoList.add(validationInfo);
0460: }
0461: }
0462:
0463: // do join view validation
0464: SQLJoinView joinView = targetTable.getJoinView();
0465: if (joinView != null) {
0466: joinView.visit(this );
0467: }
0468: validateSourceTableCondition(targetTable);
0469:
0470: // validate truncate before load option
0471: if (targetTable.isTruncateBeforeLoad()) {
0472: if (stmtType == SQLConstants.INSERT_STATEMENT) {
0473: ValidationInfo validationInfo = new ValidationInfoImpl(
0474: targetTable,
0475: "Target table "
0476: + targetTable.getFullyQualifiedName()
0477: + " will be truncated before loading, use this option if you want to refresh the data",
0478: ValidationInfo.VALIDATION_WARNING);
0479: validationInfoList.add(validationInfo);
0480: } else {
0481: ValidationInfo validationInfo = new ValidationInfoImpl(
0482: targetTable,
0483: "Can't truncate target table "
0484: + targetTable.getFullyQualifiedName()
0485: + " for the selected statement type -> "
0486: + targetTable.getStrStatementType(),
0487: ValidationInfo.VALIDATION_ERROR);
0488: validationInfoList.add(validationInfo);
0489: }
0490: }
0491: }
0492:
0493: private String buildErrorMessageWithObjectIdentifier(
0494: String identifier, String errorKey) {
0495: return buildErrorMessageWithObjectIdentifiers(identifier,
0496: errorKey, EMPTY_OBJECT_ARRAY);
0497: }
0498:
0499: private String buildErrorMessageWithObjectIdentifiers(
0500: String identifier, String errorKey, Object[] errorParams) {
0501: String nbBundle12 = mLoc.t("PRSR001: {0}-{1}", errorKey,
0502: errorParams);
0503: String errorMessage = Localizer.parse(nbBundle12);
0504: String nbBundle8 = mLoc.t("PRSR001: {0}-{1}", identifier,
0505: errorMessage);
0506: return Localizer.parse(nbBundle8);
0507: }
0508:
0509: private void doJoinConditionValidation(SQLJoinOperator operator) {
0510: // If join condition is not defined then warn user about a Cartesian join being
0511: // created
0512: String identifier = "";
0513: SQLJoinView joinView = (SQLJoinView) operator.getParentObject();
0514: if (joinView != null) {
0515: identifier = joinView.getQualifiedName();
0516: }
0517:
0518: SQLCondition jCondition = operator.getJoinCondition();
0519: if (!jCondition.isConditionDefined()) {
0520: String desc = buildErrorMessageWithObjectIdentifier(
0521: identifier, "WARNING_join_condition_missing");
0522: ValidationInfoImpl vInfo = new ValidationInfoImpl(
0523: jCondition, desc, ValidationInfo.VALIDATION_WARNING);
0524: validationInfoList.add(vInfo);
0525: } else {
0526: if (!jCondition.isValid()) {
0527: String desc = buildErrorMessageWithObjectIdentifier(
0528: identifier, "ERROR_join_condition_invalid");
0529: ValidationInfoImpl vInfo = new ValidationInfoImpl(
0530: jCondition, desc,
0531: ValidationInfo.VALIDATION_ERROR);
0532: validationInfoList.add(vInfo);
0533: }
0534: }
0535:
0536: // TODO Check whether this is too restrictive - shouldn't user be able to create
0537: // arbitrary join conditions?
0538: try {
0539: // check for at least one = condition between columns of the two joined table.
0540: // also check whether such columns are primary
0541: SQLPredicate rootPredicate = jCondition.getRootPredicate();
0542: if (rootPredicate != null) {
0543: SQLFilter filter = SQLPredicateVisitor
0544: .visit(rootPredicate);
0545: boolean foundOneEqualOp = false;
0546: boolean foundOnePKMatch = false;
0547: String pkMsg = null;
0548:
0549: while (filter != null) {
0550: SQLObject leftObj = filter
0551: .getSQLObject(SQLFilter.LEFT);
0552: SQLObject rightObj = filter
0553: .getSQLObject(SQLFilter.RIGHT);
0554: String op = filter.getOperator();
0555:
0556: if (isValidJoinOperator(op)) {
0557: if (leftObj.getObjectType() == SQLConstants.COLUMN_REF) {
0558: leftObj = ((ColumnRef) leftObj).getColumn();
0559: }
0560:
0561: if (rightObj.getObjectType() == SQLConstants.COLUMN_REF) {
0562: rightObj = ((ColumnRef) rightObj)
0563: .getColumn();
0564: }
0565:
0566: SourceColumn sLeft = getFirstSourceColumnReferencedIn(leftObj);
0567: SourceColumn sRight = getFirstSourceColumnReferencedIn(rightObj);
0568:
0569: if (sLeft != null && sRight != null) {
0570: foundOneEqualOp = true;
0571: String errorKey = null;
0572: Object[] objectNames = EMPTY_OBJECT_ARRAY;
0573:
0574: if (sLeft.isPrimaryKey()
0575: && sRight.isPrimaryKey()) {
0576: foundOnePKMatch = true;
0577: } else if (sLeft.isPrimaryKey()
0578: && !sRight.isPrimaryKey()) {
0579: errorKey = "ERROR_join_column_not_pk";
0580: objectNames = new Object[] { sRight
0581: .toString() };
0582: } else if (!sLeft.isPrimaryKey()
0583: && sRight.isPrimaryKey()) {
0584: errorKey = "ERROR_join_column_not_pk";
0585: objectNames = new Object[] { sLeft
0586: .toString() };
0587: } else {
0588: errorKey = "ERROR_join_columns_not_pks";
0589: objectNames = new Object[] {
0590: sLeft.toString(),
0591: sRight.toString() };
0592: }
0593:
0594: if (errorKey != null) {
0595: pkMsg = buildErrorMessageWithObjectIdentifiers(
0596: identifier, errorKey,
0597: objectNames);
0598: }
0599: }
0600: }
0601:
0602: filter = filter.getNextFilter();
0603: }
0604:
0605: // If an equal relation is not found between columns of both tables
0606: // then this is an error in join condition
0607: if (!foundOneEqualOp) {
0608: String desc = buildErrorMessageWithObjectIdentifier(
0609: identifier, "ERROR_join_missing_equal_op");
0610: ValidationInfoImpl vInfo = new ValidationInfoImpl(
0611: jCondition, desc,
0612: ValidationInfo.VALIDATION_WARNING);
0613: validationInfoList.add(vInfo);
0614: } else if (!foundOnePKMatch) {
0615: String desc = pkMsg;
0616: ValidationInfoImpl vInfo = new ValidationInfoImpl(
0617: jCondition, desc,
0618: ValidationInfo.VALIDATION_WARNING);
0619: validationInfoList.add(vInfo);
0620: }
0621:
0622: }
0623: } catch (BaseException ex) {
0624: mLogger
0625: .errorNoloc(
0626: mLoc
0627: .t(
0628: "PRSR130: Error while validating SQLJoinOperator-{0}",
0629: operator.getDisplayName()),
0630: ex);
0631: }
0632: }
0633:
0634: private int findColumnErrors(TargetTable targetTable,
0635: final String identifier, int stmtType,
0636: Collection uniqueIndexColumns) {
0637: int nonNullColumns = 0;
0638: List<ValidationInfo> columnErrors = new ArrayList<ValidationInfo>(
0639: 5);
0640: Iterator iter = targetTable.getColumns().values().iterator();
0641: while (iter.hasNext()) {
0642: TargetColumn col = (TargetColumn) iter.next();
0643: String argName = col.getName();
0644: SQLObject obj = col.getValue();
0645:
0646: switch (stmtType) {
0647: case SQLConstants.DELETE_STATEMENT:
0648: // Columns must not be linked for delete statement.
0649: if (obj != null) {
0650: String desc = buildErrorMessageWithObjectIdentifiers(
0651: identifier,
0652: "ERROR_input_link_prohibited_delete",
0653: new Object[] { argName });
0654: columnErrors.add(new ValidationInfoImpl(
0655: targetTable, desc,
0656: ValidationInfo.VALIDATION_ERROR));
0657: }
0658: break;
0659: case SQLConstants.UPDATE_STATEMENT:
0660: // PK and not null columns don't need to be linked for update
0661: // statement.
0662: break;
0663: default:
0664: if ((col.isPrimaryKey() || !col.isNullable())
0665: && obj == null) {
0666: String desc = buildErrorMessageWithObjectIdentifiers(
0667: identifier,
0668: "WARNING_input_link_needed_pknotnull",
0669: new Object[] { argName });
0670: columnErrors.add(new ValidationInfoImpl(
0671: targetTable, desc,
0672: ValidationInfo.VALIDATION_WARNING));
0673: } else if (uniqueIndexColumns.contains(col.getName())
0674: && (obj == null)) {
0675: String desc = buildErrorMessageWithObjectIdentifiers(
0676: identifier,
0677: "WARNING_input_link_needed_unique",
0678: new Object[] { argName });
0679: columnErrors.add(new ValidationInfoImpl(
0680: targetTable, desc,
0681: ValidationInfo.VALIDATION_WARNING));
0682: }
0683:
0684: break;
0685: }
0686:
0687: // Validate the column input against the declared datatype for this column.
0688: if (obj != null) {
0689: ValidationInfo expValidationInfo = validateInputDataType(
0690: targetTable, argName, col.getDisplayName(), obj);
0691: if (expValidationInfo != null) {
0692: columnErrors.add(expValidationInfo);
0693: }
0694: }
0695:
0696: if (columnErrors.size() != 0) {
0697: validationInfoList.addAll(columnErrors);
0698: columnErrors.clear();
0699: }
0700:
0701: if (obj != null) {
0702: nonNullColumns++;
0703: if (obj instanceof SQLConnectableObject) {
0704: SQLConnectableObject inObj = (SQLConnectableObject) obj;
0705: inObj.visit(this );
0706: }
0707: }
0708: }
0709: return nonNullColumns;
0710: }
0711:
0712: /**
0713: * Gets the first column, if any, associated with a source table that is referenced as
0714: * an input within the given SQLObject.
0715: *
0716: * @param sqlObj SQLObject whose inputs are to be traversed
0717: * @return first SourceColumn input encountered, or null if none are encountered
0718: */
0719: private SourceColumn getFirstSourceColumnReferencedIn(
0720: SQLObject sqlObj) {
0721: if (sqlObj instanceof SourceColumn) {
0722: Object parent = ((SourceColumn) sqlObj).getParent();
0723: return (parent instanceof RuntimeInput) ? null
0724: : (SourceColumn) sqlObj;
0725: } else if (sqlObj instanceof ColumnRef) {
0726: return getFirstSourceColumnReferencedIn(((ColumnRef) sqlObj)
0727: .getColumn());
0728: } else if (sqlObj instanceof SQLGenericOperator) {
0729: SQLGenericOperator op = (SQLGenericOperator) sqlObj;
0730: for (Iterator iter = op.getInputObjectMap().values()
0731: .iterator(); iter.hasNext();) {
0732: SQLInputObject inputWrapper = (SQLInputObject) iter
0733: .next();
0734: SQLObject wrappedObj = inputWrapper.getSQLObject();
0735: return getFirstSourceColumnReferencedIn(wrappedObj);
0736: }
0737: }
0738:
0739: return null;
0740: }
0741:
0742: private boolean isObjectMappedToExpression(SQLObject obj,
0743: List expObjects) {
0744: boolean result = false;
0745: Iterator it = expObjects.iterator();
0746:
0747: while (it.hasNext()) {
0748: SQLConnectableObject expObj = (SQLConnectableObject) it
0749: .next();
0750: result = SQLObjectUtil.isObjectMappedToExpression(obj,
0751: expObj);
0752: if (result) {
0753: return result;
0754: }
0755: }
0756:
0757: return result;
0758: }
0759:
0760: /**
0761: * Indicates whether the given String represents a valid operator for a join
0762: * predicate.
0763: *
0764: * @param op String representing operator to be tested
0765: * @return true if join operator is valid, false otherwise
0766: */
0767: private boolean isValidJoinOperator(String op) {
0768: // We currently only support equijoins.
0769: return op != null && op.trim().equalsIgnoreCase("=");
0770: }
0771:
0772: private void validate(Collection allObjects, List expObjects) {
0773: Iterator it = allObjects.iterator();
0774: SQLObject sqlObject = null;
0775: SQLObject column = null;
0776: SQLConnectableObject exprObj = null;
0777: String desc = null;
0778: while (it.hasNext()) {
0779: sqlObject = (SQLObject) it.next();
0780:
0781: if (sqlObject.getObjectType() == SQLConstants.COLUMN_REF) {
0782: column = ((ColumnRef) sqlObject).getColumn();
0783:
0784: if ((column.getObjectType() == SQLConstants.SOURCE_COLUMN)
0785: && !(((SQLDBColumn) column).isVisible())) {
0786: String nbBundle9 = mLoc
0787: .t(
0788: "PRSR001: Column {0} used in a condition is not visible.",
0789: sqlObject.getDisplayName());
0790: desc = Localizer.parse(nbBundle9);
0791: ValidationInfo validationInfo = new ValidationInfoImpl(
0792: sqlObject.getParentObject(), desc,
0793: ValidationInfo.VALIDATION_ERROR);
0794: validationInfoList.add(validationInfo);
0795: }
0796: }
0797:
0798: if (sqlObject instanceof SQLConnectableObject) {
0799: exprObj = (SQLConnectableObject) sqlObject;
0800: SQLValidationVisitor vVisitor = new SQLValidationVisitor();
0801: vVisitor.visitExpression(exprObj, false);
0802: List<ValidationInfo> vInfos = vVisitor
0803: .getValidationInfoList();
0804: if (vInfos.size() > 0) {
0805: validationInfoList.addAll(vInfos);
0806: }
0807: } else if (!isObjectMappedToExpression(sqlObject,
0808: expObjects)) {
0809: String nbBundle10 = mLoc.t(
0810: "PRSR001: {0} is not mapped to an expression.",
0811: new Object[] { sqlObject.getDisplayName() });
0812: desc = Localizer.parse(nbBundle10);
0813: ValidationInfo validationInfo = new ValidationInfoImpl(
0814: sqlObject, desc,
0815: ValidationInfo.VALIDATION_ERROR);
0816:
0817: validationInfoList.add(validationInfo);
0818: }
0819: }
0820: }
0821:
0822: /**
0823: * @param sqlObj
0824: * @return
0825: */
0826: private ValidationInfoImpl validateDateFormatInput(
0827: SQLConnectableObject op, String argName, SQLObject sqlObj) {
0828: ValidationInfoImpl errorInfo = null;
0829: int validationType = ValidationInfo.VALIDATION_ERROR;
0830:
0831: if (sqlObj instanceof SQLLiteral) {
0832: SQLLiteral literal = (SQLLiteral) sqlObj;
0833: String value = literal.getValue();
0834: AxionDateTimeFormatParser formatParser = new AxionDateTimeFormatParser();
0835:
0836: try {
0837: formatParser.parseDateTimeFormatToJava(value);
0838: } catch (AxionException e) {
0839: String message = buildErrorMessageWithObjectIdentifiers(
0840: op.getDisplayName(),
0841: "ERROR_dateformat_invalid",
0842: new Object[] { value });
0843: errorInfo = new ValidationInfoImpl(op, message,
0844: validationType);
0845: }
0846: }
0847:
0848: return errorInfo;
0849: }
0850:
0851: private void validateExpressionObjectChildren(
0852: SQLConnectableObject exp) {
0853: // go through children of this expression object
0854: List childList = exp.getChildSQLObjects();
0855: Iterator it = childList.iterator();
0856:
0857: while (it.hasNext()) {
0858: SQLObject sqlObject = (SQLObject) it.next();
0859: SQLConnectableObject exprObj = null;
0860: if (sqlObject instanceof SQLConnectableObject) {
0861: exprObj = (SQLConnectableObject) sqlObject;
0862: visitExpression(exprObj, true);
0863: }
0864: }
0865: }
0866:
0867: private void validateGroupBy(TargetTable targetTable, int stmtType) {
0868: if (targetTable.getSQLGroupBy() != null
0869: && !targetTable.getSQLGroupBy().getColumns().isEmpty()) {
0870: switch (stmtType) {
0871: case SQLConstants.INSERT_STATEMENT:
0872: break;
0873: default:
0874: ValidationInfo validationInfo = new ValidationInfoImpl(
0875: targetTable,
0876: "Defined GroupBy/Having clause will not be used :: "
0877: + targetTable.getSQLGroupBy()
0878: .toString(),
0879: ValidationInfo.VALIDATION_WARNING);
0880: validationInfoList.add(validationInfo);
0881: }
0882: }
0883:
0884: // validate group by columns
0885: SQLGroupBy groupBy = targetTable.getSQLGroupBy();
0886: List groupByNodes = groupBy != null ? groupBy.getColumns()
0887: : null;
0888: SQLGroupByValidationVisitor groupByVisitor = new SQLGroupByValidationVisitor(
0889: targetTable, groupByNodes);
0890: groupByVisitor.visit(targetTable.getMappedColumns());
0891: validationInfoList.addAll(groupByVisitor
0892: .getValidationInfoList());
0893:
0894: if (groupBy != null) {
0895: // validate having condition
0896: SQLCondition having = groupBy.getHavingCondition();
0897: if (having != null) {
0898: if (!having.isValid()) {
0899: String desc = buildErrorMessageWithObjectIdentifier(
0900: having.getDisplayName(),
0901: "ERROR_condition_invalid");
0902: ValidationInfo validationInfo = new ValidationInfoImpl(
0903: having, desc,
0904: ValidationInfo.VALIDATION_ERROR);
0905: validationInfoList.add(validationInfo);
0906: } else {
0907: visit(having);
0908: }
0909:
0910: groupByVisitor.reset();
0911: groupByVisitor.visit(Collections.singletonList(having
0912: .getRootPredicate()));
0913: validationInfoList.addAll(groupByVisitor
0914: .getValidationInfoList());
0915: }
0916:
0917: // validate: selected target column as group by node/expr should be mapped
0918: // validate: Selected target column should not be mapped to aggregate function
0919: for (Iterator iter = groupByNodes.iterator(); iter
0920: .hasNext();) {
0921: SQLObject sqlObj = (SQLObject) iter.next();
0922: if (sqlObj instanceof TargetColumn) {
0923: TargetColumn col = (TargetColumn) sqlObj;
0924: if (col.getValue() == null) {
0925: ValidationInfoImpl validationInfo = new ValidationInfoImpl(
0926: targetTable,
0927: "Selected group by target coulmn not mapped: "
0928: + col,
0929: ValidationInfo.VALIDATION_ERROR);
0930: validationInfoList.add(validationInfo);
0931: } else if (SQLObjectUtil
0932: .isAggregateFunctionMapped(col.getValue())) {
0933: ValidationInfoImpl validationInfo = new ValidationInfoImpl(
0934: targetTable,
0935: "Group By clause can't contain agrregate function: "
0936: + col + "->" + col.getValue(),
0937: ValidationInfo.VALIDATION_ERROR);
0938: validationInfoList.add(validationInfo);
0939: }
0940: }
0941: }
0942: }
0943: }
0944:
0945: /**
0946: * Validates whether the given SQLInputObject is an appropriate input for the argument
0947: * as referenced by the given String for the given SQLConnectableObject.
0948: *
0949: * @param op SQLConnectableObject whose input as referenced by <code>argName</code>
0950: * is to be validated
0951: * @param argName name of input argument to be validated
0952: * @param displayName display name of input argument
0953: * @param obj SQLObject representing input to be validated
0954: * @return ExpressionValidationInfo instance if an error or warning has been
0955: * generated; null otherwise.
0956: */
0957: private ValidationInfoImpl validateInputDataType(
0958: SQLConnectableObject op, String argName,
0959: String displayName, SQLObject sqlObj) {
0960: final String identifier = op.getDisplayName();
0961:
0962: ValidationInfoImpl expValidationInfo = null;
0963: String desc = null;
0964: int validationType = ValidationInfo.VALIDATION_ERROR;
0965:
0966: int compatibility = op.isInputCompatible(argName, sqlObj);
0967: switch (compatibility) {
0968: case SQLConstants.TYPE_CHECK_INCOMPATIBLE:
0969: desc = buildErrorMessageWithObjectIdentifiers(identifier,
0970: "ERROR_datatype_incompatible",
0971: new Object[] { displayName });
0972: break;
0973: case SQLConstants.TYPE_CHECK_DOWNCAST_WARNING:
0974: case SQLConstants.TYPE_CHECK_UNKNOWN:
0975: String datatype = SQLUtils.getStdSqlType(sqlObj
0976: .getJdbcType());
0977: if (datatype == null) {
0978: datatype = "unknown";
0979: }
0980: desc = buildErrorMessageWithObjectIdentifiers(identifier,
0981: "WARNING_datatype_downcast_unknown", new Object[] {
0982: datatype, displayName });
0983: validationType = ValidationInfo.VALIDATION_WARNING;
0984: break;
0985: case SQLConstants.TYPE_CHECK_COMPATIBLE:
0986: case SQLConstants.TYPE_CHECK_SAME:
0987: default:
0988: desc = null;
0989: }
0990:
0991: if (desc != null) {
0992: expValidationInfo = new ValidationInfoImpl(op, desc,
0993: validationType);
0994: } else if (sqlObj instanceof SQLCastOperator
0995: && op instanceof TargetTable) {
0996: SQLCastOperator castOp = (SQLCastOperator) sqlObj;
0997: TargetTable target = (TargetTable) op;
0998:
0999: DBColumn col = target.getColumn(argName);
1000: int colPrecision = (col != null) ? col.getPrecision() : 0;
1001: int castPrecision = castOp.getPrecision();
1002:
1003: int colType = (col != null) ? col.getJdbcType()
1004: : SQLConstants.JDBCSQL_TYPE_UNDEFINED;
1005: switch (colType) {
1006: case Types.VARCHAR:
1007: case Types.CHAR:
1008: case Types.NUMERIC:
1009: if (castPrecision > colPrecision) {
1010: String castError = buildErrorMessageWithObjectIdentifiers(
1011: identifier,
1012: "ERROR_datatype_castexceedsprecision",
1013: new Object[] { new Integer(castPrecision),
1014: displayName,
1015: new Integer(colPrecision) });
1016: expValidationInfo = new ValidationInfoImpl(castOp,
1017: castError, validationType);
1018: }
1019: break;
1020: default:
1021: break;
1022: }
1023: }
1024:
1025: return expValidationInfo;
1026: }
1027:
1028: private void validateSourceTableCondition(TargetTable targetTable) {
1029: try {
1030: // do source table extraction condition validation
1031: Iterator sIt = targetTable.getSourceTableList().iterator();
1032: while (sIt.hasNext()) {
1033: SourceTable sTable = (SourceTable) sIt.next();
1034: sTable.visit(this );
1035: }
1036: } catch (BaseException ex) {
1037: mLogger
1038: .errorNoloc(
1039: mLoc
1040: .t(
1041: "PRSR132: Could not find source tables for this target table{0}in {1}",
1042: targetTable.getName(),
1043: SQLValidationVisitor.class
1044: .getName()), ex);
1045: }
1046: }
1047:
1048: private void validateTargetConditionForTargetColumnUsage(
1049: SQLCondition condition) {
1050: try {
1051: SQLPredicate predicate = condition.getRootPredicate();
1052:
1053: if (predicate != null) {
1054: if (!predicate.hasTargetColumn()) {
1055: String nbBundle11 = mLoc
1056: .t("PRSR001: Target/Join condition should have at least one target table column.");
1057: String desc = Localizer.parse(nbBundle11);
1058: ValidationInfoImpl validationInfo = new ValidationInfoImpl(
1059: condition, desc,
1060: ValidationInfo.VALIDATION_ERROR);
1061: validationInfoList.add(validationInfo);
1062: }
1063: }
1064: } catch (Exception ex) {
1065: mLogger.errorNoloc(mLoc.t(
1066: "PRSR133: Validating target table condition in{0}",
1067: SQLValidationVisitor.class.getName()), ex);
1068: }
1069: }
1070:
1071: private void validateTargetTableCondition(TargetTable targetTable,
1072: final String identifier, int stmtType) {
1073: switch (stmtType) {
1074: case SQLConstants.INSERT_UPDATE_STATEMENT:
1075: case SQLConstants.UPDATE_STATEMENT:
1076: SQLCondition cond = targetTable.getJoinCondition();
1077: SQLPredicate rootP = null;
1078: if (cond != null) {
1079: rootP = cond.getRootPredicate();
1080: }
1081:
1082: if (rootP == null) {
1083: String desc = null;
1084:
1085: if (stmtType == SQLConstants.INSERT_UPDATE_STATEMENT) {
1086: desc = buildErrorMessageWithObjectIdentifier(
1087: identifier, "ERROR_mergecondition_missing");
1088: } else {
1089: try {
1090: // If this is a static update (no associated source tables), a
1091: // condition is not required.
1092: if (targetTable.getSourceTableList().size() == 0) {
1093: break;
1094: }
1095: desc = buildErrorMessageWithObjectIdentifier(
1096: identifier,
1097: "ERROR_updatecondition_missing");
1098: } catch (BaseException ignore) {
1099: break;
1100: }
1101: }
1102:
1103: if (desc != null) {
1104: ValidationInfoImpl validationInfo = new ValidationInfoImpl(
1105: targetTable, desc,
1106: ValidationInfo.VALIDATION_ERROR);
1107: validationInfoList.add(validationInfo);
1108: }
1109: }
1110: break;
1111: default:
1112: break;
1113: }
1114:
1115: // do target table condition validation
1116: SQLCondition joinCondition = targetTable.getJoinCondition();
1117: if (joinCondition != null) {
1118: if (!joinCondition.isValid()) {
1119: String desc = buildErrorMessageWithObjectIdentifier(
1120: identifier, "ERROR_condition_invalid");
1121: ValidationInfoImpl validationInfo = new ValidationInfoImpl(
1122: joinCondition, desc,
1123: ValidationInfo.VALIDATION_ERROR);
1124: validationInfoList.add(validationInfo);
1125: } else {
1126: validateTargetConditionForTargetColumnUsage(joinCondition);
1127: }
1128: }
1129:
1130: // do target table filter condition validation
1131: SQLCondition filterCondition = targetTable.getFilterCondition();
1132: if (filterCondition != null) {
1133: if (!filterCondition.isValid()) {
1134: String desc = buildErrorMessageWithObjectIdentifier(
1135: identifier, "ERROR_condition_invalid");
1136: ValidationInfoImpl validationInfo = new ValidationInfoImpl(
1137: filterCondition, desc,
1138: ValidationInfo.VALIDATION_ERROR);
1139: validationInfoList.add(validationInfo);
1140: }
1141: }
1142: }
1143:
1144: private void visitExpression(SQLConnectableObject operator,
1145: boolean recurse) {
1146: final String identifier = operator.getDisplayName();
1147:
1148: Iterator iter = operator.getInputObjectMap().entrySet()
1149: .iterator();
1150: while (iter.hasNext()) {
1151: Map.Entry entry = (Map.Entry) iter.next();
1152: String argName = (String) entry.getKey();
1153: SQLInputObject obj = (SQLInputObject) entry.getValue();
1154: ValidationInfo expValidationInfo = null;
1155:
1156: if (obj == null) {
1157: String desc = buildErrorMessageWithObjectIdentifiers(
1158: identifier, "ERROR_object_not_linked",
1159: new Object[] { obj.getDisplayName() });
1160: expValidationInfo = new ValidationInfoImpl(operator,
1161: desc, ValidationInfo.VALIDATION_ERROR);
1162: }
1163:
1164: SQLObject sqlObj = obj.getSQLObject();
1165: if (sqlObj == null) {
1166: String desc = buildErrorMessageWithObjectIdentifiers(
1167: identifier, "ERROR_object_not_linked",
1168: new Object[] { obj.getDisplayName() });
1169: expValidationInfo = new ValidationInfoImpl(operator,
1170: desc, ValidationInfo.VALIDATION_ERROR);
1171: } else {
1172: expValidationInfo = (sqlObj instanceof SQLCustomOperatorImpl) ? null
1173: : validateInputDataType(operator, argName, obj
1174: .getDisplayName(), sqlObj);
1175: }
1176:
1177: if (expValidationInfo != null) {
1178: validationInfoList.add(expValidationInfo);
1179: }
1180:
1181: if (sqlObj != null
1182: && operator.isInputStatic(argName)
1183: && DATE_FORMAT_OPS.contains(identifier
1184: .toLowerCase())) {
1185: if ("format".equals(argName.toLowerCase())
1186: || "right".equals(argName.toLowerCase())) {
1187: expValidationInfo = validateDateFormatInput(
1188: operator, argName, sqlObj);
1189: if (expValidationInfo != null) {
1190: validationInfoList.add(expValidationInfo);
1191: }
1192: }
1193: }
1194:
1195: if (recurse && sqlObj != null
1196: && sqlObj instanceof SQLConnectableObject) {
1197: SQLConnectableObject inObj = (SQLConnectableObject) sqlObj;
1198: inObj.visit(this );
1199: }
1200: }
1201:
1202: // validate children
1203: validateExpressionObjectChildren(operator);
1204: }
1205: }
|