Skip to content

Query 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 ​