Skip to content

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

yaml
dependencies:
  cdx_query_server_postgres:
    hosted: https://pub.vyuh.tech
    version: ^0.1.1

Wiring

dart
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.

json
{
  "schema_type": "cdx.query.filter.condition",
  "field": "status",
  "operator": "equals",
  "value": "published"
}
json
{
  "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

OperatorSQL
equals, notEquals=, <>
greaterThan, greaterThanOrEqual>, >=
lessThan, lessThanOrEqual<, <=
between, notBetweenBETWEEN, NOT BETWEEN
contains, notContainsILIKE, NOT ILIKE
startsWith, endsWithPrefix/suffix ILIKE
isNull, isNotNullIS NULL, IS NOT NULL
in, notInIN (...), 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:

dart
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:

dart
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