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}