{"record":{"id":"b8942582e4c45009","repo":"langfuse/langfuse","slug":"invalid-relationtable-relationtablename-must","errorCode":null,"errorMessage":"Invalid relationTable: ${relationTableName}. Must be one of ${Object.keys(view.tableRelations)}","messagePattern":"Invalid relationTable: (.+?)\\. Must be one of (.+?)","errorType":"exception","errorClass":"InvalidRequestError","httpStatus":400,"severity":"error","filePath":"packages/shared/src/features/query/server/queryBuilder.ts","lineNumber":803,"sourceCode":"    // Replace each @@AGGN@@ placeholder with its corresponding value\n    for (const [placeholder, replacement] of Object.entries(aggs)) {\n      const marker = `@@${placeholder.toUpperCase()}@@`;\n      result = result.replaceAll(marker, replacement);\n    }\n    return result;\n  }\n\n  private buildJoins(\n    relationTables: Set<string>,\n    view: ViewDeclarationType,\n    filterList: FilterList,\n    query: QueryType,\n    skipObservationsFinal: boolean,\n  ) {\n    const relationJoins = [];\n    for (const relationTableName of relationTables) {\n      if (!(relationTableName in view.tableRelations)) {\n        throw new InvalidRequestError(\n          `Invalid relationTable: ${relationTableName}. Must be one of ${Object.keys(view.tableRelations)}`,\n        );\n      }\n\n      const relation = view.tableRelations[relationTableName];\n      // Conditionally add FINAL - skip for observations if flag is set, and respect per-relation useFinal\n      const shouldUseFinal =\n        (relation.useFinal ?? true) &&\n        !(relation.name === \"observations\" && skipObservationsFinal);\n      const alias =\n        relation.name !== relationTableName ? ` AS ${relationTableName}` : \"\";\n      let joinStatement = `INNER JOIN ${relation.name}${alias}${shouldUseFinal ? \" FINAL\" : \"\"} ${relation.joinConditionSql}`;\n\n      // Create time dimension mapping for the relation table\n      const relationTimeDimensionMapping = {\n        uiTableName: relation.timeDimension,\n        uiTableId: relation.timeDimension,\n        clickhouseTableName: relation.name,","sourceCodeStart":785,"sourceCodeEnd":821,"githubUrl":"https://github.com/langfuse/langfuse/blob/59d92c7cf365150d10b753b5a0d1708902a2ed60/packages/shared/src/features/query/server/queryBuilder.ts#L785-L821","documentation":"Thrown by buildJoins when a query requests a relation table that is not registered in view.tableRelations for the current entity. The join layer only knows a fixed set of relation tables per view.","triggerScenarios":"Passing relationTables containing an unknown table name (e.g. \"scores\" on a view whose tableRelations lacks it) into QueryBuilder.build.","commonSituations":"Hand-constructing QueryBuilder options instead of using helper constructors; version skew where a relation exists in one view but not another; typos in relation names.","solutions":["Use only relation names listed in the error (Object.keys(view.tableRelations))","Construct queries via the provided helper functions rather than assembling relationTables manually"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const valid = new Set(Object.keys(view.tableRelations));\nconst safe = relationTables.filter(r => valid.has(r));","typeGuard":"const isValidRelationTable = (r: string, view: QueryView) => r in view.tableRelations;","tryCatchPattern":null,"preventionTips":["Use QueryBuilder helper constructors instead of hand-assembling relationTables","Keep relation names in a shared constant list"],"tags":["query-builder","joins","relation-table","validation"],"backgroundTag":"invalid-table-reference","analyzedSha":"59d92c7cf365150d10b753b5a0d1708902a2ed60","analyzedAt":"2026-08-27T22:22:00.402Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}