fetch method

Map<String, List<Object?>> fetch(
  1. String query
)

A query that returns some data back (using the data chunks API).

Note: Can't use duckdb_row_count and duckdb_value_* functions when using data chunks. Data chunks is more performant and allows access the complex data structures, e.g. enums, list, struct, array.

Implementation

Map<String, List<Object?>> fetch(String query) {
  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 = <String, List<Object?>>{};

  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);
    final ids = List.generate(rowCount, (i) => i);

    var colCount = bindings.duckdb_data_chunk_get_column_count(chunkPtr);
    if (chunk == 0) {
      for (var j = 0; j < colCount; j++) {
        var name = bindings
            .duckdb_column_name(resultPtr, j)
            .cast<Utf8>()
            .toDartString();
        out[name] = <Object?>[];
      }
    }

    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);
      var name = bindings
          .duckdb_column_name(resultPtr, j)
          .cast<Utf8>()
          .toDartString();

      switch (typeId) {
        case DUCKDB_TYPE.DUCKDB_TYPE_BOOLEAN:
          var xs = values.cast<Bool>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_TINYINT:
          var xs = values.cast<Int8>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_SMALLINT:
          var xs = values.cast<Int16>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_INTEGER:
          var xs = values.cast<Int32>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_BIGINT:
          var xs = values.cast<Int64>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_UTINYINT:
          var xs = values.cast<Uint8>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_USMALLINT:
          var xs = values.cast<Uint16>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_UINTEGER:
          var xs = values.cast<Uint32>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_UBIGINT:
          var xs = values.cast<Uint64>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_FLOAT: // 4 bytes
          var xs = values.cast<Float>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_DOUBLE: // 8 bytes
          var xs = values.cast<Double>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE
              .DUCKDB_TYPE_TIMESTAMP: // UTC DateTime, microsecond precision
          var xs = values.cast<Int64>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull
                ? null
                : DateTime.fromMicrosecondsSinceEpoch(xs[i], isUtc: true);
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_DATE: // number of days since 1970-01-01
          var xs = values.cast<Int32>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_VARCHAR: // 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>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            if (isNull) return null;
            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 i = 0;
              while (array[i] != 0) {
                charCodes.add(array[i]);
                i++;
              }
              return String.fromCharCodes(charCodes);
            } else {
              return tuple.value.pointer.ptr.cast<Utf8>().toDartString();
            }
          }));

        case DUCKDB_TYPE.DUCKDB_TYPE_DECIMAL: // 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);
          switch (type) {
            case DUCKDB_TYPE.DUCKDB_TYPE_SMALLINT ||
                  DUCKDB_TYPE.DUCKDB_TYPE_INTEGER:
              var xs = values.cast<Int32>();
              out[name]!.addAll(ids.map((i) {
                final isNull =
                    !bindings.duckdb_validity_row_is_valid(validity, i);
                if (isNull) return null;
                var res = (Decimal.fromInt(xs[i]) /
                        Decimal.ten.pow(scale).toDecimal())
                    .toDecimal();
                // print(res);
                return res;
              }));
            case DUCKDB_TYPE.DUCKDB_TYPE_BIGINT:
              var xs = values.cast<Int64>();
              out[name]!.addAll(ids.map((i) {
                final isNull =
                    !bindings.duckdb_validity_row_is_valid(validity, i);
                if (isNull) return null;
                var res = (Decimal.fromInt(xs[i]) /
                        Decimal.ten.pow(scale).toDecimal())
                    .toDecimal();
                return res;
              }));
            case _:
              throw StateError('Unsupported decimal type $type');
          }

        case DUCKDB_TYPE
              .DUCKDB_TYPE_TIMESTAMP_S: // UTC DateTime, second precision
          var xs = values.cast<Int64>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull
                ? 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>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull ? null : xs[i];
          }));

        case DUCKDB_TYPE
              .DUCKDB_TYPE_TIMESTAMP_MS: // UTC DateTime, millisecond precision
          var xs = values.cast<Int64>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull
                ? null
                : DateTime.fromMillisecondsSinceEpoch(xs[i], isUtc: true);
          }));

        case DUCKDB_TYPE
              .DUCKDB_TYPE_TIMESTAMP_NS: // UTC DateTime, nanosecond precision
          var xs = values.cast<Int64>();
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            return isNull
                ? null
                : DateTime.fromMicrosecondsSinceEpoch(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);
          out[name]!.addAll(ids.map((i) {
            final isNull =
                !bindings.duckdb_validity_row_is_valid(validity, i);
            if (isNull) return null;
            // 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];
            }
            return bindings
                .duckdb_enum_dictionary_value(logicalType, idx)
                .cast<Utf8>()
                .toDartString();
          }));

        default:
          throw StateError('TypeId $typeId has not been mapped yet');
      }

      // cleaning
      var ptr = calloc<duckdb_logical_type>();
      ptr.value = Pointer.fromAddress(logicalType.address);
      bindings.duckdb_destroy_logical_type(ptr);
    }

    // cleaning
    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;
}