fix(mongodb): offer query operators by filter position

This commit is contained in:
Loc Nguyen authored and GitHub committed 2026-09-14 17:53:17 +08:00
1 parent 14aeb93081
commit 608633edff
3 files changed
+77 -16

No files matched your search

+12 -6
View File
@@ -4,10 +4,11 @@ import {
COMMON_OPERATORS,
EXPRESSION_OPERATORS,
EXTENDED_JSON_VALUES,
FIELD_QUERY_OPERATORS,
PIPELINE_STAGES,
PUSH_MODIFIERS,
QUERY_OPERATORS,
STAGE_OPTION_KEYS,
TOP_LEVEL_QUERY_OPERATORS,
UPDATE_OPERATORS,
UPDATE_OPERATOR_LABELS,
VALUE_SNIPPETS,
@@ -34,6 +35,7 @@ export type MongoCompletionMode =
| "method"
| "cursorMethod"
| "field"
| "filterField"
| "fieldPath"
| "fieldRef"
| "value"
@@ -310,6 +312,10 @@ export function buildMongoCompletionItemsFromContext(context: MongoCompletionCon
case "field":
items = fieldItems(prefix, fields);
break;
case "filterField":
// Fields lead; `$and` / `$or` and the other whole-filter operators follow once `$` is typed.
items = [...fieldItems(prefix, fields), ...specItems(TOP_LEVEL_QUERY_OPERATORS, prefix, "query operator", 80)];
break;
case "fieldPath":
items = fieldPathItems(prefix, fields);
break;
@@ -325,7 +331,7 @@ export function buildMongoCompletionItemsFromContext(context: MongoCompletionCon
break;
case "queryOperator":
// `{ _id: { $oid: ... } }` is as valid here as `{ _id: { $gt: ... } }`.
items = [...specItems(QUERY_OPERATORS, prefix, "query operator", 100), ...specItems(EXTENDED_JSON_VALUES, prefix, "extended JSON value", 90)];
items = [...specItems(FIELD_QUERY_OPERATORS, prefix, "query operator", 100), ...specItems(EXTENDED_JSON_VALUES, prefix, "extended JSON value", 90)];
break;
case "updateOperator":
items = specItems(UPDATE_OPERATORS, prefix, "update operator", 100);
@@ -353,7 +359,7 @@ export function buildMongoCompletionItemsFromContext(context: MongoCompletionCon
/** Modes whose items are built from the target collection's sampled fields. */
export function mongoCompletionNeedsFields(mode: MongoCompletionMode): boolean {
return mode === "field" || mode === "fieldPath" || mode === "fieldRef" || mode === "expression";
return mode === "field" || mode === "filterField" || mode === "fieldPath" || mode === "fieldRef" || mode === "expression";
}
/** Modes whose items are built from the database's collection names. */
@@ -544,7 +550,7 @@ function classifyFilter(scan: MongoCallScan, rootIndex: number): MongoCompletion
if (inner.kind === "array") return VALUE_ARRAY_OPERATORS.has(inner.key ?? "") && !scan.inString ? "value" : "none";
if (inner.kind !== "object") return "none";
if (scan.inValue) return scan.inString ? "none" : "value";
if (innerDepth(scan, rootIndex) === 0) return "field";
if (innerDepth(scan, rootIndex) === 0) return "filterField";
// Inside a nested object: whose value is it?
switch (inner.key) {
@@ -552,10 +558,10 @@ function classifyFilter(scan: MongoCallScan, rootIndex: number): MongoCompletion
// An object inside an array: a sub-filter under `$and` / `$or` / `$nor`,
// or an extended JSON wrapper such as `{ $oid: ... }` under `$in`.
const parent = scan.stack[scan.stack.length - 2];
return parent?.kind === "array" && VALUE_ARRAY_OPERATORS.has(parent.key ?? "") ? "valueWrapper" : "field";
return parent?.kind === "array" && VALUE_ARRAY_OPERATORS.has(parent.key ?? "") ? "valueWrapper" : "filterField";
}
case "$elemMatch":
return "field";
return "filterField";
case "$expr":
return "expression";
case "$jsonSchema":
@@ -38,7 +38,11 @@ export function mongoOperatorItemType(apply: string): "snippet" | "keyword" {
return apply.includes("${") ? "snippet" : "keyword";
}
export const QUERY_OPERATORS: MongoOperatorSpec[] = specs([
/**
* Operators that constrain one field, valid inside `{ field: { ... } }`.
* `$and` / `$or` and friends are not: those go at the top level of a filter.
*/
export const FIELD_QUERY_OPERATORS: MongoOperatorSpec[] = specs([
["$eq", "Matches values equal to a value", "$eq: ${}"],
["$ne", "Matches values not equal to a value", "$ne: ${}"],
["$gt", "Matches values greater than a value", "$gt: ${}"],
@@ -47,18 +51,11 @@ export const QUERY_OPERATORS: MongoOperatorSpec[] = specs([
["$lte", "Matches values less than or equal to a value", "$lte: ${}"],
["$in", "Matches any value in an array", "$in: [${}]"],
["$nin", "Matches no value in an array", "$nin: [${}]"],
["$and", "Joins clauses with a logical AND", "$and: [${}]"],
["$or", "Joins clauses with a logical OR", "$or: [${}]"],
["$nor", "Joins clauses with a logical NOR", "$nor: [${}]"],
["$not", "Inverts the effect of a query expression", "$not: { ${} }"],
["$exists", "Matches documents that have the field", "$exists: true"],
["$type", "Matches documents by BSON type", '$type: "${string}"'],
["$regex", "Matches a regular expression", '$regex: "${pattern}"'],
["$text", "Performs a text search", '$text: { $search: "${text}" }'],
["$expr", "Uses aggregation expressions in a query", "$expr: { ${} }"],
["$jsonSchema", "Matches documents against a JSON schema", "$jsonSchema: { ${} }"],
["$mod", "Matches values by modulo division", "$mod: [${divisor}, ${remainder}]"],
["$where", "Matches with a JavaScript predicate", '$where: "${expression}"'],
["$all", "Matches arrays containing all the values", "$all: [${}]"],
["$elemMatch", "Matches arrays with an element matching all criteria", "$elemMatch: { ${} }"],
["$size", "Matches arrays of a given length", "$size: ${}"],
@@ -72,6 +69,23 @@ export const QUERY_OPERATORS: MongoOperatorSpec[] = specs([
["$nearSphere", "Matches points near a point on a sphere", "$nearSphere: { ${} }"],
]);
/**
* Operators that apply to the whole filter, valid at its top level and inside
* the sub-filters of `$and` / `$or` / `$nor` — never under a field.
*/
export const TOP_LEVEL_QUERY_OPERATORS: MongoOperatorSpec[] = specs([
["$and", "Joins clauses with a logical AND", "$and: [${}]"],
["$or", "Joins clauses with a logical OR", "$or: [${}]"],
["$nor", "Joins clauses with a logical NOR", "$nor: [${}]"],
["$expr", "Uses aggregation expressions in a query", "$expr: { ${} }"],
["$text", "Performs a text search", '$text: { $search: "${text}" }'],
["$where", "Matches with a JavaScript predicate", '$where: "${expression}"'],
["$jsonSchema", "Matches documents against a JSON schema", "$jsonSchema: { ${} }"],
]);
/** Every query operator, for callers that do not care about position. */
export const QUERY_OPERATORS: MongoOperatorSpec[] = [...FIELD_QUERY_OPERATORS, ...TOP_LEVEL_QUERY_OPERATORS];
export const UPDATE_OPERATORS: MongoOperatorSpec[] = specs([
["$set", "Sets field values", "$set: { ${} }"],
["$unset", "Removes fields", '$unset: { ${field}: "" }'],