Skip to content

Run Cypher queries from the rdx console - #883

Open
paracycle wants to merge 1 commit into
mainfrom
uk_rdx_console_query_mode
Open

Run Cypher queries from the rdx console#883
paracycle wants to merge 1 commit into
mainfrom
uk_rdx_console_query_mode

Conversation

@paracycle

@paracycle paracycle commented Jun 25, 2026

Copy link
Copy Markdown
Member

Goal

Let rdx console run Cypher directly, addressing the "query mode" idea from #868's review.

What it does

The session is a stock IRB workspace with graph in scope, so plain Ruby works as always (graph["Foo"]). On top of that it registers three things through IRB's own extension APIs:

# `run(<CYPHER>)` — a helper method, so the result is a value you can assign and navigate.
rubydex(main):001> rows = run("MATCH (c:Class)-[:HAS_PARENT]->(p) RETURN c, p")
rubydex(main):002> rows.first["c"].name

# `query <CYPHER>` — a command taking the rest of the line verbatim; prints a formatted table.
rubydex(main):003> query MATCH (n:Class|Module) RETURN n.name ORDER BY n.name

# `schema` — prints the queryable schema.
rubydex(main):004> schema

query takes the rest of the line verbatim, so the Cypher needs neither quoting nor to be valid Ruby. Rather than swapping the REPL's evaluator for a modal toggle, Cypher lines are simply prefixed with query — the clean, idiomatic IRB realization of a query mode. run is the programmatic entry point and query the interactive quick-look; they are deliberately separate rather than one delegating to the other.

How it hooks into IRB

IRB has two distinct registration APIs, and this PR uses each for what it's for:

  • IRB::Command.register — for query/schema, which consume the rest of the line verbatim.
  • IRB::HelperMethod.register — for run, which must stay an ordinary expression so its value can be assigned.

The session workspace is IRB's own IRB::WorkSpace.new, not a hand-rolled binding. That matters for three reasons:

  • self is main and the cref is Object, so class Foo; end at the prompt defines ::Foo. A binding captured inside class << self would define #<Class:Rubydex::Console>::Foo instead.
  • No caller locals leak into the prompt. Deriving the binding from the CLI's TOPLEVEL_BINDING would inherit every top-level local of exe/rdx — including query and schema, and a local shadows the IRB command of the same name.
  • _, help and the rest of IRB's conveniences behave exactly as in stock irb.

Only graph is injected, via IRB::WorkSpace#local_variable_set.

Notes

  • Console logic lives in lib/rubydex/console.rb. Console.run (objects), Console.render (formatted string) and Console.describe_schema are plain module methods, so they are testable without driving IRB.
  • Both IRB APIs used here landed in IRB 1.13, so the file activates gem "irb", ">= 1.13" at load. A missing or older IRB raises LoadError (Gem::MissingSpecVersionError is one), which exe/rdx turns into Interactive mode requires \irb` >= 1.13 to be in the bundle` rather than a backtrace. IRB is not declared in the gemspec, which carries no runtime dependencies.
  • run returns the rich objects from Query#run (Return Cypher query results as graph objects #873): node columns come back as live Declaration/Definition/Document handles.

Verification

  • test/console_test.rb — 8 runs, 29 assertions, 0 failures. Includes a regression test that the session workspace evaluates at top level (self == main, only graph injected, class Probe; end defines ::Probe); it fails if a hand-rolled binding is reintroduced.
  • rubocop clean.
  • Live smoke test of rdx console against this repo: selfmain, local_variables[:_, :__, :graph], Foo.name"Foo", run("MATCH (c:Class {name: 'Rubydex::Graph'}) RETURN c.name")[{"c.name" => "Rubydex::Graph"}], and query/schema both print their tables.
  • Degradation checked both ways: with no irb installed, and with only irb 1.12 installed, rdx console aborts cleanly with exit status 1.

@paracycle
paracycle requested a review from a team as a code owner June 25, 2026 20:25
@paracycle
paracycle force-pushed the uk_add_cypher_query_engine branch from f6e2615 to 7e08a7b Compare June 25, 2026 20:31
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch 2 times, most recently from e8308d4 to 9306fe5 Compare June 25, 2026 20:33
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch 6 times, most recently from c41173d to 19a7ce6 Compare July 3, 2026 21:02
@paracycle
paracycle force-pushed the uk_add_cypher_query_engine branch from ded5eb9 to 4fa3719 Compare July 3, 2026 21:16
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch 2 times, most recently from 6132b4a to 63488b8 Compare July 3, 2026 21:32
@paracycle
paracycle force-pushed the uk_add_cypher_query_engine branch 3 times, most recently from 85bbbba to cc553f6 Compare July 3, 2026 21:56
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch 2 times, most recently from 622970f to 7b27f69 Compare July 8, 2026 16:50
@paracycle
paracycle changed the base branch from uk_add_cypher_query_engine to uk_query_object_results July 8, 2026 16:50
@paracycle
paracycle force-pushed the uk_query_object_results branch from d68b816 to 51b4f05 Compare July 8, 2026 18:26
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch from 7b27f69 to c3e332e Compare July 8, 2026 18:28
@paracycle
paracycle force-pushed the uk_query_object_results branch from 51b4f05 to 2bb59e0 Compare July 8, 2026 20:05
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch from c3e332e to d2e3c09 Compare July 8, 2026 20:06
@paracycle
paracycle force-pushed the uk_query_object_results branch from 2bb59e0 to 1de091c Compare July 8, 2026 20:19
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch from d2e3c09 to 825248e Compare July 8, 2026 20:19
@paracycle
paracycle force-pushed the uk_query_object_results branch from 1de091c to ae8fcbd Compare July 9, 2026 20:13
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch from 825248e to 10e1f88 Compare July 9, 2026 20:13
@paracycle
paracycle force-pushed the uk_query_object_results branch from ae8fcbd to 9e80825 Compare July 9, 2026 20:56
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch from 10e1f88 to dc2c413 Compare July 9, 2026 20:57
@paracycle
paracycle force-pushed the uk_query_object_results branch from 9e80825 to 5fb40e7 Compare July 9, 2026 21:17
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch from dc2c413 to 47450a6 Compare July 9, 2026 21:18
@paracycle
paracycle force-pushed the uk_query_object_results branch from 5fb40e7 to 933bf3d Compare July 9, 2026 22:39
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch from 47450a6 to 140980d Compare July 9, 2026 22:40
@paracycle
paracycle force-pushed the uk_query_object_results branch from 933bf3d to a22a461 Compare July 16, 2026 20:18
@paracycle
paracycle force-pushed the uk_query_object_results branch 3 times, most recently from bdddecc to 6865c54 Compare July 29, 2026 17:24
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch from 140980d to af4305c Compare July 29, 2026 17:30
Base automatically changed from uk_query_object_results to main July 29, 2026 18:24
Comment thread lib/rubydex/console.rb Outdated
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch from af4305c to 5bf2f17 Compare July 29, 2026 21:50
@paracycle
paracycle requested a review from Morriar July 31, 2026 17:52
Add `Rubydex::Console`, which backs `rdx console` with an IRB session that
keeps `graph` in scope (for Ruby, e.g. `graph["Foo"]`) and registers two
Cypher commands via IRB's modern command API:

  rubydex> query MATCH (n:Class|Module) RETURN n.name ORDER BY n.name
  rubydex> schema

The `query` command takes the rest of the line verbatim, so the Cypher
does not need to be valid Ruby or quoted. This is the clean, idiomatic
"query mode": rather than swapping the REPL's evaluator, Cypher lines are
prefixed with `query`.

- Extract the console out of exe/rdx into lib/rubydex/console.rb; the
  query-running logic lives in Console.run_query so it is testable without
  driving IRB.
- Treat IRB as a soft, runtime dependency rather than a gemspec one. IRB is
  a default gem, so it is not declared in the gemspec; the Cypher commands
  register only when the installed IRB exposes the command API (>= 1.13),
  and the console degrades to a plain Ruby session on older versions.
@paracycle
paracycle force-pushed the uk_rdx_console_query_mode branch from 5bf2f17 to 3ecad72 Compare July 31, 2026 17:55

@Morriar Morriar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found one CI issue to fix before this lands.

Comment thread lib/rubydex/console.rb
# 1.13. Requiring an older or absent IRB raises `LoadError` (`Gem::LoadError` is one), which
# `exe/rdx` turns into a friendly message rather than a backtrace.
gem "irb", ">= 1.13"
require "irb"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may need to require "fiddle" to fix the Windows build?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants