{"record":{"id":"f6394ae2cfc3a8b7","repo":"cube-js/cube","slug":"members-invalidmembers-join-in-join-from","errorCode":null,"errorMessage":"Members ${invalidMembers.join(', ')} in join from '${j.originalFrom}' to '${j.originalTo}' doesn't reference join cubes","messagePattern":"Members (.+?) in join from '(.+?)' to '(.+?)' doesn't reference join cubes","errorType":"validation","errorClass":"UserError","httpStatus":null,"severity":"error","filePath":"packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts","lineNumber":1119,"sourceCode":"    if (fromPreAggObj.length > 1) {\n      throw new UserError(\n        `Multiple rollups found that can be used for rollup join ${JSON.stringify(join)}: ${fromPreAggObj.map(p => this.preAggregationId(p)).join(', ')}`,\n      );\n    }\n    return fromPreAggObj[0];\n  }\n\n  private resolveJoinMembers(join: FinishedJoinTree): JoinEdgeWithMembers[] {\n    const joinMap = new Set<string>();\n\n    return join.joins.map(j => {\n      joinMap.add(j.originalFrom);\n\n      const memberPaths = this.query.collectMemberNamesFor(() => this.query.evaluateSql(j.originalFrom, j.join.sql)).map(m => m.split('.'));\n\n      const invalidMembers = memberPaths.filter(m => !joinMap.has(m[0]) && m[0] !== j.originalTo);\n      if (invalidMembers.length) {\n        throw new UserError(`Members ${invalidMembers.join(', ')} in join from '${j.originalFrom}' to '${j.originalTo}' doesn't reference join cubes`);\n      }\n      const fromMembers = memberPaths.filter(m => joinMap.has(m[0])).map(m => m.join('.'));\n      if (!fromMembers.length) {\n        throw new UserError(`From members are not found in [${memberPaths.map(m => m.join('.')).join(', ')}] for join ${JSON.stringify(j)}. Please make sure join fields are referencing dimensions instead of columns.`);\n      }\n      const toMembers = memberPaths.filter(m => m[0] === j.originalTo).map(m => m.join('.'));\n      if (!toMembers.length) {\n        throw new UserError(`To members are not found in [${memberPaths.map(m => m.join('.')).join(', ')}] for join ${JSON.stringify(j)}. Please make sure join fields are referencing dimensions instead of columns.`);\n      }\n      joinMap.add(j.originalTo);\n\n      return {\n        ...j,\n        fromMembers,\n        toMembers,\n      };\n    });\n  }","sourceCodeStart":1101,"sourceCodeEnd":1137,"githubUrl":"https://github.com/cube-js/cube/blob/7d981676b36392fec34088b9afab6bdcad40207c/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts#L1101-L1137","documentation":"Cube validates that every member referenced inside a rollupJoin's join SQL belongs to one of the cubes participating in the join chain. Members whose cube prefix is neither a join cube nor the join's 'to' cube are collected as invalidMembers and this UserError is thrown. It protects against typos or wrong-cube references in join conditions.","triggerScenarios":"A join's sql expression references a member like 'OtherCube.someDimension' where OtherCube is not in the join map (not originalFrom, not a prior join cube, and not originalTo), evaluated via collectMemberNamesFor + evaluateSql during rollupJoin processing.","commonSituations":"Copy-pasting a join SQL from another cube and forgetting to update the cube prefix; referencing a dimension from an unrelated cube in the ON condition; typos in cube names so the prefix doesn't match the join's cubes.","solutions":["Fix the join sql so every member uses a prefix matching either the join's from cube or the to cube (e.g. '${Orders}.id = ${Customers}.orders_id').","Add the referenced cube to the join chain if it genuinely participates in the relationship.","Correct typos in cube names inside the join sql."],"exampleFix":"// before\njoin: { sql: '${CUBE}.customer_id = ${Geo.customer}.id' } // Geo not in join\n// after\njoin: { sql: '${CUBE}.customer_id = ${Customers}.id' }","handlingStrategy":"validation","validationCode":"// Validate that every '${Cube}.member' in a join sql references only the join's cubes\nfunction validateJoinCubes(joinSql, allowedCubes) {\n  const refs = [...joinSql.matchAll(/\\$\\{([A-Za-z0-9_]+)\\./g)].map(m => m[1]);\n  const bad = refs.filter(c => !allowedCubes.includes(c));\n  if (bad.length) throw new Error(`Join references cubes not in join: ${[...new Set(bad)].join(', ')}`);\n}","typeGuard":"function joinUsesOnlyCubes(join, cubes) { return collectMemberNames(join.sql).every(name => cubes.includes(name.split('.')[0])); }","tryCatchPattern":"try { await cube.query(query); } catch (e) { if (/doesn't reference join cubes/.test(e.message)) { console.error('Join SQL references a cube outside the join chain:', e.message); } else throw e; }","preventionTips":["Only use ${Cube}.dimension references in join sql, never raw columns","Double-check cube prefixes when copying joins between cubes","Keep join definitions next to their cubes so prefixes are obvious","Run schema compile in CI to catch invalid join members before deploy"],"tags":["pre-aggregation","rollup-join","join-sql","member-reference"],"backgroundTag":"invalid-member-reference","analyzedSha":"7d981676b36392fec34088b9afab6bdcad40207c","analyzedAt":"2026-09-02T03:45:10.400Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}