WOQL Cookbook: Pattern generation

Open inAnthropic

When faced with combinatorial problems, it is hard to know where to start. Using a logic engine to exhaust possible solutions is a novel way to approach such problems, and leverages an engine to do the hard work for us.

In this example we will explore specifically how the substr() predicate can be used to generate all possible substrings of a string using rules.

Example of pattern generation

A simple pattern that shows the pattern generation is the substr() predicate:

Example: WOQL
substr(string, before, length, after, subString)

Introducing pattern generation with two simple examples

Notice that there is only one solution for example 1 below, and two solutions for example 2, as the possible solutions for the open ended variables will be generated automatically by the engine.

Code: Example 1 of substr

Example: JavaScript
substr("string", 2, 2, "v:after", "ri")

This returns 2, as it has 2 characters before the substring ri, and we use 2 characters of the substring ri.

Code: Example 2 of substr

Example: JavaScript
substr("string", "v:before", 5, "v:after", "v:subString")

Now, this query will return two solutions:

  • First solution has before=0 and after=1, and subString="strin"
  • Second solution has before=1 and after=0, and subString="tring"

Combining the pattern generation with rules

Let's increase the complexity of the solution by adding rules for the allowed solutions and make the string a bit more complex to match against.

Note that values in TerminusDB default to being treated as IRIs, unless specifically typed as specific literals, or that the context makes a specific choice for how to interpret a parameter, such as when supplying a pattern to match to substr().

What we are doing here is matching a string that has a pattern of 8 groups of 4 digits separated by hyphens. We want to get one solution per number as an integer, and know the string positions where that number was found.

By matching on - we can filter out substrings that do not include a hyphen.

Code: Example 3 of substr

Example: WOQL
select().and(
  // Let variable string have the string of numbers
  eq("v:string", literal("0000-0001-0002-0003-0004-0005-0006-0007", "xsd:string")),

  // Get every possible substring of 4 characters
  substr("v:string", "v:start", 4, "v:end", "v:str"),
  // Filter out substrings with a hyphen and convert to integer
  and(
      not().substr("v:str", "v:n_1", "v:n_2", "v:n_3", "-"),
      typecast("v:str", "xsd:integer", "v:number")
  )
)

Here is the result of the logic.

000000
150001
2100002
3150003
4200004
5250005
6300006
7350007

Temporal and numeric pattern generation

Beyond string patterns, WOQL provides a rich set of predicates that generate sequences of dates, numbers, and other temporal values. These predicates exploit the same pattern-generation principle: leave a variable unbound and the engine exhausts every valid solution.

Sequence generation

The sequence() predicate generates values in a half-open range [start, end). It supports integers, decimals, doubles, dates, dateTimes, gYearMonth, gYear, time, gMonth, gDay, and gMonthDay. An optional step controls the increment (defaults to 1 in the natural unit of the type).

Generate integers 1 through 5

Example: JavaScript
WOQL.sequence("v:n",
  literal(1, "xsd:integer"),
  literal(6, "xsd:integer"))

Returns five results: n = 1, 2, 3, 4, 5.

Generate dates in January 2025

Example: JavaScript
WOQL.sequence("v:date",
  literal("2025-01-01", "xsd:date"),
  literal("2025-02-01", "xsd:date"))

Returns 31 results, one per day. Each date is an xsd:date value.

Generate every other day with a step

Example: JavaScript
WOQL.sequence("v:date",
  literal("2025-01-01", "xsd:date"),
  literal("2025-01-10", "xsd:date"),
  literal(2, "xsd:integer"))

Returns dates: Jan 1, Jan 3, Jan 5, Jan 7, Jan 9.

Generate months in the first half of 2025

Example: JavaScript
WOQL.sequence("v:ym",
  literal("2025-01", "xsd:gYearMonth"),
  literal("2025-07", "xsd:gYearMonth"))

Returns six gYearMonth values: 2025-01 through 2025-06.

Weekday classification

weekday() returns the ISO 8601 day-of-week number (Monday = 1, Sunday = 7). weekday_sunday_start() uses the US convention (Sunday = 1, Saturday = 7).

Get the weekday for a specific date

Example: JavaScript
WOQL.weekday(literal("2025-01-06", "xsd:date"), "v:dow")

Returns dow = 1 (Monday).

Combine sequence + weekday to find all Fridays in January

Example: JavaScript
WOQL.and(
  WOQL.sequence("v:date",
    literal("2025-01-01", "xsd:date"),
    literal("2025-02-01", "xsd:date")),
  WOQL.weekday("v:date", "v:dow"),
  WOQL.eq("v:dow", literal(5, "xsd:integer"))
)

Returns every Friday in January 2025.

ISO week number

iso_week() computes the ISO 8601 week-numbering year and week number for a date.

Example: JavaScript
WOQL.iso_week(literal("2025-01-01", "xsd:date"), "v:year", "v:week")

Returns year = 2025, week = 1. Note: the ISO year may differ from the calendar year near year boundaries.

Day navigation

day_after() and day_before() compute the next or previous calendar day. Both are bidirectional — give either argument and the engine computes the other.

Example: JavaScript
WOQL.day_after(literal("2025-01-31", "xsd:date"), "v:next")

Returns next = 2025-02-01. Month and year boundaries are handled automatically.

Example: JavaScript
WOQL.day_before(literal("2025-03-01", "xsd:date"), "v:prev")

Returns prev = 2025-02-28 (or Feb 29 in a leap year).

Month boundary helpers

month_start_date() and month_end_date() convert a gYearMonth to the first or last day of that month.

Example: JavaScript
WOQL.and(
  WOQL.month_start_date(literal("2025-02", "xsd:gYearMonth"), "v:first"),
  WOQL.month_end_date(literal("2025-02", "xsd:gYearMonth"), "v:last")
)

Returns first = 2025-02-01, last = 2025-02-28. Leap years are handled correctly.

month_start_dates() and month_end_dates() are generators that produce every first-of-month or last-of-month date in a range.

Example: JavaScript
WOQL.month_end_dates("v:eom",
  literal("2025-01-01", "xsd:date"),
  literal("2025-07-01", "xsd:date"))

Returns six results: Jan 31, Feb 28, Mar 31, Apr 30, May 31, Jun 30.

Duration arithmetic

date_duration() is tri-directional: given any two of start, end, and duration, it computes the third.

Compute a duration between two dates

Example: JavaScript
WOQL.date_duration(
  literal("2025-01-01", "xsd:date"),
  literal("2025-04-01", "xsd:date"),
  "v:duration")

Returns duration = P90D (or equivalent).

Compute an end date from start + duration

Example: JavaScript
WOQL.date_duration(
  literal("2025-03-31", "xsd:date"),
  "v:end",
  literal("P40D", "xsd:duration"))

Returns end = 2025-05-10.

Interval construction

interval() constructs or deconstructs an xdd:dateTimeInterval from start and end dates. interval_start_duration() and interval_duration_end() relate an interval to a start or end point and a duration.

Example: JavaScript
WOQL.interval(
  literal("2025-01-01", "xsd:date"),
  literal("2025-04-01", "xsd:date"),
  "v:iv")

Returns an xdd:dateTimeInterval value representing Q1 2025.

Allen's interval algebra

interval_relation_typed() classifies the temporal relationship between two intervals using Allen's 13 interval relations (before, meets, overlaps, starts, during, finishes, equals, and their inverses).

Example: JavaScript
WOQL.interval_relation_typed("v:rel",
  literal("2025-01-01/2025-04-01", "xdd:dateTimeInterval"),
  literal("2025-04-01/2025-07-01", "xdd:dateTimeInterval"))

Returns rel = "meets" — the first interval ends exactly where the second begins.

interval_relation() does the same on raw start/end pairs without constructing interval values.

Range comparison

range_min() and range_max() find the minimum or maximum value in a list. They work with any comparable type: numbers, dates, strings.

Example: JavaScript
WOQL.and(
  WOQL.range_min([
    literal("2025-05-10", "xsd:date"),
    literal("2025-05-05", "xsd:date"),
    literal("2025-05-15", "xsd:date")
  ], "v:earliest"),
  WOQL.range_max([
    literal("2025-05-10", "xsd:date"),
    literal("2025-05-05", "xsd:date"),
    literal("2025-05-15", "xsd:date")
  ], "v:latest")
)

Returns earliest = 2025-05-05, latest = 2025-05-15.

Range checking

in_range() tests whether a value falls within a half-open range [start, end).

Example: JavaScript
WOQL.in_range(
  literal("2025-03-15", "xsd:date"),
  literal("2025-01-01", "xsd:date"),
  literal("2025-04-01", "xsd:date"))

Succeeds (one result) because March 15 is within Q1.

Composing patterns

The real power comes from combining these predicates. Here is a composed pattern that finds the last business day of each month in H1 2025:

Example: JavaScript
WOQL.and(
  WOQL.month_end_dates("v:eom",
    literal("2025-01-01", "xsd:date"),
    literal("2025-07-01", "xsd:date")),
  WOQL.weekday("v:eom", "v:dow"),
  WOQL.lte("v:dow", 5)
)

month_end_dates generates month-end dates, weekday classifies each, and lte filters to weekdays only (Mon-Fri). Months whose last day is a Saturday or Sunday are excluded — combine with day_before to step backward to the preceding Friday if needed.

Conclusion

The examples show how to use pattern generation to match against string patterns and extract values from them, as well as how to generate and compose temporal sequences, date arithmetic, interval algebra, and range operations. Every possible solution is generated automatically by the engine to match against.

Was this helpful?