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 tetTriggerInsert: 068 case tetTriggerDelete: 069 case tetTriggerUpdate: 070 case tetTriggerOn: 071 case tetTriggerInsteadOf: 072 // Trigger base table: the trigger observes events on this table 073 // but does not itself write to it. Classify as READ. 074 return ETableAccessKind.READ; 075 case tetOutput: 076 return ETableAccessKind.WRITE; 077 case tetConstraintReference: 078 case tetCreateIndexRef: 079 case tetCreateMaterializedViewLog: 080 return ETableAccessKind.READ; 081 case tetPivot: 082 case tetImplicitLateralDerivedTable: 083 return ETableAccessKind.READ; 084 default: 085 return ETableAccessKind.READ; 086 } 087 } 088 089 private static ETableAccessKind mapStatementType(ESqlStatementType stmtType) { 090 switch (stmtType) { 091 case sstselect: 092 return ETableAccessKind.READ; 093 case sstinsert: 094 return ETableAccessKind.WRITE; 095 case sstupdate: 096 return ETableAccessKind.READ_WRITE; 097 case sstdelete: 098 return ETableAccessKind.WRITE; 099 case sstmerge: 100 return ETableAccessKind.READ_WRITE; 101 default: 102 return ETableAccessKind.READ; 103 } 104 } 105 106 /** 107 * Normalizes a table name for comparison: uppercase, strip quotes, strip schema prefix. 108 */ 109 public static String normalizeTableName(String tableName) { 110 if (tableName == null) { 111 return ""; 112 } 113 String normalized = tableName.trim(); 114 // Strip double quotes 115 if (normalized.startsWith("\"") && normalized.endsWith("\"")) { 116 normalized = normalized.substring(1, normalized.length() - 1); 117 } 118 // Strip schema prefix (take last part after dot) 119 int dot = normalized.lastIndexOf('.'); 120 if (dot >= 0) { 121 normalized = normalized.substring(dot + 1); 122 } 123 return normalized.toUpperCase(); 124 } 125}