Skip to content

Commit

Permalink
Create function record_merge (#108)
Browse files Browse the repository at this point in the history
* Create function `recordmerge`

* Fix Documenter warnings

---------

Co-authored-by: Alex Arslan <[email protected]>
Co-authored-by: Jarrett Revels <[email protected]>
  • Loading branch information
3 people authored Dec 21, 2023
1 parent 470a3de commit ab5d8e2
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Legolas"
uuid = "741b9549-f6ed-4911-9fbf-4a1c0c97f0cd"
authors = ["Beacon Biosignals, Inc."]
version = "0.5.17"
version = "0.5.18"

[deps]
Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45"
Expand Down
3 changes: 2 additions & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,15 @@ Legolas.accepted_field_type
Legolas.extract_schema_version
Legolas.write
Legolas.read
Legolas.tobuffer
```

## Utilities

```@docs
Legolas.lift
Legolas.construct
Legolas.assign_to_table_metadata!
Legolas.record_merge
Legolas.gather
Legolas.locations
Legolas.materialize
Expand Down
2 changes: 1 addition & 1 deletion docs/src/upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ See [here](https://github.com/beacon-biosignals/Legolas.jl/pull/54) for a compre

## Some main changes to be aware of

* In Legolas v0.4, every `Legolas.Row` field's type was available as a type parameter of `Legolas.Row`; for example, the type of a field `y` specified as `y::Real` in a `Legolas.@row` declaration would be surfaced like `Legolas.Row{..., NamedTuple{(...,:y,...),Tuple{...,typeof(y),...}}`. In Legolas v0.5, the schema version author controls which fields have their types surfaced as type parameters in Legolas-generated record types via the `field::(<:F)` syntax in [`@version`](@ref).
* In Legolas v0.4, every `Legolas.Row` field's type was available as a type parameter of `Legolas.Row`; for example, the type of a field `y` specified as `y::Real` in a `Legolas.@row` declaration would be surfaced like `Legolas.Row{..., NamedTuple{(...,:y,...),Tuple{...,typeof(y),...}}`. In Legolas v0.5, the schema version author controls which fields have their types surfaced as type parameters in Legolas-generated record types via the `field::(<:F)` syntax in [`Legolas.@version`](@ref).
* Additionally, to include type parameters associated to fields in a parent schema, they must be re-declared in the child schema. For example, the package LegolasFlux declares a `ModelV1` version with a field `weights::(<:Union{Missing,Weights})`. LegolasFlux includes an [example](https://github.com/beacon-biosignals/LegolasFlux.jl/blob/53c677848c6b65e5158ef2d43dd5f7eab174892e/examples/digits.jl#L78-L80) with a schema extension `DigitsRowV1` which extends `ModelV1`. This `@version` call must re-declare the field `weights` to be parametric in order for the `DigitsRowV1` struct to also have a type parameter for this field.
* In Legolas v0.4, `@row`-generated `Legolas.Row` constructors accepted and propagated any non-schema-declared fields provided by the caller. In Legolas v0.5, `@version`-generated record type constructors will discard any non-schema-declared fields provided by the caller. When upgrading code that formerly "implicitly extended" a given schema version by propagating non-declared fields, it is advisable to instead explicitly declare a new extension of the schema version to capture the propagated fields as declared fields; or, if it makes more sense for a given use case, one may instead define a new schema version that adds these propagated fields as declared fields directly to the schema (likely declared as `::Union{Missing,T}` to allow them to be missing).
* Before Legolas v0.5, the documented guidance for schema authors surrounding new fields' impact on schema version breakage was misleading, implying that adding a new declared field to an existing schema version is non-breaking if the field's type allowed for `Missing` values. This is incorrect. For clarity, *adding a new declared field to an existing schema version is a breaking change unless the field's type and value are both completely unconstrained in the declaration*, i.e. the field's type constraint must be `::Any` and may not feature a value-constraining or value-transforming assignment expression.
Expand Down
1 change: 1 addition & 0 deletions src/Legolas.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ const LEGOLAS_SCHEMA_QUALIFIED_METADATA_KEY = "legolas_schema_qualified"
include("lift.jl")
include("schemas.jl")
include("tables.jl")
include("record_merge.jl")

end # module
14 changes: 14 additions & 0 deletions src/record_merge.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""
record_merge(record::AbstractRecord; fields_to_merge...)
Return a new `AbstractRecord` with the same schema version as `record`, whose fields
are computed via `Tables.rowmerge(record; fields_to_merge...)`. The returned record is
constructed by passing these merged fields to the `AbstractRecord` constructor
that matches the type of the input `record`.
"""
function record_merge(record::AbstractRecord; fields_to_merge...)
# Avoid using `typeof(record)` as can cause constructor failures with parameterized
# record types.
R = record_type(schema_version_from_record(record))
return R(Tables.rowmerge(record; fields_to_merge...))
end
18 changes: 18 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -799,3 +799,21 @@ end
@test n3 isa NestedV1{Missing}
@test ismissing(n3.k)
end

@testset "Legolas.record_merge" begin
r = Legolas.record_merge(ParentV1(; x=1:3, y="a"); y="b")
@test r isa ParentV1
@test r.x == 1:3
@test r.y == "b"

r = Legolas.record_merge(ChildV1(; x=1:3, y="a", z=1); y="c", z=2)
@test r isa ChildV1
@test r.x == 1:3
@test r.y == "c"
@test r.z == 2

r = Legolas.record_merge(ParamV1(; i=UInt8(0)); i=UInt16(1))
@test r isa ParamV1{UInt16}
@test r.i isa UInt16
@test r.i == 1
end

2 comments on commit ab5d8e2

@omus
Copy link
Member Author

@omus omus commented on ab5d8e2 Dec 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/97574

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.5.18 -m "<description of version>" ab5d8e2f8742823d4ac9303243ab8c917893da69
git push origin v0.5.18

Please sign in to comment.