-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
Describe the bug
The fillWithValue pivot config option uses the || (logical OR) operator, which treats 0 as falsy. This causes actual zero values in the data to be replaced by fillWithValue, not just missing/undefined values.
Location: packages/cubejs-client-core/src/ResultSet.ts in the pivot logic:
const measureValue = (row: Record<string, any>, measure: string) => row[measure] || normalizedPivotConfig.fillWithValue || 0;Documentation note: The SDK documentation describes fillWithValue as "Value to autofill all the missing date's measure" and links it to fillMissingDates for time dimensions only. However, in practice fillWithValue also works for non-time dimension pivots (e.g., category × type). The documentation may be outdated, or this is undocumented behavior.
To Reproduce
- Create a ResultSet with data that includes actual zero values
- Call
tablePivot()orchartPivot()withfillWithValue: '-'(or any non-zero value) - Observe that actual
0values are replaced by the fillWithValue
const resultSet = // ... ResultSet with { category: 'A', value: 0 }
const pivoted = resultSet.tablePivot({
x: ['Cube.category'],
y: ['measures'],
fillWithValue: '-'
})
// Expected: { 'Cube.category': 'A', 'Cube.value': 0 }
// Actual: { 'Cube.category': 'A', 'Cube.value': '-' }Expected behavior
Only null and undefined values should be replaced by fillWithValue. Actual 0 values in the data should be preserved.
Screenshots
N/A
Minimally reproducible Cube Schema
cube(`TestCube`, {
sql: `
select 'A' as category, 'X' as type, 10 as value
UNION ALL
select 'A' as category, 'Y' as type, 0 as value
UNION ALL
select 'B' as category, 'X' as type, 30 as value
`,
measures: {
value: {
sql: `value`,
type: `sum`,
},
},
dimensions: {
category: {
sql: `category`,
type: `string`,
},
type: {
sql: `type`,
type: `string`,
},
},
});Query with pivot:
const pivotConfig = {
x: ['TestCube.category'],
y: ['TestCube.type', 'measures'],
fillWithValue: '-'
}
resultSet.tablePivot(pivotConfig)Row A should have { 'X,TestCube.value': 10, 'Y,TestCube.value': 0 } but instead has { 'X,TestCube.value': 10, 'Y,TestCube.value': '-' } because 0 is falsy.
Version:
Tested with @cubejs-client/core - the bug is in the core pivot logic in ResultSet.js, present in recent versions.
Additional context
Suggested fix: Use nullish coalescing ?? instead of ||:
// Before (buggy):
const measureValue = (row: Record<string, any>, measure: string) => row[measure] || normalizedPivotConfig.fillWithValue || 0;
// After (fixed):
const measureValue = (row: Record<string, any>, measure: string) => row[measure] ?? normalizedPivotConfig.fillWithValue ?? 0;The ?? operator only falls through for null and undefined, preserving actual 0 values.
Impact: Users cannot use fillWithValue to show "no data" indicators (like - or N/A) without also replacing actual zeros in their data. This is a common use case when displaying pivot tables with sparse data.