diff --git a/.github/workflows/dotnet-ci.yml b/.github/workflows/dotnet-ci.yml
index 6cec436..2f4bbd0 100644
--- a/.github/workflows/dotnet-ci.yml
+++ b/.github/workflows/dotnet-ci.yml
@@ -9,7 +9,7 @@ jobs:
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v1
with:
- dotnet-version: '6.0.x'
+ dotnet-version: '7.x'
include-prerelease: true
- name: Restore
working-directory: ./code/
@@ -22,7 +22,7 @@ jobs:
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v1
with:
- dotnet-version: '6.0.x'
+ dotnet-version: '7.x'
include-prerelease: true
- name: Build
working-directory: ./code/
diff --git a/VERSION b/VERSION
index bf7112c..ed50cc5 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.4.0
\ No newline at end of file
+2.0.0
\ No newline at end of file
diff --git a/code/DarkMatter/DarkMatter.csproj b/code/DarkMatter/DarkMatter.csproj
index 13b08bb..64303cf 100644
--- a/code/DarkMatter/DarkMatter.csproj
+++ b/code/DarkMatter/DarkMatter.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net7.0
enable
disable
diff --git a/code/DarkMatter/Program.cs b/code/DarkMatter/Program.cs
index 5fd4f5e..65a72c0 100644
--- a/code/DarkMatter/Program.cs
+++ b/code/DarkMatter/Program.cs
@@ -31,10 +31,18 @@
// actual use of UniverseQuery
(Gravity g, IList T) = await galaxy.Paged(
page: new Q.Page(50),
- catalysts: new List
+ clusters: new List()
{
- new(nameof(MyObject.Links), "", Operator: Q.Operator.In),
- new(nameof(MyObject.Code), "", Where: Q.Where.Or)
+ new(Catalysts: new List
+ {
+ new(nameof(MyObject.Links), "", Operator: Q.Operator.In),
+ new(nameof(MyObject.Code), "", Where: Q.Where.Or)
+ }, Where: Q.Where.And),
+ new(Catalysts: new List
+ {
+ new(nameof(MyObject.Name), Operator: Q.Operator.Defined),
+ new(nameof(MyObject.Description), Operator: Q.Operator.Defined)
+ }, Where: Q.Where.And)
},
columnOptions: new(
Names: new List
@@ -67,8 +75,7 @@ class MyObject : ICosmicEntity
public DateTime AddedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
- [JsonIgnore]
- public string PartitionKey => Code;
+ [JsonIgnore] public string PartitionKey => Code;
public string Code { get; set; }
diff --git a/code/Universe/Galaxy.cs b/code/Universe/Galaxy.cs
index f3eb587..5407324 100644
--- a/code/Universe/Galaxy.cs
+++ b/code/Universe/Galaxy.cs
@@ -4,13 +4,13 @@
namespace Universe;
/// Inherit repositories to implement Universe
-public abstract class Galaxy : IDisposable, IGalaxy where T : ICosmicEntity
+public abstract class Galaxy : IDisposable, IGalaxy where T : class, ICosmicEntity
{
- private readonly Container Container;
- private bool DisposedValue;
+ private readonly Container _container;
+ private bool _disposedValue;
- private readonly bool RecordQuery;
- private readonly bool AllowBulk;
+ private readonly bool _recordQuery;
+ private readonly bool _allowBulk;
///
protected Galaxy(CosmosClient client, string database, string container, string partitionKey, bool recordQueries = false)
@@ -18,39 +18,31 @@ protected Galaxy(CosmosClient client, string database, string container, string
if (string.IsNullOrWhiteSpace(container) || string.IsNullOrWhiteSpace(partitionKey))
throw new UniverseException("Container name and PartitionKey are required");
- RecordQuery = recordQueries;
+ _recordQuery = recordQueries;
if (client.ClientOptions is not null)
- AllowBulk = client.ClientOptions.AllowBulkExecution;
- Container = client.GetDatabase(database).CreateContainerIfNotExistsAsync(container, partitionKey).GetAwaiter().GetResult();
+ _allowBulk = client.ClientOptions.AllowBulkExecution;
+ _container = client.GetDatabase(database).CreateContainerIfNotExistsAsync(container, partitionKey).GetAwaiter().GetResult();
}
- private static QueryDefinition CreateQuery(IList catalysts, ColumnOptions? columnOptions = null, IList sorting = null, IList groups = null)
+ private static QueryDefinition CreateQuery(IList clusters, ColumnOptions? columnOptions = null, IList sorting = null, IList groups = null)
{
- // Validate Catalysts
- if (catalysts is not null && catalysts.Any() && catalysts.Any(c => c.RuleViolations().Any()))
- {
- List> violationsPerCatalyst = catalysts.Select(c => c.RuleViolations()).ToList();
- List violations = violationsPerCatalyst.SelectMany(v => v).ToList().Distinct().ToList();
- throw new UniverseException(string.Join(Environment.NewLine, violations));
- }
-
// Column Options Builder
string columnsInQuery = "*";
if (columnOptions is not null)
{
- if (columnOptions?.Names is not null && columnOptions?.Names.Count > 0)
- columnsInQuery = string.Join(", ", columnOptions?.Names.Select(c => $"c.{c}").ToList());
+ if (columnOptions.Value.Names is not null && columnOptions.Value.Names.Count > 0)
+ columnsInQuery = string.Join(", ", columnOptions.Value.Names.Select(c => $"c.{c}").ToList());
- if ((columnOptions?.Top ?? 0) > 0)
- columnsInQuery = $"TOP {columnOptions?.Top ?? 1} {columnsInQuery}";
+ if ((columnOptions.Value.Top) > 0)
+ columnsInQuery = $"TOP {columnOptions.Value.Top} {columnsInQuery}";
- if (columnOptions?.IsDistinct ?? false)
+ if (columnOptions.Value.IsDistinct)
columnsInQuery = $"DISTINCT {columnsInQuery}";
- if (columnOptions?.Count ?? false)
+ if (columnOptions.Value.Count)
{
groups ??= new List();
- groups = groups.Concat(columnOptions?.Names ?? new List()).Distinct().ToList();
+ groups = groups.Concat(columnOptions.Value.Names ?? new List()).Distinct().ToList();
columnsInQuery = $"{columnsInQuery}, COUNT(1) Count";
}
}
@@ -60,16 +52,43 @@ private static QueryDefinition CreateQuery(IList catalysts, ColumnOpti
throw new UniverseException("ORDER BY is not supported in presence of GROUP BY");
// Update Columns Builder with Group By
- if (columnsInQuery.Contains("*") && groups is not null && groups.Any())
- columnsInQuery.Replace("*", string.Join(", ", groups.Select(c => $"c.{c}").ToList()));
+ if (columnsInQuery.Contains('*') && groups is not null && groups.Any())
+ _ = columnsInQuery.Replace("*", string.Join(", ", groups.Select(c => $"c.{c}").ToList()));
- // Where Clause Builder
StringBuilder queryBuilder = new($"SELECT {columnsInQuery} FROM c");
- if (catalysts.Any())
+
+ // Validate Clusters
+ if (clusters is not null && clusters.Any(c => c.Catalysts is null || !c.Catalysts.Any()))
+ throw new UniverseException("Catalysts inside of a Cluster must not be null or empty.");
+
+ // Construct Where Clause by Clusters
+ if (clusters is not null)
{
- queryBuilder.Append($" WHERE {WhereClauseBuilder(catalysts[0])}");
- foreach (Catalyst catalyst in catalysts.Where(p => p.Column != catalysts[0].Column).ToList())
- queryBuilder.Append($" {catalyst.Where.Value()} {WhereClauseBuilder(catalyst)}");
+ foreach (Cluster cluster in clusters)
+ {
+ // Validate Catalysts
+ if (cluster.Catalysts.Any(c => c.RuleViolations().Any()))
+ {
+ List> violationsPerCatalyst = cluster.Catalysts.Select(c => c.RuleViolations()).ToList();
+ List violations = violationsPerCatalyst.SelectMany(v => v).ToList().Distinct().ToList();
+ throw new UniverseException(string.Join(Environment.NewLine, violations));
+ }
+
+ // Add the where statement if not yet present
+ if (clusters.IndexOf(cluster) == 0)
+ queryBuilder.Append(" WHERE (");
+ else queryBuilder.Append($" {cluster.Where.Value()} (");
+
+ // Where Clause Builder
+ foreach (Catalyst catalyst in cluster.Catalysts)
+ {
+ if (cluster.Catalysts.IndexOf(catalyst) == 0)
+ queryBuilder.Append(WhereClauseBuilder(catalyst));
+ else queryBuilder.Append($" {catalyst.Where.Value()} {WhereClauseBuilder(catalyst)}");
+ }
+
+ queryBuilder.Append(')');
+ }
}
// Sorting Builder
@@ -90,12 +109,13 @@ private static QueryDefinition CreateQuery(IList catalysts, ColumnOpti
// Parameters Builder
QueryDefinition query = new(queryBuilder.ToString());
- if (!catalysts.Any())
- return query;
- query = query.WithParameter($"@{catalysts[0].ParameterName()}", catalysts[0].Value);
- foreach (Catalyst catalyst in catalysts.Where(p => p.Column != catalysts[0].Column).ToList())
- query = query.WithParameter($"@{catalyst.ParameterName()}", catalyst.Value);
+ if (clusters is not null && clusters.Any())
+ {
+ query = clusters.SelectMany(cluster => cluster.Catalysts)
+ .Aggregate(query, (current, catalyst) =>
+ current.WithParameter($"@{catalyst.ParameterName()}", catalyst.Value));
+ }
return query;
@@ -115,37 +135,30 @@ private static QueryDefinition CreateQuery(IList catalysts, ColumnOpti
model.id = Guid.NewGuid().ToString();
model.AddedOn = DateTime.UtcNow;
- ItemResponse response = await Container.CreateItemAsync(model, new PartitionKey(model.PartitionKey));
+ ItemResponse response = await _container.CreateItemAsync(model, new PartitionKey(model.PartitionKey));
return (new(response.RequestCharge, null), model.id);
}
async Task IGalaxy.Create(IList models)
{
- try
- {
- if (!AllowBulk)
- throw new UniverseException("Bulk create of documents is not configured properly.");
-
- Gravity gravity = new(0, string.Empty);
- List tasks = new(models.Count);
+ if (!_allowBulk)
+ throw new UniverseException("Bulk create of documents is not configured properly.");
- foreach (T model in models)
- {
- if (string.IsNullOrWhiteSpace(model.id))
- model.id = Guid.NewGuid().ToString();
- model.AddedOn = DateTime.UtcNow;
+ Gravity gravity = new(0, string.Empty);
+ List tasks = new(models.Count);
- tasks.Add(Container.CreateItemAsync(model, new PartitionKey(model.PartitionKey))
- .ContinueWith(response => gravity = new(gravity.RU + response.Result.RequestCharge, string.Empty)));
- }
-
- await Task.WhenAll(tasks);
- return gravity;
- }
- catch
+ foreach (T model in models)
{
- throw;
+ if (string.IsNullOrWhiteSpace(model.id))
+ model.id = Guid.NewGuid().ToString();
+ model.AddedOn = DateTime.UtcNow;
+
+ tasks.Add(_container.CreateItemAsync(model, new PartitionKey(model.PartitionKey))
+ .ContinueWith(response => gravity = new(gravity.RU + response.Result.RequestCharge, string.Empty)));
}
+
+ await Task.WhenAll(tasks);
+ return gravity;
}
async Task<(Gravity, T)> IGalaxy.Modify(T model)
@@ -154,7 +167,7 @@ async Task IGalaxy.Create(IList models)
{
model.ModifiedOn = DateTime.UtcNow;
- ItemResponse response = await Container.ReplaceItemAsync(model, model.id, new PartitionKey(model.PartitionKey));
+ ItemResponse response = await _container.ReplaceItemAsync(model, model.id, new PartitionKey(model.PartitionKey));
return (new(response.RequestCharge, null), response.Resource);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
@@ -171,7 +184,7 @@ async Task IGalaxy.Modify(IList models)
{
try
{
- if (!AllowBulk)
+ if (!_allowBulk)
throw new UniverseException("Bulk modify of documents is not configured properly.");
Gravity gravity = new(0, string.Empty);
@@ -181,11 +194,11 @@ async Task IGalaxy.Modify(IList models)
{
model.ModifiedOn = DateTime.UtcNow;
- tasks.Add(Container.ReplaceItemAsync(model, model.id, new PartitionKey(model.PartitionKey))
+ tasks.Add(_container.ReplaceItemAsync(model, model.id, new PartitionKey(model.PartitionKey))
.ContinueWith(response =>
{
if (!response.IsCompletedSuccessfully)
- throw new UniverseException(response.Exception.Flatten().InnerException.Message);
+ throw new UniverseException(response.Exception?.Flatten().InnerException?.Message ?? "Oops! Something went wrong!");
gravity = new(gravity.RU + response.Result.RequestCharge, string.Empty);
}));
@@ -194,7 +207,11 @@ async Task IGalaxy.Modify(IList models)
await Task.WhenAll(tasks);
return gravity;
}
- catch
+ catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
+ {
+ throw new UniverseException($"Something went wrong doing the bulk operation. See error: {ex.Message}");
+ }
+ catch (CosmosException ex) when (ex.StatusCode != HttpStatusCode.NotFound)
{
throw;
}
@@ -204,7 +221,7 @@ async Task IGalaxy.Remove(string id, string partitionKey)
{
try
{
- ItemResponse response = await Container.DeleteItemAsync(id, new PartitionKey(partitionKey));
+ ItemResponse response = await _container.DeleteItemAsync(id, new PartitionKey(partitionKey));
return new(response.RequestCharge, null);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
@@ -221,7 +238,7 @@ async Task IGalaxy.Remove(string id, string partitionKey)
{
try
{
- ItemResponse response = await Container.ReadItemAsync(id, new PartitionKey(partitionKey));
+ ItemResponse response = await _container.ReadItemAsync(id, new PartitionKey(partitionKey));
return (new(response.RequestCharge, null), response.Resource);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
@@ -236,37 +253,20 @@ async Task IGalaxy.Remove(string id, string partitionKey)
async Task<(Gravity, T)> GetOneFromQuery(QueryDefinition query)
{
- using FeedIterator queryResponse = Container.GetItemQueryIterator(query);
+ using FeedIterator queryResponse = _container.GetItemQueryIterator(query);
if (queryResponse.HasMoreResults)
{
FeedResponse next = await queryResponse.ReadNextAsync();
- return (new(next.RequestCharge, null, RecordQuery ? (query.QueryText, query.GetQueryParameters()) : default), next.Any() ? next.Resource.FirstOrDefault() : default);
+ return (new(next.RequestCharge, null, _recordQuery ? (query.QueryText, query.GetQueryParameters()) : default), next.Any() ? next.Resource.FirstOrDefault() : default);
}
else return new(new(0, null), default);
}
- async Task<(Gravity, T)> IGalaxy.Get(Catalyst catalyst, IList columns)
- {
- try
- {
- QueryDefinition query = CreateQuery(catalysts: new[] { catalyst }, columnOptions: columns is null || !columns.Any() ? null : new(columns));
- return await GetOneFromQuery(query);
- }
- catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
- {
- throw new UniverseException($"{typeof(T).Name} does not exist.");
- }
- catch (CosmosException ex) when (ex.StatusCode != HttpStatusCode.NotFound)
- {
- throw;
- }
- }
-
- async Task<(Gravity, T)> IGalaxy.Get(IList catalysts, IList columns)
+ async Task<(Gravity, T)> IGalaxy.Get(IList clusters, IList columns)
{
try
{
- QueryDefinition query = CreateQuery(catalysts: catalysts, columnOptions: columns is null || !columns.Any() ? null : new(columns));
+ QueryDefinition query = CreateQuery(clusters, columnOptions: columns is null || !columns.Any() ? null : new(columns));
return await GetOneFromQuery(query);
}
catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
@@ -283,7 +283,7 @@ async Task IGalaxy.Remove(string id, string partitionKey)
{
double requestCharge = 0;
List collection = new();
- using FeedIterator queryResponse = Container.GetItemQueryIterator(query);
+ using FeedIterator queryResponse = _container.GetItemQueryIterator(query);
while (queryResponse.HasMoreResults)
{
FeedResponse next = await queryResponse.ReadNextAsync();
@@ -291,27 +291,14 @@ async Task IGalaxy.Remove(string id, string partitionKey)
requestCharge += next.RequestCharge;
}
- return (new(requestCharge, null, RecordQuery ? (query.QueryText, query.GetQueryParameters()) : default), collection);
- }
-
- async Task<(Gravity, IList)> IGalaxy.List(Catalyst catalyst, ColumnOptions? columnOptions, IList sorting, IList group)
- {
- try
- {
- QueryDefinition query = CreateQuery(catalysts: new[] { catalyst }, columnOptions: columnOptions, sorting: sorting, groups: group);
- return await GetListFromQuery(query);
- }
- catch (CosmosException ex) when (ex.StatusCode != HttpStatusCode.NotFound)
- {
- throw;
- }
+ return (new(requestCharge, null, _recordQuery ? (query.QueryText, query.GetQueryParameters()) : default), collection);
}
- async Task<(Gravity, IList)> IGalaxy.List(IList catalysts, ColumnOptions? columnOptions, IList sorting, IList group)
+ async Task<(Gravity, IList)> IGalaxy.List(IList clusters, ColumnOptions? columnOptions, IList sorting, IList group)
{
try
{
- QueryDefinition query = CreateQuery(catalysts: catalysts, columnOptions: columnOptions, sorting: sorting, groups: group);
+ QueryDefinition query = CreateQuery(clusters: clusters, columnOptions: columnOptions, sorting: sorting, groups: group);
return await GetListFromQuery(query);
}
catch (CosmosException ex) when (ex.StatusCode != HttpStatusCode.NotFound)
@@ -320,16 +307,16 @@ async Task IGalaxy.Remove(string id, string partitionKey)
}
}
- async Task<(Gravity, IList)> IGalaxy.Paged(Q.Page page, IList catalysts, ColumnOptions? columnOptions, IList sorting, IList group)
+ async Task<(Gravity, IList)> IGalaxy.Paged(Q.Page page, IList clusters, ColumnOptions? columnOptions, IList sorting, IList group)
{
try
{
- QueryDefinition query = CreateQuery(catalysts: catalysts, columnOptions: columnOptions, sorting: sorting, groups: group);
+ QueryDefinition query = CreateQuery(clusters: clusters, columnOptions: columnOptions, sorting: sorting, groups: group);
double requestUnit = 0;
string continuationToken = string.Empty;
List collection = new();
- using FeedIterator queryResponse = Container.GetItemQueryIterator(query,
+ using FeedIterator queryResponse = _container.GetItemQueryIterator(query,
requestOptions: new() { MaxItemCount = page.Size },
continuationToken: string.IsNullOrWhiteSpace(page.ContinuationToken) ? null : page.ContinuationToken
);
@@ -345,7 +332,7 @@ async Task IGalaxy.Remove(string id, string partitionKey)
}
}
- return (new(requestUnit, continuationToken, RecordQuery ? (query.QueryText, query.GetQueryParameters()) : default), collection);
+ return (new(requestUnit, continuationToken, _recordQuery ? (query.QueryText, query.GetQueryParameters()) : default), collection);
}
catch (CosmosException ex) when (ex.StatusCode != HttpStatusCode.NotFound)
{
@@ -356,12 +343,12 @@ async Task IGalaxy.Remove(string id, string partitionKey)
///
protected virtual void Dispose(bool disposing)
{
- if (DisposedValue) return;
+ if (_disposedValue) return;
if (disposing)
{
}
- DisposedValue = true;
+ _disposedValue = true;
}
///
diff --git a/code/Universe/Interfaces/IGalaxy.cs b/code/Universe/Interfaces/IGalaxy.cs
index c4f7ba1..b2bb62c 100644
--- a/code/Universe/Interfaces/IGalaxy.cs
+++ b/code/Universe/Interfaces/IGalaxy.cs
@@ -38,25 +38,15 @@ public interface IGalaxy where T : ICosmicEntity
///
/// Get one model from the database
///
- Task<(Gravity g, T T)> Get(Catalyst catalyst, IList columns = null);
+ Task<(Gravity g, T T)> Get(IList clusters, IList columns = null);
///
- /// Get one model from the database
- ///
- Task<(Gravity g, T T)> Get(IList catalysts, IList columns = null);
-
- ///
- /// Get a paginated list from the database
- ///
- Task<(Gravity g, IList T)> List(Catalyst catalyst, ColumnOptions? columnOptions = null, IList sorting = null, IList group = null);
-
- ///
- /// Get a paginated list from the database
+ /// Get list from the database
///
- Task<(Gravity g, IList T)> List(IList catalysts, ColumnOptions? columnOptions = null, IList sorting = null, IList group = null);
+ Task<(Gravity g, IList T)> List(IList clusters, ColumnOptions? columnOptions = null, IList sorting = null, IList group = null);
///
/// Get a paginated list from the database
///
- Task<(Gravity g, IList T)> Paged(Q.Page page, IList catalysts, ColumnOptions? columnOptions = null, IList sorting = null, IList group = null);
+ Task<(Gravity g, IList T)> Paged(Q.Page page, IList clusters, ColumnOptions? columnOptions = null, IList sorting = null, IList group = null);
}
diff --git a/code/Universe/Options/SortingOptions.cs b/code/Universe/Options/SortingOptions.cs
index abd7641..8f2967f 100644
--- a/code/Universe/Options/SortingOptions.cs
+++ b/code/Universe/Options/SortingOptions.cs
@@ -14,7 +14,7 @@ public enum Direction
}
///
- public record Option(string Column, Direction Direction = Direction.ASC);
+ public readonly record struct Option(string Column, Direction Direction = Direction.ASC);
}
///
diff --git a/code/Universe/Options/WhereClause.cs b/code/Universe/Options/WhereClause.cs
new file mode 100644
index 0000000..14dfe9b
--- /dev/null
+++ b/code/Universe/Options/WhereClause.cs
@@ -0,0 +1,8 @@
+namespace Universe.Options.Query;
+
+///
+/// Clusters are group of Catalysts that are joined by a Where operator (eg AND / OR). This will divide the where clause into multiple groups.
+///
+/// Catalysts under one group / cluster
+/// Where operator (eg AND / OR)
+public readonly record struct Cluster(IList Catalysts, Q.Where Where = Q.Where.And);
\ No newline at end of file
diff --git a/code/Universe/UniverseQuery.csproj b/code/Universe/UniverseQuery.csproj
index 8e1247c..1161164 100644
--- a/code/Universe/UniverseQuery.csproj
+++ b/code/Universe/UniverseQuery.csproj
@@ -1,9 +1,9 @@
- net6.0
+ net7.0
disable
- 10.0
+ 11.0
A simpler way of querying a CosmosDb Namespace
Nor Gelera 2022
Nor Gelera
@@ -15,16 +15,16 @@
LICENSE
A simpler way of querying a CosmosDb Namespace
- Nor Gelera 2022
+ Nor Gelera 2023
https://github.com/kuromukira/universe
cosmos simple query
Git
Nor Gelera
Universe
- 1.4.1
+ 2.0.0
View release on https://github.com/kuromukira/universe/releases
- 1.4.1.0
- 1.4.1.0
+ 2.0.0.0
+ 2.0.0.0
@@ -37,8 +37,8 @@
-
-
+
+
diff --git a/code/Universe/UniverseQuery.xml b/code/Universe/UniverseQuery.xml
index cfa9ca5..04c4e4f 100644
--- a/code/Universe/UniverseQuery.xml
+++ b/code/Universe/UniverseQuery.xml
@@ -82,27 +82,17 @@
Get one model from the database
-
+
Get one model from the database
-
+
- Get one model from the database
-
-
-
-
- Get a paginated list from the database
+ Get list from the database
-
-
- Get a paginated list from the database
-
-
-
+
Get a paginated list from the database
@@ -274,6 +264,26 @@
+
+
+ Clusters are group of Catalysts that are joined by a Where operator (eg AND / OR). This will divide the where clause into multiple groups.
+
+ Catalysts under one group / cluster
+ Where operator (eg AND / OR)
+
+
+
+ Clusters are group of Catalysts that are joined by a Where operator (eg AND / OR). This will divide the where clause into multiple groups.
+
+ Catalysts under one group / cluster
+ Where operator (eg AND / OR)
+
+
+ Catalysts under one group / cluster
+
+
+ Where operator (eg AND / OR)
+