8000 Make the recursion round limit (max_iterations) configurable per query and in OpenOptions · Issue #861 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

Make the recursion round limit (max_iterations) configurable per query and in OpenOptions #861

Closed

Description

@adityamukho

Problem

A recursive rule query stops with INT-020 after 1,000 semi-naive rounds. The limit is a hard-coded constant, DEFAULT_MAX_ITERATIONS = 1000 (src/query/datalog/evaluator.rs). The executor always passes it to the recursive operator (src/query/datalog/executor.rs, Limits { max_iterations: DEFAULT_MAX_ITERATIONS, .. }). Neither OpenOptions nor any query keyword can change it.

The #841 shape sweeps (PR #860) measured the effect. When a rule is called with a literal argument, magic-set demand shares the rules' stratum, so a chain takes about two rounds per edge. Recursion with the default limit therefore stops at 500-edge chains: 500 edges answer, 501 return INT-020. Raising max_derived_facts doesn't help, because that limit counts facts, not rounds.

This isn't a correctness bug, since the query fails loudly rather than returning partial rows. It does make deep hierarchies, long provenance chains and long linked lists unqueryable by recursion, with no workaround.

Proposal

Make the round limit configurable at both levels, following the existing max_derived_facts pattern:

  • Open level: OpenOptions::max_iterations(n), with a max_iterations field defaulting to 1000. OpenOptions is #[non_exhaustive], so the change is additive.

  • Query level: a :max-iterations N query keyword that overrides the database value for one query. It is validated like :max-derived-facts (positive integer; existing INT-012..015-style errors, or new codes if more fitting).

  • Prepared queries use the database value unless the prepared text sets :max-iterations, as they do for max_derived_facts (v2.x: prepared queries ignore OpenOptions max_derived_facts / max_results #679). A prepared query also needs a way to set the limit without re-preparing:

    Whichever form is chosen should apply equally to :max-derived-facts. Today that keyword takes only a literal (INT-015 for anything else), so a prepared query can't vary it per execution either.

  • The INT-020 message names which limit was reached (rounds or derived facts) and how to raise it.

Open questions

  • Gating: a query-level :max-iterations should be allowed only when OpenOptions::allow_overrides lists MaxIterations (see Audit which OpenOptions settings should be overridable per query (and which already are) #862).

  • Should the per-query value be able to raise the limit above the database value, or only lower it? :max-derived-facts currently replaces the database value.

  • Should the default change? With max_derived_facts and query_memory_limit (QRY-012) already bounding work, the round limit mainly catches non-terminating rules, and none should exist after semi-naive dedup. One option is a much higher default.

  • Should rounds be counted per stratum or per query? Today the count is per recursive operator.

Scope

  • Core: OpenOptions, the parser (plus tests/grammar/grammar.pest, GRAMMAR.md, corpus and verifier TOKENS, per the syntax-change rule), executor and prepared paths, and docs/ERROR_REFERENCE.md if any codes change.
  • Bindings: expose the open option in every binding that exposes max_derived_facts.
  • Docs site: datalog-reference.md (the keyword) and performance-tuning.md (the 500-edge note from Bench: large-scale shape sweeps and recursive optimization isolation #841, which should then point to the option).
  • Tests: a 501-edge chain fails at the default limit and succeeds with a raised open-level limit and with a raised query-level limit; a lowered query-level limit fails early.

Found during #841.

Activity

  1. added this to the v3.next milestone on Nov 5, 2026
  2. added
    enhancementNew feature or request
    rustPull requests that update rust code
    on Nov 5, 2026
  3. adityamukho commented on Nov 5, 2026

    @adityamukho
    CollaboratorAuthor

    Related audit of the other OpenOptions overrides: #862.

  4. modified the milestones: v3.next, v3.0.0 on Nov 5, 2026
  5. adityamukho commented on Nov 6, 2026

    @adityamukho
    CollaboratorAuthor

    Blocked by #862: the query-level :max-iterations keyword is gated by OpenOptions::allow_overrides, which #862 introduces.

  6. adityamukho commented on Nov 6, 2026

    @adityamukho
    CollaboratorAuthor

    Decisions (2026-11-06):

  7. added 2 commits that reference this issue on Nov 6, 2026
  8. adityamukho commented on Nov 6, 2026

    @adityamukho
    CollaboratorAuthor

    Done in #875 (v3) and minigraf-docs#93.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestrustPull requests that update rust code

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0