Install
Add the core parser plus the dialect package that will emit SQL for your target database.
dotnet add package Nimblesite.Lql.Core
dotnet add package Nimblesite.Lql.Postgres
dotnet add package Nimblesite.Lql.SqlServer
dotnet add package Nimblesite.Lql.SQLite
| Package | Use |
Nimblesite.Lql.Core | Parser, AST, statement conversion. |
Nimblesite.Lql.Postgres | ToPostgreSql() extension. |
Nimblesite.Lql.SqlServer | ToSqlServer() extension. |
Nimblesite.Lql.SQLite | ToSQLite() extension. |
Programmatic API
Parse once, then call the dialect extension. Errors are returned as `Result<T,E>` values.
using Nimblesite.Lql.Core;
using Nimblesite.Lql.Postgres;
using Nimblesite.Sql.Model;
using Outcome;
var lql = "Users |> filter(fn(row) => row.Users.Age > 21) |> select(Users.Name)";
var statementResult = LqlStatementConverter.ToStatement(lql);
if (statementResult is Result<LqlStatement, SqlError>.Ok<LqlStatement, SqlError> ok)
{
Result<string, SqlError> sql = ok.Value.ToPostgreSql();
}
Syntax
A query starts with a table or binding and flows left-to-right through operations.
| Construct | Shape |
| Table | users |
| Pipeline | users |> select(users.id) |
| Column | orders.total |
| Lambda | fn(row) => row.users.active = true |
| Alias | users.name as display_name |
| Let binding | let active = users |> filter(...) |
let active_users =
users |> filter(fn(row) => row.users.status = 'active')
active_users
|> select(active_users.id, active_users.name)
|> order_by(active_users.name asc)
|> limit(50)
Pipeline Operations
| Operation | Purpose |
select(cols...) | Project columns or computed expressions. |
filter(fn(row) => ...) | Filter rows before grouping. |
join(table, on = ...) | Inner join to another source. |
left_join(table, on = ...) | Preserve rows from the left source. |
group_by(cols...) | Group result rows. |
having(fn(group) => ...) | Filter grouped rows. |
order_by(col asc|desc) | Sort the result. |
limit(n) / offset(n) | Page the result. |
union(query) / union_all(query) | Combine compatible result sets. |
insert(target) | Insert query output into another table. |
Expressions And Functions
Expressions can compare values, combine predicates, compute columns, and call mapped SQL functions.
| Group | Examples |
| Comparison | =, !=, >, <, >=, <= |
| Logical | and, or, not |
| Math | +, -, *, /, round, abs |
| Aggregate | count, sum, avg, min, max |
| Window | row_number, rank, dense_rank, lag, lead |
| Session | current_setting('app.tenant_id')::uuid for PostgreSQL migration/RLS shapes. |
SQL Dialects
LQL keeps query intent stable while the dialect package handles SQL differences such as `LIMIT`, `TOP`, booleans, and function names.
users
|> filter(fn(row) => row.users.age > 18)
|> select(users.name, users.email)
|> order_by(users.name asc)
|> limit(10)
| Dialect | Generated paging shape |
| PostgreSQL | ORDER BY users.name ASC LIMIT 10 |
| SQLite | ORDER BY users.name ASC LIMIT 10 |
| SQL Server | SELECT TOP 10 ... ORDER BY users.name ASC |
Migrations, RLS, And `bodyLql`
DataProvider migrations use LQL where schema logic must be portable or generated consistently. Use `usingLql` and `withCheckLql` for RLS policies, and `bodyLql` for PostgreSQL function bodies that should be transpiled instead of handwritten.
tables:
- name: documents
columns:
- name: id
type: uuid
primaryKey: true
- name: tenant_id
type: uuid
- name: owner_id
type: uuid
rls:
enabled: true
policies:
- name: tenant_documents
command: all
usingLql: tenant_id = current_setting('app.tenant_id')::uuid
withCheckLql: owner_id = current_setting('app.user_id')::uuid
functions:
- name: current_tenant_id
schema: app
returns: uuid
language: sql
bodyLql: current_setting('app.tenant_id')::uuid
For PostgreSQL, `current_setting('key')` is rewritten to the null-tolerant `current_setting('key', true)` form before SQL is emitted. `body` and `bodyLql` are mutually exclusive.
VS Code, LSP, Database Schema, And AI
The VS Code extension runs the Rust `lql-lsp` language server for `.lql` files.
| Feature | Behavior |
| Diagnostics | ANTLR parse errors, unmatched parentheses, pipe spacing warnings, and unknown function hints. |
| Completions | Pipeline operations, keywords, functions, `let` bindings, table names, and columns. |
| Database config | Set `lql.database.connectionString`, `LQL_CONNECTION_STRING`, or `DATABASE_URL` for schema-aware completions and hovers. |
| AI completions | Optional model completions are merged after schema and keyword results and bounded by timeout. |
{
"lql.database.connectionString": "Host=localhost;Database=app;Username=postgres;Password=secret",
"lql.ai.provider": "ollama",
"lql.ai.endpoint": "http://localhost:11434/api/generate",
"lql.ai.model": "qwen2.5-coder:1.5b"
}
F# Type Provider
The type provider validates LQL at compile time and exposes the original query plus generated SQLite SQL.
<PackageReference Include="Nimblesite.Lql.TypeProvider.FSharp" Version="*" />
open Nimblesite.Lql.Core.TypeProvider
type ActiveUsers =
LqlCommand<"Users |> filter(fn(row) => row.Users.Status = 'active') |> select(*)">
let query = ActiveUsers.Query
let sql = ActiveUsers.Sql