Skip to content

Commit 8cbbbd2

Browse files
author
Dylan Storey
committed
Prepare v0.1.0-beta.1 release
Features: - MERGE clause with MATCH+MERGE pattern support - FOREACH clause for iterative updates - Parameterized queries (SQL injection prevention) - Graph algorithms: PageRank, Label Propagation - Python bindings with ergonomic Graph class (CRUD, batch ops) - Backtick-quoting for reserved words as relationship types - Relationship property updates with SET clause Fixes: - RETURN n returns full node/relationship objects with properties - ORDER BY supports RETURN aliases - JSON string escaping in agtype serialization - Graph algorithm results include user-defined node IDs Internal: - Modularize cypher_executor.c (3,988 → 724 lines) - Extract transform_with.c, transform_unwind.c, transform_expr_ops.c - Add nanographrag demo application - Vendored SQLite headers for Python bindings
1 parent b43942d commit 8cbbbd2

File tree

179 files changed

+31718
-3817
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

179 files changed

+31718
-3817
lines changed

.github/workflows/release.yml

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,24 @@ jobs:
128128
name: ${{ matrix.artifact-name }}
129129
path: build/${{ matrix.artifact-name }}
130130

131-
publish-python:
131+
# Build platform-specific wheels with bundled extensions
132+
build-wheels:
132133
needs: build-and-test
133-
runs-on: ubuntu-latest
134+
strategy:
135+
matrix:
136+
include:
137+
- os: ubuntu-latest
138+
artifact: graphqlite-linux-x86_64.so
139+
extension: graphqlite.so
140+
- os: macos-14
141+
artifact: graphqlite-macos-arm64.dylib
142+
extension: graphqlite.dylib
143+
# Note: macos-x86_64 wheel built on arm64 with universal tag
144+
- os: windows-latest
145+
artifact: graphqlite-windows-x86_64.dll
146+
extension: graphqlite.dll
147+
148+
runs-on: ${{ matrix.os }}
134149

135150
steps:
136151
- uses: actions/checkout@v4
@@ -140,15 +155,63 @@ jobs:
140155
with:
141156
python-version: '3.11'
142157

158+
- name: Download extension artifact
159+
uses: actions/download-artifact@v4
160+
with:
161+
name: ${{ matrix.artifact }}
162+
path: bindings/python/src/graphqlite/
163+
164+
- name: Rename extension to standard name
165+
working-directory: bindings/python/src/graphqlite
166+
run: mv ${{ matrix.artifact }} ${{ matrix.extension }}
167+
shell: bash
168+
143169
- name: Install build tools
144-
run: pip install build twine
170+
run: pip install build wheel
171+
172+
- name: Build wheel
173+
working-directory: bindings/python
174+
run: python -m build --wheel
175+
176+
- name: Upload wheel artifact
177+
uses: actions/upload-artifact@v4
178+
with:
179+
name: wheel-${{ matrix.os }}
180+
path: bindings/python/dist/*.whl
181+
182+
publish-python:
183+
needs: build-wheels
184+
runs-on: ubuntu-latest
185+
186+
steps:
187+
- uses: actions/checkout@v4
188+
189+
- name: Set up Python
190+
uses: actions/setup-python@v5
191+
with:
192+
python-version: '3.11'
193+
194+
- name: Download all wheels
195+
uses: actions/download-artifact@v4
196+
with:
197+
pattern: wheel-*
198+
path: dist/
199+
merge-multiple: true
200+
201+
- name: Install twine
202+
run: pip install twine
145203

146204
- name: Build source distribution
147205
working-directory: bindings/python
148-
run: python -m build --sdist
206+
run: |
207+
pip install build
208+
python -m build --sdist
209+
cp dist/*.tar.gz ../../dist/
210+
211+
- name: List distributions
212+
run: ls -la dist/
149213

150214
- name: Publish to PyPI
151-
working-directory: bindings/python
152215
env:
153216
TWINE_USERNAME: __token__
154217
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
@@ -171,7 +234,7 @@ jobs:
171234
run: cargo publish --allow-dirty
172235

173236
create-release:
174-
needs: [build-and-test, publish-python, publish-rust]
237+
needs: [build-and-test, build-wheels, publish-python, publish-rust]
175238
runs-on: ubuntu-latest
176239

177240
steps:

.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,22 @@ docs/build/
105105
.env
106106
.env.local
107107
config.local.h
108+
109+
# Python
110+
__pycache__/
111+
*.py[cod]
112+
*$py.class
113+
*.egg-info/
114+
dist/
115+
*.egg
116+
.eggs/
117+
*.whl
118+
119+
# Rust
120+
target/
121+
Cargo.lock
122+
!bindings/rust/Cargo.lock
123+
124+
# Metis database (auto-generated)
125+
.metis/metis.db
126+
bindings/python/src/graphqlite.egg-info/
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
---
2+
id: return-n-serializes-all-nodes-into
3+
level: task
4+
title: "RETURN n serializes all nodes into single JSON string instead of separate rows"
5+
short_code: "GQLITE-T-0011"
6+
created_at: 2025-12-24T14:16:05.505777+00:00
7+
updated_at: 2025-12-24T14:38:27.912817+00:00
8+
parent:
9+
blocked_by: []
10+
archived: false
11+
12+
tags:
13+
- "#task"
14+
- "#bug"
15+
- "#phase/completed"
16+
17+
18+
exit_criteria_met: false
19+
strategy_id: NULL
20+
initiative_id: NULL
21+
---
22+
23+
# RETURN n serializes all nodes into single JSON string instead of separate rows
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 **[REQUIRED]**
32+
33+
Fix the Cypher executor to return node objects as separate rows instead of serializing all nodes into a single JSON string.
34+
35+
## Backlog Item Details **[CONDITIONAL: Backlog Item]**
36+
37+
{Delete this section when task is assigned to an initiative}
38+
39+
### Type
40+
- [x] Bug - Production issue that needs fixing
41+
42+
### Priority
43+
- [ ] P1 - High (important for user experience)
44+
45+
### Impact Assessment
46+
- **Affected Users**: Anyone using `RETURN n` to get full node objects
47+
- **Reproduction Steps**:
48+
1. Create nodes: `CREATE (n:Person {name: 'Alice'})`
49+
2. Query: `MATCH (n) RETURN n`
50+
3. Observe result structure
51+
- **Expected vs Actual**:
52+
- **Expected (openCypher standard):** Multiple rows, each with `{"n": {"id": 1, "labels": [...], "properties": {...}}}`
53+
- **Actual:** Single row with `{"result": "[{\"n\": {...}}, {\"n\": {...}}]"}` - all nodes serialized as JSON string
54+
55+
### Business Justification **[CONDITIONAL: Feature]**
56+
- **User Value**: {Why users need this}
57+
- **Business Value**: {Impact on metrics/revenue}
58+
- **Effort Estimate**: {Rough size - S/M/L/XL}
59+
60+
### Technical Debt Impact **[CONDITIONAL: Tech Debt]**
61+
- **Current Problems**: {What's difficult/slow/buggy now}
62+
- **Benefits of Fixing**: {What improves after refactoring}
63+
- **Risk Assessment**: {Risks of not addressing this}
64+
65+
## Acceptance Criteria
66+
67+
## Acceptance Criteria
68+
69+
## Acceptance Criteria
70+
71+
## Acceptance Criteria **[REQUIRED]**
72+
73+
- [ ] `MATCH (n) RETURN n` returns each node as a separate row
74+
- [ ] Each row has key `n` containing the node object directly (not wrapped in JSON string)
75+
- [ ] `RETURN n.name` continues to work (regression test)
76+
- [ ] Python bindings can iterate: `for row in result: node = row["n"]`
77+
78+
## Test Cases **[CONDITIONAL: Testing Task]**
79+
80+
{Delete unless this is a testing task}
81+
82+
### Test Case 1: {Test Case Name}
83+
- **Test ID**: TC-001
84+
- **Preconditions**: {What must be true before testing}
85+
- **Steps**:
86+
1. {Step 1}
87+
2. {Step 2}
88+
3. {Step 3}
89+
- **Expected Results**: {What should happen}
90+
- **Actual Results**: {To be filled during execution}
91+
- **Status**: {Pass/Fail/Blocked}
92+
93+
### Test Case 2: {Test Case Name}
94+
- **Test ID**: TC-002
95+
- **Preconditions**: {What must be true before testing}
96+
- **Steps**:
97+
1. {Step 1}
98+
2. {Step 2}
99+
- **Expected Results**: {What should happen}
100+
- **Actual Results**: {To be filled during execution}
101+
- **Status**: {Pass/Fail/Blocked}
102+
103+
## Documentation Sections **[CONDITIONAL: Documentation Task]**
104+
105+
{Delete unless this is a documentation task}
106+
107+
### User Guide Content
108+
- **Feature Description**: {What this feature does and why it's useful}
109+
- **Prerequisites**: {What users need before using this feature}
110+
- **Step-by-Step Instructions**:
111+
1. {Step 1 with screenshots/examples}
112+
2. {Step 2 with screenshots/examples}
113+
3. {Step 3 with screenshots/examples}
114+
115+
### Troubleshooting Guide
116+
- **Common Issue 1**: {Problem description and solution}
117+
- **Common Issue 2**: {Problem description and solution}
118+
- **Error Messages**: {List of error messages and what they mean}
119+
120+
### API Documentation **[CONDITIONAL: API Documentation]**
121+
- **Endpoint**: {API endpoint description}
122+
- **Parameters**: {Required and optional parameters}
123+
- **Example Request**: {Code example}
124+
- **Example Response**: {Expected response format}
125+
126+
## Implementation Notes
127+
128+
### Technical Approach
129+
The bug is likely in the C code that handles RETURN clauses. When returning a node variable (vs a property like `n.name`), the executor serializes all results into one JSON array string instead of emitting separate result rows.
130+
131+
**Likely location:** `src/backend/transform/transform_return.c` or the executor code that builds the final JSON result.
132+
133+
### Notes
134+
- `RETURN n.name AS name` works correctly (returns scalars per row)
135+
- `RETURN count(n)` works correctly
136+
- Only `RETURN n` (full node object) exhibits this behavior
137+
138+
## Status Updates **[REQUIRED]**
139+
140+
*To be added during implementation*

.metis/backlog/features/GQLITE-T-0006.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ level: task
44
title: "Implement FOREACH clause for iterative updates"
55
short_code: "GQLITE-T-0006"
66
created_at: 2025-12-24T01:49:49.042048+00:00
7-
updated_at: 2025-12-24T01:49:49.042048+00:00
7+
updated_at: 2025-12-24T15:02:17.022679+00:00
88
parent:
99
blocked_by: []
1010
archived: false
1111

1212
tags:
1313
- "#task"
14-
- "#phase/backlog"
1514
- "#feature"
15+
- "#phase/completed"
1616

1717

1818
exit_criteria_met: false
@@ -36,6 +36,12 @@ Implement the FOREACH clause for iterating over a list and performing update ope
3636

3737
## Acceptance Criteria
3838

39+
## Acceptance Criteria
40+
41+
## Acceptance Criteria
42+
43+
## Acceptance Criteria
44+
3945
- [ ] `FOREACH (x IN [1,2,3] | CREATE (n {val: x}))` creates 3 nodes
4046
- [ ] `FOREACH (n IN nodes | SET n.updated = true)` updates all nodes in list
4147
- [ ] Nested FOREACH supported

0 commit comments

Comments
 (0)