n8n-io/n8n · error · TypeORMError

"${table}" is not part of this query

Error message

"${table}" is not part of this query

What it means

When options.lock.tables is provided with a row-level locking mode (pessimistic_write, dirty_read, pessimistic_partial_write, pessimistic_write_or_fail, for_no_key_update, for_key_share), FindOptionsUtils looks up each table name in qb.expressionMap.aliases by metadata.tableNameWithoutPrefix. If no alias matches, it throws `"${table}" is not part of this query`.

Source

Thrown at packages/@n8n/typeorm/src/find-options/FindOptionsUtils.ts:186

        if (options.lock) {
            if (options.lock.mode === "optimistic") {
                qb.setLock(options.lock.mode, options.lock.version);
            } else if (
                options.lock.mode === "pessimistic_read" ||
                options.lock.mode === "pessimistic_write" ||
                options.lock.mode === "dirty_read" ||
                options.lock.mode === "pessimistic_partial_write" ||
                options.lock.mode === "pessimistic_write_or_fail" ||
                options.lock.mode === "for_no_key_update" ||
                options.lock.mode === "for_key_share"
            ) {
                const tableNames = options.lock.tables ? options.lock.tables.map((table) => {
                    const tableAlias = qb.expressionMap.aliases.find((alias) => {
                        return alias.metadata.tableNameWithoutPrefix === table;
                    });
                    if (!tableAlias) {
                        throw new TypeORMError(`"${table}" is not part of this query`);
                    }
                    return qb.escape(tableAlias.name);
                }) : undefined;
                qb.setLock(options.lock.mode, undefined, tableNames);
            }
        }

        if (options.loadRelationIds === true) {
            qb.loadAllRelationIds();

        } else if (options.loadRelationIds instanceof Object) {
            qb.loadAllRelationIds(options.loadRelationIds as any);
        }

        if (options.where)
            qb.where(options.where);

        if ((options as FindManyOptions<T>).skip)

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Ensure each table listed in `lock.tables` is present as a join or the main alias in the query.
  2. Use the table name (matching metadata.tableNameWithoutPrefix), not the class name.
  3. If you only need to lock the main row, omit `tables` — TypeORM locks the main table by default.
  4. Re-verify after refactoring joins that the locked tables are still part of the query.

Example fix

// before - locking a table that isn't in the query
await repo.find({
  where: { id },
  lock: { mode: 'pessimistic_write', tables: ['user'] },
});

// after - lock the main alias, or join the table first
await repo.createQueryBuilder('order')
  .leftJoinAndSelect('order.user', 'user')
  .setLock('pessimistic_write', undefined, ['order', 'user'])
  .getOne();
Defensive patterns

Strategy: validation

Validate before calling

function validateLockTables(qb: import('@n8n/typeorm').QueryBuilder<any>, tables: string[]): void {
  const known = qb.expressionMap.aliases.map(a => a.metadata?.tableNameWithoutPrefix).filter(Boolean);
  for (const t of tables) {
    if (!known.includes(t)) throw new Error(`lock table ${t} is not part of this query`);
  }
}
// after building the query (with joins in place) but before getOne/getMany
if (options.lock?.tables) validateLockTables(qb, options.lock.tables);

Type guard

function tableIsInQuery(qb: import('@n8n/typeorm').QueryBuilder<any>, table: string): boolean {
  return qb.expressionMap.aliases.some(a => a.metadata?.tableNameWithoutPrefix === table);
}

Prevention

When it happens

Trigger: Locking tables that aren't joined or selected in the query (e.g. `lock: { tables: ['user'] }` on a query whose main alias is `Order` and never joins User); typo in the table name (camelCase vs snake); using the entity class name instead of the table name; locking a table after a refactor that removed the join.

Common situations: Adding pessimistic locking to a query without confirming the target table is part of the FROM/JOIN; switching naming strategy so tableNameWithoutPrefix changed; copy-pasting lock config between queries; locking a table that's only referenced via a subquery.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/03af048b6d293116. Report an issue: GitHub.