@@ -11,7 +11,7 @@ type Person = {name: Text, age: Int 1}
1111
1212do
1313 full *people = [{name "Alice" age 30} {name "Bob" age 25}]
14− people <- full *people
14+ people <- *people
1515 with {result do
1616 p <- people
1717 where p.age > 27
@@ -156,7 +156,7 @@ data Status = Open {} | Closed {}
156156
157157-- Derived: read-only computed relation
158158&seniors = do
159− people <- full *people
159+ people <- *people
160160 yield (base.filter (\p -> p.age > 65) people)
161161
162162-- Constant
@@ -321,27 +321,23 @@ The join columns (`employees.dept`, `departments.name`) and filter columns
321321planner cannot translate falls back to reading the sources and joining in
322322memory — results are identical either way, only the strategy differs.
323323
324−### `full` reads
324+### Whole-relation reads
325325
326−When a relation read is **not** pushed down to SQL, the whole relation is
327−loaded into memory. Knot requires you to mark such a read with `full` before
328−the relation name, so the cost is explicit:
326+A relation read that is **not** pushed down to SQL loads the whole relation
327+into memory. This happens at a `base.run` boundary or a whole-relation bind
328+(`rows <- *rel` in an IO do-block), both of which materialize the query:
329329
330330```knot
331331*people : [{name: Text, age: Int 1}]
332332(do
333− people <- full *people -- whole relation loaded into memory
333+ people <- *people -- whole relation loaded into memory
334334 base.println (base.show (base.count people))
335335 yield {})
336336```
337337
338338A read that *is* pushed down — a comprehension the planner translates, or a
339−recognized aggregate such as `base.count *people` — needs no marker. The
340−compiler reports an error (`reading `*rel` loads the whole relation into
341−memory … write `full *rel``) at any read it cannot push down, so you always
342−know where a full table load happens. Writing `full` on a read that turns out
343−to be pushed down is itself an error (`unnecessary `full``), so the marker is
344−present exactly where a full load occurs — no more, no less.
339+recognized aggregate such as `base.count *people` — runs in SQL and loads
340+nothing. The strategy is chosen at the use site, after the query is inlined.
345341
346342Statements in a `do` block:
347343
@@ -369,7 +365,7 @@ do
369365-- IO do block with DB operations
370366*people : [{name: Text, age: Int 1}]
371367addPerson \name age -> do
372− people <- full *people
368+ people <- *people
373369 *people = base.union people [{name name age age}]
374370```
375371
@@ -385,7 +381,7 @@ data Status = InProgress {assignee: Text} | Done {}
385381*shapes : [Shape]
386382*tickets : [{title: Text, status: Status}]
387383&circles = do
388− shapes <- full *shapes
384+ shapes <- *shapes
389385 with {result do
390386 Shape.Circle c <- shapes
391387 yield c}
@@ -393,7 +389,7 @@ data Status = InProgress {assignee: Text} | Done {}
393389 yield result)
394390
395391&inProgress = do
396− tickets <- full *tickets
392+ tickets <- *tickets
397393 with {result do
398394 t <- tickets
399395 Status.InProgress ip <- t.status
@@ -413,12 +409,12 @@ Mutation is written `*rel = expr`, which makes the source relation equal to `exp
413409
414410-- Insert (union with singleton)
415411addPerson do
416− people <- full *people
412+ people <- *people
417413 *people = base.union people [{name "Alice" age 30}]
418414
419415-- Update (map with conditional)
420416birthday \name -> do
421− people <- full *people
417+ people <- *people
422418 *people = do
423419 p <- people
424420 yield (case p.name == name of
@@ -427,7 +423,7 @@ birthday \name -> do
427423
428424-- Delete (filter to keep)
429425removePerson \name -> do
430− people <- full *people
426+ people <- *people
431427 *people = do
432428 p <- people
433429 where p.name != name
@@ -581,7 +577,7 @@ Fields can hold `[T]` — sets nested inside rows:
581577
582578-- Query into nested relations
583579&allMembers = do
584− teams <- full *teams
580+ teams <- *teams
585581 with {result do
586582 t <- teams
587583 m <- t.members
@@ -591,7 +587,7 @@ Fields can hold `[T]` — sets nested inside rows:
591587
592588-- Update nested relations
593589updateTeams do
594− teams <- full *teams
590+ teams <- *teams
595591 *teams = do
596592 t <- teams
597593 yield (base.unify t {members do
@@ -631,7 +627,7 @@ data Status = Open {} | Closed {}
631627
632628-- Insert through view — status auto-filled
633629addOpenTodo do
634− openTodos <- full *openTodos
630+ openTodos <- *openTodos
635631 *openTodos = base.union openTodos [{title "New task" owner "Alice" priority Priority.High {}}]
636632```
637633
@@ -645,12 +641,12 @@ Read-only computed relations, prefixed with `&`:
645641*people : [{name: Text, age: Int 1}]
646642*todos : [{title: Text, owner: Text, done: Int 1}]
647643&seniors = do
648− people <- full *people
644+ people <- *people
649645 yield (base.filter (\p -> p.age > 65) people)
650646
651647&stats = do
652648 with {result do
653− t <- full *todos
649+ t <- *todos
654650 groupBy {owner t.owner}
655651 yield {owner t.owner total (base.count t)}}
656652 (do
@@ -666,7 +662,7 @@ Datalog-style fixpoint iteration for transitive closure:
666662
667663-- Base case: direct management edges, renamed to ancestor/descendant
668664&direct = (do
669− manages <- full *manages
665+ manages <- *manages
670666 yield (do
671667 m <- manages
672668 yield {ancestor m.manager descendant m.report}))
@@ -674,7 +670,7 @@ Datalog-style fixpoint iteration for transitive closure:
674670-- Recursive: ancestor/descendant via fixpoint over the base case
675671&reportsTo = do
676672 reportsTo <- &reportsTo -- self-reference (IO bind)
677− manages <- full *manages
673+ manages <- *manages
678674 direct <- &direct
679675 with {step do
680676 r <- reportsTo
@@ -745,7 +741,7 @@ type Person = {name: Text, age: Int 1}
745741
746742-- All relation operations are IO:
747743birthday \name -> do
748− people <- full *people -- IO [Person]
744+ people <- *people -- IO [Person]
749745 *people = do -- IO {}
750746 p <- people
751747 yield (case p.name == name of
@@ -763,9 +759,9 @@ birthday \name -> do
763759*orders : [{item: Text, qty: Int 1}]
764760handleOrder \item -> do
765761 orderId <- atomic do
766− orders <- full *orders
762+ orders <- *orders
767763 *orders = base.union orders [{item item qty 1}]
768− newOrders <- full *orders
764+ newOrders <- *orders
769765 yield (base.count newOrders)
770766 base.println ("Order #" ++ base.show orderId)
771767 yield {orderId orderId}
@@ -1038,11 +1034,11 @@ route AdminApi where
10381034route Api = TodoApi | AdminApi
10391035
10401036addTodo (\title owner -> do
1041− todos <- full *todos
1037+ todos <- *todos
10421038 *todos = base.union todos [{title title owner owner priority (Priority.Low {})}])
10431039
10441040getTodos (\owner -> do
1045− todos <- full *todos
1041+ todos <- *todos
10461042 with {result (do t <- todos; where t.owner == owner; yield t)} yield result)
10471043
10481044-- Handler
@@ -1054,7 +1050,7 @@ api (serve Api where
10541050 addTodo title owner
10551051 yield (Result.Ok {value {title title owner owner priority (Priority.Low {})}})
10561052 GetCount = \{} -> do
1057− todos <- full *todos
1053+ todos <- *todos
10581054 yield (Result.Ok {value (base.count todos)}))
10591055```
10601056
@@ -1070,7 +1066,7 @@ route Api where
10701066 GET /users/{id: Int 1} -> {id: Int 1, name: Text} = GetUser
10711067api (serve Api where
10721068 GetUser = \{id id} -> do
1073− users <- full *people
1069+ users <- *people
10741070 case base.filter (\u -> u.id == id) users of
10751071 [] -> yield (Result.Err {error {status 404 message "user not found"}})
10761072 Cons u rest -> yield (Result.Ok {value u}))
@@ -1104,7 +1100,7 @@ route Api where
11041100
11051101api (serve Api where
11061102 GetTodos = \{authorization authorization} -> do
1107− allTodos <- full *todos
1103+ allTodos <- *todos
11081104 yield (Result.Ok {value {body allTodos headers {xTotalCount (base.count allTodos)}}}))
11091105```
11101106
@@ -1338,11 +1334,11 @@ type Todo = {title: Text, owner: Text, priority: Priority, status: Status}
13381334*todos : [Todo]
13391335
13401336add \title owner priority -> do
1341− todos <- full *todos
1337+ todos <- *todos
13421338 *todos = base.union todos [{title title owner owner priority priority status (Status.Open {})}]
13431339
13441340complete \title -> do
1345− todos <- full *todos
1341+ todos <- *todos
13461342 *todos = do
13471343 t <- todos
13481344 yield (case t.title == title of
@@ -1350,7 +1346,7 @@ complete \title -> do
13501346 Bool.False {} -> t)
13511347
13521348assign \title person -> do
1353− todos <- full *todos
1349+ todos <- *todos
13541350 *todos = do
13551351 t <- todos
13561352 yield (case t.title == title of
@@ -1358,7 +1354,7 @@ assign \title person -> do
13581354 Bool.False {} -> t)
13591355
13601356pending \owner -> do
1361− todos <- full *todos
1357+ todos <- *todos
13621358 with {result do
13631359 t <- todos
13641360 where t.owner == owner
@@ -1369,7 +1365,7 @@ pending \owner -> do
13691365
13701366&workload = do
13711367 with {result do
1372− t <- full *todos
1368+ t <- *todos
13731369 where t.status == Status.Open {}
13741370 groupBy {owner t.owner}
13751371 yield {owner t.owner count (base.count t)}}
@@ -1400,7 +1396,7 @@ do
14001396```knot
14011397*rel : [{value: Int 1}]
14021398addRow \newRow -> do
1403− rel <- full *rel
1399+ rel <- *rel
14041400 *rel = base.union rel [newRow]
14051401```
14061402
@@ -1409,7 +1405,7 @@ addRow \newRow -> do
14091405```knot
14101406*rel : [{id: Int 1, field: Int 1}]
14111407deleteWhere \valueToDelete -> do
1412− rel <- full *rel
1408+ rel <- *rel
14131409 *rel = do
14141410 r <- rel
14151411 where r.field != valueToDelete
@@ -1421,7 +1417,7 @@ deleteWhere \valueToDelete -> do
14211417```knot
14221418*rel : [{id: Int 1, field: Int 1}]
14231419updateWhere \target newValue -> do
1424− rel <- full *rel
1420+ rel <- *rel
14251421 *rel = do
14261422 r <- rel
14271423 yield (case r.id == target of
@@ -1435,8 +1431,8 @@ updateWhere \target newValue -> do
14351431*employees : [{name: Text, dept: Text}]
14361432*departments : [{name: Text, budget: Int 1}]
14371433&joined = do
1438− employees <- full *employees
1439− departments <- full *departments
1434+ employees <- *employees
1435+ departments <- *departments
14401436 with {result do
14411437 e <- employees
14421438 d <- departments
@@ -1452,11 +1448,11 @@ updateWhere \target newValue -> do
14521448*orders : [{amount: Int 1}]
14531449*people : [{name: Text, age: Int 1}]
14541450getTotal do
1455− orders <- full *orders
1451+ orders <- *orders
14561452 yield (base.fold (\acc x -> acc + x.amount) 0 orders)
14571453
14581454getCount do
1459− people <- full *people
1455+ people <- *people
14601456 yield (base.count people)
14611457```
14621458
@@ -1468,12 +1464,12 @@ data Shape = Circle {radius: Float 1} | Rect {width: Float 1, height: Float 1}
14681464
14691465-- Using match
14701466&circles = do
1471− shapes <- full *shapes
1467+ shapes <- *shapes
14721468 yield (base.match Shape.Circle shapes)
14731469
14741470-- Using pattern bind in do
14751471&circles2 = do
1476− shapes <- full *shapes
1472+ shapes <- *shapes
14771473 with {result do
14781474 Shape.Circle c <- shapes
14791475 yield c}