Skip to content

Ergonomic optional columns: Add _opt methods to Buffer #133

@aaaronme

Description

@aaaronme

Is your feature request related to a problem? Please describe.

When using the Buffer API to insert rows, the fluent builder pattern is incredibly ergonomic. However, when working with real-world data structs that contain nullable fields (represented as Option<T> in Rust), developers are forced to break the method chain to use if let statements.

Because QuestDB treats missing columns as NULL, we have to conditionally call the column methods.

Example of the current pain point:

let mut buffer = Buffer::new(ProtocolVersion::V3);

// We must break the chain to handle optional values
let mut row = buffer.table("sensors")?
      .symbol("location", "factory-1")?;

if let Some(temp) = sensor.temperature {
    row.column_f64("temperature", temp)?;
}

if let Some(hum) = sensor.humidity {
    row.column_f64("humidity", hum)?;
}

row.at_now()?;

Describe the solution you'd like

I propose adding _opt variants for all column assignment methods directly to the Buffer struct (e.g., column_f64_opt, column_str_opt, symbol_opt, column_dec_opt, etc.).

If the provided value is Some(v), the method behaves exactly like its standard counterpart. If the value is None, it simply returns Ok(self) as a no-op, allowing the builder chain to continue unbroken.

The resulting developer experience:

let mut buffer = Buffer::new(ProtocolVersion::V3);

buffer
    .table("sensors")?
    .symbol("location", "factory-1")?
    .column_f64_opt("temperature", sensor.temperature)? // Option<f64>
    .column_f64_opt("humidity", sensor.humidity)?       // Option<f64>
    .at_now()?;

Implementation Details

The new methods would reuse the exact same generic constraints currently used in the Buffer API (e.g., N: TryInto<ColumnName<'a>>) to ensure zero performance degradation and maintain compatibility with pre-validated names.

Example signature for strings:

pub fn column_str_opt<'a, N, S>(&mut self, name: N, value: Option<S>) -> crate::Result<&mut Self>
where
    N: TryInto<ColumnName<'a>>,
    S: AsRef<str>,
    Error: From<N::Error>,
{
    if let Some(v) = value {
        self.column_str(name, v)
    } else {
        Ok(self)
    }
}

Additional context

I have already built this locally using a custom extension trait and it massively cleaned up my ingestion code. I think this would be highly beneficial for the broader community.

If the team is open to this API addition, I would be more than happy to open a PR adding these methods!

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