Repository navigation
Stack prepared batches by column name: values were stored in the wrong column - #123
Conversation
A flush merges a measurement's prepared Arrow batches with concat_batches, which stacks columns by position under the first batch's schema. Two writes with different fields (used, then free) have the same column count and type, so free's values were stored under used and free was null; series with different tags swapped tag values the same way. Seen whenever a measurement's first flush mixes field sets. Batches are now stacked by name: a column a batch lacks is null (a field) or "" (a tag), and a type that differs widens as ingest does. The series batch is also padded to the table's tags before insert, as fact batches already were, keeping any tag the plan does not list yet.
PR SummaryHigh Risk Overview Merge behavior in Insert alignment: prepared series batches are Unit tests cover name-based merge, type widening, and series padding; a compat test flushes mixed-field/tag line protocol and asserts query results. Reviewed by Cursor Bugbot for commit e6b2df8. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e6b2df8. Configure here.
| (Dictionary(_, _) | Utf8, Dictionary(_, _) | Utf8) => Utf8, | ||
| _ => a.clone(), | ||
| } | ||
| } |
There was a problem hiding this comment.
Unsigned and float widen incorrectly
Medium Severity
wider_type promotes Float64 and UInt64 to Float64, but ingest widening via widen_field_disc treats those types as incompatible and keeps the stored type. A flush that stacks a uint batch with a float batch can then disagree with the metadata/table type. pad_record_batch_to_ensured clones the widened column without casting, so RecordBatch::try_new fails and the flush errors, or unsigned values are stored as floats when ingest would have nulled them.
Reviewed by Cursor Bugbot for commit e6b2df8. Configure here.
There was a problem hiding this comment.
Fixed in b20c5da. wider_type now widens only the pairs ingest's widen_field_disc widens: float over signed integer, and unsigned over signed. Float and unsigned keep the first type. Also, pad_record_batch_to_ensured now casts every field column to the table's type, which is the authority, with values that can't convert becoming null. A column of a different type used to fail RecordBatch::try_new, and the flush retried it forever. That gap predates this PR, but it's the same path. Unit tests: float_and_unsigned_keep_the_first_type and a_prepared_column_is_cast_to_the_tables_type. The compat and sharding suites pass.
Float and unsigned are not widened into each other at ingest, so the batch merge no longer does it either. And a prepared column whose type differs from the table's (built before the field widened, or merged) is cast to the table's type when padded: a mismatch failed the batch, and a flush retries until it succeeds.
There was a problem hiding this comment.
Stale comment
Left a non-blocking comment; this PR is not approved. Cursor Bugbot reported an unresolved medium-severity finding, and that thread is still open after the follow-up commit. Human review is needed, and no reviewers were assigned.
Sent by Cursor Approval Agent: Pull Request Router and Approver




Data corruption, in 0.9.2-beta and earlier. When a measurement's first flush contains writes with different fields, values are stored in the wrong column. For example,
m,host=a used=1thenm,host=a free=2, within one flush interval, stores the2underusedand leavesfreenull. Series with different tags swap tag values the same way. It reproduces 3/3 on a single node and on a sharded cluster. The SQL insert path (HYPERBYTEDB_DISABLE_ARROW_INSERT=1) stores it correctly.Cause. A flush merges a measurement's prepared Arrow batches with
concat_batchesunder the first batch's schema, which stacks columns by position.[…, used]and[…, free]have the same column count and type, sofree's values went underused. The comment said it only stacks batches with identical schemas, but nothing made them identical. Once the table exists, later writes are usually prepared with every column, which is why this mostly hits a measurement's first flush.Fix.
""for a tag, which is what the series table stores for an absent tag. A type that differs widens as ingest does: float over integer, unsigned over signed, string over dictionary.Tests.
-D warnings): all green. A first version of the padding dropped tags the plan didn't list. The sharding suite caught that, and this version keeps them.Found while testing the partial-write fix (#122); independent of it.