Skip to content

Commit 6405759

Browse files
author
Dylan Storey
committed
Add gqlite CLI improvements and release binary builds
- Fix multi-line statement handling with semicolon delimiter (Issue #5) - Add continuation prompt for multi-line input in interactive mode - Add TTY detection for different behavior in piped vs interactive mode - Create comprehensive CLI test suite (19 tests) - Add gqlite-portable Makefile target for release builds - Update release workflow to build and upload CLI binaries - Add CLI documentation
1 parent fc84558 commit 6405759

File tree

8 files changed

+1075
-70
lines changed

8 files changed

+1075
-70
lines changed

.angreal/task_test.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,46 @@ def test_constraints(verbose: bool = False) -> int:
271271
return run_make("test-constraints", verbose=verbose)
272272

273273

274+
@test()
275+
@angreal.command(
276+
name="cli",
277+
about="Run CLI tests for gqlite binary",
278+
tool=angreal.ToolDescription(
279+
"""
280+
Run CLI tests that exercise the gqlite interactive shell.
281+
282+
## When to use
283+
- After changes to src/main.c
284+
- Testing multi-line statement handling
285+
- Validating CLI behavior
286+
287+
## Examples
288+
```
289+
angreal test cli
290+
```
291+
292+
## Prerequisites
293+
- gqlite binary must be built first (auto-built if missing)
294+
""",
295+
risk_level="safe"
296+
)
297+
)
298+
@angreal.argument(
299+
name="verbose",
300+
long="verbose",
301+
short="v",
302+
is_flag=True,
303+
takes_value=False,
304+
help="Show verbose output"
305+
)
306+
def test_cli(verbose: bool = False) -> int:
307+
"""Run CLI tests."""
308+
from utils import run_make
309+
310+
print("Running CLI tests...")
311+
return run_make("test-cli", verbose=verbose)
312+
313+
274314
@test()
275315
@angreal.command(
276316
name="all",
@@ -313,6 +353,7 @@ def test_all(verbose: bool = False) -> int:
313353
tests = [
314354
("Unit tests", lambda: test_unit(verbose=verbose)),
315355
("Functional tests", lambda: test_functional(verbose=verbose)),
356+
("CLI tests", lambda: test_cli(verbose=verbose)),
316357
("Binding tests", lambda: test_bindings(verbose=verbose)),
317358
]
318359

.github/workflows/release.yml

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ on:
44
push:
55
tags:
66
- 'v*'
7+
# TEMPORARY: Test release workflow on this branch - REMOVE BEFORE MERGE
8+
branches:
9+
- 'feature/gqlite-cli-improvements'
710

811
jobs:
912
build-and-test:
@@ -14,17 +17,25 @@ jobs:
1417
- os: ubuntu-latest
1518
artifact: graphqlite.so
1619
artifact-name: graphqlite-linux-x86_64.so
20+
cli-artifact: gqlite
21+
cli-artifact-name: gqlite-linux-x86_64
1722
- os: macos-14
1823
arch: arm64
1924
artifact: graphqlite.dylib
2025
artifact-name: graphqlite-macos-arm64.dylib
26+
cli-artifact: gqlite
27+
cli-artifact-name: gqlite-macos-arm64
2128
- os: macos-14
2229
arch: x86_64
2330
artifact: graphqlite.dylib
2431
artifact-name: graphqlite-macos-x86_64.dylib
32+
cli-artifact: gqlite
33+
cli-artifact-name: gqlite-macos-x86_64
2534
- os: windows-latest
2635
artifact: graphqlite.dll
2736
artifact-name: graphqlite-windows-x86_64.dll
37+
cli-artifact: gqlite.exe
38+
cli-artifact-name: gqlite-windows-x86_64.exe
2839

2940
runs-on: ${{ matrix.os }}
3041

@@ -80,6 +91,27 @@ jobs:
8091
if: runner.os == 'Windows'
8192
run: make extension RELEASE=1
8293

94+
# Build gqlite CLI
95+
- name: Install static SQLite (Linux)
96+
if: runner.os == 'Linux'
97+
run: sudo apt-get install -y libsqlite3-dev
98+
99+
- name: Build gqlite CLI (Linux)
100+
if: runner.os == 'Linux'
101+
run: make gqlite-portable RELEASE=1
102+
103+
- name: Build gqlite CLI (macOS arm64)
104+
if: runner.os == 'macOS' && matrix.arch == 'arm64'
105+
run: make gqlite-portable RELEASE=1
106+
107+
- name: Build gqlite CLI (macOS x86_64 cross-compile)
108+
if: runner.os == 'macOS' && matrix.arch == 'x86_64'
109+
run: make gqlite-portable RELEASE=1 CC="clang -arch x86_64"
110+
111+
- name: Build gqlite CLI (Windows)
112+
if: runner.os == 'Windows'
113+
run: make gqlite-portable RELEASE=1
114+
83115
# Python binding tests
84116
- name: Set up Python (Linux/Windows)
85117
if: runner.os != 'macOS'
@@ -119,15 +151,25 @@ jobs:
119151
shell: bash
120152
run: cargo test -- --test-threads=1
121153

122-
- name: Rename artifact
154+
- name: Rename extension artifact
123155
run: cp build/${{ matrix.artifact }} build/${{ matrix.artifact-name }}
124156

157+
- name: Rename CLI artifact
158+
run: cp build/${{ matrix.cli-artifact }} build/${{ matrix.cli-artifact-name }}
159+
shell: bash
160+
125161
- name: Upload extension artifact
126162
uses: actions/upload-artifact@v4
127163
with:
128164
name: ${{ matrix.artifact-name }}
129165
path: build/${{ matrix.artifact-name }}
130166

167+
- name: Upload CLI artifact
168+
uses: actions/upload-artifact@v4
169+
with:
170+
name: ${{ matrix.cli-artifact-name }}
171+
path: build/${{ matrix.cli-artifact-name }}
172+
131173
# Build platform-specific Python wheels
132174
build-wheels:
133175
needs: build-and-test
@@ -197,6 +239,7 @@ jobs:
197239
publish-python:
198240
needs: build-wheels
199241
runs-on: ubuntu-latest
242+
if: startsWith(github.ref, 'refs/tags/')
200243

201244
steps:
202245
- uses: actions/checkout@v4
@@ -235,6 +278,7 @@ jobs:
235278
publish-rust:
236279
needs: build-and-test
237280
runs-on: ubuntu-latest
281+
if: startsWith(github.ref, 'refs/tags/')
238282

239283
steps:
240284
- uses: actions/checkout@v4
@@ -247,3 +291,43 @@ jobs:
247291
env:
248292
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
249293
run: cargo publish --allow-dirty
294+
295+
# Create GitHub Release with binaries
296+
create-release:
297+
needs: build-and-test
298+
runs-on: ubuntu-latest
299+
if: startsWith(github.ref, 'refs/tags/')
300+
permissions:
301+
contents: write
302+
303+
steps:
304+
- uses: actions/checkout@v4
305+
306+
- name: Download all artifacts
307+
uses: actions/download-artifact@v4
308+
with:
309+
path: artifacts/
310+
311+
- name: Organize release assets
312+
run: |
313+
mkdir -p release-assets
314+
# Copy extension binaries
315+
cp artifacts/graphqlite-linux-x86_64.so/graphqlite-linux-x86_64.so release-assets/
316+
cp artifacts/graphqlite-macos-arm64.dylib/graphqlite-macos-arm64.dylib release-assets/
317+
cp artifacts/graphqlite-macos-x86_64.dylib/graphqlite-macos-x86_64.dylib release-assets/
318+
cp artifacts/graphqlite-windows-x86_64.dll/graphqlite-windows-x86_64.dll release-assets/
319+
# Copy CLI binaries
320+
cp artifacts/gqlite-linux-x86_64/gqlite-linux-x86_64 release-assets/
321+
cp artifacts/gqlite-macos-arm64/gqlite-macos-arm64 release-assets/
322+
cp artifacts/gqlite-macos-x86_64/gqlite-macos-x86_64 release-assets/
323+
cp artifacts/gqlite-windows-x86_64.exe/gqlite-windows-x86_64.exe release-assets/
324+
ls -la release-assets/
325+
326+
- name: Create GitHub Release
327+
uses: softprops/action-gh-release@v1
328+
with:
329+
files: release-assets/*
330+
generate_release_notes: true
331+
draft: false
332+
env:
333+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
---
2+
id: variable-length-paths-in-match
3+
level: task
4+
title: "Variable-length paths in MATCH+RETURN queries cause syntax error"
5+
short_code: "GQLITE-T-0084"
6+
created_at: 2026-01-04T15:19:14.677175+00:00
7+
updated_at: 2026-01-04T15:19:14.677175+00:00
8+
parent:
9+
blocked_by: []
10+
archived: false
11+
12+
tags:
13+
- "#task"
14+
- "#phase/backlog"
15+
- "#bug"
16+
17+
18+
exit_criteria_met: false
19+
strategy_id: NULL
20+
initiative_id: NULL
21+
---
22+
23+
# Variable-length paths in MATCH+RETURN queries cause syntax error
24+
25+
*This template includes sections for various types of tasks. Delete sections that don't apply to your specific use case.*
26+
27+
## Parent Initiative **[CONDITIONAL: Assigned Task]**
28+
29+
[[Parent Initiative]]
30+
31+
## Objective
32+
33+
Fix the parser/query dispatch to properly handle variable-length path patterns (e.g., `[:KNOWS*]`) in MATCH+RETURN queries.
34+
35+
## Backlog Item Details
36+
37+
### Type
38+
- [x] Bug - Production issue that needs fixing
39+
40+
### Priority
41+
- [ ] P2 - Medium (nice to have)
42+
43+
### Impact Assessment
44+
- **Affected Users**: Users attempting to traverse graphs with variable-length paths
45+
- **Reproduction Steps**:
46+
1. Create nodes and relationships: `CREATE (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person)`
47+
2. Run: `MATCH path = (a:Person)-[:KNOWS*]->(end) RETURN end.name`
48+
3. Observe syntax error
49+
- **Expected vs Actual**:
50+
- Expected: Query returns all nodes reachable via KNOWS relationships
51+
- Actual: `Query failed: Line 1: syntax error, unexpected END_P, expecting ')'`
52+
53+
## Acceptance Criteria
54+
55+
- [ ] `MATCH path = (a)-[:REL*]->(b) RETURN b` executes without syntax error
56+
- [ ] Variable-length path with bounds works: `[:REL*1..3]`
57+
- [ ] CLI test added to verify this functionality
58+
59+
## Test Cases **[CONDITIONAL: Testing Task]**
60+
61+
{Delete unless this is a testing task}
62+
63+
### Test Case 1: {Test Case Name}
64+
- **Test ID**: TC-001
65+
- **Preconditions**: {What must be true before testing}
66+
- **Steps**:
67+
1. {Step 1}
68+
2. {Step 2}
69+
3. {Step 3}
70+
- **Expected Results**: {What should happen}
71+
- **Actual Results**: {To be filled during execution}
72+
- **Status**: {Pass/Fail/Blocked}
73+
74+
### Test Case 2: {Test Case Name}
75+
- **Test ID**: TC-002
76+
- **Preconditions**: {What must be true before testing}
77+
- **Steps**:
78+
1. {Step 1}
79+
2. {Step 2}
80+
- **Expected Results**: {What should happen}
81+
- **Actual Results**: {To be filled during execution}
82+
- **Status**: {Pass/Fail/Blocked}
83+
84+
## Documentation Sections **[CONDITIONAL: Documentation Task]**
85+
86+
{Delete unless this is a documentation task}
87+
88+
### User Guide Content
89+
- **Feature Description**: {What this feature does and why it's useful}
90+
- **Prerequisites**: {What users need before using this feature}
91+
- **Step-by-Step Instructions**:
92+
1. {Step 1 with screenshots/examples}
93+
2. {Step 2 with screenshots/examples}
94+
3. {Step 3 with screenshots/examples}
95+
96+
### Troubleshooting Guide
97+
- **Common Issue 1**: {Problem description and solution}
98+
- **Common Issue 2**: {Problem description and solution}
99+
- **Error Messages**: {List of error messages and what they mean}
100+
101+
### API Documentation **[CONDITIONAL: API Documentation]**
102+
- **Endpoint**: {API endpoint description}
103+
- **Parameters**: {Required and optional parameters}
104+
- **Example Request**: {Code example}
105+
- **Example Response**: {Expected response format}
106+
107+
## Implementation Notes
108+
109+
### Technical Approach
110+
The parser likely handles `[:RELTYPE*]` syntax, but the query dispatch system (`query_dispatch.c`) may not properly handle path variables in MATCH+RETURN patterns. Investigation needed in:
111+
- `src/backend/executor/query_dispatch.c` - Pattern matching for MATCH+RETURN
112+
- `src/backend/parser/cypher_gram.y` - Variable-length path grammar rules
113+
114+
### Dependencies
115+
None
116+
117+
### Risk Considerations
118+
Low risk - this is additive functionality
119+
120+
## Status Updates **[REQUIRED]**
121+
122+
*To be added during implementation*

Makefile

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,22 @@ graphqlite: $(MAIN_APP)
259259
extension: $(EXTENSION_LIB)
260260

261261

262+
# Standard gqlite build (dynamic linking)
262263
$(MAIN_APP): $(MAIN_OBJ) $(PARSER_OBJS) $(TRANSFORM_OBJS) $(EXECUTOR_OBJS) | dirs
263264
$(CC) $(CFLAGS) $^ -o $@ -lsqlite3
264265

266+
# Portable gqlite build for releases (static linking where possible)
267+
gqlite-portable: $(MAIN_OBJ) $(PARSER_OBJS) $(TRANSFORM_OBJS) $(EXECUTOR_OBJS) | dirs
268+
ifeq ($(UNAME_S),Darwin)
269+
$(CC) $(CFLAGS) $^ -o $(BUILD_DIR)/gqlite -lsqlite3
270+
else ifneq (,$(findstring MINGW,$(UNAME_S)))
271+
$(CC) $(CFLAGS) -static $^ -o $(BUILD_DIR)/gqlite.exe -lsqlite3 -lsystre -ltre -lintl -liconv
272+
else ifneq (,$(findstring MSYS,$(UNAME_S)))
273+
$(CC) $(CFLAGS) -static $^ -o $(BUILD_DIR)/gqlite.exe -lsqlite3 -lsystre -ltre -lintl -liconv
274+
else
275+
$(CC) $(CFLAGS) $^ -o $(BUILD_DIR)/gqlite -l:libsqlite3.a -lpthread -ldl -lm
276+
endif
277+
265278
# SQLite extension shared library (with full parser, transform, and executor)
266279
$(EXTENSION_LIB): $(EXTENSION_OBJ) $(PARSER_OBJS_PIC) $(TRANSFORM_OBJS_PIC) $(EXECUTOR_OBJS_PIC) | dirs $(GRAMMAR_HDR)
267280
ifeq ($(UNAME_S),Darwin)
@@ -498,7 +511,18 @@ test-functional: extension
498511
fi; \
499512
done
500513

501-
test-all: test-unit test-functional test-bindings
514+
test-cli:
515+
@echo "Building gqlite in release mode for CLI tests..."
516+
@$(MAKE) clean-app --no-print-directory 2>/dev/null || true
517+
@$(MAKE) graphqlite RELEASE=1 --no-print-directory
518+
@echo "Running CLI tests..."
519+
@./tests/cli/run_cli_tests.sh $(BUILD_DIR)/gqlite
520+
521+
# Clean only the app-related object files (for switching between debug/release)
522+
clean-app:
523+
@rm -f $(BUILD_DIR)/main.o $(BUILD_DIR)/gqlite
524+
525+
test-all: test-unit test-functional test-cli test-bindings
502526

503527
# Main test target dispatches to appropriate sub-target
504528
test: test-$(TEST_TARGET)
@@ -510,4 +534,4 @@ clean:
510534
find . -name "*.gcno" -delete
511535
find . -name "*.gcov" -delete
512536

513-
.PHONY: all help dirs test test-unit test-rust test-python test-bindings test-functional test-all test-constraints test-perf test-perf-quick test-perf-scaled test-perf-pagerank performance coverage clean unit rust python bindings functional
537+
.PHONY: all help dirs test test-unit test-rust test-python test-bindings test-functional test-cli test-all test-constraints test-perf test-perf-quick test-perf-scaled test-perf-pagerank performance coverage clean unit rust python bindings functional cli gqlite-portable

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# How-to Guides
1616

1717
- [Installation](./how-to/installation.md)
18+
- [Use the gqlite CLI](./how-to/cli.md)
1819
- [Use Graph Algorithms](./how-to/graph-algorithms.md)
1920
- [Handle Special Characters](./how-to/special-characters.md)
2021
- [Use with Other Extensions](./how-to/other-extensions.md)

0 commit comments

Comments
 (0)