{"record":{"id":"a46b679e29ff965d","repo":"appwrite/appwrite","slug":"general-query-invalid-a46b67","errorCode":"general_query_invalid","errorMessage":"$e->getMessage()","messagePattern":"\\$e->getMessage\\(\\)","errorType":"exception","errorClass":"Appwrite\\Extend\\Exception","httpStatus":400,"severity":"error","filePath":"src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php","lineNumber":118,"sourceCode":"        if ($collection->isEmpty()) {\n            throw new Exception($this->getParentNotFoundException(), params: [$collectionId]);\n        }\n\n        $hasRelationships = \\array_filter(\n            $collection->getAttribute('attributes', []),\n            fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP\n        );\n\n        if ($hasRelationships) {\n            throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk delete is not supported for ' . $this->getSDKNamespace() . ' with relationship attributes');\n        }\n\n        $originalQueries = $queries;\n\n        try {\n            $queries = Query::parseQueries($queries);\n        } catch (QueryException $e) {\n            throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());\n        }\n\n        // Handle transaction staging\n        if ($transactionId !== null) {\n            $transaction = $dbForProject->getDocument('transactions', $transactionId);\n            if ($transaction->isEmpty() || $transaction->getAttribute('status', '') !== 'pending') {\n                throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Invalid or non‑pending transaction');\n            }\n\n            // Enforce max operations per transaction\n            $maxBatch = $plan['databasesTransactionSize'] ?? APP_LIMIT_DATABASE_TRANSACTION;\n            $existing = $transaction->getAttribute('operations', 0);\n            if (($existing + 1) > $maxBatch) {\n                throw new Exception(\n                    Exception::TRANSACTION_LIMIT_EXCEEDED,\n                    'Transaction already has ' . $existing . ' operations, adding 1 would exceed the maximum of ' . $maxBatch\n                );\n            }","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/appwrite/appwrite/blob/a1d520eea492ebd1da1ef2dc8db5b2b34e277dce/src/Appwrite/Platform/Modules/Databases/Http/Databases/Collections/Documents/Bulk/Delete.php#L100-L136","documentation":"Query::parse_queries() failed to turn the incoming 'queries' strings into Query objects, and the QueryException message is forwarded verbatim as general_query_invalid. The bulk delete route receives queries as raw strings (one per array element), so any method name, argument shape, or value encoding that the Query parser does not recognize is rejected before the deleteDocuments call.","triggerScenarios":"Hand-written query strings like 'equal(\"status\", archived)' (missing quotes, wrong casing, unsupported method); building strings by concatenation instead of the SDK's Query helpers; passing a single comma-joined string instead of an array of method(...) strings; using query methods that are not allowed on this endpoint.","commonSituations":"Porting curl examples into code and mangling quoting; switching SDK versions where helper output format changed; storing query strings in config and editing them by hand until they drift from the parser grammar.","solutions":["Build queries with the SDK's Query class (Query.equal, Query.limit, Query.cursorAfter, ...) instead of hand-writing strings","Read the forwarded parser message - it names the exact method or argument that failed to parse","Keep within the documented limits (at most 100 query strings, each within the element size cap)","Log the exact queries array you send when this fires, and replay it through the parser mentally: method(\"attr\", [values])"],"exampleFix":"// before: hand-built query string with broken quoting\nawait sdk.databases.deleteDocuments(dbId, colId, ['equal(\"status\",archived)']);\n\n// after: let the SDK generate the query string\nawait sdk.databases.deleteDocuments(dbId, colId, [Query.equal('status', 'archived')]);","handlingStrategy":"validation","validationCode":"// always build query strings with the SDK helpers instead of hand-writing them\nimport { Query } from 'node-appwrite';\nconst queries = [Query.equal('status', 'archived'), Query.limit(1000)];\nawait sdk.databases.deleteDocuments(dbId, colId, queries);","typeGuard":"function isWellFormedQueryString(q: string): boolean {\n  // every element must look like method(\"attr\", [args]) as produced by the SDK Query helpers\n  return /^[a-zA-Z]+\\([^)]*\\)$/.test(q.trim()) && q.length <= 1000; // APP_LIMIT_ARRAY_ELEMENT_SIZE\n}","tryCatchPattern":"try {\n  await sdk.databases.deleteDocuments(dbId, colId, queries);\n} catch (e) {\n  if (e.code === 'general_query_invalid') {\n    // e.message is the parser's own diagnostic - fix the offending string it names\n    logger.error('query parse failed', { queries, detail: e.message });\n  }\n  throw e;\n}","preventionTips":["Generate queries only through the SDK Query class; never concatenate strings","Keep to the documented caps (query count and per-string length)","Add a unit test that asserts your stored query strings still parse after SDK upgrades"],"tags":["queries","validation","bulk-delete","databases"],"backgroundTag":"invalid-query-syntax","analyzedSha":"a1d520eea492ebd1da1ef2dc8db5b2b34e277dce","analyzedAt":"2026-08-18T21:34:56.994Z","contentChangedAt":"2026-08-18T21:34:56.994Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}