001package gudusoft.gsqlparser.sqlenv.parser.sqlflow;
002
003import gudusoft.gsqlparser.EDbVendor;
004import gudusoft.gsqlparser.sqlenv.ESQLDataObjectType;
005import gudusoft.gsqlparser.sqlenv.IdentifierInputForm;
006import gudusoft.gsqlparser.sqlenv.TSQLEnv;
007import gudusoft.gsqlparser.util.SQLUtil;
008
009import java.util.HashMap;
010import java.util.List;
011import java.util.Map;
012
013/**
014 * The {@code nameForm} declaration of the sqlflow {@code servers} metadata format (plain and
015 * sharded), for a reader that works on the parsed JSON tree itself.
016 *
017 * <p>A file declares once, at its root, how it writes object names: {@code SQL_LEXICAL}
018 * ({@code "Amount"}, the default and what the sqlflow-ingester exporter emits) or
019 * {@code CATALOG_STORED} ({@code Amount}, the name the database catalog holds). Every reader
020 * of the format reads names as SQL text, so a {@code CATALOG_STORED} tree is rewritten here,
021 * once, into the SQL spelling of the same stored names. The spelling rule itself is
022 * {@link TSQLEnv#toLexical}; this class only knows which JSON key holds which kind of name.</p>
023 *
024 * <p>{@code SqlflowSQLEnv} and {@code SqlflowShardedSQLEnv} apply the same rule name by name
025 * instead, because their public constructors receive a caller-owned map that must not be
026 * modified. See docs/refactor/identifier/README.md, "Catalog inputs and name output".</p>
027 */
028public final class SqlflowNameForm {
029
030    private static final Map<String, ESQLDataObjectType> ROLE_OF_LIST = new HashMap<String, ESQLDataObjectType>();
031
032    static {
033        ROLE_OF_LIST.put("databases", ESQLDataObjectType.dotCatalog);
034        ROLE_OF_LIST.put("schemas", ESQLDataObjectType.dotSchema);
035        ROLE_OF_LIST.put("tables", ESQLDataObjectType.dotTable);
036        ROLE_OF_LIST.put("views", ESQLDataObjectType.dotTable);
037        ROLE_OF_LIST.put("columns", ESQLDataObjectType.dotColumn);
038        ROLE_OF_LIST.put("packages", ESQLDataObjectType.dotOraclePackage);
039        ROLE_OF_LIST.put("procedures", ESQLDataObjectType.dotProcedure);
040        ROLE_OF_LIST.put("functions", ESQLDataObjectType.dotFunction);
041        ROLE_OF_LIST.put("triggers", ESQLDataObjectType.dotTrigger);
042        ROLE_OF_LIST.put("synonyms", ESQLDataObjectType.dotSynonyms);
043    }
044
045    private SqlflowNameForm() {
046    }
047
048    /**
049     * The form a {@code servers} file declares; {@code SQL_LEXICAL} when it declares none.
050     *
051     * @throws TSQLEnv.InvalidNameFormException for a misspelt value
052     */
053    public static IdentifierInputForm declared(Map root) {
054        return TSQLEnv.parseNameForm(root == null ? null : root.get("nameForm"), IdentifierInputForm.SQL_LEXICAL);
055    }
056
057    /**
058     * Rewrite a parsed {@code servers} file, in place, so that its names are SQL text. Does
059     * nothing unless the file declares {@code CATALOG_STORED}. The declaration is then changed
060     * to {@code SQL_LEXICAL}, so a second call cannot encode the names twice.
061     *
062     * @param defaultVendor the dialect of a server that carries no {@code dbVendor}
063     * @return the form the file declared before the call
064     */
065    public static IdentifierInputForm toLexicalInPlace(EDbVendor defaultVendor, Map root) {
066        IdentifierInputForm form = declared(root);
067        if (form != IdentifierInputForm.CATALOG_STORED) {
068            return form;
069        }
070        Object servers = root.get("servers");
071        if (servers instanceof List) {
072            for (Object server : (List) servers) {
073                if (!(server instanceof Map)) continue;
074                String dbVendor = (String) ((Map) server).get("dbVendor");
075                EDbVendor vendor = SQLUtil.isEmpty(dbVendor) ? defaultVendor : EDbVendor.valueOf(dbVendor);
076                // The server's own "name" is a connection label, not a database object name.
077                walk(vendor, (Map) server, null);
078            }
079        } else {
080            // The bare server object as root, the shape SqlflowSQLEnv is constructed with.
081            walk(defaultVendor, root, null);
082        }
083        root.put("nameForm", IdentifierInputForm.SQL_LEXICAL.name());
084        return form;
085    }
086
087    /**
088     * Rewrite one catalog or schema shard of a sharded export, in place. A shard carries no
089     * declaration of its own: the manifest's {@code nameForm} covers all of them.
090     *
091     * @param role what the shard's own {@code name} is ({@code dotCatalog} or
092     *             {@code dotSchema}), or null to leave it alone
093     */
094    public static void shardToLexicalInPlace(EDbVendor vendor, Map shard, ESQLDataObjectType role,
095                                             IdentifierInputForm manifestForm) {
096        if (manifestForm == IdentifierInputForm.CATALOG_STORED && shard != null) {
097            walk(vendor, shard, role);
098        }
099    }
100
101    private static void walk(EDbVendor vendor, Map node, ESQLDataObjectType role) {
102        if (role != null) {
103            rewrite(vendor, node, "name", role);
104            if (role == ESQLDataObjectType.dotSynonyms) {
105                rewrite(vendor, node, "database", ESQLDataObjectType.dotCatalog);
106                rewrite(vendor, node, "schema", ESQLDataObjectType.dotSchema);
107                rewrite(vendor, node, "sourceDatabase", ESQLDataObjectType.dotCatalog);
108                rewrite(vendor, node, "sourceSchema", ESQLDataObjectType.dotSchema);
109                rewrite(vendor, node, "sourceName", ESQLDataObjectType.dotTable);
110            }
111        }
112        for (Object e : node.entrySet()) {
113            Map.Entry entry = (Map.Entry) e;
114            if (!(entry.getValue() instanceof List)) continue;
115            ESQLDataObjectType childRole = ROLE_OF_LIST.get(String.valueOf(entry.getKey()));
116            for (Object child : (List) entry.getValue()) {
117                if (child instanceof Map) {
118                    walk(vendor, (Map) child, childRole);
119                }
120            }
121        }
122    }
123
124    private static void rewrite(EDbVendor vendor, Map node, String key, ESQLDataObjectType role) {
125        Object value = node.get(key);
126        if (value instanceof String) {
127            node.put(key, TSQLEnv.toLexical(vendor, role, (String) value, IdentifierInputForm.CATALOG_STORED));
128        }
129    }
130}