parseMultiWithOutputs static method

({List<MultiResultItem> items, List<ParamValue> outputParamValues}) parseMultiWithOutputs(
  1. Uint8List data
)

Parses a buffer that was produced by the DRT1 + multi-result engine path: a MULT envelope (v2) followed by an optional OUT1 footer.

This is used when a directed OUT call (executeQueryDirectedParams) returns additional result sets or row-counts from SQLMoreResults in addition to the primary result set and scalar OUT / INOUT values.

Returns a record containing the parsed items and any output parameter values. Throws FormatException on malformed input.

Implementation

static ({List<MultiResultItem> items, List<ParamValue> outputParamValues})
    parseMultiWithOutputs(Uint8List data) {
  if (data.length < _headerV2Len) {
    throw const FormatException(
      'Buffer too small for MULT + OUT1 header',
    );
  }
  final firstWord =
      ByteData.sublistView(data, 0, 4).getUint32(0, _littleEndian);
  if (firstWord != multiResultMagic) {
    throw FormatException(
      'Expected MULT magic 0x${multiResultMagic.toRadixString(16)}, '
      'got 0x${firstWord.toRadixString(16)}',
    );
  }
  final byteData = ByteData.sublistView(data);
  final version = byteData.getUint16(4, _littleEndian);
  if (version != multiResultVersionV2) {
    throw FormatException(
      'Unsupported multi-result version: $version '
      '(expected $multiResultVersionV2)',
    );
  }
  final itemCount = byteData.getUint32(8, _littleEndian);

  // Parse items and track consumed bytes so we can locate OUT1.
  final items = <MultiResultItem>[];
  var offset = _headerV2Len;
  for (var i = 0; i < itemCount; i++) {
    if (offset + itemHeaderSize > data.length) {
      throw const FormatException(
        'Multi-result buffer truncated at item header',
      );
    }
    final tag = data[offset];
    offset += 1;
    if (tag != tagResultSet && tag != tagRowCount) {
      throw FormatException('Unknown multi-result item tag: $tag');
    }
    final length = byteData.getUint32(offset, _littleEndian);
    offset += 4;
    if (offset + length > data.length) {
      throw const FormatException(
        'Multi-result buffer truncated at item payload',
      );
    }
    final payload = data.sublist(offset, offset + length);
    switch (tag) {
      case tagResultSet:
        items.add(
          MultiResultItemResultSet(BinaryProtocolParser.parse(payload)),
        );
      case tagRowCount:
        if (length != 8) {
          throw const FormatException(
            'RowCount item expected 8-byte payload',
          );
        }
        items.add(
          MultiResultItemRowCount(byteData.getInt64(offset, _littleEndian)),
        );
    }
    offset += length;
  }

  // Trailing OUT1 footer (optional) — reuse BinaryProtocolParser internals
  // by calling parseWithOutputs on a synthetic single-result header-less
  // slice is not possible, but the OUT1 structure is simple enough to parse
  // inline: magic(4) + count(4) + repeated ParamValue payloads.
  final outputs = <ParamValue>[];
  if (offset + 8 <= data.length) {
    final trailMagic =
        byteData.getUint32(offset, _littleEndian);
    if (trailMagic == BinaryProtocolParser.outputFooterMagic) {
      offset += 4;
      final n = byteData.getUint32(offset, _littleEndian);
      offset += 4;
      for (var i = 0; i < n; i++) {
        final d = deserializeParamValue(data, offset: offset);
        outputs.add(d.value);
        offset += d.consumed;
      }
    }
  }

  return (items: items, outputParamValues: outputs);
}