Skip to content

Commit f23da33

Browse files
committed
Clean up dead draft version of usertypes doc that was inlined in rzsql.json doc.
1 parent 3e2dda3 commit f23da33

4 files changed

Lines changed: 11 additions & 91 deletions

File tree

doc/Configuration/Json.md

Lines changed: 8 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -119,103 +119,23 @@ _default: `[]`_
119119

120120
This optional setting allows you to bring your own types into RZSQL's type system.
121121

122-
I like to use a lot of little wrapper types in my domain layer.
123-
124-
Instead of `string`, I might have an `EmailAddress` type. Instead of passing around `int` IDs, I like to have `UserId` and `GroupId` and `CompanyId` and so on.
125-
126-
This allows validation rules to live in the type's constructor, it makes methods self-documenting,
127-
and it creates a compiler error if I accidentally call `service.AddUser(userId, companyId)` when that method is supposed to take `(companyId, userId)`.
128-
129-
By default though, RZSQL only understands the handful of built-in SQL primitives described in [Language/Data Types](../Language/DataTypes.md).
130-
131-
If we put our user type definitions in a separate assembly, reference that assembly from the project where our SQL
132-
model+queries live, and add the assembly name to the `"usertypes"` list in rzsql.json, Rezoom SQL can use any user type that wraps a supported underlying primitive.
133-
134122
Reference the assembly full name in rzsql.json like so:
135123

136124
```javascript
137125
"usertypes": ["MyProduct.MyCustomTypesAssembly"]
138126
```
139127

140-
You must also reference the MyProduct.MyCustomTypesAssembly project from your F# project where you're using Rezoom.SQL.Provider.
141-
142-
The type provider will search the listed assemblies for user types with mappings to primitive types.
143-
144-
A "primitive type" means any of the .NET types listed in the table at the top of [Language/Data Types](../Language/DataTypes.md).
145-
146-
A user type is:
147-
148-
* Any F# single-case union that wraps a primitive type, such as `type UserId = UserId of Guid`. `[<Struct>]` unions are also supported.
149-
* Or, any type `T` for which we find a class with static `ToPrimitive` and `FromPrimitive` methods mapping `T` to and from a primitive type.
150-
* Or, any type `T` for which we find F#-style extension methods `member this.ToPrimitive()` and `static member FromPrimitive(x)` mapping `T` to and from a primitive type.
151-
152-
In the latter two cases, it should be noted that `T` does not HAVE to be a type that you own.
153-
154-
For example, you can write ToPrimitive and FromPrimitive extension methods for `System.TimeOnly` in your UserTypes assembly, and then use `TimeOnly` in your SQL schema.
155-
156-
User-type code:
157-
158-
```fsharp
159-
// simple DU
160-
type UserId = UserId of System.Guid
161-
162-
// custom mapping for a type defined elsewhere
163-
module TimeOnlyMapping =
164-
type System.TimeOnly with
165-
member this.ToPrimitive() =
166-
this.ToString("o")
167-
static member FromPrimitive(s : string) =
168-
System.TimeOnly.ParseExact(s, "o")
169-
```
170-
171-
SQL schema:
172-
173-
```sql
174-
create table Employees
175-
( Id UserId primary key
176-
, Name string(80)
177-
, ShiftStarts TimeOnly
178-
, ShiftEnds TimeOnly
179-
);
180-
```
181-
182-
The actual SQL that runs on your database will treat ShiftStarts and ShiftEnds as if you'd written `string`.
183-
184-
Selecting from this table will give you a `TimeOnly` property in your result set rows from the type provider.
185-
186-
Comparing to a parameter will cause that parameter to get a `TimeOnly` type inferred:
187-
188-
189-
```fsharp
190-
type MyQuery = SQL<"""
191-
select * from Employees where ShiftStarts = @t
192-
""">
193-
194-
let usage =
195-
MyQuery.Command(t = TimeOnly(0, 0, 0))
196-
```
197-
198-
### Caveats and limitations
199-
200-
It is not supported to define a custom primitive that is backed by multiple columns. ToPrimitive and FromPrimitive must convert to a single primitive object! There are no plans to add multi-column primitives in the future.
201-
202-
It is not supported to define custom primitive mappings for a generic type. You cannot have `Id<User>` and `Id<Company>`. All custom-mapped types must be simple non-generic types.
203-
204-
When implementing ToPrimitive you should not return a null, and when implementing FromPrimitive you do not need to handle nulls.
205-
You are defining the mapping for a non-null object. The mapping for a null / None object is always assumed to be null / None and cannot be overridden.
128+
Your F# project using the type provider must also reference the MyProduct.MyCustomTypesAssembly project.
206129

207-
The ToPrimitive and FromPrimitive methods for any single UserType must be defined in the same class. You can't have
208-
`ToPrimitive(x : Foo) : int` in one class and `FromPrimitive(x : int) : Foo` in another class and have it work -- the
209-
assembly search will not detect `Foo` as a mapped UserType.
130+
Read [the full UserTypes feature documentation here](../Language/UserTypes/README.md).
210131

211-
You must be aware of the underlying representation for usertypes when writing your SQL. For example, your custom type
212-
may override comparison operators, but SQL doesn't know about that, and indeed doesn't know about your custom type at
213-
all. It is erased to the underlying primitive at F# compile-time.
132+
You can reference multiple assemblies in this list. For example, you could have your primitive mappings in one assembly
133+
and your row interfaces in another.
214134

215-
If you write `where ShiftStarts < @t and ShiftEnds > @t`, that comparison will be done on the underlying *string
216-
representation*! With the above example ToPrimitive and FromPrimitive methods using the "o" format string this will
217-
actually work fine, but a different representation might not hold up so well. So choose your underlying representation
218-
carefully and be mindful of how your queries are actually working.
135+
It is also supported to reference a path to a .dll file for the assembly. Paths will be resolved relative to the folder
136+
containing rzsql.json. However, it is generally better to use a project reference and just name the project, without
137+
path elements or the ".dll" extension. Otherwise you'll have issues where, for example, you're pointing at the path to
138+
the DLL under "debug", and your build breaks or pulls an outdated assembly when you're building in "release" mode.
219139

220140
---
221141
<!-- nav-bottom -->

doc/Language/UserTypes/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Your UserTypes MUST be in a separate assembly from your SQL queries, and must bu
3333
The type provider cannot "see" types defined in the same assembly it's trying to compile. They don't exist yet!
3434

3535
The fsproj where you're using Rezoom.SQL.Provider must have a project reference to your UserType project(s). It must
36-
**also** name those projects in [rzsql.json's](../../Configuration/Json.md) `"UserTypes"` list. This tells the type
36+
**also** name those projects in [rzsql.json's](../../Configuration/Json.md) `"usertypes"` list. This tells the type
3737
provider to search the listed assemblies at design-time to find your custom types.
3838

3939
Referencing Rezoom.SQL.Annotations is optional. This is a lightweight package that only defines attributes.

doc/Language/UserTypes/SolutionLayout.gv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ digraph "Solution Layout" {
3232
tpu[label=<
3333
<table border="3" cellborder="1" cellspacing="0" cellpadding="14">
3434
<tr><td colspan="2"><b>YourProject.SQLQueries.fsproj</b></td></tr>
35-
<tr><td>rzsql.json</td><td align="left">{ &quot;UserTypes&quot;: [&quot;YourProject.UserTypes&quot;] }</td></tr>
35+
<tr><td>rzsql.json</td><td align="left">{ &quot;usertypes&quot;: [&quot;YourProject.UserTypes&quot;] }</td></tr>
3636
<tr><td>V1.model.sql</td><td align="left">create table Users(Id UserId primary key, Email EmailAddress, ...)</td></tr>
3737
<tr><td rowspan="3">Query.fs</td><td align="left">type MyQuery = SQL&lt;&quot;select&lt;IUser&gt; * from Users where Id = @id&quot;&gt;</td></tr>
3838
<tr><td align="left">let! rows = MyQuery.Command(id = UserId(myGuid)).Plan()</td></tr>

doc/Language/UserTypes/SolutionLayout.gv.svg

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)