fetchRows<T> method

List<T> fetchRows<T>(
  1. String query,
  2. T rowMapper(
    1. List<Object?>
    )
)

Return data back and convert each row to a type T, using the rowMapper function.

Implementation

List<T> fetchRows<T>(String query, T Function(List<Object?>) rowMapper) {
  var q = query.toNativeUtf8().cast<Char>();
  var resultPtr = calloc<duckdb_result>();
  if (bindings.duckdb_query(ptrCon.value, q, resultPtr) ==
      duckdb_state.DuckDBError) {
    throw StateError(
        bindings.duckdb_result_error(resultPtr).cast<Utf8>().toDartString());
  }

  var out = <T>[];
  var chunkCount = bindings.duckdb_result_chunk_count(resultPtr.ref);
  for (var chunk = 0; chunk < chunkCount; chunk++) {
    var chunkPtr = bindings.duckdb_result_get_chunk(resultPtr.ref, chunk);
    var rowCount = bindings.duckdb_data_chunk_get_size(chunkPtr);
    var colCount = bindings.duckdb_data_chunk_get_column_count(chunkPtr);

    var rs =
        List.generate(rowCount, (i) => List<Object?>.filled(colCount, null));

    for (var j = 0; j < colCount; j++) {
      var vector = bindings.duckdb_data_chunk_get_vector(chunkPtr, j);
      var logicalType = bindings.duckdb_vector_get_column_type(vector);
      var typeId = bindings.duckdb_get_type_id(logicalType);
      var values = bindings.duckdb_vector_get_data(vector);
      var validity = bindings.duckdb_vector_get_validity(vector);

      switch (typeId) {
        case DUCKDB_TYPE.DUCKDB_TYPE_BOOLEAN:
          var xs = values.cast<Bool>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_TINYINT:
          var xs = values.cast<Int8>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_SMALLINT:
          var xs = values.cast<Int16>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_INTEGER:
          var xs = values.cast<Int32>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_BIGINT:
          var xs = values.cast<Int64>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_UTINYINT:
          var xs = values.cast<Uint8>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_USMALLINT:
          var xs = values.cast<Uint16>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_UINTEGER:
          var xs = values.cast<Uint32>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_UBIGINT:
          var xs = values.cast<Uint64>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_FLOAT: // 4 bytes
          var xs = values.cast<Float>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_DOUBLE: // 8 bytes
          var xs = values.cast<Double>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE
              .DUCKDB_TYPE_TIMESTAMP: // UTC DateTime, microsecond precision
          var xs = values.cast<Int64>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_DATE: // number of days since 1970-01-01
          var xs = values.cast<Int32>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_VARCHAR:
          // see test 'Test DataChunk varchar result fetch in C API'
          // https://github.com/duckdb/duckdb/blob/main/test/api/capi/test_capi_data_chunk.cpp#L260
          var xs = values.cast<duckdb_string_t>();
          for (var i = 0; i < rowCount; i++) {
            if (!bindings.duckdb_validity_row_is_valid(validity, i)) continue;
            final tuple = xs[i];
            if (bindings.duckdb_string_is_inlined(tuple)) {
              // The data is small enough to fit in the string_t, it does not have a separate allocation
              var array = tuple.value.inlined.inlined;
              final charCodes = <int>[];
              var k = 0;
              while (array[k] != 0) {
                charCodes.add(array[k]);
                k++;
              }
              rs[i][j] = String.fromCharCodes(charCodes);
            } else {
              rs[i][j] = tuple.value.pointer.ptr.cast<Utf8>().toDartString();
            }
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_DECIMAL:

          /// https://github.com/Giorgi/DuckDB.NET/blob/8520bf5005d9309f762ef61d71412d60d24ca32c/DuckDB.NET.Data/Internal/Reader/DecimalVectorDataReader.cs#L43
          var type = bindings.duckdb_decimal_internal_type(logicalType);
          var scale = bindings.duckdb_decimal_scale(logicalType);
          for (var i = 0; i < rowCount; i++) {
            switch (type) {
              case DUCKDB_TYPE.DUCKDB_TYPE_SMALLINT ||
                    DUCKDB_TYPE.DUCKDB_TYPE_INTEGER:
                var xs = values.cast<Int32>();
                if (!bindings.duckdb_validity_row_is_valid(validity, i)) {
                  continue;
                }
                rs[i][j] = (Decimal.fromInt(xs[i]) /
                        Decimal.ten.pow(scale).toDecimal())
                    .toDecimal();
              case DUCKDB_TYPE.DUCKDB_TYPE_BIGINT:
                var xs = values.cast<Int64>();
                if (!bindings.duckdb_validity_row_is_valid(validity, i)) {
                  continue;
                }
                rs[i][j] = (Decimal.fromInt(xs[i]) /
                        Decimal.ten.pow(scale).toDecimal())
                    .toDecimal();
              case _:
                throw StateError('Unsupported decimal type $type');
            }
          }

        case DUCKDB_TYPE
              .DUCKDB_TYPE_TIMESTAMP_S: // UTC DateTime, second precision
          var xs = values.cast<Int64>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : DateTime.fromMillisecondsSinceEpoch(xs[i] * 1000,
                    isUtc: true);
          }

        case DUCKDB_TYPE
              .DUCKDB_TYPE_TIMESTAMP_TZ: // return the number of microseconds
          var xs = values.cast<Int64>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : xs[i];
          }

        case DUCKDB_TYPE
              .DUCKDB_TYPE_TIMESTAMP_MS: // UTC DateTime, millisecond precision
          var xs = values.cast<Int64>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : DateTime.fromMillisecondsSinceEpoch(xs[i], isUtc: true);
          }

        case DUCKDB_TYPE
              .DUCKDB_TYPE_TIMESTAMP_NS: // UTC DateTime, nanosecond precision
          var xs = values.cast<Int64>();
          for (var i = 0; i < rowCount; i++) {
            rs[i][j] = !bindings.duckdb_validity_row_is_valid(validity, i)
                ? null
                : DateTime.fromMillisecondsSinceEpoch(xs[i] ~/ 1000,
                    isUtc: true);
          }

        case DUCKDB_TYPE.DUCKDB_TYPE_ENUM:
          // there are several internal types for ENUMs based on the size
          // of the dictionary (uint8_t, uint16_t, uint32_t)
          // See https://github.com/duckdb/duckdb/blob/main/test/api/capi/test_capi_complex_types.cpp#L52
          var enumInternalType =
              bindings.duckdb_enum_internal_type(logicalType);
          for (var i = 0; i < rowCount; i++) {
            if (!bindings.duckdb_validity_row_is_valid(validity, i)) continue;
            // get the index of the enum dictionary for this row
            late int idx;
            if (enumInternalType == DUCKDB_TYPE.DUCKDB_TYPE_UTINYINT) {
              idx = (values as Pointer<Uint8>)[i];
            } else if (enumInternalType ==
                DUCKDB_TYPE.DUCKDB_TYPE_USMALLINT) {
              idx = (values as Pointer<Uint16>)[i];
            } else if (enumInternalType == DUCKDB_TYPE.DUCKDB_TYPE_UINTEGER) {
              idx = (values as Pointer<Uint32>)[i];
            }
            rs[i][j] = bindings
                .duckdb_enum_dictionary_value(logicalType, idx)
                .cast<Utf8>()
                .toDartString();
          }

        default:
          throw StateError('TypeId $typeId has not been mapped yet');
      }
      // clean the column
      var ptr = calloc<duckdb_logical_type>();
      ptr.value = Pointer.fromAddress(logicalType.address);
      bindings.duckdb_destroy_logical_type(ptr);
    }

    for (var i = 0; i < rowCount; i++) {
      out.add(rowMapper(rs[i]));
    }

    // clean the chunk
    var ptr = calloc<duckdb_data_chunk>();
    ptr.value = Pointer.fromAddress(chunkPtr.address);
    bindings.duckdb_destroy_data_chunk(ptr);
  }

  bindings.duckdb_destroy_result(resultPtr);
  return out;
}