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}