FieldedText TypeScript Library
    Preparing search index...

    Read Sequence with Ordinals Example

    This example demonstrates using field ordinals (indices) for faster reading of large files with sequences.

    Deserializes sequence metadata from an XML string and reads the same pet data as read-sequence example, but uses ordinals instead of field names for better performance.

    An ordinal is the index of a field in the current record. For example, in a record with fields [Type, Name, RunningSpeed], the ordinals are:

    • Type = 0
    • Name = 1
    • RunningSpeed = 2

    When accessing fields by name (e.g., reader.getFieldByName("Type")), the library must:

    1. Search the field list for a matching name
    2. Return the field at that index

    When using ordinals (e.g., reader.fieldList.get(0)), you:

    1. Access the field directly by index ✅

    For large files with many records, this performance difference adds up significantly.

    1. getFieldIndexByName(fieldName): Look up a field's index by name (once per table)
    2. Ordinal Caching: Calculate ordinals once, reuse for all records in a table
    3. Table Boundaries: Ordinals become invalid when table changes (new sequence structure)
    4. nextResult(): Move to next table and recalculate ordinals
    5. Meta Deserialization: Load metadata from XML text with FtXmlMetaSerialization.deserialize()
    npx tsx examples/read-sequence-ordinal/index.ts
    
    Reading pets with sequences using ordinals (faster):

    1,1: 1,Misty,45
    2,1: 1,Oscar,35
    3,2: 2,Buddy,0.5,35,false
    4,3: 2,Charlie,2,48,true,John,32
    5,4: 2,Max,0.5,30,false
    6,5: 3,Bubbles,Orange,Wen
    7,5: 3,Flash,Yellow,Crucian

    Note: Using ordinals is faster than field names for large files
    because it avoids name lookup on every field access.

    The first number is record count, second is table count.

    Use ordinals when:

    • ✅ Processing large files (millions of records)
    • ✅ Performance is critical
    • ✅ You can calculate ordinals once per table

    Use field names when:

    • ✅ File is small
    • ✅ Code readability is more important than performance
    • ✅ Prototyping or one-off scripts
    let recOrdinals: number[] | null = null;

    do {
    while (reader.read()) {
    if (recOrdinals === null) {
    // Calculate ordinals once per table
    recOrdinals = calculateRecordOrdinals(reader);
    }

    // Use ordinals for fast access
    const value = reader.fieldList.get(recOrdinals[i]).value;
    }

    // Reset ordinals when table changes
    recOrdinals = null;
    } while (reader.nextResult());
    // Read Sequence with Ordinals Example
    // Demonstrates using field ordinals for faster reading of large files with sequences
    // An ordinal is the index of a field in the current record

    import { FtReader, FtXmlMetaSerialization } from "@pbkware/fielded-text-web";

    // Sample CSV data with sequences
    const csvData = `1,Misty,45
    1,Oscar,35
    2,Buddy,0.5,35,False
    2,Charlie,2,48,True,John,32
    2,Max,0.5,30,False
    3,Bubbles,Orange,Wen
    3,Flash,Yellow,Crucian`;

    // Define Type values
    const CatType = BigInt(1);
    const DogType = BigInt(2);
    const GoldFishType = BigInt(3);

    // Define Field Names
    const TypeFieldName = "Type";
    const NameFieldName = "Name";
    const RunningSpeedFieldName = "RunningSpeed";
    const WalkDistanceFieldName = "WalkDistance";
    const TrainingFieldName = "Training";
    const TrainerFieldName = "Trainer";
    const SessionCostFieldName = "SessionCost";
    const ColorFieldName = "Color";
    const ChineseClassificationFieldName = "ChineseClassification";

    // Load metadata from XML string
    const metaXml = `<?xml version="1.0" encoding="utf-8"?>
    <FieldedText>
    <Field DataType="Integer" Name="Type" />
    <Field Id="1" Name="Name" />
    <Field DataType="Float" Id="2" Name="RunningSpeed" />
    <Field DataType="Float" Id="3" Name="WalkDistance" />
    <Field DataType="Boolean" Id="4" Name="Training" />
    <Field Id="5" Name="Trainer" />
    <Field DataType="Decimal" Id="6" Name="SessionCost" />
    <Field Id="7" Name="Color" />
    <Field Id="8" Name="ChineseClassification" />
    <Sequence Name="Root" Root="True">
    <Item FieldIndex="0">
    <Redirect SequenceName="Cat" InvokationDelay="AfterSequence" Value="1" />
    <Redirect SequenceName="Dog" InvokationDelay="AfterSequence" Value="2" />
    <Redirect SequenceName="GoldFish" InvokationDelay="AfterSequence" Value="3" />
    </Item>
    <Item FieldIndex="1" />
    </Sequence>
    <Sequence Name="Cat">
    <Item FieldIndex="2" />
    </Sequence>
    <Sequence Name="Dog">
    <Item FieldIndex="3" />
    <Item FieldIndex="2" />
    <Item FieldIndex="4">
    <Redirect SequenceName="Training" InvokationDelay="AfterField" Value="True" />
    </Item>
    </Sequence>
    <Sequence Name="GoldFish">
    <Item FieldIndex="7" />
    <Item FieldIndex="8" />
    </Sequence>
    <Sequence Name="Training">
    <Item FieldIndex="5" />
    <Item FieldIndex="6" />
    </Sequence>
    </FieldedText>`;

    const meta = FtXmlMetaSerialization.deserialize(metaXml);

    // Helper function to calculate ordinals for the current record
    function calculateRecordOrdinals(reader: FtReader): number[] {
    const maxFieldCount = 7;
    const recOrdinals = new Array<number>(maxFieldCount);

    // Read root sequence fields
    const typeOrdinal = reader.getFieldIndexByName(TypeFieldName)!;
    recOrdinals[0] = typeOrdinal;
    recOrdinals[1] = reader.getFieldIndexByName(NameFieldName)!;

    let fieldCount: number;

    // Type field determines which sequence is active
    const type = reader.fieldList.get(typeOrdinal).asBigInt;
    if (type === CatType) {
    recOrdinals[2] = reader.getFieldIndexByName(RunningSpeedFieldName)!;
    fieldCount = 3;
    } else if (type === DogType) {
    recOrdinals[2] = reader.getFieldIndexByName(WalkDistanceFieldName)!;
    recOrdinals[3] = reader.getFieldIndexByName(RunningSpeedFieldName)!;
    recOrdinals[4] = reader.getFieldIndexByName(TrainingFieldName)!;
    const training = reader.fieldList.get(recOrdinals[4]).asBoolean;
    if (!training) {
    fieldCount = 5;
    } else {
    recOrdinals[5] = reader.getFieldIndexByName(TrainerFieldName)!;
    recOrdinals[6] = reader.getFieldIndexByName(SessionCostFieldName)!;
    fieldCount = 7;
    }
    } else if (type === GoldFishType) {
    recOrdinals[2] = reader.getFieldIndexByName(ColorFieldName)!;
    recOrdinals[3] = reader.getFieldIndexByName(
    ChineseClassificationFieldName,
    )!;
    fieldCount = 4;
    } else {
    fieldCount = 2;
    }

    return recOrdinals.slice(0, fieldCount);
    }

    // Create reader
    const reader = new FtReader(meta, csvData);

    console.log("Reading pets with sequences using ordinals (faster):\n");

    // Track ordinals - reset when table changes
    let recOrdinals: number[] | null = null;

    do {
    // Loop for each table in file
    while (reader.read()) {
    if (recOrdinals === null) {
    // Ordinals only need to be calculated for first row of each table
    // They remain valid for all records in the same table
    recOrdinals = calculateRecordOrdinals(reader);
    }

    // Access fields by ordinal (faster than by name)
    const values: unknown[] = [];
    for (const ordinal of recOrdinals) {
    values.push(reader.fieldList.get(ordinal).value);
    }

    console.log(
    `${reader.recordCount},${reader.tableCount}: ${values.join(",")}`,
    );
    }

    // Ordinals are no longer valid after table ends
    recOrdinals = null;
    } while (reader.nextResult());

    console.log(
    "\nNote: Using ordinals is faster than field names for large files",
    );
    console.log("because it avoids name lookup on every field access.");