executeQueryAsMaps method

  1. @override
Future<List<Map<String, dynamic>>> executeQueryAsMaps(
  1. JsonQuery query
)
override

Execute a query and deserialize results to maps.

If the query includes relations via include, results are automatically deserialized into nested structures using the RelationDeserializer.

Implementation

@override
Future<List<Map<String, dynamic>>> executeQueryAsMaps(JsonQuery query) async {
  // Compile to get relation metadata
  final sqlQuery = compiler.compile(query);

  // Execute the query
  final result = await _executeWithLogging(
    sql: sqlQuery.sql,
    parameters: sqlQuery.args,
    model: query.modelName,
    operation: query.action,
    execute: () => adapter.queryRaw(sqlQuery),
  );

  // Check if we need to deserialize relations
  final hasRelations =
      sqlQuery.hasRelations && sqlQuery.relationMetadata is CompiledRelations;

  // Convert to maps - preserve aliases when relations are present so
  // the RelationDeserializer can match column aliases like "user__name"
  final flatMaps = _resultSetToMaps(result, preserveAliases: hasRelations);

  if (hasRelations) {
    final compiledRelations = sqlQuery.relationMetadata as CompiledRelations;

    // Use relation deserializer to nest flat JOIN results
    final deserializer = RelationDeserializer(
      schema: compiler.schema ?? schemaRegistry,
    );

    final deserialized = deserializer.deserialize(
      rows: flatMaps,
      baseModel: query.modelName,
      columnAliases: compiledRelations.columnAliases,
      includedRelations: compiledRelations.includedRelations,
    );

    // Preserve computed fields that were lost during relation deserialization.
    // Computed fields are added to the SELECT clause but not registered in
    // columnAliases, so they get dropped by _extractBaseColumns().
    // We need to copy them back from the flat maps.
    //
    // Note: This relies on primary keys to match rows. Models without primary
    // keys (extremely rare in Prisma) won't have computed fields preserved.
    // This is consistent with RelationDeserializer which has the same limitation.
    if (sqlQuery.computedFieldNames.isNotEmpty && flatMaps.isNotEmpty) {
      final model =
          (compiler.schema ?? schemaRegistry).getModel(query.modelName);
      if (model != null && model.primaryKeys.isNotEmpty) {
        // Support composite primary keys (@@id([field1, field2]))
        final pkColumns =
            model.primaryKeys.map((pk) => pk.columnName).toList();

        // Helper to generate a composite primary key string from a row map.
        String getPkValue(Map<String, dynamic> row) {
          return pkColumns.map((c) => row[c]?.toString() ?? '').join('::');
        }

        // Group flat maps by primary key to match with deserialized results.
        // Keep only the first occurrence for each PK (same as deserializer).
        final flatMapByPk = <String, Map<String, dynamic>>{};
        for (final row in flatMaps) {
          flatMapByPk.putIfAbsent(getPkValue(row), () => row);
        }

        // Copy computed field values from the original flat map to the
        // deserialized result.
        for (final resultRow in deserialized) {
          final flatRow = flatMapByPk[getPkValue(resultRow)];
          if (flatRow != null) {
            for (final fieldName in sqlQuery.computedFieldNames) {
              if (flatRow.containsKey(fieldName)) {
                resultRow[fieldName] = flatRow[fieldName];
              }
            }
          }
        }
      }
    }

    return deserialized;
  }

  return flatMaps;
}