001package gudusoft.gsqlparser.lineage.v2.producer; 002 003import gudusoft.gsqlparser.EDbVendor; 004import gudusoft.gsqlparser.TBaseType; 005import gudusoft.gsqlparser.lineage.v2.fold.FoldKernel; 006import gudusoft.gsqlparser.sqlenv.TSQLEnv; 007 008/** 009 * The caller's settings for one SQL-LR production run. Immutable; build with {@link #builder(EDbVendor)}. 010 * 011 * <p>Only settings that change what the document asserts live here; the analysis options the producer forces 012 * (plan 06, Section 3.1) are not configurable.</p> 013 * 014 * @since 4.2.17 015 */ 016public final class ProducerConfig { 017 018 /** The producer name every document of this implementation declares; the version is the GSP version. */ 019 public static final String PRODUCER_NAME = "gsp-java"; 020 021 /** Literal values appear in {@code CONSTANT} inputs. */ 022 public static final String PRESENT = "PRESENT"; 023 /** Literal values are withheld (the default). */ 024 public static final String REDACTED = "REDACTED"; 025 026 private final EDbVendor vendor; 027 private final String authorityDomain; 028 private final Metadata metadata; 029 private final String defaultServer; 030 private final String defaultDatabase; 031 private final String defaultSchema; 032 private final String constantDisclosure; 033 private final boolean analyzeDynamicSql; 034 private final int maxFoldStates; 035 private final int maxFoldWorlds; 036 private final String producerName; 037 private final String producerVersion; 038 private final String createdAt; 039 040 private ProducerConfig(Builder b) { 041 this.vendor = b.vendor; 042 this.authorityDomain = b.authorityDomain; 043 this.metadata = b.metadata; 044 this.defaultServer = b.defaultServer; 045 this.defaultDatabase = b.defaultDatabase; 046 this.defaultSchema = b.defaultSchema; 047 this.constantDisclosure = b.constantDisclosure; 048 this.analyzeDynamicSql = b.analyzeDynamicSql; 049 this.maxFoldStates = b.maxFoldStates; 050 this.maxFoldWorlds = b.maxFoldWorlds; 051 this.producerName = PRODUCER_NAME; 052 this.producerVersion = TBaseType.versionid; 053 this.createdAt = b.createdAt; 054 } 055 056 public static Builder builder(EDbVendor vendor) { 057 return new Builder(vendor); 058 } 059 060 public EDbVendor getVendor() { 061 return vendor; 062 } 063 064 /** The caller-declared authority domain, used byte for byte; {@code null} means a {@code PRODUCER_DEFAULT} authority. */ 065 public String getAuthorityDomain() { 066 return authorityDomain; 067 } 068 069 /** The metadata the caller supplies, or {@code null} (metadata status {@code NONE}). */ 070 public Metadata getMetadata() { 071 return metadata; 072 } 073 074 /** 075 * The metadata of an analysis (specification Section 6.2): the catalog GSP resolves against, the caller's 076 * declaration of how complete it is, and every artifact it was built from (format id and the SHA-256 of the 077 * bytes as supplied), which the analysis context's metadata digest covers. 078 */ 079 public static final class Metadata { 080 private final TSQLEnv catalog; 081 private final String status; 082 private final java.util.List<String[]> artifacts; 083 084 private Metadata(TSQLEnv catalog, String status, java.util.List<String[]> artifacts) { 085 this.catalog = catalog; 086 this.status = status; 087 this.artifacts = artifacts; 088 } 089 090 /** The catalog; the producer analyses against a private copy and never modifies it. */ 091 public TSQLEnv getCatalog() { 092 return catalog; 093 } 094 095 /** {@code PARTIAL} or {@code COMPLETE}. */ 096 public String getStatus() { 097 return status; 098 } 099 100 /** {format id, SHA-256 hex} of every artifact, in the order given. */ 101 public java.util.List<String[]> getArtifacts() { 102 java.util.List<String[]> copy = new java.util.ArrayList<String[]>(); 103 for (String[] a : artifacts) { 104 copy.add(a.clone()); 105 } 106 return copy; 107 } 108 } 109 110 public String getDefaultServer() { 111 return defaultServer; 112 } 113 114 public String getDefaultDatabase() { 115 return defaultDatabase; 116 } 117 118 public String getDefaultSchema() { 119 return defaultSchema; 120 } 121 122 /** {@link #REDACTED} (default) or {@link #PRESENT}. Credential redaction applies either way. */ 123 public String getConstantDisclosure() { 124 return constantDisclosure; 125 } 126 127 public boolean isAnalyzeDynamicSql() { 128 return analyzeDynamicSql; 129 } 130 131 public int getMaxFoldStates() { 132 return maxFoldStates; 133 } 134 135 public int getMaxFoldWorlds() { 136 return maxFoldWorlds; 137 } 138 139 /** The analysing implementation: always {@link #PRODUCER_NAME}; not settable, it identifies the analyzer. */ 140 public String getProducerName() { 141 return producerName; 142 } 143 144 public String getProducerVersion() { 145 return producerVersion; 146 } 147 148 /** The optional {@code createdAt} of the header; never read from a clock. */ 149 public String getCreatedAt() { 150 return createdAt; 151 } 152 153 /** Builder. */ 154 public static final class Builder { 155 private final EDbVendor vendor; 156 private String authorityDomain; 157 private Metadata metadata; 158 private String defaultServer; 159 private String defaultDatabase; 160 private String defaultSchema; 161 private String constantDisclosure = REDACTED; 162 private boolean analyzeDynamicSql = true; 163 private int maxFoldStates = FoldKernel.DEFAULT_MAX_STATES; 164 private int maxFoldWorlds = FoldKernel.DEFAULT_MAX_WORLDS; 165 private String createdAt; 166 167 private Builder(EDbVendor vendor) { 168 if (vendor == null) { 169 throw new IllegalArgumentException("no vendor"); 170 } 171 this.vendor = vendor; 172 } 173 174 /** A non-empty domain name, used byte for byte ({@code VERBATIM}). */ 175 public Builder authorityDomain(String domain) { 176 if (domain != null && domain.isEmpty()) { 177 throw new SqllrProducerException("PRODUCER_INVALID_INPUT", "an authority domain is never empty"); 178 } 179 this.authorityDomain = domain; 180 return this; 181 } 182 183 /** 184 * The catalog and what it was built from. 185 * 186 * @param catalog the catalog GSP resolves names against 187 * @param status {@code PARTIAL} or {@code COMPLETE}: the caller's declaration of its coverage 188 * @param artifacts one or more {format id, artifact bytes}; the bytes are hashed, not kept 189 */ 190 public Builder metadata(TSQLEnv catalog, String status, java.util.List<Object[]> artifacts) { 191 if (catalog == null) { 192 throw new IllegalArgumentException("no catalog"); 193 } 194 if (!"PARTIAL".equals(status) && !"COMPLETE".equals(status)) { 195 throw new SqllrProducerException("PRODUCER_INVALID_INPUT", "metadata status must be PARTIAL or " 196 + "COMPLETE, not " + status); 197 } 198 if (artifacts == null || artifacts.isEmpty()) { 199 throw new SqllrProducerException("PRODUCER_INVALID_INPUT", "metadata needs at least one artifact"); 200 } 201 java.util.List<String[]> list = new java.util.ArrayList<String[]>(); 202 for (Object[] artifact : artifacts) { 203 if (artifact == null || artifact.length != 2 || !(artifact[0] instanceof String) 204 || ((String) artifact[0]).isEmpty() || !(artifact[1] instanceof byte[])) { 205 throw new SqllrProducerException("PRODUCER_INVALID_INPUT", "an artifact is {format id, bytes}"); 206 } 207 list.add(new String[] {(String) artifact[0], 208 gudusoft.gsqlparser.lineage.v2.SqllrResources.sha256Hex((byte[]) artifact[1])}); 209 } 210 this.metadata = new Metadata(catalog, status, list); 211 return this; 212 } 213 214 public Builder defaultServer(String server) { 215 this.defaultServer = server; 216 return this; 217 } 218 219 public Builder defaultDatabase(String database) { 220 this.defaultDatabase = database; 221 return this; 222 } 223 224 public Builder defaultSchema(String schema) { 225 this.defaultSchema = schema; 226 return this; 227 } 228 229 /** {@link #REDACTED} or {@link #PRESENT}; {@code HASHED} needs a keyed token domain and is not supported. */ 230 public Builder constantDisclosure(String disclosure) { 231 if (!REDACTED.equals(disclosure) && !PRESENT.equals(disclosure)) { 232 throw new SqllrProducerException("PRODUCER_INVALID_INPUT", "constant disclosure " + disclosure 233 + " is not supported; use REDACTED or PRESENT"); 234 } 235 this.constantDisclosure = disclosure; 236 return this; 237 } 238 239 public Builder analyzeDynamicSql(boolean analyze) { 240 this.analyzeDynamicSql = analyze; 241 return this; 242 } 243 244 public Builder foldLimits(int maxStates, int maxWorlds) { 245 if (maxStates < 1 || maxWorlds < 1) { 246 throw new IllegalArgumentException("fold limits must be positive"); 247 } 248 this.maxFoldStates = maxStates; 249 this.maxFoldWorlds = maxWorlds; 250 return this; 251 } 252 253 public Builder createdAt(String timestamp) { 254 this.createdAt = timestamp; 255 return this; 256 } 257 258 public ProducerConfig build() { 259 return new ProducerConfig(this); 260 } 261 } 262}