Skip to content

Pivot fillWithValue incorrectly replaces legitimate zero measure values #10225

@fgrund

Description

@fgrund

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

  1. Create a ResultSet with data that includes actual zero values
  2. Call tablePivot() or chartPivot() with fillWithValue: '-' (or any non-zero value)
  3. Observe that actual 0 values 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions