001package gudusoft.gsqlparser.slpc.adapter;
002
003import gudusoft.gsqlparser.TBaseType;
004import gudusoft.gsqlparser.dlineage.dataflow.model.xml.column;
005import gudusoft.gsqlparser.dlineage.dataflow.model.AuthoritativeLineageEvidence;
006import gudusoft.gsqlparser.dlineage.dataflow.model.xml.dataflow;
007import gudusoft.gsqlparser.dlineage.dataflow.model.xml.procedure;
008import gudusoft.gsqlparser.dlineage.dataflow.model.xml.relationship;
009import gudusoft.gsqlparser.dlineage.dataflow.model.xml.table;
010import gudusoft.gsqlparser.slpc.model.SlpcObject;
011
012import java.nio.charset.StandardCharsets;
013import java.security.MessageDigest;
014import java.security.NoSuchAlgorithmException;
015import java.util.ArrayList;
016import java.util.Collections;
017import java.util.List;
018
019/**
020 * Caller-supplied facts that do not exist in the legacy dataflow POJO.
021 * Supplying these values is not SQL reparsing; it makes artifact and identity
022 * provenance explicit at the adapter boundary.
023 */
024public final class LegacyAdapterContext {
025
026    public interface IdentityResolver {
027        /** Returns a proven descriptor or null to quarantine the endpoint. */
028        ResolvedIdentity resolve(table relation, column field);
029    }
030
031    public static final IdentityResolver NO_IDENTITY = new IdentityResolver() {
032        @Override public ResolvedIdentity resolve(table relation, column field) { return null; }
033    };
034
035    /**
036     * Supplies typed operation and ordered-operand evidence produced while the
037     * SQL AST is still available. The legacy XML transform text and its set
038     * iteration order are deliberately not treated as this evidence.
039     */
040    public interface TypedTransformResolver {
041        /** Returns a proven profile for this legacy relationship, or null. */
042        TypedTransform resolve(relationship relation);
043    }
044
045    public static final TypedTransformResolver NO_TYPED_TRANSFORM = new TypedTransformResolver() {
046        @Override public TypedTransform resolve(relationship relation) { return null; }
047    };
048
049    /**
050     * Supplies overload identity, formal-parameter binding, and explicit
051     * parameter/return flows while the routine AST or catalog signature is
052     * still available. Legacy argument names are display evidence only and
053     * are never matched to variable names by this adapter.
054     */
055    public interface RoutineBindingResolver {
056        RoutineBinding resolve(procedure routine);
057    }
058
059    public static final RoutineBindingResolver NO_ROUTINE_BINDING = new RoutineBindingResolver() {
060        @Override public RoutineBinding resolve(procedure routine) { return null; }
061    };
062
063    /** Supplies declaration-order composite FK pairs lost by legacy output. */
064    public interface CompositeForeignKeyResolver {
065        List<ForeignKeyBinding> resolve(dataflow completed);
066    }
067
068    public static final CompositeForeignKeyResolver NO_COMPOSITE_FOREIGN_KEY =
069            new CompositeForeignKeyResolver() {
070                @Override public List<ForeignKeyBinding> resolve(dataflow completed) {
071                    return Collections.emptyList();
072                }
073            };
074
075    private final String sourceKind;
076    private final String logicalLocator;
077    private final String sqlDialect;
078    private final String contentSha256;
079    private final String statementKind;
080    private final String engineVersion;
081    private final IdentityResolver identityResolver;
082    private final TypedTransformResolver typedTransformResolver;
083    private final RoutineBindingResolver routineBindingResolver;
084    private final CompositeForeignKeyResolver compositeForeignKeyResolver;
085
086    private LegacyAdapterContext(Builder builder) {
087        this.sourceKind = required(builder.sourceKind, "sourceKind");
088        this.logicalLocator = required(builder.logicalLocator, "logicalLocator");
089        this.sqlDialect = required(builder.sqlDialect, "sqlDialect");
090        this.contentSha256 = required(builder.contentSha256, "contentSha256");
091        this.statementKind = required(builder.statementKind, "statementKind");
092        this.engineVersion = required(builder.engineVersion, "engineVersion");
093        this.identityResolver = builder.identityResolver == null ? NO_IDENTITY : builder.identityResolver;
094        this.typedTransformResolver = builder.typedTransformResolver == null
095                ? NO_TYPED_TRANSFORM : builder.typedTransformResolver;
096        this.routineBindingResolver = builder.routineBindingResolver == null
097                ? NO_ROUTINE_BINDING : builder.routineBindingResolver;
098        this.compositeForeignKeyResolver = builder.compositeForeignKeyResolver == null
099                ? NO_COMPOSITE_FOREIGN_KEY : builder.compositeForeignKeyResolver;
100    }
101
102    public static Builder builder() { return new Builder(); }
103
104    /** Convenience factory; the SQL is hashed only and is never parsed by the adapter. */
105    public static Builder forInlineSql(String logicalLocator, String dialect, String sql) {
106        return builder().sourceKind("INLINE_SQL").logicalLocator(logicalLocator).sqlDialect(dialect)
107                .contentSha256(sha256(sql.getBytes(StandardCharsets.UTF_8)));
108    }
109
110    public String sourceKind() { return sourceKind; }
111    public String logicalLocator() { return logicalLocator; }
112    public String sqlDialect() { return sqlDialect; }
113    public String contentSha256() { return contentSha256; }
114    public String statementKind() { return statementKind; }
115    public String engineVersion() { return engineVersion; }
116    public IdentityResolver identityResolver() { return identityResolver; }
117    public TypedTransformResolver typedTransformResolver() { return typedTransformResolver; }
118    public RoutineBindingResolver routineBindingResolver() { return routineBindingResolver; }
119    public CompositeForeignKeyResolver compositeForeignKeyResolver() { return compositeForeignKeyResolver; }
120
121    public static final class TypedTransform {
122        private final String operationFamily;
123        private final String operationCode;
124        private final List<TypedOperand> operands;
125
126        public TypedTransform(String operationFamily, String operationCode, List<TypedOperand> operands) {
127            this.operationFamily = required(operationFamily, "operationFamily");
128            this.operationCode = required(operationCode, "operationCode");
129            if (operands == null || operands.isEmpty()) {
130                throw new IllegalArgumentException("operands must not be empty");
131            }
132            this.operands = Collections.unmodifiableList(new ArrayList<TypedOperand>(operands));
133        }
134
135        public String operationFamily() { return operationFamily; }
136        public String operationCode() { return operationCode; }
137        public List<TypedOperand> operands() { return operands; }
138    }
139
140    public static final class TypedOperand {
141        /** Legacy POJO record ID used only for edge correlation, never a database identifier. */
142        private final String sourceId;
143        private final String roleCode;
144
145        public TypedOperand(String sourceId, String roleCode) {
146            this.sourceId = required(sourceId, "sourceId");
147            this.roleCode = required(roleCode, "roleCode");
148        }
149
150        public String sourceId() { return sourceId; }
151        public String roleCode() { return roleCode; }
152    }
153
154    public static final class RoutineBinding {
155        private final ResolvedIdentity routineIdentity;
156        private final List<RoutineParameterBinding> parameters;
157        private final List<RoutineValueFlow> flows;
158
159        public RoutineBinding(ResolvedIdentity routineIdentity, List<RoutineParameterBinding> parameters,
160                List<RoutineValueFlow> flows) {
161            if (routineIdentity == null) throw new IllegalArgumentException("routineIdentity must not be null");
162            this.routineIdentity = routineIdentity;
163            this.parameters = immutable(parameters);
164            this.flows = immutable(flows);
165        }
166
167        public ResolvedIdentity routineIdentity() { return routineIdentity; }
168        public List<RoutineParameterBinding> parameters() { return parameters; }
169        public List<RoutineValueFlow> flows() { return flows; }
170    }
171
172    public static final class RoutineParameterBinding {
173        private final String bindingKey;
174        private final String legacyArgumentId;
175        private final String boundLegacyEndpointId;
176        private final String displayName;
177        private final String role;
178        private final int ordinal;
179        private final String declaredType;
180        private final ResolvedIdentity identity;
181
182        public RoutineParameterBinding(String bindingKey, String legacyArgumentId, String boundLegacyEndpointId,
183                String displayName, String role, int ordinal, String declaredType, ResolvedIdentity identity) {
184            this.bindingKey = required(bindingKey, "bindingKey");
185            this.legacyArgumentId = optional(legacyArgumentId);
186            this.boundLegacyEndpointId = optional(boundLegacyEndpointId);
187            this.displayName = required(displayName, "displayName");
188            this.role = required(role, "role");
189            if (ordinal < 1) throw new IllegalArgumentException("ordinal must be positive");
190            this.ordinal = ordinal;
191            this.declaredType = optional(declaredType);
192            if (identity == null) throw new IllegalArgumentException("identity must not be null");
193            this.identity = identity;
194        }
195
196        public String bindingKey() { return bindingKey; }
197        public String legacyArgumentId() { return legacyArgumentId; }
198        public String boundLegacyEndpointId() { return boundLegacyEndpointId; }
199        public String displayName() { return displayName; }
200        public String role() { return role; }
201        public int ordinal() { return ordinal; }
202        public String declaredType() { return declaredType; }
203        public ResolvedIdentity identity() { return identity; }
204    }
205
206    public static final class RoutineValueRef {
207        private final String parameterBindingKey;
208        private final String legacyEndpointId;
209
210        private RoutineValueRef(String parameterBindingKey, String legacyEndpointId) {
211            this.parameterBindingKey = optional(parameterBindingKey);
212            this.legacyEndpointId = optional(legacyEndpointId);
213            if ((this.parameterBindingKey == null) == (this.legacyEndpointId == null)) {
214                throw new IllegalArgumentException("exactly one routine value reference kind is required");
215            }
216        }
217
218        public static RoutineValueRef parameter(String bindingKey) {
219            return new RoutineValueRef(required(bindingKey, "bindingKey"), null);
220        }
221
222        public static RoutineValueRef legacyEndpoint(String legacyEndpointId) {
223            return new RoutineValueRef(null, required(legacyEndpointId, "legacyEndpointId"));
224        }
225
226        public String parameterBindingKey() { return parameterBindingKey; }
227        public String legacyEndpointId() { return legacyEndpointId; }
228    }
229
230    public static final class RoutineValueFlow {
231        private final RoutineValueRef source;
232        private final RoutineValueRef target;
233        private final String role;
234        private final String operationKind;
235        private final String operationInputKind;
236
237        public RoutineValueFlow(RoutineValueRef source, RoutineValueRef target, String role,
238                String operationKind, String operationInputKind) {
239            if (source == null || target == null) throw new IllegalArgumentException("routine flow endpoints are required");
240            this.source = source;
241            this.target = target;
242            this.role = required(role, "role");
243            this.operationKind = required(operationKind, "operationKind");
244            this.operationInputKind = required(operationInputKind, "operationInputKind");
245        }
246
247        public RoutineValueRef source() { return source; }
248        public RoutineValueRef target() { return target; }
249        public String role() { return role; }
250        public String operationKind() { return operationKind; }
251        public String operationInputKind() { return operationInputKind; }
252    }
253
254    public static final class ForeignKeyBinding {
255        private final List<String> evidenceRelationshipIds;
256        private final List<ForeignKeyPair> keyPairs;
257
258        public ForeignKeyBinding(List<String> evidenceRelationshipIds, List<ForeignKeyPair> keyPairs) {
259            this.evidenceRelationshipIds = immutableRequired(evidenceRelationshipIds, "evidenceRelationshipIds");
260            this.keyPairs = immutableRequired(keyPairs, "keyPairs");
261        }
262
263        public List<String> evidenceRelationshipIds() { return evidenceRelationshipIds; }
264        public List<ForeignKeyPair> keyPairs() { return keyPairs; }
265    }
266
267    public static final class ForeignKeyPair {
268        private final String foreignKeyFieldLegacyId;
269        private final String referencedFieldLegacyId;
270
271        public ForeignKeyPair(String foreignKeyFieldLegacyId, String referencedFieldLegacyId) {
272            this.foreignKeyFieldLegacyId = required(foreignKeyFieldLegacyId, "foreignKeyFieldLegacyId");
273            this.referencedFieldLegacyId = required(referencedFieldLegacyId, "referencedFieldLegacyId");
274        }
275
276        public String foreignKeyFieldLegacyId() { return foreignKeyFieldLegacyId; }
277        public String referencedFieldLegacyId() { return referencedFieldLegacyId; }
278    }
279
280    public static final class ResolvedIdentity {
281        private final SlpcObject identity;
282        private final String resolutionStatus;
283        private final String publicationState;
284
285        public ResolvedIdentity(SlpcObject identity, String resolutionStatus, String publicationState) {
286            if (identity == null) throw new IllegalArgumentException("identity must not be null");
287            this.identity = identity;
288            this.resolutionStatus = required(resolutionStatus, "resolutionStatus");
289            this.publicationState = required(publicationState, "publicationState");
290        }
291
292        public SlpcObject identity() { return identity; }
293        public String resolutionStatus() { return resolutionStatus; }
294        public String publicationState() { return publicationState; }
295    }
296
297    public static final class Builder {
298        private String sourceKind = "INLINE_SQL";
299        private String logicalLocator;
300        private String sqlDialect;
301        private String contentSha256;
302        private String statementKind = "UNKNOWN";
303        private String engineVersion = TBaseType.versionid;
304        private IdentityResolver identityResolver;
305        private TypedTransformResolver typedTransformResolver;
306        private RoutineBindingResolver routineBindingResolver;
307        private CompositeForeignKeyResolver compositeForeignKeyResolver;
308
309        public Builder sourceKind(String value) { this.sourceKind = value; return this; }
310        public Builder logicalLocator(String value) { this.logicalLocator = value; return this; }
311        public Builder sqlDialect(String value) { this.sqlDialect = value; return this; }
312        public Builder contentSha256(String value) { this.contentSha256 = value; return this; }
313        public Builder statementKind(String value) { this.statementKind = value; return this; }
314        public Builder engineVersion(String value) { this.engineVersion = value; return this; }
315        public Builder identityResolver(IdentityResolver value) { this.identityResolver = value; return this; }
316        public Builder typedTransformResolver(TypedTransformResolver value) { this.typedTransformResolver = value; return this; }
317        public Builder routineBindingResolver(RoutineBindingResolver value) { this.routineBindingResolver = value; return this; }
318        public Builder compositeForeignKeyResolver(CompositeForeignKeyResolver value) { this.compositeForeignKeyResolver = value; return this; }
319        /**
320         * Connects evidence captured by the same DataFlowAnalyzer run to all
321         * AST/catalog-authoritative adapter resolvers. Explicit resolver calls
322         * made after this method may override an individual strategy.
323         */
324        public Builder authoritativeEvidence(AuthoritativeLineageEvidence value) {
325            LegacyAuthoritativeEvidenceBridge bridge = new LegacyAuthoritativeEvidenceBridge(value);
326            this.typedTransformResolver = bridge;
327            this.routineBindingResolver = bridge;
328            this.compositeForeignKeyResolver = bridge;
329            return this;
330        }
331        public LegacyAdapterContext build() { return new LegacyAdapterContext(this); }
332    }
333
334    private static <T> List<T> immutable(List<T> values) {
335        return values == null ? Collections.<T>emptyList()
336                : Collections.unmodifiableList(new ArrayList<T>(values));
337    }
338
339    private static <T> List<T> immutableRequired(List<T> values, String field) {
340        if (values == null || values.isEmpty()) throw new IllegalArgumentException(field + " must not be empty");
341        return Collections.unmodifiableList(new ArrayList<T>(values));
342    }
343
344    private static String optional(String value) {
345        return value == null || value.isEmpty() ? null : value;
346    }
347
348    private static String required(String value, String field) {
349        if (value == null || value.isEmpty()) throw new IllegalArgumentException(field + " must not be empty");
350        return value;
351    }
352
353    private static String sha256(byte[] value) {
354        try {
355            byte[] digest = MessageDigest.getInstance("SHA-256").digest(value);
356            StringBuilder result = new StringBuilder();
357            for (byte item : digest) result.append(String.format("%02x", item & 255));
358            return result.toString();
359        } catch (NoSuchAlgorithmException impossible) { throw new IllegalStateException(impossible); }
360    }
361}