ent/ent · error

{{ $pkg }}: {{ $upsertOne }}.ID is not supported by MySQL dr

Error message

{{ $pkg }}: {{ $upsertOne }}.ID is not supported by MySQL driver. Use {{ $upsertOne }}.Exec instead

What it means

ent's generated UpsertOne.ID(ctx) helper refuses to run on MySQL when the entity ID is a non-numeric field. MySQL lacks a RETURNING clause for INSERT ... ON DUPLICATE KEY UPDATE, so there is no way to read back the generated non-numeric (e.g. string/UUID) ID after an upsert. The library fails fast instead of returning a zero value.

Source

Thrown at entc/gen/template/dialect/sql/feature/upsert.tmpl:234

	}
	return u.create.Exec(ctx)
}

// ExecX is like Exec, but panics if an error occurs.
func (u *{{ $upsertOne }}) ExecX(ctx context.Context) {
	if err := u.create.Exec(ctx); err != nil {
		panic(err)
	}
}

{{ if $.HasOneFieldID }}
	// Exec executes the UPSERT query and returns the inserted/updated ID.
	func (u *{{ $upsertOne }}) ID(ctx context.Context) (id {{ $.ID.Type }}, err error) {
		{{- if and $udfID (not $.ID.Type.Numeric) }}
			if u.create.driver.Dialect() == dialect.MySQL {
				// In case of "ON CONFLICT", there is no way to get back non-numeric ID
				// fields from the database since MySQL does not support the RETURNING clause.
				return id, errors.New("{{ $pkg }}: {{ $upsertOne }}.ID is not supported by MySQL driver. Use {{ $upsertOne }}.Exec instead")
			}
		{{- end }}
		node, err := u.create.Save(ctx)
		if err != nil {
			return id, err
		}
		return node.ID, nil
	}

	// IDX is like ID, but panics if an error occurs.
	func (u *{{ $upsertOne }}) IDX(ctx context.Context) {{ $.ID.Type }} {
		id, err := u.ID(ctx)
		if err != nil {
			panic(err)
		}
		return id
	}
{{ end }}

View on GitHub (pinned to 69d5d4deb1)

Solutions

  1. Use Exec(ctx) instead of ID(ctx) and set the ID yourself before the upsert (e.g. entity.SetID(uuid.New()) on the create builder), so no read-back is needed
  2. Pass explicit OnConflictColumns and query the row afterwards with a normal Query if you must know which row resulted
  3. Switch the ID field to a numeric type (int) if the schema allows it, so MySQL's LastInsertId path works
  4. Use a database that supports RETURNING (Postgres/SQLite) for this operation

Example fix

// before (MySQL, UUID PK)
id, err := client.User.Create().SetID(uuid.New()).SetName("a").OnConflict(sql.DoNothing()).ID(ctx)
// after
err := client.User.Create().SetID(uuid.New()).SetName("a").OnConflict(sql.DoNothing()).Exec(ctx)
// the UUID is already known: id := uuid.New() before the call
Defensive patterns

Strategy: validation

Validate before calling

func mysqlNonNumericIDUpsert(dialectName string, idType reflect.Kind) bool {
    return dialectName == dialect.MySQL && (idType == reflect.String)
}

Type guard

func isTxDriver(d dialect.Driver) bool { _, ok := d.(*txDriver); return ok } // for tx case; here: check dialect before calling ID()

Try / catch

id, err := u.ID(ctx)
if err != nil && strings.Contains(err.Error(), "not supported by MySQL") {
    // fallback: ID was set client-side before the upsert
    id = preGeneratedID
}

Prevention

When it happens

Trigger: Calling u.ID(ctx) on an UpsertOne builder (created via client.<Entity>.Create().OnConflict(...).UpdateNewValues().ID(ctx)) where the entity's ID field is non-numeric (e.g. field.UUID / field.String ID) and the driver is MySQL.

Common situations: Projects that generate UUID primary keys and later add upsert logic; code written for Postgres (which supports ON CONFLICT ... RETURNING) ported to MySQL; copy-pasted upsert examples that assume numeric auto-increment IDs.

Related errors


AI-assisted analysis of ent/ent@69d5d4deb1 (2026-09-03). Data as JSON: /api/errors/1a0d7190197dd966. Report an issue: GitHub.