PostgREST Parser for Rust
A high-performance Rust implementation of the PostgREST URL-to-SQL parser, supporting both native and WASM targets.
Features
- ✅ Complete PostgREST API: All 22+ filter operators fully implemented
- ✅ Logic Operators: AND, OR, NOT with arbitrary nesting depth
- ✅ Select Parsing: Fields, relations, spreads, aliases, JSON paths, type casting
- ✅ Full-Text Search: Multiple FTS operators with language support
- ✅ Array/Range Operations: PostgreSQL array and range type support
- ✅ Quantifiers:
anyandallfor array comparisons - ✅ Order Parsing: Multi-column ordering with nulls handling
- ✅ Parameterized SQL: Safe SQL generation with $1, $2, etc. placeholders
- ✅ Zero Regex: Uses nom parser combinators for better performance
- ✅ Type Safe: Comprehensive error handling with thiserror
- ✅ WASM Support: Full TypeScript/JavaScript bindings for browser and Deno (optional feature)
- ✅ TypeScript Client: Type-safe API with zero
anytypes, object-based APIs, and IntelliSense - ✅ 171 Tests: Comprehensive test coverage (148 Rust + 23 WASM integration tests)
Installation
Rust
Add to your Cargo.toml:
[]
= "0.1.0"
TypeScript/JavaScript (WASM)
The parser is available as a WebAssembly module with full TypeScript support for use in browsers, Node.js, Deno, and edge runtimes.
Quick Start for TypeScript Projects
1. Build the WASM package:
# Install wasm-pack if you haven't already
# Build for web (browsers, Deno, Cloudflare Workers, etc.)
# Or for Node.js
# Development build (faster compilation, larger file)
2. Copy to your TypeScript project:
# Copy the entire pkg/ directory to your project
# Or publish to npm (requires package.json configuration)
&&
3. Import and use:
🎉 NEW: Type-Safe Client API
We now provide a fully type-safe TypeScript client with zero
anytypes, object-based APIs, and better IntelliSense support. See TYPESCRIPT_GUIDE.md for details.// Recommended: Type-safe client (client.ts) import { createClient } from './postgrest-parser/client.js'; const client = createClient(); const result = client.select("users", { filters: { age: "gte.18", status: "eq.active" }, order: ["name.asc"], limit: 10 }); // Full IntelliSense, no 'any' types, native objects// Alternative: Low-level WASM API (postgrest_parser.js) import init, { parseRequest } from './postgrest-parser/postgrest_parser.js'; await init(); const result = parseRequest("GET", "users", "age=gte.18&limit=10", null, null); // Direct WASM bindings, requires manual query string construction
TypeScript Integration Guide
1. HTTP Method Routing (Recommended Approach)
The parseRequest() function is the primary entry point - it automatically routes HTTP methods to SQL operations following PostgREST conventions:
import init, { parseRequest } from './postgrest-parser/postgrest_parser.js';
await init();
// GET → SELECT
const getUsers = parseRequest(
"GET",
"users",
"age=gte.18&status=eq.active&order=name.asc&limit=10",
null,
null
);
// Generates: SELECT * FROM "users" WHERE "age" >= $1 AND "status" = $2 ORDER BY "name" ASC LIMIT $3
// POST → INSERT
const createUser = parseRequest(
"POST",
"users",
"returning=id,name,email",
JSON.stringify({ name: "Alice", email: "alice@example.com" }),
JSON.stringify({ Prefer: "return=representation" })
);
// Generates: INSERT INTO "users" ("name", "email") VALUES ($1, $2) RETURNING "id", "name", "email"
// PUT → UPSERT (auto ON CONFLICT from query filters)
const upsertUser = parseRequest(
"PUT",
"users",
"email=eq.alice@example.com&returning=*",
JSON.stringify({ email: "alice@example.com", name: "Alice Updated" }),
null
);
// Generates: INSERT ... ON CONFLICT ("email") DO UPDATE SET ... RETURNING *
// PATCH → UPDATE
const updateUser = parseRequest(
"PATCH",
"users",
"id=eq.123&returning=id,status",
JSON.stringify({ status: "verified" }),
null
);
// Generates: UPDATE "users" SET "status" = $1 WHERE "id" = $2 RETURNING "id", "status"
// DELETE → DELETE
const deleteUser = parseRequest(
"DELETE",
"users",
"id=eq.123&returning=id",
null,
null
);
// Generates: DELETE FROM "users" WHERE "id" = $1 RETURNING "id"
// RPC → Function call
const rpcResult = parseRequest(
"POST",
"rpc/calculate_total",
"select=total,tax",
JSON.stringify({ order_id: 123, tax_rate: 0.08 }),
null
);
// Generates: SELECT * FROM calculate_total($1, $2)
2. Express.js Integration
import express from 'express';
import init, { parseRequest } from './postgrest-parser/postgrest_parser.js';
import pg from 'pg';
const app = express();
const db = new pg.Pool({ connectionString: process.env.DATABASE_URL });
// Initialize WASM once at startup
await init();
app.use(express.json());
// Universal PostgREST-compatible endpoint
app.all('/api/:table', async (req, res) => {
try {
const result = parseRequest(
req.method,
req.params.table,
new URLSearchParams(req.query).toString(),
req.body ? JSON.stringify(req.body) : null,
JSON.stringify(req.headers)
);
const { rows } = await db.query(result.query, result.params);
res.json(rows);
} catch (error) {
res.status(400).json({ error: error.message });
}
});
app.listen(3000);
Now your API supports:
&select=id,name
3. Next.js API Route
// pages/api/[table].ts
import type { NextApiRequest, NextApiResponse } from 'next';
import init, { parseRequest } from '@/lib/postgrest-parser/postgrest_parser.js';
import { query } from '@/lib/db';
let initialized = false;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (!initialized) {
await init();
initialized = true;
}
const { table } = req.query;
const queryString = new URLSearchParams(req.query as Record<string, string>).toString();
try {
const result = parseRequest(
req.method!,
table as string,
queryString,
req.body ? JSON.stringify(req.body) : null,
JSON.stringify(req.headers)
);
const rows = await query(result.query, result.params);
res.status(200).json(rows);
} catch (error) {
res.status(400).json({ error: (error as Error).message });
}
}
4. Deno Edge Function
// supabase/functions/postgrest-proxy/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import init, { parseRequest } from './postgrest_parser.js';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
await init();
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
);
serve(async (req) => {
const url = new URL(req.url);
const path = url.pathname.slice(1);
const query = url.search.slice(1);
let body = null;
if (req.method !== 'GET' && req.method !== 'DELETE') {
body = await req.text();
}
try {
const result = parseRequest(
req.method,
path,
query,
body,
JSON.stringify(Object.fromEntries(req.headers))
);
const { data, error } = await supabase.rpc('execute_sql', {
query: result.query,
params: result.params
});
if (error) throw error;
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(JSON.stringify({ error: error.message }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
});
5. Type-Safe Wrapper
// lib/postgrest.ts
import init, { parseRequest, WasmQueryResult } from './postgrest-parser/postgrest_parser.js';
let initialized = false;
async function ensureInit() {
if (!initialized) {
await init();
initialized = true;
}
}
export interface QueryOptions {
select?: string;
filters?: Record<string, string>;
order?: string;
limit?: number;
offset?: number;
}
export class PostgRESTClient {
constructor(private executeQuery: (sql: string, params: any[]) => Promise<any[]>) {}
async select(table: string, options: QueryOptions = {}): Promise<any[]> {
await ensureInit();
const params = new URLSearchParams();
if (options.select) params.set('select', options.select);
if (options.filters) Object.entries(options.filters).forEach(([k, v]) => params.set(k, v));
if (options.order) params.set('order', options.order);
if (options.limit) params.set('limit', String(options.limit));
if (options.offset) params.set('offset', String(options.offset));
const result = parseRequest('GET', table, params.toString(), null, null);
return this.executeQuery(result.query, result.params);
}
async insert(table: string, data: any | any[], returning = '*'): Promise<any[]> {
await ensureInit();
const result = parseRequest(
'POST',
table,
`returning=${returning}`,
JSON.stringify(data),
JSON.stringify({ Prefer: 'return=representation' })
);
return this.executeQuery(result.query, result.params);
}
async upsert(
table: string,
data: any,
conflictColumns: string[],
returning = '*'
): Promise<any[]> {
await ensureInit();
const filters = conflictColumns.map(col => `${col}=eq.${data[col]}`).join('&');
const result = parseRequest(
'PUT',
table,
`${filters}&returning=${returning}`,
JSON.stringify(data),
null
);
return this.executeQuery(result.query, result.params);
}
async update(
table: string,
data: any,
filters: Record<string, string>,
returning = '*'
): Promise<any[]> {
await ensureInit();
const params = new URLSearchParams(filters);
params.set('returning', returning);
const result = parseRequest('PATCH', table, params.toString(), JSON.stringify(data), null);
return this.executeQuery(result.query, result.params);
}
async delete(table: string, filters: Record<string, string>, returning = 'id'): Promise<any[]> {
await ensureInit();
const params = new URLSearchParams(filters);
params.set('returning', returning);
const result = parseRequest('DELETE', table, params.toString(), null, null);
return this.executeQuery(result.query, result.params);
}
async rpc(functionName: string, args: any = {}, returning?: string): Promise<any[]> {
await ensureInit();
const queryString = returning ? `returning=${returning}` : '';
const result = parseRequest(
'POST',
`rpc/${functionName}`,
queryString,
Object.keys(args).length > 0 ? JSON.stringify(args) : null,
null
);
return this.executeQuery(result.query, result.params);
}
}
// Usage:
// const client = new PostgRESTClient(async (sql, params) => {
// const { rows } = await db.query(sql, params);
// return rows;
// });
//
// const users = await client.select('users', {
// select: 'id,name,email',
// filters: { 'age': 'gte.18', 'status': 'eq.active' },
// order: 'name.asc',
// limit: 10
// });
6. Basic Usage (Browser/Deno)
import init, { parseQueryString } from './postgrest_parser.js';
// Initialize WASM module (call once)
await init();
// Parse a PostgREST query string
const result = parseQueryString(
"users",
"age=gte.18&status=in.(active,pending)&order=created_at.desc&limit=10"
);
console.log('SQL:', result.query);
// SELECT * FROM "users" WHERE "age" >= $1 AND "status" = ANY($2) ORDER BY "created_at" DESC LIMIT $3
console.log('Params:', result.params);
// ["18", ["active", "pending"], 10]
console.log('Tables:', result.tables);
// ["users"]
Parse Only (Without SQL Generation)
import init, { parseOnly } from './postgrest_parser.js';
await init();
// Parse query structure without generating SQL
const parsed = parseOnly("select=id,name&age=gte.18&limit=10");
console.log(parsed);
// {
// select: [{ field: "id" }, { field: "name" }],
// filters: [...],
// limit: 10,
// offset: null,
// order: []
// }
Using with Deno
// run_parser.ts
import init, { parseQueryString } from "./pkg/postgrest_parser.js";
await init();
const result = parseQueryString(
"posts",
"select=id,title,author(name)&status=eq.published&created_at=gte.2024-01-01"
);
console.log("Generated SQL:", result.query);
console.log("Parameters:", result.params);
Run with:
Real-World Example: API Endpoint
// api/posts.ts
import init, { parseQueryString } from '../postgrest_parser.js';
// Initialize once at startup
await init();
export async function handlePostsRequest(request: Request) {
const url = new URL(request.url);
const queryString = url.search.slice(1); // Remove leading '?'
try {
const result = parseQueryString("posts", queryString);
// Execute query with your database client
const rows = await db.query(result.query, result.params);
return new Response(JSON.stringify(rows), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(
JSON.stringify({ error: error.message }),
{ status: 400 }
);
}
}
JSON Serialization
const result = parseQueryString("users", "age=gte.18");
// Convert to plain JSON object
const json = result.toJSON();
console.log(JSON.stringify(json, null, 2));
// {
// "query": "SELECT * FROM \"users\" WHERE \"age\" >= $1",
// "params": ["18"],
// "tables": ["users"]
// }
Complete WASM API Reference
The WASM module provides comprehensive TypeScript/JavaScript bindings for all PostgREST operations.
📚 Full Documentation: See WASM_API.md for complete API reference with 40+ examples.
Core Functions:
| Function | Purpose | HTTP Method Equivalent |
|---|---|---|
parseRequest(method, path, qs, body?, headers?) |
Main entry point - Routes HTTP methods to SQL | All methods |
parseQueryString(table, queryString) |
Direct SELECT generation | GET |
parseInsert(table, body, qs?, headers?) |
Direct INSERT generation | POST |
parseUpdate(table, body, qs, headers?) |
Direct UPDATE generation | PATCH |
parseDelete(table, qs, headers?) |
Direct DELETE generation | DELETE |
parseRpc(function, body?, qs?, headers?) |
Direct RPC call | POST to rpc/* |
parseOnly(queryString) |
Parse without SQL generation | N/A |
buildFilterClause(filters) |
Build WHERE clause from filters | N/A |
Return Type:
interface WasmQueryResult {
query: string; // Parameterized SQL with $1, $2, ... placeholders
params: any[]; // Parameter values (strings, numbers, arrays, etc.)
tables: string[]; // Referenced table names
}
HTTP Method Routing:
parseRequest("GET", path, qs) // → SELECT
parseRequest("POST", path, qs) // → INSERT (or RPC if path starts with "rpc/")
parseRequest("PUT", path, qs) // → UPSERT (auto ON CONFLICT from filters)
parseRequest("PATCH", path, qs) // → UPDATE
parseRequest("DELETE", path, qs) // → DELETE
Examples: See examples/wasm_mutations_example.ts for 21 comprehensive examples covering all operations.
Running Examples and Tests
Run comprehensive examples:
# Build WASM
# Run SELECT examples (20 examples)
# Run mutation examples (21 examples: INSERT, UPDATE, DELETE, RPC, HTTP routing)
Run integration tests:
# Install Deno (if not already installed)
|
# Run WASM integration tests
# Or use the Deno task
See tests/integration/README.md for detailed test documentation.
TypeScript Type Definitions:
The WASM package includes full TypeScript definitions in pkg/postgrest_parser.d.ts. Your IDE will automatically provide:
- IntelliSense/autocomplete for all functions
- Type checking for parameters and return values
- JSDoc documentation on hover
import init, { parseRequest, WasmQueryResult } from './postgrest-parser/postgrest_parser.js';
// TypeScript knows the exact shape of WasmQueryResult
const result: WasmQueryResult = parseRequest("GET", "users", "age=gte.18", null, null);
// ^-- Type: { query: string; params: any[]; tables: string[] }
Performance
WASM performance benchmarks (from integration tests):
- Average parse time: ~0.01ms per query
- 100 queries: ~1ms total
- Throughput: ~100,000 queries/second in browser
The WASM build maintains near-native performance while running in JavaScript environments.
Browser Compatibility
Tested and working in:
- ✅ Chrome 90+
- ✅ Firefox 89+
- ✅ Safari 15+
- ✅ Edge 90+
- ✅ Deno 1.x+
- ✅ Node.js 16+ (with
--target nodejs)
Troubleshooting
Module not found:
// Make sure to use the correct path to pkg/
import init from './pkg/postgrest_parser.js'; // ✅
import init from './postgrest_parser.js'; // ❌
WASM initialization:
// Always call init() before using other functions
await init(); // ✅
parseQueryString(...); // ✅
parseQueryString(...); // ❌ Will fail - init() not called
Type errors in TypeScript:
// Use generated .d.ts files
import init, { parseQueryString } from './pkg/postgrest_parser.js';
// Type definitions are in ./pkg/postgrest_parser.d.ts
Usage
Rust: Basic Query Parsing
use *;
// Parse a query string
let params = parse_query_string?;
assert!;
assert!;
// Generate SQL
let result = to_sql?;
println!;
println!;
// Output:
// Query: SELECT "id", "name" FROM "users" WHERE "id" = $1 ORDER BY "id" DESC LIMIT $2
// Params: [String("1"), Number(10)]
Filter Operators
Comparison Operators
// Equality
let params = parse_query_string?; // WHERE "id" = $1
let params = parse_query_string?; // WHERE "status" <> $1
// Comparison
let params = parse_query_string?; // WHERE "age" > $1
let params = parse_query_string?; // WHERE "age" >= $1
let params = parse_query_string?; // WHERE "age" < $1
let params = parse_query_string?; // WHERE "age" <= $1
// Negation works with all operators
let params = parse_query_string?; // WHERE "age" <= $1
Pattern Matching
// SQL LIKE operators
let params = parse_query_string?; // WHERE "name" LIKE $1
let params = parse_query_string?; // WHERE "name" ILIKE $1 (case-insensitive)
// POSIX regex
let params = parse_query_string?; // WHERE "name" ~ $1
let params = parse_query_string?; // WHERE "name" ~* $1 (case-insensitive)
List and Array Operators
// IN operator
let params = parse_query_string?; // WHERE "status" = ANY($1)
// Array contains
let params = parse_query_string?; // WHERE "tags" @> $1
let params = parse_query_string?; // WHERE "tags" <@ $1
// Array overlap
let params = parse_query_string?; // WHERE "tags" && $1
Full-Text Search
// Basic FTS (uses plainto_tsquery)
let params = parse_query_string?;
// WHERE to_tsvector('english', "content") @@ plainto_tsquery('english', $1)
// With custom language
let params = parse_query_string?;
// WHERE to_tsvector('french', "content") @@ plainto_tsquery('french', $1)
// Phrase search
let params = parse_query_string?;
// WHERE to_tsvector('english', "content") @@ phraseto_tsquery('english', $1)
// Websearch (most lenient)
let params = parse_query_string?;
// WHERE to_tsvector('english', "content") @@ websearch_to_tsquery('english', $1)
Range Operators (PostgreSQL ranges)
let params = parse_query_string?; // WHERE "range" << $1 (strictly left)
let params = parse_query_string?; // WHERE "range" >> $1 (strictly right)
let params = parse_query_string?; // WHERE "range" &< $1
let params = parse_query_string?; // WHERE "range" &> $1
let params = parse_query_string?; // WHERE "range" -|- $1 (adjacent)
Special Operators
// IS operator
let params = parse_query_string?; // WHERE "deleted_at" IS NULL
let params = parse_query_string?; // WHERE "deleted_at" IS NOT NULL
let params = parse_query_string?; // WHERE "active" IS TRUE
let params = parse_query_string?; // WHERE "active" IS FALSE
Quantifiers
// ANY quantifier
let params = parse_query_string?; // WHERE "tags" = ANY($1)
// ALL quantifier
let params = parse_query_string?; // WHERE "tags" = ALL($1)
JSON Path Navigation
// Arrow operator (returns JSON)
let params = parse_query_string?;
// WHERE "data"->'name' = $1
// Double arrow operator (returns text)
let params = parse_query_string?;
// WHERE "data"->>'email' LIKE $1
// Nested paths
let params = parse_query_string?;
// WHERE "data"->'user'->'name' = $1
Type Casting
let params = parse_query_string?;
// WHERE "price"::numeric > $1
let params = parse_query_string?;
// WHERE ("data"->'age')::int >= $1
Logic Trees
// AND conditions
let params = parse_query_string?;
// OR conditions
let params = parse_query_string?;
// Nested logic
let params = parse_query_string?;
// Negated logic
let params = parse_query_string?;
Select with Relations
let params = parse_query_string?;
assert!;
Ordering
// Single column
let params = parse_query_string?;
// Multiple columns
let params = parse_query_string?;
// With nulls handling
let params = parse_query_string?;
Development
Building
# Native
# WASM
Testing
# Run all tests
# Run specific test
# Run with output
Benchmarks
# Run all benchmarks
# Run specific benchmark group
# See docs/BENCHMARKS.md for detailed results and analysis
Latest Benchmark Results
Benchmarked on Darwin 24.6.0 (macOS) with release optimizations.
Simple Parsing Performance:
| Operation | Time (median) | Throughput |
|---|---|---|
select=id,name,email |
1.14 µs | 880K ops/s |
age=gte.18 |
781 ns | 1.28M ops/s |
order=created_at.desc |
1.27 µs | 790K ops/s |
limit=10&offset=20 |
520 ns | 1.92M ops/s |
Realistic Workload Performance:
| Workload | Time (median) | Throughput | Description |
|---|---|---|---|
| User Search | 7.19 µs | 139K ops/s | SELECT + 2 filters + ORDER + LIMIT |
| Paginated List | 6.84 µs | 146K ops/s | SELECT + relation + filter + ORDER + pagination |
| Filtered Report | 9.92 µs | 101K ops/s | SELECT + relation + 4 filters + ORDER |
| Complex Search | 10.66 µs | 94K ops/s | SELECT + FTS + array ops + 3 filters + ORDER |
| Dashboard Aggregation | 7.84 µs | 128K ops/s | Complex logic tree + date range + ORDER |
Operator Performance:
| Operator Category | Example | Time (median) |
|---|---|---|
Comparison (eq, gte) |
id=eq.1 |
~750-783 ns |
| Pattern Match | name=like.*Smith* |
~783-803 ns |
| List Operations | status=in.(a,b,c) |
~1.07 µs |
| Full-Text Search | content=fts.term |
~941 ns |
| FTS with Language | content=fts(french).terme |
~974 ns |
| Array Operations | tags=cs.{rust,elixir} |
~787-1.06 µs |
| Range Operations | range=sl.[1,10) |
~1.01 µs |
| JSON Path | data->name=eq.test |
~934-1.13 µs |
| Type Casting | price::numeric=gt.100 |
~1.10 µs |
| Quantifiers | tags=eq(any).{a,b} |
~907-1.04 µs |
SQL Generation (End-to-End):
| Query Type | Time (median) | Throughput |
|---|---|---|
| Simple SELECT | 2.21 µs | 452K ops/s |
| With Filters | 3.03 µs | 330K ops/s |
| With ORDER | 3.75 µs | 267K ops/s |
| Complex Query | 7.57 µs | 132K ops/s |
Query Scaling:
- 1 filter: 1.97 µs
- 3 filters: 4.09 µs (2.1x)
- 5 filters: 6.79 µs (3.4x)
- 10 filters: 13.40 µs (6.8x)
Performance vs Reference Implementation:
- Simple queries: 1.3-6x faster
- Complex queries: 1.3x faster
- All operations under 15 µs
See BENCHMARKS.md for complete performance analysis.
Complete Operator Reference
| Operator | PostgREST | SQL | Example |
|---|---|---|---|
eq |
Equal | = |
id=eq.1 |
neq |
Not equal | <> |
status=neq.deleted |
gt |
Greater than | > |
age=gt.18 |
gte |
Greater than or equal | >= |
age=gte.18 |
lt |
Less than | < |
age=lt.65 |
lte |
Less than or equal | <= |
age=lte.65 |
like |
LIKE pattern | LIKE |
name=like.*Smith* |
ilike |
Case-insensitive LIKE | ILIKE |
name=ilike.*smith* |
match |
POSIX regex | ~ |
name=match.^John |
imatch |
Case-insensitive regex | ~* |
name=imatch.^john |
in |
In list | = ANY($1) |
status=in.(active,pending) |
is |
IS check | IS |
deleted=is.null |
fts |
Full-text search | @@ |
content=fts.search |
plfts |
Plain FTS | @@ |
content=plfts.search |
phfts |
Phrase FTS | @@ |
content=phfts.exact phrase |
wfts |
Websearch FTS | @@ |
content=wfts.query |
cs |
Contains | @> |
tags=cs.{rust} |
cd |
Contained in | <@ |
tags=cd.{rust,elixir} |
ov |
Overlaps | && |
tags=ov.(rust,elixir) |
sl |
Strictly left | << |
range=sl.[1,10) |
sr |
Strictly right | >> |
range=sr.[1,10) |
nxl |
Not extends right | &< |
range=nxl.[1,10) |
nxr |
Not extends left | &> |
range=nxr.[1,10) |
adj |
Adjacent | `- | -` |
Performance
- Zero-copy parsing where possible with nom combinators
- No regex usage - all parsing done with efficient pattern matching
- 148 passing tests with comprehensive coverage
- Optimized for Rust - leverages Rust's zero-cost abstractions
Architecture
- AST (src/ast/): Typed intermediate representation of parsed queries
- Parser (src/parser/): nom-based combinator parsers (no regex)
common.rs- Shared parsing utilities (identifiers, lists, JSON paths)filter.rs- Filter/operator parsinglogic.rs- Logic tree parsing (AND/OR/NOT)order.rs- ORDER BY clause parsingselect.rs- SELECT clause parsing with relations
- SQL Builder (src/sql/): Parameterized PostgreSQL SQL generation
- Error Handling (src/error/): Typed errors using thiserror
Development
Building
# Native
# With all features
# WASM (if you need browser support)
Testing
# Run all Rust tests (148 tests)
# Run specific test
# Run with output
# Check code quality
# Run WASM integration tests (23 tests)
# Requires Deno: https://deno.land
# Or use Deno task
Code Quality
# Format code
# Run linter
# Check for security vulnerabilities
Roadmap
- Complete PostgREST filter operator support (22+ operators)
- Logic trees with arbitrary nesting
- Full-text search with language support
- Array and range operators
- Quantifiers (any/all)
- Comprehensive test coverage (148 tests)
- WASM bindings for TypeScript/JavaScript with Deno integration tests
- Benchmark suite comparing to reference implementation
- Count aggregation support
-
on_conflictparameter support - Relation column filtering
Contributing
Contributions are welcome! Areas of interest:
- Additional test cases and edge cases
- Performance optimizations
- WASM/JavaScript bindings
- Documentation improvements
- Bug reports and fixes
License
MIT
Acknowledgments
- Inspired by the PostgREST project
- Parser built with nom
- Reference implementation: postgrest_parser (Elixir)