001package gudusoft.gsqlparser.lineage.v2.producer; 002 003import java.lang.reflect.Field; 004import java.lang.reflect.Modifier; 005import java.util.Collections; 006import java.util.LinkedHashMap; 007import java.util.Map; 008import java.util.TreeMap; 009 010import gudusoft.gsqlparser.dlineage.dataflow.model.Option; 011import gudusoft.gsqlparser.lineage.v2.SqllrResources; 012import gudusoft.gsqlparser.lineage.v2.json.CanonicalJson; 013 014/** 015 * The analysis options of a SQL-LR production run (plan 06, Section 3.1). 016 * 017 * <p>Every field of {@link Option} has one class:</p> 018 * <ul> 019 * <li>{@link Kind#FIXED}: the producer sets it. Anything that hides an input, picks a candidate or changes what 020 * the runtime graph contains is fixed, because a digest only records a suppression, it does not repair it.</li> 021 * <li>{@link Kind#CALLER}: the caller decides it ({@link ProducerConfig}); it enters 022 * {@code effectiveOptionsDigest}.</li> 023 * <li>{@link Kind#IRRELEVANT}: it changes only legacy output, progress reporting or delegation, which the producer 024 * never uses.</li> 025 * </ul> 026 * 027 * <p>{@code ProducerOptionsTest} fails when {@link Option} gains a field this table does not classify.</p> 028 * 029 * @since 4.2.17 030 */ 031public final class ProducerOptions { 032 033 /** The class of an {@link Option} field. */ 034 public enum Kind { 035 FIXED, CALLER, IRRELEVANT 036 } 037 038 private static final Map<String, Kind> KINDS; 039 040 static { 041 Map<String, Kind> m = new LinkedHashMap<String, Kind>(); 042 // set by the caller (ProducerConfig), recorded in the digest 043 for (String name : new String[] {"vendor", "defaultServer", "defaultDatabase", "defaultSchema", 044 "analyzeDynamicSql"}) { 045 m.put(name, Kind.CALLER); 046 } 047 // fixed by the producer: they change the runtime graph or hide inputs 048 for (String name : new String[] {"simpleOutput", "showJoin", "ignoreRecordSet", "ignoreTemporaryTable", 049 "ignoreTopSelect", "ignoreUnusedSynonym", "showCallRelation", "linkOrphanColumnToFirstTable", 050 "transform", "transformCoordinate", "collectAuthoritativeLineageEvidence", "showImplicitSchema", 051 "showCountTableColumn", "showConstantTable", "showCaseWhenAsDirect", "filePathDatabase", "filePathSchema", 052 "showERDiagram", "targetResultSetTypes", "filterRelationTypes", "sqlflowIgnoreFunction", 053 "dynamicSqlTrustMode", "excludedProcedureNames", "excludedProcedurePatterns", "showCandidateTable", 054 "ignoreInsertIntoValues", "enablePipelinedStitching", "identityFirstVariablePools", 055 "maxPipelinedExpansionDepth", "maxStitchedSourcesPerColumn", "analyzeMode", "identifierProfile", 056 "reportDynamicSqlSitesAsErrors", "dynamicSqlCaseSensitiveCompare", "recoverRoutineBodyErrors", 057 "assumeExternalScriptPassthrough", "autoDetectLargeFile", 058 // client placeholders are not T-SQL, and Power Query input is not a producer input format 059 "enableMssqlColonBindVariables", "powerQueryInnerVendor"}) { 060 m.put(name, Kind.FIXED); 061 } 062 // legacy output, progress, delegation and comment collection only 063 for (String name : new String[] {"collectCommentBindings", "commentBindingMaxRawLength", 064 "commentBindingMaxCommentsPerSourceUnit", "commentBindingMaxTotalRawLength", "handleListener", 065 "textFormat", "simpleShowFunction", "simpleShowSynonym", "simpleShowUdfFunctionOnly", "simpleShowCursor", 066 "simpleShowVariable", "simpleRetainIntermediate", "simpleShowTopSelectResultSet", "ignoreCoordinate", 067 "startId", "output", "traceSQL", "showComment", "traceProcedure", "parallel", "simpleShowRelationTypes", 068 "normalizeOutput", "traceTablePosition", "largeSqlInfoCountThreshold", "largeSqlTotalSizeThreshold", 069 "largeQueryCountThreshold", "largeShardCountThreshold", "largeFileSplitSizeMB", 070 "largeSqlThresholdMultiplier", "estimatedMemoryPerTaskMB"}) { 071 m.put(name, Kind.IRRELEVANT); 072 } 073 KINDS = Collections.unmodifiableMap(m); 074 } 075 076 private ProducerOptions() { 077 } 078 079 /** The class of every {@link Option} field this table knows. */ 080 public static Map<String, Kind> kinds() { 081 return KINDS; 082 } 083 084 /** The instance fields of {@link Option}, by declaration. */ 085 static Map<String, Field> optionFields() { 086 Map<String, Field> fields = new TreeMap<String, Field>(); 087 for (Field field : Option.class.getDeclaredFields()) { 088 if (!Modifier.isStatic(field.getModifiers()) && !field.isSynthetic()) { 089 fields.put(field.getName(), field); 090 } 091 } 092 return fields; 093 } 094 095 /** The {@link Option} a production run analyses with. */ 096 public static Option option(ProducerConfig config) { 097 Option option = new Option(); 098 option.setVendor(config.getVendor()); 099 option.setOutput(false); 100 option.setSimpleOutput(false); 101 option.setShowJoin(false); 102 option.setIgnoreRecordSet(false); 103 option.setIgnoreTemporaryTable(false); 104 option.setIgnoreTopSelect(false); 105 option.setShowCallRelation(false); 106 option.setLinkOrphanColumnToFirstTable(false); 107 option.setTransform(false); 108 option.setTransformCoordinate(false); 109 option.setCollectAuthoritativeLineageEvidence(false); 110 option.setShowImplicitSchema(false); 111 option.setShowCountTableColumn(true); 112 option.setShowConstantTable(true); 113 option.setShowCaseWhenAsDirect(true); 114 option.setIgnoreInsertIntoValues(false); 115 option.setIdentityFirstVariablePools(true); 116 option.setAutoDetectLargeFile(false); 117 option.setReportDynamicSqlSitesAsErrors(false); 118 option.setRecoverRoutineBodyErrors(false); 119 option.setEnableMssqlColonBindVariables(false); 120 option.setPowerQueryInnerVendor(null); 121 option.setAnalyzeDynamicSql(config.isAnalyzeDynamicSql()); 122 if (config.getDefaultServer() != null) { 123 option.setDefaultServer(config.getDefaultServer()); 124 } 125 if (config.getDefaultDatabase() != null) { 126 option.setDefaultDatabase(config.getDefaultDatabase()); 127 } 128 if (config.getDefaultSchema() != null) { 129 option.setDefaultSchema(config.getDefaultSchema()); 130 } 131 return option; 132 } 133 134 /** 135 * The effective analysis options as a JSON object: every {@link Kind#FIXED} and {@link Kind#CALLER} field of the 136 * option a run uses, plus the producer's own settings that change what a document asserts. Field names carry 137 * the {@code gsp.} prefix; values are JSON scalars or sorted lists. 138 */ 139 public static Map<String, Object> effectiveOptions(ProducerConfig config) { 140 Option option = option(config); 141 Map<String, Object> out = new TreeMap<String, Object>(); 142 for (Map.Entry<String, Field> entry : optionFields().entrySet()) { 143 Kind kind = KINDS.get(entry.getKey()); 144 if (kind == Kind.IRRELEVANT || kind == null) { 145 continue; 146 } 147 Object value; 148 try { 149 Field field = entry.getValue(); 150 field.setAccessible(true); 151 value = field.get(option); 152 } catch (IllegalAccessException e) { 153 throw new IllegalStateException(e); 154 } 155 out.put("gsp.option." + entry.getKey(), jsonValue(value)); 156 } 157 out.put("gsp.producer.constantDisclosure", config.getConstantDisclosure()); 158 out.put("gsp.producer.foldMaxStates", Long.valueOf(config.getMaxFoldStates())); 159 out.put("gsp.producer.foldMaxWorlds", Long.valueOf(config.getMaxFoldWorlds())); 160 return out; 161 } 162 163 /** {@code effectiveOptionsDigest}: SHA-256 of the JCS form of {@link #effectiveOptions}. */ 164 public static String effectiveOptionsDigest(ProducerConfig config) { 165 return SqllrResources.sha256Hex(CanonicalJson.jcsBytes(effectiveOptions(config))); 166 } 167 168 private static Object jsonValue(Object value) { 169 if (value == null || value instanceof Boolean || value instanceof String) { 170 return value; 171 } 172 if (value instanceof Integer || value instanceof Long || value instanceof Short) { 173 return Long.valueOf(((Number) value).longValue()); 174 } 175 if (value instanceof Enum<?>) { 176 return ((Enum<?>) value).name(); 177 } 178 if (value instanceof java.util.Collection<?>) { 179 java.util.List<String> items = new java.util.ArrayList<String>(); 180 for (Object item : (java.util.Collection<?>) value) { 181 items.add(String.valueOf(jsonValue(item))); 182 } 183 Collections.sort(items); 184 return items; 185 } 186 if (value instanceof gudusoft.gsqlparser.sqlenv.IdentifierProfile) { 187 // the producer runs only under the vendor default profile (IdentityPolicy); its fingerprint names it 188 return Long.valueOf(((gudusoft.gsqlparser.sqlenv.IdentifierProfile) value).getFingerprint()); 189 } 190 return String.valueOf(value); 191 } 192}