diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..01fcc26 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,59 @@ +name: CI + +# Trigger the workflow on pushes to main branches and pull requests +on: + push: + branches: + - main + - master + - develop + pull_request: + branches: + - main + - master + - develop + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + # Step 1: Checkout the repository + - name: Checkout repository + uses: actions/checkout@v4 + + # Step 2: Setup .NET SDK + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.0.x' + + # Step 3: Restore dependencies + - name: Restore dependencies + run: dotnet restore + + # Step 4: Build the project + - name: Build + run: dotnet build --configuration Release --no-restore + + # Step 5: Run tests + - name: Test + run: dotnet test --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage" + + # Step 6: Upload test results + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: '**/TestResults/**/*' + + # Step 7: Build summary + - name: Build Summary + if: always() + run: | + echo "### Build Summary :rocket:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Status**: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY + echo "- **Branch**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/csharp.yml b/.github/workflows/csharp.yml index e26e136..cb9fbee 100644 --- a/.github/workflows/csharp.yml +++ b/.github/workflows/csharp.yml @@ -13,11 +13,11 @@ jobs: steps: # Step 1: Checkout the repository - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Step 2: Setup .NET SDK - name: Setup .NET - uses: actions/setup-dotnet@v3 + uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x' # Updated to match your project diff --git a/Foundation.Data.Doublets.Cli/AdvancedMixedQueryProcessor.cs b/Foundation.Data.Doublets.Cli/AdvancedMixedQueryProcessor.cs index a51417b..9e86362 100644 --- a/Foundation.Data.Doublets.Cli/AdvancedMixedQueryProcessor.cs +++ b/Foundation.Data.Doublets.Cli/AdvancedMixedQueryProcessor.cs @@ -24,6 +24,9 @@ public class Options public static void ProcessQuery(NamedLinksDecorator links, Options options) { + ArgumentNullException.ThrowIfNull(links); + ArgumentNullException.ThrowIfNull(options); + var query = options.Query; TraceIfEnabled(options, $"[ProcessQuery] Query: \"{query}\""); @@ -93,16 +96,18 @@ public static void ProcessQuery(NamedLinksDecorator links, Options options .ToList(); // ---------------------------------------------------------------- - // FIX: If we see restrictionLink with exactly 1 sub-link => that sub-link has 2 sub-values => no IDs => interpret as a single composite pattern + // FIX: If we see restrictionLink with exactly 1 sub-link => that sub-link has 2 sub-values => interpret as a single composite pattern + // This handles patterns like ((() (1 2))) where the outer restriction has a single composite child if ( string.IsNullOrEmpty(restrictionLink.Id) && restrictionLink.Values?.Count == 1 ) { var single = restrictionLink.Values[0]; + // Check if this is a composite (has 2 sub-values) and doesn't have a numeric/wildcard ID if ( - string.IsNullOrEmpty(single.Id) && - single.Values?.Count == 2 && !IsNumericOrStar(single.Id) + single.Values?.Count == 2 && + (string.IsNullOrEmpty(single.Id) || !IsNumericOrStar(single.Id)) ) { // Create a single composite pattern from ((1 *) (* 2)) @@ -120,7 +125,7 @@ public static void ProcessQuery(NamedLinksDecorator links, Options options restrictionInternalPatterns.Add(topLevelPattern); TraceIfEnabled(options, - "[ProcessQuery] Detected single sub-link (no ID) with 2 sub-values => replaced with one composite restriction pattern."); + "[ProcessQuery] Detected single sub-link with 2 sub-values => replaced with one composite restriction pattern."); } } // ---------------------------------------------------------------- @@ -659,9 +664,10 @@ private static uint ResolveId( { return anyConstant; } - if (TryParseLinkId(identifier, links.Constants, ref anyConstant)) + uint parsedValue = anyConstant; + if (TryParseLinkId(identifier, links.Constants, ref parsedValue)) { - return anyConstant; + return parsedValue; } // Add name resolution for deletion patterns var namedId = links.GetByName(identifier); @@ -800,10 +806,6 @@ private static void CreateOrUpdateLink(NamedLinksDecorator links, DoubletL TraceIfEnabled(options, $"[CreateOrUpdateLink] Updating link #{linkDefinition.Index}: {existingDoublet.Source}->{linkDefinition.Source}, {existingDoublet.Target}->{linkDefinition.Target}."); LinksExtensions.EnsureCreated(links, linkDefinition.Index); - options.ChangesHandler?.Invoke( - new DoubletLink(linkDefinition.Index, nullConstant, nullConstant), - new DoubletLink(linkDefinition.Index, nullConstant, nullConstant) - ); links.Update( new DoubletLink(linkDefinition.Index, anyConstant, anyConstant), linkDefinition, diff --git a/Foundation.Data.Doublets.Cli/EnumerableExtensions.cs b/Foundation.Data.Doublets.Cli/EnumerableExtensions.cs index 1651473..0b231fc 100644 --- a/Foundation.Data.Doublets.Cli/EnumerableExtensions.cs +++ b/Foundation.Data.Doublets.Cli/EnumerableExtensions.cs @@ -1,81 +1,84 @@ using System; using System.Collections.Generic; -public static class EnumerableExtensions +namespace Foundation.Data.Doublets.Cli { - public static void Deconstruct(this IEnumerable source, out T first) + public static class EnumerableExtensions { - using var enumerator = source.GetEnumerator(); - first = enumerator.MoveNext() ? enumerator.Current : default!; - } + public static void Deconstruct(this IEnumerable source, out T first) + { + using var enumerator = source.GetEnumerator(); + first = enumerator.MoveNext() ? enumerator.Current : default!; + } - public static void Deconstruct(this IEnumerable source, out T first, out T second) - { - using var enumerator = source.GetEnumerator(); - first = enumerator.MoveNext() ? enumerator.Current : default!; - second = enumerator.MoveNext() ? enumerator.Current : default!; - } + public static void Deconstruct(this IEnumerable source, out T first, out T second) + { + using var enumerator = source.GetEnumerator(); + first = enumerator.MoveNext() ? enumerator.Current : default!; + second = enumerator.MoveNext() ? enumerator.Current : default!; + } - public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third) - { - using var enumerator = source.GetEnumerator(); - first = enumerator.MoveNext() ? enumerator.Current : default!; - second = enumerator.MoveNext() ? enumerator.Current : default!; - third = enumerator.MoveNext() ? enumerator.Current : default!; - } + public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third) + { + using var enumerator = source.GetEnumerator(); + first = enumerator.MoveNext() ? enumerator.Current : default!; + second = enumerator.MoveNext() ? enumerator.Current : default!; + third = enumerator.MoveNext() ? enumerator.Current : default!; + } - public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out T fourth) - { - using var enumerator = source.GetEnumerator(); - first = enumerator.MoveNext() ? enumerator.Current : default!; - second = enumerator.MoveNext() ? enumerator.Current : default!; - third = enumerator.MoveNext() ? enumerator.Current : default!; - fourth = enumerator.MoveNext() ? enumerator.Current : default!; - } + public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out T fourth) + { + using var enumerator = source.GetEnumerator(); + first = enumerator.MoveNext() ? enumerator.Current : default!; + second = enumerator.MoveNext() ? enumerator.Current : default!; + third = enumerator.MoveNext() ? enumerator.Current : default!; + fourth = enumerator.MoveNext() ? enumerator.Current : default!; + } - public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out T fourth, out T fifth) - { - using var enumerator = source.GetEnumerator(); - first = enumerator.MoveNext() ? enumerator.Current : default!; - second = enumerator.MoveNext() ? enumerator.Current : default!; - third = enumerator.MoveNext() ? enumerator.Current : default!; - fourth = enumerator.MoveNext() ? enumerator.Current : default!; - fifth = enumerator.MoveNext() ? enumerator.Current : default!; - } + public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out T fourth, out T fifth) + { + using var enumerator = source.GetEnumerator(); + first = enumerator.MoveNext() ? enumerator.Current : default!; + second = enumerator.MoveNext() ? enumerator.Current : default!; + third = enumerator.MoveNext() ? enumerator.Current : default!; + fourth = enumerator.MoveNext() ? enumerator.Current : default!; + fifth = enumerator.MoveNext() ? enumerator.Current : default!; + } - public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out T fourth, out T fifth, out T sixth) - { - using var enumerator = source.GetEnumerator(); - first = enumerator.MoveNext() ? enumerator.Current : default!; - second = enumerator.MoveNext() ? enumerator.Current : default!; - third = enumerator.MoveNext() ? enumerator.Current : default!; - fourth = enumerator.MoveNext() ? enumerator.Current : default!; - fifth = enumerator.MoveNext() ? enumerator.Current : default!; - sixth = enumerator.MoveNext() ? enumerator.Current : default!; - } + public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out T fourth, out T fifth, out T sixth) + { + using var enumerator = source.GetEnumerator(); + first = enumerator.MoveNext() ? enumerator.Current : default!; + second = enumerator.MoveNext() ? enumerator.Current : default!; + third = enumerator.MoveNext() ? enumerator.Current : default!; + fourth = enumerator.MoveNext() ? enumerator.Current : default!; + fifth = enumerator.MoveNext() ? enumerator.Current : default!; + sixth = enumerator.MoveNext() ? enumerator.Current : default!; + } - public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out T fourth, out T fifth, out T sixth, out T seventh) - { - using var enumerator = source.GetEnumerator(); - first = enumerator.MoveNext() ? enumerator.Current : default!; - second = enumerator.MoveNext() ? enumerator.Current : default!; - third = enumerator.MoveNext() ? enumerator.Current : default!; - fourth = enumerator.MoveNext() ? enumerator.Current : default!; - fifth = enumerator.MoveNext() ? enumerator.Current : default!; - sixth = enumerator.MoveNext() ? enumerator.Current : default!; - seventh = enumerator.MoveNext() ? enumerator.Current : default!; - } + public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out T fourth, out T fifth, out T sixth, out T seventh) + { + using var enumerator = source.GetEnumerator(); + first = enumerator.MoveNext() ? enumerator.Current : default!; + second = enumerator.MoveNext() ? enumerator.Current : default!; + third = enumerator.MoveNext() ? enumerator.Current : default!; + fourth = enumerator.MoveNext() ? enumerator.Current : default!; + fifth = enumerator.MoveNext() ? enumerator.Current : default!; + sixth = enumerator.MoveNext() ? enumerator.Current : default!; + seventh = enumerator.MoveNext() ? enumerator.Current : default!; + } - public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out T fourth, out T fifth, out T sixth, out T seventh, out T eighth) - { - using var enumerator = source.GetEnumerator(); - first = enumerator.MoveNext() ? enumerator.Current : default!; - second = enumerator.MoveNext() ? enumerator.Current : default!; - third = enumerator.MoveNext() ? enumerator.Current : default!; - fourth = enumerator.MoveNext() ? enumerator.Current : default!; - fifth = enumerator.MoveNext() ? enumerator.Current : default!; - sixth = enumerator.MoveNext() ? enumerator.Current : default!; - seventh = enumerator.MoveNext() ? enumerator.Current : default!; - eighth = enumerator.MoveNext() ? enumerator.Current : default!; + public static void Deconstruct(this IEnumerable source, out T first, out T second, out T third, out T fourth, out T fifth, out T sixth, out T seventh, out T eighth) + { + using var enumerator = source.GetEnumerator(); + first = enumerator.MoveNext() ? enumerator.Current : default!; + second = enumerator.MoveNext() ? enumerator.Current : default!; + third = enumerator.MoveNext() ? enumerator.Current : default!; + fourth = enumerator.MoveNext() ? enumerator.Current : default!; + fifth = enumerator.MoveNext() ? enumerator.Current : default!; + sixth = enumerator.MoveNext() ? enumerator.Current : default!; + seventh = enumerator.MoveNext() ? enumerator.Current : default!; + eighth = enumerator.MoveNext() ? enumerator.Current : default!; + } } } \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/Exceptions.cs b/Foundation.Data.Doublets.Cli/Exceptions.cs new file mode 100644 index 0000000..2732cfa --- /dev/null +++ b/Foundation.Data.Doublets.Cli/Exceptions.cs @@ -0,0 +1,49 @@ +using System; + +namespace Foundation.Data.Doublets.Cli +{ + /// + /// Exception thrown when a link has an invalid format or structure. + /// + public class InvalidLinkFormatException : Exception + { + public InvalidLinkFormatException(string message) : base(message) + { + } + + public InvalidLinkFormatException(string message, Exception innerException) + : base(message, innerException) + { + } + } + + /// + /// Exception thrown when a link structure doesn't match expected patterns. + /// + public class UnexpectedLinkStructureException : InvalidOperationException + { + public UnexpectedLinkStructureException(string message) : base(message) + { + } + + public UnexpectedLinkStructureException(string message, Exception innerException) + : base(message, innerException) + { + } + } + + /// + /// Exception thrown when a query pattern is invalid or cannot be processed. + /// + public class InvalidQueryPatternException : Exception + { + public InvalidQueryPatternException(string message) : base(message) + { + } + + public InvalidQueryPatternException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/Foundation.Data.Doublets.Cli/ILinksUnrestricted.cs b/Foundation.Data.Doublets.Cli/ILinksUnrestricted.cs deleted file mode 100644 index 4638292..0000000 --- a/Foundation.Data.Doublets.Cli/ILinksUnrestricted.cs +++ /dev/null @@ -1,130 +0,0 @@ -namespace Foundation.Data.Doublets.Cli -{ - // TODO: support ILinksUnrestricted and TConstants - - // An unrestricted version of the ILinks interface, without the IUnsignedNumber constraint - public interface ILinksUnrestricted - { - } - - // An unrestricted version of the ILinks interface, without the IUnsignedNumber or LinksConstants constraints - public interface ILinksUnrestricted - { - #region Constants - - /// - /// Returns the set of constants that is necessary for effective communication with the methods of this interface. - /// Возвращает набор констант, который необходим для эффективной коммуникации с методами этого интерфейса. - /// - /// - /// These constants are not changed since the creation of the links storage access point. - /// Эти константы не меняются с момента создания точки доступа к хранилищу связей. - /// - TConstants Constants { get; } - - #endregion - - #region Read - - /// - /// Counts and returns the total number of links in the storage that meet the specified restriction. - /// Подсчитывает и возвращает общее число связей находящихся в хранилище, соответствующих указанному ограничению. - /// - /// Restriction on the contents of links.Ограничение на содержимое связей. - /// The total number of links in the storage that meet the specified restriction.Общее число связей находящихся в хранилище, соответствующих указанному ограничению. - TLinkAddress Count(IList? restriction); - - /// - /// Passes through all the links matching the pattern, invoking a handler for each matching link. - /// Выполняет проход по всем связям, соответствующим шаблону, вызывая обработчик (handler) для каждой подходящей связи. - /// - /// - /// Restriction on the contents of links. Each constraint can have values: Constants.Null - the 0th link denoting a reference to the void, Any - the absence of a constraint, 1..∞ a specific link index. - /// Ограничение на содержимое связей. Каждое ограничение может иметь значения: Constants.Null - 0-я связь, обозначающая ссылку на пустоту, Any - отсутствие ограничения, 1..∞ конкретный индекс связи. - /// - /// A handler for each matching link.Обработчик для каждой подходящей связи. - /// Constants.Continue, if the pass through the links was not interrupted, and Constants.Break otherwise.Constants.Continue, в случае если проход по связям не был прерван и Constants.Break в обратном случае. - TLinkAddress Each(IList? restriction, Platform.Delegates.ReadHandler? handler); - - #endregion - - #region Write - - /// - /// Creates a link. - /// Создаёт связь. - /// - /// The content of a new link. This argument is optional, if the null passed as value that means no content of a link is set. - /// Содержимое новой связи. Этот аргумент опционален, если null передан в качестве значения это означает, что никакого содержимого для связи не установлено. - /// - /// - /// A function to handle each executed change. This function can use Constants.Continue to continue proccess each change. Constants.Break can be used to stop receiving of executed changes. - /// Функция для обработки каждого выполненного изменения. Эта функция может использовать Constants.Continue чтобы продолжить обрабатывать каждое изменение. Constants.Break может быть использована для остановки получения выполненных изменений. - /// - /// - /// - /// - /// Constants.Continue if all executed changes are handled. - /// Constants.Break if proccessing of handled changes is stoped. - /// - /// - /// Constants.Continue если все выполненные изменения обработаны. - /// Constants.Break если обработака выполненных изменений остановлена. - /// - /// - TLinkAddress Create(IList? substitution, Platform.Delegates.WriteHandler? handler); - - /// - /// Обновляет связь с указанными restriction[Constants.IndexPart] в адресом связи - /// на связь с указанным новым содержимым. - /// - /// - /// Ограничение на содержимое связей. - /// Предполагается, что будет указан индекс связи (в restriction[Constants.IndexPart]) и далее за ним будет следовать содержимое связи. - /// Каждое ограничение может иметь значения: Constants.Null - 0-я связь, обозначающая ссылку на пустоту, - /// Constants.Itself - требование установить ссылку на себя, 1..∞ конкретный индекс другой связи. - /// - /// - /// - /// A function to handle each executed change. This function can use Constants.Continue to continue proccess each change. Constants.Break can be used to stop receiving of executed changes. - /// Функция для обработки каждого выполненного изменения. Эта функция может использовать Constants.Continue чтобы продолжить обрабатывать каждое изменение. Constants.Break может быть использована для остановки получения выполненных изменений. - /// - /// - /// - /// Constants.Continue if all executed changes are handled. - /// Constants.Break if proccessing of handled changes is stoped. - /// - /// - /// Constants.Continue если все выполненные изменения обработаны. - /// Constants.Break если обработака выполненных изменений остановлена. - /// - /// - TLinkAddress Update(IList? restriction, IList? substitution, Platform.Delegates.WriteHandler? handler); - - /// - /// Deletes links that match the specified restriction. - /// Удаляет связи соответствующие указанному ограничению. - /// - /// - /// Restriction on the content of a link. This argument is optional, if the null passed as value that means no restriction on the content of a link are set. - /// Ограничение на содержимое связи. Этот аргумент опционален, если null передан в качестве значения это означает, что никаких ограничений на содержимое связи не установлено. - /// - /// - /// A function to handle each executed change. This function can use Constants.Continue to continue proccess each change. Constants.Break can be used to stop receiving of executed changes. - /// Функция для обработки каждого выполненного изменения. Эта функция может использовать Constants.Continue чтобы продолжить обрабатывать каждое изменение. Constants.Break может быть использована для остановки получения выполненных изменений. - /// - /// - /// - /// Constants.Continue if all executed changes are handled. - /// Constants.Break if proccessing of handled changes is stoped. - /// - /// - /// Constants.Continue если все выполненные изменения обработаны. - /// Constants.Break если обработака выполненных изменений остановлена. - /// - /// - TLinkAddress Delete(IList? restriction, Platform.Delegates.WriteHandler? handler); - - #endregion - } -} diff --git a/Foundation.Data.Doublets.Cli/LinksExtensions.cs b/Foundation.Data.Doublets.Cli/LinksExtensions.cs index c1fda7c..f190787 100644 --- a/Foundation.Data.Doublets.Cli/LinksExtensions.cs +++ b/Foundation.Data.Doublets.Cli/LinksExtensions.cs @@ -11,13 +11,15 @@ public static class LinksExtensions public static void EnsureCreated(this ILinks links, Func creator, params TLinkAddress[] addresses) where TLinkAddress : IUnsignedNumber { - var addressToUInt64Converter = CheckedConverter.Default; - var uInt64ToAddressConverter = CheckedConverter.Default; var nonExistentAddresses = new HashSet(addresses.Where(x => !links.Exists(x))); if (nonExistentAddresses?.Count > 0) { var max = nonExistentAddresses.Max()!; - max = uInt64ToAddressConverter.Convert(TLinkAddress.CreateTruncating(Math.Min(ulong.CreateTruncating(max), ulong.CreateTruncating(links.Constants.InternalReferencesRange.Maximum)))); + // Ensure max doesn't exceed the maximum internal reference + var maxUlong = ulong.CreateTruncating(max); + var internalMaxUlong = ulong.CreateTruncating(links.Constants.InternalReferencesRange.Maximum); + max = TLinkAddress.CreateTruncating(Math.Min(maxUlong, internalMaxUlong)); + var createdLinks = new List(); TLinkAddress createdLink; do diff --git a/Foundation.Data.Doublets.Cli/MixedQueryProcessor.cs b/Foundation.Data.Doublets.Cli/MixedQueryProcessor.cs index 1bbfabb..d74c999 100644 --- a/Foundation.Data.Doublets.Cli/MixedQueryProcessor.cs +++ b/Foundation.Data.Doublets.Cli/MixedQueryProcessor.cs @@ -20,6 +20,9 @@ public class Options public static void ProcessQuery(ILinks links, Options options) { + ArgumentNullException.ThrowIfNull(links); + ArgumentNullException.ThrowIfNull(options); + var query = options.Query; var @null = links.Constants.Null; var any = links.Constants.Any; @@ -316,20 +319,32 @@ static DoubletLink ToDoubletLink(ILinks links, LinoLink linoLink, uint def return new DoubletLink(index, source, target); } - static void TryParseLinkId(string? id, LinksConstants constants, ref uint parsedValue) + static bool TryParseLinkId(string? id, LinksConstants constants, ref uint parsedValue) { if (string.IsNullOrEmpty(id)) { - return; + return false; } if (id == "*") { parsedValue = constants.Any; + return true; + } + else if (id.EndsWith(":")) + { + var trimmed = id.TrimEnd(':'); + if (uint.TryParse(trimmed, out uint linkId)) + { + parsedValue = linkId; + return true; + } } else if (uint.TryParse(id, out uint linkId)) { parsedValue = linkId; + return true; } + return false; } } } \ No newline at end of file diff --git a/Foundation.Data.Doublets.Cli/NamedLinks.cs b/Foundation.Data.Doublets.Cli/NamedLinks.cs index 819a6cd..6b6e916 100644 --- a/Foundation.Data.Doublets.Cli/NamedLinks.cs +++ b/Foundation.Data.Doublets.Cli/NamedLinks.cs @@ -103,15 +103,18 @@ public void RemoveName(TLinkAddress link) var nameCandidate = _links.GetTarget(nameCandidatePair); if (_links.GetSource(nameCandidate).Equals(_nameType)) { - // Remove the name link - _links.Delete(nameCandidatePair); - // Remove the nameType->nameSequence link if not used elsewhere var nameSequence = _links.GetTarget(nameCandidate); var nameTypeToNameSequenceLink = nameCandidate; - // Check if this nameType->nameSequence is used elsewhere - var queryNameType = new Link(any, _nameType, nameSequence); - var linksToNameSequence = _links.All(queryNameType).ToList(); - if (linksToNameSequence.Count == 1) // only this one exists + + // Check if this nameType->nameSequence is used elsewhere BEFORE deleting + var queryNameType = new Link(any, any, nameTypeToNameSequenceLink); + var usagesOfNameCandidate = _links.All(queryNameType).ToList(); + + // Remove the name link (link->nameCandidate) + _links.Delete(nameCandidatePair); + + // Remove the nameType->nameSequence link if this was the only usage + if (usagesOfNameCandidate.Count == 1) { _links.Delete(nameTypeToNameSequenceLink); } @@ -122,20 +125,7 @@ public void RemoveName(TLinkAddress link) public void RemoveNameByExternalReference(TLinkAddress externalReference) { var reference = new Hybrid(externalReference, isExternal: true); - // Get the name for this external reference - var name = GetName(reference); - if (name != null) - { - // Remove the mapping from name to external reference - var nameSequence = _createString(name); - var nameTypeToNameSequenceLink = _links.SearchOrDefault(_nameType, nameSequence); - if (!nameTypeToNameSequenceLink.Equals(_links.Constants.Null)) - { - // Remove the nameType->nameSequence link if it exists - _links.Delete(nameTypeToNameSequenceLink); - } - } - // Remove the name link (externalRef->nameType->nameSequence) + // RemoveName will handle all deletion logic including the nameType->nameSequence link RemoveName(reference); } } diff --git a/Foundation.Data.Doublets.Cli/NamedLinksDecorator.cs b/Foundation.Data.Doublets.Cli/NamedLinksDecorator.cs index 4c2d3be..251a4cb 100644 --- a/Foundation.Data.Doublets.Cli/NamedLinksDecorator.cs +++ b/Foundation.Data.Doublets.Cli/NamedLinksDecorator.cs @@ -56,6 +56,11 @@ public NamedLinksDecorator(string databaseFilename, bool tracingEnabled = false) { } + /// + /// Gets the name associated with the specified link address. + /// + /// The link address to get the name for. + /// The name associated with the link, or null if no name is set. public string? GetName(TLinkAddress link) { if (_tracingEnabled) Console.WriteLine($"[Trace] GetName called for link: {link}"); @@ -64,6 +69,12 @@ public NamedLinksDecorator(string databaseFilename, bool tracingEnabled = false) return result; } + /// + /// Sets the name for the specified link address. + /// + /// The link address to name. + /// The name to assign to the link. + /// The link address representing the name assignment. public TLinkAddress SetName(TLinkAddress link, string name) { if (_tracingEnabled) Console.WriteLine($"[Trace] SetName called for link: {link} with name: '{name}'"); @@ -74,6 +85,11 @@ public TLinkAddress SetName(TLinkAddress link, string name) return result; } + /// + /// Gets the link address associated with the specified name. + /// + /// The name to look up. + /// The link address associated with the name, or Null if not found. public TLinkAddress GetByName(string name) { if (_tracingEnabled) Console.WriteLine($"[Trace] GetByName called for name: '{name}'"); @@ -82,6 +98,10 @@ public TLinkAddress GetByName(string name) return result; } + /// + /// Removes the name association for the specified link address. + /// + /// The link address whose name should be removed. public void RemoveName(TLinkAddress link) { if (_tracingEnabled) Console.WriteLine($"[Trace] RemoveName called for link: {link}"); diff --git a/Foundation.Data.Doublets.Cli/PinnedTypes.cs b/Foundation.Data.Doublets.Cli/PinnedTypes.cs index 591de18..494392b 100644 --- a/Foundation.Data.Doublets.Cli/PinnedTypes.cs +++ b/Foundation.Data.Doublets.Cli/PinnedTypes.cs @@ -29,15 +29,18 @@ IEnumerator IEnumerable.GetEnumerator() // Private custom enumerator private class PinnedTypesEnumerator : IEnumerator { + private const int MaxPinnedTypes = 6; // Type, UnicodeSymbolType, UnicodeSequenceType, StringType, EmptyStringType, NameType private readonly ILinks _links; private readonly TLinkAddress _initialSource; private TLinkAddress _currentAddress; + private int _count; public PinnedTypesEnumerator(ILinks links) { _links = links; _initialSource = TLinkAddress.One; _currentAddress = TLinkAddress.One; // Start with the first address + _count = 0; } public TLinkAddress Current { get; private set; } @@ -46,6 +49,12 @@ public PinnedTypesEnumerator(ILinks links) public bool MoveNext() { + // Stop after creating/verifying MaxPinnedTypes + if (_count >= MaxPinnedTypes) + { + return false; + } + if (_links.Exists(_currentAddress)) { var link = new Link(_links.GetLink(_currentAddress)); @@ -58,7 +67,7 @@ public bool MoveNext() else { // Link exists but does not match the expected structure - throw new InvalidOperationException($"Unexpected link found at address {_currentAddress}. Expected: {expectedLink}, Found: {link}."); + throw new UnexpectedLinkStructureException($"Unexpected link found at address {_currentAddress}. Expected: {expectedLink}, Found: {link}."); } } else @@ -69,6 +78,7 @@ public bool MoveNext() // Increment the current address for the next type _currentAddress++; + _count++; return true; } @@ -76,6 +86,7 @@ public bool MoveNext() public void Reset() { _currentAddress = TLinkAddress.One; + _count = 0; } public void Dispose() diff --git a/Foundation.Data.Doublets.Cli/QueryConstants.cs b/Foundation.Data.Doublets.Cli/QueryConstants.cs new file mode 100644 index 0000000..745b2fd --- /dev/null +++ b/Foundation.Data.Doublets.Cli/QueryConstants.cs @@ -0,0 +1,23 @@ +namespace Foundation.Data.Doublets.Cli +{ + /// + /// Constants used in query processing for pattern matching and link notation. + /// + public static class QueryConstants + { + /// + /// Prefix for variable identifiers (e.g., "$variable"). + /// + public const string VariablePrefix = "$"; + + /// + /// Symbol representing a wildcard match (matches any value). + /// + public const string WildcardSymbol = "*"; + + /// + /// Suffix for explicit link index notation (e.g., "123:"). + /// + public const string IndexSuffix = ":"; + } +} diff --git a/Foundation.Data.Doublets.Cli/SimpleLinksDecorator.cs b/Foundation.Data.Doublets.Cli/SimpleLinksDecorator.cs index 5fe1fd1..fd7a427 100644 --- a/Foundation.Data.Doublets.Cli/SimpleLinksDecorator.cs +++ b/Foundation.Data.Doublets.Cli/SimpleLinksDecorator.cs @@ -40,7 +40,7 @@ public static string MakeNamesDatabaseFilename(string databaseFilename) public SimpleLinksDecorator(ILinks links, string namesDatabaseFilename, bool tracingEnabled = false) : base(links) { _tracingEnabled = tracingEnabled; - if (_tracingEnabled) Console.WriteLine($"[Trace] Constructing NamedLinksDecorator with names DB: {namesDatabaseFilename}"); + if (_tracingEnabled) Console.WriteLine($"[Trace] Constructing SimpleLinksDecorator with names DB: {namesDatabaseFilename}"); var namesConstants = new LinksConstants(enableExternalReferencesSupport: true); var namesMemory = new FileMappedResizableDirectMemory(namesDatabaseFilename, UnitedMemoryLinks.DefaultLinksSizeStep); var namesLinks = new UnitedMemoryLinks(namesMemory, UnitedMemoryLinks.DefaultLinksSizeStep, namesConstants, IndexTreeType.Default); diff --git a/Foundation.Data.Doublets.Cli/UnicodeStringStorage.cs b/Foundation.Data.Doublets.Cli/UnicodeStringStorage.cs index ef8fc6f..4b22103 100644 --- a/Foundation.Data.Doublets.Cli/UnicodeStringStorage.cs +++ b/Foundation.Data.Doublets.Cli/UnicodeStringStorage.cs @@ -147,7 +147,7 @@ public string GetString(TLinkAddress stringValue) } current = Links.GetTarget(current); } - throw new Exception("The passed link does not contain a string."); + throw new InvalidLinkFormatException("The passed link does not contain a string."); } } } \ No newline at end of file