< Summary

Information
Class: NGql.Core.Builders.FieldFactory
Assembly: NGql.Core
File(s): /home/runner/work/NGql/NGql/src/Core/Builders/FieldFactory.cs
Line coverage
100%
Covered lines: 258
Uncovered lines: 0
Coverable lines: 258
Total lines: 676
Line coverage: 100%
Branch coverage
100%
Covered branches: 158
Total branches: 158
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/Builders/FieldFactory.cs

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2using NGql.Core.Abstractions;
 3using NGql.Core.Extensions;
 4using NGql.Core.Pooling;
 5
 6namespace NGql.Core.Builders;
 7
 8/// <summary>
 9/// Factory class for creating and processing FieldDefinition instances.
 10/// Handles complex field creation logic, including dotted paths, type parsing, and field merging.
 11/// </summary>
 12internal static class FieldFactory
 13{
 14    /// <summary>
 15    /// Gets or adds a field to the collection, handling all field path complexities.
 16    /// This overload operates on the root-level <see cref="QueryDefinition.Fields"/> dictionary.
 17    /// </summary>
 18    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 19    internal static FieldDefinition GetOrAddField(Dictionary<string, FieldDefinition> fieldDefinitions, ReadOnlySpan<cha
 20    {
 2448321        var fieldType = type.IsEmpty ? Constants.DefaultFieldTypeSpan : type;
 22
 23        // FAST PATH: Simple field name
 2448324        if (fieldPath.IsSimpleField())
 25        {
 759626            return fieldDefinitions.GetOrAddSimpleField(fieldPath, fieldType, arguments, parentPath, metadata);
 27        }
 28
 29        // MEDIUM PATH: Dotted field
 1688730        if (fieldPath.IsDottedField())
 31        {
 1602032            return GetOrAddDottedField(fieldDefinitions, fieldPath, fieldType, arguments, parentPath, metadata);
 33        }
 34
 35        // SLOW PATH: Complex field processing
 86736        return GetOrAddComplexField(fieldDefinitions, fieldPath, fieldType, arguments, parentPath, metadata);
 37    }
 38
 39    /// <summary>
 40    /// Gets or adds a field as a child of the given parent node, handling all field path complexities.
 41    /// This overload is for per-node child access (not root-level dictionary access).
 42    /// </summary>
 43    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 44    internal static FieldDefinition GetOrAddField(FieldDefinition parent, ReadOnlySpan<char> fieldPath, ReadOnlySpan<cha
 45    {
 290146        var fieldType = type.IsEmpty ? Constants.DefaultFieldTypeSpan : type;
 290147        var children = parent._children ??= new FieldChildren();
 48
 49        // FAST PATH: Simple field name
 290150        if (fieldPath.IsSimpleField())
 51        {
 216652            return children.GetOrAddSimpleField(fieldPath, fieldType, arguments, parentPath, metadata);
 53        }
 54
 55        // MEDIUM PATH: Dotted field
 73556        if (fieldPath.IsDottedField())
 57        {
 39358            return GetOrAddDottedField(parent, fieldPath, fieldType, arguments, parentPath, metadata);
 59        }
 60
 61        // SLOW PATH: Complex field processing
 34262        return GetOrAddComplexField(parent, fieldPath, fieldType, arguments, parentPath, metadata);
 63    }
 64
 65    /// <summary>
 66    /// Gets or adds a dotted field (contains dots for nested access) — root-level variant.
 67    /// </summary>
 68    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 69    private static FieldDefinition GetOrAddDottedField(Dictionary<string, FieldDefinition> fieldDefinitions, ReadOnlySpa
 70    {
 1602071        var hasNoArguments = arguments == null;
 1602072        var hasNoMetadata = metadata == null;
 73
 74        // FAST PATH: No arguments/metadata - use optimized processing
 1602075        if (hasNoArguments && hasNoMetadata)
 76        {
 1518977            return ProcessDottedFieldFastPath(fieldDefinitions, fieldPath, fieldType);
 78        }
 79
 80        // SLOW PATH: With arguments/metadata
 83181        return ProcessDottedFieldWithMetadata(fieldDefinitions, fieldPath, fieldType, arguments, parentPath, metadata);
 82    }
 83
 84    /// <summary>
 85    /// Gets or adds a dotted field (contains dots for nested access) — per-node variant.
 86    /// </summary>
 87    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 88    private static FieldDefinition GetOrAddDottedField(FieldDefinition rootParent, ReadOnlySpan<char> fieldPath, ReadOnl
 89    {
 39390        var hasNoArguments = arguments == null;
 39391        var hasNoMetadata = metadata == null;
 92
 39393        if (hasNoArguments && hasNoMetadata)
 94        {
 28595            return ProcessDottedFieldFastPath(rootParent, fieldPath, fieldType);
 96        }
 97
 10898        return ProcessDottedFieldWithMetadata(rootParent, fieldPath, fieldType, arguments, parentPath, metadata);
 99    }
 100
 101    /// <summary>
 102    /// Processes dotted fields without arguments or metadata for optimal performance — root-level variant.
 103    /// The first segment uses the root dictionary; subsequent segments use <see cref="FieldDefinition._children"/>.
 104    /// </summary>
 105    // Callers reach here only via IsDottedField() which guarantees fieldPath contains '.',
 106    // so the loop runs at least twice and parentField is non-null on exit.
 107    private static FieldDefinition ProcessDottedFieldFastPath(Dictionary<string, FieldDefinition> rootFields, ReadOnlySp
 108    {
 15189109        FieldDefinition? parentField = null;
 15189110        var pathStart = 0;
 111
 61884112        while (pathStart < fieldPath.Length)
 113        {
 46695114            ExtractDottedSegment(fieldPath, pathStart, out var spanSegment, out var nextStart);
 46695115            parentField = parentField is null
 46695116                ? GetOrCreateRootSegment(rootFields, spanSegment, fieldPath, pathStart, fieldType)
 46695117                : GetOrCreateChildSegment(parentField, spanSegment, fieldPath, pathStart, fieldType);
 46695118            pathStart = nextStart;
 119        }
 120
 15189121        return parentField!;
 122    }
 123
 124    /// <summary>
 125    /// Processes dotted fields without arguments or metadata — per-node variant.
 126    /// All segments use <see cref="FieldDefinition._children"/>.
 127    /// </summary>
 128    private static FieldDefinition ProcessDottedFieldFastPath(FieldDefinition rootParent, ReadOnlySpan<char> fieldPath, 
 129    {
 285130        var currentParent = rootParent;
 285131        var pathStart = 0;
 132
 1410133        while (pathStart < fieldPath.Length)
 134        {
 1125135            ExtractDottedSegment(fieldPath, pathStart, out var spanSegment, out var nextStart);
 1125136            currentParent = GetOrCreateChildSegment(currentParent, spanSegment, fieldPath, pathStart, fieldType);
 1125137            pathStart = nextStart;
 138        }
 139
 285140        return currentParent;
 141    }
 142
 143    private static FieldDefinition GetOrCreateRootSegment(Dictionary<string, FieldDefinition> rootFields, SpanSegment sp
 144    {
 15189145        var segmentName = spanSegment.Name.ToString();
 15189146        if (!rootFields.TryGetValue(segmentName, out var field))
 147        {
 1464148            field = CreateDottedFieldSegment(spanSegment.Name, fieldPath, pathStart + spanSegment.Name.Length, spanSegme
 1464149            rootFields[segmentName] = field;
 1464150            return field;
 151        }
 13725152        PromoteToObjectIfNeeded(field, spanSegment.IsLastFragment);
 13725153        return field;
 154    }
 155
 156    private static FieldDefinition GetOrCreateChildSegment(FieldDefinition parentField, SpanSegment spanSegment, ReadOnl
 157    {
 32631158        var children = parentField._children ??= new FieldChildren();
 32631159        if (!children.TryGetValue(spanSegment.Name, out var field) || field is null)
 160        {
 27513161            field = CreateDottedFieldSegment(spanSegment.Name, fieldPath, pathStart + spanSegment.Name.Length, spanSegme
 27513162            children.Append(field);
 27513163            return field;
 164        }
 5118165        PromoteToObjectIfNeeded(field, spanSegment.IsLastFragment);
 5118166        return field;
 167    }
 168
 169    private static void PromoteToObjectIfNeeded(FieldDefinition field, bool isLastFragment)
 170    {
 18843171        if (!isLastFragment && field.ShouldConvertToObjectType())
 172        {
 6174173            field._type = Constants.ObjectFieldType;
 174        }
 18843175    }
 176
 177    /// <summary>
 178    /// Processes dotted fields with arguments and metadata — root-level variant.
 179    /// </summary>
 180    private static FieldDefinition ProcessDottedFieldWithMetadata(Dictionary<string, FieldDefinition> fieldDefinitions, 
 181    {
 831182        var parentPathSpan = parentPath.AsSpan();
 183
 184        // Use stack allocation for small paths, pooled resources for larger ones
 831185        var estimatedPathLength = parentPathSpan.Length + fieldPath.Length + 10; // Extra space for dots
 186
 831187        if (estimatedPathLength <= 512)
 188        {
 825189            Span<char> pathBuffer = stackalloc char[512];
 825190            var pathBuilder = new SpanPathBuilder(pathBuffer);
 191
 825192            if (!parentPathSpan.IsEmpty)
 193            {
 6194                pathBuilder.Append(parentPathSpan);
 195            }
 196
 825197            return ProcessDottedFieldSegments(fieldDefinitions, fieldPath, fieldType, arguments, metadata, ref pathBuild
 198        }
 199        else
 200        {
 201            // Use pooled resources for very long paths
 6202            using var pooledArray = CharArrayPool.GetPooled(estimatedPathLength);
 6203            var pathBuilder = new SpanPathBuilder(pooledArray.AsSpan());
 204
 6205            if (!parentPathSpan.IsEmpty)
 206            {
 3207                pathBuilder.Append(parentPathSpan);
 208            }
 209
 6210            return ProcessDottedFieldSegments(fieldDefinitions, fieldPath, fieldType, arguments, metadata, ref pathBuild
 211        }
 6212    }
 213
 214    /// <summary>
 215    /// Processes dotted fields with arguments and metadata — per-node variant.
 216    /// </summary>
 217    private static FieldDefinition ProcessDottedFieldWithMetadata(FieldDefinition rootParent, ReadOnlySpan<char> fieldPa
 218    {
 108219        var parentPathSpan = parentPath.AsSpan();
 108220        var estimatedPathLength = parentPathSpan.Length + fieldPath.Length + 10;
 221
 108222        if (estimatedPathLength <= 512)
 223        {
 105224            Span<char> pathBuffer = stackalloc char[512];
 105225            var pathBuilder = new SpanPathBuilder(pathBuffer);
 201226            if (!parentPathSpan.IsEmpty) pathBuilder.Append(parentPathSpan);
 105227            return ProcessDottedFieldSegments(rootParent, fieldPath, fieldType, arguments, metadata, ref pathBuilder);
 228        }
 229        else
 230        {
 3231            using var pooledArray = CharArrayPool.GetPooled(estimatedPathLength);
 3232            var pathBuilder = new SpanPathBuilder(pooledArray.AsSpan());
 6233            if (!parentPathSpan.IsEmpty) pathBuilder.Append(parentPathSpan);
 3234            return ProcessDottedFieldSegments(rootParent, fieldPath, fieldType, arguments, metadata, ref pathBuilder);
 235        }
 3236    }
 237
 238    /// <summary>
 239    /// Processes individual segments of a dotted field path — root-level variant.
 240    /// </summary>
 241    // Callers reach here only via IsDottedField() which guarantees fieldPath contains '.', so the
 242    // loop runs at least once and result is non-null on exit.
 243    private static FieldDefinition ProcessDottedFieldSegments(Dictionary<string, FieldDefinition> rootFields, ReadOnlySp
 244    {
 831245        FieldDefinition? parentField = null;
 831246        FieldDefinition? result = null;
 247
 3186248        while (fieldPath.Length > 0)
 249        {
 2355250            ExtractDottedSegmentWithPath(fieldPath, out var spanSegment, out var remainingPath);
 251
 2355252            pathBuilder.Append(spanSegment.Name);
 253
 2355254            if (parentField == null)
 255            {
 256                // Root level — use the root Dictionary.
 831257                result = ProcessDottedSegment(rootFields, spanSegment.Name, spanSegment.IsLastFragment, fieldType, argum
 258            }
 259            else
 260            {
 261                // Nested level — use FieldChildren.
 1524262                var children = parentField._children ??= new FieldChildren();
 1524263                result = ProcessDottedSegment(children, spanSegment.Name, spanSegment.IsLastFragment, fieldType, argumen
 264            }
 265
 2355266            parentField = result;
 2355267            fieldPath = remainingPath;
 268        }
 269
 831270        return result!;
 271    }
 272
 273    /// <summary>
 274    /// Processes individual segments of a dotted field path — per-node variant.
 275    /// </summary>
 276    private static FieldDefinition ProcessDottedFieldSegments(FieldDefinition rootParent, ReadOnlySpan<char> fieldPath, 
 277    {
 108278        var currentParent = rootParent;
 108279        FieldDefinition? result = null;
 280
 1101281        while (fieldPath.Length > 0)
 282        {
 993283            ExtractDottedSegmentWithPath(fieldPath, out var spanSegment, out var remainingPath);
 284
 993285            pathBuilder.Append(spanSegment.Name);
 993286            var children = currentParent._children ??= new FieldChildren();
 993287            result = ProcessDottedSegment(children, spanSegment.Name, spanSegment.IsLastFragment, fieldType, arguments, 
 993288            currentParent = result;
 993289            fieldPath = remainingPath;
 290        }
 291
 108292        return result!;
 293    }
 294
 295    /// <summary>
 296    /// Creates a field segment for dotted field processing.
 297    /// </summary>
 298    private static FieldDefinition CreateDottedFieldSegment(ReadOnlySpan<char> segment, ReadOnlySpan<char> fullPath, int
 299    {
 28977300        var segmentType = isLastSegment ? fieldType : Constants.ObjectFieldTypeSpan;
 28977301        var segmentPath = fullPath.Slice(0, segmentEnd);
 302
 28977303        return Helpers.CreateFieldDefinition(segment, segmentType, ReadOnlySpan<char>.Empty, null, segmentPath, null);
 304    }
 305
 306    /// <summary>
 307    /// Processes a single dotted segment with arguments and metadata — root-Dict variant.
 308    /// </summary>
 309    private static FieldDefinition ProcessDottedSegment(Dictionary<string, FieldDefinition> currentFields, ReadOnlySpan<
 310    {
 831311        if (!currentFields.TryGetValue(segment, out var field))
 312        {
 168313            field = CreateDottedSegmentField(segment, isLastSegment, fieldType, arguments, metadata, segmentPath);
 168314            currentFields.SetValue(segment, field);
 168315            return field;
 316        }
 317
 318        // This Dictionary variant only processes the FIRST segment of dotted paths, so isLastSegment
 319        // is necessarily false (single-segment paths route through GetOrAddSimpleField instead).
 663320        var existing = field!;
 663321        if (existing.ShouldConvertToObjectType())
 322        {
 306323            existing = existing with { Type = Constants.ObjectFieldType };
 306324            currentFields.SetValue(segment, existing);
 325        }
 663326        return existing;
 327    }
 328
 329    /// <summary>
 330    /// Processes a single dotted segment with arguments and metadata — FieldChildren variant.
 331    /// </summary>
 332    private static FieldDefinition ProcessDottedSegment(FieldChildren children, ReadOnlySpan<char> segment, bool isLastS
 333    {
 2517334        if (!children.TryGetValue(segment, out var field))
 335        {
 2097336            field = CreateDottedSegmentField(segment, isLastSegment, fieldType, arguments, metadata, segmentPath);
 2097337            children.Append(field);
 2097338            return field;
 339        }
 340
 501341        if (!isLastSegment) return PromoteIntermediateChildToObject(field!);
 342        // FieldBuilder normalizes empty argument dictionaries to null upstream, so a
 343        // non-null `arguments` here always has Count > 0.
 339344        return arguments is null ? field! : MergeArgumentsIntoExistingChild(children, segment, field!, arguments);
 345    }
 346
 347    // ProcessDottedFieldFastPath handles the args-null case before reaching here, and
 348    // FieldBuilder.Create normalizes empty argument dictionaries to null upstream — so by
 349    // the time we get here arguments is always a non-null dictionary with Count > 0.
 350    private static FieldDefinition MergeArgumentsIntoExistingChild(FieldChildren children, ReadOnlySpan<char> segment, F
 351    {
 336352        var merged = existing.MergeFieldArguments(arguments);
 336353        children.Set(segment, merged);
 336354        return merged;
 355    }
 356
 357    private static FieldDefinition PromoteIntermediateChildToObject(FieldDefinition existing)
 358    {
 81359        if (existing.ShouldConvertToObjectType())
 360        {
 6361            existing._type = Constants.ObjectFieldType;
 362        }
 81363        return existing;
 364    }
 365
 366    private static FieldDefinition CreateDottedSegmentField(ReadOnlySpan<char> segment, bool isLastSegment, ReadOnlySpan
 367    {
 2265368        var segmentArgs = isLastSegment ? arguments : null;
 2265369        var segmentType = isLastSegment ? fieldType : Constants.ObjectFieldTypeSpan;
 2265370        var segmentMetadata = isLastSegment ? metadata : null;
 2265371        return Helpers.CreateFieldDefinition(segment, segmentType, ReadOnlySpan<char>.Empty, segmentArgs, segmentPath, s
 372    }
 373
 374    /// <summary>
 375    /// Gets or adds a complex field with type parsing and alias handling — root-level variant.
 376    /// The Dictionary variant is the entry point from QueryBuilder.AddField; callers never pass a
 377    /// non-empty parentPath (that's used only by the per-node FieldChildren overload below). The
 378    /// parameter exists to share a signature with the FieldChildren variant via the public dispatch.
 379    /// </summary>
 380    // AddFieldCore rejects null/whitespace fieldPath at the public-API boundary, so by the time
 381    // we reach here at least one non-whitespace segment exists and result is non-null on exit.
 382#pragma warning disable S1172 // parentPath unused — kept for signature symmetry with the FieldChildren variant.
 383    private static FieldDefinition GetOrAddComplexField(Dictionary<string, FieldDefinition> fieldDefinitions, ReadOnlySp
 384#pragma warning restore S1172
 385    {
 867386        Span<char> pathBuffer = stackalloc char[512];
 867387        var pathBuilder = new SpanPathBuilder(pathBuffer);
 388
 867389        fieldPath = Helpers.ParseFieldTypeFromPath(fieldPath, fieldType, out var parsedFieldType);
 390
 867391        FieldDefinition? parentField = null;
 867392        FieldDefinition? result = null;
 393
 4083394        while (fieldPath.Length > 0)
 395        {
 3216396            var segment = ExtractNextSegment(ref fieldPath);
 397
 3216398            if (segment.Name.IsWhiteSpace())
 399                continue;
 400
 3213401            pathBuilder.Append(segment.Name);
 3213402            var typeToUse = !segment.ParsedType.IsEmpty ? segment.ParsedType : parsedFieldType;
 403
 3213404            if (parentField == null)
 405            {
 406                // Root level
 867407                result = ProcessFieldSegment(fieldDefinitions, segment, arguments, typeToUse, pathBuilder.AsSpan(), meta
 408            }
 409            else
 410            {
 411                // Nested level
 2346412                var children = parentField._children ??= new FieldChildren();
 2346413                result = ProcessFieldSegment(children, segment, arguments, typeToUse, pathBuilder.AsSpan(), metadata);
 414            }
 415
 3213416            parentField = result;
 417        }
 418
 867419        return result!;
 420    }
 421
 422    /// <summary>
 423    /// Gets or adds a complex field with type parsing and alias handling — per-node variant.
 424    /// </summary>
 425    private static FieldDefinition GetOrAddComplexField(FieldDefinition rootParent, ReadOnlySpan<char> fieldPath, ReadOn
 426    {
 342427        var parentPathSpan = parentPath.AsSpan();
 342428        Span<char> pathBuffer = stackalloc char[512];
 342429        var pathBuilder = new SpanPathBuilder(pathBuffer);
 430
 681431        if (!parentPathSpan.IsEmpty) pathBuilder.Append(parentPathSpan);
 432
 342433        fieldPath = Helpers.ParseFieldTypeFromPath(fieldPath, fieldType, out var parsedFieldType);
 434
 342435        var currentParent = rootParent;
 342436        FieldDefinition? result = null;
 437
 780438        while (fieldPath.Length > 0)
 439        {
 438440            var segment = ExtractNextSegment(ref fieldPath);
 441
 438442            if (segment.Name.IsWhiteSpace())
 443                continue;
 444
 435445            pathBuilder.Append(segment.Name);
 435446            var typeToUse = !segment.ParsedType.IsEmpty ? segment.ParsedType : parsedFieldType;
 435447            var children = currentParent._children ??= new FieldChildren();
 435448            result = ProcessFieldSegment(children, segment, arguments, typeToUse, pathBuilder.AsSpan(), metadata);
 435449            currentParent = result;
 450        }
 451
 342452        return result!;
 453    }
 454
 455    /// <summary>
 456    /// Processes a field segment for complex field creation — root-Dict variant.
 457    /// </summary>
 458    private static FieldDefinition ProcessFieldSegment(Dictionary<string, FieldDefinition> currentFields, SpanSegment se
 459    {
 867460        if (!currentFields.TryGetValue(segment.Name.ToString(), out var field))
 461        {
 411462            return CreateNewField(currentFields, segment, arguments, parsedFieldType, fullPath, metadata);
 463        }
 464
 456465        return UpdateExistingField(currentFields, segment, field, arguments, parsedFieldType);
 466    }
 467
 468    /// <summary>
 469    /// Processes a field segment for complex field creation — FieldChildren variant.
 470    /// </summary>
 471    private static FieldDefinition ProcessFieldSegment(FieldChildren children, SpanSegment segment, IDictionary<string, 
 472    {
 2781473        if (!children.TryGetValue(segment.Name, out var field))
 474        {
 1761475            return CreateNewField(children, segment, arguments, parsedFieldType, fullPath, metadata);
 476        }
 477
 1020478        return UpdateExistingField(children, segment, field, arguments, parsedFieldType);
 479    }
 480
 481    /// <summary>
 482    /// Creates a new field for complex field processing — root-Dict variant.
 483    /// </summary>
 484    private static FieldDefinition CreateNewField(Dictionary<string, FieldDefinition> currentFields, SpanSegment segment
 485    {
 411486        var (fieldArgs, fieldMetadata) = ResolveSegmentArgsAndMetadata(segment, arguments, metadata);
 411487        var fieldType = ResolveSegmentFieldType(segment, parsedFieldType);
 411488        var field = Helpers.CreateFieldDefinition(segment.Name, fieldType, segment.Alias, fieldArgs, fullPath, fieldMeta
 411489        currentFields[segment.Name.ToString()] = field;
 411490        return field;
 491    }
 492
 493    /// <summary>
 494    /// Creates a new field for complex field processing — FieldChildren variant.
 495    /// </summary>
 496    private static FieldDefinition CreateNewField(FieldChildren children, SpanSegment segment, IDictionary<string, objec
 497    {
 1761498        var (fieldArgs, fieldMetadata) = ResolveSegmentArgsAndMetadata(segment, arguments, metadata);
 1761499        var fieldType = ResolveSegmentFieldType(segment, parsedFieldType);
 1761500        var field = Helpers.CreateFieldDefinition(segment.Name, fieldType, segment.Alias, fieldArgs, fullPath, fieldMeta
 1761501        children.Append(field);
 1761502        return field;
 503    }
 504
 505    private static (IDictionary<string, object?>? args, Dictionary<string, object?>? meta) ResolveSegmentArgsAndMetadata
 2172506        => segment.IsLastFragment ? (arguments, metadata) : (null, null);
 507
 508    private static ReadOnlySpan<char> ResolveSegmentFieldType(SpanSegment segment, ReadOnlySpan<char> parsedFieldType)
 509    {
 3348510        if (segment.IsLastFragment) return segment.HasParsedType ? segment.ParsedType : parsedFieldType;
 996511        return segment.HasParsedType && segment.ParsedType.SequenceEqual(Constants.ArrayTypeMarkerSpan)
 996512            ? Constants.ArrayTypeMarkerSpan
 996513            : Constants.ObjectFieldTypeSpan;
 514    }
 515
 516    /// <summary>
 517    /// Updates an existing field during complex field processing — root-Dict variant.
 518    /// </summary>
 519    private static FieldDefinition UpdateExistingField(Dictionary<string, FieldDefinition> currentFields, SpanSegment se
 520    {
 894521        if (!ApplyIntermediateUpdates(segment, field)) return field;
 522
 18523        if (arguments?.Count > 0)
 524        {
 3525            var fieldKey = segment.Name.ToString();
 3526            field = currentFields[fieldKey] = field.MergeFieldArguments(arguments);
 527        }
 18528        ApplyParsedFieldType(field, parsedFieldType);
 18529        return field;
 530    }
 531
 532    private static FieldDefinition UpdateExistingField(FieldChildren children, SpanSegment segment, FieldDefinition fiel
 533    {
 2025534        if (!ApplyIntermediateUpdates(segment, field)) return field;
 535
 15536        if (arguments?.Count > 0)
 537        {
 3538            field = field.MergeFieldArguments(arguments);
 3539            children.Set(segment.Name, field);
 540        }
 15541        ApplyParsedFieldType(field, parsedFieldType);
 15542        return field;
 543    }
 544
 545    // Mutates intermediate-segment metadata (alias, object-promotion). Returns true when the
 546    // segment is the last fragment and the caller should continue with last-fragment updates.
 547    private static bool ApplyIntermediateUpdates(SpanSegment segment, FieldDefinition field)
 548    {
 549        // segment.HasAlias <=> !Alias.IsEmpty by SpanSegment's invariant.
 1476550        if (segment.HasAlias && field._alias is null)
 551        {
 6552            field._alias = segment.Alias.ToString();
 553        }
 1476554        if (!segment.IsLastFragment && field.ShouldConvertToObjectType())
 555        {
 9556            field._type = Constants.ObjectFieldType;
 557        }
 1476558        return segment.IsLastFragment;
 559    }
 560
 561    private static void ApplyParsedFieldType(FieldDefinition field, ReadOnlySpan<char> parsedFieldType)
 562    {
 33563        if (!parsedFieldType.Equals(Constants.DefaultFieldTypeSpan, StringComparison.OrdinalIgnoreCase)
 33564            && !field._type.AsSpan().Equals(parsedFieldType, StringComparison.OrdinalIgnoreCase))
 565        {
 6566            field._type = parsedFieldType.ToString();
 567        }
 33568    }
 569
 570    /// <summary>
 571    /// Creates or merges a field definition into the target collection — root-Dict variant.
 572    /// </summary>
 573    internal static FieldDefinition CreateOrMergeField(Dictionary<string, FieldDefinition> fields, FieldDefinition field
 574    {
 575        // Try to find existing field to merge with
 60576        var existingField = Helpers.FindExistingField(fields, fieldDefinition);
 60577        if (existingField != null)
 578        {
 30579            var mergedField = existingField.MergeFieldArguments(fieldDefinition._arguments);
 30580            fields[existingField.Name] = mergedField;
 30581            return mergedField;
 582        }
 583
 30584        var newField = CloneFieldDefinitionForMerge(fieldDefinition);
 30585        fields[fieldDefinition.Name] = newField;
 30586        return newField;
 587    }
 588
 589    /// <summary>
 590    /// Creates or merges a field definition into the target collection — FieldChildren variant.
 591    /// </summary>
 592    internal static FieldDefinition CreateOrMergeField(FieldChildren children, FieldDefinition fieldDefinition)
 593    {
 186594        var existingField = Helpers.FindExistingField(children, fieldDefinition);
 186595        if (existingField != null)
 596        {
 24597            var mergedField = existingField.MergeFieldArguments(fieldDefinition._arguments);
 24598            children.Set(existingField.Name, mergedField);
 24599            return mergedField;
 600        }
 601
 162602        var newField = CloneFieldDefinitionForMerge(fieldDefinition);
 162603        children.Append(newField);
 162604        return newField;
 605    }
 606
 607    // FieldDefinition._type is always set to a non-empty value by all constructors (defaulting
 608    // to Constants.DefaultFieldType), so the type-empty branch was a dead defensive check.
 609    private static FieldDefinition CloneFieldDefinitionForMerge(FieldDefinition fieldDefinition)
 610    {
 192611        var fieldAlias = string.IsNullOrEmpty(fieldDefinition._alias) ? Span<char>.Empty : fieldDefinition._alias.AsSpan
 192612        return Helpers.CreateFieldDefinition(
 192613            fieldDefinition.Name.AsSpan(),
 192614            fieldDefinition._type.AsSpan(),
 192615            fieldAlias,
 192616            fieldDefinition._arguments,
 192617            fieldDefinition.Path.AsSpan(),
 192618            fieldDefinition.Metadata);
 619    }
 620
 621    private static void ExtractDottedSegment(ReadOnlySpan<char> fieldPath, int pathStart, out SpanSegment segment, out i
 622    {
 47820623        var dotIndex = fieldPath.Slice(pathStart).IndexOf('.');
 47820624        var isLastSegment = dotIndex == -1;
 47820625        var segmentEnd = isLastSegment ? fieldPath.Length : pathStart + dotIndex;
 47820626        var segmentSpan = fieldPath.Slice(pathStart, segmentEnd - pathStart);
 47820627        nextStart = isLastSegment ? fieldPath.Length : segmentEnd + 1;
 628
 47820629        segment = new SpanSegment(segmentSpan, ReadOnlySpan<char>.Empty, isLastSegment, ReadOnlySpan<char>.Empty);
 47820630    }
 631
 632    private static void ExtractDottedSegmentWithPath(ReadOnlySpan<char> fieldPath, out SpanSegment segment, out ReadOnly
 633    {
 3348634        var dotIndex = fieldPath.IndexOf('.');
 3348635        var isLastSegment = dotIndex == -1;
 3348636        var segmentSpan = isLastSegment ? fieldPath : fieldPath[..dotIndex];
 3348637        remainingPath = isLastSegment ? ReadOnlySpan<char>.Empty : fieldPath[(dotIndex + 1)..];
 638
 3348639        segment = new SpanSegment(segmentSpan, ReadOnlySpan<char>.Empty, isLastSegment, ReadOnlySpan<char>.Empty);
 3348640    }
 641
 642    private static SpanSegment ExtractNextSegment(ref ReadOnlySpan<char> fieldPath)
 643    {
 3654644        var nextDot = fieldPath.IndexOf('.');
 3654645        var isLastFragment = nextDot == -1;
 3654646        var currentPart = isLastFragment ? fieldPath : fieldPath[..nextDot];
 3654647        var trimmedPart = currentPart.Trim();
 648
 649        // Parse and remove type information from the segment
 3654650        var cleanedPath = Helpers.ParseFieldTypeFromPath(trimmedPart, Constants.DefaultFieldType, out var parsedType);
 651
 652        // Parse field name and alias from cleanedPath
 653        // Match original behavior: split on ':' and only handle exactly 2 non-empty parts
 3654654        var parts = cleanedPath.ToString().Split(':', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEnt
 655
 656        ReadOnlySpan<char> name, alias;
 3654657        if (parts.Length == 2)
 658        {
 1368659            alias = parts[0].AsSpan();
 1368660            name = parts[1].AsSpan();
 661        }
 662        else
 663        {
 2286664            name = cleanedPath.Trim();
 2286665            alias = ReadOnlySpan<char>.Empty;
 666        }
 667
 668        // Only include parsed type if it's not the default
 3654669        var typeToInclude = parsedType.SequenceEqual(Constants.DefaultFieldTypeSpan) ? ReadOnlySpan<char>.Empty : parsed
 3654670        var segment = new SpanSegment(name, alias, isLastFragment, typeToInclude);
 671
 3654672        fieldPath = nextDot == -1 ? ReadOnlySpan<char>.Empty : fieldPath[(nextDot + 1)..];
 673
 3654674        return segment;
 675    }
 676}

Methods/Properties

GetOrAddField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
GetOrAddField(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
GetOrAddDottedField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
GetOrAddDottedField(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ProcessDottedFieldFastPath(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>)
ProcessDottedFieldFastPath(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>)
GetOrCreateRootSegment(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.SpanSegment,System.ReadOnlySpan`1<System.Char>,System.Int32,System.ReadOnlySpan`1<System.Char>)
GetOrCreateChildSegment(NGql.Core.Abstractions.FieldDefinition,NGql.Core.Abstractions.SpanSegment,System.ReadOnlySpan`1<System.Char>,System.Int32,System.ReadOnlySpan`1<System.Char>)
PromoteToObjectIfNeeded(NGql.Core.Abstractions.FieldDefinition,System.Boolean)
ProcessDottedFieldWithMetadata(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ProcessDottedFieldWithMetadata(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ProcessDottedFieldSegments(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,NGql.Core.Builders.SpanPathBuilder&)
ProcessDottedFieldSegments(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,NGql.Core.Builders.SpanPathBuilder&)
CreateDottedFieldSegment(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Int32,System.Boolean,System.ReadOnlySpan`1<System.Char>)
ProcessDottedSegment(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.Boolean,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>)
ProcessDottedSegment(NGql.Core.Abstractions.FieldChildren,System.ReadOnlySpan`1<System.Char>,System.Boolean,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>)
MergeArgumentsIntoExistingChild(NGql.Core.Abstractions.FieldChildren,System.ReadOnlySpan`1<System.Char>,NGql.Core.Abstractions.FieldDefinition,System.Collections.Generic.IDictionary`2<System.String,System.Object>)
PromoteIntermediateChildToObject(NGql.Core.Abstractions.FieldDefinition)
CreateDottedSegmentField(System.ReadOnlySpan`1<System.Char>,System.Boolean,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>)
GetOrAddComplexField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
GetOrAddComplexField(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ProcessFieldSegment(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.SpanSegment,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ProcessFieldSegment(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.SpanSegment,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
CreateNewField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.SpanSegment,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
CreateNewField(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.SpanSegment,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ResolveSegmentArgsAndMetadata(NGql.Core.Abstractions.SpanSegment,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ResolveSegmentFieldType(NGql.Core.Abstractions.SpanSegment,System.ReadOnlySpan`1<System.Char>)
UpdateExistingField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.SpanSegment,NGql.Core.Abstractions.FieldDefinition,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>)
UpdateExistingField(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.SpanSegment,NGql.Core.Abstractions.FieldDefinition,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>)
ApplyIntermediateUpdates(NGql.Core.Abstractions.SpanSegment,NGql.Core.Abstractions.FieldDefinition)
ApplyParsedFieldType(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>)
CreateOrMergeField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.FieldDefinition)
CreateOrMergeField(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.FieldDefinition)
CloneFieldDefinitionForMerge(NGql.Core.Abstractions.FieldDefinition)
ExtractDottedSegment(System.ReadOnlySpan`1<System.Char>,System.Int32,NGql.Core.Abstractions.SpanSegment&,System.Int32&)
ExtractDottedSegmentWithPath(System.ReadOnlySpan`1<System.Char>,NGql.Core.Abstractions.SpanSegment&,System.ReadOnlySpan`1<System.Char>&)
ExtractNextSegment(System.ReadOnlySpan`1<System.Char>&)