createContext method

GenerationContext createContext(
  1. Insertable<D> entry,
  2. InsertMode mode, {
  3. UpsertClause<T, D>? onConflict,
  4. bool returning = false,
})

Creates a GenerationContext which contains the sql necessary to run an insert statement fro the entry with the mode.

This method is used internally by drift. Consider using insert instead.

Implementation

GenerationContext createContext(Insertable<D> entry, InsertMode mode,
    {UpsertClause<T, D>? onConflict, bool returning = false}) {
  _validateIntegrity(entry);

  final rawValues = entry.toColumns(true);

  // apply default values for columns that have one
  final map = <String, Expression>{};
  for (final column in table.$columns) {
    final columnName = column.$name;

    if (rawValues.containsKey(columnName)) {
      final value = rawValues[columnName]!;
      map[columnName] = value;
    } else {
      if (column.clientDefault != null) {
        map[columnName] = column._evaluateClientDefault();
      }
    }

    // column not set, and doesn't have a client default. So just don't
    // include this column
  }

  final ctx = GenerationContext.fromDb(database);

  if (ctx.dialect == SqlDialect.postgres &&
      mode != InsertMode.insert &&
      mode != InsertMode.insertOrIgnore) {
    throw ArgumentError('$mode not supported on postgres');
  }

  ctx.buffer
    ..write(_insertKeywords[
        ctx.dialect == SqlDialect.postgres ? InsertMode.insert : mode])
    ..write(' INTO ')
    ..write(table.$tableName)
    ..write(' ');

  if (map.isEmpty) {
    ctx.buffer.write('DEFAULT VALUES');
  } else {
    writeInsertable(ctx, map);
  }

  void writeDoUpdate(DoUpdate<T, D> onConflict) {
    if (onConflict._usesExcludedTable) {
      ctx.hasMultipleTables = true;
    }
    final upsertInsertable = onConflict._createInsertable(table);

    if (!identical(entry, upsertInsertable)) {
      // We run a ON CONFLICT DO UPDATE, so make sure upsertInsertable is
      // valid for updates.
      // the identical check is a performance optimization - for the most
      // common call (insertOnConflictUpdate) we don't have to check twice.
      table
          .validateIntegrity(upsertInsertable, isInserting: false)
          .throwIfInvalid(upsertInsertable);
    }

    final updateSet = upsertInsertable.toColumns(true);

    ctx.buffer.write(' ON CONFLICT(');

    final conflictTarget = onConflict.target ?? table.$primaryKey.toList();

    if (conflictTarget.isEmpty) {
      throw ArgumentError(
          'Table has no primary key, so a conflict target is needed.');
    }

    var first = true;
    for (final target in conflictTarget) {
      if (!first) ctx.buffer.write(', ');

      // Writing the escaped name directly because it should not have a table
      // name in front of it.
      ctx.buffer.write(target.escapedName);
      first = false;
    }

    if (ctx.dialect == SqlDialect.postgres &&
        mode == InsertMode.insertOrIgnore) {
      ctx.buffer.write(') DO NOTHING ');
    } else {
      ctx.buffer.write(') DO UPDATE SET ');

      first = true;
      for (final update in updateSet.entries) {
        final column = escapeIfNeeded(update.key);

        if (!first) ctx.buffer.write(', ');
        ctx.buffer.write('$column = ');
        update.value.writeInto(ctx);

        first = false;
      }

      if (onConflict._where != null) {
        ctx.writeWhitespace();
        final where = onConflict._where!(
            table.asDslTable, table.createAlias('excluded').asDslTable);
        where.writeInto(ctx);
      }
    }
  }

  if (onConflict is DoUpdate<T, D>) {
    writeDoUpdate(onConflict);
  } else if (onConflict is UpsertMultiple<T, D>) {
    onConflict.clauses.forEach(writeDoUpdate);
  }

  if (returning) {
    ctx.buffer.write(' RETURNING *');
  } else if (ctx.dialect == SqlDialect.postgres) {
    if (table.$primaryKey.length == 1) {
      final id = table.$primaryKey.firstOrNull;
      if (id != null && id.type is IntType) {
        ctx.buffer.write(' RETURNING ${id.name}');
      }
    }
  }

  return ctx;
}