< Summary

Information
Class: NGql.Core.Features.PreserveExtensions
Assembly: NGql.Core
File(s): /home/runner/work/NGql/NGql/src/Core/Features/PreserveExtensions.cs
Line coverage
100%
Covered lines: 90
Uncovered lines: 0
Coverable lines: 90
Total lines: 222
Line coverage: 100%
Branch coverage
100%
Covered branches: 70
Total branches: 70
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/NGql/NGql/src/Core/Features/PreserveExtensions.cs

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2using NGql.Core.Abstractions;
 3using NGql.Core.Builders;
 4using NGql.Core.Extensions;
 5
 6namespace NGql.Core.Features;
 7
 8/// <summary>
 9/// Extension methods for preserving specific QueryBuilder fields based on specified paths.
 10/// </summary>
 11internal static class PreserveExtensions
 12{
 13    /// <summary>
 14    /// Preserves only the fields that match or are descendants of the specified paths.
 15    /// </summary>
 16    /// <param name="query">The query builder to preserve fields from.</param>
 17    /// <param name="fieldPaths">The field paths to preserve.</param>
 18    /// <returns>A new QueryBuilder instance with only the preserved fields.</returns>
 19    public static QueryBuilder Preserve(this QueryBuilder query, params string[]? fieldPaths)
 20    {
 30621        if (fieldPaths == null || fieldPaths.Length == 0)
 622            return query;
 23
 30024        var newQuery = QueryBuilder.CreateDefaultBuilder(query.Definition.Name);
 25
 26        // Copy merging strategy and metadata
 30027        newQuery.Definition.MergingStrategy = query.Definition.MergingStrategy;
 30028        if (query.Definition._metadata != null)
 29        {
 6030            foreach (var metadata in query.Definition.Metadata)
 31            {
 1832                newQuery.Definition.Metadata[metadata.Key] = metadata.Value;
 33            }
 34        }
 35
 163236        foreach (var targetPath in fieldPaths)
 37        {
 51638            ExtractMatchingFields(query.Definition.Fields, newQuery.Definition.Fields, targetPath.AsSpan());
 39        }
 40
 41        // Extract variables from all preserved fields
 30042        ExtractVariablesFromFields(newQuery.Definition.Fields, newQuery.Definition.Variables);
 43
 30044        return newQuery;
 45    }
 46
 47    private static void ExtractMatchingFields(Dictionary<string, FieldDefinition> sourceFields,
 48        Dictionary<string, FieldDefinition> targetFields, ReadOnlySpan<char> path)
 49    {
 51650        var isLeafPath = SplitFirstSegment(path, out var currentSegment, out var remainingPath);
 51
 51652        var match = FindFieldByNameOrAlias(sourceFields, currentSegment);
 54053        if (!match.HasValue) return;
 54
 49255        var (fieldKey, fieldDef) = match.Value;
 56
 49257        if (isLeafPath)
 58        {
 5159            targetFields[fieldKey] = fieldDef;
 5160            return;
 61        }
 62
 44763        if (fieldDef._children is null) return;
 64
 43565        var existing = GetOrAddTargetChild(targetFields, fieldKey, fieldDef);
 43566        ExtractMatchingFields(fieldDef._children, existing._children!, remainingPath);
 43567    }
 68
 69    private static void ExtractMatchingFields(FieldChildren sourceFields, FieldChildren targetFields, ReadOnlySpan<char>
 70    {
 124871        var isLeafPath = SplitFirstSegment(path, out var currentSegment, out var remainingPath);
 72
 125773        if (!TryFindByNameOrAlias(sourceFields, currentSegment, out var fieldKey, out var fieldDef)) return;
 74
 123975        if (isLeafPath)
 76        {
 42377            targetFields.Set(fieldKey, fieldDef);
 42378            return;
 79        }
 80
 81981        if (fieldDef._children is null) return;
 82
 81383        var targetChild = GetOrAddTargetChild(targetFields, fieldKey, fieldDef);
 81384        ExtractMatchingFields(fieldDef._children, targetChild._children!, remainingPath);
 81385    }
 86
 87    private static bool SplitFirstSegment(ReadOnlySpan<char> path, out ReadOnlySpan<char> current, out ReadOnlySpan<char
 88    {
 176489        var dot = path.IndexOf('.');
 176490        if (dot < 0)
 91        {
 48992            current = path;
 48993            remaining = ReadOnlySpan<char>.Empty;
 48994            return true;
 95        }
 127596        current = path[..dot];
 127597        remaining = path[(dot + 1)..];
 127598        return false;
 99    }
 100
 101    private static bool TryFindByNameOrAlias(FieldChildren children, ReadOnlySpan<char> nameOrAlias, out string key, out
 102    {
 4617103        foreach (var f in children.AsSpan())
 104        {
 1680105            if (f.Name.AsSpan().Equals(nameOrAlias, StringComparison.OrdinalIgnoreCase)
 1680106                || (f.Alias is { Length: > 0 } alias && alias.AsSpan().Equals(nameOrAlias, StringComparison.OrdinalIgnor
 107            {
 1239108                key = f.Name;
 1239109                field = f;
 1239110                return true;
 111            }
 112        }
 9113        key = string.Empty;
 9114        field = null!;
 9115        return false;
 116    }
 117
 118    private static FieldDefinition GetOrAddTargetChild(Dictionary<string, FieldDefinition> target, string fieldKey, Fiel
 119    {
 435120        if (!target.TryGetValue(fieldKey, out var existing))
 121        {
 249122            existing = CloneFieldWithoutChildren(source);
 249123            target[fieldKey] = existing;
 124        }
 435125        existing._children ??= new FieldChildren();
 435126        return existing;
 127    }
 128
 129    private static FieldDefinition GetOrAddTargetChild(FieldChildren targetFields, string fieldKey, FieldDefinition sour
 130    {
 813131        if (!targetFields.TryGetValue(fieldKey, out var existing) || existing is null)
 132        {
 447133            existing = CloneFieldWithoutChildren(source);
 447134            targetFields.Set(fieldKey, existing);
 135        }
 813136        existing._children ??= new FieldChildren();
 813137        return existing;
 138    }
 139
 140    /// <summary>
 141    /// Creates a shallow clone of a field without its children, preserving arguments and metadata.
 142    /// </summary>
 143    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 144    private static FieldDefinition CloneFieldWithoutChildren(FieldDefinition source)
 145    {
 146        // FieldDefinition._type is always set by every constructor; the null-coalesce was
 147        // a defensive guard for the rare `with { Type = null }` path and never fires here.
 696148        var cloned = new FieldDefinition(
 696149            source.Name,
 696150            source._type!,
 696151            source.Alias,
 696152            source._arguments)
 696153        {
 696154            Path = source.Path
 696155        };
 156
 696157        cloned.IsNeverMerge = source.IsNeverMerge;
 158
 696159        if (source._metadata != null)
 160        {
 18161            foreach (var meta in source._metadata)
 162            {
 6163                cloned.Metadata[meta.Key] = meta.Value;
 164            }
 165        }
 166
 696167        return cloned;
 168    }
 169
 170    // All call sites pre-check the fields collection for null (NavigatePath, PreserveAtPath,
 171    // QueryDefinitionExtensions.NavigatePath); the parameter could safely be tightened but
 172    // is kept nullable to preserve the existing internal API signature.
 173    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 174    internal static KeyValuePair<string, FieldDefinition>? FindFieldByNameOrAlias(IReadOnlyDictionary<string, FieldDefin
 175    {
 4617176        foreach (var kvp in fields!)
 177        {
 1638178            if (MatchesKeyNameOrAlias(kvp, nameOrAlias))
 179            {
 1191180                return kvp;
 181            }
 182        }
 183
 75184        return null;
 1191185    }
 186
 187    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 188    private static bool MatchesKeyNameOrAlias(KeyValuePair<string, FieldDefinition> kvp, ReadOnlySpan<char> nameOrAlias)
 1638189        => nameOrAlias.Equals(kvp.Key.AsSpan(), StringComparison.OrdinalIgnoreCase)
 1638190        || nameOrAlias.Equals(kvp.Value.Name.AsSpan(), StringComparison.OrdinalIgnoreCase)
 1638191        || (!string.IsNullOrEmpty(kvp.Value.Alias) && nameOrAlias.Equals(kvp.Value.Alias.AsSpan(), StringComparison.Ordi
 192
 193    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 194    private static void ExtractVariablesFromFields(FieldChildren children, SortedSet<Variable> variables)
 195    {
 3582196        foreach (var field in children.AsSpan())
 197        {
 1017198            ExtractVariablesFromField(field, variables);
 199        }
 774200    }
 201
 202    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 203    private static void ExtractVariablesFromFields(Dictionary<string, FieldDefinition> fields, SortedSet<Variable> varia
 204    {
 1200205        foreach (var field in fields.Values)
 206        {
 300207            ExtractVariablesFromField(field, variables);
 208        }
 300209    }
 210
 211    private static void ExtractVariablesFromField(FieldDefinition field, SortedSet<Variable> variables)
 212    {
 1317213        if (field._arguments?.Count > 0)
 214        {
 6215            Helpers.ExtractVariablesFromValue(field._arguments, variables);
 216        }
 1317217        if (field._children is { Count: > 0 })
 218        {
 774219            ExtractVariablesFromFields(field._children, variables);
 220        }
 1317221    }
 222}

Methods/Properties

Preserve(NGql.Core.Builders.QueryBuilder,System.String[])
ExtractMatchingFields(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>)
ExtractMatchingFields(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.FieldChildren,System.ReadOnlySpan`1<System.Char>)
SplitFirstSegment(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>&,System.ReadOnlySpan`1<System.Char>&)
TryFindByNameOrAlias(NGql.Core.Abstractions.FieldChildren,System.ReadOnlySpan`1<System.Char>,System.String&,NGql.Core.Abstractions.FieldDefinition&)
GetOrAddTargetChild(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.String,NGql.Core.Abstractions.FieldDefinition)
GetOrAddTargetChild(NGql.Core.Abstractions.FieldChildren,System.String,NGql.Core.Abstractions.FieldDefinition)
CloneFieldWithoutChildren(NGql.Core.Abstractions.FieldDefinition)
FindFieldByNameOrAlias(System.Collections.Generic.IReadOnlyDictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>)
MatchesKeyNameOrAlias(System.Collections.Generic.KeyValuePair`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>)
ExtractVariablesFromFields(NGql.Core.Abstractions.FieldChildren,System.Collections.Generic.SortedSet`1<NGql.Core.Variable>)
ExtractVariablesFromFields(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.Collections.Generic.SortedSet`1<NGql.Core.Variable>)
ExtractVariablesFromField(NGql.Core.Abstractions.FieldDefinition,System.Collections.Generic.SortedSet`1<NGql.Core.Variable>)