synalog 2.0.0__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. {synalog-2.0.0 → synalog-2.2.0}/Cargo.lock +1 -1
  2. {synalog-2.0.0 → synalog-2.2.0}/Cargo.toml +1 -1
  3. {synalog-2.0.0 → synalog-2.2.0}/PKG-INFO +38 -38
  4. {synalog-2.0.0 → synalog-2.2.0}/README.md +37 -37
  5. {synalog-2.0.0 → synalog-2.2.0}/docs/assertions.md +6 -5
  6. synalog-2.2.0/docs/assets/synalog-dark.svg +11 -0
  7. synalog-2.2.0/docs/assets/synalog-light.svg +11 -0
  8. {synalog-2.0.0 → synalog-2.2.0}/docs/cli.md +29 -15
  9. {synalog-2.0.0 → synalog-2.2.0}/docs/development.md +1 -1
  10. {synalog-2.0.0 → synalog-2.2.0}/docs/differences-datalog.md +8 -8
  11. {synalog-2.0.0 → synalog-2.2.0}/docs/differences.md +2 -2
  12. {synalog-2.0.0 → synalog-2.2.0}/docs/engines.md +1 -1
  13. synalog-2.2.0/docs/examples/functions.log +103 -0
  14. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/recursion.log +20 -20
  15. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/run.py +2 -2
  16. synalog-2.0.0/docs/examples/shop/concepts/Customer.l → synalog-2.2.0/docs/examples/shop/concepts/customer.l +1 -1
  17. synalog-2.0.0/docs/examples/shop/concepts/OrderStatus.l → synalog-2.2.0/docs/examples/shop/concepts/order_status.l +1 -1
  18. synalog-2.0.0/docs/examples/shop/rules/CustomerRevenue.l → synalog-2.2.0/docs/examples/shop/rules/customer_revenue.l +2 -2
  19. synalog-2.0.0/docs/examples/shop/rules/OrdersByStatus.l → synalog-2.2.0/docs/examples/shop/rules/orders_by_status.l +2 -2
  20. synalog-2.0.0/docs/examples/shop/rules/TopCustomers.l → synalog-2.2.0/docs/examples/shop/rules/top_customers.l +1 -1
  21. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/shop.log +6 -6
  22. synalog-2.0.0/docs/examples/smoke/concepts/Device.l → synalog-2.2.0/docs/examples/smoke/concepts/device.l +1 -1
  23. synalog-2.0.0/docs/examples/smoke/concepts/TestStatus.l → synalog-2.2.0/docs/examples/smoke/concepts/test_status.l +1 -1
  24. synalog-2.0.0/docs/examples/smoke/rules/FailuresByDevice.l → synalog-2.2.0/docs/examples/smoke/rules/failures_by_device.l +2 -2
  25. synalog-2.0.0/docs/examples/smoke/rules/RunsPerDay.l → synalog-2.2.0/docs/examples/smoke/rules/runs_per_day.l +1 -1
  26. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/smoke.log +5 -5
  27. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/syntax.log +7 -7
  28. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/temporal.log +3 -3
  29. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/temporal_graph.log +11 -11
  30. {synalog-2.0.0 → synalog-2.2.0}/docs/getting-started.md +16 -16
  31. {synalog-2.0.0 → synalog-2.2.0}/docs/javascripts/playground.js +2 -2
  32. synalog-2.2.0/docs/javascripts/synalog-highlight.js +302 -0
  33. {synalog-2.0.0 → synalog-2.2.0}/docs/knowledge-graphs.md +24 -24
  34. {synalog-2.0.0 → synalog-2.2.0}/docs/language/aggregation.md +7 -7
  35. {synalog-2.0.0 → synalog-2.2.0}/docs/language/directives.md +8 -8
  36. {synalog-2.0.0 → synalog-2.2.0}/docs/language/functions.md +5 -5
  37. {synalog-2.0.0 → synalog-2.2.0}/docs/language/functors.md +4 -4
  38. {synalog-2.0.0 → synalog-2.2.0}/docs/language/index.md +35 -35
  39. {synalog-2.0.0 → synalog-2.2.0}/docs/language/recursion.md +8 -6
  40. {synalog-2.0.0 → synalog-2.2.0}/docs/language/syntax.md +15 -15
  41. {synalog-2.0.0 → synalog-2.2.0}/docs/language/temporal.md +11 -11
  42. {synalog-2.0.0 → synalog-2.2.0}/docs/python-api.md +39 -8
  43. {synalog-2.0.0 → synalog-2.2.0}/docs/stylesheets/extra.css +49 -0
  44. {synalog-2.0.0 → synalog-2.2.0}/docs/verification.md +5 -4
  45. synalog-2.2.0/img/synalog-dark.svg +11 -0
  46. synalog-2.2.0/img/synalog-light.svg +11 -0
  47. {synalog-2.0.0 → synalog-2.2.0}/pyproject.toml +1 -1
  48. {synalog-2.0.0 → synalog-2.2.0}/python/synalog/__init__.py +4 -0
  49. {synalog-2.0.0 → synalog-2.2.0}/python/synalog/checking.py +37 -36
  50. {synalog-2.0.0 → synalog-2.2.0}/python/synalog/cli.py +85 -116
  51. synalog-2.2.0/python/synalog/config.py +54 -0
  52. {synalog-2.0.0 → synalog-2.2.0}/python/synalog/execution.py +11 -30
  53. {synalog-2.0.0 → synalog-2.2.0}/python/synalog/introspect.py +46 -41
  54. {synalog-2.0.0 → synalog-2.2.0}/python/synalog/project.py +32 -49
  55. {synalog-2.0.0 → synalog-2.2.0}/python/synalog/runners.py +94 -91
  56. {synalog-2.0.0 → synalog-2.2.0}/skills/synalog/SKILL.md +43 -42
  57. synalog-2.0.0/skills/synalog/examples/company/concepts/Department.l → synalog-2.2.0/skills/synalog/examples/company/concepts/department.l +1 -1
  58. synalog-2.0.0/skills/synalog/examples/company/concepts/Person.l → synalog-2.2.0/skills/synalog/examples/company/concepts/person.l +1 -1
  59. synalog-2.0.0/skills/synalog/examples/company/concepts/WorksIn.l → synalog-2.2.0/skills/synalog/examples/company/concepts/works_in.l +3 -3
  60. synalog-2.0.0/skills/synalog/examples/company/rules/TeamMate.l → synalog-2.2.0/skills/synalog/examples/company/rules/team_mate.l +3 -3
  61. synalog-2.0.0/skills/synalog/examples/org/concepts/AllManagers.l → synalog-2.2.0/skills/synalog/examples/org/concepts/all_managers.l +1 -1
  62. synalog-2.0.0/skills/synalog/examples/sales/concepts/Region.l → synalog-2.2.0/skills/synalog/examples/sales/concepts/region.l +1 -1
  63. synalog-2.0.0/skills/synalog/examples/sales/rules/MonthlySales.l → synalog-2.2.0/skills/synalog/examples/sales/rules/monthly_sales.l +1 -1
  64. synalog-2.0.0/skills/synalog/examples/sales/rules/TopRegion.l → synalog-2.2.0/skills/synalog/examples/sales/rules/top_region.l +1 -1
  65. synalog-2.0.0/skills/synalog/examples/sales/rules/TotalByRegion.l → synalog-2.2.0/skills/synalog/examples/sales/rules/total_by_region.l +2 -2
  66. {synalog-2.0.0 → synalog-2.2.0}/src/assertion/mod.rs +1 -1
  67. {synalog-2.0.0 → synalog-2.2.0}/src/assertion/parse.rs +10 -0
  68. {synalog-2.0.0 → synalog-2.2.0}/src/assertion/translate.rs +22 -18
  69. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/dialects.rs +8 -4
  70. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/expr_translate_test.rs +12 -0
  71. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/functors.rs +231 -2
  72. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/functors_test.rs +12 -0
  73. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/universe.rs +144 -8
  74. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/universe_test.rs +138 -12
  75. {synalog-2.0.0 → synalog-2.2.0}/src/errors.rs +7 -0
  76. {synalog-2.0.0 → synalog-2.2.0}/src/python.rs +95 -12
  77. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/assertions.rs +5 -0
  78. synalog-2.2.0/src/verifier/dependencies.rs +63 -0
  79. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/directives.rs +3 -2
  80. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/functors.rs +1 -1
  81. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/mod.rs +32 -1
  82. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/undefined.rs +120 -4
  83. {synalog-2.0.0 → synalog-2.2.0}/src/wasm.rs +13 -2
  84. {synalog-2.0.0 → synalog-2.2.0}/uv.lock +1 -1
  85. {synalog-2.0.0 → synalog-2.2.0}/zensical.toml +4 -2
  86. synalog-2.0.0/docs/assets/synalog-dark.svg +0 -11
  87. synalog-2.0.0/docs/assets/synalog-light.svg +0 -11
  88. synalog-2.0.0/docs/examples/axioms.l +0 -122
  89. synalog-2.0.0/docs/examples/axioms.log +0 -13203
  90. synalog-2.0.0/docs/examples/axioms.ttl +0 -68
  91. synalog-2.0.0/docs/examples/functions.log +0 -103
  92. synalog-2.0.0/img/synalog-dark.svg +0 -11
  93. synalog-2.0.0/img/synalog-light.svg +0 -11
  94. synalog-2.0.0/python/synalog/config.py +0 -113
  95. {synalog-2.0.0 → synalog-2.2.0}/.gitignore +0 -0
  96. {synalog-2.0.0 → synalog-2.2.0}/LICENSE +0 -0
  97. {synalog-2.0.0 → synalog-2.2.0}/NOTICE +0 -0
  98. {synalog-2.0.0 → synalog-2.2.0}/docs/assets/synalog_favicon.png +0 -0
  99. {synalog-2.0.0 → synalog-2.2.0}/docs/benchmark/results.json +0 -0
  100. {synalog-2.0.0 → synalog-2.2.0}/docs/benchmark/speedup_by_engine.png +0 -0
  101. {synalog-2.0.0 → synalog-2.2.0}/docs/benchmark/speedup_distribution.png +0 -0
  102. {synalog-2.0.0 → synalog-2.2.0}/docs/benchmark/summary.md +0 -0
  103. {synalog-2.0.0 → synalog-2.2.0}/docs/benchmark/summary.png +0 -0
  104. {synalog-2.0.0 → synalog-2.2.0}/docs/benchmark/time_comparison.png +0 -0
  105. {synalog-2.0.0 → synalog-2.2.0}/docs/benchmark/verify_by_engine.png +0 -0
  106. {synalog-2.0.0 → synalog-2.2.0}/docs/benchmark.md +0 -0
  107. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/aggregation.l +0 -0
  108. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/aggregation.log +0 -0
  109. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/directives.l +0 -0
  110. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/directives.log +0 -0
  111. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/functions.l +0 -0
  112. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/functors.l +0 -0
  113. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/functors.log +0 -0
  114. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/getting_started.l +0 -0
  115. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/getting_started.log +0 -0
  116. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/knowledge_graphs.l +0 -0
  117. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/knowledge_graphs.log +0 -0
  118. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/recursion.l +0 -0
  119. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/shop/data/orders.csv +0 -0
  120. /synalog-2.0.0/docs/examples/shop/synalog.toml → /synalog-2.2.0/docs/examples/shop/layer.toml +0 -0
  121. /synalog-2.0.0/docs/examples/shop/tables/Orders.l → /synalog-2.2.0/docs/examples/shop/tables/orders.l +0 -0
  122. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/smoke/data/smoke_tests.csv +0 -0
  123. /synalog-2.0.0/docs/examples/smoke/synalog.toml → /synalog-2.2.0/docs/examples/smoke/layer.toml +0 -0
  124. /synalog-2.0.0/docs/examples/smoke/tables/SmokeTests.l → /synalog-2.2.0/docs/examples/smoke/tables/smoke_tests.l +0 -0
  125. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/syntax.l +0 -0
  126. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/temporal.l +0 -0
  127. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/temporal_graph.l +0 -0
  128. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/verification.l +0 -0
  129. {synalog-2.0.0 → synalog-2.2.0}/docs/examples/verification.log +0 -0
  130. {synalog-2.0.0 → synalog-2.2.0}/docs/index.md +0 -0
  131. {synalog-2.0.0 → synalog-2.2.0}/docs/javascripts/summarize.js +0 -0
  132. {synalog-2.0.0 → synalog-2.2.0}/docs/playground/vendor/codemirror.js +0 -0
  133. {synalog-2.0.0 → synalog-2.2.0}/docs/playground.md +0 -0
  134. {synalog-2.0.0 → synalog-2.2.0}/docs/why.md +0 -0
  135. {synalog-2.0.0 → synalog-2.2.0}/python/synalog/__main__.py +0 -0
  136. {synalog-2.0.0 → synalog-2.2.0}/skills/synalog/examples/company/data/departments.csv +0 -0
  137. {synalog-2.0.0 → synalog-2.2.0}/skills/synalog/examples/company/data/employees.csv +0 -0
  138. /synalog-2.0.0/skills/synalog/examples/company/synalog.toml → /synalog-2.2.0/skills/synalog/examples/company/layer.toml +0 -0
  139. /synalog-2.0.0/skills/synalog/examples/company/tables/Departments.l → /synalog-2.2.0/skills/synalog/examples/company/tables/departments.l +0 -0
  140. /synalog-2.0.0/skills/synalog/examples/company/tables/Employees.l → /synalog-2.2.0/skills/synalog/examples/company/tables/employees.l +0 -0
  141. {synalog-2.0.0 → synalog-2.2.0}/skills/synalog/examples/org/data/employees.csv +0 -0
  142. /synalog-2.0.0/skills/synalog/examples/org/synalog.toml → /synalog-2.2.0/skills/synalog/examples/org/layer.toml +0 -0
  143. /synalog-2.0.0/skills/synalog/examples/org/tables/Employees.l → /synalog-2.2.0/skills/synalog/examples/org/tables/employees.l +0 -0
  144. {synalog-2.0.0 → synalog-2.2.0}/skills/synalog/examples/sales/data/sales.csv +0 -0
  145. /synalog-2.0.0/skills/synalog/examples/sales/synalog.toml → /synalog-2.2.0/skills/synalog/examples/sales/layer.toml +0 -0
  146. /synalog-2.0.0/skills/synalog/examples/sales/tables/Sales.l → /synalog-2.2.0/skills/synalog/examples/sales/tables/sales.l +0 -0
  147. {synalog-2.0.0 → synalog-2.2.0}/src/assertion/report.rs +0 -0
  148. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/annotations.rs +0 -0
  149. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/annotations_test.rs +0 -0
  150. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/concertina.rs +0 -0
  151. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/dialects_test.rs +0 -0
  152. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/expr_translate.rs +0 -0
  153. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/mod.rs +0 -0
  154. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/program.rs +0 -0
  155. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/program_test.rs +0 -0
  156. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/rule_translate.rs +0 -0
  157. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/rule_translate_test.rs +0 -0
  158. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/type_inference/builder.rs +0 -0
  159. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/type_inference/built_in.rs +0 -0
  160. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/type_inference/edge.rs +0 -0
  161. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/type_inference/expression.rs +0 -0
  162. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/type_inference/graph.rs +0 -0
  163. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/type_inference/inference.rs +0 -0
  164. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/type_inference/intersection.rs +0 -0
  165. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/type_inference/mod.rs +0 -0
  166. {synalog-2.0.0 → synalog-2.2.0}/src/compiler/type_inference/types.rs +0 -0
  167. {synalog-2.0.0 → synalog-2.2.0}/src/lib.rs +0 -0
  168. {synalog-2.0.0 → synalog-2.2.0}/src/parser/json.rs +0 -0
  169. {synalog-2.0.0 → synalog-2.2.0}/src/parser/json_test.rs +0 -0
  170. {synalog-2.0.0 → synalog-2.2.0}/src/parser/mod.rs +0 -0
  171. {synalog-2.0.0 → synalog-2.2.0}/src/parser/names.rs +0 -0
  172. {synalog-2.0.0 → synalog-2.2.0}/src/parser/parse.rs +0 -0
  173. {synalog-2.0.0 → synalog-2.2.0}/src/parser/parse_test.rs +0 -0
  174. {synalog-2.0.0 → synalog-2.2.0}/src/parser/rewrite.rs +0 -0
  175. {synalog-2.0.0 → synalog-2.2.0}/src/parser/rewrite_test.rs +0 -0
  176. {synalog-2.0.0 → synalog-2.2.0}/src/parser/span.rs +0 -0
  177. {synalog-2.0.0 → synalog-2.2.0}/src/parser/span_test.rs +0 -0
  178. {synalog-2.0.0 → synalog-2.2.0}/src/parser/traverse.rs +0 -0
  179. {synalog-2.0.0 → synalog-2.2.0}/src/parser/traverse_test.rs +0 -0
  180. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/arity.rs +0 -0
  181. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/contradiction.rs +0 -0
  182. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/front_matter.rs +0 -0
  183. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/orderby.rs +0 -0
  184. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/positional.rs +0 -0
  185. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/recursion.rs +0 -0
  186. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/reserved.rs +0 -0
  187. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/safety.rs +0 -0
  188. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/sqlexpr.rs +0 -0
  189. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/stratification.rs +0 -0
  190. {synalog-2.0.0 → synalog-2.2.0}/src/verifier/vars.rs +0 -0
@@ -2348,7 +2348,7 @@ dependencies = [
2348
2348
 
2349
2349
  [[package]]
2350
2350
  name = "synalog"
2351
- version = "2.0.0"
2351
+ version = "2.2.0"
2352
2352
  dependencies = [
2353
2353
  "anyhow",
2354
2354
  "duckdb",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "synalog"
3
- version = "2.0.0"
3
+ version = "2.2.0"
4
4
  edition = "2024"
5
5
  description = "Logic programming for AI agents: Datalog-family language compiling to optimized SQL"
6
6
  license = "Apache-2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synalog
3
- Version: 2.0.0
3
+ Version: 2.2.0
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: 3
@@ -162,13 +162,13 @@ $ synalog program.l run EngineeringTeam
162
162
  - `--limit` / `--offset` paginate the result.
163
163
  - `--csv` (with `run`) prints results as CSV instead of the rendered table.
164
164
  - `--search REGEX` (with `print`/`run`) keeps only rows where some column matches the regular expression `REGEX`, e.g. `synalog program.l run Customers --search "(?i)acme"`. In the interactive session the same is `.search Customers (?i)acme`.
165
- - `run` executes on `duckdb` (the default), `sqlite` (stdlib) or `psql` (with `--dsn` or `SYNALOG_PSQL_DSN`) out of the box: synalog depends on duckdb and on psycopg with its bundled libpq, on macOS and Linux alike. For other engines, install their driver (`pip install 'synalog[trino]'`, ...) or use `print` and run the SQL with your own client.
165
+ - `run` executes on `duckdb` (the default), `sqlite` (stdlib) or `psql` (in a [project](#projects-layertoml) connected to it) out of the box: synalog depends on duckdb and on psycopg with its bundled libpq, on macOS and Linux alike. For other engines, install their driver (`pip install 'synalog[trino]'`, ...) or use `print` and run the SQL with your own client.
166
166
  - `import path.to.file.Pred;` statements resolve `path/to/file.l` against the program file's directory, then the current directory; pass `--import-root DIR` (repeatable) to search elsewhere.
167
167
  - `--load TABLE=PATH` (repeatable) loads a csv/tsv/json/jsonl/parquet file as a table before running, e.g. `synalog senior.l run Senior --load employees=employees.csv`.
168
168
 
169
- ### Projects: `synalog.toml`
169
+ ### Projects: `layer.toml`
170
170
 
171
- A folder with a `synalog.toml` is a project. Its `[connection]` says which database its programs run on, as plain fields — commit it:
171
+ A folder with a `layer.toml` is a project. Its `[connection]` says which database its programs run on, as plain fields — commit it:
172
172
 
173
173
  ```toml
174
174
  [connection]
@@ -180,7 +180,7 @@ user = "analyst"
180
180
  schema = "public"
181
181
  ```
182
182
 
183
- Secrets never go in the file (synalog refuses it): each comes from the environment as `SYNALOG_<ENGINE>_<FIELD>` — `SYNALOG_PSQL_PASSWORD`, `SYNALOG_DATABRICKS_ACCESS_TOKEN`, or `GOOGLE_APPLICATION_CREDENTIALS` for BigQuery — usually from the project's `.env`, kept out of git. Inside the project (from the program's folder or the current directory, and their parents), `run`, `print` and `verify` use that engine and connection, imports resolve from the project's folder, and `synalog introspect` writes the project's `tables/`, one file per table of the database; `--engine`, an `@Engine` annotation, `--dsn` and `SYNALOG_<ENGINE>_DSN` still take precedence. The fields of every engine are in `synalog.project.ENGINES`.
183
+ Secrets never go in the file (synalog refuses it): each comes from the environment as `SYNALOG_<ENGINE>_<FIELD>` — `SYNALOG_PSQL_PASSWORD`, `SYNALOG_DATABRICKS_ACCESS_TOKEN`, or `GOOGLE_APPLICATION_CREDENTIALS` for BigQuery — usually from the project's `.env`, kept out of git. Inside the project (from the program's folder or the current directory, and their parents), `run`, `print` and `verify` use that engine and connection, imports resolve from the project's folder, and `synalog introspect` writes the project's `tables/`, one file per table of the database. The project's file is the only place a connection comes from; `--engine` and an `@Engine` annotation choose the dialect, and a remote engine runs only through the project's connection to it. `synalog connect <engine> key=value ...` writes it — the non-secret fields to `layer.toml`, the secret ones to `.env` — `synalog connect` shows it (secrets hidden), `synalog connect clear` removes it. The fields of every engine are in `synalog.project.ENGINES`.
184
184
 
185
185
  Running `synalog` with no arguments starts an interactive session, in the spirit of `python` (the options above, e.g. `--engine` or `--load`, apply to it too):
186
186
 
@@ -262,7 +262,7 @@ for name, sql in sqls.items():
262
262
  print(name, sql)
263
263
  ```
264
264
 
265
- ### `execute(source, predicate, engine=None, dsn=None, import_root=None, limit=None, offset=None, pattern=None, assertion=None, loads=()) -> tuple[list[str], list[tuple]]`
265
+ ### `execute(source, predicate, engine=None, project=None, import_root=None, limit=None, offset=None, pattern=None, assertion=None, loads=()) -> tuple[list[str], list[tuple]]`
266
266
 
267
267
  Run a predicate on its database (the program's or project's engine and connection) and return `(columns, rows)`. Each recursion stops as soon as a step changes nothing, so it costs the steps its data needs, and `@Recursive(P, -1)` (until nothing changes) runs on every engine. `plan()` returns the same steps for a host that runs them itself.
268
268
 
@@ -270,9 +270,9 @@ Run a predicate on its database (the program's or project's engine and connectio
270
270
  columns, rows = synalog.execute(source, "TopCustomers", limit=20)
271
271
  ```
272
272
 
273
- ### `check(source, engine=None, import_root=None, assertions=True, dsn=None) -> tuple[list[str], list[str]]`
273
+ ### `check(source, engine=None, import_root=None, assertions=True, project=None) -> tuple[list[str], list[str]]`
274
274
 
275
- Run structural validation. Returns the error messages and the warning messages, as two lists. The program is valid when there is no error; warnings do not make it invalid. Inside a project whose `synalog.toml` has a `[connection]` (or given a `dsn`), it also runs the program's `@Assert` statements against the database and reports each violated one as an error; `assertions=False` keeps it offline.
275
+ Run structural validation. Returns the error messages and the warning messages, as two lists. The program is valid when there is no error; warnings do not make it invalid. Inside a project whose `layer.toml` has a `[connection]` — the current directory's, or the folder `project` names — it also runs the program's `@Assert` statements against the database and reports each violated one as an error; `assertions=False` keeps it offline.
276
276
 
277
277
  ```python
278
278
  errors, warnings = synalog.check(source)
@@ -285,22 +285,22 @@ All of these functions accept an optional `engine` keyword that overrides the pr
285
285
 
286
286
  ## Language overview
287
287
 
288
- A Synalog program is a **project**: a folder with a `synalog.toml`, holding one predicate per file in three folders, the layout of [semantic layers](https://github.com/SynaLinks/semantic-layers):
288
+ A Synalog program is a **project**: a folder with a `layer.toml`, holding one predicate per file in three folders, the layout of [semantic layers](https://github.com/SynaLinks/semantic-layers):
289
289
 
290
290
  ```
291
291
  shop/
292
- ├── tables/Orders.l # the data: one file per database table
293
- ├── concepts/Customer.l # entities and relationships extracted from tables
294
- ├── rules/CustomerRevenue.l # insights derived from concepts
295
- ├── rules/TopCustomers.l
296
- └── synalog.toml # the project's name, description and database
292
+ ├── tables/orders.l # the data: one file per database table
293
+ ├── concepts/customer.l # entities and relationships extracted from tables
294
+ ├── rules/customer_revenue.l # insights derived from concepts
295
+ ├── rules/top_customers.l
296
+ └── layer.toml # the project's name, description and database
297
297
  ```
298
298
 
299
299
  Each file is named after its predicate and opens with front matter (`name`, the predicate; `description`, what its rows are), then imports what it builds on by folder, file and predicate, then orders its predicate with `@OrderBy`. A database table is referenced by its lowercase database name in `tables/`, and everything else builds on the PascalCase predicate that maps it:
300
300
 
301
- `tables/Orders.l`
301
+ `tables/orders.l`
302
302
 
303
- ```logica
303
+ ```synalog
304
304
  ---
305
305
  name: Orders
306
306
  description: One row per order, with its customer, amount and status.
@@ -310,41 +310,41 @@ Orders(order_id:, customer_id:, amount:, status:) :-
310
310
  orders(order_id:, customer_id:, amount:, status:);
311
311
  ```
312
312
 
313
- `concepts/Customer.l`
313
+ `concepts/customer.l`
314
314
 
315
- ```logica
315
+ ```synalog
316
316
  ---
317
317
  name: Customer
318
318
  description: Every customer who placed at least one order.
319
319
  ---
320
- import tables.Orders.Orders;
320
+ import tables.orders.Orders;
321
321
 
322
322
  @OrderBy(Customer, "customer_id");
323
323
  Customer(customer_id:) distinct :- Orders(customer_id:);
324
324
  ```
325
325
 
326
- `rules/CustomerRevenue.l`
326
+ `rules/customer_revenue.l`
327
327
 
328
- ```logica
328
+ ```synalog
329
329
  ---
330
330
  name: CustomerRevenue
331
331
  description: Total amount ordered by each customer, all statuses included.
332
332
  ---
333
- import concepts.Customer.Customer;
334
- import tables.Orders.Orders;
333
+ import concepts.customer.Customer;
334
+ import tables.orders.Orders;
335
335
 
336
336
  @OrderBy(CustomerRevenue, "customer_id");
337
337
  CustomerRevenue(customer_id:, total? += amount) distinct :-
338
338
  Customer(customer_id:), Orders(customer_id:, amount:);
339
339
  ```
340
340
 
341
- Imports resolve from the project's folder, so every file runs on its own: `synalog rules/CustomerRevenue.l run CustomerRevenue`. The snippets below show the language itself, one rule at a time.
341
+ Imports resolve from the project's folder, so every file runs on its own: `synalog rules/customer_revenue.l run CustomerRevenue`. The snippets below show the language itself, one rule at a time.
342
342
 
343
343
  ### Named arguments
344
344
 
345
345
  Synalog uses **named arguments only** (no positional arguments). The left side of `:` is the column name, the right side is the variable:
346
346
 
347
- ```logica
347
+ ```synalog
348
348
  # column "amount" bound to variable "total"
349
349
  Orders(amount: total)
350
350
 
@@ -356,7 +356,7 @@ Orders(amount:)
356
356
 
357
357
  Variables are defined with `==`. Arithmetic, string and comparison operators are supported:
358
358
 
359
- ```logica
359
+ ```synalog
360
360
  OrderWithTax(order_id:, total:) :-
361
361
  Orders(order_id:, amount:),
362
362
  total == amount * 1.10;
@@ -368,7 +368,7 @@ OrderWithTax(order_id:, total:) :-
368
368
 
369
369
  Aggregation uses the `distinct` keyword and special operators in the rule head:
370
370
 
371
- ```logica
371
+ ```synalog
372
372
  # Sum
373
373
  Revenue(total? += amount) distinct :- Orders(amount:);
374
374
 
@@ -392,7 +392,7 @@ TopSeller(name? ArgMax= name -> revenue) distinct :- Sales(name:, revenue:);
392
392
 
393
393
  ### Logical operators
394
394
 
395
- ```logica
395
+ ```synalog
396
396
  # Conjunction (AND), comma
397
397
  Result(x:, y:) :- TableA(x:), TableB(x:, y:);
398
398
 
@@ -405,14 +405,14 @@ Inactive(user_id:) :- Users(user_id:), ~Logins(user_id:);
405
405
 
406
406
  Multiple rule definitions for the same predicate combine results (union):
407
407
 
408
- ```logica
408
+ ```synalog
409
409
  HighValue(user_id:) :- Orders(user_id:, amount:), amount > 10000;
410
410
  HighValue(user_id:) :- Referrals(user_id:, tier: "vip");
411
411
  ```
412
412
 
413
413
  ### Conditionals
414
414
 
415
- ```logica
415
+ ```synalog
416
416
  OrderSize(order_id:, size:) :-
417
417
  Orders(order_id:, amount:),
418
418
  size == (if amount > 1000 then "large"
@@ -424,7 +424,7 @@ OrderSize(order_id:, size:) :-
424
424
 
425
425
  Directives control predicate behavior and **must be placed before** the rule definition:
426
426
 
427
- ```logica
427
+ ```synalog
428
428
  @OrderBy(TopCustomers, "total", "DESC");
429
429
  @Limit(TopCustomers, 10);
430
430
  TopCustomers(customer_id:, total? += amount) distinct :- Orders(customer_id:, amount:);
@@ -442,7 +442,7 @@ TopCustomers(customer_id:, total? += amount) distinct :- Orders(customer_id:, am
442
442
 
443
443
  Functors let you reuse predicate logic by parameterizing input predicates:
444
444
 
445
- ```logica
445
+ ```synalog
446
446
  # Define a reusable pattern
447
447
  @OrderBy(SegmentRevenue, "segment_id");
448
448
  SegmentRevenue(segment_id:, total? += amount) distinct :-
@@ -458,7 +458,7 @@ SMBRevenue := SegmentRevenue(Segment: SMBCustomers);
458
458
 
459
459
  Recursive predicates compute transitive closures, for example finding all managers above an employee:
460
460
 
461
- ```logica
461
+ ```synalog
462
462
  @Recursive(AllManagers, 20);
463
463
 
464
464
  # Base case: direct manager
@@ -476,7 +476,7 @@ Useful for: referral chains, org charts, product taxonomies, bill of materials.
476
476
 
477
477
  Find shortest paths in weighted graphs by enumerating route costs recursively, then keeping the minimum per destination with a `Min=` aggregation:
478
478
 
479
- ```logica
479
+ ```synalog
480
480
  # Enumerate route costs from the origin, hop by hop.
481
481
  @Recursive(RouteCost, 10);
482
482
  RouteCost(destination:, cost:) :-
@@ -495,7 +495,7 @@ ShippingCost(destination:, total? Min= cost) distinct :- RouteCost(destination:,
495
495
 
496
496
  When working with timestamps or dates, always convert to string first:
497
497
 
498
- ```logica
498
+ ```synalog
499
499
  @OrderBy(MonthlyOrders, "month");
500
500
  MonthlyOrders(month:, count? += 1) distinct :-
501
501
  Orders(created_at:),
@@ -509,7 +509,7 @@ RecentOrders(order_id:) :-
509
509
 
510
510
  `Today(date:)` (today's date as `"YYYY-MM-DD"`) and `Now(timestamp:)` (the current instant as the engine's native timestamp) are built-in concepts. They are inlined per dialect by the compiler, with no runtime table needed, so they work on every engine. `Now` is the most precise value; derive coarser parts (date, time, hour) from it through the `ToString` → `Substr` pipeline. Join against `Today` whenever a rule needs "today":
511
511
 
512
- ```logica
512
+ ```synalog
513
513
  @OrderBy(ThisMonthOrders, "order_id");
514
514
  ThisMonthOrders(order_id:, created_at:) :-
515
515
  Orders(order_id:, created_at:),
@@ -523,7 +523,7 @@ They are reserved names: you cannot redefine, extend, or update them.
523
523
 
524
524
  Entities and relationships become node and edge concepts, and rules traverse them. Edges join **through** the node concepts rather than the raw table, so a filter on a node applies to every edge built on it:
525
525
 
526
- ```logica
526
+ ```synalog
527
527
  @OrderBy(Person, "person_id");
528
528
  Person(person_id:, name:, profile_url:) distinct :- Employees(person_id:, name:, profile_url:);
529
529
 
@@ -536,7 +536,7 @@ WorksIn(person_id:, department_id:) distinct :-
536
536
 
537
537
  Relationships with a lifetime carry it as a half-open interval `[valid_from, valid_to)`, using `"9999-12-31"` as the open end so ISO strings compare correctly, and `Today` supplies the clock for point-in-time questions:
538
538
 
539
- ```logica
539
+ ```synalog
540
540
  @OrderBy(CurrentEmployment, "person_id");
541
541
  CurrentEmployment(person_id:, company_id:, role:) distinct :-
542
542
  EmployedAt(person_id:, company_id:, role:, valid_from:, valid_to:),
@@ -120,13 +120,13 @@ $ synalog program.l run EngineeringTeam
120
120
  - `--limit` / `--offset` paginate the result.
121
121
  - `--csv` (with `run`) prints results as CSV instead of the rendered table.
122
122
  - `--search REGEX` (with `print`/`run`) keeps only rows where some column matches the regular expression `REGEX`, e.g. `synalog program.l run Customers --search "(?i)acme"`. In the interactive session the same is `.search Customers (?i)acme`.
123
- - `run` executes on `duckdb` (the default), `sqlite` (stdlib) or `psql` (with `--dsn` or `SYNALOG_PSQL_DSN`) out of the box: synalog depends on duckdb and on psycopg with its bundled libpq, on macOS and Linux alike. For other engines, install their driver (`pip install 'synalog[trino]'`, ...) or use `print` and run the SQL with your own client.
123
+ - `run` executes on `duckdb` (the default), `sqlite` (stdlib) or `psql` (in a [project](#projects-layertoml) connected to it) out of the box: synalog depends on duckdb and on psycopg with its bundled libpq, on macOS and Linux alike. For other engines, install their driver (`pip install 'synalog[trino]'`, ...) or use `print` and run the SQL with your own client.
124
124
  - `import path.to.file.Pred;` statements resolve `path/to/file.l` against the program file's directory, then the current directory; pass `--import-root DIR` (repeatable) to search elsewhere.
125
125
  - `--load TABLE=PATH` (repeatable) loads a csv/tsv/json/jsonl/parquet file as a table before running, e.g. `synalog senior.l run Senior --load employees=employees.csv`.
126
126
 
127
- ### Projects: `synalog.toml`
127
+ ### Projects: `layer.toml`
128
128
 
129
- A folder with a `synalog.toml` is a project. Its `[connection]` says which database its programs run on, as plain fields — commit it:
129
+ A folder with a `layer.toml` is a project. Its `[connection]` says which database its programs run on, as plain fields — commit it:
130
130
 
131
131
  ```toml
132
132
  [connection]
@@ -138,7 +138,7 @@ user = "analyst"
138
138
  schema = "public"
139
139
  ```
140
140
 
141
- Secrets never go in the file (synalog refuses it): each comes from the environment as `SYNALOG_<ENGINE>_<FIELD>` — `SYNALOG_PSQL_PASSWORD`, `SYNALOG_DATABRICKS_ACCESS_TOKEN`, or `GOOGLE_APPLICATION_CREDENTIALS` for BigQuery — usually from the project's `.env`, kept out of git. Inside the project (from the program's folder or the current directory, and their parents), `run`, `print` and `verify` use that engine and connection, imports resolve from the project's folder, and `synalog introspect` writes the project's `tables/`, one file per table of the database; `--engine`, an `@Engine` annotation, `--dsn` and `SYNALOG_<ENGINE>_DSN` still take precedence. The fields of every engine are in `synalog.project.ENGINES`.
141
+ Secrets never go in the file (synalog refuses it): each comes from the environment as `SYNALOG_<ENGINE>_<FIELD>` — `SYNALOG_PSQL_PASSWORD`, `SYNALOG_DATABRICKS_ACCESS_TOKEN`, or `GOOGLE_APPLICATION_CREDENTIALS` for BigQuery — usually from the project's `.env`, kept out of git. Inside the project (from the program's folder or the current directory, and their parents), `run`, `print` and `verify` use that engine and connection, imports resolve from the project's folder, and `synalog introspect` writes the project's `tables/`, one file per table of the database. The project's file is the only place a connection comes from; `--engine` and an `@Engine` annotation choose the dialect, and a remote engine runs only through the project's connection to it. `synalog connect <engine> key=value ...` writes it — the non-secret fields to `layer.toml`, the secret ones to `.env` — `synalog connect` shows it (secrets hidden), `synalog connect clear` removes it. The fields of every engine are in `synalog.project.ENGINES`.
142
142
 
143
143
  Running `synalog` with no arguments starts an interactive session, in the spirit of `python` (the options above, e.g. `--engine` or `--load`, apply to it too):
144
144
 
@@ -220,7 +220,7 @@ for name, sql in sqls.items():
220
220
  print(name, sql)
221
221
  ```
222
222
 
223
- ### `execute(source, predicate, engine=None, dsn=None, import_root=None, limit=None, offset=None, pattern=None, assertion=None, loads=()) -> tuple[list[str], list[tuple]]`
223
+ ### `execute(source, predicate, engine=None, project=None, import_root=None, limit=None, offset=None, pattern=None, assertion=None, loads=()) -> tuple[list[str], list[tuple]]`
224
224
 
225
225
  Run a predicate on its database (the program's or project's engine and connection) and return `(columns, rows)`. Each recursion stops as soon as a step changes nothing, so it costs the steps its data needs, and `@Recursive(P, -1)` (until nothing changes) runs on every engine. `plan()` returns the same steps for a host that runs them itself.
226
226
 
@@ -228,9 +228,9 @@ Run a predicate on its database (the program's or project's engine and connectio
228
228
  columns, rows = synalog.execute(source, "TopCustomers", limit=20)
229
229
  ```
230
230
 
231
- ### `check(source, engine=None, import_root=None, assertions=True, dsn=None) -> tuple[list[str], list[str]]`
231
+ ### `check(source, engine=None, import_root=None, assertions=True, project=None) -> tuple[list[str], list[str]]`
232
232
 
233
- Run structural validation. Returns the error messages and the warning messages, as two lists. The program is valid when there is no error; warnings do not make it invalid. Inside a project whose `synalog.toml` has a `[connection]` (or given a `dsn`), it also runs the program's `@Assert` statements against the database and reports each violated one as an error; `assertions=False` keeps it offline.
233
+ Run structural validation. Returns the error messages and the warning messages, as two lists. The program is valid when there is no error; warnings do not make it invalid. Inside a project whose `layer.toml` has a `[connection]` — the current directory's, or the folder `project` names — it also runs the program's `@Assert` statements against the database and reports each violated one as an error; `assertions=False` keeps it offline.
234
234
 
235
235
  ```python
236
236
  errors, warnings = synalog.check(source)
@@ -243,22 +243,22 @@ All of these functions accept an optional `engine` keyword that overrides the pr
243
243
 
244
244
  ## Language overview
245
245
 
246
- A Synalog program is a **project**: a folder with a `synalog.toml`, holding one predicate per file in three folders, the layout of [semantic layers](https://github.com/SynaLinks/semantic-layers):
246
+ A Synalog program is a **project**: a folder with a `layer.toml`, holding one predicate per file in three folders, the layout of [semantic layers](https://github.com/SynaLinks/semantic-layers):
247
247
 
248
248
  ```
249
249
  shop/
250
- ├── tables/Orders.l # the data: one file per database table
251
- ├── concepts/Customer.l # entities and relationships extracted from tables
252
- ├── rules/CustomerRevenue.l # insights derived from concepts
253
- ├── rules/TopCustomers.l
254
- └── synalog.toml # the project's name, description and database
250
+ ├── tables/orders.l # the data: one file per database table
251
+ ├── concepts/customer.l # entities and relationships extracted from tables
252
+ ├── rules/customer_revenue.l # insights derived from concepts
253
+ ├── rules/top_customers.l
254
+ └── layer.toml # the project's name, description and database
255
255
  ```
256
256
 
257
257
  Each file is named after its predicate and opens with front matter (`name`, the predicate; `description`, what its rows are), then imports what it builds on by folder, file and predicate, then orders its predicate with `@OrderBy`. A database table is referenced by its lowercase database name in `tables/`, and everything else builds on the PascalCase predicate that maps it:
258
258
 
259
- `tables/Orders.l`
259
+ `tables/orders.l`
260
260
 
261
- ```logica
261
+ ```synalog
262
262
  ---
263
263
  name: Orders
264
264
  description: One row per order, with its customer, amount and status.
@@ -268,41 +268,41 @@ Orders(order_id:, customer_id:, amount:, status:) :-
268
268
  orders(order_id:, customer_id:, amount:, status:);
269
269
  ```
270
270
 
271
- `concepts/Customer.l`
271
+ `concepts/customer.l`
272
272
 
273
- ```logica
273
+ ```synalog
274
274
  ---
275
275
  name: Customer
276
276
  description: Every customer who placed at least one order.
277
277
  ---
278
- import tables.Orders.Orders;
278
+ import tables.orders.Orders;
279
279
 
280
280
  @OrderBy(Customer, "customer_id");
281
281
  Customer(customer_id:) distinct :- Orders(customer_id:);
282
282
  ```
283
283
 
284
- `rules/CustomerRevenue.l`
284
+ `rules/customer_revenue.l`
285
285
 
286
- ```logica
286
+ ```synalog
287
287
  ---
288
288
  name: CustomerRevenue
289
289
  description: Total amount ordered by each customer, all statuses included.
290
290
  ---
291
- import concepts.Customer.Customer;
292
- import tables.Orders.Orders;
291
+ import concepts.customer.Customer;
292
+ import tables.orders.Orders;
293
293
 
294
294
  @OrderBy(CustomerRevenue, "customer_id");
295
295
  CustomerRevenue(customer_id:, total? += amount) distinct :-
296
296
  Customer(customer_id:), Orders(customer_id:, amount:);
297
297
  ```
298
298
 
299
- Imports resolve from the project's folder, so every file runs on its own: `synalog rules/CustomerRevenue.l run CustomerRevenue`. The snippets below show the language itself, one rule at a time.
299
+ Imports resolve from the project's folder, so every file runs on its own: `synalog rules/customer_revenue.l run CustomerRevenue`. The snippets below show the language itself, one rule at a time.
300
300
 
301
301
  ### Named arguments
302
302
 
303
303
  Synalog uses **named arguments only** (no positional arguments). The left side of `:` is the column name, the right side is the variable:
304
304
 
305
- ```logica
305
+ ```synalog
306
306
  # column "amount" bound to variable "total"
307
307
  Orders(amount: total)
308
308
 
@@ -314,7 +314,7 @@ Orders(amount:)
314
314
 
315
315
  Variables are defined with `==`. Arithmetic, string and comparison operators are supported:
316
316
 
317
- ```logica
317
+ ```synalog
318
318
  OrderWithTax(order_id:, total:) :-
319
319
  Orders(order_id:, amount:),
320
320
  total == amount * 1.10;
@@ -326,7 +326,7 @@ OrderWithTax(order_id:, total:) :-
326
326
 
327
327
  Aggregation uses the `distinct` keyword and special operators in the rule head:
328
328
 
329
- ```logica
329
+ ```synalog
330
330
  # Sum
331
331
  Revenue(total? += amount) distinct :- Orders(amount:);
332
332
 
@@ -350,7 +350,7 @@ TopSeller(name? ArgMax= name -> revenue) distinct :- Sales(name:, revenue:);
350
350
 
351
351
  ### Logical operators
352
352
 
353
- ```logica
353
+ ```synalog
354
354
  # Conjunction (AND), comma
355
355
  Result(x:, y:) :- TableA(x:), TableB(x:, y:);
356
356
 
@@ -363,14 +363,14 @@ Inactive(user_id:) :- Users(user_id:), ~Logins(user_id:);
363
363
 
364
364
  Multiple rule definitions for the same predicate combine results (union):
365
365
 
366
- ```logica
366
+ ```synalog
367
367
  HighValue(user_id:) :- Orders(user_id:, amount:), amount > 10000;
368
368
  HighValue(user_id:) :- Referrals(user_id:, tier: "vip");
369
369
  ```
370
370
 
371
371
  ### Conditionals
372
372
 
373
- ```logica
373
+ ```synalog
374
374
  OrderSize(order_id:, size:) :-
375
375
  Orders(order_id:, amount:),
376
376
  size == (if amount > 1000 then "large"
@@ -382,7 +382,7 @@ OrderSize(order_id:, size:) :-
382
382
 
383
383
  Directives control predicate behavior and **must be placed before** the rule definition:
384
384
 
385
- ```logica
385
+ ```synalog
386
386
  @OrderBy(TopCustomers, "total", "DESC");
387
387
  @Limit(TopCustomers, 10);
388
388
  TopCustomers(customer_id:, total? += amount) distinct :- Orders(customer_id:, amount:);
@@ -400,7 +400,7 @@ TopCustomers(customer_id:, total? += amount) distinct :- Orders(customer_id:, am
400
400
 
401
401
  Functors let you reuse predicate logic by parameterizing input predicates:
402
402
 
403
- ```logica
403
+ ```synalog
404
404
  # Define a reusable pattern
405
405
  @OrderBy(SegmentRevenue, "segment_id");
406
406
  SegmentRevenue(segment_id:, total? += amount) distinct :-
@@ -416,7 +416,7 @@ SMBRevenue := SegmentRevenue(Segment: SMBCustomers);
416
416
 
417
417
  Recursive predicates compute transitive closures, for example finding all managers above an employee:
418
418
 
419
- ```logica
419
+ ```synalog
420
420
  @Recursive(AllManagers, 20);
421
421
 
422
422
  # Base case: direct manager
@@ -434,7 +434,7 @@ Useful for: referral chains, org charts, product taxonomies, bill of materials.
434
434
 
435
435
  Find shortest paths in weighted graphs by enumerating route costs recursively, then keeping the minimum per destination with a `Min=` aggregation:
436
436
 
437
- ```logica
437
+ ```synalog
438
438
  # Enumerate route costs from the origin, hop by hop.
439
439
  @Recursive(RouteCost, 10);
440
440
  RouteCost(destination:, cost:) :-
@@ -453,7 +453,7 @@ ShippingCost(destination:, total? Min= cost) distinct :- RouteCost(destination:,
453
453
 
454
454
  When working with timestamps or dates, always convert to string first:
455
455
 
456
- ```logica
456
+ ```synalog
457
457
  @OrderBy(MonthlyOrders, "month");
458
458
  MonthlyOrders(month:, count? += 1) distinct :-
459
459
  Orders(created_at:),
@@ -467,7 +467,7 @@ RecentOrders(order_id:) :-
467
467
 
468
468
  `Today(date:)` (today's date as `"YYYY-MM-DD"`) and `Now(timestamp:)` (the current instant as the engine's native timestamp) are built-in concepts. They are inlined per dialect by the compiler, with no runtime table needed, so they work on every engine. `Now` is the most precise value; derive coarser parts (date, time, hour) from it through the `ToString` → `Substr` pipeline. Join against `Today` whenever a rule needs "today":
469
469
 
470
- ```logica
470
+ ```synalog
471
471
  @OrderBy(ThisMonthOrders, "order_id");
472
472
  ThisMonthOrders(order_id:, created_at:) :-
473
473
  Orders(order_id:, created_at:),
@@ -481,7 +481,7 @@ They are reserved names: you cannot redefine, extend, or update them.
481
481
 
482
482
  Entities and relationships become node and edge concepts, and rules traverse them. Edges join **through** the node concepts rather than the raw table, so a filter on a node applies to every edge built on it:
483
483
 
484
- ```logica
484
+ ```synalog
485
485
  @OrderBy(Person, "person_id");
486
486
  Person(person_id:, name:, profile_url:) distinct :- Employees(person_id:, name:, profile_url:);
487
487
 
@@ -494,7 +494,7 @@ WorksIn(person_id:, department_id:) distinct :-
494
494
 
495
495
  Relationships with a lifetime carry it as a half-open interval `[valid_from, valid_to)`, using `"9999-12-31"` as the open end so ISO strings compare correctly, and `Today` supplies the clock for point-in-time questions:
496
496
 
497
- ```logica
497
+ ```synalog
498
498
  @OrderBy(CurrentEmployment, "person_id");
499
499
  CurrentEmployment(person_id:, company_id:, role:) distinct :-
500
500
  EmployedAt(person_id:, company_id:, role:, valid_from:, valid_to:),
@@ -9,7 +9,7 @@ Assertions are written in first-order logic, not in Synalog. A rule and its asse
9
9
 
10
10
  ## A first assertion
11
11
 
12
- ```logica
12
+ ```synalog
13
13
  Parent(x:, y:) :- parents(parent: x, child: y);
14
14
 
15
15
  @Assert(Ancestor,
@@ -48,7 +48,7 @@ Carol is Alice's grandchild, Erin is Carol's, but Erin is Alice's great-grandchi
48
48
 
49
49
  `@Assert` takes the predicate the properties are about, then one named argument per property: the name identifies the property in reports, the string is the statement.
50
50
 
51
- ```logica
51
+ ```synalog
52
52
  @Assert(Revenue,
53
53
  one_row_per_customer: "∀ c r s, Revenue c r → Revenue c s → r = s",
54
54
  positive: "∀ c, Revenue c > 0");
@@ -57,6 +57,7 @@ Carol is Alice's grandchild, Erin is Carol's, but Erin is Alice's great-grandchi
57
57
  - A predicate may have several `@Assert` annotations; a name may appear only once per predicate.
58
58
  - A statement that spans several lines goes in a triple-quoted string (`"""..."""`).
59
59
  - An assertion can be written before its predicate exists: it waits, as `pending`, until the predicate is defined. Write the contract first.
60
+ - Once its predicate exists, an assertion naming a predicate that nothing defines or imports is a warning: a misspelled name, or a missing import, would leave it pending, never checked.
60
61
  - Assertions do not change the generated SQL.
61
62
 
62
63
  ## The statement language
@@ -74,7 +75,7 @@ Statements are written as [Lean](https://lean-lang.org/) propositions, with the
74
75
  | `+`, `-`, `*`, `/` | | arithmetic |
75
76
  | `∑ x, t` | `sum x, t` | sum of `t` over `x` |
76
77
 
77
- ```logica
78
+ ```synalog
78
79
  @Assert(Revenue, consistent: """forall c r s,
79
80
  Revenue c r -> Revenue c s -> r = s""");
80
81
  ```
@@ -92,7 +93,7 @@ A name bound by a quantifier is a variable; an unbound lowercase name is univers
92
93
 
93
94
  The properties worth stating are the ones a rule can silently get wrong. All of these run as written against the tables `orders(order_id, customer_id, amount, status)` and `customers(customer_id, country)`:
94
95
 
95
- ```logica
96
+ ```synalog
96
97
  Order(order_id:, customer_id:, amount:, status:) :- orders(order_id:, customer_id:, amount:, status:);
97
98
  Customer(customer_id:, country:) :- customers(customer_id:, country:);
98
99
 
@@ -143,7 +144,7 @@ An assertion is checked by looking for its counterexamples: Synalog compiles tha
143
144
  | `synalog program.l verify [Predicate ...]` | Runs every assertion, or those of the given predicates, and prints the counterexamples of each violated one (5 by default, `--limit` to change). Exits 1 if one is violated. |
144
145
  | `synalog program.l run Predicate` | Checks the program's assertions before it prints anything, and exits 1 with a few counterexamples if one is violated. |
145
146
  | `synalog program.l print Predicate` | Never touches the database: assertions are not run. |
146
- | [`check()`](python-api.md#check) | Runs the assertions when a database is known: inside a project whose `synalog.toml` has a `[connection]`, or given a `dsn`. Each violated assertion is an error. `assertions=False` keeps it offline. |
147
+ | [`check()`](python-api.md#check) | Runs the assertions when a database is known: inside a project whose `layer.toml` has a `[connection]` (the current directory's, or the folder `project` names). Each violated assertion is an error. `assertions=False` keeps it offline. |
147
148
 
148
149
  ```text
149
150
  $ synalog family.l run Grandparent --load parents=parents.csv
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 100 100" role="img" aria-label="Synalog">
2
+ <title>Synalog</title>
3
+ <!-- A Hilbert curve of order 3: one line made of smaller copies of itself. -->
4
+ <defs>
5
+ <linearGradient id="synalog" gradientUnits="userSpaceOnUse" x1="14.86" y1="14.86" x2="85.14" y2="85.14">
6
+ <stop offset="0" stop-color="#f0e3fb"/>
7
+ <stop offset="1" stop-color="#a271d6"/>
8
+ </linearGradient>
9
+ </defs>
10
+ <path d="M14.86 14.86 L14.86 24.90 L24.90 24.90 L24.90 14.86 L34.94 14.86 L44.98 14.86 L44.98 24.90 L34.94 24.90 L34.94 34.94 L44.98 34.94 L44.98 44.98 L34.94 44.98 L24.90 44.98 L24.90 34.94 L14.86 34.94 L14.86 44.98 L14.86 55.02 L24.90 55.02 L24.90 65.06 L14.86 65.06 L14.86 75.10 L14.86 85.14 L24.90 85.14 L24.90 75.10 L34.94 75.10 L34.94 85.14 L44.98 85.14 L44.98 75.10 L44.98 65.06 L34.94 65.06 L34.94 55.02 L44.98 55.02 L55.02 55.02 L65.06 55.02 L65.06 65.06 L55.02 65.06 L55.02 75.10 L55.02 85.14 L65.06 85.14 L65.06 75.10 L75.10 75.10 L75.10 85.14 L85.14 85.14 L85.14 75.10 L85.14 65.06 L75.10 65.06 L75.10 55.02 L85.14 55.02 L85.14 44.98 L85.14 34.94 L75.10 34.94 L75.10 44.98 L65.06 44.98 L55.02 44.98 L55.02 34.94 L65.06 34.94 L65.06 24.90 L55.02 24.90 L55.02 14.86 L65.06 14.86 L75.10 14.86 L75.10 24.90 L85.14 24.90 L85.14 14.86" fill="none" stroke="url(#synalog)" stroke-width="5.71" stroke-linecap="round" stroke-linejoin="round"/>
11
+ </svg>
@@ -0,0 +1,11 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 100 100" role="img" aria-label="Synalog">
2
+ <title>Synalog</title>
3
+ <!-- A Hilbert curve of order 3: one line made of smaller copies of itself. -->
4
+ <defs>
5
+ <linearGradient id="synalog" gradientUnits="userSpaceOnUse" x1="14.86" y1="14.86" x2="85.14" y2="85.14">
6
+ <stop offset="0" stop-color="#4e2275"/>
7
+ <stop offset="1" stop-color="#b07ddb"/>
8
+ </linearGradient>
9
+ </defs>
10
+ <path d="M14.86 14.86 L14.86 24.90 L24.90 24.90 L24.90 14.86 L34.94 14.86 L44.98 14.86 L44.98 24.90 L34.94 24.90 L34.94 34.94 L44.98 34.94 L44.98 44.98 L34.94 44.98 L24.90 44.98 L24.90 34.94 L14.86 34.94 L14.86 44.98 L14.86 55.02 L24.90 55.02 L24.90 65.06 L14.86 65.06 L14.86 75.10 L14.86 85.14 L24.90 85.14 L24.90 75.10 L34.94 75.10 L34.94 85.14 L44.98 85.14 L44.98 75.10 L44.98 65.06 L34.94 65.06 L34.94 55.02 L44.98 55.02 L55.02 55.02 L65.06 55.02 L65.06 65.06 L55.02 65.06 L55.02 75.10 L55.02 85.14 L65.06 85.14 L65.06 75.10 L75.10 75.10 L75.10 85.14 L85.14 85.14 L85.14 75.10 L85.14 65.06 L75.10 65.06 L75.10 55.02 L85.14 55.02 L85.14 44.98 L85.14 34.94 L75.10 34.94 L75.10 44.98 L65.06 44.98 L55.02 44.98 L55.02 34.94 L65.06 34.94 L65.06 24.90 L55.02 24.90 L55.02 14.86 L65.06 14.86 L75.10 14.86 L75.10 24.90 L85.14 24.90 L85.14 14.86" fill="none" stroke="url(#synalog)" stroke-width="5.71" stroke-linecap="round" stroke-linejoin="round"/>
11
+ </svg>