001package gudusoft.gsqlparser.resolver2.matcher; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType; 005import gudusoft.gsqlparser.sqlenv.IdentifierService; 006 007/** 008 * Vendor-specific name matcher using IdentifierService. 009 * 010 * <p>This implementation provides database vendor-specific identifier normalization 011 * and comparison by delegating to {@link IdentifierService}.</p> 012 * 013 * <p>Key features:</p> 014 * <ul> 015 * <li>Vendor-specific case folding (Oracle: UPPER, PostgreSQL: LOWER, etc.)</li> 016 * <li>Proper handling of quoted vs unquoted identifiers</li> 017 * <li>Support for different object types (tables, columns, schemas)</li> 018 * <li>High-performance static caching via IdentifierService</li> 019 * </ul> 020 * 021 * <p>Usage example:</p> 022 * <pre> 023 * // Create matcher for Oracle 024 * VendorNameMatcher matcher = new VendorNameMatcher(EDbVendor.dbvoracle); 025 * 026 * // Normalize column name 027 * String normalized = matcher.normalize("\"Column1\""); // Returns "Column1" (quoted preserved) 028 * String normalized2 = matcher.normalize("column1"); // Returns "COLUMN1" (unquoted folded) 029 * 030 * // Compare names 031 * boolean match = matcher.matches("COL1", "col1"); // Returns true (both normalize to COL1) 032 * </pre> 033 * 034 * @since 3.1.0.9 035 * @see IdentifierService 036 */ 037public class VendorNameMatcher implements INameMatcher { 038 039 private final EDbVendor vendor; 040 private final ESQLDataObjectType defaultObjectType; 041 042 /** 043 * Create a vendor name matcher with default object type (column). 044 * 045 * @param vendor the database vendor 046 */ 047 public VendorNameMatcher(EDbVendor vendor) { 048 this(vendor, ESQLDataObjectType.dotColumn); 049 } 050 051 /** 052 * Create a vendor name matcher with specified default object type. 053 * 054 * @param vendor the database vendor 055 * @param defaultObjectType the default object type for normalization/comparison 056 */ 057 public VendorNameMatcher(EDbVendor vendor, ESQLDataObjectType defaultObjectType) { 058 this.vendor = vendor != null ? vendor : EDbVendor.dbvoracle; 059 this.defaultObjectType = defaultObjectType != null ? defaultObjectType : ESQLDataObjectType.dotColumn; 060 } 061 062 @Override 063 public boolean matches(String name1, String name2) { 064 return matches(name1, name2, defaultObjectType); 065 } 066 067 /** 068 * Check if two names match according to vendor rules for a specific object type. 069 * 070 * @param name1 first name 071 * @param name2 second name 072 * @param objectType the object type (table, column, etc.) 073 * @return true if names match according to vendor rules 074 */ 075 public boolean matches(String name1, String name2, ESQLDataObjectType objectType) { 076 if (name1 == null || name2 == null) { 077 return name1 == name2; 078 } 079 return IdentifierService.areEqualStatic(vendor, objectType, name1, name2); 080 } 081 082 @Override 083 public boolean matchesPattern(String name, String pattern) { 084 if (name == null || pattern == null) { 085 return false; 086 } 087 // Normalize both and do prefix matching 088 String normalizedName = normalize(name); 089 String normalizedPattern = normalize(pattern); 090 return normalizedName.startsWith(normalizedPattern); 091 } 092 093 @Override 094 public String normalize(String name) { 095 return normalize(name, defaultObjectType); 096 } 097 098 /** 099 * Normalize a name according to vendor rules for a specific object type. 100 * 101 * @param name the name to normalize 102 * @param objectType the object type (table, column, etc.) 103 * @return normalized name 104 */ 105 public String normalize(String name, ESQLDataObjectType objectType) { 106 if (name == null || name.isEmpty()) { 107 return name; 108 } 109 return IdentifierService.normalizeStatic(vendor, objectType, name); 110 } 111 112 /** 113 * Normalize a qualified name (e.g., schema.table.column). 114 * 115 * <p>This method properly normalizes each part of a multi-part identifier 116 * according to vendor rules.</p> 117 * 118 * @param qualifiedName the qualified name 119 * @param objectType the final object type 120 * @return normalized qualified name 121 */ 122 public String normalizeQualifiedName(String qualifiedName, ESQLDataObjectType objectType) { 123 if (qualifiedName == null || qualifiedName.isEmpty()) { 124 return qualifiedName; 125 } 126 127 // Parse the qualified name into parts 128 // Handle quoted identifiers that may contain dots 129 StringBuilder result = new StringBuilder(); 130 StringBuilder currentPart = new StringBuilder(); 131 boolean inQuote = false; 132 char quoteChar = 0; 133 134 for (int i = 0; i < qualifiedName.length(); i++) { 135 char c = qualifiedName.charAt(i); 136 137 // Track quote state 138 if (!inQuote && (c == '"' || c == '`' || c == '[')) { 139 inQuote = true; 140 quoteChar = c; 141 currentPart.append(c); 142 } else if (inQuote && ((quoteChar == '"' && c == '"') || 143 (quoteChar == '`' && c == '`') || 144 (quoteChar == '[' && c == ']'))) { 145 inQuote = false; 146 currentPart.append(c); 147 } else if (!inQuote && c == '.') { 148 // End of a part - normalize it 149 if (currentPart.length() > 0) { 150 if (result.length() > 0) { 151 result.append('.'); 152 } 153 // Use dotTable for intermediate parts, objectType for final part 154 result.append(normalize(currentPart.toString(), ESQLDataObjectType.dotTable)); 155 currentPart.setLength(0); 156 } 157 } else { 158 currentPart.append(c); 159 } 160 } 161 162 // Handle the last part with the actual object type 163 if (currentPart.length() > 0) { 164 if (result.length() > 0) { 165 result.append('.'); 166 } 167 result.append(normalize(currentPart.toString(), objectType)); 168 } 169 170 return result.toString(); 171 } 172 173 @Override 174 public boolean isCaseSensitive() { 175 // This is a simplified check - actual sensitivity depends on vendor and object type 176 // For most databases, unquoted identifiers are case-insensitive 177 return false; 178 } 179 180 /** 181 * Get the database vendor. 182 * 183 * @return the vendor 184 */ 185 public EDbVendor getVendor() { 186 return vendor; 187 } 188 189 /** 190 * Get the default object type. 191 * 192 * @return the default object type 193 */ 194 public ESQLDataObjectType getDefaultObjectType() { 195 return defaultObjectType; 196 } 197 198 /** 199 * Create a new matcher with a different default object type. 200 * 201 * @param objectType the new default object type 202 * @return new VendorNameMatcher instance 203 */ 204 public VendorNameMatcher withObjectType(ESQLDataObjectType objectType) { 205 return new VendorNameMatcher(vendor, objectType); 206 } 207 208 @Override 209 public String toString() { 210 return String.format("VendorNameMatcher{vendor=%s, defaultType=%s}", vendor, defaultObjectType); 211 } 212}