001package gudusoft.gsqlparser.dlineage.util;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.dlineage.dataflow.model.ModelBindingManager;
005import gudusoft.gsqlparser.nodes.TObjectName;
006import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
007import gudusoft.gsqlparser.util.SQLUtil;
008
009/**
010 * The names dlineage publishes, version 1: the legacy normal form.
011 *
012 * <p>Each segment is {@code IdentifierService.normalizeStatic} of the name, upper-cased where the legacy collation
013 * classes call the role case-insensitive ({@link SQLUtil#legacyNormalSegment}). A name that shares that string with
014 * a different name of the same analysis gets a name of its own ({@code NormalNameCollisions}), except in the
015 * normalize-output relabel, which asks for the bare legacy fold ({@link Profile#NORMALIZED_OUTPUT}).
016 *
017 * <p>This is a compatibility renderer, not identity. Call it only where a name is PUBLISHED:
018 * <ul>
019 * <li>the normalize-output relabel ({@link Profile#NORMALIZED_OUTPUT});</li>
020 * <li>the JSON model's names in normalized mode;</li>
021 * <li>result-set display labels;</li>
022 * <li>a table's {@code normalizedName}.</li>
023 * </ul>
024 * Do not use it for equality, lookups, keys or merges: use the name service's keys ({@code CanonKey},
025 * {@code QualifiedCanonKey}, {@link DlineageUtil#getTableIdentityKey}, {@link DlineageUtil#lexicalColumnKey}). The one
026 * exception is explicit: the compatibility bucket of a name that carries no identity ({@link #tableBucket},
027 * {@link #columnBucket}, {@link #columnSegmentBucket}).
028 *
029 * <p>The strings it gives are the published contract of dlineage output version 1. The name service's stored form
030 * (the vendor's own bare name) is another output version, not a fix to this one.
031 */
032public final class LegacyDlineageWireNamesV1 {
033
034    /** Which names of output version 1 to render. */
035    public enum Profile {
036        /**
037         * The names the analysis publishes. Two different names of the analysis that share a legacy fold get names
038         * of their own ({@code NormalNameCollisions}). Given identifiers.
039         */
040        ANALYSIS,
041        /**
042         * The normalize-output relabel of finished output ({@code Option.setNormalizeOutput(true)}): the bare legacy
043         * fold of every name field it is given, labels included, because output version 1 publishes them that way
044         * (PostgreSQL {@code 'abc'} becomes {@code 'ABC'}). Colliding names are already kept apart by id.
045         */
046        NORMALIZED_OUTPUT
047    }
048
049    private LegacyDlineageWireNamesV1() {
050    }
051
052    /** The published form of a name of the given role, qualified or not, under the analysis's vendor. */
053    public static String name(String name, ESQLDataObjectType objectType) {
054        return name(Profile.ANALYSIS, ModelBindingManager.getGlobalVendor(), name, objectType);
055    }
056
057    /** The published form of a name field of the given role under the analysis's vendor, in the given profile. */
058    public static String name(Profile profile, String name, ESQLDataObjectType objectType) {
059        return name(profile, ModelBindingManager.getGlobalVendor(), name, objectType);
060    }
061
062    /** The published form of a name of the given role, qualified or not. */
063    public static String name(EDbVendor vendor, String name, ESQLDataObjectType objectType) {
064        return name(Profile.ANALYSIS, vendor, name, objectType);
065    }
066
067    /** The published form of a name field of the given role, in the given profile. */
068    public static String name(Profile profile, EDbVendor vendor, String name, ESQLDataObjectType objectType) {
069        ModelBindingManager manager = profile == Profile.NORMALIZED_OUTPUT ? ModelBindingManager.get() : null;
070        boolean previous = manager != null && manager.useLegacyNormalNames(true);
071        try {
072            return SQLUtil.getIdentifierNormalName(vendor, name, objectType);
073        } finally {
074            if (manager != null) {
075                manager.useLegacyNormalNames(previous);
076            }
077        }
078    }
079
080    /** The published form of a column name, qualified or not, under the analysis's vendor. */
081    public static String columnName(String name) {
082        return name(name, ESQLDataObjectType.dotColumn);
083    }
084
085    /** The published form of a column name, qualified or not. */
086    public static String columnName(EDbVendor vendor, String name) {
087        return name(vendor, name, ESQLDataObjectType.dotColumn);
088    }
089
090    /**
091     * The published form of a column reference's own segment: its column name, or the whole reference where it has
092     * none.
093     */
094    public static String columnName(TObjectName column) {
095        if (column == null || column.toString() == null) {
096            return null;
097        }
098        String name = column.getColumnNameOnly();
099        return columnName(name == null || "".equals(name.trim()) ? column.toString().trim() : name.trim());
100    }
101
102    /** The published form of one column segment: the fold without the qualified-name layout. */
103    public static String columnSegment(String name) {
104        return columnSegment(ModelBindingManager.getGlobalVendor(), name);
105    }
106
107    /** The published form of one column segment: the fold without the qualified-name layout. */
108    public static String columnSegment(EDbVendor vendor, String name) {
109        return segment(vendor, ESQLDataObjectType.dotColumn, name);
110    }
111
112    /** The published form of one name segment of the given role (a database link, a column). */
113    public static String segment(EDbVendor vendor, ESQLDataObjectType objectType, String name) {
114        return SQLUtil.normalizeIdentifier(vendor, objectType, name);
115    }
116
117    /** The published form of a routine name under the analysis's vendor. */
118    public static String functionName(String name) {
119        return name(name, ESQLDataObjectType.dotFunction);
120    }
121
122    /**
123     * The compatibility bucket of a table's name that carries no identity: its published form in output version 1,
124     * the only evidence a merge has left. A table read back from a serialized document publishes either a SQL
125     * spelling or a bare stored name, and nothing says which, so the name service cannot read it; a result set's
126     * or a variable's name is a label. Merges group such tables by this bucket, as they always did, and key every
127     * table that carries an identity by the name service ({@link PhysicalTableMergeKeys}). Not an identity: two
128     * tables of one bucket stay apart wherever an identity tells them apart.
129     */
130    public static String tableBucket(String qualifiedName) {
131        return tableName(qualifiedName);
132    }
133
134    /**
135     * The compatibility bucket of a column's name that carries no identity (a label, a column read back from a
136     * serialized document): its published form in output version 1. See {@link #tableBucket}.
137     */
138    public static String columnBucket(String name) {
139        return columnName(name);
140    }
141
142    /** {@link #columnBucket} of one column segment, without the qualified-name layout. */
143    public static String columnSegmentBucket(String name) {
144        return columnSegment(name);
145    }
146
147    /** The published form of a table name. */
148    public static String tableName(EDbVendor vendor, String name) {
149        return name(vendor, name, ESQLDataObjectType.dotTable);
150    }
151
152    /**
153     * The published form of a table reference under the analysis's vendor. A database link and a call-shaped name
154     * keep their suffix as written ({@code t@link}, {@code f(1)}).
155     */
156    public static String tableName(String name) {
157        if (name == null) {
158            return null;
159        }
160        EDbVendor vendor = ModelBindingManager.getGlobalVendor();
161        if (ModelBindingManager.get() != null && ModelBindingManager.get().isDblinkTable(name)) {
162            int index = name.lastIndexOf("@");
163            return index > 0 ? tableName(vendor, name.substring(0, index)) + name.substring(index) : name;
164        }
165        if (name.endsWith(")") && name.indexOf("(") != -1) {
166            return tableName(vendor, name.substring(0, name.lastIndexOf("("))) + name.substring(name.lastIndexOf("("));
167        }
168        return tableName(vendor, name);
169    }
170}