Skip to content

fix(schema-compiler): Throw error for duplicate cube and view names in JS models#10909

Open
paveltiunov wants to merge 1 commit into
masterfrom
cursor/fix-js-duplicate-cube-view-names-d5ac
Open

fix(schema-compiler): Throw error for duplicate cube and view names in JS models#10909
paveltiunov wants to merge 1 commit into
masterfrom
cursor/fix-js-duplicate-cube-view-names-d5ac

Conversation

@paveltiunov
Copy link
Copy Markdown
Member

Check List

  • Tests have been run in packages where changes have been made if available
  • Linter has been run for changed code
  • Tests for the changes have been added if not covered yet
  • Docs have been added / updated if required

Description of Changes Made

Previously, when two cubes or views shared the same name in JS data model files, the later definition would silently overwrite the earlier one. This was already fixed for YAML models (in YamlCompiler.ts) but not for JS models.

This PR adds duplicate name detection in CubeDictionary.compile() which runs during stage 0 of compilation (the first phase that processes all collected cube/view definitions). It catches:

  • Duplicate cube names — same name used twice for cubes (within same file or across files)
  • Duplicate view names — same name used twice for views (within same file or across files)
  • Conflicting cube and view names — a cube and a view sharing the same name

The detection is placed in CubeDictionary because it's the first compiler to receive the complete list of cubes/views regardless of source format (JS or YAML). This provides a unified safety net for all model types.

Error messages:

  • Found duplicate cube name '<name>'.
  • Found duplicate view name '<name>'.
  • Found conflicting cube and view name '<name>'.

Tests added: 5 new test cases covering single-file duplicates, cross-file duplicates, and cube/view name conflicts.

Open in Web Open in Cursor 

…n JS models

Previously, when two cubes or views shared the same name in JS data model
files, the later definition would silently overwrite the earlier one. This
was already fixed for YAML models but not for JS models.

This adds duplicate name detection in CubeDictionary.compile() which runs
during stage 0 of compilation, catching:
- Duplicate cube names (same name used twice for cubes)
- Duplicate view names (same name used twice for views)
- Conflicting cube and view names (a cube and view sharing the same name)

The check works for both single-file and cross-file duplicates.

Co-authored-by: Pavel Tiunov <pavel.tiunov@gmail.com>
@github-actions github-actions Bot added the javascript Pull requests that update Javascript code label May 19, 2026
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 19, 2026

Claude finished @paveltiunov's task in 2m 34s —— View job


Code Review: PR #10909

  • Read changed files and gather context
  • Review CubeDictionary.ts changes
  • Review test coverage in schema.test.ts
  • Check for edge cases and potential issues
  • Post review feedback

Note: Unable to run tests due to missing tool permissions (npx jest). Review is based on code analysis.


Summary

Clean, well-scoped fix that adds duplicate cube/view name detection for JS models by placing the check in CubeDictionary.compile() — the right location since it's the first compiler to see the complete, merged list of cubes/views from all source formats (JS and YAML). The logic is simple, correct, and the tests cover the key scenarios.

Detailed Review

Code QualityCubeDictionary.ts changes (lines 20–28)

The implementation is concise and correct. The Map.has() check before set() catches duplicates with O(1) overhead. The existing ErrorReporter.error() deduplication (it skips identical messages at ErrorReporter.ts:143-145) prevents double-reporting when YAML in-file duplicates are caught by both YamlCompiler.checkDuplicateNames() and this new check.

One minor observation:

After reporting the duplicate error, the code still executes this.byId.set(cube.name, cube) (line 30), silently overwriting the earlier definition. This is fine in practice because throwIfAnyErrors() will prevent successful compilation. However, if omitErrors is true on the DataSchemaCompiler, duplicates will still be silently overwritten — same as before, so no regression, but worth noting.

Error messages — The messages are clear and consistent with the existing YAML duplicate messages. One improvement to consider for a future PR: including file names in the error message (e.g., "Found duplicate cube name 'orders' (defined in orders1.js and orders2.js)") would help users locate the conflict faster, especially in large projects with many model files. Not a blocker.

Testing — 5 tests covering:

Scenario Covered
Duplicate cube names, single file
Duplicate cube names, across files
Duplicate view names, single file
Cube/view name conflict, single file
Cube/view name conflict, across files

Test structure uses try/catch with a throw-on-success pattern, which is reasonable for error-path testing in this codebase.

Potential gaps (non-blocking):

  • No test for duplicate view names across files (only single-file view duplicates are tested)
  • No cross-format test (JS cube + YAML cube with same name) — the mechanism would catch it since CubeDictionary is format-agnostic, but a test would be good documentation

Security — No concerns. This is a compile-time check with no user input flowing to dangerous sinks.

Performance — Negligible. Single Map.has() + Map.get() per cube during compilation.

Verdict

Looks good. The placement in CubeDictionary is the right architectural choice — it provides a unified safety net for all model formats. The minor gaps in test coverage (cross-file view duplicates, cross-format duplicates) are non-blocking since the underlying mechanism is identical for all cases.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 19, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 58.41%. Comparing base (3d59c59) to head (ad238d7).
⚠️ Report is 6 commits behind head on master.

❗ There is a different number of reports uploaded between BASE (3d59c59) and HEAD (ad238d7). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (3d59c59) HEAD (ad238d7)
cubesql 1 0
Additional details and impacted files
@@             Coverage Diff             @@
##           master   #10909       +/-   ##
===========================================
- Coverage   83.47%   58.41%   -25.07%     
===========================================
  Files         254      216       -38     
  Lines       75490    16956    -58534     
  Branches        0     3440     +3440     
===========================================
- Hits        63014     9904    -53110     
+ Misses      12476     6551     -5925     
- Partials        0      501      +501     
Flag Coverage Δ
cube-backend 58.41% <100.00%> (?)
cubesql ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@paveltiunov paveltiunov marked this pull request as ready for review May 20, 2026 06:18
@paveltiunov paveltiunov requested a review from a team as a code owner May 20, 2026 06:18
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 20, 2026

Claude finished @paveltiunov's task in 2m 17s —— View job


Code Review: PR #10909

  • Read changed files and gather full context
  • Review CubeDictionary.ts implementation
  • Review test coverage in schema.test.ts
  • Check existing duplicate detection in YamlCompiler.ts for consistency
  • Assess edge cases and potential issues
  • Post comprehensive review

Summary

Looks good. Clean, well-scoped fix that adds duplicate cube/view name detection for JS models by placing the check in CubeDictionary.compile() — the right architectural location since it's the first compiler (stage 0 via cubeNameCompilers) to see the complete, merged list of cubes/views from all source formats.

Key Findings

Architecture (positive): The YamlCompiler.checkDuplicateNames() catches within-file YAML duplicates during transpilation, while this new CubeDictionary check catches cross-file duplicates and JS duplicates during stage 0 compilation. For within-file YAML duplicates, both checks fire, but ErrorReporter.error() deduplicates identical messages (ErrorReporter.ts:143), so no double-reporting occurs.

Code quality: Implementation is concise — Map.has() before set() with O(1) overhead. The three-way detection (duplicate cube, duplicate view, conflicting cube+view) is handled cleanly.

Minor observations (all non-blocking, posted as inline comments):

  1. After reporting the duplicate error, the later definition still overwrites the earlier one. Fine because throwIfAnyErrors() halts compilation, but under omitErrors: true the last-definition-wins behavior persists (same as pre-PR, no regression).
  2. File names could be included in error messages for better DX — cube.fileName is already available on the objects.
  3. Test pattern could use expect(...).rejects.toThrow(...) instead of try/catch for idiomatic Jest style.
  4. Missing test for duplicate view names across files and cross-format (JS+YAML) duplicates.

Security: No concerns. Compile-time check with no user input flowing to dangerous sinks.

Performance: Negligible. Single Map.has() + Map.get() per cube during compilation.

Verdict

Approve. The inline comments are all non-blocking suggestions for future improvement.

Comment on lines +20 to +28
if (errorReporter && this.byId.has(cube.name)) {
const existing = this.byId.get(cube.name)!;
const existingType = existing.isView ? 'view' : 'cube';
const newType = cube.isView ? 'view' : 'cube';
if (existingType === newType) {
errorReporter.error(`Found duplicate ${newType} name '${cube.name}'.`);
} else {
errorReporter.error(`Found conflicting cube and view name '${cube.name}'.`);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: After reporting the duplicate error, line 30 still executes this.byId.set(cube.name, cube), silently overwriting the earlier definition with the later one. This is fine in practice because throwIfAnyErrors() will halt compilation. However, if omitErrors is true (used in some contexts), duplicates are swallowed and the last-definition-wins behavior persists — same as before this PR, so no regression, but worth a brief comment or a continue to make the intent explicit.

Also worth noting: the YamlCompiler.checkDuplicateNames() catches within-file YAML duplicates during the transpile phase, while this new check catches cross-file duplicates (and JS duplicates) during stage 0 compilation. For YAML within-file duplicates, both checks will fire, but ErrorReporter.error() deduplicates identical messages (at ErrorReporter.ts:143), so no double-reporting occurs. Good design.

One future improvement to consider: including file names in the error message (e.g., Found duplicate cube name 'orders' (defined in orders1.js and orders2.js)) would help users locate conflicts faster in large projects. The cube.fileName and existing.fileName are already available on the objects. Not a blocker for this PR.

Comment on lines +1388 to +1393
try {
await compiler.compile();
throw new Error('compile must return an error');
} catch (e: any) {
expect(e.message).toContain("Found duplicate cube name 'orders'");
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: The try/catch + throw new Error('compile must return an error') pattern works but is fragile — if the error message changes slightly, the toContain still passes but the test doesn't verify the compile actually failed for the right reason.

Consider using expect(...).rejects.toThrow(...) which is the idiomatic Jest pattern for async error assertions:

Suggested change
try {
await compiler.compile();
throw new Error('compile must return an error');
} catch (e: any) {
expect(e.message).toContain("Found duplicate cube name 'orders'");
}
await expect(compiler.compile()).rejects.toThrow("Found duplicate cube name 'orders'");

This is cleaner and avoids the risk of a false-positive if compile() throws a different error that happens to contain the substring. Same applies to the other four tests in this describe block. Non-blocking.

expect(e.message).toContain("Found conflicting cube and view name 'orders'");
}
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage (non-blocking):

  1. Duplicate view names across files — only single-file view duplicates are tested (line 1422). A cross-file view duplicate test would round out the matrix:

    it('detects duplicate view names across JS files', async () => { ... });
  2. Cross-format duplicates (JS cube + YAML cube with same name) — the CubeDictionary check is format-agnostic, so it would catch this, but a test would serve as documentation that this is intentional.

Neither is a blocker since the underlying Map.has() check is identical for all cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update Javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants