Changelog - 2026-09-29
inSql and ninSql
FeatureIn one line. A where can now say "this column is among the rows a subquery returns" - { id: { inSql: sql`...` } } - so filtering by another table no longer needs a first query that collects ids.
What is new
typescript
import { sql } from 'drizzle-orm';
const taggedRed = sql`SELECT ${tagTable.orderId} FROM ${tagTable} WHERE ${tagTable.label} = ${label}`;
await orderRepository.find({ filter: { where: { id: { inSql: taggedRed } } } });
await orderRepository.count({ where: { id: { ninSql: taggedRed } } });inSqlemits"col" IN (<subquery>),ninSqlemits"col" NOT IN (<subquery>).- Works in
find,findOne,count, update and delete wheres, nestedand/or, and together with the default filter and row scope. TWhereOperatorsgainsinSql?: TSqlFragmentandninSql?: TSqlFragment.TSqlFragmentis{ getSQL(): unknown }- a DrizzleSQLorSQLWrapper- named structurally so@venizia/ignis-filterstays free of Drizzle.
Safety
The operand must be a SQL object built in server code. A string is refused with a 400, and a filter that arrives as JSON over HTTP cannot carry a SQL object, so the operators add no injection path. Values go into the subquery through Drizzle's ${} bindings.
Good to know
- An empty subquery matches nothing under
inSqland everything underninSql. - If the subquery returns a NULL,
NOT INmatches no row, as in SQL. Filter NULLs out inside the subquery when the column can hold them. - Postgres and SQLite support both operators. Typesense and Meilisearch refuse them with a 400.
Who is affected
Nobody has to change anything. Code that collects ids in a first query and then filters with inq can move to inSql and drop the round trip and any cap on the id list.
Files: packages/connectors/src/relational/core/repositories/dialect/internal/subquery.ts