001package gudusoft.gsqlparser.resolver2.scope;
002
003import gudusoft.gsqlparser.nodes.TParseTreeNode;
004import gudusoft.gsqlparser.nodes.TJoinExpr;
005import gudusoft.gsqlparser.EJoinType;
006import gudusoft.gsqlparser.resolver2.ScopeType;
007import gudusoft.gsqlparser.resolver2.namespace.INamespace;
008
009/**
010 * Scope for JOIN operations.
011 * Handles nullable semantics for different JOIN types.
012 *
013 * JOIN semantics:
014 * - INNER JOIN: both sides non-nullable
015 * - LEFT JOIN: left non-nullable, right nullable
016 * - RIGHT JOIN: left nullable, right non-nullable
017 * - FULL JOIN: both sides nullable
018 * - CROSS JOIN: both sides non-nullable
019 *
020 * Example:
021 * FROM users u
022 * LEFT JOIN orders o ON u.id = o.user_id
023 *      ^^^^^           ^^^^^^
024 *      leftNamespace   rightNamespace (nullable)
025 */
026public class JoinScope extends ListBasedScope {
027
028    /** The JOIN AST node */
029    private final TJoinExpr joinExpr;
030
031    /** Left side namespace */
032    private INamespace leftNamespace;
033
034    /** Right side namespace */
035    private INamespace rightNamespace;
036
037    /** Whether left side is nullable */
038    private boolean leftNullable;
039
040    /** Whether right side is nullable */
041    private boolean rightNullable;
042
043    public JoinScope(IScope parent, TJoinExpr joinExpr) {
044        super(parent, joinExpr, ScopeType.JOIN);
045        this.joinExpr = joinExpr;
046    }
047
048    /**
049     * Set the left side of the JOIN
050     */
051    public void setLeft(INamespace namespace, String alias, boolean nullable) {
052        this.leftNamespace = namespace;
053        this.leftNullable = nullable;
054        addChild(namespace, alias, nullable);
055    }
056
057    /**
058     * Set the right side of the JOIN
059     */
060    public void setRight(INamespace namespace, String alias, boolean nullable) {
061        this.rightNamespace = namespace;
062        this.rightNullable = nullable;
063        addChild(namespace, alias, nullable);
064    }
065
066    /**
067     * Determine nullable semantics based on JOIN type
068     */
069    public static NullableSemantics getNullableSemantics(TJoinExpr joinExpr) {
070        if (joinExpr == null || joinExpr.getJointype() == null) {
071            return new NullableSemantics(false, false);
072        }
073
074        EJoinType joinType = joinExpr.getJointype();
075
076        switch (joinType) {
077            case inner:
078            case join:  // same as inner join
079            case natural_inner:
080                return new NullableSemantics(false, false);
081
082            case left:
083            case leftouter:
084            case natural_left:
085            case natural_leftouter:
086            case leftsemi:
087                return new NullableSemantics(false, true);
088
089            case right:
090            case rightouter:
091            case natural_right:
092            case natural_rightouter:
093                return new NullableSemantics(true, false);
094
095            case full:
096            case fullouter:
097            case natural_full:
098            case natural_fullouter:
099                return new NullableSemantics(true, true);
100
101            case cross:
102            case straight:
103            case crossapply:
104                return new NullableSemantics(false, false);
105
106            case outerapply:
107                return new NullableSemantics(false, true);
108
109            case leftanti:
110            case anti:
111            case semi:
112                return new NullableSemantics(false, true);
113
114            case asof:
115                return new NullableSemantics(false, true);
116
117            default:
118                // Conservative default: assume both nullable
119                return new NullableSemantics(true, true);
120        }
121    }
122
123    public TJoinExpr getJoinExpr() {
124        return joinExpr;
125    }
126
127    public INamespace getLeftNamespace() {
128        return leftNamespace;
129    }
130
131    public INamespace getRightNamespace() {
132        return rightNamespace;
133    }
134
135    public boolean isLeftNullable() {
136        return leftNullable;
137    }
138
139    public boolean isRightNullable() {
140        return rightNullable;
141    }
142
143    @Override
144    public String toString() {
145        return String.format("JoinScope(left=%s[%s], right=%s[%s])",
146            leftNamespace != null ? leftNamespace.getDisplayName() : "null",
147            leftNullable ? "nullable" : "not-null",
148            rightNamespace != null ? rightNamespace.getDisplayName() : "null",
149            rightNullable ? "nullable" : "not-null"
150        );
151    }
152
153    /**
154     * Nullable semantics for a JOIN operation
155     */
156    public static class NullableSemantics {
157        public final boolean leftNullable;
158        public final boolean rightNullable;
159
160        public NullableSemantics(boolean leftNullable, boolean rightNullable) {
161            this.leftNullable = leftNullable;
162            this.rightNullable = rightNullable;
163        }
164    }
165}