You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 2a597ea
Browse filesBrowse the repository at this point in the historyBrowse files
feat(cli): `os migrate unmapped-columns --object NAME` reads the values of a retired field's columns, keyed by record id, for a conversion before `os migrate apply --allow-destructive` drops them (#21573)
6
+
7
+
Clause-②: yes (widening)
8
+
9
+
-**What it reads.** The columns `os migrate plan` reports as `unmapped_column` for one object's table: a column that is still in the table and that no metadata declares, typically one a retired field left behind. The column set is the plan's own findings, from the same differ on the same read-only boot, so the command never reads a column the plan does not report. Each record is emitted as `{ id, values }`. `--json` prints one document, `{ database, object, table, columns, count, records, duration }`. The text face lists the columns and each record's values.
10
+
-**Why it exists.** A read or a write through the engine now serves an object's declared fields only, and naming an undeclared column is refused. An app that moves a retired field's values into the field that replaced it reads them once with this command, writes them with its own script, and then drops the columns with `os migrate apply --allow-destructive`. That is the route the read and write narrowing in `@objectstack/objectql` names for this case.
11
+
-**Operator-only and read-only.** It runs under the database credentials you pass (`--database-url`, else `OS_DATABASE_URL`, else the project database), and it reads every organization's rows. No REST route, API flag or per-request option serves these values, and the runtime doors are unchanged. It boots the way `os migrate plan` does: no schema DDL, no seed data, and no database file created.
12
+
-**Values as stored.** An unmapped column has no declared type, so each value is emitted as the database client returns it, with no field-type decoding; a PostgreSQL `timestamp` arrives as a date and is emitted as its ISO 8601 text. A value JSON cannot carry as stored (binary bytes, a `bigint`, or a non-finite number) is refused in both faces with exit 1, naming the column and the record id, and no record is emitted: read that column with the database's own client. No column the platform creates for a field type answers with one of these, on SQLite or on PostgreSQL.
13
+
-**Answers.** An object with no unmapped column, or with no table yet: empty work, exit 0. No SQL driver: `os migrate plan`'s own `no_sql_driver` answer, exit 0. An undeclared object name: `OBJECT_NOT_FOUND`, exit 1. An object the plan does not diff (federated, or bound to another datasource): refused, exit 1. A read that cannot be complete, such as one stopped by `--max-records`: refused, exit 1, and no partial set is emitted. A value JSON cannot carry as stored: refused, exit 1, as above.
14
+
-`MigrateUnmappedColumnsCommand` is exported from `@objectstack/cli` beside the other `os migrate` commands.
15
+
16
+
Nothing that ran before changes. This is a new command.
Copy file name to clipboardExpand all lines: content/docs/deployment/cli.mdx
+51-1Lines changed: 51 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -519,7 +519,7 @@ as the project's **default** datasource it is refused at boot the same way.
519
519
**What it boots:**
520
520
- Reads the artifact's `manifest`, `objects`, `views`, `flows`, …
521
521
- Auto-registers the platform services declared in `requires: [...]` (e.g. `ai`, `automation`, `analytics`, `auth`, `ui`). Declaring a **service** capability (`automation`, `analytics`, `ai`, `audit`, …) is a *requirement*: if its provider package isn't installed, boot **fails fast** with a clear error instead of silently starting without a capability you asked for. (`auth` and `ui` are tier-gated with their own opt-in rules — `auth`'s secret-gated skip is described below.)
522
-
- Auto-detects the driver from the database URL scheme (`libsql://`/`https://*.turso.*` → Turso — via the optional `@objectstack/driver-turso` package, and a loud failure with the install command when it is missing rather than a fallback to sqlite —, `postgres[ql]://`/`pg://` → pg, `mongodb[+srv]://` → MongoDB, otherwise sqlite; `:memory:` is SQLite's own in-memory database). `memory://` and `mingo://` are refused — see the callout below.
522
+
- Auto-detects the driver from the database URL scheme (`libsql://`/`https://*.turso.*` → Turso — via the optional `@objectstack/driver-turso` package, and a loud failure with the install command when it is missing rather than a fallback to sqlite —, `postgres[ql]://`/`pg://` → pg, `mongodb[+srv]://` → MongoDB, otherwise sqlite; `:memory:` is SQLite's own in-memory database). `memory://` and `mingo://` are refused — see the callout above.
523
523
- Runs standalone boot mode with one active environment.
524
524
525
525
**Authentication:**
@@ -853,6 +853,7 @@ diverges from the live schema, and the physical column wins at write time.
853
853
| `os migrate plan` | Dry-run: show how the database has drifted from metadata, categorised safe / needs-confirm / destructive (no changes applied) |
854
854
| `os migrate apply` | Reconcile the database to metadata. Applies loosening changes; destructive ones require `--allow-destructive` |
855
855
| `os migrate multi-value-columns` | Migrate a stale `varchar`/`text` column to `json` where the field declares `multiple: true` — one of three drift ops `apply` never reconciles for you. Dry run by default; `--apply` runs the statement the finding prints |
856
+
| `os migrate unmapped-columns` | Read the values of the columns `plan` reports as `unmapped_column` for one object, keyed by record id — the conversion route for a retired field's values before `apply --allow-destructive` drops its columns. Read-only, and operator-only: no runtime door serves these values |
856
857
857
858
```bash
858
859
os migrate plan # Preview drift (no changes)
@@ -861,6 +862,7 @@ os migrate apply --yes # Skip the prompt (CI / scripts)
861
862
os migrate apply --allow-destructive --yes # Also drop orphaned columns, tighten NOT NULL, narrow types
862
863
os migrate apply --force # Migrate even though another process is using the database
863
864
os migrate plan --json # Machine-readable output
865
+
os migrate unmapped-columns --object contact --json # A retired field's stored values, keyed by record id (read-only)
864
866
```
865
867
866
868
#### Nothing is written before you confirm
@@ -1048,6 +1050,54 @@ hook or an integration already copied into some *other* single-value column is
1048
1050
not something it looks for, and it is deliberately not something it will grow
1049
1051
into: that repair is specific to what your automations did with the value.
1050
1052
1053
+
#### `os migrate unmapped-columns`
1054
+
1055
+
Retiring a field leaves its column in the table: the additive sync never drops
1056
+
one, and `os migrate plan` reports it as `unmapped_column` until
1057
+
`os migrate apply --allow-destructive` does. In between, the values are still
1058
+
stored, and no runtime door serves them: a read or a write through the data API
1059
+
returns the object's declared fields only, and naming the column in `fields` is
1060
+
refused. When the values have to move into the field that replaced it, this is
1061
+
the read:
1062
+
1063
+
```bash
1064
+
os migrate unmapped-columns --object contact # The columns, and every record's values
1065
+
os migrate unmapped-columns --object contact --json > out.json # The same, for a conversion script
1066
+
os migrate unmapped-columns --object contact --max-records 1000000 --json
1067
+
os migrate unmapped-columns --object contact --database-url postgres://…
1068
+
```
1069
+
1070
+
The conversion route is three steps: read the values with this command, write
1071
+
them into the declared fields with your own script, then run
1072
+
`os migrate apply --allow-destructive` to drop the columns.
1073
+
1074
+
- **One column set.** It reads exactly the columns `os migrate plan` reports as
1075
+
`unmapped_column` for that object's table: the same differ, the same boot.
1076
+
Columns the differ never reports are never read either, such as the driver's
1077
+
own `id`, `created_at` and `updated_at`.
1078
+
- **Operator-only and read-only.** It runs under the database credentials you
1079
+
pass, and covers every organization's rows. There is no REST route or API
1080
+
flag behind it. It boots the way `plan` does, so it writes nothing, and it
1081
+
drops nothing.
1082
+
- **Values as stored.** An unmapped column has no declared type, so each value is
1083
+
emitted as the database client returns it, with no field-type decoding: a
1084
+
retired `json` field on SQLite reads as its stored text, a retired `boolean`
1085
+
as `0` or `1`, and a retired `datetime` on PostgreSQL arrives as a date and is
1086
+
emitted as its ISO 8601 text. A value JSON cannot carry as stored (binary
1087
+
bytes, a `bigint`, or a non-finite number) is refused with exit 1, naming the
1088
+
column and the record id, and no record is emitted; read that column with
1089
+
the database's own client.
1090
+
- **Empty work, exit 0**, for an object with no unmapped column, or with no table
1091
+
in this database yet. `--json` prints one document:
0 commit comments