MCPcopy Create free account
hub / github.com/mikro-orm/mikro-orm / renderPartialIndexWhere

Function renderPartialIndexWhere

packages/sql/src/AbstractSqlDriver.ts:2970–3027  ·  view source on GitHub ↗

* Renders a `FilterQuery` predicate into a SQL fragment (without the `WHERE` keyword and * without table-alias prefixes) suitable for inlining into a partial-index DDL statement. * Used by `DatabaseTable.addIndex` when the user passes an object `where` on `@Index` / * `@Unique`. Strings are

(entityName: EntityName<T>, where: string | FilterQuery<T>)

Source from the content-addressed store, hash-verified

2968 * `@Unique`. Strings are returned unchanged.
2969 */
2970 renderPartialIndexWhere<T extends object>(entityName: EntityName<T>, where: string | FilterQuery<T>): string {
2971 if (typeof where === 'string') {
2972 return where;
2973 }
2974
2975 const name = Utils.className(entityName);
2976
2977 if (where == null || (Utils.isPlainObject(where) && Object.keys(where as Dictionary).length === 0)) {
2978 throw new Error(`Cannot render partial-index predicate for entity '${name}': \`where\` is empty.`);
2979 }
2980
2981 const alias = '__p';
2982 const qb = this.createQueryBuilder(entityName, undefined, undefined, undefined, undefined, alias);
2983 qb.where(where as any);
2984 const sql = qb.getFormattedQuery();
2985
2986 // Relation traversal produces join clauses whose aliased identifiers can't be inlined
2987 // into a CREATE INDEX ... WHERE clause — reject with a clear error rather than emitting broken DDL.
2988 if (/\bjoin\b/i.test(sql.split(/\bwhere\b/i)[0])) {
2989 throw new Error(
2990 `Cannot render partial-index predicate for entity '${name}': \`where\` may not traverse relations.`,
2991 );
2992 }
2993
2994 // Anchor at end-of-string only — the synthetic QB has no top-level order by / limit /
2995 // group by / having / offset, so any such keyword inside the captured predicate is
2996 // inside a subquery and must not terminate the match.
2997 const match = /\bwhere\s+([\s\S]+)$/i.exec(sql);
2998
2999 if (!match) {
3000 throw new Error(`Failed to render partial-index predicate for entity '${name}': ${sql}`);
3001 }
3002
3003 const quote = (s: string) => this.platform.quoteIdentifier(s);
3004 const aliasPrefix = new RegExp(`${quote(alias).replace(/[[\]]/g, '\\$&')}\\.`, 'g');
3005 const stripped = match[1].replace(aliasPrefix, '').trim();
3006
3007 // Any qualified column reference remaining after the alias strip points at another table or
3008 // subquery and can't be inlined into a CREATE INDEX ... WHERE predicate. Covers both
3009 // QB-generated sub-aliases (quoted, e.g. `"e0"."col"`) and raw fragments with bare refs
3010 // (e.g. `raw('other_table.col = 1')`). String literals are erased first so dots inside
3011 // them (e.g. JSON path operands like `'$.path'`) don't trip the guard.
3012 // Both patterns use a `(?!\s*\()` lookahead so schema-qualified function calls
3013 // (`pg_catalog.lower(name)`, `"public".my_func(col)`) are accepted — only `<id>.<id>` not
3014 // followed by `(` is treated as a cross-table column reference.
3015 const withoutStrings = stripped.replace(/'(?:[^']|'')*'/g, "''");
3016 const quotedIdent = String.raw`(?:"(?:[^"]|"")+"|\`(?:[^\`]|\`\`)+\`|\[(?:[^\]]|\]\])+\])`;
3017 const anyIdent = `(?:${quotedIdent}|[A-Za-z_]\\w*)`;
3018 const quotedCrossRef = new RegExp(`${quotedIdent}\\s*\\.\\s*${anyIdent}(?!\\s*\\()`);
3019 const bareCrossRef = /\b[A-Za-z_]\w*\s*\.\s*[A-Za-z_]\w*\b(?!\s*\()/;
3020 if (quotedCrossRef.test(withoutStrings) || bareCrossRef.test(withoutStrings)) {
3021 throw new Error(
3022 `Cannot render partial-index predicate for entity '${name}': \`where\` references another table or subquery which cannot be inlined into a CREATE INDEX ... WHERE clause.`,
3023 );
3024 }
3025
3026 return stripped;
3027 }

Callers

nothing calls this directly

Calls 8

classNameMethod · 0.80
isPlainObjectMethod · 0.80
getFormattedQueryMethod · 0.80
quoteFunction · 0.70
whereMethod · 0.65
testMethod · 0.65
keysMethod · 0.45
createQueryBuilderMethod · 0.45

Tested by

no test coverage detected