001package gudusoft.gsqlparser.sqlenv;
002
003
004import gudusoft.gsqlparser.TBaseType;
005import gudusoft.gsqlparser.util.SQLUtil;
006
007import java.util.*;
008
009/**
010 * SQL schema, contains a list of schema objects.
011 */
012public class TSQLSchema extends TSQLObject {
013
014
015    public String getQualifiedName(){
016        return catalog.name+"."+name;
017    }
018
019    private TSQLCatalog catalog;
020
021    public TSQLCatalog getCatalog() {
022        return catalog;
023    }
024
025    // ===== Shadow Introduction: New type-specific indexes (Phase 2) =====
026    // These LinkedHashMaps provide O(1) lookup per object type, replacing the mixed schemaObjectMap
027    private final LinkedHashMap<String, TSQLTable> tables = new LinkedHashMap<>();
028    private final LinkedHashMap<String, TSQLFunction> functions = new LinkedHashMap<>();
029    private final LinkedHashMap<String, TSQLProcedure> procedures = new LinkedHashMap<>();
030    private final LinkedHashMap<String, TSQLTrigger> triggers = new LinkedHashMap<>();
031    private final LinkedHashMap<String, TSQLOraclePackage> oraclePackages = new LinkedHashMap<>();
032    private final LinkedHashMap<String, TSQLSynonyms> synonyms = new LinkedHashMap<>();
033
034    // ===== Phase 3: Bucketed indexes for SQL Server COLLATION_BASED =====
035    // These provide ~25x performance improvement for SQL Server (10,000 objects: 20μs → 800ns)
036    private BucketedIndex bucketedTables;
037    private BucketedIndex bucketedFunctions;
038    private BucketedIndex bucketedProcedures;
039    private BucketedIndex bucketedTriggers;
040    private BucketedIndex bucketedOraclePackages;
041    private BucketedIndex bucketedSynonyms;
042
043    // IdentifierService for consistent key generation (lazy initialization)
044    private IdentifierService identifierService;
045
046    // CollatorProvider for SQL Server (lazy initialization)
047    private CollatorProvider collatorProvider;
048
049    // ===== Phase 6: Schema seal mechanism for thread-safety hardening =====
050    /**
051     * Seal state flag (volatile for thread visibility).
052     */
053    private volatile boolean sealed = false;
054
055    /**
056     * Seal this schema to prevent further modifications (Phase 6).
057     *
058     * <p>See {@link TSQLEnv#seal()} for detailed documentation.
059     */
060    public void seal() {
061        this.sealed = true;
062    }
063
064    /**
065     * Check if this schema is sealed (Phase 6).
066     *
067     * @return true if sealed, false otherwise
068     */
069    public boolean isSealed() {
070        return sealed;
071    }
072
073    /**
074     * Check if schema is not sealed, throw exception if sealed and enforcement is enabled.
075     *
076     * @throws IllegalStateException if sealed and TBaseType.ENFORCE_CATALOG_SEAL is true
077     */
078    private void checkNotSealed() {
079        if (sealed && TBaseType.ENFORCE_CATALOG_SEAL) {
080            throw new IllegalStateException("Cannot modify sealed schema. Call seal() marks schema as read-only.");
081        }
082    }
083
084    /**
085     * Create a new instance of the schema, added to the catalog.
086     *
087     * @param sqlCatalog catalog contains this schema
088     * @param schemaName name of the schema
089     */
090    public TSQLSchema(TSQLCatalog sqlCatalog,String schemaName){
091        super(sqlCatalog.getSqlEnv(), schemaName,ESQLDataObjectType.dotSchema);
092        this.catalog = sqlCatalog;
093        this.catalog.addSchema(this);
094    }
095
096    /**
097     * Get or create IdentifierService (lazy initialization)
098     */
099    private IdentifierService getIdentifierService() {
100        if (identifierService == null) {
101            IdentifierProfile profile = IdentifierProfile.forVendor(
102                this.sqlEnv.getDBVendor(),
103                IdentifierProfile.VendorFlags.defaults()
104            );
105            identifierService = new IdentifierService(profile, null);
106        }
107        return identifierService;
108    }
109
110    /**
111     * Get or create CollatorProvider (lazy initialization, SQL Server only)
112     */
113    private CollatorProvider getCollatorProvider() {
114        if (collatorProvider == null) {
115            collatorProvider = new CollatorProvider();
116        }
117        return collatorProvider;
118    }
119
120    /**
121     * Check if SQL Server and bucketed index is enabled
122     */
123    private boolean shouldUseBucketedIndex() {
124        return TBaseType.USE_BUCKETED_INDEX &&
125               (this.sqlEnv.getDBVendor() == gudusoft.gsqlparser.EDbVendor.dbvmssql ||
126                this.sqlEnv.getDBVendor() == gudusoft.gsqlparser.EDbVendor.dbvazuresql);
127    }
128
129    /**
130     * Slice S3: cache of whether the hierarchical type-specific map is globally
131     * legacy-compatible for a given {@code (vendor, objectType)} pair.
132     *
133     * <p>Key format: {@code "<EDbVendor.name()>|<ESQLDataObjectType.name()>"}.
134     * Both the IdentifierProfile (built from {@link IdentifierProfile#forVendor})
135     * and the legacy {@link TSQLEnv} per-vendor case-sensitivity maps are static
136     * relative to the (vendor, type) tuple, so the answer is stable for the
137     * JVM lifetime and safe to cache process-wide.
138     */
139    private static final java.util.concurrent.ConcurrentHashMap<String, Boolean>
140        HIERARCHICAL_COMPAT_CACHE = new java.util.concurrent.ConcurrentHashMap<>();
141
142    /**
143     * Slice S3: detect whether the hierarchical type-specific map can be safely
144     * consulted for the entire {@code (vendor, objectType)} pair.
145     *
146     * <p>The hierarchical map keys come from {@link IdentifierService#keyForMap}.
147     * The legacy {@code schemaObjectMap} keys come from
148     * {@link gudusoft.gsqlparser.util.SQLUtil#getIdentifierNormalName}.
149     *
150     * <p>For several vendor/type combinations the two key functions diverge:
151     * <ul>
152     *   <li>SQL Server / Azure SQL with {@code COLLATION_BASED} rules
153     *       ({@code IdentifierRules.forSQLServer}: {@code CaseFold.NONE}) when
154     *       {@link TSQLEnv#tableCollationCaseSensitive} reports {@code false}
155     *       (legacy adds a {@code toUpperCase} pass).</li>
156     *   <li>BigQuery tables ({@code IdentifierRules.forBigQueryTable}:
157     *       {@code CaseFold.NONE / SENSITIVE}) where
158     *       {@link TSQLEnv#tableCollationCaseSensitive} is {@code false}.</li>
159     *   <li>Hive/Teradata routines and any vendor whose {@link IdentifierRules}
160     *       differ from the legacy {@link TSQLEnv} per-type case-sensitivity
161     *       map.</li>
162     * </ul>
163     *
164     * <p>When the two diverge, distinct case-different writes land under
165     * different keys in hierarchical (both retained) but collapse to the same
166     * key in legacy (last write wins). A subsequent lookup that happens to
167     * use a probe input where the two functions <em>locally</em> agree (e.g.
168     * an already-uppercase {@code "FOO"} on BigQuery) would still hit the
169     * stale earlier hierarchical entry while legacy holds the authoritative
170     * later write. So input-local parity is insufficient — the gate must be
171     * vendor/type-global.
172     *
173     * <p>We probe once with a case-mixed ASCII input and cache the result.
174     * If the two key functions agree on the probe they agree on every input
175     * (they only differ in fold direction; a probe that survives both folds
176     * is sufficient). When they diverge, hierarchical is skipped permanently
177     * for that {@code (vendor, type)} and the lookup falls through to legacy.
178     *
179     * <p>This is intentionally conservative; once the strict-mode switch from
180     * plan §3.3 lands (slice S7) the gate can flip to "always trust
181     * hierarchical" and accept the (more correct) behavior change.
182     */
183    private boolean isHierarchicalCompatibleForType(ESQLDataObjectType objectType) {
184        try {
185            gudusoft.gsqlparser.EDbVendor vendor = this.sqlEnv.getDBVendor();
186            String cacheKey = vendor.name() + "|" + objectType.name();
187            Boolean cached = HIERARCHICAL_COMPAT_CACHE.get(cacheKey);
188            if (cached != null) {
189                return cached;
190            }
191            // Probe both unquoted and quoted representative inputs because
192            // IdentifierRules has independent {@code unquotedFold/Compare} and
193            // {@code quotedFold/Compare} axes; a vendor/type can match for one
194            // representation and diverge for the other (e.g. Snowflake where
195            // unquoted folds to UPPER on both sides but quoted preserves case
196            // in keyForMap while legacy still appends toUpperCase via
197            // tableCollationCaseSensitive=false).
198            String unquotedProbe = "ProbeXyz";
199            String quotedProbe = quoteIdentifierForProbe(vendor, unquotedProbe);
200            IdentifierService svc = getIdentifierService();
201            boolean compat = probeMatches(svc, vendor, unquotedProbe, objectType)
202                && probeMatches(svc, vendor, quotedProbe, objectType);
203            HIERARCHICAL_COMPAT_CACHE.put(cacheKey, compat);
204            return compat;
205        } catch (Throwable ignore) {
206            return false;
207        }
208    }
209
210    private boolean probeMatches(IdentifierService svc,
211                                 gudusoft.gsqlparser.EDbVendor vendor,
212                                 String probe,
213                                 ESQLDataObjectType objectType) {
214        String hierarchicalKey = svc.keyForMap(probe, objectType);
215        String legacyKey = SQLUtil.getIdentifierNormalName(vendor, probe, objectType);
216        return hierarchicalKey != null && hierarchicalKey.equals(legacyKey);
217    }
218
219    /**
220     * Wrap {@code name} in the vendor's quoted-identifier delimiter so the
221     * probe exercises {@code IdentifierRules.quotedFold/quotedCompare} and the
222     * legacy {@link gudusoft.gsqlparser.util.SQLUtil#normalizeIdentifier}
223     * branch that strips quotes before folding. Mirrors the dispatch in
224     * {@link TSQLEnv#isDelimitedIdentifier(gudusoft.gsqlparser.EDbVendor, String)}.
225     */
226    private static String quoteIdentifierForProbe(gudusoft.gsqlparser.EDbVendor vendor, String name) {
227        switch (vendor) {
228            case dbvmssql:
229            case dbvazuresql:
230                return "[" + name + "]";
231            case dbvmysql:
232            case dbvbigquery:
233            case dbvcouchbase:
234            case dbvhive:
235            case dbvimpala:
236                return "`" + name + "`";
237            case dbvdax:
238                return "'" + name + "'";
239            default:
240                return "\"" + name + "\"";
241        }
242    }
243
244    /**
245     * Get or create bucketed index for a specific object type
246     */
247    private BucketedIndex getBucketedIndex(ESQLDataObjectType objectType) {
248        if (!shouldUseBucketedIndex()) {
249            return null;
250        }
251
252        String collation = getIdentifierService().getProfile().getFlags().defaultCollation;
253        CollatorProvider provider = getCollatorProvider();
254
255        switch (objectType) {
256            case dotTable:
257                if (bucketedTables == null) {
258                    bucketedTables = new BucketedIndex(provider, collation, objectType);
259                }
260                return bucketedTables;
261
262            case dotFunction:
263                if (bucketedFunctions == null) {
264                    bucketedFunctions = new BucketedIndex(provider, collation, objectType);
265                }
266                return bucketedFunctions;
267
268            case dotProcedure:
269                if (bucketedProcedures == null) {
270                    bucketedProcedures = new BucketedIndex(provider, collation, objectType);
271                }
272                return bucketedProcedures;
273
274            case dotTrigger:
275                if (bucketedTriggers == null) {
276                    bucketedTriggers = new BucketedIndex(provider, collation, objectType);
277                }
278                return bucketedTriggers;
279
280            case dotOraclePackage:
281                if (bucketedOraclePackages == null) {
282                    bucketedOraclePackages = new BucketedIndex(provider, collation, objectType);
283                }
284                return bucketedOraclePackages;
285
286            case dotSynonyms:
287                if (bucketedSynonyms == null) {
288                    bucketedSynonyms = new BucketedIndex(provider, collation, objectType);
289                }
290                return bucketedSynonyms;
291
292            default:
293                return null;
294        }
295    }
296
297    public List<TSQLSchemaObject> getSchemaObjectList() {
298        List<TSQLSchemaObject> schemaObjectList = new LinkedList<>();
299        synchronized (schemaObjectMap) {
300            for(String schemaObjectKey: schemaObjectMap.keySet()){
301                TSQLSchemaObject schemaObject = schemaObjectMap.get(schemaObjectKey);
302                if(schemaObject instanceof TSQLProcedure){
303                    if(((TSQLProcedure)schemaObject).isOraclePackageProcedure()){
304                        continue;
305                    }
306                }
307                schemaObjectList.add(schemaObject);
308            }
309        }
310        return schemaObjectList;
311    }
312
313    public List<TSQLSchemaObject> getPackageObjectList(TSQLOraclePackage oraclePackage) {
314        List<TSQLSchemaObject> schemaObjectList = new LinkedList<>();
315        synchronized (schemaObjectMap) {
316            for (String schemaObjectKey : schemaObjectMap.keySet()) {
317                TSQLSchemaObject schemaObject = schemaObjectMap.get(schemaObjectKey);
318                if (schemaObject instanceof TSQLProcedure) {
319                    if (!((TSQLProcedure) schemaObject).isOraclePackageProcedure()) {
320                        continue;
321                    }
322                    TSQLProcedure procedure = (TSQLProcedure) schemaObject;
323                    if (procedure.getOraclePackage() == oraclePackage) {
324                        schemaObjectList.add(schemaObject);
325                    }
326                }
327            }
328        }
329        return schemaObjectList;
330    }
331
332    private Map<String, TSQLSchemaObject> schemaObjectMap = Collections.synchronizedMap(new LinkedHashMap<String, TSQLSchemaObject>( ));
333
334//    public boolean addTable(TSQLTable sqlTable){
335//        return addDataObject(sqlTable);
336//    }
337//
338//    public boolean addProcedure(TSQLProcedure sqlProcedure){
339//        return addDataObject(sqlProcedure);
340//    }
341//
342//    public boolean addFunction(TSQLFunction sqlFunction){
343//        return addDataObject(sqlFunction);
344//    }
345//
346//    public boolean addTrigger(TSQLTrigger sqlTrigger){
347//        return addDataObject(sqlTrigger);
348//    }
349
350    /**
351     * add database object
352     *
353     * <p><strong>Dual-write strategy:</strong> Updates both legacy schemaObjectMap
354     * and new type-specific maps for backward compatibility and performance optimization.
355     *
356     * @param schemaObject schema object to add
357     */
358    protected void addSchemaObject(TSQLSchemaObject schemaObject){
359        checkNotSealed();  // Phase 6: Prevent modification after seal
360        // ===== Legacy path: Update schemaObjectMap (backward compatibility) =====
361        String normalName = SQLUtil.getIdentifierNormalName(this.sqlEnv.getDBVendor(), schemaObject.name, schemaObject.getDataObjectType());
362        if (schemaObject.getDataObjectType() == ESQLDataObjectType.dotFunction)
363        {
364            normalName = normalName+"$function";
365        }
366        else if (schemaObject.getDataObjectType() == ESQLDataObjectType.dotProcedure)
367        {
368            normalName = normalName+"$procedure";
369        }
370        else if (schemaObject.getDataObjectType() == ESQLDataObjectType.dotTrigger)
371        {
372            normalName = normalName+"$trigger";
373        }
374        else if (schemaObject.getDataObjectType() == ESQLDataObjectType.dotOraclePackage)
375        {
376            normalName = normalName+"$package";
377        }
378        schemaObjectMap.put(normalName, schemaObject);
379        this.catalog.getSqlEnv().putSchemaObject(schemaObject.getQualifiedName(),schemaObject);
380
381        // ===== New path: Update type-specific maps (Phase 2 optimization) =====
382        if (TBaseType.USE_HIERARCHICAL_INDEX) {
383            try {
384                String key = getIdentifierService().keyForMap(schemaObject.name, schemaObject.getDataObjectType());
385
386                switch (schemaObject.getDataObjectType()) {
387                    case dotTable:
388                        tables.put(key, (TSQLTable) schemaObject);
389                        break;
390                    case dotFunction:
391                        functions.put(key, (TSQLFunction) schemaObject);
392                        break;
393                    case dotProcedure:
394                        procedures.put(key, (TSQLProcedure) schemaObject);
395                        break;
396                    case dotTrigger:
397                        triggers.put(key, (TSQLTrigger) schemaObject);
398                        break;
399                    case dotOraclePackage:
400                        oraclePackages.put(key, (TSQLOraclePackage) schemaObject);
401                        break;
402                    case dotSynonyms:
403                        synonyms.put(key, (TSQLSynonyms) schemaObject);
404                        break;
405                }
406            } catch (Throwable ignore) {
407                // Silently fail to maintain backward compatibility
408            }
409        }
410
411        // ===== Phase 3: Update bucketed index for SQL Server =====
412        if (shouldUseBucketedIndex()) {
413            try {
414                BucketedIndex bucketedIndex = getBucketedIndex(schemaObject.getDataObjectType());
415                if (bucketedIndex != null) {
416                    bucketedIndex.put(schemaObject.name, schemaObject);
417                }
418            } catch (Throwable ignore) {
419                // Silently fail to maintain backward compatibility
420            }
421        }
422    }
423
424    TSQLSchemaObject findSchemaObject(ESQLDataObjectType dataObjectType, String schemaObjectName) {
425        // Phase 3.5: Use bucketed index for SQL Server (25x faster for 10,000+ objects)
426        if (shouldUseBucketedIndex()) {
427            try {
428                BucketedIndex bucketedIndex = getBucketedIndex(dataObjectType);
429                if (bucketedIndex != null) {
430                    TSQLSchemaObject result = bucketedIndex.get(schemaObjectName);
431                    if (result != null) {
432                        if (TBaseType.LOG_INDEX_HIT_RATE) {
433                            System.out.println("[BucketedIndex] Hit: " + dataObjectType + " - " + schemaObjectName);
434                        }
435                        return result;
436                    }
437                }
438            } catch (Throwable ignore) {
439                // Fall through to legacy path
440            }
441        }
442
443        // Phase 2: Use hierarchical index (type-specific maps) when flag is enabled.
444        //
445        // Slice S3: Only consult hierarchical when its IdentifierService.keyForMap
446        // key function is globally compatible with legacy
447        // SQLUtil.getIdentifierNormalName for this {@code (vendor, type)} pair.
448        // See {@link #isHierarchicalCompatibleForType} for the rationale; in
449        // particular, an input-local parity check is insufficient because a
450        // case-different earlier write may already have populated hierarchical
451        // under a key that legacy collapsed (e.g. BigQuery: writes "FOO" then
452        // "foo"; legacy retains only the last; hierarchical retains both;
453        // querying "FOO" would still return the stale earlier hierarchical
454        // entry). Falling through to legacy preserves write/read symmetry.
455        if (TBaseType.USE_HIERARCHICAL_INDEX
456                && isHierarchicalCompatibleForType(dataObjectType)) {
457            try {
458                String key = getIdentifierService().keyForMap(schemaObjectName, dataObjectType);
459                TSQLSchemaObject result = null;
460
461                switch (dataObjectType) {
462                    case dotTable:
463                        result = tables.get(key);
464                        break;
465                    case dotFunction:
466                        result = functions.get(key);
467                        break;
468                    case dotProcedure:
469                        result = procedures.get(key);
470                        break;
471                    case dotTrigger:
472                        result = triggers.get(key);
473                        break;
474                    case dotOraclePackage:
475                        result = oraclePackages.get(key);
476                        break;
477                    case dotSynonyms:
478                        result = synonyms.get(key);
479                        break;
480                }
481
482                if (result != null) {
483                    // Log hit rate if enabled
484                    if (TBaseType.LOG_INDEX_HIT_RATE) {
485                        System.out.println("[HierarchicalIndex] Hit: " + dataObjectType + " - " + schemaObjectName);
486                    }
487                    return result;
488                }
489            } catch (Throwable ignore) {
490                // Fall through to legacy path
491            }
492        }
493
494        // Legacy path: use suffix hacks and local map (backward compatibility)
495        String normalName = SQLUtil.getIdentifierNormalName(this.sqlEnv.getDBVendor(), schemaObjectName, dataObjectType);
496        if (dataObjectType == ESQLDataObjectType.dotFunction)
497        {
498            normalName = normalName+"$function";
499        }
500        else if (dataObjectType == ESQLDataObjectType.dotProcedure)
501        {
502            normalName = normalName+"$procedure";
503        }
504        else if (dataObjectType == ESQLDataObjectType.dotTrigger)
505        {
506            normalName = normalName+"$trigger";
507        }
508        else if (dataObjectType == ESQLDataObjectType.dotOraclePackage)
509        {
510            normalName = normalName+"$package";
511        }
512        return schemaObjectMap.get(normalName);
513    }
514
515    /**
516     * create a oracle package belong to this schema
517     *
518     * @param oraclePackageName oracle package name
519     * @return an instance of the oracle package
520     */
521    public TSQLOraclePackage createOraclePackage(String oraclePackageName){
522        return (TSQLOraclePackage)createSchemaObject(oraclePackageName,ESQLDataObjectType.dotOraclePackage);
523    }
524
525    /**
526     * create a procedure belong to this schema
527     *
528     * @param procedureName procedure name
529     * @return an instance of the SQL procedure
530     */
531    public TSQLProcedure createProcedure(String procedureName){
532        return (TSQLProcedure)createSchemaObject(procedureName,ESQLDataObjectType.dotProcedure);
533    }
534
535    public TSQLProcedure createProcedure(String procedureName, ESQLDataObjectType type){
536        return (TSQLProcedure)createSchemaObject(procedureName,type);
537    }
538
539    /**
540     * create a function belong to this schema
541     *
542     * @param functionName function name
543     * @return an instance of the SQL function.
544     */
545    public TSQLFunction createFunction(String functionName){
546        return (TSQLFunction)createSchemaObject(functionName,ESQLDataObjectType.dotFunction);
547    }
548
549    /**
550     * create a trigger belong to this schema
551     *
552     * @param triggerName trigger name
553     * @return an instance of the SQL trigger.
554     */
555    public TSQLTrigger createTrigger(String triggerName){
556        return (TSQLTrigger)createSchemaObject(triggerName,ESQLDataObjectType.dotTrigger);
557    }
558
559    /**
560     * create a synonyms belong to this schema
561     *
562     * @param synonymsName synonyms name
563     * @return an instance of the SQL synonyms
564     */
565    public TSQLSynonyms createSynonyms(String synonymsName){
566        return (TSQLSynonyms)createSchemaObject(synonymsName,ESQLDataObjectType.dotSynonyms);
567    }
568
569    /**
570     * create a table belong to this schema. If table with the same name already exists in the schema
571     * return the existing table.
572     *
573     * @param tableName table name
574     * @return an instance of the SQL table
575     */
576    public TSQLTable createTable(String tableName){
577        return (TSQLTable)createSchemaObject(tableName,ESQLDataObjectType.dotTable);
578    }
579
580    public TSQLTable createTable(String tableName, int priority){
581        return (TSQLTable)createSchemaObject(tableName,ESQLDataObjectType.dotTable, priority);
582    }
583
584    // Slice S3: route public lookups through findSchemaObject so they go through
585    // the bucketed → hierarchical (IdentifierService.keyForMap) → legacy fallback
586    // chain, instead of bypassing both indexes via raw schemaObjectMap.
587    public boolean containsTable(String tableName) {
588        return findSchemaObject(ESQLDataObjectType.dotTable, tableName) != null;
589    }
590
591    public TSQLTable findTable(String tableName) {
592        return (TSQLTable) findSchemaObject(ESQLDataObjectType.dotTable, tableName);
593    }
594    
595        public TSQLSchemaObject findSchemaObject(String schemaObjectName) {
596                TSQLSchemaObject object = findSchemaObject(ESQLDataObjectType.dotFunction, schemaObjectName);
597                if (object == null) {
598                        object = findSchemaObject(ESQLDataObjectType.dotProcedure, schemaObjectName);
599                }
600                if (object == null) {
601                        object = findSchemaObject(ESQLDataObjectType.dotTrigger, schemaObjectName);
602                }
603                if (object == null) {
604                        object = findSchemaObject(ESQLDataObjectType.dotOraclePackage, schemaObjectName);
605                }
606                return object;
607        }
608
609    /**
610     *
611     * @param schemaObjectName 该名称不带 catalog, schema 前缀
612     * @param dataObjectType
613     * @return
614     */
615    public TSQLSchemaObject createSchemaObject(String schemaObjectName, ESQLDataObjectType dataObjectType){
616        return createSchemaObject(schemaObjectName, dataObjectType, 0);
617    }
618
619    /**
620     *
621     * @param schemaObjectName 该名称不带 catalog, schema 前缀
622     * @param dataObjectType
623     * @param priority
624     * @return
625     */
626    protected TSQLSchemaObject createSchemaObject(String schemaObjectName, ESQLDataObjectType dataObjectType, int priority){
627        TSQLSchemaObject result = null;
628        TSQLSchemaObject schemaObject = findSchemaObject(dataObjectType, schemaObjectName);
629        if (schemaObject == null){
630            switch (dataObjectType){
631                case dotTable:
632                    result = new TSQLTable(this,schemaObjectName);
633                    break;
634                case dotOraclePackage:
635                    result = new TSQLOraclePackage(this,schemaObjectName);
636                    break;
637                case dotProcedure:
638                    result = new TSQLProcedure(this,schemaObjectName);
639                    break;
640                case dotFunction:
641                    result = new TSQLFunction(this,schemaObjectName);
642                    break;
643                case dotTrigger:
644                    result = new TSQLTrigger(this,schemaObjectName);
645                    break;
646                case dotSynonyms:
647                    result = new TSQLSynonyms(this,schemaObjectName);
648                    break;
649            }
650        }else if (dataObjectType == schemaObject.getDataObjectType()){
651            result = schemaObject;
652        }else{
653            System.out.println("object name conflict:"+getQualifiedName()+"."+schemaObjectName+",type:"+dataObjectType+" VS "+schemaObject.getQualifiedName()+", type:"+schemaObject.getDataObjectType());
654        };
655
656        if (result!=null && priority > result.getPriority()) {
657            result.setPriority(priority);
658        }
659        return result;
660    }
661
662}