synalog 2.1.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.
- {synalog-2.1.0 → synalog-2.2.0}/Cargo.lock +1 -1
- {synalog-2.1.0 → synalog-2.2.0}/Cargo.toml +1 -1
- {synalog-2.1.0 → synalog-2.2.0}/PKG-INFO +29 -29
- {synalog-2.1.0 → synalog-2.2.0}/README.md +28 -28
- {synalog-2.1.0 → synalog-2.2.0}/docs/assertions.md +5 -4
- synalog-2.2.0/docs/assets/synalog-dark.svg +11 -0
- synalog-2.2.0/docs/assets/synalog-light.svg +11 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/cli.md +7 -7
- {synalog-2.1.0 → synalog-2.2.0}/docs/differences-datalog.md +8 -8
- {synalog-2.1.0 → synalog-2.2.0}/docs/differences.md +2 -2
- {synalog-2.1.0 → synalog-2.2.0}/docs/engines.md +1 -1
- synalog-2.1.0/docs/examples/shop/concepts/Customer.l → synalog-2.2.0/docs/examples/shop/concepts/customer.l +1 -1
- synalog-2.1.0/docs/examples/shop/concepts/OrderStatus.l → synalog-2.2.0/docs/examples/shop/concepts/order_status.l +1 -1
- synalog-2.1.0/docs/examples/shop/rules/CustomerRevenue.l → synalog-2.2.0/docs/examples/shop/rules/customer_revenue.l +2 -2
- synalog-2.1.0/docs/examples/shop/rules/OrdersByStatus.l → synalog-2.2.0/docs/examples/shop/rules/orders_by_status.l +2 -2
- synalog-2.1.0/docs/examples/shop/rules/TopCustomers.l → synalog-2.2.0/docs/examples/shop/rules/top_customers.l +1 -1
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/shop.log +6 -6
- synalog-2.1.0/docs/examples/smoke/concepts/Device.l → synalog-2.2.0/docs/examples/smoke/concepts/device.l +1 -1
- synalog-2.1.0/docs/examples/smoke/concepts/TestStatus.l → synalog-2.2.0/docs/examples/smoke/concepts/test_status.l +1 -1
- synalog-2.1.0/docs/examples/smoke/rules/FailuresByDevice.l → synalog-2.2.0/docs/examples/smoke/rules/failures_by_device.l +2 -2
- synalog-2.1.0/docs/examples/smoke/rules/RunsPerDay.l → synalog-2.2.0/docs/examples/smoke/rules/runs_per_day.l +1 -1
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/smoke.log +5 -5
- {synalog-2.1.0 → synalog-2.2.0}/docs/getting-started.md +15 -15
- {synalog-2.1.0 → synalog-2.2.0}/docs/javascripts/playground.js +2 -2
- synalog-2.2.0/docs/javascripts/synalog-highlight.js +302 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/knowledge-graphs.md +24 -24
- {synalog-2.1.0 → synalog-2.2.0}/docs/language/aggregation.md +7 -7
- {synalog-2.1.0 → synalog-2.2.0}/docs/language/directives.md +8 -8
- {synalog-2.1.0 → synalog-2.2.0}/docs/language/functions.md +5 -5
- {synalog-2.1.0 → synalog-2.2.0}/docs/language/functors.md +4 -4
- {synalog-2.1.0 → synalog-2.2.0}/docs/language/index.md +31 -31
- {synalog-2.1.0 → synalog-2.2.0}/docs/language/recursion.md +8 -6
- {synalog-2.1.0 → synalog-2.2.0}/docs/language/syntax.md +15 -15
- {synalog-2.1.0 → synalog-2.2.0}/docs/language/temporal.md +11 -11
- {synalog-2.1.0 → synalog-2.2.0}/docs/python-api.md +35 -4
- {synalog-2.1.0 → synalog-2.2.0}/docs/stylesheets/extra.css +49 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/verification.md +5 -4
- synalog-2.2.0/img/synalog-dark.svg +11 -0
- synalog-2.2.0/img/synalog-light.svg +11 -0
- {synalog-2.1.0 → synalog-2.2.0}/pyproject.toml +1 -1
- {synalog-2.1.0 → synalog-2.2.0}/python/synalog/__init__.py +4 -0
- {synalog-2.1.0 → synalog-2.2.0}/python/synalog/cli.py +2 -2
- {synalog-2.1.0 → synalog-2.2.0}/python/synalog/introspect.py +23 -8
- {synalog-2.1.0 → synalog-2.2.0}/python/synalog/runners.py +12 -0
- {synalog-2.1.0 → synalog-2.2.0}/skills/synalog/SKILL.md +34 -33
- synalog-2.1.0/skills/synalog/examples/company/concepts/Department.l → synalog-2.2.0/skills/synalog/examples/company/concepts/department.l +1 -1
- synalog-2.1.0/skills/synalog/examples/company/concepts/Person.l → synalog-2.2.0/skills/synalog/examples/company/concepts/person.l +1 -1
- synalog-2.1.0/skills/synalog/examples/company/concepts/WorksIn.l → synalog-2.2.0/skills/synalog/examples/company/concepts/works_in.l +3 -3
- synalog-2.1.0/skills/synalog/examples/company/rules/TeamMate.l → synalog-2.2.0/skills/synalog/examples/company/rules/team_mate.l +3 -3
- synalog-2.1.0/skills/synalog/examples/org/concepts/AllManagers.l → synalog-2.2.0/skills/synalog/examples/org/concepts/all_managers.l +1 -1
- synalog-2.1.0/skills/synalog/examples/sales/concepts/Region.l → synalog-2.2.0/skills/synalog/examples/sales/concepts/region.l +1 -1
- synalog-2.1.0/skills/synalog/examples/sales/rules/MonthlySales.l → synalog-2.2.0/skills/synalog/examples/sales/rules/monthly_sales.l +1 -1
- synalog-2.1.0/skills/synalog/examples/sales/rules/TopRegion.l → synalog-2.2.0/skills/synalog/examples/sales/rules/top_region.l +1 -1
- synalog-2.1.0/skills/synalog/examples/sales/rules/TotalByRegion.l → synalog-2.2.0/skills/synalog/examples/sales/rules/total_by_region.l +2 -2
- {synalog-2.1.0 → synalog-2.2.0}/src/assertion/mod.rs +1 -1
- {synalog-2.1.0 → synalog-2.2.0}/src/assertion/parse.rs +10 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/assertion/translate.rs +22 -18
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/dialects.rs +8 -4
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/expr_translate_test.rs +12 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/functors.rs +231 -2
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/functors_test.rs +12 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/universe.rs +144 -8
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/universe_test.rs +138 -12
- {synalog-2.1.0 → synalog-2.2.0}/src/errors.rs +7 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/python.rs +95 -12
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/assertions.rs +5 -0
- synalog-2.2.0/src/verifier/dependencies.rs +63 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/directives.rs +3 -2
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/functors.rs +1 -1
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/mod.rs +32 -1
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/undefined.rs +120 -4
- {synalog-2.1.0 → synalog-2.2.0}/src/wasm.rs +13 -2
- {synalog-2.1.0 → synalog-2.2.0}/uv.lock +1 -1
- {synalog-2.1.0 → synalog-2.2.0}/zensical.toml +4 -2
- synalog-2.1.0/docs/assets/synalog-dark.svg +0 -11
- synalog-2.1.0/docs/assets/synalog-light.svg +0 -11
- synalog-2.1.0/docs/examples/axioms.l +0 -122
- synalog-2.1.0/docs/examples/axioms.log +0 -12968
- synalog-2.1.0/docs/examples/axioms.ttl +0 -68
- synalog-2.1.0/img/synalog-dark.svg +0 -11
- synalog-2.1.0/img/synalog-light.svg +0 -11
- {synalog-2.1.0 → synalog-2.2.0}/.gitignore +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/LICENSE +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/NOTICE +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/assets/synalog_favicon.png +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/benchmark/results.json +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/benchmark/speedup_by_engine.png +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/benchmark/speedup_distribution.png +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/benchmark/summary.md +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/benchmark/summary.png +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/benchmark/time_comparison.png +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/benchmark/verify_by_engine.png +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/benchmark.md +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/development.md +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/aggregation.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/aggregation.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/directives.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/directives.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/functions.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/functions.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/functors.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/functors.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/getting_started.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/getting_started.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/knowledge_graphs.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/knowledge_graphs.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/recursion.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/recursion.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/run.py +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/shop/data/orders.csv +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/shop/layer.toml +0 -0
- /synalog-2.1.0/docs/examples/shop/tables/Orders.l → /synalog-2.2.0/docs/examples/shop/tables/orders.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/smoke/data/smoke_tests.csv +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/smoke/layer.toml +0 -0
- /synalog-2.1.0/docs/examples/smoke/tables/SmokeTests.l → /synalog-2.2.0/docs/examples/smoke/tables/smoke_tests.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/syntax.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/syntax.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/temporal.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/temporal.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/temporal_graph.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/temporal_graph.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/verification.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/examples/verification.log +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/index.md +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/javascripts/summarize.js +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/playground/vendor/codemirror.js +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/playground.md +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/docs/why.md +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/python/synalog/__main__.py +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/python/synalog/checking.py +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/python/synalog/config.py +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/python/synalog/execution.py +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/python/synalog/project.py +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/skills/synalog/examples/company/data/departments.csv +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/skills/synalog/examples/company/data/employees.csv +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/skills/synalog/examples/company/layer.toml +0 -0
- /synalog-2.1.0/skills/synalog/examples/company/tables/Departments.l → /synalog-2.2.0/skills/synalog/examples/company/tables/departments.l +0 -0
- /synalog-2.1.0/skills/synalog/examples/company/tables/Employees.l → /synalog-2.2.0/skills/synalog/examples/company/tables/employees.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/skills/synalog/examples/org/data/employees.csv +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/skills/synalog/examples/org/layer.toml +0 -0
- /synalog-2.1.0/skills/synalog/examples/org/tables/Employees.l → /synalog-2.2.0/skills/synalog/examples/org/tables/employees.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/skills/synalog/examples/sales/data/sales.csv +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/skills/synalog/examples/sales/layer.toml +0 -0
- /synalog-2.1.0/skills/synalog/examples/sales/tables/Sales.l → /synalog-2.2.0/skills/synalog/examples/sales/tables/sales.l +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/assertion/report.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/annotations.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/annotations_test.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/concertina.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/dialects_test.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/expr_translate.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/mod.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/program.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/program_test.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/rule_translate.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/rule_translate_test.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/type_inference/builder.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/type_inference/built_in.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/type_inference/edge.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/type_inference/expression.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/type_inference/graph.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/type_inference/inference.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/type_inference/intersection.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/type_inference/mod.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/compiler/type_inference/types.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/lib.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/json.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/json_test.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/mod.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/names.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/parse.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/parse_test.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/rewrite.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/rewrite_test.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/span.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/span_test.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/traverse.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/parser/traverse_test.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/arity.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/contradiction.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/front_matter.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/orderby.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/positional.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/recursion.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/reserved.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/safety.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/sqlexpr.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/stratification.rs +0 -0
- {synalog-2.1.0 → synalog-2.2.0}/src/verifier/vars.rs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: synalog
|
|
3
|
-
Version: 2.
|
|
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
|
|
@@ -289,18 +289,18 @@ A Synalog program is a **project**: a folder with a `layer.toml`, holding one pr
|
|
|
289
289
|
|
|
290
290
|
```
|
|
291
291
|
shop/
|
|
292
|
-
├── tables/
|
|
293
|
-
├── concepts/
|
|
294
|
-
├── rules/
|
|
295
|
-
├── rules/
|
|
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
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/
|
|
301
|
+
`tables/orders.l`
|
|
302
302
|
|
|
303
|
-
```
|
|
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/
|
|
313
|
+
`concepts/customer.l`
|
|
314
314
|
|
|
315
|
-
```
|
|
315
|
+
```synalog
|
|
316
316
|
---
|
|
317
317
|
name: Customer
|
|
318
318
|
description: Every customer who placed at least one order.
|
|
319
319
|
---
|
|
320
|
-
import tables.
|
|
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/
|
|
326
|
+
`rules/customer_revenue.l`
|
|
327
327
|
|
|
328
|
-
```
|
|
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.
|
|
334
|
-
import tables.
|
|
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/
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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:),
|
|
@@ -247,18 +247,18 @@ A Synalog program is a **project**: a folder with a `layer.toml`, holding one pr
|
|
|
247
247
|
|
|
248
248
|
```
|
|
249
249
|
shop/
|
|
250
|
-
├── tables/
|
|
251
|
-
├── concepts/
|
|
252
|
-
├── rules/
|
|
253
|
-
├── rules/
|
|
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
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/
|
|
259
|
+
`tables/orders.l`
|
|
260
260
|
|
|
261
|
-
```
|
|
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/
|
|
271
|
+
`concepts/customer.l`
|
|
272
272
|
|
|
273
|
-
```
|
|
273
|
+
```synalog
|
|
274
274
|
---
|
|
275
275
|
name: Customer
|
|
276
276
|
description: Every customer who placed at least one order.
|
|
277
277
|
---
|
|
278
|
-
import tables.
|
|
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/
|
|
284
|
+
`rules/customer_revenue.l`
|
|
285
285
|
|
|
286
|
-
```
|
|
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.
|
|
292
|
-
import tables.
|
|
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/
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
-
```
|
|
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
|
|
|
@@ -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>
|
|
@@ -99,16 +99,16 @@ duckdb and the PostgreSQL driver ship with synalog. For the other engines (`bigq
|
|
|
99
99
|
|
|
100
100
|
### Imports
|
|
101
101
|
|
|
102
|
-
In a project, `import <folder>.<
|
|
102
|
+
In a project, `import <folder>.<name>.<Name>;` statements resolve from the project's folder (the one holding `layer.toml`), whichever file runs and wherever from: `import tables.orders.Orders;` reads the project's `tables/orders.l`. The program file's directory and the current directory come next. Pass `--import-root DIR` (repeatable) to search elsewhere; explicit roots replace the defaults.
|
|
103
103
|
|
|
104
|
-
In the [`shop` project](language/index.md), `rules/
|
|
104
|
+
In the [`shop` project](language/index.md), `rules/top_customers.l` builds on `rules/customer_revenue.l`:
|
|
105
105
|
|
|
106
|
-
```
|
|
107
|
-
--8<-- "docs/examples/shop/rules/
|
|
106
|
+
```synalog
|
|
107
|
+
--8<-- "docs/examples/shop/rules/top_customers.l"
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
```console
|
|
111
|
-
$ synalog rules/
|
|
111
|
+
$ synalog rules/top_customers.l run TopCustomers --load orders=data/orders.csv
|
|
112
112
|
+-------------+-------+
|
|
113
113
|
| customer_id | total |
|
|
114
114
|
+-------------+-------+
|
|
@@ -118,7 +118,7 @@ $ synalog rules/TopCustomers.l run TopCustomers --load orders=data/orders.csv
|
|
|
118
118
|
2 rows
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
-
`import rules.
|
|
121
|
+
`import rules.customer_revenue.CustomerRevenue as Revenue;` imports the same predicate under another name. Directives attached to an imported predicate (its `@OrderBy` here) travel with it.
|
|
122
122
|
|
|
123
123
|
### Errors
|
|
124
124
|
|
|
@@ -198,7 +198,7 @@ $ synalog introspect
|
|
|
198
198
|
Wrote 2 table file(s) to /path/to/sales/tables
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
-
```
|
|
201
|
+
```synalog
|
|
202
202
|
---
|
|
203
203
|
name: PublicOrders
|
|
204
204
|
description: Orders.
|
|
@@ -19,7 +19,7 @@ Synalog belongs to the [Datalog](https://en.wikipedia.org/wiki/Datalog) family:
|
|
|
19
19
|
|
|
20
20
|
Datalog identifies arguments by position: `edge(X, Y)` means whatever the first and second columns happen to be. Synalog uses [named arguments](language/syntax.md#named-arguments) exclusively:
|
|
21
21
|
|
|
22
|
-
```
|
|
22
|
+
```synalog
|
|
23
23
|
# Datalog: positional, not supported
|
|
24
24
|
# edge(X, Y) :- node(X), node(Y), link(X, Y).
|
|
25
25
|
|
|
@@ -39,7 +39,7 @@ The trade is deliberate: you give up an incremental in-memory engine and gain th
|
|
|
39
39
|
|
|
40
40
|
A Datalog relation is a *set*: duplicates cannot exist. Synalog inherits SQL's *multiset* semantics: a rule body that matches a row twice produces it twice, and the union operator `|` is a `UNION ALL`. Deduplication is opt-in with the `distinct` keyword:
|
|
41
41
|
|
|
42
|
-
```
|
|
42
|
+
```synalog
|
|
43
43
|
# Set semantics, as in Datalog: duplicates removed
|
|
44
44
|
Customer(customer_id:) distinct :- Orders(customer_id:);
|
|
45
45
|
```
|
|
@@ -50,7 +50,7 @@ This is why the documentation marks concepts and aggregating rules `distinct` th
|
|
|
50
50
|
|
|
51
51
|
Pure Datalog restricts terms to constants and variables, with no function symbols, which is exactly what guarantees its termination. Synalog drops that restriction: rule bodies can compute with [arithmetic, string and comparison operators](language/syntax.md#operators), [conditionals](language/syntax.md#conditionals), [records](language/syntax.md#records), arrays, [built-in functions](language/functions.md) and [user-defined functions](language/functions.md#user-defined-functions):
|
|
52
52
|
|
|
53
|
-
```
|
|
53
|
+
```synalog
|
|
54
54
|
OrderSize(order_id:, size:) :-
|
|
55
55
|
Orders(order_id:, amount:),
|
|
56
56
|
size == (if amount > 1000 then "large" else "small");
|
|
@@ -60,7 +60,7 @@ OrderSize(order_id:, size:) :-
|
|
|
60
60
|
|
|
61
61
|
Core Datalog has no aggregation; systems that add it bolt it on with varying syntax and semantics. In Synalog [aggregation](language/aggregation.md) is part of the rule head: `+=`, `Min=`, `Max=`, `Avg=`, `List=`, `Set=`, `ArgMax=` and more:
|
|
62
62
|
|
|
63
|
-
```
|
|
63
|
+
```synalog
|
|
64
64
|
@OrderBy(CustomerSpend, "total", "DESC");
|
|
65
65
|
CustomerSpend(customer_id:, total? += amount) distinct :- Orders(customer_id:, amount:);
|
|
66
66
|
```
|
|
@@ -71,7 +71,7 @@ The non-aggregated head columns act as the grouping key, like SQL's `GROUP BY`.
|
|
|
71
71
|
|
|
72
72
|
Datalog recursion always terminates because the Herbrand universe is finite: no function symbols means no new values can ever be created. Synalog's expressions break that guarantee (a recursive rule can compute `cost + hop` forever), so [recursion](language/recursion.md) requires an explicit iteration bound via the [`@Recursive` directive](language/directives.md#recursive):
|
|
73
73
|
|
|
74
|
-
```
|
|
74
|
+
```synalog
|
|
75
75
|
@Recursive(AllManagers, 20);
|
|
76
76
|
AllManagers(employee_id:, manager_id:) :- Employees(employee_id:, manager_id:);
|
|
77
77
|
AllManagers(employee_id:, manager_id:) :-
|
|
@@ -85,7 +85,7 @@ The limit bounds the number of hops, so even cyclic graphs terminate. For a clos
|
|
|
85
85
|
|
|
86
86
|
Datalog answers are unordered sets, with no notion of "the first ten results". Synalog adds [`@OrderBy` and `@Limit`](language/directives.md) so results are deterministic and pageable, which is what lets an agent with a limited context window walk a large result set page by page:
|
|
87
87
|
|
|
88
|
-
```
|
|
88
|
+
```synalog
|
|
89
89
|
@OrderBy(TopCustomers, "total DESC");
|
|
90
90
|
@Limit(TopCustomers, 10);
|
|
91
91
|
TopCustomers(customer_id:, total:) :- CustomerSpend(customer_id:, total:);
|
|
@@ -97,7 +97,7 @@ This is also why `@OrderBy` is mandatory on every concept and rule: without it,
|
|
|
97
97
|
|
|
98
98
|
Datalog predicates are first-order: a rule cannot take another predicate as a parameter. Synalog's [functors](language/functors.md) allow exactly that, instantiating a generic rule with different predicates:
|
|
99
99
|
|
|
100
|
-
```
|
|
100
|
+
```synalog
|
|
101
101
|
EnterpriseRevenue := SegmentRevenue(Segment: EnterpriseCustomer);
|
|
102
102
|
SMBRevenue := SegmentRevenue(Segment: SMBCustomer);
|
|
103
103
|
```
|
|
@@ -112,7 +112,7 @@ The classic Datalog safety conditions, range restriction (every head variable bo
|
|
|
112
112
|
|
|
113
113
|
Datalog has no way to state, next to a predicate, what it should satisfy; some engines accept integrity constraints, written as Datalog rules that must derive nothing. New in Synalog 2.0, and found in neither Datalog nor Logica, `@Assert` states a property in first-order logic, with quantifiers, implication and sums, in a notation distinct from the rules:
|
|
114
114
|
|
|
115
|
-
```
|
|
115
|
+
```synalog
|
|
116
116
|
@Assert(Ancestor,
|
|
117
117
|
transitive: "∀ x y z, Ancestor x y → Ancestor y z → Ancestor x z",
|
|
118
118
|
irreflexive: "∀ x, ¬ Ancestor x x");
|
|
@@ -6,7 +6,7 @@ Synalog is a fork of [Logica](https://logica.dev/) with a fast Rust core. It kee
|
|
|
6
6
|
|
|
7
7
|
Synalog doesn't support positional attributes like Logica or Datalog: it only uses **named attributes**, which reduce agent mistakes. This feature is optional in Logica; Synalog makes it mandatory.
|
|
8
8
|
|
|
9
|
-
```
|
|
9
|
+
```synalog
|
|
10
10
|
# Synalog: always named
|
|
11
11
|
Employee(name:, salary:)
|
|
12
12
|
|
|
@@ -30,7 +30,7 @@ Synalog embeds a [formal verifier](verification.md) that catches structural erro
|
|
|
30
30
|
|
|
31
31
|
New in Synalog 2.0, and not in Logica: `@Assert` states what a predicate must satisfy, in first-order logic written as [Lean](https://lean-lang.org/) propositions, and Synalog checks it against the data by searching for counterexamples. `synalog program.l verify` reports the assertions that are violated, and `run` refuses a program that violates one.
|
|
32
32
|
|
|
33
|
-
```
|
|
33
|
+
```synalog
|
|
34
34
|
@Assert(Ancestor, transitive: "∀ x y z, Ancestor x y → Ancestor y z → Ancestor x z");
|
|
35
35
|
```
|
|
36
36
|
|
|
@@ -3,7 +3,7 @@ name: Customer
|
|
|
3
3
|
description: Every customer who placed at least one order.
|
|
4
4
|
keywords: [customer, buyer, client]
|
|
5
5
|
---
|
|
6
|
-
import tables.
|
|
6
|
+
import tables.orders.Orders;
|
|
7
7
|
|
|
8
8
|
@OrderBy(Customer, "customer_id");
|
|
9
9
|
Customer(customer_id:) distinct :- Orders(customer_id:);
|
|
@@ -3,8 +3,8 @@ name: CustomerRevenue
|
|
|
3
3
|
description: Total amount ordered by each customer, all statuses included.
|
|
4
4
|
keywords: [revenue, spend, sales]
|
|
5
5
|
---
|
|
6
|
-
import concepts.
|
|
7
|
-
import tables.
|
|
6
|
+
import concepts.customer.Customer;
|
|
7
|
+
import tables.orders.Orders;
|
|
8
8
|
|
|
9
9
|
@OrderBy(CustomerRevenue, "customer_id");
|
|
10
10
|
CustomerRevenue(customer_id:, total? += amount) distinct :-
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
name: OrdersByStatus
|
|
3
3
|
description: Number of orders per status.
|
|
4
4
|
---
|
|
5
|
-
import concepts.
|
|
6
|
-
import tables.
|
|
5
|
+
import concepts.order_status.OrderStatus;
|
|
6
|
+
import tables.orders.Orders;
|
|
7
7
|
|
|
8
8
|
@OrderBy(OrdersByStatus, "status");
|
|
9
9
|
OrdersByStatus(status:, count? += 1) distinct :- OrderStatus(status:), Orders(status:);
|