/labelkit-net

.NET toolkit for parsing and using kubernetes-like label-selectors including support for EFCore.

Primary LanguageC#MIT LicenseMIT

LabelKit

LabelKit is a .NET toolkit for parsing kubernetes-like label-selectors and using them to build filters, including query-expressions for EFCore (PostgreSQL, MySql, SqlServer, Sqlite). Labels are name/value pairs that can be represented as dictionaries (name->value) or simple string collections (name+delimiter+value).

Packages

The majority of packages support netstandard2.0.

The following NuGet packages are provided:

Examples

EFCore PostgreSQL:

public class Entity
{
  // Stored as JSONB
  public Dictionary<string, string> Labels { get; set; }
}

dotnet add package LabelKit.Parser

LabelKit.Parser offers a default parser built with Pidgin.

The parser is able to parse raw label-selectors adhering to the kubernetes syntax.

using LabelKit;

var selector = LabelSelectorParser.Parse(
  "label1 = value, label2 = value, label3 in (value1, value2)");

dotnet add package LabelKit.EFCore.PostgreSQL

var expressionBuilder = NpgsqlLabelSelectorExpressionBuilders.Jsonb<Dictionary<string, string>>();

var entities = await dbContext.Set<Entity>()
  .MatchLabels(e => e.Labels, selector, expressionBuilder)
  .ToListAsync();

Executed SQL:

-- @__json_1='{"label1":"value","label2":"value"}' (DbType = Object)
-- @__Format_2='{"label3":"value1"}' (DbType = Object)
-- @__Format_3='{"label3":"value2"}' (DbType = Object)
SELECT t."Id", t."Labels"
FROM "TestEntity" AS t
WHERE t."Labels" @> @__json_1 AND (t."Labels" @> @__Format_2 OR t."Labels" @> @__Format_3)

Label-Selectors

Label-selectors can be easily created and extended...

var selector = LabelSelector.New()
  .Match("label1").Exact("value")
  .Match("label2").Not("value")
  .Match("label3").In("value1", "value2")
  .Match("label4").NotIn("value1", "value2")
  .Match("label5").Exists()
  .Match("label6").NotExists();

They can be merged...

var selector1 = LabelSelector.New()
  .Match("label1").Exact("value");

var selector2 = LabelSelector.New()
  .Match("label2").Exact("value");

// Contains a fully copy of all expressions
var merged = selector1.Merge(selector2);

// You can merge an arbitrary number of selectors

merged = LabelSelector.Merge(selector1, selector2, ...);

They can be rendered...

// label1 = value, label2 = value
merged.ToString();

Matching Labels Offline

You can also use label-selectors offline without any database interaction.

dotnet add package LabelKit

var selector = LabelSelector.New()
  .Match("label1").Exact("value")
  .Match("label2").Exact("value");

string[] labels = [ "label1:value", "label2:value" ];

// Default delimiter is ':'
// -> true
var doesMatch = selector.Matches(labels);

The same can be done with dictionary-like labels:

var selector = LabelSelector.New()
  .Match("label1").Exact("value")
  .Match("label2").Exact("value");

var labels = new Dictionary<string, string()
{
  ["label1"] = "value",
  ["label2"] = "value"
};

// -> true
var doesMatch = selector.Matches(labels);

Tip

Any component implementing ILabelSelector (meaning it can provide a collection of selector-expressions) can be used to match offline.

Expressions (EFCore)

LabelKit supports infrastructure for building expression-trees that can be used to filter IQueryables. Components implementing ILabelSelectorExpressionBuilder are responsible for creating Expressions from label-selectors. Different expression-builders are needed for different scenarios.

Example: If your labels are stored as JSONB (PostgreSQL), the resulting SQL query has to be vastly different from if they were stored as an array. Therefore, you need a different expression.

Supported EFCore Providers

Provided Expression-Builders

  • NpgsqlJsonbLabelSelectorExpressionBuilder (NpgsqlLabelSelectorExpressionBuilders.Jsonb())
    • Builds expression specifically suited for PostgreSQL JSONB columns.
  • MySqlJsonLabelSelectorExpressionBuilder (MySqlLabelSelectorExpressionBuilders.Json())
    • Builds expression specifically suited for MySql JSON columns.
  • CollectionLabelSelectorExpressionBuilder (LabelSelectorExpressionBuilders.Collection())
    • Builds generic expression suitable for any collection of strings.
    • Supported by PostgreSQL, SqlServer, Sqlite (and MySql).
    • Available in package LabelKit.Expressions.

Important

CollectionLabelSelectorExpressionBuilder produces expressions that should be translatable to SQL by EFCore providers supporting primitive-collections. However, you should also be able to use those expressions offline (compiled).

Filter IQueryables

dotnet add package LabelKit.Expressions

using LabelKit;

var expressionBuilder = LabelSelectorExpressionBuilders.Collection<string[]>();

// e => e.Labels is an expression representing the labels to match.
// You can also mark your entity as ILabelledEntity to avoid having to specify this every time.
var entities = await dbContext.Set<Entity>()
  .MatchLabels(e => e.Labels,
    selector => selector
        .Match("label1").Exact("value")
        .Match("label2").Exact("value")
    , expressionBuilder)
  .ToListAsync();