Geode documentation tagged with Read-Eval-Print Loop (REPL). The REPL is an interactive shell that reads GQL commands, evaluates them against your database, and prints results immediately—the primary tool for exploratory analysis, query development, and learning GQL.
Introduction to the REPL
A Read-Eval-Print Loop (REPL) is an interactive programming environment that accepts user input, executes it, and displays the result. REPLs have been fundamental to programming since the 1960s, popularized by Lisp and later adopted by languages like Python, Ruby, and JavaScript. For databases, REPLs provide immediate feedback for query development, making them essential tools for exploration and debugging.
Geode’s REPL, invoked with geode shell, provides a rich interactive environment for working with graph data. Unlike batch query execution or programmatic access through client libraries, the REPL emphasizes immediate feedback and iterative refinement. You type a GQL query, press Enter, and see results instantly. This tight feedback loop accelerates learning, enables rapid prototyping, and makes debugging far more efficient than edit-compile-run cycles.
The Geode REPL goes beyond simple query execution to provide a comprehensive development environment. Features include syntax highlighting, auto-completion for keywords and labels, query history with search, multi-line editing for complex queries, and formatted result display with automatic table rendering. Transaction management, meta-commands for database introspection, and session persistence make the REPL suitable for both quick experiments and extended development sessions.
Key Concepts
Interactive Query Execution
The core REPL workflow:
- Prompt: Display
geode>prompt waiting for input - Read: Accept GQL query or meta-command from user
- Evaluate: Execute query against database
- Print: Display formatted results
- Loop: Return to prompt for next command
Multi-Line Editing
Complex queries span multiple lines:
geode> MATCH (p:Person)-[:WORKS_AT]->(c:Company)
WHERE p.age > 30
AND c.industry = 'Technology'
RETURN p.name, c.name, p.salary
ORDER BY p.salary DESC
LIMIT 10;
The REPL detects incomplete statements and continues accepting input until a semicolon or empty line.
Result Formatting
Results are displayed in readable tables:
geode> MATCH (p:Person) RETURN p.name, p.age LIMIT 3;
╭──────────────┬──────────╮
│ p.name │ p.age │
├──────────────┼──────────┤
│ Alice Smith │ 32 │
│ Bob Johnson │ 28 │
│ Carol Davis │ 45 │
╰──────────────┴──────────╯
3 rows (15.2ms)
Meta-Commands
Meta-commands provide database introspection:
\l- List available graphs\dt- List node labels and relationship types\d <label>- Describe label properties\timing on- Enable query timing\explain <query>- Show query execution plan\profile <query>- Profile query execution\history- Show command history\save <file>- Save session to file\load <file>- Load and execute file
Session Management
REPL sessions maintain state:
- Transaction Context: BEGIN/COMMIT/ROLLBACK manage transaction scope
- Variable Binding: Store query results for later reference
- History: Full command history with Up/Down arrow navigation
- Settings: Persistent configuration (display format, timing, etc.)
How the REPL Works in Geode
Starting the REPL
Launch the interactive shell:
# Start REPL connected to local server
geode shell
# Connect to remote server
geode shell --host db.example.com --port 3141
# Authenticate with username/password
geode shell --user admin --password
# Connect with TLS certificate
geode shell --cert client.crt --key client.key
Query Execution Flow
When you execute a query in the REPL:
- Input Collection: REPL accumulates input lines until complete statement
- Syntax Highlighting: Keywords, strings, and operators highlighted in color
- Client Transmission: Query sent to server via QUIC connection
- Server Processing: Query parsed, optimized, and executed
- Result Streaming: Results stream back to client
- Rendering: REPL formats results as tables or JSON
- Timing Display: Execution time displayed
Interactive Features
The REPL provides rich editing capabilities:
- Tab Completion: Complete keywords, labels, properties
- History Search: Ctrl+R for reverse history search
- Line Editing: Emacs or Vi key bindings
- Syntax Validation: Real-time syntax checking
- Error Display: Detailed error messages with line/column
Transaction Management
Manage transactions interactively:
geode> BEGIN;
Transaction started (txn:550e8400)
geode> INSERT (:Person {name: 'Eve', age: 29});
1 row inserted (5.2ms)
geode> MATCH (p:Person {name: 'Eve'}) RETURN p.name;
╭────────╮
│ p.name │
├────────┤
│ Eve │
╰────────╯
geode> COMMIT;
Transaction committed (12.3ms)
Use Cases
Exploratory Data Analysis
geode> -- Explore graph structure
geode> MATCH (n) RETURN DISTINCT labels(n) AS label, count(*) AS count;
╭────────────┬───────╮
│ label │ count │
├────────────┼───────┤
│ Person │ 1523 │
│ Company │ 245 │
│ Product │ 3421 │
╰────────────┴───────╯
geode> -- Examine specific node
geode> MATCH (p:Person {name: 'Alice'}) RETURN p;
╭────────────────────────────────────────╮
│ p │
├────────────────────────────────────────┤
│ { │
│ "id": "node:12345", │
│ "labels": ["Person"], │
│ "properties": { │
│ "name": "Alice", │
│ "age": 32, │
│ "email": "[email protected]" │
│ } │
│ } │
╰────────────────────────────────────────╯
geode> -- Find relationship patterns
geode> MATCH (p:Person)-[r]->(c:Company)
RETURN type(r) AS rel_type, count(*) AS count
ORDER BY count DESC;
╭──────────────┬───────╮
│ rel_type │ count │
├──────────────┼───────┤
│ WORKS_AT │ 1205 │
│ INVESTED_IN │ 142 │
│ FOUNDED │ 38 │
╰──────────────┴───────╯
Query Development and Testing
geode> -- Test query incrementally
geode> MATCH (p:Person)
WHERE p.age > 30
RETURN count(*);
╭──────────╮
│ count(*) │
├──────────┤
│ 823 │
╰──────────╯
geode> -- Add more conditions
geode> MATCH (p:Person)
WHERE p.age > 30 AND p.city = 'Seattle'
RETURN count(*);
╭──────────╮
│ count(*) │
├──────────┤
│ 156 │
╰──────────╯
geode> -- Profile for performance
geode> \profile MATCH (p:Person)
WHERE p.age > 30 AND p.city = 'Seattle'
RETURN p.name, p.email;
Query Plan:
┌─ Filter (cost=0.1, rows=156)
│ └─ IndexScan (Person.city = 'Seattle')
│ └─ Filter (age > 30)
│ └─ Return (p.name, p.email)
Execution: 2.3ms (156 rows)
Learning GQL
geode> -- Try basic MATCH patterns
geode> MATCH (n) RETURN n LIMIT 5;
geode> -- Experiment with relationships
geode> MATCH (a)-[r]->(b) RETURN a, type(r), b LIMIT 5;
geode> -- Learn aggregation
geode> MATCH (p:Person)
RETURN p.city, count(*) AS population
ORDER BY population DESC;
geode> -- Practice pattern matching
geode> MATCH (p:Person)-[:KNOWS]->()-[:KNOWS]->(friend)
WHERE p.name = 'Alice'
RETURN DISTINCT friend.name;
Database Administration
geode> -- Check database size
geode> \dt
Labels:
Person (1523 nodes)
Company (245 nodes)
Product (3421 nodes)
Relationship Types:
WORKS_AT (1205 relationships)
KNOWS (3421 relationships)
PURCHASED (12456 relationships)
geode> -- View indexes
geode> SHOW INDEXES;
╭────────────────┬──────────────────┬─────────╮
│ label │ properties │ type │
├────────────────┼──────────────────┼─────────┤
│ Person │ [name] │ BTREE │
│ Person │ [email] │ UNIQUE │
│ Product │ [sku] │ UNIQUE │
│ Person │ [embedding] │ HNSW │
╰────────────────┴──────────────────┴─────────╯
geode> -- Create new index
geode> CREATE INDEX person_age FOR (p:Person) ON (p.age);
Index created (234.5ms)
Best Practices
Query Development Workflow
- Start Simple: Begin with basic MATCH patterns
- Add Incrementally: Add WHERE clauses and filters one at a time
- Profile Early: Use EXPLAIN/PROFILE to understand execution
- Test with LIMIT: Use LIMIT during development to avoid large result sets
- Refine Performance: Iterate based on profiling feedback
Interactive Session Management
- Use Transactions: Wrap related operations in BEGIN/COMMIT
- Save Work: Use
\saveto preserve successful queries - History Search: Use Ctrl+R to find previous commands
- Clear Output: Use
\clearfor long sessions - Tab Completion: Leverage auto-completion for efficiency
Result Display Optimization
- Limit Results: Always use LIMIT for unbounded queries
- Format Selection: Choose JSON or table format based on data
- Column Width: Adjust terminal width for readable tables
- Vertical Display: Use
\xfor wide result sets - Paging: Use
\pset pager onfor long results
Learning and Exploration
- Experiment Freely: REPL is perfect for trying new syntax
- Use Meta-Commands: Explore
\?for all available commands - Read Error Messages: REPL shows detailed error information
- Copy Queries: Transfer successful queries to application code
- Practice Daily: Regular REPL use builds GQL fluency
Performance Considerations
REPL Overhead
REPL introduces minimal overhead:
- Parsing: Client-side syntax highlighting adds <1ms
- Rendering: Table formatting adds 1-5ms for typical results
- Network: Same QUIC protocol as programmatic clients
- Buffering: Results stream with low memory overhead
Large Result Sets
Handle large results efficiently:
geode> -- Use LIMIT during exploration
geode> MATCH (n:Person) RETURN n LIMIT 100;
geode> -- Count before retrieving
geode> MATCH (n:Person) RETURN count(n);
geode> -- Use cursors for large exports
geode> \export persons.json
geode> MATCH (n:Person) RETURN n;
Query Timing
Enable timing to monitor performance:
geode> \timing on
Timing is on.
geode> MATCH (p:Person {email: '[email protected]'}) RETURN p;
╭─────────────────────────────────╮
│ p │
├─────────────────────────────────┤
│ {"name": "Alice", "age": 32} │
╰─────────────────────────────────╯
1 row (0.8ms)
Troubleshooting
Connection Issues
Symptom: Cannot connect to database
Solutions:
- Verify server is running (
geode serve) - Check host/port configuration
- Confirm network connectivity
- Validate TLS certificates
- Review authentication credentials
Slow Query Performance
Symptom: Queries take longer than expected
Solutions:
- Use
\explainto view query plan - Check for missing indexes
- Add LIMIT to unbounded queries
- Verify network latency
- Profile with
\profile
Display Issues
Symptom: Results not displaying correctly
Solutions:
- Adjust terminal width
- Switch format with
\format json - Use vertical display
\x on - Check for Unicode characters
- Clear screen with
\clear
History Problems
Symptom: Command history not working
Solutions:
- Check history file permissions
- Verify
~/.geode_historyexists - Clear corrupted history file
- Restart REPL session
Related Topics
- CLI - Command-line interface and tools
- Query Development - Query development workflow
- GQL Syntax - GQL language syntax
- Interactive Tools - Development tools
- Debugging - Query debugging techniques
- Query Optimization - Performance tuning
Advanced REPL Features
Query History and Search
The REPL maintains a searchable history of all commands:
geode> \history
1: MATCH (n) RETURN count(n);
2: MATCH (p:Person) RETURN p.name LIMIT 10;
3: MATCH (p:Person {name: 'Alice'}) RETURN p;
...
geode> # Search history with Ctrl+R
(reverse-i-search)`MATCH': MATCH (p:Person) RETURN p.name LIMIT 10;
geode> # Re-execute previous command with !!
geode> !!
MATCH (p:Person) RETURN p.name LIMIT 10;
geode> # Execute specific history entry with !<number>
geode> !2
MATCH (p:Person) RETURN p.name LIMIT 10;
Session Persistence
Save and load REPL sessions:
geode> \save session.gql
Saved 15 queries to session.gql
geode> \load session.gql
Loading and executing session.gql...
Query 1/15: MATCH (n) RETURN count(n); ✓
Query 2/15: CREATE INDEX FOR (p:Person) ON (p.email); ✓
...
Session loaded successfully.
geode> \export results.json
Exporting query results to results.json...
Exported 1523 rows
Variable Binding and Reuse
Bind query results to variables for reuse:
geode> @high_value_users := MATCH (u:User)
WHERE u.lifetime_value > 10000
RETURN u.id, u.name, u.email;
Bound 142 rows to @high_value_users
geode> # Use bound variable in subsequent queries
geode> MATCH (u:User)
WHERE u.id IN [x.id FOR x IN @high_value_users]
MATCH (u)-[:PURCHASED]->(p:Product)
RETURN u.name, count(p) AS purchases
ORDER BY purchases DESC;
╭──────────────┬────────────╮
│ u.name │ purchases │
├──────────────┼────────────┤
│ Alice Smith │ 87 │
│ Bob Johnson │ 72 │
...
╰──────────────┴────────────╯
Multi-Line Query Editing
Advanced editing capabilities for complex queries:
geode> # Press Ctrl+X Ctrl+E to open query in $EDITOR
geode> # Edit multi-line query with full text editor support
# Opens in vim/emacs/nano with syntax highlighting:
MATCH (u:User)-[:WORKS_AT]->(c:Company),
(u)-[:LIVES_IN]->(city:City)
WHERE c.industry = 'Technology'
AND city.name IN ['San Francisco', 'Seattle', 'Austin']
AND u.salary > 100000
WITH u, c, city,
duration.between(u.hire_date, date()) AS tenure
WHERE tenure.years > 2
RETURN u.name AS employee,
c.name AS company,
city.name AS location,
u.salary AS salary,
tenure.years AS years_employed
ORDER BY u.salary DESC
LIMIT 50;
# Save and exit editor, query executes in REPL
Advanced Meta-Commands
Comprehensive database introspection:
geode> \d Person
Label: Person
Properties:
id INTEGER PRIMARY KEY, INDEXED
name STRING NOT NULL
email STRING UNIQUE, INDEXED
age INTEGER
city STRING INDEXED
created_at TIMESTAMP DEFAULT now()
Indexes:
person_id_idx (id) - BTREE
person_email_idx (email) - UNIQUE BTREE
person_city_idx (city) - BTREE
person_embedding_idx (embedding) - HNSW
Constraints:
person_id_unique (id IS UNIQUE)
person_email_unique (email IS UNIQUE)
person_name_exists (name IS NOT NULL)
Relationships:
(Person)-[:WORKS_AT]->(Company) 14523 relationships
(Person)-[:FRIEND]->(Person) 35621 relationships
(Person)-[:LIVES_IN]->(City) 14523 relationships
geode> \timing on
Timing is on.
geode> \explain on
Query plans will be displayed.
geode> MATCH (p:Person {email: '[email protected]'}) RETURN p;
Query Plan:
┌─ IndexScan (person_email_idx)
│ ├─ Condition: email = '[email protected]'
│ ├─ Estimated rows: 1
│ └─ Cost: 0.1
└─ Return
├─ Columns: p
└─ Cost: 0.01
Execution: 0.8ms (1 row)
geode> \watch 5
Repeating query every 5 seconds. Press Ctrl+C to stop.
geode> MATCH (u:User) WHERE u.online = true RETURN count(u);
╭──────────╮
│ count(u) │
├──────────┤
│ 1523 │
╰──────────╯
[2024-01-15 14:30:00]
╭──────────╮
│ count(u) │
├──────────┤
│ 1531 │
╰──────────╯
[2024-01-15 14:30:05]
...
Output Formatting Options
Control result display format:
geode> \format table
Output format set to table.
geode> MATCH (p:Person) RETURN p.name, p.age LIMIT 3;
╭──────────────┬──────╮
│ p.name │ p.age│
├──────────────┼──────┤
│ Alice Smith │ 32 │
│ Bob Johnson │ 28 │
│ Carol Davis │ 45 │
╰──────────────┴──────╯
geode> \format json
Output format set to JSON.
geode> MATCH (p:Person) RETURN p.name, p.age LIMIT 3;
[
{"p.name": "Alice Smith", "p.age": 32},
{"p.name": "Bob Johnson", "p.age": 28},
{"p.name": "Carol Davis", "p.age": 45}
]
geode> \format csv
Output format set to CSV.
geode> MATCH (p:Person) RETURN p.name, p.age LIMIT 3;
p.name,p.age
Alice Smith,32
Bob Johnson,28
Carol Davis,45
geode> \format vertical
Output format set to vertical.
geode> MATCH (p:Person) RETURN p.name, p.email, p.age LIMIT 1;
─────────────── Row 1 ───────────────
p.name │ Alice Smith
p.email │ [email protected]
p.age │ 32
Batch Operations
Execute multiple queries efficiently:
geode> \batch
Batch mode enabled. Commands will queue until '\execute'.
batch> CREATE (:User {id: 1, name: 'Alice'});
Queued (1)
batch> CREATE (:User {id: 2, name: 'Bob'});
Queued (2)
batch> CREATE (:User {id: 3, name: 'Carol'});
Queued (3)
batch> MATCH (a:User {id: 1}), (b:User {id: 2})
CREATE (a)-[:FRIEND]->(b);
Queued (4)
batch> \execute
Executing 4 queued queries in transaction...
✓ Query 1 completed (1.2ms)
✓ Query 2 completed (0.9ms)
✓ Query 3 completed (0.8ms)
✓ Query 4 completed (1.1ms)
Batch completed: 4/4 succeeded (4.0ms total)
Remote Server Management
Manage Geode servers from REPL:
geode> \connect production-db.example.com:3141
Connecting to production-db.example.com:3141...
Connected to Geode v0.1.3 (cluster mode, 5 nodes)
geode> \cluster status
Cluster Status:
┌────────┬─────────────────────┬─────────┬────────┬────────────┐
│ Node │ Address │ Role │ Status │ Lag (ms) │
├────────┼─────────────────────┼─────────┼────────┼────────────┤
│ node1 │ 10.0.1.10:7000 │ Leader │ Healthy│ 0 │
│ node2 │ 10.0.1.11:7000 │ Follower│ Healthy│ 2 │
│ node3 │ 10.0.1.12:7000 │ Follower│ Healthy│ 1 │
│ node4 │ 10.0.2.10:7000 │ Observer│ Healthy│ 45 │
│ node5 │ 10.0.2.11:7000 │ Observer│ Healthy│ 48 │
└────────┴─────────────────────┴─────────┴────────┴────────────┘
geode> \admin rebalance
Initiating cluster rebalance...
Progress: [████████████████████████████████] 100% (15.2s)
Rebalanced 128 shards across 5 nodes
geode> \admin backup /backups/prod-$(date +%Y%m%d)
Starting backup to /backups/prod-20240115...
Backing up data files... ████████████████ 100% (2.1GB)
Backing up WAL files... ████████████████ 100% (512MB)
Backup completed: /backups/prod-20240115.tar.gz (2.6GB)
Keyboard Shortcuts Reference
Navigation:
Ctrl+A / Home - Beginning of line
Ctrl+E / End - End of line
Ctrl+F / → - Forward one character
Ctrl+B / ← - Backward one character
Alt+F - Forward one word
Alt+B - Backward one word
Editing:
Ctrl+D / Delete - Delete character
Ctrl+H / Backspace - Delete previous character
Ctrl+K - Kill (cut) to end of line
Ctrl+U - Kill to beginning of line
Ctrl+W - Kill previous word
Alt+D - Kill next word
Ctrl+Y - Yank (paste) killed text
History:
Ctrl+R - Reverse search history
Ctrl+S - Forward search history
↑ / Ctrl+P - Previous command
↓ / Ctrl+N - Next command
Alt+< - First command in history
Alt+> - Last command in history
Completion:
Tab - Auto-complete
Alt+? - List all completions
Control:
Ctrl+C - Cancel current input
Ctrl+D - Exit (if input empty)
Ctrl+L - Clear screen
Ctrl+X Ctrl+E - Edit query in $EDITOR
Scripting with REPL
Automate tasks with REPL scripts:
#!/usr/bin/env bash
# Daily analytics script
geode shell --non-interactive <<'EOF'
-- Daily user activity report
\output daily_report_$(date +%Y%m%d).csv
\format csv
MATCH (u:User)-[:PERFORMED]->(e:Event)
WHERE e.timestamp > datetime().minusDays(1)
WITH u, count(e) AS event_count, collect(DISTINCT e.type) AS event_types
ORDER BY event_count DESC
RETURN u.id, u.name, event_count, event_types
LIMIT 1000;
-- Export to JSON for downstream processing
\output user_cohorts.json
\format json
MATCH (u:User)
WITH u,
duration.between(u.created_at, datetime()).days AS days_since_signup
WITH CASE
WHEN days_since_signup <= 7 THEN 'new'
WHEN days_since_signup <= 30 THEN 'active'
ELSE 'established'
END AS cohort,
count(u) AS user_count
RETURN cohort, user_count;
\quit
EOF
echo "Reports generated:"
ls -lh daily_report_*.csv user_cohorts.json
IDE Integration
Use REPL output in development workflows:
# Generate schema documentation
geode shell --execute "\dt" > docs/schema.txt
# Extract sample data for testing
geode shell --execute "
MATCH (u:User) RETURN u LIMIT 100;
\format json
" > test/fixtures/users.json
# Monitor query performance
geode shell --execute "
\timing on
\profile
MATCH (u:User)-[:PURCHASED]->(p:Product)
WHERE p.category = 'Electronics'
RETURN u.name, count(p) AS purchases
ORDER BY purchases DESC LIMIT 10;
" > performance_report.txt
Further Reading
- REPL Advanced Guide - Complete REPL documentation
- GQL Quick Reference - GQL syntax cheat sheet
- REPL Basics Tutorial - Hands-on REPL tutorial
- Readline Documentation - Keyboard shortcuts
- PostgreSQL psql - Inspiration for meta-commands