{"record":{"id":"540e9d160b47e960","repo":"Tencent/WeKnora","slug":"opensearch-transport-error","errorCode":null,"errorMessage":"opensearch: transport error","messagePattern":"opensearch: transport error","errorType":"error_code","errorClass":"AppError","httpStatus":503,"severity":"error","filePath":"internal/application/repository/retriever/opensearch/errors.go","lineNumber":29,"sourceCode":"var (\n\t// ErrIndexNotFound — alias / underlying index missing. Search and\n\t// delete-by-query operations return this when the per-dim alias has\n\t// not been created yet (no Save has been issued for that dim).\n\tErrIndexNotFound = errors.New(\"opensearch: index not found\")\n\n\t// ErrDimensionMismatch — embedding dimension violates the per-dim\n\t// invariant (e.g. dim <= 0, dim > 16000, or embeddings within a\n\t// single batch disagree).\n\tErrDimensionMismatch = errors.New(\"opensearch: embedding dimension mismatch\")\n\n\t// ErrAuth — cluster returned 401 / 403. Distinguished from ErrTransport\n\t// so the service layer can map to a clean 4xx instead of 503.\n\tErrAuth = errors.New(\"opensearch: authentication failed\")\n\n\t// ErrTransport — network / 5xx / opaque cluster error. Classified as\n\t// transient: ensureReady does NOT persist this in initErr, so the next\n\t// caller will retry.\n\tErrTransport = errors.New(\"opensearch: transport error\")\n\n\t// ErrVersionUnsupported — cluster is not OpenSearch, is OS 1.x, or is\n\t// OS 2.0~2.3 (pre-Lucene-HNSW-GA). probeVersion enforces.\n\tErrVersionUnsupported = errors.New(\"opensearch: cluster version unsupported\")\n\n\t// ErrConfigInvalid — IndexConfig / storeID / sanitizeIndexName guard\n\t// failed, or the k-NN plugin is missing on one or more cluster nodes.\n\tErrConfigInvalid = errors.New(\"opensearch: invalid index config\")\n\n\t// ErrFeatureNotEnabled — stubs.go returns this from methods whose real\n\t// implementation has not landed yet (CopyIndices / BatchUpdateChunk* /\n\t// swapToVersion, plus the read/write methods that a follow-up commit\n\t// will replace with production code).\n\tErrFeatureNotEnabled = errors.New(\"opensearch: feature not enabled in this build\")\n\n\t// ErrBatchTooLarge — Save / Delete batch exceeded the driver's sync\n\t// cap. Distinct from ErrFeatureNotEnabled so the service layer can\n\t// chunk + retry rather than treat the failure as \"waiting on a future","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/repository/retriever/opensearch/errors.go#L11-L47","documentation":"A SubPlan node was found during AST validation. SubPlan is an internal planner representation that never appears in raw parsed user SQL; encountering one means the input is not a plain parse tree (pre-planned or forged input), so the validator rejects it defensively as part of its default-deny posture.","triggerScenarios":"Passing a parsed/planner-processed query tree (not a freshly pg_query-parsed statement) into validateNode, or crafted protobuf input containing a SubPlan node.","commonSituations":"Caching parse trees across planner stages; feeding plans (EXPLAIN output trees) rather than parse trees into the validator; security research probing the validator with hand-built nodes.","solutions":["Ensure the validated input comes directly from pg_query.Parse of raw SQL, never from planner output or a persisted plan tree.","Drop any cache layer that stores post-parse/planner node trees and re-parse the raw SQL instead.","Reject at the input boundary: validate that submitted SQL is plain text before parsing."],"exampleFix":"// before\nplan := planner.Plan(sql)\nerr := validator.ValidateNodeTree(plan)\n\n// after\ntree, _ := pg_query.Parse(sql)\nerr := validator.ValidateNodeTree(tree)","handlingStrategy":"type-guard","validationCode":"func isFreshParseTree(tree *pg_query.ParseResult, rawSQL string) bool {\n\t// only validate trees produced directly from parsing rawSQL in this request\n\treturn tree != nil && parseCacheOrigin[tree] == rawSQL\n}","typeGuard":"if _, ok := node.Node.(*pg_query.Node_SubPlan); ok {\n\treturn false // not a raw parse-tree node; reject input source\n}","tryCatchPattern":"if err := validator.ValidateQuery(rawSQL); err != nil {\n\tif strings.Contains(err.Error(), \"SubPlan\") {\n\t\treturn fmt.Errorf(\"input must be raw SQL text, not a plan tree: %w\", err)\n\t}\n}","preventionTips":["Only pass output of pg_query.Parse on raw SQL text to the validator.","Never cache or persist post-planner node trees for validation.","Keep the parse->validate step synchronous within one request."],"tags":["sql","security","ast","default-deny"],"backgroundTag":"sql-validation-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}