001package gudusoft.gsqlparser.ir.semantic.validation.oracle; 002 003import gudusoft.gsqlparser.ir.semantic.Diagnostic; 004import gudusoft.gsqlparser.ir.semantic.RecoveryMetadata; 005import gudusoft.gsqlparser.nodes.TExpression; 006 007import java.util.ArrayDeque; 008import java.util.ArrayList; 009import java.util.Collections; 010import java.util.Deque; 011import java.util.IdentityHashMap; 012import java.util.List; 013import java.util.Map; 014 015/** Immutable result of validating one Oracle query block's legacy (+) facts. */ 016public final class OracleLegacyOuterJoinValidationResult { 017 018 private static final OracleLegacyOuterJoinValidationResult EMPTY = 019 new OracleLegacyOuterJoinValidationResult( 020 Collections.<Diagnostic>emptyList(), 021 Collections.<RecoveryMetadata.Entry>emptyList(), 022 new IdentityHashMap<TExpression, PredicateJoinIntent>()); 023 024 private final List<Diagnostic> diagnostics; 025 private final List<RecoveryMetadata.Entry> recoveryEntries; 026 private final IdentityHashMap<TExpression, PredicateJoinIntent> intents; 027 028 OracleLegacyOuterJoinValidationResult( 029 List<Diagnostic> diagnostics, 030 List<RecoveryMetadata.Entry> recoveryEntries, 031 Map<TExpression, PredicateJoinIntent> intents) { 032 this.diagnostics = Collections.unmodifiableList( 033 new ArrayList<Diagnostic>(diagnostics)); 034 this.recoveryEntries = Collections.unmodifiableList( 035 new ArrayList<RecoveryMetadata.Entry>(recoveryEntries)); 036 this.intents = new IdentityHashMap<TExpression, PredicateJoinIntent>(); 037 this.intents.putAll(intents); 038 } 039 040 static OracleLegacyOuterJoinValidationResult empty() { 041 return EMPTY; 042 } 043 044 public List<Diagnostic> getDiagnostics() { 045 return diagnostics; 046 } 047 048 public List<RecoveryMetadata.Entry> getRecoveryEntries() { 049 return recoveryEntries; 050 } 051 052 public boolean hasErrors() { 053 return !diagnostics.isEmpty(); 054 } 055 056 /** 057 * Return the intent for a promotion conjunct. Parenthesis wrappers are 058 * handled without re-running validation; all decisions come from facts 059 * indexed during the Sema pass. 060 */ 061 public PredicateJoinIntent intentForConjunct(TExpression conjunct) { 062 if (conjunct == null) return null; 063 PredicateJoinIntent exact = intents.get(conjunct); 064 if (exact != null) return exact; 065 066 Deque<TExpression> work = new ArrayDeque<TExpression>(); 067 work.push(conjunct); 068 PredicateJoinIntent found = null; 069 while (!work.isEmpty()) { 070 TExpression current = work.pop(); 071 PredicateJoinIntent candidate = intents.get(current); 072 if (candidate != null) { 073 if (candidate.getValidity() == PredicateJoinIntent.Validity.INVALID) { 074 return candidate; 075 } 076 if (found != null && found != candidate) { 077 return PredicateJoinIntent.unknown( 078 candidate.getReferencedRelationInstanceIds()); 079 } 080 found = candidate; 081 } 082 pushExpressionChildren(current, work); 083 } 084 return found; 085 } 086 087 private static void pushExpressionChildren(TExpression expression, 088 Deque<TExpression> work) { 089 if (expression.getSubQuery() != null) return; 090 if (expression.getExprList() != null) { 091 for (int i = expression.getExprList().size() - 1; i >= 0; i--) { 092 TExpression child = expression.getExprList().getExpression(i); 093 if (child != null) work.push(child); 094 } 095 } 096 if (expression.getBetweenOperand() != null) { 097 work.push(expression.getBetweenOperand()); 098 } 099 if (expression.getRightOperand() != null) { 100 work.push(expression.getRightOperand()); 101 } 102 if (expression.getLeftOperand() != null) { 103 work.push(expression.getLeftOperand()); 104 } 105 } 106}