{"record":{"id":"f255dba821c8ced7","repo":"bytebase/bytebase","slug":"no-fields-to-update-f255db","errorCode":null,"errorMessage":"no fields to update","messagePattern":"no fields to update","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/store/role.go","lineNumber":232,"sourceCode":"\tif v := patch.Name; v != nil {\n\t\tset.Comma(\"name = ?\", *v)\n\t}\n\tif v := patch.Description; v != nil {\n\t\tset.Comma(\"description = ?\", *v)\n\t}\n\tif v := patch.Permissions; v != nil {\n\t\tp := &storepb.RolePermissions{}\n\t\tfor k := range *v {\n\t\t\tp.Permissions = append(p.Permissions, k)\n\t\t}\n\t\tpermissionBytes, err := protojson.Marshal(p)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tset.Comma(\"permissions = ?\", permissionBytes)\n\t}\n\tif set.Len() == 0 {\n\t\treturn nil, errors.New(\"no fields to update\")\n\t}\n\n\tq := qb.Q().Space(`\n\t\tUPDATE role\n\t\tSET ?\n\t\tWHERE resource_id = ? AND workspace = ?\n\t\tRETURNING name, description, permissions\n\t`, set, patch.ResourceID, patch.Workspace)\n\n\tquery, args, err := q.ToSQL()\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to build sql\")\n\t}\n\n\trole := &RoleMessage{\n\t\tResourceID:  patch.ResourceID,\n\t\tPermissions: map[permission.Permission]bool{},\n\t}","sourceCodeStart":214,"sourceCodeEnd":250,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/store/role.go#L214-L250","documentation":"UpdateRole in backend/store/role.go builds a SET clause from the Role patch fields (e.g. permissions). If none are set, set.Len() == 0 and the store returns \"no fields to update\" rather than issuing an UPDATE with an empty SET against the role table matched by resource_id and workspace.","triggerScenarios":"Calling UpdateRole with a message lacking any patchable field — an API role update whose mask selects nothing, or a permissions update path where the permission list conversion failed before the set.Comma call.","commonSituations":"Client submits an empty role edit; an admin UI saves a form with no changes; handler filters masked fields and the mask contains only unsupported paths like title-only renames not backed by patch fields.","solutions":["Set Permissions (or another patchable field) on the role update message before calling UpdateRole","Validate in the API layer that the update mask covers at least one patchable field and return CodeInvalidArgument otherwise","Skip the write and return the existing role when the request is a true no-op"],"exampleFix":"// before\n_, err := store.UpdateRole(ctx, &store.RoleMessage{ResourceID: roleID, Workspace: ws})\n// after\n_, err := store.UpdateRole(ctx, &store.RoleMessage{ResourceID: roleID, Workspace: ws, Permissions: perms})","handlingStrategy":"validation","validationCode":"if msg.Permissions == nil {\n\treturn connect.NewError(connect.CodeInvalidArgument, errors.New(\"role update requires permissions\"))\n}","typeGuard":null,"tryCatchPattern":"role, err := store.UpdateRole(ctx, msg)\nif err != nil && strings.Contains(err.Error(), \"no fields to update\") {\n\treturn connect.NewError(connect.CodeInvalidArgument, errors.New(\"empty role update\"))\n}","preventionTips":["Populate the Permissions field for role updates","Validate the update mask covers patchable fields in the service layer","No-op saves should return the existing role without a write"],"tags":["go","database","store","empty-update","rbac"],"backgroundTag":"empty-required-field","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}