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}