Filters — cdx_query_server_postgres
PostgresQueryPlugin is the shipped QueryPlugin for Postgres. It plugs the shared cdx_query AST into vyuh_server by compiling a typed filter tree into a parameterized SQL WHERE fragment.
Register it when entity list/count/position endpoints need ?query= support or when an EntityCrudConfig.scope returns a Filter.
Install
dependencies:
cdx_query_server_postgres:
hosted: https://pub.vyuh.tech
version: ^0.1.1Wiring
import 'package:vyuh_server/vyuh_server.dart';
import 'package:vyuh_server_plugin_postgres/vyuh_server_plugin_postgres.dart';
import 'package:cdx_query_server_postgres/cdx_query_server_postgres.dart';
import 'package:vyuh_server_plugin_entity_crud/vyuh_server_plugin_entity_crud.dart';
final runtime = await VyuhServer.bootstrap(
name: 'catalog-api',
plugins: [
PostgresDbPlugin(),
PostgresQueryPlugin(),
EntityCrudPlugin(),
],
features: [catalogFeature],
);vyuh.query is now a PostgresQueryCompiler. EntityCrudPlugin uses it for ?query=, EntityCrudConfig.scope, count, grouped, and position queries. If the plugin is missing, plain unquery-driven CRUD can still run, but scoped or query requests fail with a structured validation error.
Filter JSON
The wire format is the cdx_query AST: either a condition or a group.
{
"schema_type": "cdx.query.filter.condition",
"field": "status",
"operator": "equals",
"value": "published"
}{
"schema_type": "cdx.query.filter.group",
"op": "and",
"children": [
{
"schema_type": "cdx.query.filter.condition",
"field": "tenant_id",
"operator": "equals",
"value": "acme"
},
{
"schema_type": "cdx.query.filter.condition",
"field": "price_cents",
"operator": "greaterThan",
"value": 1000
}
]
}Values are JSON-native: a scalar for single-value operators, a list for in/notIn, { "from": ..., "to": ... } for ranges, and null for isNull/isNotNull.
Supported Operators
| Operator | SQL |
|---|---|
equals, notEquals | =, <> |
greaterThan, greaterThanOrEqual | >, >= |
lessThan, lessThanOrEqual | <, <= |
between, notBetween | BETWEEN, NOT BETWEEN |
contains, notContains | ILIKE, NOT ILIKE |
startsWith, endsWith | Prefix/suffix ILIKE |
isNull, isNotNull | IS NULL, IS NOT NULL |
in, notIn | IN (...), NOT IN (...) |
Groups support and, or, and not. The Postgres compiler emits named parameters, never interpolated SQL.
Field Allow-Listing
By default, the compiler accepts any field and lets Postgres reject an unknown column. Public APIs should restrict the field set:
PostgresQueryPlugin(
allowedFields: const {
'tenant_id',
'status',
'price_cents',
'created_at',
},
);A filter referencing an unlisted field throws before SQL execution and returns a structured 400 through the framework error middleware.
Direct Use
Custom routes can use the compiler directly:
Future<Response> searchProducts(Request req) async {
final filterJson = req.url.queryParameters['filter'];
var where = 'tenant_id = @tenant';
final params = <String, Object?>{
'tenant': req.actor.tenantId,
};
if (filterJson != null) {
final compiled = vyuh.query!.compile(jsonDecode(filterJson));
if (compiled.whereClause.isNotEmpty) {
where = '$where AND ${compiled.whereClause}';
params.addAll(compiled.parameters);
}
}
final rows = await vyuh.db.execute(
'SELECT * FROM products WHERE $where ORDER BY created_at DESC LIMIT 100',
parameters: params,
);
return jsonResponse(body: {'success': true, 'data': rows.asMaps});
}Where to Go Next
- Entity CRUD — built-in list/count/grouped filtering
vyuh.query— the runtime accessor