Operators

Qwiratry Operators Specification

1. Introduction

This document specifies the query operators available in Raku Qwiratry. All operators are implemented as Query AST objects (descendants of RakuAST::Node) that represent declarative query intent. Operators are interpreted by Walkers during the planning phase and executed via QueryIterators during traversal.

1.1 Architecture Integration

In Qwiratry, operators are part of the Query Group - they describe what to query, not how to execute. The architecture separates:

  • Query AST (operators) - Declarative intent, immutable, composable

  • Walker - Interprets operators during plan() phase, determines execution strategy

  • QueryIterator - Produces results incrementally during traversal

  • Transformer - Can use operators in mold when clauses for node selection

1.2 Key Design Principles

  • Declarative Semantics: Operators specify what to find, not how to find it

  • Immutability: Query AST nodes are immutable and can be safely shared

  • Composability: Operators can be combined to form complex queries

  • Domain Flexibility: Same operator may have different semantics for trees vs tables

  • Lazy Evaluation: Results are produced incrementally via QueryIterator

  • Introspectable: Walkers can examine operator structure for optimization

1.3 Usage Contexts

Operators can be used in:

  1. Query Slang expressions - Direct query construction

  2. Transformer molds - In when clauses for node matching

  3. Inline code - As part of Raku expressions that produce Query AST

2. Operator Precedence

The precedence levels mentioned here follow Raku's Operator Precedence rules. Qwiratry operators are integrated into this precedence hierarchy.

2.1 Precedence Table

Precedence levels in bold are standard Raku levels. Those not in bold are levels we've added.

Precedence LevelAssociativityOperatorsComments
Symbolic Unarynon⇤ Π ρ ⮳
Replicationleftσ 𜸈 ⇅ ⌿ ». ⪪ ⪫ ⪪⪪ ⪫⪫ ⪨ ⪩ ⪨⪨ ⪩⪩ ⥷ ↱ ⮣ ↴ ⮧ ⮷
Concatenationlist⋉ ⋊ ▷ ◁ ⨝ ⟕ ⟖ ⟗ ▷=◁ ≜ ÷ ▵ ×
Junctive Exponentiationright𝒫Tighter than Junctive unary
Junctive unaryleftTighter than Junctive and
Junctive andlist∩ ⩃
Junctive orlist∪ ⩂ ⊖ ⊙ ⊟ ⊡ ∖
Chainingchain∈ ∊ (elem) ∉ ∋ ∍ (cont) ∌ ⊂ (<) ⊄ ⊃ (>) ⊅ ⊆ (<=) ⊈ ⊇ (>=) ⊉ ≡ (==) ≢

3. I/O and Transformation Operators

I/O and Transformation Operators handle parsing, rendering, and reading/writing data from external sources. These operators take arguments on the right-hand side to specify format or location.

3.1 Parse Operator

OperatorUnicodeDescription
↱ or ⮣Parse input (JSON, XML, CSV, etc.)

The parse operator takes a format argument (e.g., JSON, XML, CSV). Possible values are controlled by the existence of a Qwiratry::Format::* module with a Parse implementation.

The left-hand side is normally text. An IO::Handle (including $*IN) is slurped first; the handle is left open.

Parse Examples:

# Parse JSON
my $json_root = ⮳ <data.json> ↱ <JSON>;

# Parse XML
my $xml_root = ⮳ <config.xml> ↱ <XML>;

# Parse from STDIN (or any IO::Handle)
my $from_stdin = $*IN ↱ <JSON>;

3.2 Render Operator

OperatorUnicodeDescription
↴ or ⮧Render output (JSON, XML, CSV, formatters)

The render operator takes a format argument specifying the output format. It converts query results or tree structures into the specified serialization format.

It takes a format argument (e.g., JSON, XML, CSV). Possible values are controlled by the existence of a Qwiratry::Format::* module with a Render implementation.

Format Options: Some formats support additional options via adverbs or named arguments:

# Render with indentation
my $json = $root ↴ <JSON> :pretty;

# Render CSV with custom delimiter
my $csv = $relation ↴ <CSV> :delimiter<;>;

# Render XML with specific encoding
my $xml = $root ↴ <XML> :encoding<UTF-8>;

Render Examples:

# Render to JSON
my $json = $root ↴ <JSON>;

# Pretty-printed JSON
my $pretty_json = $root ↴ <JSON> :pretty;

3.3 Source Operator

OperatorUnicodeDescription
Read nodes from external sources (filesystem, web, commands)

The source operator accepts a location parameter that specifies where to read data from. The location can be a file path, URL, or command.

Location Parameter Rules:

  • If no : is found, it's assumed to be a filename (relative or absolute path)

  • If a : is found, it's assumed to be a URL or protocol-based location

Example Protocols and URL Schemes:

  • File System (file:// or no protocol):

    • ⮳ <data.json> - Relative file path

    • ⮳ </absolute/path/data.json> - Absolute file path

  • HTTP/HTTPS (http://, https://):

    • ⮳ <https://example.com/data.json> - HTTPS URL

    • ⮳ <http://localhost:8080/api/data> - HTTP URL with port

Note that it's also possible to avoid the source operator:

  • Since Raku has shell quoting (qx), there's no need for shell execution

  • Pass $*IN (or any IO::Handle) to parse: $*IN ↱ <JSON>

  • The "filename.json".IO.lines could also be used

  • Databases can be read by providing database-reading classes

Source Examples:

# Local file
my $local = ⮳ <data.json>;

# Absolute path
my $absolute = ⮳ </home/user/data.json>;

# HTTP URL
my $http = ⮳ <https://api.example.com/data.json>;

3.4 Destination Operator

OperatorUnicodeDescription
Write nodes to external destinations

The destination operator accepts a location parameter that specifies where to write data. It uses the same protocol and URL scheme rules as the source operator.

Supported Protocols and URL Schemes:

  • File System (file:// or no protocol):

    • ⮷ <output.json> - Write to relative file path

    • ⮷ </absolute/path/output.json> - Write to absolute file path

  • HTTP/HTTPS (http://, https://):

    • ⮷ <https://api.example.com/upload> - POST data to HTTPS endpoint

    • ⮷ <http://localhost:8080/api/data> - POST data to HTTP endpoint

    • Typically uses POST or PUT methods depending on the endpoint

See the Source operator for examples of other alternatives.

Destination Examples:

# Local file
my $result = $data ↴ <JSON> ⮷ <output.json>;

# Absolute path
my $result = $data ↴ <XML> ⮷ </var/www/data.xml>;

# HTTP POST
my $result = $data ↴ <JSON> ⮷ <https://api.example.com/upload>;

3.5 Usage Examples

# Parse JSON from a file
my $json_root = ⮳ <data.json> ↱ <JSON>;

# Parse and select, then render
my $result = ⮳ <data.json> ↱ <JSON> ⪪ <record> ↴ <JSON> ⮷ <output.json>;

# Parse XML from URL
my $xml_root = ⮳ <https://example.com/data.xml> ↱ <XML>;

# Render to CSV
my $csv = $relation ↴ <CSV> ⮷ <output.csv>;

3.6 I/O Operator Composition and Pipelines

I/O operators are designed to be composed into data processing pipelines. They can be chained together with navigation, selection, and transformation operators to create complex data workflows.

Basic Pipeline Pattern: The typical pattern is: Source → Parse → Query → Render → Destination

# Complete pipeline: read, parse, filter, transform, render, write
my $result = ⮳ <data.json> ↱ <JSON> 
              ⪪⪪ <item> 
              σ { ⥷ <price> > 100 } 
              ↴ <JSON> 
              ⮷ <expensive-items.json>;

Multi-Format Pipeline: Convert between formats in a single pipeline:

# XML → JSON conversion
my $json = ⮳ <data.xml> ↱ <XML> ↴ <JSON> ⮷ <data.json>;

# CSV → YAML conversion with filtering
my $yaml = ⮳ <data.csv> ↱ <CSV> 
           σ { ⥷ <status> eq 'active' } 
           ↴ <YAML> 
           ⮷ <active-items.yaml>;

Multi-Source Pipeline: Combine data from multiple sources:

# Read from multiple sources and combine
my $combined = (⮳ <data1.json> ↱ <JSON> ⪪ <items>) 
               ∪ 
               (⮳ <data2.json> ↱ <JSON> ⪪ <items>);

# Write combined result
my $result = $combined ↴ <JSON> ⮷ <combined.json>;

HTTP Pipeline: Fetch, process, and upload data:

# Fetch from API, process, upload to another endpoint
my $result = ⮳ <https://api.example.com/data.json> ↱ <JSON>
              ⪪ <records>
              σ { ⥷ <date> > Date.today - 7 }
              ↴ <JSON>
              ⮷ <https://api.example.com/recent.json>;

Streaming Pipeline: Process data incrementally (Walker-dependent):

# Large file processing with streaming
my $stream = ⮳ <large-data.json> ↱ <JSON>
             ⪪⪪ <record>
             σ { ⥷ <category> eq 'important' }
             ↴ <JSON>
             ⮷ <important-records.json>;
# Walker may stream results without loading entire file into memory

Conditional Pipeline: Use selection to route data:

# Route data to different destinations based on criteria
my $data = ⮳ <data.json> ↱ <JSON> ⪪ <items>;

# Write active items to one file
($data σ { ⥷ <status> eq 'active' }) ↴ <JSON> ⮷ <active.json>;

# Write inactive items to another file
($data σ { ⥷ <status> eq 'inactive' }) ↴ <JSON> ⮷ <inactive.json>;

Format-Specific Pipeline Examples:

# XML processing pipeline
my $xml_result = ⮳ <catalog.xml> ↱ <XML>
                 ⪪⪪ <product>
                 σ { ⥷ <price> < 50 && ⥷ <in_stock> }
                 ↴ <XML>
                 ⮷ <cheap-products.xml>;

# CSV processing pipeline
my $csv_result = ⮳ <sales.csv> ↱ <CSV>
                 σ { ⥷ <amount> > 1000 }
                 ⇅ { ⥷ <date> }
                 ↴ <CSV>
                 ⮷ <large-sales.csv>;

# YAML configuration processing
my $config = ⮳ <config.yaml> ↱ <YAML>
            ⪪ <settings>
            ↴ <JSON>
            ⮷ <settings.json>;

4. Map-Reduce and Aggregation Operators

These operators perform selection, transformation, sorting, and reduction operations on relations.

OperatorUnicodeSet Theory TermTraditional TermDescription
σ, 𜸈U+03C3, U+1CE08SelectiongrepChoose some tuples; equivalent to WHERE clause in SQL
U+21C5sortSorts tuples
».mapHyper operator (core Raku) equivalent to Map call
U+233FreduceReduces a set of tuples by repeatedly applying a function/operation

4.1 Usage Examples

# Selection (filter)
my $filtered = $relation ==> σ { $_.age > 18 };

# Sort
my $sorted = $relation ==> ⇅ { $_.name };

# Map (hyper operator)
my $mapped = $relation ». { $_.name.uc };

# Reduce
my $sum = $relation ==> ⌿ { $^a + $^b.age };

5. Navigation Operators

Navigation Operators provide axis-based navigation for tree-like structures (XML, JSON, ASTs, Match trees, etc.), but other structures, such as tables. These operators are inspired by XPath axes but stay idiomatically Raku. All navigation operators require a right operand (which can be a Whatever *), except for the root operator () which is a unary postfix operator. If a left operand is provided, they use that as input, otherwise, they use $_ as input. They may take additional operands as adverbs.

Navigation operators can be chained: $node ⪪ * ⪪ * means "children of children"

5.1 Navigation Operators Reference

Each operator returns the set of nodes along the corresponding axis. All axis operators are both callable and iterable, allowing them to be used in path specifications and composed with other operators. Navigation operators can be used with trees, but also tables and relational data structures. The semantics depend on the left operand type.

AxisUnicodeAliasTreeTableTable Row
ChildchildDirect children of the current nodeReturns rowsReturns FK rows or nothing (if column has no FK)
ParentparentDirect parent of the current nodeReturns containing namespace (schema/database/etc.)Returns the table containing the row
Descendant⪪⪪descendantAll descendants of the current nodeReturns all rows (same as ⪪)Walker-dependent: may descend into FKs (optionally filtered by labels), or throw exception
Ancestor⪫⪫ancestorAll ancestors of the current nodeReturns namespace hierarchy (schema/database/etc.)Returns the table (same as ⪫)
Following-Siblingfollowing-siblingSiblings after the current nodeReturns nothing/errorReturns next row in table (if ordered)
Preceding-Siblingpreceding-siblingSiblings before the current nodeReturns nothing/errorReturns previous row in table (if ordered)
Following⪨⪨followingAll nodes following in document orderReturns nothing/errorReturns all rows after this row (if ordered)
Preceding⪩⪩precedingAll nodes preceding in document orderReturns nothing/errorReturns all rows before this row (if ordered)
Rootdata-rootThe root node of the tree (unary postfix operator)Returns root of namespace hierarchy (unary postfix)Returns root of namespace hierarchy (walks up from table via namespace, unary postfix)
AttributeattributeKey/value or associative childrenReturns nothing/errorReturns column values

5.1.1 Usage Examples

# Select all child nodes (wildcard selects all)
my @children = $root ⪪ *;

# Select all descendants (wildcard selects all)
my @all-descendants = $root ⪪⪪ *;

# Compose operators: children's children
my @grandchildren = $root ⪪ * ⪪ *;

# Select with predicate
my @items = $root ⪪ <item>;

# Select descendants matching a pattern
my @divs = $root ⪪⪪ <div> σ { ⥷ <class> };

# Navigate up the tree or namespace
my $parent = $node ⪫ *;

# Get root (unary postfix operator)
my $root = $node ⇤;

# Combine with set operations
my @nodes = $root ⪪ * ∪ $root ⥷ <attr>;

5.2 Operators

5.2.1 Child Operator (⪪)

Table → Rows: When the left operand is a table, the child operator () requires a right operand to select rows. The right operand may be a wildcard to choose all rows, or a selector to choose some.

my $table = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];
my @rows = $table ⪪ *;  # Returns the 3 rows (wildcard selects all)

Row → Foreign Key Navigation: When the left operand is a row and the right operand is a column name that has a foreign key constraint, the child operator () follows the foreign key and returns an array of related rows from the foreign table:

my $order = get-order(123);
my @customers = $order ⪪ <customer_id>;  # Follows FK, returns customer row(s)

Return value: Always returns an array/sequence of rows, regardless of cardinality:

  • One-to-one: Array containing a single row

  • One-to-many: Array containing multiple rows

  • This provides a consistent API regardless of relationship cardinality

Non-Foreign Key Columns: When using the child operator () on a column that does not have a foreign key constraint, the default behavior is to return nothing (empty result). Use the attribute operator () to access column values:

my $row = get-row();
my @nothing = $row ⪪ <non_fk_column>;  # Returns empty (no FK to follow)
my $value = $row ⥷ <non_fk_column>;   # Gets the column value

Note: This is default behavior; specific walkers may override this semantics.

Edge Cases and Error Handling:

  • Null foreign keys: If a foreign key column contains NULL, the child operator returns an empty result (no rows to follow)

  • Invalid foreign key values: If a foreign key value does not exist in the referenced table, behavior is Walker-dependent (may return empty, throw exception, or handle gracefully)

  • Circular foreign key references: Walkers must handle circular references appropriately to avoid infinite loops

  • Multiple foreign keys to same table: If a row has multiple foreign keys pointing to the same table, each FK column must be navigated separately

5.2.3 Attribute Operator (⥷)

The attribute operator () is used to get column values from rows, rather than following foreign keys:

my $order = get-order(123);
my $customer_id = $order ⥷ <customer_id>;  # Gets the FK value
my @customer = $order ⪪ <customer_id>;     # Follows FK, gets related row(s)

When using the child operator () on a column that does not have a foreign key constraint, the default behavior is to return nothing (empty result). Use the attribute operator () to access column values:

my $row = get-row();
my $value = $row ⥷ <non_fk_column>;   # Gets the column value

5.2.4 Descendant Operator (⪪⪪)

Table → Rows: When used on a table, the descendant operator (⪪⪪) requires a right operand and returns all rows (same as the child operator ):

my $table = [[1, 2, 3], [4, 5, 6]];
my @all-rows = $table ⪪⪪ *;  # Returns all rows (wildcard selects all)

Row → Foreign Key Navigation: The behavior of the descendant operator (⪪⪪) on rows is implementation-dependent and determined by the Walker. The current table implementation throws by default on table rows, as recursive foreign key following can lead to circular references and infinite loops. Use the :recursive adverb to follow a foreign key once.

Walkers may choose to:

  • Throw an exception (default)

  • Treat it the same as the child operator () - following the FK once

  • Implement recursive foreign key following (with appropriate cycle detection)

  • Descend into only specific foreign keys based on labels provided as arguments

  • Provide an adverb to enable recursive behavior when explicitly requested

When labels are provided, the Walker can selectively descend into only those foreign keys:

my $row = get-row();
my @direct = $row ⪪ <fk_column>;        # Follows FK once
# my @descendants = $row ⪪⪪ <fk_column>; # Default: throws exception

# If Walker supports label filtering:
my @filtered = $row ⪪⪪ <fk_column1, fk_column2>;  # Only descend into specified FKs

If a Walker supports recursive foreign key following, it may provide an adverb:

# Walker-specific behavior (if supported)
my @recursive = $row ⪪⪪ :recursive <fk_column>;  # Recursively follows FKs

5.2.5 Parent Operator (⪫)

The parent operator () can take a :reference adverb to navigate back through foreign key relationships.

Table → Namespace: When used on a table, the parent operator () requires a right operand and returns the containing namespace (e.g., schema in PostgreSQL, database in MySQL, or equivalent in other systems):

my $table = get-table('users');
my $schema = $table ⪫ *;  # Returns the schema containing the table

Row → Table: When used on a row, the parent operator () requires a right operand and returns the table containing the row:

my $row = get-row();
my $table = $row ⪫ *;  # Returns the table containing the row

Row → Referencing Rows (with :reference adverb): When used on a row with the :reference adverb, the parent operator navigates back to the row(s) that reference this row via foreign keys:

my $customer = get-customer(123);
my @orders = $customer ⪫ * :reference;  # Returns all orders that reference this customer via FK

This is the inverse of the child operator () when used for foreign key navigation.

Return Value Semantics: The :reference adverb always returns an array/sequence of rows, regardless of how many tables or rows reference the current row:

  • Single reference: Array containing a single row

  • Multiple references from one table: Array containing multiple rows from the same table

  • Multiple references from multiple tables: Array containing rows from all referencing tables

Multiple Tables: When multiple tables have foreign keys pointing to the same row, the :reference adverb returns rows from all referencing tables. The order of rows in the result is implementation-dependent and may vary by Walker:

my $product = get-product(456);
# Product might be referenced by: orders, inventory, reviews, etc.
my @all-references = $product ⪫ * :reference;
# Returns: [order_row1, order_row2, inventory_row, review_row1, review_row2, ...]

# Filter by specific table
my @only-orders = @all-references σ { $_.table-name eq 'orders' };

5.2.6 Ancestor Operator (⪫⪫)

The ancestor operator (⪫⪫) can take a :reference adverb to navigate back through foreign key relationships.

Table → Namespace Hierarchy: When used on a table, the ancestor operator (⪫⪫) requires a right operand and returns the full namespace hierarchy (e.g., database → schema → table):

my $table = get-table('users');
my @hierarchy = $table ⪫⪫ *;  # Returns [database, schema, table] or equivalent

Row → Table: When used on a row, the ancestor operator (⪫⪫) requires a right operand and returns the table (same as the parent operator ):

my $row = get-row();
my $table = $row ⪫⪫ *;  # Returns the table containing the row

Row → Referencing Rows (with :reference adverb): When used on a row with the :reference adverb, the ancestor operator navigates back through foreign key relationships to find all rows that reference this row:

my $customer = get-customer(123);
my @all-referencing = $customer ⪫⪫ * :reference;  # Returns all rows that reference this customer via FKs

Parent vs. Ancestor with :reference: The exact behavior of ⪫⪫ with :reference is Walker-dependent and may:

  • Return the same as ⪫ :reference (direct references only)

  • Recursively follow foreign key chains to find all referencing rows

  • Throw an exception if recursive behavior is not supported

Return Value Semantics: Like the parent operator, the ancestor operator with :reference always returns an array/sequence of rows, including rows from all referencing tables.

5.2.7 Root Operator (⇤)

The root operator () is a unary postfix operator (unlike other navigation operators which are binary). It returns the root of the namespace hierarchy by walking up from the current node.

Table → Root of Namespace Hierarchy: When used on a table, the root operator returns the root of the namespace hierarchy:

my $table = get-table('users');
my $root = $table ⇤;  # Returns root of namespace hierarchy

Row → Root of Namespace Hierarchy: When used on a row, the root operator walks up through the table and namespace hierarchy to return the root:

my $row = get-row();
my $root = $row ⇤;  # Returns root of namespace hierarchy (walks up from table via namespace)

5.2.8 Following-Sibling Operator (⪨)

The following-sibling operator () returns the next sibling(s) after the current node in document order.

Table → Error: When used on a table, the following-sibling operator () requires a right operand but typically returns nothing or throws an error, as tables do not have a sibling relationship in the namespace hierarchy:

my $table = get-table('users');
# my @siblings = $table ⪨ *;  # Typically returns nothing or error

Row → Next Row (if ordered): When used on a row, the following-sibling operator () requires a right operand and returns the next row(s) in the table, if the table has a defined ordering. If the table is unordered or the row is the last row, it returns nothing:

my $row = get-row();
my @next-rows = $row ⪨ *;  # Returns next row(s) if table is ordered, empty otherwise

# With specific count
my $next-row = $row ⪨ 1;  # Returns the next single row

Ordering Requirements: The behavior of on rows is Walker-dependent and requires the table to have a defined ordering (e.g., primary key order, explicit sort order, or insertion order). If no ordering is defined, the operator may:

  • Return nothing (empty result)

  • Throw an exception

  • Use a default ordering (e.g., primary key)

Edge Cases:

  • Last row: Returns nothing

  • Unordered table: Behavior is Walker-dependent (may return nothing or error)

  • Multiple rows: The right operand can specify how many following siblings to return

5.2.9 Preceding-Sibling Operator (⪩)

The preceding-sibling operator () returns the previous sibling(s) before the current node in document order.

Table → Error: When used on a table, the preceding-sibling operator () requires a right operand but typically returns nothing or throws an error, as tables do not have a sibling relationship in the namespace hierarchy:

my $table = get-table('users');
# my @siblings = $table ⪩ *;  # Typically returns nothing or error

Row → Previous Row (if ordered): When used on a row, the preceding-sibling operator () requires a right operand and returns the previous row(s) in the table, if the table has a defined ordering. If the table is unordered or the row is the first row, it returns nothing:

my $row = get-row();
my @prev-rows = $row ⪩ *;  # Returns previous row(s) if table is ordered, empty otherwise

# With specific count
my $prev-row = $row ⪩ 1;  # Returns the previous single row

Ordering Requirements: Like the following-sibling operator, the behavior of on rows is Walker-dependent and requires the table to have a defined ordering. If no ordering is defined, the operator may:

  • Return nothing (empty result)

  • Throw an exception

  • Use a default ordering (e.g., primary key)

Edge Cases:

  • First row: Returns nothing

  • Unordered table: Behavior is Walker-dependent (may return nothing or error)

  • Multiple rows: The right operand can specify how many preceding siblings to return

5.2.10 Following Operator (⪨⪨)

The following operator (⪨⪨) returns all nodes that follow the current node in document order (not just siblings).

Table → Error: When used on a table, the following operator (⪨⪨) requires a right operand but typically returns nothing or throws an error:

my $table = get-table('users');
# my @following = $table ⪨⪨ *;  # Typically returns nothing or error

Row → All Following Rows (if ordered): When used on a row, the following operator (⪨⪨) requires a right operand and returns all rows that come after the current row in the table, if the table has a defined ordering. If the table is unordered, it returns nothing:

my $row = get-row();
my @all-following = $row ⪨⪨ *;  # Returns all rows after this row if table is ordered

# Combine with filtering
my @recent-following = $row ⪨⪨ * σ { ⥷ <created_at> > Date.today - 7 };

Ordering Requirements: The behavior of ⪨⪨ on rows is Walker-dependent and requires the table to have a defined ordering. The result includes all rows that appear after the current row according to the table's ordering.

Edge Cases:

  • Last row: Returns nothing

  • Unordered table: Behavior is Walker-dependent (may return nothing or error)

  • Empty result: If the row is the last row or the table is unordered

5.2.11 Preceding Operator (⪩⪩)

The preceding operator (⪩⪩) returns all nodes that precede the current node in document order (not just siblings).

Table → Error: When used on a table, the preceding operator (⪩⪩) requires a right operand but typically returns nothing or throws an error:

my $table = get-table('users');
# my @preceding = $table ⪩⪩ *;  # Typically returns nothing or error

Row → All Preceding Rows (if ordered): When used on a row, the preceding operator (⪩⪩) requires a right operand and returns all rows that come before the current row in the table, if the table has a defined ordering. If the table is unordered, it returns nothing:

my $row = get-row();
my @all-preceding = $row ⪩⪩ *;  # Returns all rows before this row if table is ordered

# Combine with filtering
my @old-preceding = $row ⪩⪩ * σ { ⥷ <created_at> < Date.today - 30 };

Ordering Requirements: The behavior of ⪩⪩ on rows is Walker-dependent and requires the table to have a defined ordering. The result includes all rows that appear before the current row according to the table's ordering.

Edge Cases:

  • First row: Returns nothing

  • Unordered table: Behavior is Walker-dependent (may return nothing or error)

  • Empty result: If the row is the first row or the table is unordered

5.2.12 Table Usage Examples

# Get all rows from a table
my @all-rows = $table ⪪ *;

# Follow a foreign key from a row
my $order = get-order(123);
my @customer = $order ⪪ <customer_id>;  # Get customer row(s)

# Get a column value
my $order-date = $order ⥷ <order_date>;

# Navigate through multiple relationships
my @order-items = $order ⪪ <order_id>;  # Assuming order_items table has FK to orders
my @products = @order-items[0] ⪪ <product_id>;  # Get products for first item

# Combine with filtering
my @recent-orders = $table ⪪ * σ { ⥷ <order_date> > Date.today - 30 };

# Navigate backwards with :reference adverb
my $customer = get-customer(123);
my @customer-orders = $customer ⪫ * :reference;  # All orders referencing this customer
my @recent-customer-orders = $customer ⪫ * :reference σ { ⥷ <order_date> > Date.today - 30 };

# Find all rows referencing a product
my $product = get-product(456);
my @all-references = $product ⪫ * :reference;  # Orders, inventory, reviews, etc.

# Navigate backwards through multiple relationships
my $category = get-category(789);
my @products = $category ⪪ *;  # Products in this category
my @all-referencing = $category ⪫⪫ * :reference;  # All rows referencing this category

# Sibling navigation (requires ordered table)
my $current-row = get-row();
my $next-row = $current-row ⪨ 1;  # Next row
my $prev-row = $current-row ⪩ 1;  # Previous row

# Following/preceding navigation (requires ordered table)
my @all-after = $current-row ⪨⪨ *;  # All rows after current
my @all-before = $current-row ⪩⪩ *;  # All rows before current

# Combine sibling navigation with filtering
my @recent-next = $current-row ⪨ * σ { ⥷ <status> eq 'active' };

5.3 Table Catalog, Schema Discovery, and Foreign Keys

Table-domain navigation requires a Qwiratry::Table::Catalog that names tables, declares foreign keys, and optionally designates an active table for row scans. Build catalogs explicitly or let discover-catalog infer them from your data root.

Use givenroot $root { ... } when a query expression starts from a row or node but must resolve navigation against a larger data root. The root term returns the active data root inside that dynamic scope. Load Qwiratry::Mold::Slang after Qwiratry::Query::Slang for this statement syntax. The statement form currently accepts a variable root.

5.3.1 Explicit catalog with make-catalog

use Qwiratry::Table;
use Qwiratry::Query::Slang;
use Qwiratry::Mold::Slang;

my @orders = [
    %(order_id => 1, customer_id => 10),
    %(order_id => 2, customer_id => 20),
];  # use a trailing comma inside [%(...),] for single-row tables
my @customers = [
    %(customer_id => 10, name => 'Alice'),
    %(customer_id => 20, name => 'Bob'),
];

my $catalog = make-catalog(
    :orders(@orders), :customers(@customers),
    :foreign-keys(
        ForeignKey.new(
            :from-table('orders'), :from-column('customer_id'),
            :to-table('customers'), :to-column('customer_id'),
        ),
    ),
    :active-table('orders'),
);

my $order = @orders[0];
my @customer;
givenroot $catalog {
    @customer = ($order ⪪ <customer_id>).List;
}
# → one row: %(customer_id => 10, name => 'Alice')

5.3.2 Inferred foreign keys from multi-table roots

When the origin is an Associative whose values are row collections, discover-catalog builds a catalog and infer-foreign-keys guesses links from orders.customer_idcustomers.customer_id style naming:

use Qwiratry::Table::Schema;
use Qwiratry::Mold::Slang;

my %database = %(orders => @orders, customers => @customers);
my @related;
givenroot %database {
    @related = ($order ⪪ <customer_id>).List;
}

5.3.3 Attached schema via providing metadata

Attach schema to a single table container when the root is not a multi-table map:

use Qwiratry::Table::Schema;
use Qwiratry::Mold::Slang;

attach-schema(@orders, %(
    table-name => 'orders',
    tables => %(orders => @orders, customers => @customers),
));

my @related;
givenroot @orders {
    @related = (@orders[0] ⪪ <customer_id>).List;
}

5.3.4 Table-row navigation semantics (current implementation)

OperatorOn table/catalogOn row
All active-table rows (*)Follow FK for named column; empty if non-FK or null FK
N/AColumn value (does not follow FK)
Parent container (the table array)Parent table; add :reference for reverse FK lookup
⪪⪪Same as on active tableThrows unless :recursive adverb is present
⪪⪪ :recursiveFollow FK once (no unbounded recursion)
/ EmptyNext/previous row by positional index in active table
⪨⪨ / ⪩⪩EmptyAll rows after/before current row in active table

Reverse FK (⪫ :reference):

my $customer = @customers[0];
my @orders-for-customer;
givenroot $catalog {
    @orders-for-customer = ($customer ⪫ <*> :reference).List;
}
# → both orders with customer_id == 10

5.4 Implementation Notes

  • Axis operators are implementation-dependent - different Walkers may implement them differently:

    • They may compose iterators to retrieve nodes along the specified axis

    • They may be translated into other query languages (e.g., SQL for database walkers)

    • They may use domain-specific traversal strategies

  • In normal code, query slang operators evaluate through Qwiratry::Query::Runtime and return lazy Seq results

  • Use query-ast({ ... }) when a Walker, Mold, planner, or explicit runtime call needs raw operator AST

  • Raw operators do not perform traversal themselves; they rely on Walkers or the query runtime to interpret and execute them

  • Predicates and combinators (like union , intersection , difference ) can be applied after or during operator execution

6. Set Operators

Set Operators work on collections of elements. This includes anything that can do Associative, whether table-based, tree nodes (eg. results from navigation operators), or just regular arrays of items. These operators are based on relational algebra and set theory, and include both relational/tuple operations and column-based operations.

6.1 Relational/Tuple Operators

Relational/Tuple Operators work on collections of elements that can do Associative. This includes table-based structures, tree nodes (e.g., results from navigation operators), or regular arrays of items.

6.1.1 Operators Returning Bool

These operators compare collections or test membership, returning boolean results.

6.1.1.1 Membership Operators
OperatorUnicodeInverseInverse NotOperandsDescription
element of/contains∈ ∊ (elem)∋ ∍ (cont)∉ ∌A Collection and an ElementTests if an element is a member of a collection
6.1.1.2 Subset/Superset Operators
OperatorUnicodeInverseInverse NotOperandsDescription
strict subset/superset⊂ (<)⊃ (>)⊄ ⊅CollectionsTests if one collection is a strict subset/superset of another
subset/superset or equal⊆ (<=)⊇ (>=)⊈ ⊉CollectionsTests if one collection is a subset/superset or equal to another
6.1.1.3 Identity Operator
OperatorUnicodeInverseOperandsDescription
identity≡ (==)CollectionsTests if two collections are identical

6.1.2 Operators Returning Collections

These operators combine collections to produce new collections.

6.1.2.1 Basic Set Operations
OperatorUnicodeSet Theory TermBoolean Algebra TermDescription
U+2229IntersectionANDReturns elements present in both collections
U+222AUnionORReturns elements present in either collection
U+2296Symmetric Set DifferenceXORReturns elements in exactly one collection
U+229FSet DifferenceAND-NOTReturns elements in left collection but not in right
U+2216Set Difference (alias)AND-NOTAlias of ; same glyph Raku uses for set minus

Glyph families: circled operators ( / ) are the symmetric-difference pair; squared operators ( / ) are the (asymmetric) set-difference pair. remains as an alias of .

6.1.2.2 Universe-based Operators

These operators require a Universe set that encompasses both collections. The context variable $_ must be a collection that's a superset of both operands.

OperatorUnicodeSet Theory TermBoolean Algebra TermDescription
U+2A43Intersection ComplementNANDReturns elements not in the intersection
U+2A42Union ComplementNORReturns elements not in the union
U+2299Symmetric Set Difference ComplementXNORReturns elements not in the symmetric difference
U+22A1Set Difference ComplementIMPLYReturns elements not in the set difference
U+2201ComplementNOTReturns elements not in the collection (unary)

6.1.3 Operators Returning Array[Collection]

OperatorUnicodeSet Theory TermDescription
𝒫U+1D4ABPower SetUnary. Makes a set whose members are all possible subsets of the collection

6.1.4 Collection Types and Semantics

Set operators work with any collection that can do Associative. The semantics depend on the collection type:

OperatorGeneral SemanticsNotes
∈ ∊Tests if an element is in a collectionElement identity depends on collection type
⊂ ⊆Tests if one collection is a subset of anotherBoth collections must be of compatible types
Returns elements present in both collectionsElement matching depends on collection type
Returns elements present in either collectionDuplicate handling depends on collection type
Returns elements in exactly one collectionElement matching depends on collection type
Returns elements in left collection but not in rightElement matching depends on collection type
𝒫Returns all possible subsets of the collectionWorks with any collection type

Collection Type Considerations:

  • Tables/Relations: Element identity is typically determined by value equality of all columns/fields

  • Tree nodes: Element identity is typically determined by object identity or a Walker-defined equality

  • Arrays: Element identity is typically determined by value equality

  • Composability: Navigation operator results (tree nodes) can be combined with set operators to create complex queries

  • Type compatibility: Set operations generally require compatible collection types, though Walkers may provide type coercion

6.1.5 Usage Examples

Table/Relation Examples:

# Test membership
if $tuple ∈ $relation { ... }

# Test subset
if $relation1 ⊆ $relation2 { ... }

# Intersection
my $common = $relation1 ∩ $relation2;

# Union
my $combined = $relation1 ∪ $relation2;

# Set difference
my $only-in-first = $relation1 ⊟ $relation2;

# Power set
my @subsets = 𝒫 $relation;

Tree Node Examples:

# Union of tree navigation results
my @combined = ($root ⪪ <item>) ∪ ($root ⪪ <product>);

# Intersection of tree queries
my @common = ($root ⪪⪪ <div>) ∩ ($root ⪪⪪ <span>);

# Set difference: nodes in first query but not second
my @unique = ($root ⪪ <item>) ⊟ ($root ⪪ <item> σ { ⥷ <archived> });

# Test membership: is this node in the set?
if $node ∈ ($root ⪪⪪ <item>) { ... }

# Test subset: are all active items also in the items set?
if ($root ⪪ <item> σ { ⥷ <active> }) ⊆ ($root ⪪ <item>) { ... }

# Combine navigation with set operations
my @mixed = ($root ⪪ <item>) ∪ ($root ⥷ <metadata>);

# Power set of tree nodes
my @subsets = 𝒫 ($root ⪪ <item>);

Array Examples:

# Union of arrays
my @combined = @array1 ∪ @array2;

# Intersection
my @common = @array1 ∩ @array2;

# Set difference
my @only-in-first = @array1 ⊟ @array2;

# Test membership
if $item ∈ @array { ... }

# Test subset
if @subset ⊆ @array { ... }

6.2 Column-based Operators

Column-based Operators manipulate the structure of relations by selecting, renaming, or combining columns.

6.2.1 Basic Operators

OperatorUnicodeRelational Algebra TermDescription
ΠU+03A0ProjectionChoose fields; equivalent to field selection in SQL. May combine with subtraction to select "all fields except..."
ρU+03C1RenameRename fields; equivalent to AS statement in SQL

6.2.2 Sub-Join Operators

These operators return rows from one relation based on matches with another, but don't include rows from the other relation.

OperatorUnicodeRelational Algebra TermDescription
U+22C9Left SemijoinIncludes rows from left table that match right table (but no rows from right)
U+22CARight SemijoinIncludes rows from right table that match left table (but no rows from left)
U+25B7Left AntijoinIncludes rows from left relation which do NOT have a match in the right relation
U+25C1Right AntijoinIncludes rows from right relation which do NOT have a match in the left relation

6.2.3 Basic Join Operators

These operators combine rows from two relations based on matching conditions.

OperatorUnicodeRelational Algebra TermDescription
U+2A1DInner JoinOnly include rows that appear in both relations. Mathematically, this is Natural Join
U+27D5Left Outer JoinInclude all rows in left relation, and any that match in right relation
U+27D6Right Outer JoinInclude all rows in right relation, and any that match in left relation
U+27D7Full Outer JoinInclude all rows in both relations, matching up where possible

6.2.4 Other Join Operators

OperatorUnicodeRelational Algebra TermDescription
▷=◁EquijoinLike inner join, but only = comparison allowed
U+225CNatural JoinLike equijoin, but column names must be the same in both tables
÷U+00F7DivisionCreates a relation listing every item in A that matches all elements in B
U+25B5Named JoinUnary prefix operator that takes a Join operand (for recalling a join)
×U+00D7Cross JoinJoins every record in left operand to every record in right operand

6.2.5 Usage Examples

# Projection - select specific columns
my $projected = Π <name, age> $relation;

# Rename columns
my $renamed = ρ <old_name => new_name> $relation;

# Inner join
my $joined = $relation1 ⨝ $relation2;

# Left outer join
my $left_joined = $relation1 ⟕ $relation2;

# Natural join
my $natural = $relation1 ≜ $relation2;

# Cross join (Cartesian product)
my $cartesian = $relation1 × $relation2;

Note: Cross join uses U+00D7 (×, multiplication sign). Raku also uses × for numeric multiplication (for example 3 × 4). Query slang defines typed multis on relations and query operators so cross join does not override integer math in the same compunit.

7. Operator Composition and Usage

7.1 Composition Patterns

Operators in Qwiratry are designed to be highly composable. They can be combined in several ways:

7.1.1 Chaining

Operators can be chained to form path expressions:

# Navigate through tree structure
my @results = $root ⪪⪪ <div> ⪪ <span>;

# Combine tree navigation with filtering
my @filtered = $root ⪪⪪ * σ { $_.name eq 'item' };

7.1.2 Set Operations

Multiple query results can be combined using set operators:

# Union of two queries
my @combined = ($root ⪪ <item>) ∪ ($root ⥷ <metadata>);

# Intersection
my @common = $query1 ∩ $query2;

# Difference
my @unique = $query1 ⊟ $query2;

7.1.3 Predicates

Operators can be combined with predicates (blocks) for filtering:

# Tree navigation with predicate
my @items = $root ⪪ <item> σ { $_.value > 10 };

# Relational selection
my $filtered = σ { $_.age > 18 && $_.active } $relation;

7.2 Integration with Walkers

Operators are interpreted by Walkers during the plan() phase:

  1. Query AST Construction: Operators form Query AST nodes

  2. Walker Planning: Walker analyzes the AST and creates an execution plan

  3. QueryIterator Execution: Plan produces a QueryIterator that yields results

Different Walkers may interpret the same operator differently:

  • Tree::Walker::DFS interprets as tree child navigation

  • Table::Walker::Scan may interpret as relation navigation

  • Logic::Walker::Backward may interpret operators as goal patterns

7.2.1 Default Tree Walker Traversal Behavior

The default tree walker (when no specific walker is specified) treats data structures as follows:

  • Objects that do Positional as having children

  • Objects that do Associative as having attributes

  • Objects with .parent method for up-navigation (error if not available)

This produces a flexible, "duck-typed DOM" suitable for Raku's heterogeneous data structures. However, different walkers may implement different data models. For example:

  • A JSON walker may treat JSON objects differently than the default

  • A database walker may map operators to SQL queries

  • A custom walker may define its own traversal semantics

The traversal behavior described here applies only to the default tree walker implementation.

7.3 Integration with Transformers

Operators can be used in Transformer molds:

transformer MyTransformer {
    # Use operators in when clause - uses $_ automatically
    mold item() when { ⪪⪪ <item> } do {
        make transform-item($_);
    }
    
    # Combine with predicates - uses $_ automatically
    mold active-item() when { 
        ⪪ <item> σ { .active }
    } do {
        make process-active($_);
    }
}

7.4 Domain-Specific Semantics

The same operator may have different semantics depending on the data model:

  • Tree Model: navigates to child nodes

  • Table Model: may navigate relations

  • Graph Model: may follow edges

Walkers are responsible for interpreting operators according to their domain.

7.5 Optimization Opportunities

Since operators are Query AST nodes, Walkers can:

  • Introspect operator structure for optimization

  • Push down predicates to data sources

  • Reorder operations for efficiency

  • Cache intermediate results

  • Parallelize independent operations

Example optimization:

# Original query
my $query = $root ⪪⪪ * σ { $_.name eq 'item' } σ { $_.value > 10 };

# Optimized: combine predicates
my $optimized = $root ⪪⪪ * σ { $_.name eq 'item' && $_.value > 10 };

7.6 Lazy Evaluation

Query execution is pull-based: query operators and query-runtime.select() return lazy Seq values, and Walkers produce QueryIterator instances that yield one result per pull-one / next call.

Default query operators:

use Qwiratry::Query::Slang;

my @rows = [%(score => 1), %(score => 5), %(score => 3)];
my $seq = @rows σ -> $_ { $_<score> >= 3 };  # lazy Seq — no rows evaluated yet
my $first = $seq.first;                      # pulls until first match

Raw AST plus select / select-seq (Qwiratry::Query::Runtime):

use Qwiratry::Query::Slang;
use Qwiratry::Query::Runtime;
my constant query-runtime = Qwiratry::Query::Runtime.instance;

my @rows = [%(score => 1), %(score => 5), %(score => 3)];
my $query = query-ast({ @rows σ -> $_ { $_<score> >= 3 } });

my $seq = query-runtime.select($query, @rows);   # lazy Seq — no rows evaluated yet
my $first = $seq.first;            # pulls until first match

Lazy set operators: UnionOperator, IntersectionOperator, and join operators (, , etc.) use lazy iterators — operands are not fully scanned before the first result is returned. Avoid calling .list on a select result unless you intend to materialize the full result set.

Walker iterators: walker.plan($query, $root).iterator creates an independent QueryIterator per call. The Table Walker scans rows via indexed access when a Strategy is attached; Master composite iterators pull incrementally from subplan iterators.

I/O pipelines: Qwiratry::Operator::Capability uses select-seq internally. Render and destination steps materialize at the pipeline boundary; upstream selection stays lazy.

use Qwiratry::Operator::Capability;

my $query = query-ast({ @rows σ -> $_ { $_<score> >= 3 } });
my $result = execute($query, :origin(@rows));  # materializes at pipeline end

8. Summary

Qwiratry operators provide a comprehensive set of tools for querying and transforming data:

  1. I/O Operations - Parsing, rendering, and external data access

  2. Aggregation - Selection, sorting, mapping, and reduction

  3. Data Navigation - Axis-based traversal for hierarchical structures

  4. Set Operations - Set theory and relational algebra for tables/relations, including column manipulation, projection, renaming, and joins

All operators are:

  • Declarative - Specify intent, not implementation

  • Composable - Can be combined to form complex queries

  • Domain-Flexible - Interpreted by Walkers according to data model

  • Lazy - Results produced incrementally via QueryIterator

  • Introspectable - Can be analyzed and optimized by Walkers

This design enables a unified query interface that works across trees, tables, graphs, and other structured data models while maintaining the flexibility for domain-specific optimizations and interpretations.

Qwiratry v0.0.9

Declarative query and data-walking architecture for Raku, with transformers, molds, walkers, and I/O pipelines.

Authors

  • Tim Nelson

License

Dependencies

SlangifyImplementation::Loader:ver<0.0.9+>Glob::Grammar

Test Dependencies

Provides

  • Qwiratry
  • Qwiratry::Context
  • Qwiratry::Format
  • Qwiratry::Format::Base
  • Qwiratry::Format::CSVdemo
  • Qwiratry::Format::JSONdemo
  • Qwiratry::Location
  • Qwiratry::Location::Base
  • Qwiratry::Location::File
  • Qwiratry::Mold
  • Qwiratry::Mold::Compiler
  • Qwiratry::Mold::Registry
  • Qwiratry::Mold::Slang
  • Qwiratry::Operator::Capability
  • Qwiratry::Operator::IO
  • Qwiratry::Operator::MapReduce
  • Qwiratry::Operator::Navigation
  • Qwiratry::Operator::Set
  • Qwiratry::Query::Evaluator::Eager
  • Qwiratry::Query::Evaluator::Filter
  • Qwiratry::Query::Evaluator::Join
  • Qwiratry::Query::Evaluator::Lazy
  • Qwiratry::Query::Evaluator::MapReduce
  • Qwiratry::Query::Evaluator::Navigation
  • Qwiratry::Query::Evaluator::Relational
  • Qwiratry::Query::Evaluator::Row
  • Qwiratry::Query::Evaluator::Set
  • Qwiratry::Query::Evaluator::Union
  • Qwiratry::Query::Extract
  • Qwiratry::Query::NamedJoins
  • Qwiratry::Query::RelationCommon
  • Qwiratry::Query::Runtime
  • Qwiratry::Query::Selector
  • Qwiratry::Query::Slang
  • Qwiratry::Query::Specificity
  • Qwiratry::Query::Topic
  • Qwiratry::QueryIterator
  • Qwiratry::QueryMatch
  • Qwiratry::Setup
  • Qwiratry::Strategy
  • Qwiratry::Strategy::ControlSignal
  • Qwiratry::Strategy::FinishResult
  • Qwiratry::Strategy::RewriteSpec
  • Qwiratry::Strategy::Traversal
  • Qwiratry::Suggest
  • Qwiratry::Table
  • Qwiratry::Table::Schema
  • Qwiratry::Transformer
  • Qwiratry::Transformer::Copy
  • Qwiratry::Transformer::TreeRewrite
  • Qwiratry::Tree::Navigator
  • Qwiratry::Tree::Navigator::Base
  • Qwiratry::Tree::Navigator::Filesystem
  • Qwiratry::Tree::Navigator::Match
  • Qwiratry::Tree::Navigator::RakuAST
  • Qwiratry::Tree::Replace
  • Qwiratry::Walker
  • Qwiratry::Walker::Capabilities
  • Qwiratry::Walker::Factory
  • Qwiratry::Walker::Implementation::Table
  • Qwiratry::Walker::Implementation::Tree
  • Qwiratry::Walker::Master
  • Qwiratry::Walker::Providing
  • X::Qwiratry

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.