|
| 1 | +module TypeProviderUser.Postgres.TestUserPrimitivePoint |
| 2 | +open NUnit.Framework |
| 3 | +open Rezoom.SQL |
| 4 | +open Rezoom.SQL.Raw |
| 5 | +open TypeProviderUser.Postgres.UserTypes |
| 6 | + |
| 7 | +// Point2D maps to PG's `point` type via the System.Object escape hatch |
| 8 | +// with NpgsqlPoint as the driver value (not a string), exercising a |
| 9 | +// different shape from the jsonb/Address case in TestUserPrimitiveSystemObject. |
| 10 | + |
| 11 | +let private homerPoint = { X = 1.5; Y = 2.5 } |
| 12 | +let private margePoint = { X = 1.5; Y = 2.5 } |
| 13 | +let private bartPoint = { X = -7.25; Y = 99.0 } |
| 14 | + |
| 15 | +type InsertAndSelectPoints = SQL<""" |
| 16 | +insert into UserLocations(UserId, Coord) |
| 17 | +values((select Id from Users where Name = 'Homer'), @homer); |
| 18 | +insert into UserLocations(UserId, Coord) |
| 19 | +values((select Id from Users where Name = 'Marge'), @marge); |
| 20 | +select Coord from UserLocations order by Id; |
| 21 | +"""> |
| 22 | + |
| 23 | +[<Test>] |
| 24 | +let ``select roundtrips a Point2D user primitive over PG point`` () = |
| 25 | + let results = InsertAndSelectPoints.Command(homerPoint, margePoint) |> runOnTestData |
| 26 | + Assert.AreEqual(2, results.Count) |
| 27 | + Assert.AreEqual(homerPoint, results.[0].Coord) |
| 28 | + Assert.AreEqual(margePoint, results.[1].Coord) |
| 29 | + |
| 30 | +// PG's point type has no `=` operator (42883: "operator does not |
| 31 | +// exist: point = point"). Equality is `~=` (the same-as operator), |
| 32 | +// which Rezoom's parser doesn't know — unsafe_inject_raw is the |
| 33 | +// idiomatic escape hatch here. The parameter @needle still binds |
| 34 | +// through Rezoom as a Point2D, then PG's ~= compares it against the |
| 35 | +// column value at row scan time, exercising the full |
| 36 | +// parameter-as-point pipeline. |
| 37 | +type FindPointByParameterSameAs = SQL<""" |
| 38 | +insert into UserLocations(UserId, Coord) |
| 39 | +values((select Id from Users where Name = 'Homer'), @homer); |
| 40 | +insert into UserLocations(UserId, Coord) |
| 41 | +values((select Id from Users where Name = 'Marge'), @bart); |
| 42 | +select Coord from UserLocations ul where unsafe_inject_raw(@filter); |
| 43 | +"""> |
| 44 | + |
| 45 | +[<Test>] |
| 46 | +let ``select with Point2D parameter equality matches via PG ~= operator`` () = |
| 47 | + // Identifiers are emitted unquoted by Rezoom's PG backend, so PG |
| 48 | + // folds them lowercase — `ul.coord`, not `"Coord"`. |
| 49 | + // |
| 50 | + // Caveat: Rezoom.SQL.Raw.arg does not apply user-type ToPrimitive |
| 51 | + // translation — it routes the value straight to ADO.NET with a |
| 52 | + // guessed DbType. So we cannot pass a Point2D here and expect the |
| 53 | + // Point2D → NpgsqlPoint conversion to happen automatically. We |
| 54 | + // pre-convert to NpgsqlPoint in user space; Npgsql then |
| 55 | + // auto-detects the wire format from the value's runtime type. |
| 56 | + // The fully-translated user-type → parameter pipeline is already |
| 57 | + // exercised by the INSERT in the roundtrip test above; this test |
| 58 | + // covers the WHERE-side parameter comparison via ~=. |
| 59 | + let needle = NpgsqlTypes.NpgsqlPoint(homerPoint.X, homerPoint.Y) |
| 60 | + let results = |
| 61 | + FindPointByParameterSameAs.Command |
| 62 | + ( bart = bartPoint |
| 63 | + , filter = [| sql "ul.coord ~= "; arg needle |] |
| 64 | + , homer = homerPoint |
| 65 | + ) |
| 66 | + |> runOnTestData |
| 67 | + Assert.AreEqual(1, results.Count) |
| 68 | + Assert.AreEqual(homerPoint, results.[0].Coord) |
| 69 | + |
| 70 | +// Same functional intent as FindPointByParameterSameAs above, but using |
| 71 | +// the vendor/imagine escape hatch instead of unsafe_inject_raw. The |
| 72 | +// IMAGINE clause is typechecked against Rezoom's dialect, informing the |
| 73 | +// typechecker that @needle is a Point2D and that the result set has a |
| 74 | +// Coord column. The vendor body runs PG-native SQL — including ~= and |
| 75 | +// the `{@needle}` extra-brace param reference — and the user-type |
| 76 | +// translation pipeline still fires for @needle on the parameter side, |
| 77 | +// so the caller passes a real Point2D, not a NpgsqlPoint, from F#. |
| 78 | +type FindPointByParameterVendor = SQL<""" |
| 79 | +insert into UserLocations(UserId, Coord) |
| 80 | +values((select Id from Users where Name = 'Homer'), @homer); |
| 81 | +insert into UserLocations(UserId, Coord) |
| 82 | +values((select Id from Users where Name = 'Marge'), @bart); |
| 83 | +vendor postgres { |
| 84 | + select Coord from UserLocations where coord ~= {@needle} |
| 85 | +} imagine { |
| 86 | + select Coord from UserLocations where Coord = @needle |
| 87 | +}; |
| 88 | +"""> |
| 89 | + |
| 90 | +[<Test>] |
| 91 | +let ``select with Point2D parameter equality matches via vendor ~= with IMAGINE`` () = |
| 92 | + // No manual NpgsqlPoint conversion: @needle stays typed as Point2D |
| 93 | + // all the way through Rezoom, so the user-type SQLParameterDbType |
| 94 | + // attribute is applied to the actual parameter being compared. |
| 95 | + let results = |
| 96 | + FindPointByParameterVendor.Command |
| 97 | + ( bart = bartPoint |
| 98 | + , homer = homerPoint |
| 99 | + , needle = homerPoint |
| 100 | + ) |
| 101 | + |> runOnTestData |
| 102 | + Assert.AreEqual(1, results.Count) |
| 103 | + Assert.AreEqual(homerPoint, results.[0].Coord) |
0 commit comments