compileWithRelations method

CompiledMutation compileWithRelations(
  1. JsonQuery query
)

Compile a mutation query with potential relation operations.

This is used when CREATE or UPDATE queries include connect or disconnect operations for many-to-many relations.

Example:

final query = JsonQueryBuilder()
    .model('SlotOfAppointment')
    .action(QueryAction.create)
    .data({
      'id': 'slot-123',
      'startsAt': DateTime.now(),
      'users': {
        'connect': [{'id': 'user-1'}, {'id': 'user-2'}],
      },
    })
    .build();

Implementation

CompiledMutation compileWithRelations(JsonQuery query) {
  final args = query.args.arguments ?? {};
  final data = args['data'] as Map<String, dynamic>? ?? {};

  // Extract relation operations from data
  final relationMutations = <SqlQuery>[];
  final cleanData = <String, dynamic>{};
  final effectiveSchema = schema ?? schemaRegistry;

  for (final entry in data.entries) {
    final value = entry.value;
    if (value is Map<String, dynamic> &&
        (value.containsKey('connect') || value.containsKey('disconnect'))) {
      // This is a relation operation - look up the relation info
      final relation =
          effectiveSchema.getRelation(query.modelName, entry.key);
      if (relation != null && relation.type == RelationType.manyToMany) {
        // Get primary key field name from schema (fallback to 'id' for compatibility)
        final parentModel = effectiveSchema.getModel(query.modelName);
        final parentPkFieldName = parentModel?.primaryKeys.isNotEmpty == true
            ? parentModel!.primaryKeys.first.name
            : 'id';

        // Get the primary key value for the parent record
        // For create, it's in the data; for update, it's in the where clause
        String? parentId;
        if (query.action == 'create') {
          parentId = data[parentPkFieldName]?.toString();
        } else if (query.action == 'update') {
          final where = args['where'] as Map<String, dynamic>?;
          parentId = where?[parentPkFieldName]?.toString();
        }

        if (parentId != null) {
          // Compile connect operations
          if (value.containsKey('connect')) {
            final connectItems =
                _normalizeConnectDisconnect(value['connect']);
            relationMutations.addAll(_compileConnectOperations(
              parentId: parentId,
              relation: relation,
              connectItems: connectItems,
              effectiveSchema: effectiveSchema,
            ));
          }

          // Compile disconnect operations
          if (value.containsKey('disconnect')) {
            final disconnectItems =
                _normalizeConnectDisconnect(value['disconnect']);
            relationMutations.addAll(_compileDisconnectOperations(
              parentId: parentId,
              relation: relation,
              disconnectItems: disconnectItems,
              effectiveSchema: effectiveSchema,
            ));
          }
        }
      } else if (relation != null) {
        // Non-M2M relation with connect/disconnect - not supported
        throw UnsupportedError(
          'connect/disconnect operations are only supported for many-to-many '
          'relations. Field "${entry.key}" is a ${relation.type.name} relation.',
        );
      }
      // If relation is null, the field will be passed through and likely fail
      // downstream with a more specific error about the unknown field
    } else {
      // Regular field - keep in clean data
      cleanData[entry.key] = value;
    }
  }

  // Create modified query with clean data (no relation operations)
  final cleanArgs = Map<String, dynamic>.from(args);
  cleanArgs['data'] = cleanData;
  final cleanQuery = JsonQuery(
    modelName: query.modelName,
    action: query.action,
    args: JsonQueryArgs(arguments: cleanArgs),
  );

  // Compile the main query
  final mainQuery = compile(cleanQuery);

  return CompiledMutation(
    mainQuery: mainQuery,
    relationMutations: relationMutations,
  );
}