Read all column info for empty SAS data files - #384
Conversation
|
The change can also be validated by the SAS files that ReadStat produces itself. So a test like this should in theory work: hpoettker@e14ea64 In practice, it turns out that the chopped column information is a bigger problem for the test framework than for the library itself. A lot of assertions on columns are tested before one can be sure that all column data has been loaded. The suspicion falls on the test framework as the dumped buffer from the failing test can be read correctly with the command line utility I'll have a deeper look but it might take some time. |
ed4cf75 to
a649cb2
Compare
|
I've found a different and perhaps even more elegant solution for the problem. The root cause of the issue is that the SAS column name subheaders are currently not always read. One particular case are multi-page SAS data files without any rows that have column name subheaders beyond the first page. The new proposal is to move the parsing of the column name subheaders from the second to the first pass. This is possible as parsing the column name subheaders does not depend on the column text subheaders having been parsed before. This also resolves the issue in the testing framework as it does its assertions in the second pass and can now depend on the column name subheaders to be parsed. The added test fails on the current dev branch and succeeds with the proposed change. |
|
I asked Fable 5 to review, would appreciate an update based on the findings Code Review: PR #365 — SAS7BDAT moved-rows / REFERENCE-pointer supportReview of WizardMac/ReadStat#365, ranked most severe first. The top items were verified directly against the code. Memory safety1. Out-of-bounds heap read in the NONE_MOVED branch
Failure scenario: A crafted file has a REFERENCE (0x03) pointer routing to a target page whose subheader pointer has 2. Read failure on the moved-to page goes undetected
Failure scenario: A custom Correctness3. Duplicate rows plus silent truncation
Nothing tracks which moved subheaders a REFERENCE pointer has already consumed. Two REFERENCE pointers resolving to the same 4. Loud errors became silent row loss (
|
|
Thanks for the review on #365! It's posted here in the wrong PR but it's of course clear from the title of the comment which PR it refers to. If you could post a Fable review for this PR as well that would be very much appreciated. This PR addresses the only remaining issue that I've found while reading SAS data files in my personal use case. There are some more issues in reading SAS catalog files but that's of lower priority to me at the moment. |
Human error :-) Here is a Fable 5.1 review of this PR, hot off the presses.... Code Review: PR #384 — Move parsing of SAS column name subheader to first passReview of WizardMac/ReadStat#384 (head SummaryThe PR moves Correctness1. Column attributes, formats and labels are still dropped for 0-row multi-page files
The root cause the PR description identifies — pass 2 breaks out after the first page because The PR's own test file demonstrates it. Page layout of the 64-bit file the test writes (page size 4096):
Reading that file back through the PR build, every one of the 80 variables comes out as For the pyreadstat use case that motivated the PR, this means a wide empty file now gets correct column names but wrong dtypes (everything object/string), zero widths, and no labels or formats for every column whose metadata spilled past page 0. Suggested fix. Skip the early exit when there are no rows to find: - if (ctx->parsed_row_count == ctx->row_limit)
+ if (ctx->row_limit > 0 && ctx->parsed_row_count == ctx->row_limit)
break;Verified on base + this change alone (without the pass-1 move): the PR's new test passes; a variant with labels, 2. Pass 1 visits trailing AMD pages in reverse order, and column-name assignment is order-dependent
Tests and quality3.
|
a649cb2 to
d032578
Compare
|
Thanks a lot! I've rebased on I've applied the suggestion from point 1, which makes points 2 and 5 obsolete. I addressed point 4 by adding labels and formats to the test columns. This also helped with point 3 as the metadata now spills over with only 20 columns instead of 80 columns. The size of the |
This PR fixes a problem that I've observed with empty SAS data files that have a very high number (literally thousands) of columns.
An empty SAS data file can extend over multiple pages if the number of columns is high enough. When for such a file a column name header is on the second page or beyond, this header is currently not being read. The root cause is an early exit for the second pass that exits when the number of expected rows has been reached, which is always after the first page for empty data files.
In ReadStat the bug manifests itself in blank column names. Downstream in pyreadstat the bug produces errors due to column names with value
Nonewhich pyreadstat isn't (and shouldn't be) prepared to handle.