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}