diff --git a/docs/prepare-db-upgrade-downgrade.md b/docs/prepare-db-upgrade-downgrade.md index 1ea1dcffd45d..113f49ce3083 100644 --- a/docs/prepare-db-upgrade-downgrade.md +++ b/docs/prepare-db-upgrade-downgrade.md @@ -101,6 +101,65 @@ relation1.rel: run upgrade.qlo predicate1 relation2.rel: run upgrade.qlo predicate2 ``` +### Creating new database entities + +An upgrade or downgrade query can create database entities that do not exist in +the source database by using the compiler-provided `QlBuiltins::NewEntity` +module. Define a `newtype` whose values uniquely identify the entities to +create, and instantiate `NewEntity` with that type: + +```ql +class Element extends @element { + string toString() { none() } +} + +// Create one wrapper for each existing element. +newtype TAddedElement = TWrapper(Element element) + +module Fresh = QlBuiltins::NewEntity; +``` + +`Fresh::map` maps each value of `TAddedElement` to a distinct new entity ID. +Using an algebraic data type with multiple constructors allows several distinct +entities to be created for the same source entity. + +The query is compiled against the source schema, so its static QL types cannot +refer to an entity type that exists only in the target schema. An output column +that contains only fresh IDs can use `Fresh::EntityId` directly: + +```ql +query predicate new_wrappers(Fresh::EntityId wrapper, Element element) { + wrapper = Fresh::map(TWrapper(element)) +} +``` + +Run the predicate to populate a relation in the target schema: + +```properties +wrappers.rel: run upgrade.qlo new_wrappers +``` + +If an output column can contain both existing source IDs and fresh IDs, define a +union of the source-schema type and `Fresh::EntityId`, then use a class extending +that union as the column's static QL type: + +```ql +class TExistingOrFreshElement = @element or Fresh::EntityId; + +class ExistingOrFreshElement extends TExistingOrFreshElement { + string toString() { none() } +} +``` + +These QL types only describe values while evaluating the transformation against +the source schema. The corresponding column type in the target database schema +determines the database type assigned to each fresh ID. Emit every target +relation needed to describe the new entity, including relationships such as its +parent, location, or type. When rewriting an existing relation, its output +predicate will usually need to preserve all existing rows as well as add rows +containing the fresh IDs. Reuse the same `Fresh::map(...)` expression in each +predicate that refers to a particular new entity. + ### Testing your scripts Although we have some automated testing of the scripts (e.g. to test that you can upgrade databases all the way from an initial dbscheme to the newest, and back), it's essential that you apply some more rigorous testing for any non-trivial upgrade or downgrade. You might do so as follows: