001package gudusoft.gsqlparser.analyzer.v2.callgraph;
002
003import gudusoft.gsqlparser.ESqlStatementType;
004import gudusoft.gsqlparser.ETableEffectType;
005import gudusoft.gsqlparser.analyzer.v2.ETableAccessKind;
006import gudusoft.gsqlparser.ir.bound.BoundObjectRef;
007
008/**
009 * Extracts table access kind (READ/WRITE/READ_WRITE) from a {@link BoundObjectRef},
010 * using {@code ETableEffectType} from the AST's {@code TTable.getEffectType()} as
011 * the primary signal, with {@code ESqlStatementType} as fallback.
012 * <p>
013 * Correctly distinguishes:
014 * <ul>
015 *   <li>MERGE target (READ_WRITE) vs source (READ)</li>
016 *   <li>INSERT...SELECT target (WRITE) vs source (READ)</li>
017 *   <li>DELETE target (WRITE) vs subquery tables (READ)</li>
018 * </ul>
019 */
020public final class TableAccessExtractor {
021
022    private TableAccessExtractor() {}
023
024    /**
025     * Infers the access kind for a table reference.
026     *
027     * @param objRef the bound object reference with properties "tableEffectType" and/or "statementType"
028     * @return the access kind
029     */
030    public static ETableAccessKind inferAccessKind(BoundObjectRef objRef) {
031        // Primary: use ETableEffectType from TTable.getEffectType()
032        Object effectType = objRef.getProperty("tableEffectType");
033        if (effectType instanceof ETableEffectType) {
034            return mapEffectType((ETableEffectType) effectType);
035        }
036
037        // Fallback: use ESqlStatementType (backward compat)
038        Object stmtType = objRef.getProperty("statementType");
039        if (stmtType instanceof ESqlStatementType) {
040            return mapStatementType((ESqlStatementType) stmtType);
041        }
042
043        return ETableAccessKind.READ;
044    }
045
046    private static ETableAccessKind mapEffectType(ETableEffectType effectType) {
047        switch (effectType) {
048            case tetSelect:
049            case tetSelectInto:
050                return ETableAccessKind.READ;
051            case tetInsert:
052            case tetDelete:
053            case tetDrop:
054            case tetTruncate:
055            case tetCopy:
056                return ETableAccessKind.WRITE;
057            case tetUpdate:
058            case tetMerge:
059            case tetUpsert:
060                return ETableAccessKind.READ_WRITE;
061            case tetCreate:
062            case tetCreateAs:
063            case tetAlter:
064            case tetAlterTableRename:
065            case tetAlterTableSwapWith:
066                return ETableAccessKind.WRITE;
067            case tetTriggerOn:
068            case tetTriggerInsert:
069            case tetTriggerDelete:
070            case tetTriggerUpdate:
071            case tetTriggerInsteadOf:
072                return ETableAccessKind.READ_WRITE;
073            case tetOutput:
074                return ETableAccessKind.WRITE;
075            case tetConstraintReference:
076            case tetCreateIndexRef:
077            case tetCreateMaterializedViewLog:
078                return ETableAccessKind.READ;
079            case tetPivot:
080            case tetImplicitLateralDerivedTable:
081                return ETableAccessKind.READ;
082            default:
083                return ETableAccessKind.READ;
084        }
085    }
086
087    private static ETableAccessKind mapStatementType(ESqlStatementType stmtType) {
088        switch (stmtType) {
089            case sstselect:
090                return ETableAccessKind.READ;
091            case sstinsert:
092                return ETableAccessKind.WRITE;
093            case sstupdate:
094                return ETableAccessKind.READ_WRITE;
095            case sstdelete:
096                return ETableAccessKind.WRITE;
097            case sstmerge:
098                return ETableAccessKind.READ_WRITE;
099            default:
100                return ETableAccessKind.READ;
101        }
102    }
103
104    /**
105     * Normalizes a table name for comparison: uppercase, strip quotes, strip schema prefix.
106     */
107    public static String normalizeTableName(String tableName) {
108        if (tableName == null) {
109            return "";
110        }
111        String normalized = tableName.trim();
112        // Strip double quotes
113        if (normalized.startsWith("\"") && normalized.endsWith("\"")) {
114            normalized = normalized.substring(1, normalized.length() - 1);
115        }
116        // Strip schema prefix (take last part after dot)
117        int dot = normalized.lastIndexOf('.');
118        if (dot >= 0) {
119            normalized = normalized.substring(dot + 1);
120        }
121        return normalized.toUpperCase();
122    }
123}