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}