# Renaming and removing things that hold data

> Renaming an entity or property, and removing one, are changes to something that already holds rows. Say what you meant in a migration and the rename keeps your data where it is and the removal is recorded rather than forgotten.

<!-- id: project-renaming-and-removing · area: project · stability: stable · html: https://osysharp.com/reference/project/renaming-and-removing/ -->

## Summary        {#summary}
Renaming an entity or a property is not the same kind of change as adding one. To your source it is a small edit; to
your database it is a table full of rows whose name just changed out from under it. A removal is the same problem
seen from the other side — the property is gone from your code, and its data is not.

Neither is guessed at. A deploy that removes something holding data is **refused** until you say what you meant in a
`.migration` — and then it does the safe thing: a rename keeps every row exactly where it is, and a removal is
recorded, so the column it leaves behind has an owner instead of becoming an anonymous leftover.

## Signature      {#signature}
```osy syntax
migration "what changed" {
  from "ast:<hash>";
  to   "ast:<hash>";

  rename entity   Job       -> Assignment;
  rename property Job.Notes -> Job.Remarks;
  drop   property Job.Spare;
  drop   entity   Ghost;
  drop   enum     JobStatus.Parked;
}
```

## Description    {#description}

### What you see if you forget   {#refusal}
Deploy a removal with nothing to authorize it and the deploy stops, having changed nothing:

```text
This deploy removes something that holds data, and nothing says you meant to. Removing an entity or a property is
not a change the deploy will make on your behalf, because the data outlives the source: the rows stay in the
database after the declaration is gone. Offending change(s): entity 'Job' removed.
Say what you meant in a migration and deploy again:
  drop entity Job;              // or: rename entity Job -> <NewName>;
```

The reason it asks rather than guessing is that a removal and a rename look **identical** from the outside — both are
just "this name is gone" — and they want opposite handling. Guess wrong in either direction and you lose data: treat a
rename as a removal and every row is stranded in a table your app can no longer name; treat a removal as a rename and
an unrelated table's rows are silently adopted.

### You do not have to write this by hand   {#generating}
Ask for it, and the file is generated from the difference between what is deployed and what you have now:

```console
$ osy compile --generate-migration
```

The generated file carries only the changes that need your word — the additive ones are simply applied. Where it can
tell a rename from a genuine removal it writes the `rename` for you and says so:

```osy syntax
rename entity Job -> Assignment;   // INFERRED: 75% of its properties match by name and type. Verify — if these are
                                   // genuinely different entities, replace this with `drop entity Job;`
```

It only writes a `rename` when there is one obvious answer. If two properties of the same type left and two arrived,
or a removed entity resembles several new ones equally, it will not guess — it lists the candidates as a comment and
leaves the `drop` in place for you to correct. That reticence is deliberate: a rename you did not mean adopts an
unrelated table's rows, which is worse than the removal it replaced.

Review the file, fix anything it guessed wrong, and deploy with it:

```console
$ osy compile --migration migrations/9f00abcd.migration --new-version           # local
$ osyrin app compile --migration migrations/9f00abcd.migration --new-version    # a deployed app
```

### What a rename actually does — which is nothing, physically   {#rename}
Nothing is copied, nothing is moved, and no table is renamed. Your app's description of its data is versioned; the
data itself is shared. So the new name simply *points at the same table*:

- the version you just deployed knows the entity as `Assignment`;
- a run still finishing on an older version knows it as `Job`;
- both read and write the same rows, at the same time, correctly.

A physical rename would be the wrong tool for exactly that reason — it would break every run still asking for the old
name. Renaming a property works the same way and for the same reason.

This holds through repeated renames. `Job` → `Assignment` → `Task` still reads and writes the rows you created on day
one; each version just calls them something different.

### What a rename saves you from   {#rename-why}
Without one, the change reads as a removal plus an addition, because that is all there is to see. You would get a new,
empty `Assignment` and every existing row left behind in a `Job` your app can no longer name. Nothing is deleted — but
nothing is reachable either, and the app comes up looking like it forgot everything. That is the outcome the refusal
exists to stop.

### What a removal actually does   {#removal}
The property disappears from your app immediately — it is gone from the model, from queries, from screens. Its
**column stays**, because a run still finishing on an older version may go on reading and writing it, and that is the
whole point of versioning.

What is new is that the removal is **recorded**. The version you just deployed keeps a note saying "this property
existed and stopped here". That record is what later lets the column be reclaimed safely, and it travels forward: two
deploys later, the record still says the property died in the version where it actually died.

The practical effect is that a leftover column is never confused with a mystery column. When space is eventually
reclaimed, only columns with a recorded removal are taken. Anything else that turns up on a table — the residue of a
hand-run `ALTER`, a half-finished change — is reported to you and left exactly where it is. Deleting a column nobody
can account for is not a decision worth making automatically.

### Removing a required property   {#removal-required}
Its column stops being required, because it has to: the version you just deployed does not know about the property,
so it has nothing to put there, and rows created from now on simply leave it empty. A run still pinned to an older
version will therefore find that value missing on rows created after the removal. Finish moving your readers and
writers over before anything depends on it being filled in.

### Removing an enum member is the sharpest case     {#enum-member}
An enum member is not stored by its name. A column holds the member's **position**, so removing one **re-numbers every
member after it** — and no existing row is rewritten. `{ Queued, Parked, Done }` minus `Parked` is `{ Queued, Done }`,
and every row that said `Parked` now says `Done`.

That is worse than the stranding above, and quieter. A dropped property leaves data unreachable, which you notice. A
dropped enum member leaves data perfectly reachable and **meaning something else**, which you do not.

So it is treated exactly like the other removals: a deploy that removes a member is refused until a migration says you
meant it, and the remedy names the member:

```osy syntax
migration "retire the parked state" {
  drop enum JobStatus.Parked;
  // …or, if the member was really renamed:
  rename enum JobStatus.Parked -> JobStatus.Held;
}
```

A **rename** is the interesting one, and it is why the two are separate verbs. Renaming a member keeps its position,
so every stored row goes on meaning what it always meant, under the new name. That is almost always what you wanted.

⚠ **While you are developing, the local compile does not refuse — it warns.** `osy compile` edits the version in place
rather than deploying a new one, so it applies the change and tells you what it did to your data:

```text
enum member 'JobStatus.Done' changed its stored value from 2 to 1 — usually because an earlier member was removed
and the rest re-indexed. Existing rows are NOT rewritten, so rows holding 1 now read as 'Done', and rows written
when 'Done' meant 2 no longer do.
```

Read it rather than scrolling past it: the rows in your development database now say something you did not write. The
warning appears whenever a member's position moves — a removal, or a reorder — and stays silent when you simply add a
member at the end, which moves nothing.

### One thing you cannot do   {#refused}
Changing the stored **type**, **width** or **precision** of a property that already has data is refused. There is no
shape a column can take that is simultaneously the old type for a run still using it and the new type for the code
you just deployed. Do it additively instead — add a new property, copy the data across, then remove the old one once
every reader **and writer** has moved. See [Deploying while workflows are running](https://osysharp.com/reference/project/app-versions/) for the full sequence and its warning.

## Examples       {#examples}

Renaming an entity and one of its properties in the same deploy:

```osy title="before" test app=renaming
entity Job {
  [Required, MaxLength(200)] string Title;
  [MaxLength(400)] string? Notes;
}
```

```osy title="after" test app=renaming-after
entity Assignment {
  [Required, MaxLength(200)] string Title;
  [MaxLength(400)] string? Remarks;
}
```

The migration that says so:

```osy title="the migration that carries the data across" syntax
migration "job becomes assignment" {
  from "ast:<the deployed hash>";
  to   "ast:<the new hash>";

  rename entity   Job       -> Assignment;
  rename property Job.Notes -> Job.Remarks;
}
```

Deploy it and every `Job` you ever created is an `Assignment`, with its notes intact under the new name. Runs that
were already in flight go on calling it a `Job` until they finish.

## See also       {#see-also}
- [Deploying while workflows are running](https://osysharp.com/reference/project/app-versions/) — why an older version is still reading your data, and how versions are cleaned up
- [Stopping runs after a bad deploy](https://osysharp.com/reference/project/cancel-runs/) — stopping runs that are still executing a version you want rid of
- [entity](https://osysharp.com/reference/entity/declaration/) — declaring the entities this is all about
- [enum](https://osysharp.com/reference/enum/declaration/) — declaring the enums whose members re-number when one is removed
