You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+20Lines changed: 20 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,25 @@
1
1
# Change Log
2
2
3
+
## 0.7.0 - 2026-06-15
4
+
5
+
### Added
6
+
*`__depth` selectable pseudo-column on recursive queries — reports each row's traversal depth (seed = 0); in `via` mode it reports the shallowest depth at which a node is reached
7
+
*`walk.` filter prefix to prune the traversal — skips a non-matching node and everything beyond it
8
+
* Bidirectional graph traversal with `via!both(src,dst)` — follows edges in either direction
9
+
* Resource embedding now composes with single-table recursion (not supported together with `via()` traversal)
10
+
11
+
### Changed
12
+
***Breaking:** plain filters on recursive queries now filter the *result* instead of pruning the traversal; the previous prune-the-walk behavior is now opt-in via the `walk.` prefix
13
+
14
+
## 0.6.1 - 2026-06-12
15
+
16
+
### Fixed
17
+
* JSONB filter behavior now matches PostgREST (#19): JSON-path filters with quoted string values, containment against JSON string arrays, and whole-column JSONB equality/inequality
18
+
19
+
### Improved
20
+
* Improved release process (goreleaser config and `make release` target)
Copy file name to clipboardExpand all lines: README.md
+11-39Lines changed: 11 additions & 39 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -397,65 +397,37 @@ When disabled, attempts to use aggregate functions will return an error.
397
397
### Recursive Queries
398
398
399
399
> [!NOTE]
400
-
> This is a SmoothDB extension to PostgREST syntax. We plan to propose it upstream to PostgREST.
400
+
> This is a SmoothDB extension to PostgREST syntax. We plan to propose it to PostgREST.
401
401
402
-
SmoothDB supports recursive queries on self-referential tables and views using the `start` and `recurse` operators. These generate PostgreSQL recursive CTEs with automatic cycle detection.
403
-
404
-
#### Basic Usage
405
-
406
-
Given a table with a parent pointer (e.g. `employees.manager_id → employees.id`):
402
+
SmoothDB walks self-referential tables (and separate edge tables) with the `start`/`recurse` operators, generating PostgreSQL recursive CTEs with automatic cycle detection.
407
403
408
404
```http
409
405
GET /api/testdb/employees?id=start.1&manager_id=recurse.3 HTTP/1.1
410
406
```
411
407
412
-
This returns the row with `id=1` and all descendants up to 3 levels deep, following `manager_id` back to `id`.
408
+
Returns row `id=1` and its descendants up to 3 levels deep, following `manager_id → id`. Use `recurse.all` (or bare `recurse`) for unlimited depth (capped by `MaxRecursiveDepth`), and `after` instead of `start` to exclude the seed row.
413
409
414
-
Use `recurse.all` (or bare `recurse`) for unlimited depth (capped by `MaxRecursiveDepth`):
410
+
**Result vs. traversal filters.** A plain filter restricts the *result*: the whole subtree is walked, then non-matching rows are dropped. Prefix a filter with `walk.` to prune the *traversal* instead — a non-matching node, and everything beyond it, is skipped:
415
411
416
412
```http
417
-
GET /api/testdb/employees?id=start.1&manager_id=recurse.all HTTP/1.1
413
+
GET /api/testdb/employees?id=start.1&manager_id=recurse.all&is_active=is.true HTTP/1.1 # keep only active rows
414
+
GET /api/testdb/employees?id=start.1&manager_id=recurse.all&walk.is_active=is.true HTTP/1.1 # stop walking at inactive nodes
418
415
```
419
416
420
-
#### Excluding the Root
421
-
422
-
Use `after` instead of `start` to exclude the seed row from results:
417
+
Standard parameters (`select`, `order`, `limit`, …) apply to the result set. The pseudo-column `__depth` is selectable to get each row's traversal depth (seed = 0), and related resources can be embedded:
423
418
424
419
```http
425
-
GET /api/testdb/employees?id=after.1&manager_id=recurse.all HTTP/1.1
420
+
GET /api/testdb/employees?id=start.1&manager_id=recurse.all&select=id,name,__depth,tasks(title)&order=__depth HTTP/1.1
426
421
```
427
422
428
-
#### Combining with Standard Parameters
429
-
430
-
All standard query parameters work on the recursive result set:
431
-
432
-
```http
433
-
GET /api/testdb/employees?id=start.1&manager_id=recurse.all&is_active=is.true&select=id,name&order=name.asc&limit=10 HTTP/1.1
434
-
```
435
-
436
-
Filters are applied at every level of the recursion, pruning entire branches.
437
-
438
-
#### Multi-Table Traversal with `via`
439
-
440
-
For graph traversal through a separate edge/relationship table, use the `via` operator. Given a `documents` table and a `relationships` table with `src_id` and `dst_id` columns:
423
+
**Edge tables (`via`).** Traverse a graph through a separate edge table with source/target columns. Add the `!both` hint to follow edges in either direction; filter which edges to follow with the standard `table.column` syntax (`eq`, `in`, `or`, …):
441
424
442
425
```http
443
426
GET /api/testdb/documents?id=after.1&id=recurse.all&relationships=via(src_id,dst_id) HTTP/1.1
427
+
GET /api/testdb/documents?id=after.1&id=recurse.all&relationships=via!both(src_id,dst_id)&relationships.rel_type=in.(contains,references) HTTP/1.1
444
428
```
445
429
446
-
This traverses all documents reachable from document 1 through the `relationships` table, excluding the root.
447
-
448
-
Filter which edges to follow using the standard PostgREST `table.column` prefix syntax:
449
-
450
-
```http
451
-
GET /api/testdb/documents?id=after.1&id=recurse.all&relationships=via(src_id,dst_id)&relationships.rel_type=eq.contains HTTP/1.1
452
-
```
453
-
454
-
Full PostgREST filter syntax is supported on the edge table, including `or` and `in`:
455
-
456
-
```http
457
-
GET /api/testdb/documents?id=after.1&id=recurse.all&relationships=via(src_id,dst_id)&relationships.rel_type=in.(contains,references) HTTP/1.1
458
-
```
430
+
Embedding is not supported together with `via` traversal.
q.WriteString(" INNER JOIN "+qvia+" ON "+qvia+"."+viaTo+" = "+qtable+"."+startField)
1146
-
q.WriteString(" INNER JOIN "+cteName+" ON "+qvia+"."+viaFrom+" = "+cteName+"."+startField)
1175
+
ifrec.ViaBidirectional {
1176
+
// via!both: follow edges in either direction. The new node (qtable) sits on
1177
+
// one end of the edge, the known node (cte) on the opposite end.
1178
+
q.WriteString(" INNER JOIN "+qvia+" ON "+qvia+"."+viaTo+" = "+qtable+"."+startField+" OR "+qvia+"."+viaFrom+" = "+qtable+"."+startField)
1179
+
q.WriteString(" INNER JOIN "+cteName+" ON ("+qvia+"."+viaFrom+" = "+cteName+"."+startField+" AND "+qvia+"."+viaTo+" = "+qtable+"."+startField+") OR ("+qvia+"."+viaTo+" = "+cteName+"."+startField+" AND "+qvia+"."+viaFrom+" = "+qtable+"."+startField+")")
1180
+
} else {
1181
+
q.WriteString(" INNER JOIN "+qvia+" ON "+qvia+"."+viaTo+" = "+qtable+"."+startField)
1182
+
q.WriteString(" INNER JOIN "+cteName+" ON "+qvia+"."+viaFrom+" = "+cteName+"."+startField)
1183
+
}
1147
1184
nmarker++
1148
1185
q.WriteString(" WHERE "+cteName+".__depth < $"+strconv.Itoa(nmarker))
1149
1186
valueList=append(valueList, maxDepth)
1150
1187
q.WriteString(" AND NOT "+qtable+"."+startField+" = ANY("+cteName+".__path)")
0 commit comments