Files
sql-utilities/docs/Rules.ClassDiagram.md

12 KiB

Strata.SqlTools.Rules Class Diagram

This diagram shows the class hierarchy for the expression system.

classDiagram
    class IVisitable {
        <<interface>>
        +Accept~T~(IVisitor~T~) T
    }
    note for IVisitable "Visitor Pattern Interface<br/>var visitor = new MyRuleVisitor()#59;<br/>var result = expr.Accept(visitor)#59;"

    class Expression {
        <<abstract>>
        +Accept~T~(IVisitor~T~) T
    }

    class BoolExpr {
        <<abstract>>
    }

    class Literal {
        +Value object
    }

    class LiteralGeneric~TValue~ {
        +Value TValue
    }
    note for LiteralGeneric "Generic Literal<br/>var literal = new Literal&lt;int&gt;<br/>{ Value = 100 }#59;"

    class Property {
        +Expression Expression
        +PropertyName string
    }
    note for Property "Property Access<br/>var prop = new Property<br/>{ PropertyName = #quot;Age#quot; }#59;"

    class Logical {
        <<abstract>>
        +Left BoolExpr
        +Right BoolExpr
    }

    class Comparison {
        <<abstract>>
        +Left Expression
        +Right Expression
        +ExpressionType ExpressionType
    }

    class And
    note for And "AND Logic<br/>var and = new And<br/>{<br/>  Left = expr1,<br/>  Right = expr2<br/>}#59;"

    class Or
    note for Or "OR Logic<br/>var or = new Or<br/>{<br/>  Left = expr1,<br/>  Right = expr2<br/>}#59;"

    class With
    note for With "WITH Sequential<br/>var with = new With<br/>{<br/>  Left = expr1,<br/>  Right = expr2<br/>}#59;"

    class Equal {
        +ExpressionType ExpressionType
    }
    note for Equal "Equality#58; Age == 25<br/>var eq = new Equal<br/>{<br/>  Left = new Property<br/>    { PropertyName = #quot;Age#quot; },<br/>  Right = new NumberLiteral<br/>    { Value = 25m }<br/>}#59;"

    class GreaterThan {
        +ExpressionType ExpressionType
    }
    note for GreaterThan "Comparison#58; Score &gt; 100<br/>var gt = new GreaterThan<br/>{<br/>  Left = new Property<br/>    { PropertyName = #quot;Score#quot; },<br/>  Right = new NumberLiteral<br/>    { Value = 100m }<br/>}#59;"

    class NumberLiteral {
        +Value decimal
    }
    note for NumberLiteral "Number Literal<br/>var num = new NumberLiteral<br/>{ Value = 42.5m }#59;"

    class StringLiteral {
        +Value string
    }
    note for StringLiteral "String Literal<br/>var str = new StringLiteral<br/>{ Value = #quot;Hello#quot; }#59;"

    IVisitable <|.. Expression
    Expression <|-- BoolExpr
    Expression <|-- Literal
    Expression <|-- Property

    BoolExpr <|-- Logical
    BoolExpr <|-- Comparison

    Literal <|-- LiteralGeneric

    LiteralGeneric <|-- NumberLiteral
    LiteralGeneric <|-- StringLiteral

    Logical <|-- And
    Logical <|-- Or
    Logical <|-- With

    Comparison <|-- Equal
    Comparison <|-- GreaterThan

Class Descriptions

Core Classes

  • IVisitable: Interface for classes that can be visited using the visitor pattern
  • Expression: Base abstract class for all expressions
  • BoolExpr: Base class for expressions that evaluate to boolean values

Literal Expressions

  • Literal: Represents a literal value
  • Literal<TValue>: Generic typed literal expression
  • NumberLiteral: Represents numeric literal values (decimal)
  • StringLiteral: Represents string literal values

Property Expressions

  • Property: Represents property access in expressions

Logical Expressions

  • Logical: Base class for logical operations (AND, OR, WITH)
  • And: Logical AND operation
  • Or: Logical OR operation
  • With: Sequential WITH operation

Comparison Expressions

  • Comparison: Base class for comparison operations
  • Equal: Equality comparison (==)
  • GreaterThan: Greater than comparison (>)

C# Usage Examples

Creating Literal Expressions

// String literal
var stringLiteral = new StringLiteral
{
    Value = "Hello World"
};

// Number literal
var numberLiteral = new NumberLiteral
{
    Value = 42.5m
};

// Generic typed literal
var typedLiteral = new Literal<int>
{
    Value = 100
};

Creating Property Expressions

// Simple property access
var propertyExpr = new Property
{
    PropertyName = "Age"
};

// Property with nested expression
var nestedPropertyExpr = new Property
{
    PropertyName = "Address",
    Expression = new Property
    {
        PropertyName = "City"
    }
};

Creating Comparison Expressions

// Equal comparison: Age == 25
var equalExpr = new Equal
{
    Left = new Property { PropertyName = "Age" },
    Right = new NumberLiteral { Value = 25m }
};

// Greater than comparison: Score > 100
var greaterThanExpr = new GreaterThan
{
    Left = new Property { PropertyName = "Score" },
    Right = new NumberLiteral { Value = 100m }
};

Creating Logical Expressions

// AND expression: Age > 18 AND Status == "Active"
var andExpr = new And
{
    Left = new GreaterThan
    {
        Left = new Property { PropertyName = "Age" },
        Right = new NumberLiteral { Value = 18m }
    },
    Right = new Equal
    {
        Left = new Property { PropertyName = "Status" },
        Right = new StringLiteral { Value = "Active" }
    }
};

// OR expression: Type == "Premium" OR Score > 500
var orExpr = new Or
{
    Left = new Equal
    {
        Left = new Property { PropertyName = "Type" },
        Right = new StringLiteral { Value = "Premium" }
    },
    Right = new GreaterThan
    {
        Left = new Property { PropertyName = "Score" },
        Right = new NumberLiteral { Value = 500m }
    }
};

Complex Expression Example

// (Age > 18 AND Status == "Active") OR (Type == "Premium" WITH Score > 500)
var complexExpr = new Or
{
    Left = new And
    {
        Left = new GreaterThan
        {
            Left = new Property { PropertyName = "Age" },
            Right = new NumberLiteral { Value = 18m }
        },
        Right = new Equal
        {
            Left = new Property { PropertyName = "Status" },
            Right = new StringLiteral { Value = "Active" }
        }
    },
    Right = new With
    {
        Left = new Equal
        {
            Left = new Property { PropertyName = "Type" },
            Right = new StringLiteral { Value = "Premium" }
        },
        Right = new GreaterThan
        {
            Left = new Property { PropertyName = "Score" },
            Right = new NumberLiteral { Value = 500m }
        }
    }
};

Using the Visitor Pattern

// Implement a custom visitor
public class MyRuleVisitor : IVisitor<string>
{
    public string Visit(And expression)
    {
        return $"({expression.Left.Accept(this)} AND {expression.Right.Accept(this)})";
    }

    public string Visit(Equal expression)
    {
        return $"{expression.Left.Accept(this)} == {expression.Right.Accept(this)}";
    }

    public string Visit(StringLiteral expression)
    {
        return $"\"{expression.Value}\"";
    }

    // ... implement other Visit methods
}

// Use the visitor
var visitor = new MyRuleVisitor();
var result = complexExpr.Accept(visitor);
Console.WriteLine(result);

Markdown Parser

The Markdown class provides functionality to parse markdown/LaTeX mathematical expressions and convert them into Expression objects. This is useful for:

  • Documenting rules in markdown format
  • Creating expressions from user-friendly text representations
  • Converting mathematical notation to executable rule expressions

Supported Markdown Delimiters

The parser automatically strips these common markdown delimiters:

  • Inline math: $...$
  • Block math: $$...$$
  • Code fence: ```math...```

Supported Syntax

Logical Operators

  • AND or \land or \wedge - Logical AND
  • OR or \lor or \vee - Logical OR

Comparison Operators

  • = - Equality
  • != or \neq - Not equal
  • > or \gt - Greater than

Literals

  • Numbers: 42, 3.14
  • Strings: "text" or 'text' or \text{text}
  • Booleans: true, false

Properties

  • Simple: PropertyName
  • With parameter: x.PropertyName
  • LaTeX format: \text{x.PropertyName}

Parentheses

  • Regular: (...)
  • LaTeX: \left(...\right)

Markdown Parser Usage Examples

Basic Parsing

using Strata.SqlTools.Rules.Rule.Expression;

// Parse a simple comparison
var expr1 = Markdown.Parse("x.Age > 18");
// Returns: GreaterThan { Left = Property("x", "Age"), Right = NumberLiteral(18) }

// Parse with inline math delimiters
var expr2 = Markdown.Parse("$x.Status = 'active'$");
// Returns: Equal { Left = Property("x", "Status"), Right = StringLiteral("active") }

// Parse with block math delimiters
var expr3 = Markdown.Parse(@"$$
    user.IsVerified = true
$$");
// Returns: Equal { Left = Property("user", "IsVerified"), Right = Literal(true) }

Parsing Logical Operations

// Parse AND expression
var andExpr = Markdown.Parse("x.Age > 18 AND x.Active = true");
// Returns: And { Left = GreaterThan(...), Right = Equal(...) }

// Parse OR with LaTeX notation
var orExpr = Markdown.Parse(@"$
    x.Type = 'premium' \lor x.Score > 500
$");
// Returns: Or { Left = Equal(...), Right = GreaterThan(...) }

// Parse with LaTeX wedge (AND) and vee (OR)
var complexExpr = Markdown.Parse(@"
    (x.Valid = true \wedge x.Count > 0) \vee y.Override = true
");
// Returns: Or { Left = And(...), Right = Equal(...) }

Parsing Complex Expressions

// Complex business rule with nested conditions
var businessRule = Markdown.Parse(@"$$
    (invoice.TotalCharges > 1000 \land invoice.Status = \text{pending})
    \lor
    (invoice.Priority = \text{urgent} \land invoice.ApprovedBy \neq \text{})
$$");

// Use with visitor pattern
var visitor = new MyRuleVisitor();
var result = businessRule.Accept(visitor);

Safe Parsing with TryParse

// Use TryParse for error handling
if (Markdown.TryParse("x.Age > 18", out var expression))
{
    Console.WriteLine("Parsed successfully!");
    // Use the expression
    var result = expression.Accept(myVisitor);
}
else
{
    Console.WriteLine("Failed to parse expression");
}

Real-World Example

// Define a rule in markdown documentation
var ruleMarkdown = @"
# User Eligibility Rule

The user must meet one of the following conditions:

\`\`\`math
(\text{user.Age} > 18 \land \text{user.AccountStatus} = \text{active})
\lor
(\text{user.Role} = \text{admin})
\`\`\`
";

// Extract and parse the math block
var mathContent = ExtractMathBlock(ruleMarkdown); // Your extraction logic
var eligibilityRule = Markdown.Parse(mathContent);

// Apply the rule
public class EligibilityChecker : IVisitor<bool>
{
    private readonly User _user;

    public EligibilityChecker(User user) => _user = user;

    public bool VisitAnd(And expression) =>
        expression.Left.Accept(this) && expression.Right.Accept(this);

    public bool VisitOr(Or expression) =>
        expression.Left.Accept(this) || expression.Right.Accept(this);

    public bool VisitEqual(Equal expression)
    {
        var left = expression.Left.Accept(new PropertyEvaluator(_user));
        var right = expression.Right.Accept(new LiteralEvaluator());
        return Equals(left, right);
    }

    // ... other visitor methods
}

// Check eligibility
var checker = new EligibilityChecker(currentUser);
bool isEligible = eligibilityRule.Accept(checker);

Benefits of Using Markdown Parser

  1. Documentation and Code Alignment: Keep rule documentation and implementation in sync
  2. Human-Readable Rules: Write business rules in a format that non-developers can understand
  3. LaTeX Support: Use standard mathematical notation for complex logical expressions
  4. Easy Testing: Write test cases using readable markdown expressions
  5. Version Control Friendly: Track rule changes in readable text format