diff --git a/cli/describe.go b/cli/describe.go index 9ddb8a90..c1c62277 100644 --- a/cli/describe.go +++ b/cli/describe.go @@ -68,7 +68,7 @@ for name, def in pairs(tags) do end table.sort(result, function(a, b) return a.name < b.name end) -local page = space.readPage("Library/Std/Docs/LIQ Reference") +local page = space.readPage("Library/Std/Docs/SLIQ Reference") local parsed = index.extractFrontmatter(page, {removeFrontMatterSection = true}) return { tags = result, syntax = parsed.text } diff --git a/cli/integration_test.go b/cli/integration_test.go index 54c94400..32cdddea 100644 --- a/cli/integration_test.go +++ b/cli/integration_test.go @@ -229,8 +229,8 @@ func TestIntegration_Describe_Text(t *testing.T) { assert.Contains(t, out, "from = index.tag") } -func TestIntegration_LIQ_Reference_Page(t *testing.T) { - out := runCLI(t, "--text", "script", `return space.readPage("Library/Std/Docs/LIQ Reference")`) +func TestIntegration_SLIQ_Reference_Page(t *testing.T) { + out := runCLI(t, "--text", "script", `return space.readPage("Library/Std/Docs/SLIQ Reference")`) assert.Contains(t, out, "from = index.tag") assert.Contains(t, out, "table.select") assert.Contains(t, out, "array_agg") diff --git a/cli/lua.go b/cli/lua.go index 6442b912..1dd09895 100644 --- a/cli/lua.go +++ b/cli/lua.go @@ -124,9 +124,9 @@ func LuaScriptCommand() *cobra.Command { func QueryCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "query ", + Use: "query ", Short: "Run a query (wraps in query[[...]])", - Long: "Evaluate a Lua Integrated Query. The argument is the query body.\nExample: silverbullet-cli query 'from t = index.tag \"task\" where not t.done'", + Long: "Evaluate a Space Lua Integrated Query. The argument is the query body.\nExample: silverbullet-cli query 'from t = index.tag \"task\" where not t.done'", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { conn, err := connFromFlags(cmd) diff --git a/client/markdown_renderer/result_render.ts b/client/markdown_renderer/result_render.ts index b755cecb..fc47a800 100644 --- a/client/markdown_renderer/result_render.ts +++ b/client/markdown_renderer/result_render.ts @@ -3,7 +3,7 @@ import { LuaTable, luaToString, } from "../space_lua/runtime.ts"; -import { isSqlNull } from "../space_lua/liq_null.ts"; +import { isSqlNull } from "../space_lua/sliq_null.ts"; export function defaultTransformer(v: any, _k: string): Promise { if (v === undefined || v === null || isSqlNull(v)) { diff --git a/client/space_lua/aggregates.ts b/client/space_lua/aggregates.ts index 6862ec51..154acb7e 100644 --- a/client/space_lua/aggregates.ts +++ b/client/space_lua/aggregates.ts @@ -1,5 +1,5 @@ /** - * Aggregate function definitions and execution for LIQ. + * Aggregate function definitions and execution for SLIQ. * * Builtins implement ILuaFunction via plain objects rather than * LuaBuiltinFunction instances. This avoids ES module TDZ issues: @@ -16,7 +16,7 @@ import { luaValueToJS, type LuaValue, } from "./runtime.ts"; -import { isSqlNull } from "./liq_null.ts"; +import { isSqlNull } from "./sliq_null.ts"; import type { LuaExpression, LuaOrderBy } from "./ast.ts"; import { buildItemEnv } from "./query_env.ts"; import { asyncMergeSort } from "./util.ts"; diff --git a/client/space_lua/query_collection.ts b/client/space_lua/query_collection.ts index cbf9f62d..ff4fa12c 100644 --- a/client/space_lua/query_collection.ts +++ b/client/space_lua/query_collection.ts @@ -26,7 +26,7 @@ import { type LuaValue, singleResult, } from "./runtime.ts"; -import { isSqlNull, LIQ_NULL } from "./liq_null.ts"; +import { isSqlNull, SLIQ_NULL } from "./sliq_null.ts"; import { evalExpression, luaOp } from "./eval.ts"; import { asyncMergeSort } from "./util.ts"; import type { DataStore } from "../data/datastore.ts"; @@ -295,7 +295,7 @@ function containsAggregate(expr: LuaExpression, config?: Config): boolean { // Wrap a value for select result tables so that the column key survives // in the `LuaTable` function selectVal(v: LuaValue): LuaValue { - return v === null || v === undefined ? LIQ_NULL : v; + return v === null || v === undefined ? SLIQ_NULL : v; } /** @@ -491,7 +491,7 @@ function normalizeSelectResults(results: any[]): any[] { const rebuilt = new LuaTable(); for (const k of canonicalKeys) { const v = item.rawGet(k); - void rebuilt.rawSet(k, v === undefined || v === null ? LIQ_NULL : v); + void rebuilt.rawSet(k, v === undefined || v === null ? SLIQ_NULL : v); } for (const k of luaKeys(item)) { if (typeof k !== "string") { @@ -675,7 +675,7 @@ async function evalSelectExpression( for (const k of luaKeys(result)) { const v = result.rawGet(k); if (v === null || v === undefined) { - void result.rawSet(k, LIQ_NULL); + void result.rawSet(k, SLIQ_NULL); } } return result; diff --git a/client/space_lua/render_lua_markdown.test.ts b/client/space_lua/render_lua_markdown.test.ts index 9cd5adb5..35bb5ed4 100644 --- a/client/space_lua/render_lua_markdown.test.ts +++ b/client/space_lua/render_lua_markdown.test.ts @@ -2,7 +2,7 @@ import { expect, test } from "vitest"; import { parse } from "../markdown_parser/parse_tree.ts"; import { extendedMarkdownLanguage } from "../markdown_parser/parser.ts"; import { renderMarkdownToHtml } from "../markdown_renderer/markdown_render.ts"; -import { LIQ_NULL } from "./liq_null.ts"; +import { SLIQ_NULL } from "./sliq_null.ts"; import { makeLuaFloat } from "./numeric.ts"; import { renderResultToCleanMarkdown, @@ -29,8 +29,8 @@ test("undefined renders as empty markdown with dataType nil", async () => { expect(r).toEqual({ markdown: "", dataType: "nil" }); }); -test("LIQ_NULL (SQL NULL) renders as empty markdown with dataType nil", async () => { - const r = renderResultToMarkdown(LIQ_NULL); +test("SLIQ_NULL (SQL NULL) renders as empty markdown with dataType nil", async () => { + const r = renderResultToMarkdown(SLIQ_NULL); expect(r).toEqual({ markdown: "", dataType: "nil" }); }); @@ -188,10 +188,10 @@ test("LuaTable with mixed keys uses keys order in header", async () => { // ── Null / empty values inside cells ──────────────────────────────── -test("LIQ_NULL value in a table cell renders as empty td", async () => { +test("SLIQ_NULL value in a table cell renders as empty td", async () => { const row = new LuaTable(); await row.rawSet("a", 1); - await row.rawSet("b", LIQ_NULL); + await row.rawSet("b", SLIQ_NULL); const tbl = new LuaTable(); await tbl.rawSet(1, row); @@ -201,9 +201,9 @@ test("LIQ_NULL value in a table cell renders as empty td", async () => { expect(r.markdown).toContain(""); }); -test("LIQ_NULL in a table cell renders as empty td", async () => { +test("SLIQ_NULL in a table cell renders as empty td", async () => { const row = new LuaTable(); - await row.rawSet("val", LIQ_NULL); + await row.rawSet("val", SLIQ_NULL); const tbl = new LuaTable(); await tbl.rawSet(1, row); @@ -212,10 +212,10 @@ test("LIQ_NULL in a table cell renders as empty td", async () => { expect(r.markdown).toContain(""); }); -test("LIQ_NULL item in a list renders as empty bullet", async () => { +test("SLIQ_NULL item in a list renders as empty bullet", async () => { const tbl = new LuaTable(); await tbl.rawSet(1, 1); - await tbl.rawSet(2, LIQ_NULL); + await tbl.rawSet(2, SLIQ_NULL); await tbl.rawSet(3, 3); const r = renderResultToMarkdown(tbl); @@ -499,7 +499,7 @@ test("e2e: empty table preserves data-table-empty attribute", async () => { test("e2e: data attributes survive the pipeline", async () => { const row = new LuaTable(); await row.rawSet("x", 42); - await row.rawSet("y", LIQ_NULL); + await row.rawSet("y", SLIQ_NULL); const tbl = new LuaTable(); await tbl.rawSet(1, row); @@ -678,10 +678,10 @@ test("clean: pipe inside scalar array cell is escaped", async () => { ).toBe("|vals|\n|--|\n|a\\|b
c|"); }); -test("clean: LIQ_NULL cell renders as empty", async () => { +test("clean: SLIQ_NULL cell renders as empty", async () => { const row = new LuaTable(); await row.rawSet("x", 42); - await row.rawSet("y", LIQ_NULL); + await row.rawSet("y", SLIQ_NULL); expect(await renderResultToCleanMarkdown(row)).toBe("|x|y|\n|--|--|\n|42||"); }); diff --git a/client/space_lua/render_lua_markdown.ts b/client/space_lua/render_lua_markdown.ts index ebf724fc..c1c5c232 100644 --- a/client/space_lua/render_lua_markdown.ts +++ b/client/space_lua/render_lua_markdown.ts @@ -3,7 +3,7 @@ import { escapeRegularPipes, jsonToMDTable, } from "../markdown_renderer/result_render.ts"; -import { isSqlNull } from "../space_lua/liq_null.ts"; +import { isSqlNull } from "../space_lua/sliq_null.ts"; import { isTaggedFloat } from "../space_lua/numeric.ts"; import { LuaTable, luaFormatNumber } from "../space_lua/runtime.ts"; diff --git a/client/space_lua/runtime.ts b/client/space_lua/runtime.ts index f665c620..58053832 100644 --- a/client/space_lua/runtime.ts +++ b/client/space_lua/runtime.ts @@ -3,7 +3,7 @@ import { evalStatement } from "./eval.ts"; import { asyncQuickSort } from "./util.ts"; import { isPromise, rpAll } from "./rp.ts"; import { isNegativeZero, isTaggedFloat } from "./numeric.ts"; -import { isSqlNull } from "./liq_null.ts"; +import { isSqlNull } from "./sliq_null.ts"; import { luaFormat } from "./stdlib/format.ts"; export type LuaType = diff --git a/client/space_lua/liq_null.ts b/client/space_lua/sliq_null.ts similarity index 54% rename from client/space_lua/liq_null.ts rename to client/space_lua/sliq_null.ts index a66a793b..5135079e 100644 --- a/client/space_lua/liq_null.ts +++ b/client/space_lua/sliq_null.ts @@ -1,6 +1,6 @@ // Sentinel value representing SQL NULL in query results. -export const LIQ_NULL = Symbol.for("silverbullet.sqlNull"); +export const SLIQ_NULL = Symbol.for("silverbullet.sqlNull"); export function isSqlNull(v: any): boolean { - return v === LIQ_NULL; + return v === SLIQ_NULL; } diff --git a/client/space_lua/stdlib.ts b/client/space_lua/stdlib.ts index d182c2f9..5692ac59 100644 --- a/client/space_lua/stdlib.ts +++ b/client/space_lua/stdlib.ts @@ -35,7 +35,7 @@ import { cryptoApi } from "./stdlib/crypto.ts"; import { netApi } from "./stdlib/net.ts"; import { isTaggedFloat, makeLuaFloat } from "./numeric.ts"; import { isPromise } from "./rp.ts"; -import { isSqlNull } from "./liq_null.ts"; +import { isSqlNull } from "./sliq_null.ts"; const printFunction = new LuaBuiltinFunction(async (_sf, ...args) => { console.log("[Lua]", ...(await Promise.all(args.map((v) => luaToString(v))))); diff --git a/client/space_lua/stdlib/space_lua.ts b/client/space_lua/stdlib/space_lua.ts index 6835cb33..2df7faa0 100644 --- a/client/space_lua/stdlib/space_lua.ts +++ b/client/space_lua/stdlib/space_lua.ts @@ -12,7 +12,7 @@ import { luaValueToJS, singleResult, } from "../runtime.ts"; -import { isSqlNull } from "../liq_null.ts"; +import { isSqlNull } from "../sliq_null.ts"; /** * These are Space Lua specific functions that are available to all scripts, but are not part of the standard Lua language. diff --git a/libraries/Library/Std/APIs/Aggregate.md b/libraries/Library/Std/APIs/Aggregate.md index 10ea9ff3..bd6c1368 100644 --- a/libraries/Library/Std/APIs/Aggregate.md +++ b/libraries/Library/Std/APIs/Aggregate.md @@ -1,9 +1,9 @@ --- -description: APIs to define custom aggregate functions for LIQ +description: APIs to define custom aggregate functions for SLIQ tags: meta/api --- -APIs to define and override aggregate functions used in LIQ `select` and `having` clauses after `group by`. +APIs to define and override aggregate functions used in SLIQ `select` and `having` clauses after `group by`. All aggregates skip null/nil values by convention. Empty groups return null (except `count` which returns 0 and `string_agg` which returns an empty string). diff --git a/libraries/Library/Std/Docs/LIQ Reference.md b/libraries/Library/Std/Docs/SLIQ Reference.md similarity index 95% rename from libraries/Library/Std/Docs/LIQ Reference.md rename to libraries/Library/Std/Docs/SLIQ Reference.md index fca1efa9..0c095a57 100644 --- a/libraries/Library/Std/Docs/LIQ Reference.md +++ b/libraries/Library/Std/Docs/SLIQ Reference.md @@ -1,7 +1,7 @@ --- tags: meta --- -Lua Integrated Query (LIQ) — query syntax for SilverBullet data. +Space Lua Integrated Query (SLIQ) — query syntax for SilverBullet data. Syntax: from = index.tag "" diff --git a/website/API/index.md b/website/API/index.md index 777b4f68..af2a7579 100644 --- a/website/API/index.md +++ b/website/API/index.md @@ -4,7 +4,7 @@ The `index` API provides functions for interacting with SilverBullet's [[Object| ## Object Operations ## index.tag(name) -Returns a given [[Object#Tags]] as a query collection, to be queried using [[Space Lua/Lua Integrated Query]]. +Returns a given [[Object#Tags]] as a query collection, to be queried using [[Space Lua/Integrated Query]]. Example: ${query[[from index.tag("page") limit 1]]} diff --git a/website/Architecture.md b/website/Architecture.md index 8f2118d0..8a35702d 100644 --- a/website/Architecture.md +++ b/website/Architecture.md @@ -73,7 +73,7 @@ Syscalls (system calls) are the abstraction used in SilverBullet to cleanly crea ## Datastore The DataStore is an (IndexedDB-backed) key-value store that lives in the browser. It serves as the local persistence layer for everything the client needs to keep track of: -* **Object index**: All indexed objects (pages, tasks, items, links, tags, etc.) are stored here, enabling fast local queries via [[Space Lua/Lua Integrated Query]]. +* **Object index**: All indexed objects (pages, tasks, items, links, tags, etc.) are stored here, enabling fast local queries via [[Space Lua/Integrated Query]]. * **File cache**: The [[Sync]] engine stores a local copy of all space files, enabling offline access. * **Configuration**: Runtime configuration and state. * **Message queue**: An internal message queue (MQ) used for asynchronous operations. diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index cf1d069e..8cc16b0d 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -13,7 +13,8 @@ Whenever a commit is pushed to the `main` branch, within ~5 minutes, it will be * Rebindable built-in keyboard shortcuts: almost all built-in keyboard shortcuts are now proper SilverBullet commands and can be rebound. * [[Plugs/Development]] (now with new docs!) gains an optional `build:` section in manifests, running `esbuild`, `sass`, or `copy` transforms before asset bundling — enables plugs to ship bundled TSX/SCSS UIs. * Action buttons: new `command` attribute for `actionButton.define`. -* [[Runtime API|CLI]] iteration: renamed `lua` → `eval` and `luascript` → `script`, and added a new `describe` command that describes LIQ and lists tags with defined schemas. +* [[Runtime API|CLI]] iteration: renamed `lua` → `eval` and `luascript` → `script`, and added a new `describe` command that describes SLIQ and lists tags with defined schemas. +* Rebrand: “Lua Integrated Query” (LIQ) is now called [[Space Lua/Integrated Query|Space Lua Integrated Query]] (_SLIQ!_) (as coined by Matouš Jan Fialka) * HTTP: content type is now also exposed via the `X-Content-Type` header. * Docker: removed `VOLUME` declaration from the Dockerfile (it gave a false sense of persistence `/space` must be explicitly mounted, as documented). This also fixed the silverbullet-website repo. * Fix: [[Sync]] now falls through to local data on browser-native network errors instead of returning 503; previously synced spaces serve locally immediately after a service worker restart. @@ -41,7 +42,7 @@ Whenever a commit is pushed to the `main` branch, within ~5 minutes, it will be * [[Space Lua]] enhancements: * Performance: Lua interpreter hot-path optimizations, tree traversal and page index optimizations. * Performance: `LuaTable` internals tuned for faster Lua execution. -* [[Space Lua/Lua Integrated Query]] improvements (courtesy of [Matouš Jan Fialka](https://github.com/mjf)): +* [[Space Lua/Integrated Query]] improvements (courtesy of [Matouš Jan Fialka](https://github.com/mjf)): * [Unified field list syntax](https://github.com/silverbulletmd/silverbullet/pull/1909) for `from`, `select`, and `group by` clauses, enabling multi-source cross-joins * [Implicit single group](https://github.com/silverbulletmd/silverbullet/pull/1907) for aggregates without `group by` * `offset` clause support @@ -113,8 +114,8 @@ Whenever a commit is pushed to the `main` branch, within ~5 minutes, it will be * Sync snapshots are now persisted after every file sync, reducing (and hopefully eliminating) edge cases where the sync engine is killed mid-sync (for whatever reason) and the snapshot becomes of sync with “reality”. * The index status progress indicator (blue circle) should now be more reliably reflect the actual indexing status. * HTTP status codes >= 500 are now treated as offline (better offline detection). -* [[Space Lua/Lua Integrated Query]] improvements (courtesy of [Matouš Jan Fialka](https://github.com/mjf)): - * [[Space Lua/Lua Integrated Query/Grouping|group by]] and `having` clauses with [[Space Lua/Lua Integrated Query/Aggregating|aggregator]] support +* [[Space Lua/Integrated Query]] improvements (courtesy of [Matouš Jan Fialka](https://github.com/mjf)): + * [[Space Lua/Integrated Query/Grouping|group by]] and `having` clauses with [[Space Lua/Integrated Query/Aggregating|aggregator]] support * `filter(where )` clause for per-row aggregate filtering * `nulls first`/`nulls last` in `order by` * Null/missing query cells now render as empty @@ -127,7 +128,7 @@ Whenever a commit is pushed to the `main` branch, within ~5 minutes, it will be * Implement `string.pack`, `string.unpack` and `string.packsize` * Implement `math.random`, `math.randomseed`, `math.tointeger`, `math.frexp` and `math.ldexp` * Implement `table.move`; align `table.pack` and `table.unpack` with Lua semantics - * [[API/table#table.select(table, keys...)]] (non-standard in Lua) API, convenient to use in [[Space Lua/Lua Integrated Query]] `select` clauses, see example in docs. + * [[API/table#table.select(table, keys...)]] (non-standard in Lua) API, convenient to use in [[Space Lua/Integrated Query]] `select` clauses, see example in docs. * [Extend `os` module](https://github.com/silverbulletmd/silverbullet/pull/1836) * Add `_VERSION` environment variable * `tostring()` now respects `__tostring` metamethod; `#` operator now respects `__len` metamethod @@ -135,7 +136,7 @@ Whenever a commit is pushed to the `main` branch, within ~5 minutes, it will be * **Load order** of scripts is now well defined: `order by (script.priority or 0) desc, script.ref` * New _experimental_ API: [[API/tag#tag.define(spec)]], see linked page for docs and example uses. Brings back ability to define 📅 deadlines for tasks (see example). Another part of this is [[Schema]] support for [[Tag|tags]]. When a schema is defined for a tag, you get: * [[Frontmatter]] **attribute completion and linting** (in-editor error indicators) for attributes defined as part of the tag’s schema. - * [[Space Lua/Lua Integrated Query]] **attribute code completion** _if_ you use the `from v = index.tag(“bla”)` style syntax (so explicitly bind your iterator variable). + * [[Space Lua/Integrated Query]] **attribute code completion** _if_ you use the `from v = index.tag(“bla”)` style syntax (so explicitly bind your iterator variable). * Item-level linting (highlights the object in-line in case of validation errors). * Tag schema updates: * `pos` (present in link, item and some other tags) is now _deprecated_, use `range` instead @@ -190,7 +191,7 @@ Whenever a commit is pushed to the `main` branch, within ~5 minutes, it will be * `Navigate: Copy Ref To Current Position` * `Navigate: Copy Link To Current Position` * Lua: - * [LIQ fix](https://github.com/silverbulletmd/silverbullet/issues/1705) + * [SLIQ fix](https://github.com/silverbulletmd/silverbullet/issues/1705) * [Ctrl-click](https://github.com/silverbulletmd/silverbullet/pull/1713) navigate to definition on non-Mac operating systems * Support for `` in Lua (by [Matouš Jan Fialka](https://github.com/silverbulletmd/silverbullet/pull/1715)) * Production builds now include sourcemaps for easier debugging in browser DevTools. If you don't want to serve sourcemaps publicly, you can block `*.js.map` files at your reverse proxy level (see [[TLS#Blocking sourcemaps]]). diff --git a/website/Full Text Search.md b/website/Full Text Search.md index 2920c591..b987d44b 100644 --- a/website/Full Text Search.md +++ b/website/Full Text Search.md @@ -5,8 +5,8 @@ Two community search libraries are available: * **[Silversearch](https://github.com/MrMugame/silversearch)** (recommended) — a more capable search implementation * **[basic-search](https://github.com/silverbulletmd/basic-search)** — a simpler search that was previously built into SilverBullet -Install either one via the [[Library Manager]]. +Install either one via the [[Configuration Manager]]. # Tips -* For structured queries over metadata and objects, use [[Space Lua/Lua Integrated Query]] instead — it's much more powerful for querying attributes, tags, and relationships +* For structured queries over metadata and objects, use [[Space Lua/Integrated Query]] instead — it's much more powerful for querying attributes, tags, and relationships * The [[Page Picker]] (`Cmd-k` / `Ctrl-k`) also searches page _names_ and can be faster for finding pages you know the name of \ No newline at end of file diff --git a/website/Guide/Journaling.md b/website/Guide/Journaling.md index 127b6819..46960f9b 100644 --- a/website/Guide/Journaling.md +++ b/website/Guide/Journaling.md @@ -61,7 +61,7 @@ Over time, each topic page accumulates a reverse-chronological log of every jour This works for any kind of page: people, projects, concepts, books. Your journal becomes the connective tissue between all your topics. # 4. Query your journal -Use [[Space Lua/Lua Integrated Query]] to pull insights from your journal pages. For example, show recent journal entries on your [[Index Page]]: +Use [[Space Lua/Integrated Query]] to pull insights from your journal pages. For example, show recent journal entries on your [[Index Page]]: ```lua ${template.each(query[[ diff --git a/website/Guide/Knowledge Base.md b/website/Guide/Knowledge Base.md index 27fc2093..31e4150b 100644 --- a/website/Guide/Knowledge Base.md +++ b/website/Guide/Knowledge Base.md @@ -45,7 +45,7 @@ status: reading Now this page is tagged `book` and has queryable `author` and `status` attributes. You can add tags to a page either with the `#book` syntax, or via a [[Frontmatter]] attribute. # 5. Query your knowledge -[[Space Lua/Lua Integrated Query]] lets you pull live data from your pages. Create a “Currently Reading” page with a query: +[[Space Lua/Integrated Query]] lets you pull live data from your pages. Create a “Currently Reading” page with a query: ```lua ${template.each(query[[ diff --git a/website/Guide/Task Management.md b/website/Guide/Task Management.md index f31dc11a..d0269b41 100644 --- a/website/Guide/Task Management.md +++ b/website/Guide/Task Management.md @@ -58,7 +58,7 @@ Navigate to your "Website Redesign" page. At the top, the **[[Linked Tasks]]** w You can check off a linked task from either page; the state change propagates. No manual copying or moving of tasks needed. # 6. Build a dashboard -Create a "Dashboard" page that pulls everything together using [[Space Lua/Lua Integrated Query]]: +Create a "Dashboard" page that pulls everything together using [[Space Lua/Integrated Query]]: ```lua # Active projects diff --git a/website/Linked Mention.md b/website/Linked Mention.md index e6bc602f..59ce61a6 100644 --- a/website/Linked Mention.md +++ b/website/Linked Mention.md @@ -25,7 +25,7 @@ config.set("std.widgets.linkedMentions.enabled", false) ``` # Programmatic access -You can query linked mentions directly using [[Space Lua/Lua Integrated Query]]: +You can query linked mentions directly using [[Space Lua/Integrated Query]]: ```lua query[[ diff --git a/website/Manual.md b/website/Manual.md index 1bca1247..f8d3aa29 100644 --- a/website/Manual.md +++ b/website/Manual.md @@ -79,7 +79,7 @@ The main ways to roam your space, beside following page links, are: * [[Attribute]] * [[Space Lua]] * [[Space Lua/Standard Library]] - * [[Space Lua/Lua Integrated Query]] + * [[Space Lua/Integrated Query]] * [[Space Lua/DOM]] * [[Space Lua/JavaScript Interop]] * [[Template]] diff --git a/website/Migrate from v1.md b/website/Migrate from v1.md index e3b92654..58ae2f61 100644 --- a/website/Migrate from v1.md +++ b/website/Migrate from v1.md @@ -3,10 +3,10 @@ SilverBullet v2 removed a slew of features that were still present in the 0.x se Here are some pointers on what was removed and how to adapt. # Queries -v2 does not have support for old-style [queries](https://v1.silverbullet.md/Query%20Language) (live queries) anymore. They have been replaced with [[Space Lua/Lua Integrated Query]]. Give the linked page a read, but generally there’s a few differences: +v2 does not have support for old-style [queries](https://v1.silverbullet.md/Query%20Language) (live queries) anymore. They have been replaced with [[Space Lua/Integrated Query]]. Give the linked page a read, but generally there’s a few differences: -1. Lua Integrated Queries tend to start with `from index.tag "tag-name"` instead of plain `tag-name`. This is a bit longer, but since whatever comes after `from` is a Lua expression, you can not just query [[Object]], you can query any Lua table as well in the same way. -2. In the old query language, you access attribute simply by their name, this works in LIQ too, but stylistically it’s nicer to use either `_.attribute`, or to give the object you’re iterating over a name, using `from page = index.tag "page"`, for instance. +1. Space Lua Integrated Queries tend to start with `from index.tag "tag-name"` instead of plain `tag-name`. This is a bit longer, but since whatever comes after `from` is a Lua expression, you can not just query [[Object]], you can query any Lua table as well in the same way. +2. In the old query language, you access attribute simply by their name, this works in SLIQ too, but stylistically it’s nicer to use either `_.attribute`, or to give the object you’re iterating over a name, using `from page = index.tag "page"`, for instance. 3. The `=` equals operator is `==` in Lua 😄 # Templates diff --git a/website/Object Index.md b/website/Object Index.md index 8eaf758d..c92ffe57 100644 --- a/website/Object Index.md +++ b/website/Object Index.md @@ -12,7 +12,7 @@ You interact with it in a few ways: # Indexing ## Initial indexing process -When you launch a fresh client for the first time, the object index will be built from scratch. Depending on the size of your space this can take anything between a few seconds and minutes. If the process takes longer than a few seconds, you will see progress with a blue status circle. Until this initial indexing process finishes, you will notice that things like [[API/widget|Widgets]] and [[Space Lua/Lua Integrated Query]] are not yet rendered, this is to avoid errors and invalid data. +When you launch a fresh client for the first time, the object index will be built from scratch. Depending on the size of your space this can take anything between a few seconds and minutes. If the process takes longer than a few seconds, you will see progress with a blue status circle. Until this initial indexing process finishes, you will notice that things like [[API/widget|Widgets]] and [[Space Lua/Integrated Query]] are not yet rendered, this is to avoid errors and invalid data. After the initial index process, the index will be kept up-to-date incrementally. @@ -37,7 +37,7 @@ The indexObject API looks at the `tags` of the found objects, and for each tag: * Stores the resulting objects with for the given tag # Query -The Object Index is generally queried using [[Space Lua/Lua Integrated Query]]. +The Object Index is generally queried using [[Space Lua/Integrated Query]]. Entry points are: diff --git a/website/Object.md b/website/Object.md index af8a77d3..142bb7ca 100644 --- a/website/Object.md +++ b/website/Object.md @@ -5,7 +5,7 @@ tags: glossary SilverBullet automatically maintains an [[Object Index]] extracted from all [[Markdown]] [[Page|pages]] in your [[Space|Space]]. -Objects are a feature that powers a lot of SilverBullet functionality, including the [[Page Picker]], [[Linked Mention|Linked Mentions]] and many others. They can also be queried by the user directly, typically via [[Space Lua/Lua Integrated Query]]. +Objects are a feature that powers a lot of SilverBullet functionality, including the [[Page Picker]], [[Linked Mention|Linked Mentions]] and many others. They can also be queried by the user directly, typically via [[Space Lua/Integrated Query]]. # Terminology * [[Object]]: represent _things_ in your space at various level of granularity. Examples include [[Object/page]] at the highest level, but also more granular things like [[Object/task]] and [[Object/link]]. In relational database parlance, you can think of Objects as **database rows**. diff --git a/website/Quick Start.md b/website/Quick Start.md index 0260b36f..23af457c 100644 --- a/website/Quick Start.md +++ b/website/Quick Start.md @@ -42,7 +42,7 @@ tags: project These attributes become queryable, which we’re going to do next. # 5. Run your first queries -Pages in SilverBullet can use [[Space Lua]] and [[Space Lua/Lua Integrated Query]] feature specifically to dynamically generate content. Add this to any page: +Pages in SilverBullet can use [[Space Lua]] and [[Space Lua/Integrated Query]] feature specifically to dynamically generate content. Add this to any page: ```lua ${query[[from tags.page limit 5]]} diff --git a/website/SilverBullet.md b/website/SilverBullet.md index a6f78005..919a3835 100644 --- a/website/SilverBullet.md +++ b/website/SilverBullet.md @@ -7,7 +7,7 @@ Let’s get more specific. In SilverBullet you keep your content as a collection of [[Markdown]] [[Page|Pages]] (called a [[Space]]). You navigate your space using the [[Page Picker]] like a traditional notes app, or through [[Link|Links]] like a wiki (except they are [[Linked Mention|bi-directional]]). -If you are the **writer** type, you’ll appreciate SilverBullet as a clean [[Markdown]] editor with [[Live Preview]]. If you have more of an **outliner** personality, SilverBullet has [[Outlines|Outlining]] tools for you. Productivity freak? Have a look at [[Task|Tasks]]. More of a **database** person? You will appreciate [[Object|Objects]] and [[Space Lua/Lua Integrated Query|Queries]]. +If you are the **writer** type, you’ll appreciate SilverBullet as a clean [[Markdown]] editor with [[Live Preview]]. If you have more of an **outliner** personality, SilverBullet has [[Outlines|Outlining]] tools for you. Productivity freak? Have a look at [[Task|Tasks]]. More of a **database** person? You will appreciate [[Object|Objects]] and [[Space Lua/Integrated Query|Queries]]. And if you are comfortable **programming** a little bit — now we’re really talking. You will love _dynamically generating content_ with [[Space Lua]] (SilverBullet’s [[Lua]] dialect), or to use it to create custom [[Command|Commands]], [[Page Template|Page Templates]] or [[API/widget|Widgets]]. @@ -16,7 +16,7 @@ Dynamically generating content, _programmable notes_... why would you want that, Let’s say you have documented a set of product features in individual pages that you’ve [[Tag|tagged]] with a #feature tag, and annotated with a few custom [[Frontmatter]] [[Attribute|Attributes]]. -With a simple [[Space Lua/Lua Integrated Query|Query]] and [[Template]], you can now dynamically build a product feature list, ordered by _awesomeness_ (`Alt-click` or hover and click the edit button to see the underlying code): +With a simple [[Space Lua/Integrated Query|Query]] and [[Template]], you can now dynamically build a product feature list, ordered by _awesomeness_ (`Alt-click` or hover and click the edit button to see the underlying code): ${query[[ from f = tags.feature diff --git a/website/Slash Command.md b/website/Slash Command.md index 17c4b196..377c5b0f 100644 --- a/website/Slash Command.md +++ b/website/Slash Command.md @@ -21,7 +21,7 @@ Slash commands are quick ways to perform repetitive tasks. You trigger them by t # Slash templates Most slash commands are implemented as [[Slash Templates]] — pages tagged with `#meta/template/slash` whose content is inserted at the cursor. The standard library includes slash templates for: -* `/query` — insert a LIQ query block +* `/query` — insert a SLIQ query block * `/lua-query` — insert a Lua query expression * `/code` — insert a fenced code block * `/table` — insert a Markdown table diff --git a/website/Space Lua.md b/website/Space Lua.md index bb81badc..1609a0d7 100644 --- a/website/Space Lua.md +++ b/website/Space Lua.md @@ -42,7 +42,7 @@ local myCodeHere Scripts are loaded in _reverse priority_ order. When you set no priority (the default) your scripts will be run last. -The order used is determined by this [[Space Lua/Lua Integrated Query|query]] (also part of your [[^Library/Std/Pages/Space Overview]]) page: +The order used is determined by this [[Space Lua/Integrated Query|query]] (also part of your [[^Library/Std/Pages/Space Overview]]) page: query[[ from t = index.tag "space-lua" @@ -65,7 +65,7 @@ One SilverBullet specific [[Markdown]] [[Markdown/Extensions]] is the `${lua exp For example: 10 + 2 = ${adder(10, 2)} (Alt-click, or select to see the expression) is using the just defined `adder` function. -This mechanism is often used in conjunction with [[Space Lua/Lua Integrated Query]] and [[API/widget|Widgets]]. +This mechanism is often used in conjunction with [[Space Lua/Integrated Query]] and [[API/widget|Widgets]]. # API ![[API]] @@ -76,5 +76,5 @@ While the aim is to be 95% (let’s say) compatible with regular Lua, there are In addition to quirks, Space introduces a (minimal) set of new features on top core Lua: -1. [[Space Lua/Lua Integrated Query]], embedding a query language into Lua itself +1. [[Space Lua/Integrated Query]], embedding a query language into Lua itself 2. [[Space Lua/Thread Locals]] \ No newline at end of file diff --git a/website/Space Lua/Integrated Query.md b/website/Space Lua/Integrated Query.md new file mode 100644 index 00000000..0e088ef0 --- /dev/null +++ b/website/Space Lua/Integrated Query.md @@ -0,0 +1,318 @@ +--- +description: A Lua-embedded query syntax for selecting and transforming objects. +tags: glossary +--- +Space Lua Integrated Query (SLIQ) is a SilverBullet specific Lua extension. It adds a convenient query syntax to the language in a backwards compatible way. It does so by overloading Lua’s default function call + single argument syntax when using `query` as the function call. As a result, Lua programs using SLIQ are still syntactically valid Lua. + +The syntax for SLIQ is `query[[my query]]`. In regular Lua `[[my query]]` is just another way of writing `"my query"` (it is an alternative string syntax). Function calls that only take a string argument can omit parentheses, therefore `query[[my query]]` is equivalent to `query("my query")`. + +However, in [[Space Lua]] it is interpreted as an SQL- and [LINQ](https://learn.microsoft.com/en-us/dotnet/csharp/linq/)-inspired integrated query language. + +General syntax: + +```postgres +query [[ + from <> + [ where <> ] + [ group by <> [, ...] ] + [ having <> ] + [ order by <> [, ...] ] + [ limit <> [, <>] ] + [ offset <> ] + [ select <> ] +]] +``` + +Unlike in SQL where clauses must be written in a particular order, SLIQ clauses can be written in any order, and the only mandatory clause is the `from` clause. + +The `order by` clause uses `<>`: + +```postgres +<> + [ asc | desc | using <> ] + [ nulls { first | last } ] +``` + +If an aggregate function call is used for projection (`select`), the general syntax is: + +```postgres +<>( <> [, ...] + [ order by <> [, ...] ] + [ filter (where <>) ] +) +``` + +See [[Space Lua/Integrated Query/Aggregating]] for details. + +Aggregate functions support an optional intra-aggregate `order by` and/or `filter` clause: + + ( [order by [asc|desc] [nulls {first|last}], ...]) + ( ...) filter(where ) + +These can be combined. See [[Space Lua/Integrated Query/Aggregating]] for details. + +SLIQ operates on any Lua collection. + +For instance, to sort a list of numbers in descending order: +${query[[from n = {1, 2, 3} order by n desc]]} + +However, in most cases you’ll use it in conjunction with [[API/index#index.tag(name)]]. Here’s an example querying the 3 pages that were last modified: + +${query[[ + from p = index.tag "page" + order by p.lastModified desc + select p.name + limit 3 +]]} + +Note that the query returns a regular Lua table, so it can be part of a bigger expression: + +${some(query[[ + from p = index.tag "page" + limit 0 +]]) or "Matched no pages"} + +# Clauses +Here are the clauses that are currently supported: + +## `from` +The `from` clause specifies the source of your data. There are two syntactic variants: + +**Recommended:** With explicit variable binding: + + from v = <> + +binding each item to the variable `v`. + +However, there is also the more concise: + + from <> + +implicitly binding each item to the variable `_` as well as making all attributes directly available as variables. The latter, while shorter, is less performant and will block future optimizations, so the variable-binding variant is preferred. + +> **warning** Warning +> When you use a `from` clause without explicit variable binding (so without the `v in` syntax), note that any attribute of the object you’re iterating over will shadow global variables. For instance, if you have an object with a `table` attribute, regular `table` APIs will become inaccessible within the query. +> +> **Recommendation:** Use the explicit variable binding syntax + +Example without variable binding: +${query[[from {1, 2, 3} select _]]} + +With variable binding: +${query[[from n = {1, 2, 3} select n]]} + +A more realistic example using `index.tag`: +${query[[from p = index.tag "page" order by p.lastModified select p.name limit 3]]} + +## `where` +The `where` clause allows you to filter data. When the expression evaluated to a truthy value, the item is included in the result. + +Example: + +${query[[from n = {1, 2, 3, 4, 5} where n > 2]]} + +Or to select 5 pages tagged with `#meta`: + +${query[[from p = index.tag "page" where table.includes(p.tags, "meta") limit 5]]} + +Or select based on name (including folder) and a [[API/string|string function]]: + +${query[[from p = index.tag "page" where p.name:startsWith("Person")]]} + +## `group by` +The `group by` clause groups results by one or more key expressions. After grouping, each result row becomes a table with two fields: + +- `key` — the group key value (single value for one key, table for multi-key) +- `group` — a table (array) of all original items in that group + +The `group by` field names are also available as bare variables in `having`, `select`, and `order by`. Use `#group` to get the count of items in a group. + +Example: + +${query[[ + from p = index.tag "tag" + group by p.name + select { name = name, count = #group } + limit 5 +]]} + +See [[Space Lua/Integrated Query/Grouping]] for detailed examples. + +## `having` +The `having` clause filters groups **after** `group by`. It follows SQL semantics: only group key fields, `key`, and `group` are accessible — use `where` to filter individual rows before grouping. + +Aggregate functions like `count()`, `sum()`, `min()`, `max()`, and `avg()` can be used in `having` expressions. See [[Space Lua/Integrated Query/Aggregating]] for details. + +Example: + +${query[[ + from p = index.tag "tag" + group by p.name + having #group > 2 + select { name = name, count = #group } + order by count desc + limit 5 +]]} + +See [[Space Lua/Integrated Query/Grouping]] for detailed examples. + +## `order by` +The `order by` clause sorts results by one or more expressions. By default, sorting is ascending. Append `desc` for descending order, or `asc` to be explicit about ascending. + +As an example, the last 3 modified pages: + +${query[[ + from p = index.tag "page" + order by p.lastModified desc + select p.name + limit 3 +]]} + +You can sort by multiple expressions separated by commas. Each key is evaluated left to right — the second key only matters when the first compares as equal: + +${query[[ + from p = index.tag "page" + order by p.lastModified desc, p.name + select p.name + limit 3 +]]} + +Each sort key can have its own direction: + +```lua +query[[ + from p = data + order by p.category asc, p.priority desc + select { name = p.name } +]] +``` + +### Null placement +By default, `nil` values follow SQL conventions: they appear **last** for ascending order and **first** for descending order. You can override this per key with `nulls first` or `nulls last`: + +${query[[ + from p = index.tag "page" + order by p.priority desc nulls last + select { name = p.name, priority = p.priority } + limit 3 +]]} + +### String collation +Sorting of strings can be adjusted with `queryCollation` in [[^Library/Std/Config]]. + +### `using` (custom comparators) +The `using` clause specifies a custom comparator function instead of the default `asc`/`desc` ordering. The two are mutually exclusive — `using` defines both the comparison logic and the direction. + +The comparator must accept two arguments and return `true` when the first should come strictly before the second. It can be a named function: + +```lua +function byLength(a, b) + return #a < #b +end +``` + +```lua +query [[ + from p = index.tag "page" + order by p.name using byLength + select p.name + limit 5 +]] +``` + +Or an anonymous function inline. Example: + +${query[[ + from n = {5, 1, 3, 2, 4} + order by n using function(a, b) return a < b end +]]} + +The `nulls` clause works with `using`, and each sort key can independently choose `asc`/`desc` or `using`: + +```lua +query [[ + from p = data + order by + p.category using customCategoryCmp, + p.priority desc nulls last + select { + name = p.name + } +]] +``` + +When `using` is specified, it overrides any `queryCollation` configuration for that sort key. + +> **note** Note +> `using` is a reserved keyword in Space Lua and cannot be used as a variable name. + +#### Strict weak ordering +A comparator must satisfy **strict weak ordering** (SWO) — if comparing A with B returns `true`, then comparing B with A must return `false`. In practice this means using strict comparisons like `<` or `>` and **never** `<=` or `>=`. + +The query engine validates this at runtime. If comparing two values in both directions both return `true`, the query fails with a clear error: + +${query [[ + from n = {5, 1, 3, 2, 3} + order by n using function(a, b) return a <= b end +]]} + +The query engine uses a *stable merge sort* algorithm with guaranteed performance. Items that compare as equal preserve their original order and an invalid comparator cannot cause an infinite loop or crash — the violation is detected and reported as an error. + +## `limit` +The `limit` clause allows you to limit the number of results, optionally with an inline offset. + +Example: + +${query[[from {1, 2, 3, 4, 5} limit 3]]} + +You can also specify an offset as a second argument to skip some results: + +${query[[from {1, 2, 3, 4, 5} limit 3, 2]]} + +> **note** Note +> If both an inline offset (`limit 3, 2`) and a standalone `offset` clause are present, the last one encountered wins. + +## `offset` +The `offset` clause skips a number of rows from the beginning of the result set. It can be used with or without `limit`. + +Skip the first 2 results: + +${query[[from {1, 2, 3, 4, 5} offset 2]]} + +Combined with `limit`: + +${query[[from {1, 2, 3, 4, 5} offset 2 limit 3]]} + +If the offset is larger than the number of available rows, the result is empty — this is not an error, matching PostgreSQL semantics. + +> **note** Note +> If both a standalone `offset` clause and an inline offset in `limit` (`limit 3, 2`) are present, the last one encountered wins. + +## `select` +The `select` clause allows you to transform each item in the result set. If omitted, it defaults to returning the item itself. + +When used with `group by`, aggregate functions like `sum()`, `count()`, `min()`, `max()`, `avg()`, and `array_agg()` can be used in the `select` expression to compute values across each group. Aggregates also support intra-aggregate `order by` to control the order in which values are processed, and `filter(where ...)` to restrict which rows contribute. See [[Space Lua/Integrated Query/Aggregating]] for details. + +Some examples: + +Double each number: +${query[[from n = {1, 2, 3} select n * 2]]} + +It is convenient to combine it with the [[API/table#table.select(table, keys...)]] API: +${query[[ + from p = index.tag "page" + select table.select(p, "name", "lastModified") + limit 3 +]]} + +# Rendering the output +To render the output as a template, you can rely on the fact that queries return Lua tables. For example, to apply a template to render every page as a link: + +${query[[ + from p = index.tag "page" + order by p.lastModified desc + limit 3 + select templates.pageItem(p) +]]} + +To render pages as links with their full local URL, use `templates.fullPageItem`. For more information on available templates, see [[^Library/Std/Infrastructure/Query Templates]]. diff --git a/website/Space Lua/Integrated Query/Aggregating.md b/website/Space Lua/Integrated Query/Aggregating.md new file mode 100644 index 00000000..db8821c2 --- /dev/null +++ b/website/Space Lua/Integrated Query/Aggregating.md @@ -0,0 +1,241 @@ +#maturity/experimental + +The `group by` and `having` clauses of [[Space Lua/Integrated Query]] support aggregate functions for grouped analysis, following SQL-style semantics. + +After `group by`, each result row contains: + +- `key`: the group key (a single value or, for multi-key grouping, a table) +- `group`: a Lua table containing all items in that group + +All aggregate functions (such as `count`, `sum`, `min`, `max`, `avg`, and custom aggregates) can be applied in `select` and `having` clauses. Aggregate expressions are available in both forms: with or without a variable binding in the `from` clause. The variable `_` always refers to the current item. + +Field names used in `group by` are exposed as locals in `having`, `select`, and `order by`. Use `#group` to obtain the item count per group. + +> **note** The `having` clause acts only on grouped output. For filtering individual items, use `where` prior to grouping. + +> **warning** Inside a grouped query bare function names that match registered aggregates are treated as aggregates! To call a global function with the same name, use qualified access (e.g., `_G.sum(x)`). + +# Aggregates without `group by` +When an aggregate function appears in `select` or `having` but no `group by` clause is present, the entire result set is treated as a single implicit group. The `key` variable is `nil` in this case. + +This is useful for computing a single summary value over a collection: + +${query [[ + from p = index.tag "page" + select { total = count(p.name), biggest = max(p.size) } +]]} + +A simple sum over a list: + +${query [[ + from n = {10, 20, 30} + select sum(n) +]]} + +The `having` clause also works without `group by` — it filters the single implicit group: + +${query [[ + from n = {1, 2, 3} + having sum(n) > 5 + select sum(n) +]]} + +> **note** Note +> Without `group by`, the query always returns at most one row. If `having` rejects the implicit group, the result is empty. + +# Available aggregates + +All registered aggregate functions — built-in, user-defined, and aliases — can be listed via `index.aggregates()`: + +${query[[ + select + { + Name = '`' .. name .. '`', + Description = description, + Kind = + (builtin and 'builtin' or 'custom') .. + (target and ' alias for ' .. '`' .. target .. '`' or ''), + } + from + index.aggregates() + order by + builtin desc, + name +]]} + +See [[Library/Std/APIs/Aggregate|Aggregate API]] for how to define custom aggregates and aliases. + +# Examples +All example queries operate on `tags.page`, but will work with any query collection. As always, to see the underlying query, hover over the result table and click the _Edit_ button to see the underlying query. + +## Counting with and without binding +Grouping pages by their first tag, and computing the count and aggregate statistics: + +**Without binding variable** +${query [[ + from + tags.page + group by + tags[1] + select { + tag = key, + total = count(name), + min_size = min(size), + max_size = max(size), + avg_size = avg(size) + } + order by total desc +]]} + +**With binding variable** +${query [[ + from + p = tags.page + group by + p.tags[1] + select { + tag = key, + total = count(p.name), + min_size = min(p.size), + max_size = max(p.size), + avg_size = avg(p.size) + } + order by total desc +]]} + +## Multi-key grouping and aggregate +${query[[ + from + p = tags.page + group by + p.tags[1], + p.tags[2] + select { + first = key[1], + second = key[2], + count = count(p.name) + } +]]} + +## Group filtering with `having` and aggregates +Only groups with more than two items and at least one tag set: +${query[[ + from + p = tags.page + group by + p.tags[1] + having + count(p.name) > 2 and key + select { + tag = key, + total = count(p.name) + } +]]} + +## Per-aggregate filtering with `filter(where ...)` +Individual aggregate expressions can include a `filter(where )` clause to restrict which rows contribute to that specific aggregate. + +Unlike `where` (which filters rows before grouping) and `having` (which filters entire groups after aggregation), `filter(where ...)` applies per-aggregate, per-row within each group. Multiple aggregates in the same `select` can each have different filters. + +${query [[ + from + p = index.tag 'page' + group by + p.tags[1] + select { + tag = key, + total = count(p.name), + big = count(p.name) filter(where p.size > 10), + big_sz = sum(p.size) filter(where p.size > 10) + } + order by + tag +]]} + +The filter clause works with all aggregate functions: `count`, `sum`, `min`, `max`, `avg`, `array_agg`, and custom aggregates. When no rows match the filter condition, aggregates return their empty-group value: `0` for `count`, `nil` for `sum`, `min`, `max`, and `avg`, and an empty table `{}` for `array_agg`. + +## Intra-aggregate `order by` +Aggregate functions can include an `order by` clause **inside** the function call to control the order in which values are processed. + +For commutative aggregates like `sum`, `count`, `min`, `max`, and `avg`, the intra-aggregate `order by` has no effect on the result because the value is the same regardless of iteration order. It is only meaningful for order-dependent aggregates like `array_agg`. + +Ordered-set aggregates such as `quantile`, `percentile_cont`, and `percentile_disc` require an intra-aggregate `order by` clause to produce correct results, as they depend on the iteration order of input values. Without `order by`, results are undefined. + +### Basic example + +Collect page names sorted alphabetically within each group: + +${query [[ + from + p = index.tag 'page' + group by + p.tags[1] + select { + tag = key, + names_asc = array_agg(p.name order by p.name asc), + names_desc = array_agg(p.name order by p.name desc) + } + order by + tag + limit + 5 +]]} + +### Combined with `filter(where ...)` +The `order by` and `filter` clauses can be used together. The filter is applied first (excluding rows), then the remaining rows are sorted before iteration: + +${query [[ + from + p = index.tag 'page' + group by + p.tags[1] + select { + tag = key, + big_by_size = array_agg(p.name order by p.size desc) filter(where p.size > 5) + } + order by + tag + limit + 5 +]]} + +### Null handling +The `nulls first` and `nulls last` modifiers work inside intra-aggregate `order by` the same way they do in the query-level `order by`: + +```lua +query [[ + from + p = data + group + by p.category + select { + cat = key, + items = array_agg(p.name + order by + p.priority asc nulls last + ) + } +]] +``` + +## Field access after grouping +Non-aggregated field references, such as `name` in `select`, refer to the first item in the group, matching common SQL and MySQL semantics. + +${query [[ + from + p = tags.page + group by + p.tags[1] + select { + tag = key, + first_page = p.name, + n = count(p.name) + } +]]} + +## Custom aggregators +Custom aggregator functions may be defined by the user using [[Library/Std/APIs/Aggregate|dedicated API]]. + +# See also +* [[Space Lua/Integrated Query/Grouping]] — grouping queries without aggregation +* [[Space Lua/Integrated Query]] — full SLIQ language reference and listing available aggregates diff --git a/website/Space Lua/Integrated Query/Grouping.md b/website/Space Lua/Integrated Query/Grouping.md new file mode 100644 index 00000000..39c72aca --- /dev/null +++ b/website/Space Lua/Integrated Query/Grouping.md @@ -0,0 +1,286 @@ +#maturity/experimental + +The `group by` and `having` clauses extend [[Space Lua/Integrated Query]] with SQL-style grouping and aggregate filtering. + +After `group by`, each result row has two fields: + +- **`key`** - the group key (single value or table for multi-key) +- **`group`** - a table (array) of all items in that group + +The field names used in `group by` are also available as bare variables in `having`, `select`, and `order by`. Use `#group` to count items per group. + +> **note** Note +> `having` can only reference group key fields, `key`, `group`, aggregate expressions like `#group`, and aggregate functions like `count()`. To filter individual rows, use `where`. + +# Examples + +All examples below use `tags.tag`. + +## Group by single key +Group all tags by `name`: + +${query [[ + from + t = tags.tag + group by + t.name + limit 5 +]]} + +## Group by multiple keys +Group tags by `name` and `parent` together: + +${query[[ + from + t = tags.tag + group by + t.name, + t.parent + limit 5 +]]} + +## Filter groups by count +Only show tags that appear more than 2 times: + +${query[[ + from + t = tags.tag + group by + t.name + having + #group > 2 + limit 5 +]]} + +## Find unique tags +Tags appearing exactly once: + +${query[[ + from + t = tags.tag + group by + t.name + having + #group == 1 + select key +]]} + +## Filter groups by key value +Only show the group where `name` is "meta": + +${query[[ + from + tags.tag + group by + name + having + name == "meta" +]]} + +${query[[ + from + t = tags.tag + group by + t.name + having + t.name == "meta" +]]} + +## Multi-key having + +Groups by `name` and `parent`, keep only page-level tags with more than 1 entry: + +${query [[ + from + tags.tag + group by + name, + parent + having + parent == 'page' and + #group > 1 +]]} + +## `where` before `group by` + +Filter to page parents first, then group by `name`: + +${query [[ + from + tags.tag + where + parent == 'page' + group by + name +]]} + +## `where`, `group by` and `having` combined + +Filter to page parents, group by `name`, keep groups with 2+ items: +${query [[ + from + index.tag 'tag' + where + parent == 'page' + group by + name + having + #group >= 2 +]]} + +## `select` name and count + +Project each group into a table with `name` and `count`: +${query [[ + from + index.tag 'tag' + group by + name + select { + name = name, + count = #group + } +]]} + +## `select` with multi-key + +Project both key parts and count: + +${query [[ + from + index.tag 'tag' + group by + name, + parent + select { + name = name, + parent = parent, + count = #group + } +]]} + +## Full pipeline: `where`, `group by`, `having` and `select` + +Filter, group, filter groups, then project: + +${query [[ + from + index.tag 'tag' + where + parent == 'page' or + parent == 'task' + group by + name + having + #group > 1 + select { + tag = name, + total = #group + } +]]} + +## Order groups by count + +Sort groups by size, largest first: + +${query [[ + from + index.tag 'tag' + group by + name + order by + #group desc +]]} + +## Top tags with `having`, `order by`, and `select` + +Tags with 2+ occurrences, sorted by count, projected: +${query [[ + from + index.tag 'tag' + group by + name + having + #group >= 2 + order by + #group desc + select { + tag = name, + count = #group + } +]]} + +## Top N groups with `limit` +${query [[ + from + index.tag 'tag' + group by + name + order by + #group desc + limit + 3 +]]} + +## Full pipeline with `limit` +Top 5 tags with 2+ uses, showing name and count: +${query [[ + from + p = index.tag 'tag' + group by + p.name + having + #group > 1 + select { + tag = name, + count = #group + } +]]} + +## Multi-key with explicit object variable +Full pipeline with `p =` binding and two group keys: + +${query [[ + from + p = index.tag 'tag' + where + p.parent == 'page' + group by + p.name, + p.parent + having + #group >= 2 + order by + #group desc + select { + tag = name, + parent = parent, + count = #group + } +]]} + +## Access `key` directly + +For single-key grouping, `key` holds the value directly: +${query [[ + from + index.tag 'tag' + group by + name + having + key == 'meta' +]]} + +## Access `key` table for multi-key + +For multi-key grouping, `key` is a table indexed from 1: +${query [[ + from + index.tag 'tag' + group by + name, + parent + having + key[1] == 'meta' and + key[2] == 'page' +]]} diff --git a/website/Space Lua/Lua Integrated Query.md b/website/Space Lua/Lua Integrated Query.md index d208eda5..ef708b83 100644 --- a/website/Space Lua/Lua Integrated Query.md +++ b/website/Space Lua/Lua Integrated Query.md @@ -1,318 +1 @@ ---- -description: A Lua-embedded query syntax for selecting and transforming objects. -tags: glossary ---- -Lua Integrated Query (LIQ) is a SilverBullet specific Lua extension. It adds a convenient query syntax to the language in a backwards compatible way. It does so by overloading Lua’s default function call + single argument syntax when using `query` as the function call. As a result, Lua programs using LIQ are still syntactically valid Lua. - -The syntax for LIQ is `query[[my query]]`. In regular Lua `[[my query]]` is just another way of writing `"my query"` (it is an alternative string syntax). Function calls that only take a string argument can omit parentheses, therefore `query[[my query]]` is equivalent to `query("my query")`. - -However, in [[Space Lua]] it is interpreted as an SQL- and [LINQ](https://learn.microsoft.com/en-us/dotnet/csharp/linq/)-inspired integrated query language. - -General syntax: - -```postgres -query [[ - from <> - [ where <> ] - [ group by <> [, ...] ] - [ having <> ] - [ order by <> [, ...] ] - [ limit <> [, <>] ] - [ offset <> ] - [ select <> ] -]] -``` - -Unlike in SQL where clauses must be written in a particular order, LIQ clauses can be written in any order, and the only mandatory clause is the `from` clause. - -The `order by` clause uses `<>`: - -```postgres -<> - [ asc | desc | using <> ] - [ nulls { first | last } ] -``` - -If an aggregate function call is used for projection (`select`), the general syntax is: - -```postgres -<>( <> [, ...] - [ order by <> [, ...] ] - [ filter (where <>) ] -) -``` - -See [[Space Lua/Lua Integrated Query/Aggregating]] for details. - -Aggregate functions support an optional intra-aggregate `order by` and/or `filter` clause: - - ( [order by [asc|desc] [nulls {first|last}], ...]) - ( ...) filter(where ) - -These can be combined. See [[Space Lua/Lua Integrated Query/Aggregating]] for details. - -LIQ operates on any Lua collection. - -For instance, to sort a list of numbers in descending order: -${query[[from n = {1, 2, 3} order by n desc]]} - -However, in most cases you’ll use it in conjunction with [[API/index#index.tag(name)]]. Here’s an example querying the 3 pages that were last modified: - -${query[[ - from p = index.tag "page" - order by p.lastModified desc - select p.name - limit 3 -]]} - -Note that the query returns a regular Lua table, so it can be part of a bigger expression: - -${some(query[[ - from p = index.tag "page" - limit 0 -]]) or "Matched no pages"} - -# Clauses -Here are the clauses that are currently supported: - -## `from` -The `from` clause specifies the source of your data. There are two syntactic variants: - -**Recommended:** With explicit variable binding: - - from v = <> - -binding each item to the variable `v`. - -However, there is also the more concise: - - from <> - -implicitly binding each item to the variable `_` as well as making all attributes directly available as variables. The latter, while shorter, is less performant and will block future optimizations, so the variable-binding variant is preferred. - -> **warning** Warning -> When you use a `from` clause without explicit variable binding (so without the `v in` syntax), note that any attribute of the object you’re iterating over will shadow global variables. For instance, if you have an object with a `table` attribute, regular `table` APIs will become inaccessible within the query. -> -> **Recommendation:** Use the explicit variable binding syntax - -Example without variable binding: -${query[[from {1, 2, 3} select _]]} - -With variable binding: -${query[[from n = {1, 2, 3} select n]]} - -A more realistic example using `index.tag`: -${query[[from p = index.tag "page" order by p.lastModified select p.name limit 3]]} - -## `where` -The `where` clause allows you to filter data. When the expression evaluated to a truthy value, the item is included in the result. - -Example: - -${query[[from n = {1, 2, 3, 4, 5} where n > 2]]} - -Or to select 5 pages tagged with `#meta`: - -${query[[from p = index.tag "page" where table.includes(p.tags, "meta") limit 5]]} - -Or select based on name (including folder) and a [[API/string|string function]]: - -${query[[from p = index.tag "page" where p.name:startsWith("Person")]]} - -## `group by` -The `group by` clause groups results by one or more key expressions. After grouping, each result row becomes a table with two fields: - -- `key` — the group key value (single value for one key, table for multi-key) -- `group` — a table (array) of all original items in that group - -The `group by` field names are also available as bare variables in `having`, `select`, and `order by`. Use `#group` to get the count of items in a group. - -Example: - -${query[[ - from p = index.tag "tag" - group by p.name - select { name = name, count = #group } - limit 5 -]]} - -See [[Space Lua/Lua Integrated Query/Grouping]] for detailed examples. - -## `having` -The `having` clause filters groups **after** `group by`. It follows SQL semantics: only group key fields, `key`, and `group` are accessible — use `where` to filter individual rows before grouping. - -Aggregate functions like `count()`, `sum()`, `min()`, `max()`, and `avg()` can be used in `having` expressions. See [[Space Lua/Lua Integrated Query/Aggregating]] for details. - -Example: - -${query[[ - from p = index.tag "tag" - group by p.name - having #group > 2 - select { name = name, count = #group } - order by count desc - limit 5 -]]} - -See [[Space Lua/Lua Integrated Query/Grouping]] for detailed examples. - -## `order by` -The `order by` clause sorts results by one or more expressions. By default, sorting is ascending. Append `desc` for descending order, or `asc` to be explicit about ascending. - -As an example, the last 3 modified pages: - -${query[[ - from p = index.tag "page" - order by p.lastModified desc - select p.name - limit 3 -]]} - -You can sort by multiple expressions separated by commas. Each key is evaluated left to right — the second key only matters when the first compares as equal: - -${query[[ - from p = index.tag "page" - order by p.lastModified desc, p.name - select p.name - limit 3 -]]} - -Each sort key can have its own direction: - -```lua -query[[ - from p = data - order by p.category asc, p.priority desc - select { name = p.name } -]] -``` - -### Null placement -By default, `nil` values follow SQL conventions: they appear **last** for ascending order and **first** for descending order. You can override this per key with `nulls first` or `nulls last`: - -${query[[ - from p = index.tag "page" - order by p.priority desc nulls last - select { name = p.name, priority = p.priority } - limit 3 -]]} - -### String collation -Sorting of strings can be adjusted with `queryCollation` in [[^Library/Std/Config]]. - -### `using` (custom comparators) -The `using` clause specifies a custom comparator function instead of the default `asc`/`desc` ordering. The two are mutually exclusive — `using` defines both the comparison logic and the direction. - -The comparator must accept two arguments and return `true` when the first should come strictly before the second. It can be a named function: - -```lua -function byLength(a, b) - return #a < #b -end -``` - -```lua -query [[ - from p = index.tag "page" - order by p.name using byLength - select p.name - limit 5 -]] -``` - -Or an anonymous function inline. Example: - -${query[[ - from n = {5, 1, 3, 2, 4} - order by n using function(a, b) return a < b end -]]} - -The `nulls` clause works with `using`, and each sort key can independently choose `asc`/`desc` or `using`: - -```lua -query [[ - from p = data - order by - p.category using customCategoryCmp, - p.priority desc nulls last - select { - name = p.name - } -]] -``` - -When `using` is specified, it overrides any `queryCollation` configuration for that sort key. - -> **note** Note -> `using` is a reserved keyword in Space Lua and cannot be used as a variable name. - -#### Strict weak ordering -A comparator must satisfy **strict weak ordering** (SWO) — if comparing A with B returns `true`, then comparing B with A must return `false`. In practice this means using strict comparisons like `<` or `>` and **never** `<=` or `>=`. - -The query engine validates this at runtime. If comparing two values in both directions both return `true`, the query fails with a clear error: - -${query [[ - from n = {5, 1, 3, 2, 3} - order by n using function(a, b) return a <= b end -]]} - -The query engine uses a *stable merge sort* algorithm with guaranteed performance. Items that compare as equal preserve their original order and an invalid comparator cannot cause an infinite loop or crash — the violation is detected and reported as an error. - -## `limit` -The `limit` clause allows you to limit the number of results, optionally with an inline offset. - -Example: - -${query[[from {1, 2, 3, 4, 5} limit 3]]} - -You can also specify an offset as a second argument to skip some results: - -${query[[from {1, 2, 3, 4, 5} limit 3, 2]]} - -> **note** Note -> If both an inline offset (`limit 3, 2`) and a standalone `offset` clause are present, the last one encountered wins. - -## `offset` -The `offset` clause skips a number of rows from the beginning of the result set. It can be used with or without `limit`. - -Skip the first 2 results: - -${query[[from {1, 2, 3, 4, 5} offset 2]]} - -Combined with `limit`: - -${query[[from {1, 2, 3, 4, 5} offset 2 limit 3]]} - -If the offset is larger than the number of available rows, the result is empty — this is not an error, matching PostgreSQL semantics. - -> **note** Note -> If both a standalone `offset` clause and an inline offset in `limit` (`limit 3, 2`) are present, the last one encountered wins. - -## `select` -The `select` clause allows you to transform each item in the result set. If omitted, it defaults to returning the item itself. - -When used with `group by`, aggregate functions like `sum()`, `count()`, `min()`, `max()`, `avg()`, and `array_agg()` can be used in the `select` expression to compute values across each group. Aggregates also support intra-aggregate `order by` to control the order in which values are processed, and `filter(where ...)` to restrict which rows contribute. See [[Space Lua/Lua Integrated Query/Aggregating]] for details. - -Some examples: - -Double each number: -${query[[from n = {1, 2, 3} select n * 2]]} - -It is convenient to combine it with the [[API/table#table.select(table, keys...)]] API: -${query[[ - from p = index.tag "page" - select table.select(p, "name", "lastModified") - limit 3 -]]} - -# Rendering the output -To render the output as a template, you can rely on the fact that queries return Lua tables. For example, to apply a template to render every page as a link: - -${query[[ - from p = index.tag "page" - order by p.lastModified desc - limit 3 - select templates.pageItem(p) -]]} - -To render pages as links with their full local URL, use `templates.fullPageItem`. For more information on available templates, see [[^Library/Std/Infrastructure/Query Templates]]. +This page has moved to [[Space Lua/Integrated Query]]. diff --git a/website/Space Lua/Lua Integrated Query/Aggregating.md b/website/Space Lua/Lua Integrated Query/Aggregating.md index b25b35d7..638d6a5a 100644 --- a/website/Space Lua/Lua Integrated Query/Aggregating.md +++ b/website/Space Lua/Lua Integrated Query/Aggregating.md @@ -1,241 +1 @@ -#maturity/experimental - -The `group by` and `having` clauses of [[Space Lua/Lua Integrated Query]] support aggregate functions for grouped analysis, following SQL-style semantics. - -After `group by`, each result row contains: - -- `key`: the group key (a single value or, for multi-key grouping, a table) -- `group`: a Lua table containing all items in that group - -All aggregate functions (such as `count`, `sum`, `min`, `max`, `avg`, and custom aggregates) can be applied in `select` and `having` clauses. Aggregate expressions are available in both forms: with or without a variable binding in the `from` clause. The variable `_` always refers to the current item. - -Field names used in `group by` are exposed as locals in `having`, `select`, and `order by`. Use `#group` to obtain the item count per group. - -> **note** The `having` clause acts only on grouped output. For filtering individual items, use `where` prior to grouping. - -> **warning** Inside a grouped query bare function names that match registered aggregates are treated as aggregates! To call a global function with the same name, use qualified access (e.g., `_G.sum(x)`). - -# Aggregates without `group by` -When an aggregate function appears in `select` or `having` but no `group by` clause is present, the entire result set is treated as a single implicit group. The `key` variable is `nil` in this case. - -This is useful for computing a single summary value over a collection: - -${query [[ - from p = index.tag "page" - select { total = count(p.name), biggest = max(p.size) } -]]} - -A simple sum over a list: - -${query [[ - from n = {10, 20, 30} - select sum(n) -]]} - -The `having` clause also works without `group by` — it filters the single implicit group: - -${query [[ - from n = {1, 2, 3} - having sum(n) > 5 - select sum(n) -]]} - -> **note** Note -> Without `group by`, the query always returns at most one row. If `having` rejects the implicit group, the result is empty. - -# Available aggregates - -All registered aggregate functions — built-in, user-defined, and aliases — can be listed via `index.aggregates()`: - -${query[[ - select - { - Name = '`' .. name .. '`', - Description = description, - Kind = - (builtin and 'builtin' or 'custom') .. - (target and ' alias for ' .. '`' .. target .. '`' or ''), - } - from - index.aggregates() - order by - builtin desc, - name -]]} - -See [[Library/Std/APIs/Aggregate|Aggregate API]] for how to define custom aggregates and aliases. - -# Examples -All example queries operate on `tags.page`, but will work with any query collection. As always, to see the underlying query, hover over the result table and click the _Edit_ button to see the underlying query. - -## Counting with and without binding -Grouping pages by their first tag, and computing the count and aggregate statistics: - -**Without binding variable** -${query [[ - from - tags.page - group by - tags[1] - select { - tag = key, - total = count(name), - min_size = min(size), - max_size = max(size), - avg_size = avg(size) - } - order by total desc -]]} - -**With binding variable** -${query [[ - from - p = tags.page - group by - p.tags[1] - select { - tag = key, - total = count(p.name), - min_size = min(p.size), - max_size = max(p.size), - avg_size = avg(p.size) - } - order by total desc -]]} - -## Multi-key grouping and aggregate -${query[[ - from - p = tags.page - group by - p.tags[1], - p.tags[2] - select { - first = key[1], - second = key[2], - count = count(p.name) - } -]]} - -## Group filtering with `having` and aggregates -Only groups with more than two items and at least one tag set: -${query[[ - from - p = tags.page - group by - p.tags[1] - having - count(p.name) > 2 and key - select { - tag = key, - total = count(p.name) - } -]]} - -## Per-aggregate filtering with `filter(where ...)` -Individual aggregate expressions can include a `filter(where )` clause to restrict which rows contribute to that specific aggregate. - -Unlike `where` (which filters rows before grouping) and `having` (which filters entire groups after aggregation), `filter(where ...)` applies per-aggregate, per-row within each group. Multiple aggregates in the same `select` can each have different filters. - -${query [[ - from - p = index.tag 'page' - group by - p.tags[1] - select { - tag = key, - total = count(p.name), - big = count(p.name) filter(where p.size > 10), - big_sz = sum(p.size) filter(where p.size > 10) - } - order by - tag -]]} - -The filter clause works with all aggregate functions: `count`, `sum`, `min`, `max`, `avg`, `array_agg`, and custom aggregates. When no rows match the filter condition, aggregates return their empty-group value: `0` for `count`, `nil` for `sum`, `min`, `max`, and `avg`, and an empty table `{}` for `array_agg`. - -## Intra-aggregate `order by` -Aggregate functions can include an `order by` clause **inside** the function call to control the order in which values are processed. - -For commutative aggregates like `sum`, `count`, `min`, `max`, and `avg`, the intra-aggregate `order by` has no effect on the result because the value is the same regardless of iteration order. It is only meaningful for order-dependent aggregates like `array_agg`. - -Ordered-set aggregates such as `quantile`, `percentile_cont`, and `percentile_disc` require an intra-aggregate `order by` clause to produce correct results, as they depend on the iteration order of input values. Without `order by`, results are undefined. - -### Basic example - -Collect page names sorted alphabetically within each group: - -${query [[ - from - p = index.tag 'page' - group by - p.tags[1] - select { - tag = key, - names_asc = array_agg(p.name order by p.name asc), - names_desc = array_agg(p.name order by p.name desc) - } - order by - tag - limit - 5 -]]} - -### Combined with `filter(where ...)` -The `order by` and `filter` clauses can be used together. The filter is applied first (excluding rows), then the remaining rows are sorted before iteration: - -${query [[ - from - p = index.tag 'page' - group by - p.tags[1] - select { - tag = key, - big_by_size = array_agg(p.name order by p.size desc) filter(where p.size > 5) - } - order by - tag - limit - 5 -]]} - -### Null handling -The `nulls first` and `nulls last` modifiers work inside intra-aggregate `order by` the same way they do in the query-level `order by`: - -```lua -query [[ - from - p = data - group - by p.category - select { - cat = key, - items = array_agg(p.name - order by - p.priority asc nulls last - ) - } -]] -``` - -## Field access after grouping -Non-aggregated field references, such as `name` in `select`, refer to the first item in the group, matching common SQL and MySQL semantics. - -${query [[ - from - p = tags.page - group by - p.tags[1] - select { - tag = key, - first_page = p.name, - n = count(p.name) - } -]]} - -## Custom aggregators -Custom aggregator functions may be defined by the user using [[Library/Std/APIs/Aggregate|dedicated API]]. - -# See also -* [[Space Lua/Lua Integrated Query/Grouping]] — grouping queries without aggregation -* [[Space Lua/Lua Integrated Query]] — full LIQ language reference and listing available aggregates +This page has moved to [[Space Lua/Integrated Query/Aggregating]]. diff --git a/website/Space Lua/Lua Integrated Query/Grouping.md b/website/Space Lua/Lua Integrated Query/Grouping.md index f88e580c..d09dd16d 100644 --- a/website/Space Lua/Lua Integrated Query/Grouping.md +++ b/website/Space Lua/Lua Integrated Query/Grouping.md @@ -1,286 +1 @@ -#maturity/experimental - -The `group by` and `having` clauses extend [[Space Lua/Lua Integrated Query]] with SQL-style grouping and aggregate filtering. - -After `group by`, each result row has two fields: - -- **`key`** - the group key (single value or table for multi-key) -- **`group`** - a table (array) of all items in that group - -The field names used in `group by` are also available as bare variables in `having`, `select`, and `order by`. Use `#group` to count items per group. - -> **note** Note -> `having` can only reference group key fields, `key`, `group`, aggregate expressions like `#group`, and aggregate functions like `count()`. To filter individual rows, use `where`. - -# Examples - -All examples below use `tags.tag`. - -## Group by single key -Group all tags by `name`: - -${query [[ - from - t = tags.tag - group by - t.name - limit 5 -]]} - -## Group by multiple keys -Group tags by `name` and `parent` together: - -${query[[ - from - t = tags.tag - group by - t.name, - t.parent - limit 5 -]]} - -## Filter groups by count -Only show tags that appear more than 2 times: - -${query[[ - from - t = tags.tag - group by - t.name - having - #group > 2 - limit 5 -]]} - -## Find unique tags -Tags appearing exactly once: - -${query[[ - from - t = tags.tag - group by - t.name - having - #group == 1 - select key -]]} - -## Filter groups by key value -Only show the group where `name` is "meta": - -${query[[ - from - tags.tag - group by - name - having - name == "meta" -]]} - -${query[[ - from - t = tags.tag - group by - t.name - having - t.name == "meta" -]]} - -## Multi-key having - -Groups by `name` and `parent`, keep only page-level tags with more than 1 entry: - -${query [[ - from - tags.tag - group by - name, - parent - having - parent == 'page' and - #group > 1 -]]} - -## `where` before `group by` - -Filter to page parents first, then group by `name`: - -${query [[ - from - tags.tag - where - parent == 'page' - group by - name -]]} - -## `where`, `group by` and `having` combined - -Filter to page parents, group by `name`, keep groups with 2+ items: -${query [[ - from - index.tag 'tag' - where - parent == 'page' - group by - name - having - #group >= 2 -]]} - -## `select` name and count - -Project each group into a table with `name` and `count`: -${query [[ - from - index.tag 'tag' - group by - name - select { - name = name, - count = #group - } -]]} - -## `select` with multi-key - -Project both key parts and count: - -${query [[ - from - index.tag 'tag' - group by - name, - parent - select { - name = name, - parent = parent, - count = #group - } -]]} - -## Full pipeline: `where`, `group by`, `having` and `select` - -Filter, group, filter groups, then project: - -${query [[ - from - index.tag 'tag' - where - parent == 'page' or - parent == 'task' - group by - name - having - #group > 1 - select { - tag = name, - total = #group - } -]]} - -## Order groups by count - -Sort groups by size, largest first: - -${query [[ - from - index.tag 'tag' - group by - name - order by - #group desc -]]} - -## Top tags with `having`, `order by`, and `select` - -Tags with 2+ occurrences, sorted by count, projected: -${query [[ - from - index.tag 'tag' - group by - name - having - #group >= 2 - order by - #group desc - select { - tag = name, - count = #group - } -]]} - -## Top N groups with `limit` -${query [[ - from - index.tag 'tag' - group by - name - order by - #group desc - limit - 3 -]]} - -## Full pipeline with `limit` -Top 5 tags with 2+ uses, showing name and count: -${query [[ - from - p = index.tag 'tag' - group by - p.name - having - #group > 1 - select { - tag = name, - count = #group - } -]]} - -## Multi-key with explicit object variable -Full pipeline with `p =` binding and two group keys: - -${query [[ - from - p = index.tag 'tag' - where - p.parent == 'page' - group by - p.name, - p.parent - having - #group >= 2 - order by - #group desc - select { - tag = name, - parent = parent, - count = #group - } -]]} - -## Access `key` directly - -For single-key grouping, `key` holds the value directly: -${query [[ - from - index.tag 'tag' - group by - name - having - key == 'meta' -]]} - -## Access `key` table for multi-key - -For multi-key grouping, `key` is a table indexed from 1: -${query [[ - from - index.tag 'tag' - group by - name, - parent - having - key[1] == 'meta' and - key[2] == 'page' -]]} +This page has moved to [[Space Lua/Integrated Query/Grouping]]. diff --git a/website/Space Style.md b/website/Space Style.md index a63e1565..01cdb3c6 100644 --- a/website/Space Style.md +++ b/website/Space Style.md @@ -21,7 +21,7 @@ somestyle { } ``` -The following [[Space Lua/Lua Integrated Query]] is used to determine the order in which Space Style is loaded: +The following [[Space Lua/Integrated Query]] is used to determine the order in which Space Style is loaded: ```lua query[[from index.tag "space-style" order by _.priority desc]] diff --git a/website/Tag.md b/website/Tag.md index 7f383eba..598ccc48 100644 --- a/website/Tag.md +++ b/website/Tag.md @@ -5,7 +5,7 @@ tags: glossary Tags in SilverBullet are used to encode types of [[Object|Objects]], they’re similar to tables in relational databases, or classes in object-oriented programming. -Every [[Object]] has a main `tag`, which signifies the type of object being described. In addition, any number of additional tags can be assigned as well via the `tags` attribute. You can use either the main `tag` or any of the `tags` as query sources in [[Space Lua/Lua Integrated Query]]. +Every [[Object]] has a main `tag`, which signifies the type of object being described. In addition, any number of additional tags can be assigned as well via the `tags` attribute. You can use either the main `tag` or any of the `tags` as query sources in [[Space Lua/Integrated Query]]. # Built-in tags ${widgets.subPages("Object")} diff --git a/website/Task.md b/website/Task.md index e67e87d1..eb2a733c 100644 --- a/website/Task.md +++ b/website/Task.md @@ -15,7 +15,7 @@ which renders as follows: SilverBullet allows you to simply toggle the complete state of a task by clicking the checkbox. -All tasks across your space are automatically [[Object/task|indexed]] and can therefore be [[Space Lua/Lua Integrated Query|queried]]. +All tasks across your space are automatically [[Object/task|indexed]] and can therefore be [[Space Lua/Integrated Query|queried]]. # Custom states Tasks support the default `x` and ` ` states (done and not done), but custom states as well. Support for this is still basic, however.