EPICS Component Reference Manual
The table record holds a column-oriented table: a set of named columns, each an array of up to MAXROWS elements of a per-column data type. It is intended to be served over PV Access as a pvData NTTable; the bundled QSRV2 (pvxs) integration publishes the record's columns, labels and timestamp as the structured fields of an NTTable.
Rather than a single value field, the record exposes each column as a bundle of fixed-name fields, in the same spirit as the aSub record's A..U inputs. A column nn (hex 00..3F) is described by CnnNAME, CnnLABEL, CnnTYPE and CnnINP, with its data held in CnnVAL and its runtime status in CnnCHGD and CnnNROWS. Up to 64 such data columns may be configured.
A second family of up to 64 optional columns (COnn...) is provided for per-column metadata: each optional column carries exactly one value per data column (so it is sized to NUMCOLS rows, not MAXROWS) and is published alongside the table, typically as a column attribute.
Columns are configured statically in the database (or filled in by device support) and validated once, when the record initializes; thereafter the column layout is fixed and only the column data changes as the record processes.
The record-specific fields are described below, grouped by function. The 64 data columns share an identical set of per-column fields, named with the column index 00..3F embedded in the field name (for example C00NAME, C01NAME, ..., C3FNAME); only the column 00 fields are tabulated here, the rest follow the same pattern. The 64 optional columns are named CO00.....CO3F... in the same way.
The table record has the standard fields for specifying under what circumstances it will be processed. These fields are listed in Scan Fields.
INP is an optional input link for the record as a whole. Device support may use it (for example, the simulation and CSV device supports read their table description or contents through INP); the Soft Channel support ignores it and instead reads each column's own CnnINP link.
MAXROWS is the row capacity allocated for every data column. It must be set before initialization (it is read-only afterwards); if left at zero, init_record forces it to 1. Each data column's value buffer is allocated to hold MAXROWS elements.
NUMCOLS and NUMOPTCOLS report how many data and optional columns are active. They are not set by the user: init_record computes them by counting the configured (non-empty) column names, and they are read-only (SPC_NOMOD).
Field Summary Type DCT Default Read Write CA PP INP Input Specification INLINK Yes Yes Yes No MAXROWS Max number of rows per column ULONG No 1 Yes No No NUMCOLS Number of active columns (set by device support) ULONG No Yes No No NUMOPTCOLS Number of active optional columns (set by device support) ULONG No Yes No No
Each data column is configured by four fields and exposes three more at runtime:
CnnNAME - the column name. A column is "present" if its name is non-empty. Names must match [A-Za-z_][A-Za-z0-9_]*, must be unique across data columns, and must be contiguous (no gaps): column nn may only be named if all lower-numbered columns are also named. These rules are checked by init_record, which rejects the record (logging an error) if any is violated. The name is fixed after initialization (SPC_NOMOD) and becomes the field label of the corresponding NTTable column.
CnnLABEL - an optional human-readable column label carried alongside the name. If empty, consumers typically fall back to the name.
CnnTYPE - the column's element type, chosen from the menuFtype menu (STRING, CHAR, SHORT, LONG, DOUBLE, ...). Defaults to DOUBLE. It is read-only after initialization, since the value buffer is allocated for this type. STRING columns use a variable-length string encoding (see "Variable Strings") so cells may hold strings longer than the usual 39-character limit; PV Access clients receive the full string, while Channel Access clients see a truncated preview for strings exceeding 39 characters.
CnnINP - an optional per-column input link. The Soft Channel device support reads the column's data from this link (a constant array, a database link, or a channel-access link).
CnnVAL - the column data buffer, an array of MAXROWS elements of type CnnTYPE. It is process-passive (pp(TRUE)): a dbPutField to it triggers processing. Reading or writing it through Channel Access / dbAccess uses the column's element type and reports CnnNROWS elements as the current length.
CnnNROWS - the number of valid rows currently in the column (0..MAXROWS), maintained by device support / array puts. Read-only.
CnnCHGD - a "changed this cycle" flag. It is cleared at the start of process and set when the column's data is updated, so a publisher can send only the columns that actually changed.
Field Summary Type DCT Default Read Write CA PP C00NAME Column 00 name STRING [40] Yes Yes No No C00LABEL Column 00 label STRING [40] Yes Yes Yes No C00TYPE Column 00 field type MENU (menuFtype) Yes DOUBLE Yes No No C00INP Column 00 input link INLINK Yes Yes Yes No C00VAL Column 00 data Set by C00TYPE[MAXROWS] No Yes Yes Yes C00NROWS Column 00 number of valid rows ULONG No Yes No No C00CHGD Column 00 changed flag UCHAR No Yes Yes No
Optional columns hold one value per data column rather than MAXROWS rows; an optional column's buffer is therefore sized to NUMCOLS elements. They are intended for per-column metadata (for example a units or description attribute for each data column) that is published together with the table.
They follow the same bundle pattern and the same validation rules as data columns, with one addition: an optional column name may contain a single dotted component, prefix.field (matching [A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?), which lets a consumer map the optional column onto a nested attribute. COnnNAME and COnnTYPE are fixed after initialization; COnnVAL, COnnNROWS and COnnCHGD behave as for data columns.
Field Summary Type DCT Default Read Write CA PP CO00NAME Optional column 00 name STRING [40] Yes Yes No No CO00TYPE Optional column 00 field type MENU (menuFtype) Yes DOUBLE Yes No No CO00INP Optional column 00 input link INLINK Yes Yes Yes No CO00VAL Optional column 00 data Set by CO00TYPE[NUMCOLS] No Yes Yes Yes CO00NROWS Optional column 00 number of valid rows ULONG No Yes No No CO00CHGD Optional column 00 changed flag UCHAR No Yes Yes No
Called twice (pass 0 then pass 1). On pass 0 it:
Verifies that device support is attached: the DSET must be present, and must declare at least 5 routines with a non-NULL read_table. If not, an error is logged and initialization fails.
Forces MAXROWS to 1 if it was left at 0.
Calls the device support's init_record (if any) so it can allocate memory and fill in column names/types. If that routine returns TABLEREC_DEVINIT_PASS1 the record arranges to call it again in pass 1.
Validates the data column names (format, uniqueness, no gaps as described above) and sets NUMCOLS to the number found. The optional column names are validated the same way and NUMOPTCOLS is set.
Allocates any column value buffers not already provided by device support: MAXROWS elements for each data column, NUMCOLS elements for each optional column.
On pass 1, if device support requested it, the device init_record is called once more (now able to read links and other records).
See "Record Processing" below.
CnnVAL and COnnVAL accept array puts via special. Before the data lands (after=0), a STRING column's previous variable-length cells are released; after the put (after=1), the column's CHGD flag is set. Puts to inactive columns are rejected. The column NAME and TYPE fields are read-only at run time (SPC_NOMOD) and cannot be changed once the record has initialized.
These present each CnnVAL/COnnVAL field as an array whose element type is the column's TYPE, whose capacity is MAXROWS (data) or NUMCOLS (optional columns), and whose current length is the column's NROWS.
Routine process implements the following algorithm:
If device support or its read_table routine is missing, set PACT TRUE, log an error and return, so processing is not retried (avoiding error storms).
Clear the CHGD flag of every active data and optional column.
Call the device support read_table routine to refresh the column data. If it succeeds, clear UDF.
Update the time stamp and reset alarms.
Post monitor events for the column data.
If a publisher has registered a notify hook in RPVT (as the QSRV2 NTTable source does), call it synchronously while the record lock is held and this cycle's CHGD flags are still valid.
Scan the forward link, set PACT FALSE and return.
Device support obtains a fresh table whenever read_table is called, and is chiefly interested in the column buffers and counts.
Field Summary Type DCT Default Read Write CA PP PACT Record active UCHAR No Yes No No DPVT Device Private NOACCESS No No No No UDF Undefined UCHAR Yes 1 Yes Yes Yes NUMCOLS Number of active columns (set by device support) ULONG No Yes No No NUMOPTCOLS Number of active optional columns (set by device support) ULONG No Yes No No MAXROWS Max number of rows per column ULONG No 1 Yes No No
A table device support entry table (tabledset) extends the standard DSET with one routine:
typedef struct {
dset common; /* standard 5-entry DSET */
long (*read_table)(struct tableRecord *prec);
} tabledset;
read_table must refresh the active column buffers (and their NROWS) and return 0 on success. Device support that needs to allocate buffers or populate column names/types does so from common.init_record. Because reading links is only safe after all records exist, support that must read links at init returns TABLEREC_DEVINIT_PASS1 from its pass-0 init_record to ask the record to call it again in pass 1.
The Soft Channel support (devTableSoft) implements a table whose columns are driven by their individual CnnINP / COnnINP links. At initialization it loads any constant links into the column buffers; on each read_table it re-reads the non-constant links. STRING columns are staged through a temporary buffer and re-encoded into variable-length cells so that numeric-to-string conversions cannot leave stale bytes in a cell.
EPICS strings are normally limited to MAX_STRING_SIZE bytes (40), leaving at most 39 usable characters. STRING-typed columns use a variable-length string encoding (defined in tableVStr.h) that lets device support store strings of arbitrary length, including strings with embedded NUL bytes.
Each 40-byte cell in a STRING column buffer is stored in one of three formats, discriminated by two sentinel bytes within the cell:
Type 1 - very short string (length ≤ 31, no embedded NUL): the string bytes are stored directly in the cell, NUL-terminated. Fully compatible with an ordinary DBF_STRING.
Type 2 - short string (32 ≤ length ≤ 39, no embedded NUL): the string bytes are stored directly in the cell, NUL-terminated. Also compatible with an ordinary DBF_STRING.
Type 3 - long string (length > 39, or embedded NUL): up to 31 bytes of a UTF-8-clean preview are stored in the cell inline; a pointer to a heap-allocated tablerecVStr block carrying the full string is stored in bytes 32..39 of the cell.
PV Access clients (NTTable) always receive the full string regardless of encoding type. Channel Access and dbAccess clients see the ordinary DBF_STRING cell: the complete content for Types 1 and 2, and only the 31-byte UTF-8 preview for Type 3.
Type 3 cells can only be produced by device support code. The C API in tableVStr.h provides:
void tablerec_vstr_write(void *colbuf, epicsUInt32 row,
const char *bytes, epicsUInt32 len);
The C++ wrapper in tableRecordUtil.h provides write_string_column and vstr_write_cell as higher-level helpers. External writes via caput, dbpf, or PV Access are accepted but the DBF_STRING type caps them at 39 characters, so they produce at most a Type 2 cell.
Overflow heap blocks are freed automatically whenever a cell is overwritten or cleared. The special() handler also frees any overflow pointer before an external put can overwrite a cell, preventing leaks during normal IOC operation. All access to a STRING column buffer must be done while holding the record lock (dbScanLock / process / dbPutField).