Reads a file written in standard qs2 format, that is, one saved with
qs2::qs_save or with qs2_write_atomic. The call goes straight to
qs2::qs_read.
Details
The function does not read the qdata format (qs2::qd_save). A qdata file
raises the qs2 error qdata format detected, use qs2::qd_read. swereg
writes no qdata file.
Stored objects from an older schema
The reader refuses a TTEEnrollment below schema 7 and a TTEPlan below schema 6. The error names the file. It says to rebuild the plan with s0 and re-run s1, then s2 and s3. The reader does not migrate such an object.
The refusal runs here, before the reader calls check_version(). A
deserialised R6 object keeps the method bodies it was saved with, so its
check_version() is the code of the release that saved it. Its panel also
keeps the old column names. A model fitted on that panel loses its calendar
term, and gives no error.
data.table over-allocation
The reader restores data.table over-allocation before it returns. qs2 does
not keep the over-allocated column slots, so a table read from disk has a
truelength() of 0. The first := on that table inside a function writes
to a shallow copy. The caller then never sees the new column, and
data.table reports nothing.
qs2_read() calls data.table::setalloccol() on every data.table it
reaches. It reaches the top-level object, every element of a plain list at
any depth, and every field of an R6 object, public or private. There is no
depth limit.
It enters nothing else. It returns everything it does not enter
unchanged, so a data.table held inside one of these keeps a
truelength() of 0:
an active binding, because reading one runs user code
a function, because its enclosure is an environment
a classed list, a
data.frameincluded, because[[<-dispatches therean environment that is not an R6 object, because a binding in one can hold a package namespace
any other object, an S4 object included
The walker visits a self-referential R6 object once. A plain list cannot refer to itself, because R copies a list on assignment.
Two figures, and which table gets which:
A SKELETON gets 4096 free column slots, held in
R/dt_alloc.Ras.DT_ALLOC_SPARE_SLOTS. A skeleton is the top-level object when that is a data.table, and the$datafield of a Skeleton. Theadd_*functions reserve the same number.Every OTHER data.table gets 1024, which is data.table's own default. A plan can hold thousands of small result tables, and one free slot costs 16 bytes whether it is used or not.
The reader does not read options(datatable.alloccol). Set that option and
it changes what [.data.table reserves, not what this reader restores.
The repair is cheap. setalloccol() allocates a new column-pointer header
and shares the column data by reference. It costs a few bytes per free
slot, not a copy of the table.
