001package gudusoft.gsqlparser.resolver2.binding;
002
003import gudusoft.gsqlparser.resolver2.ColumnLevel;
004import gudusoft.gsqlparser.resolver2.namespace.INamespace;
005import gudusoft.gsqlparser.resolver2.namespace.MetadataState;
006
007/**
008 * Maps a (namespace, column-name) pair to a {@link ColumnAuthority} verdict.
009 *
010 * <p>Slice S4 (plan §5.5). The binding post-pass consumes this lookup to
011 * decide whether {@code UNKNOWN_COLUMN} is justified for a not-found column:
012 * only an {@link ColumnAuthority#AUTHORITATIVE_ABSENT} verdict is sufficient
013 * — every other verdict is silent (or, in strict mode S6, escalates to
014 * {@code CATALOG_METADATA_UNAVAILABLE} WARNING).</p>
015 *
016 * <p>Originally package-private behind the post-pass. Widened to public for
017 * MantisBT 4659: resolver2 must ask the same "can this namespace PROVE the
018 * column?" question before it is allowed to re-point a reference away from a
019 * FROM-clause table. Keeping one definition of the verdict is what stops the
020 * resolver and the diagnostics from disagreeing about what counts as proof.</p>
021 */
022public final class BindingMetadataAuthority {
023
024    private BindingMetadataAuthority() {
025        // static-only
026    }
027
028    /**
029     * Return the column-authority verdict for a {@code columnName} against
030     * {@code namespace}.
031     *
032     * <ul>
033     *   <li>Returns {@link ColumnAuthority#METADATA_UNAVAILABLE} when either
034     *       input is {@code null}/empty, when namespace state is anything
035     *       other than {@link MetadataState#FOUND}, or when the namespace's
036     *       own column lookup is ambiguous ({@link ColumnLevel#MAYBE}).</li>
037     *   <li>Returns {@link ColumnAuthority#AUTHORITATIVE_PRESENT} when
038     *       namespace state is {@code FOUND} and the namespace declares the
039     *       column exists.</li>
040     *   <li>Returns {@link ColumnAuthority#AUTHORITATIVE_ABSENT} when
041     *       namespace state is {@code FOUND} and the namespace declares the
042     *       column does NOT exist.</li>
043     * </ul>
044     *
045     * <p>This intentionally never throws: a defensive lookup is the right
046     * default for diagnostics-emission code.</p>
047     */
048    public static ColumnAuthority lookup(INamespace namespace, String columnName) {
049        if (namespace == null || columnName == null || columnName.isEmpty()) {
050            return ColumnAuthority.METADATA_UNAVAILABLE;
051        }
052
053        MetadataState state = namespace.getMetadataState();
054        if (state != MetadataState.FOUND) {
055            return ColumnAuthority.METADATA_UNAVAILABLE;
056        }
057
058        ColumnLevel level = namespace.hasColumn(columnName);
059        if (level == ColumnLevel.EXISTS) {
060            return ColumnAuthority.AUTHORITATIVE_PRESENT;
061        }
062        if (level == ColumnLevel.NOT_EXISTS) {
063            return ColumnAuthority.AUTHORITATIVE_ABSENT;
064        }
065        // ColumnLevel.MAYBE — namespace cannot decide; defer to non-authoritative.
066        return ColumnAuthority.METADATA_UNAVAILABLE;
067    }
068
069    /**
070     * Same verdict as {@link #lookup}, but asked against the namespace's
071     * OUTPUT SCHEMA ({@link INamespace#hasAuthoritativeOutputColumn}) rather than
072     * its lookup-time {@link INamespace#hasColumn} view.
073     *
074     * <p>Use this one when the question is "can this namespace PROVE it exposes
075     * the column?" asked AFTER resolution has run. By then {@code hasColumn} has
076     * been poisoned: {@code resolveColumn()} caches columns it merely inferred, so
077     * a CTE that projects only {@code id, val} will happily answer {@code EXISTS}
078     * for {@code derived_col} once something has asked for it. The output-schema
079     * view filters those inferred entries out (MantisBT 4659).</p>
080     *
081     * <p>{@link #lookup} is left alone deliberately: the binding post-pass gates
082     * {@code UNKNOWN_COLUMN} on it, and tightening that gate here would silently
083     * change which diagnostics are emitted.</p>
084     */
085    public static ColumnAuthority lookupOutputSchema(INamespace namespace, String columnName) {
086        if (namespace == null || columnName == null || columnName.isEmpty()) {
087            return ColumnAuthority.METADATA_UNAVAILABLE;
088        }
089
090        if (namespace.getMetadataState() != MetadataState.FOUND) {
091            return ColumnAuthority.METADATA_UNAVAILABLE;
092        }
093
094        ColumnLevel level = namespace.hasAuthoritativeOutputColumn(columnName);
095        if (level == ColumnLevel.EXISTS) {
096            return ColumnAuthority.AUTHORITATIVE_PRESENT;
097        }
098        if (level == ColumnLevel.NOT_EXISTS) {
099            return ColumnAuthority.AUTHORITATIVE_ABSENT;
100        }
101        return ColumnAuthority.METADATA_UNAVAILABLE;
102    }
103}