packages feed

esqueleto 3.5.14.0 → 3.6.0.3

raw patch · 20 files changed

Files

changelog.md view
@@ -1,3 +1,114 @@+3.6.0.3+=======+- @parsonsmatt+    - [#438](https://github.com/bitemyapp/esqueleto/pull/438)+        - Relax the upper bound on `time` to `< 1.17`, allowing+          `time-1.14`, `time-1.15`, and `time-1.16`.++3.6.0.2+=======+- @parsonsmatt+    - [#436](https://github.com/bitemyapp/esqueleto/pull/436)+        - After a set operation, resume ident allocation from the union of+          both branches' ident states (previously the left branch's state+          only), so the enclosing query can never reuse an ident either+          branch consumed.++3.6.0.1+=======+- @parsonsmatt+    - [#435](https://github.com/bitemyapp/esqueleto/pull/435)+        - Fix two sources of duplicate column aliases, which made outer+          references to the affected columns fail on PostgreSQL with+          `42702 column reference is ambiguous` and silently select the wrong+          column on SQLite: set operations whose branches allocate different+          numbers of aliases (a variant of+          [#299](https://github.com/bitemyapp/esqueleto/issues/299)), and+          selecting the same inner-scope reference more than once in a single+          select list.+        - Support declaring a CTE (`with`) inside a set operation branch by+          parenthesizing the branch; previously this rendered invalid SQL.+          SQLite does not support parenthesized set operation operands, so+          this remains PostgreSQL/MySQL-only.++3.6.0.0+=======+- @parsonsmatt+    - [#422](https://github.com/bitemyapp/esqueleto/pull/422)+        - The instance of `HasField` for `SqlExpr (Maybe (Entity a))` joins+          `Maybe` values together. This means that if you `leftJoin` a table+          with a `Maybe` column, the result will be a `SqlExpr (Value (Maybe+          typ))`, instead of `SqlExpr (Value (Maybe (Maybe typ)))`.+        - To make this a less breaking change, `joinV` has been given a similar+          behavior. If the input type to `joinV` is `Maybe (Maybe typ)`, then+          the result becomes `Maybe typ`. If the input type is `Maybe typ`, then+          the output is also `Maybe typ`. The `joinV'` function is given as an+          alternative with monomorphic behavior.+        - The `just` function is also modified to avoid nesting `Maybe`.+          Likewise, `just'` is provided to give monomorphic behavior.+        - `subSelect`, `max_`, `min_`, and `coalesce` were all+          given `Nullable` output types as well. This should help to reduce the+          incidence of nested `Maybe`.+        - The operator `??.` was introduced which can do nested `Maybe`. You may+          want this if you have type inference issues with `?.` combining+          `Maybe`.+    - [#420](https://github.com/bitemyapp/esqueleto/pull/420)+        - Add a fixity declaration to `?.`+    - [#412](https://github.com/bitemyapp/esqueleto/pull/412)+        - The `random_` and `rand` functions (deprecated in 2.6.0) have been+          removed. Please refer to the database specific ones (ie+          `Database.Esqueleto.PostgreSQL` etc)+        - The `sub_select` function (deprecated in 3.2.0) has been removed.+          Please use the safer variants like `subSelect`, `subSelectMaybe`, etc.+        - The `ToAliasT` and `ToAliasReferenceT` types has been removed after having been deprecated in 3.4.0.1.+        - The `Union` type (deprecated in 3.4) was removed. Please use `union_`+          instead.+        - The `UnionAll` type (deprecated in 3.4) was removed. Please use+          `unionAll_` instead.+        - The `Except` type  (deprecated in 3.4) was removed. Please use+          `except_` instead.+        - The `Intersect` type  (deprecated in 3.4) was removed. Please use+          `intersect_` instead.+        - The `SubQuery` type (deprecated in 3.4) was removed. You do not need+          to tag subqueries to use them in `from` clauses.+        - The `SelectQuery` type (deprecated in 3.4) was removed. You do not+          need to tag `SqlQuery` values with `SelectQuery`.+    - [#287](https://github.com/bitemyapp/esqueleto/pull/278)+        - Deprecate `distinctOn` and `distinctOnOrderBy`. Use the variants+          defined in `PostgreSQL` module instead. The signature has changed, but+          the refactor is straightforward:+          ```+              -- old:+              p <- from $ table+              distinctOn [don x] $ do+                  pure p++              -- new:+              p <- from $ table+              distinctOn [don x]+              pure p+          ```+    - [#301](https://github.com/bitemyapp/esqueleto/pull/301)+        - Postgresql `upsert` and `upsertBy` now require a `NonEmpty` list of+          updates. If you want to provide an empty list of updates, you'll need+          to use `upsertMaybe` and `upsertMaybeBe` instead. Postgres does not+          return rows from the database if no updates are performed.+    - [#413](https://github.com/bitemyapp/esqueleto/pull/413)+        - The ability to `coerce` `SqlExpr` was removed. Instead, use+          `veryUnsafeCoerceSqlExpr`. See the documentation on+          `veryUnsafeCoerceSqlExpr` for safe use example.+        - `unsafeCeorceSqlExpr` is provided as an option when the underlying+          Haskell types are coercible. This is still unsafe, as different+          `PersistFieldSql` instances may be at play.+    - [#420](https://github.com/bitemyapp/esqueleto/pull/421)+        - The `LockingKind` constructors are deprecated, and will be removed+          from non-Internal modules in a future release. Smart constructors+          replace them, and you may need to import them from a different+          database-specific module.+    - [#425](https://github.com/bitemyapp/esqueleto/pull/425)+        - `fromBaseId` is introduced as the inverse of `toBaseId`.+        - `toBaseIdMaybe` and `fromBaseIdMaybe` are introduced.+ 3.5.14.0 ======== - @parsonsmatt
esqueleto.cabal view
@@ -1,8 +1,7 @@ cabal-version: 1.12  name:           esqueleto--version:        3.5.14.0+version:        3.6.0.3 synopsis:       Type-safe EDSL for SQL queries on persistent backends. description:    @esqueleto@ is a bare bones, type-safe EDSL for SQL queries that works with unmodified @persistent@ SQL backends.  Its language closely resembles SQL, so you don't have to learn new concepts, just new syntax, and it's fairly easy to predict the generated SQL and optimize it for your backend. Most kinds of errors committed when writing SQL are caught as compile-time errors---although it is possible to write type-checked @esqueleto@ queries that fail at runtime.                 .@@ -25,7 +24,7 @@  source-repository head     type: git-    location: git://github.com/bitemyapp/esqueleto.git+    location: https://github.com/bitemyapp/esqueleto.git  library     exposed-modules:@@ -53,7 +52,7 @@     hs-source-dirs:         src/     build-depends:-        base >=4.8 && <5.0+        base >=4.12 && <5.0       , aeson >=1.0       , attoparsec >= 0.13 && < 0.15       , blaze-html@@ -66,7 +65,7 @@       , tagged >=0.2       , template-haskell       , text >=0.11 && <2.2-      , time >=1.5.0.1 && <=1.13+      , time >=1.5.0.1 && <1.17       , transformers >=0.2       , unliftio       , unordered-containers >=0.2
src/Database/Esqueleto.hs view
@@ -51,14 +51,14 @@     -- $gettingstarted      -- * @esqueleto@'s Language-    where_, on, groupBy, orderBy, rand, asc, desc, limit, offset+    where_, on, groupBy, orderBy, asc, desc, limit, offset              , distinct, distinctOn, don, distinctOnOrderBy, having, locking-             , sub_select, (^.), (?.)-             , val, isNothing, just, nothing, joinV, withNonNull+             , (^.), (?.)+             , val, isNothing, just, just', nothing, joinV, joinV', withNonNull              , countRows, count, countDistinct              , not_, (==.), (>=.), (>.), (<=.), (<.), (!=.), (&&.), (||.)              , between, (+.), (-.), (/.), (*.)-             , random_, round_, ceiling_, floor_+             , round_, ceiling_, floor_              , min_, max_, sum_, avg_, castNum, castNumM              , coalesce, coalesceDefault              , lower_, upper_, trim_, ltrim_, rtrim_, length_, left_, right_@@ -66,7 +66,7 @@              , subList_select, valList, justList              , in_, notIn, exists, notExists              , set, (=.), (+=.), (-=.), (*=.), (/=.)-             , case_, toBaseId+             , case_, toBaseId, fromBaseId, toBaseIdMaybe, fromBaseIdMaybe   , subSelect   , subSelectMaybe   , subSelectCount@@ -83,6 +83,8 @@   , OrderBy   , DistinctOn   , LockingKind(..)+  , forUpdate+  , forUpdateSkipLocked   , LockableEntity(..)   , SqlString     -- ** Joins
src/Database/Esqueleto/Experimental.hs view
@@ -22,7 +22,6 @@       from     , table     , Table(..)-    , SubQuery(..)     , selectQuery      -- ** Joins@@ -40,14 +39,9 @@       -- ** Set Operations       -- $sql-set-operations     , union_-    , Union(..)     , unionAll_-    , UnionAll(..)     , except_-    , Except(..)     , intersect_-    , Intersect(..)-    , pattern SelectQuery        -- ** Common Table Expressions     , with@@ -57,18 +51,16 @@     , From(..)     , ToMaybe(..)     , ToAlias(..)-    , ToAliasT     , ToAliasReference(..)-    , ToAliasReferenceT     , ToSqlSetOperation(..)     , SqlSelect+    , Nullable      -- * The Normal Stuff     , where_     , groupBy     , groupBy_     , orderBy-    , rand     , asc     , desc     , limit@@ -80,8 +72,9 @@     , distinctOnOrderBy     , having     , locking+    , forUpdate+    , forUpdateSkipLocked -    , sub_select     , (^.)     , (?.) @@ -113,7 +106,6 @@     , (/.)     , (*.) -    , random_     , round_     , ceiling_     , floor_@@ -162,6 +154,9 @@      , case_     , toBaseId+    , toBaseIdMaybe+    , fromBaseId+    , fromBaseIdMaybe     , subSelect     , subSelectMaybe     , subSelectCount
src/Database/Esqueleto/Experimental/From.hs view
@@ -98,10 +98,6 @@                )  -{-# DEPRECATED SubQuery "/Since: 3.4.0.0/ - It is no longer necessary to tag 'SqlQuery' values with @SubQuery@" #-}-newtype SubQuery a = SubQuery a-instance (SqlSelect a r, ToAlias a, ToAliasReference a) => ToFrom (SubQuery (SqlQuery a)) a where-    toFrom (SubQuery q) = selectQuery q instance (SqlSelect a r, ToAlias a, ToAliasReference a) => ToFrom (SqlQuery a) a where     toFrom = selectQuery 
src/Database/Esqueleto/Experimental/From/SqlSetOperation.hs view
@@ -56,8 +56,11 @@                   case p of                     Parens -> Parens                     Never ->+                      -- A WITH clause inside a branch is only valid SQL+                      -- when the branch is parenthesized.                       if (sdLimitClause sideData) /= mempty-                          || length (sdOrderByClause sideData) > 0 then+                          || length (sdOrderByClause sideData) > 0+                          || not (null (sdCteClause sideData)) then                         Parens                       else                         Never@@ -68,17 +71,22 @@ mkSetOperation :: (ToSqlSetOperation a a', ToSqlSetOperation b a')                => TLB.Builder -> a -> b -> SqlSetOperation a' mkSetOperation operation lhs rhs = SqlSetOperation $ \p -> do-    state <- Q $ lift S.get+    stateBefore <- Q $ lift S.get     (leftValue, leftClause) <- unSqlSetOperation (toSqlSetOperation lhs) p-    Q $ lift $ S.put state+    stateAfterLeft <- Q $ lift S.get+    -- Rewind so both branches allocate the same idents; they render as+    -- sibling SELECTs, so identical idents cannot collide.+    Q $ lift $ S.put stateBefore     (_, rightClause) <- unSqlSetOperation (toSqlSetOperation rhs) p+    stateAfterRight <- Q $ lift S.get+    -- Resume from the union of both branches' states, so the enclosing+    -- query can never reuse an ident either branch consumed: the left+    -- branch's idents escape in 'leftValue' (a variant of issue #299),+    -- and the right branch's, while branch-internal, shouldn't be+    -- handed out again either.+    Q $ lift $ S.put (IdentState (inUse stateAfterLeft <> inUse stateAfterRight))     pure (leftValue, \info -> leftClause info <> (operation, mempty) <> rightClause info) -{-# DEPRECATED Union "/Since: 3.4.0.0/ - Use the 'union_' function instead of the 'Union' data constructor" #-}-data Union a b = a `Union` b-instance ToSqlSetOperation a a' => ToSqlSetOperation (Union a a) a' where-    toSqlSetOperation (Union a b) = union_ a b- -- | Overloaded @union_@ function to support use in both 'SqlSetOperation' -- and 'withRecursive' --@@ -102,30 +110,10 @@   => UnionAll_ (a -> b -> res) where     unionAll_ = mkSetOperation " UNION ALL " -{-# DEPRECATED UnionAll "/Since: 3.4.0.0/ - Use the 'unionAll_' function instead of the 'UnionAll' data constructor" #-}-data UnionAll a b = a `UnionAll` b-instance ToSqlSetOperation a a' => ToSqlSetOperation (UnionAll a a) a' where-    toSqlSetOperation (UnionAll a b) = unionAll_ a b--{-# DEPRECATED Except "/Since: 3.4.0.0/ - Use the 'except_' function instead of the 'Except' data constructor" #-}-data Except a b = a `Except` b-instance ToSqlSetOperation a a' => ToSqlSetOperation (Except a a) a' where-    toSqlSetOperation (Except a b) = except_ a b- -- | @EXCEPT@ SQL set operation. Can be used as an infix function between 'SqlQuery' values. except_ :: (ToSqlSetOperation a a', ToSqlSetOperation b a') => a -> b -> SqlSetOperation a' except_ = mkSetOperation " EXCEPT " -{-# DEPRECATED Intersect "/Since: 3.4.0.0/ - Use the 'intersect_' function instead of the 'Intersect' data constructor" #-}-data Intersect a b = a `Intersect` b-instance ToSqlSetOperation a a' => ToSqlSetOperation (Intersect a a) a' where-    toSqlSetOperation (Intersect a b) = intersect_ a b- -- | @INTERSECT@ SQL set operation. Can be used as an infix function between 'SqlQuery' values. intersect_ :: (ToSqlSetOperation a a', ToSqlSetOperation b a') => a -> b -> SqlSetOperation a' intersect_ = mkSetOperation " INTERSECT "--{-# DEPRECATED SelectQuery "/Since: 3.4.0.0/ - It is no longer necessary to tag 'SqlQuery' values with @SelectQuery@" #-}-pattern SelectQuery :: p -> p-pattern SelectQuery a = a-
src/Database/Esqueleto/Experimental/ToAlias.hs view
@@ -8,16 +8,18 @@ import Database.Esqueleto.Internal.Internal hiding (From, from, on) import Database.Esqueleto.Internal.PersistentImport -{-# DEPRECATED ToAliasT "This type alias doesn't do anything. Please delete it. Will be removed in the next release." #-}-type ToAliasT a = a- -- Tedious tuple magic class ToAlias a where     toAlias :: a -> SqlQuery a  instance ToAlias (SqlExpr (Value a)) where     toAlias e@(ERaw m f)-      | Just _ <- sqlExprMetaAlias m = pure e+      -- Idempotent for values aliased in the current scope, but a reference+      -- into an inner scope needs a fresh alias: the same reference can+      -- appear several times in one select list, and re-exporting the inner+      -- column name each time would produce duplicate column names.+      | Just _ <- sqlExprMetaAlias m+      , not (sqlExprMetaIsReference m) = pure e       | otherwise = do             ident <- newIdentFor (DBName "v")             pure $ ERaw noMeta{sqlExprMetaAlias = Just ident} f
src/Database/Esqueleto/Experimental/ToAliasReference.hs view
@@ -5,13 +5,9 @@ module Database.Esqueleto.Experimental.ToAliasReference     where -import Data.Coerce import Database.Esqueleto.Internal.Internal hiding (From, from, on) import Database.Esqueleto.Internal.PersistentImport -{-# DEPRECATED ToAliasReferenceT "This type alias doesn't do anything. Please delete it. Will be removed in the next release." #-}-type ToAliasReferenceT a = a- -- more tedious tuple magic class ToAliasReference a where     toAliasReference :: Ident -> a -> SqlQuery a@@ -31,7 +27,7 @@  instance ToAliasReference (SqlExpr (Maybe (Entity a))) where     toAliasReference aliasSource e =-        coerce <$> toAliasReference aliasSource (coerce e :: SqlExpr (Entity a))+        veryUnsafeCoerceSqlExpr <$> toAliasReference aliasSource (veryUnsafeCoerceSqlExpr e :: SqlExpr (Entity a))   instance (ToAliasReference a, ToAliasReference b) => ToAliasReference (a, b) where
src/Database/Esqueleto/Experimental/ToMaybe.hs view
@@ -2,14 +2,13 @@ {-# LANGUAGE TypeFamilies #-}  module Database.Esqueleto.Experimental.ToMaybe+    ( module Database.Esqueleto.Experimental.ToMaybe+    , Nullable+    )     where  import Database.Esqueleto.Internal.Internal hiding (From(..), from, on) import Database.Esqueleto.Internal.PersistentImport (Entity(..))--type family Nullable a where-    Nullable (Maybe a) = a-    Nullable a =  a  class ToMaybe a where     type ToMaybeT a
src/Database/Esqueleto/Internal/Internal.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE CPP #-}+{-# language AllowAmbiguousTypes #-}+{-# LANGUAGE RoleAnnotations #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-}@@ -35,6 +37,8 @@ -- tracker so we can safely support it. module Database.Esqueleto.Internal.Internal where +import Data.Typeable (TypeRep, typeRep)+import Data.Coerce (Coercible) import Control.Applicative ((<|>)) import Control.Arrow (first, (***)) import Control.Exception (Exception, throw, throwIO)@@ -126,7 +130,7 @@     maybelize         :: PreprocessedFrom (SqlExpr (Entity a))         -> PreprocessedFrom (SqlExpr (Maybe (Entity a)))-    maybelize (PreprocessedFrom e f') = PreprocessedFrom (coerce e) f'+    maybelize (PreprocessedFrom e f') = PreprocessedFrom (veryUnsafeCoerceSqlExpr e) f'  -- | (Internal) Do a @JOIN@. fromJoin@@ -362,12 +366,14 @@ distinctOn :: [SqlExpr DistinctOn] -> SqlQuery a -> SqlQuery a distinctOn exprs act = Q (W.tell mempty { sdDistinctClause = DistinctOn exprs }) >> act +{-# DEPRECATED distinctOn "This function is deprecated, as it is only supported in Postgresql. Please use the variant in `Database.Esqueleto.PostgreSQL` instead." #-}+ -- | Erase an SqlExpression's type so that it's suitable to -- be used by 'distinctOn'. -- -- @since 2.2.4 don :: SqlExpr (Value a) -> SqlExpr DistinctOn-don = coerce+don = veryUnsafeCoerceSqlExpr  -- | A convenience function that calls both 'distinctOn' and -- 'orderBy'.  In other words,@@ -401,11 +407,7 @@               $ TLB.toLazyText b             , vals ) --- | @ORDER BY random()@ clause.------ @since 1.3.10-rand :: SqlExpr OrderBy-rand = ERaw noMeta $ \_ _ -> ("RANDOM()", [])+{-# DEPRECATED distinctOnOrderBy "This function is deprecated, as it is only supported in Postgresql. Please use the function defined in `Database.Esqueleto.PostgreSQL` instead." #-}  -- | @HAVING@. --@@ -430,29 +432,6 @@ putLocking :: LockingClause -> SqlQuery () putLocking clause = Q $ W.tell mempty { sdLockingClause = clause } -{-#-  DEPRECATED-    sub_select-    "sub_select \n \-sub_select is an unsafe function to use. If used with a SqlQuery that \n \-returns 0 results, then it may return NULL despite not mentioning Maybe \n \-in the return type. If it returns more than 1 result, then it will throw a \n \-SQL error.\n\n Instead, consider using one of the following alternatives: \n \-- subSelect: attaches a LIMIT 1 and the Maybe return type, totally safe.  \n \-- subSelectMaybe: Attaches a LIMIT 1, useful for a query that already \n \-  has a Maybe in the return type. \n \-- subSelectCount: Performs a count of the query - this is always safe. \n \-- subSelectUnsafe: Performs no checks or guarantees. Safe to use with \n \-  countRows and friends."-  #-}--- | Execute a subquery @SELECT@ in an SqlExpression.  Returns a--- simple value so should be used only when the @SELECT@ query--- is guaranteed to return just one row.------ Deprecated in 3.2.0.-sub_select :: PersistField a => SqlQuery (SqlExpr (Value a)) -> SqlExpr (Value a)-sub_select         = sub SELECT- -- | Execute a subquery @SELECT@ in a 'SqlExpr'. The query passed to this -- function will only return a single result - it has a @LIMIT 1@ passed in to -- the query to make it safe, and the return type is 'Maybe' to indicate that@@ -470,9 +449,9 @@ -- -- @since 3.2.0 subSelect-  :: PersistField a+  :: (PersistField a, NullableFieldProjection a a')   => SqlQuery (SqlExpr (Value a))-  -> SqlExpr (Value (Maybe a))+  -> SqlExpr (Value (Maybe a')) subSelect query = just (subSelectUnsafe (query <* limit 1))  -- | Execute a subquery @SELECT@ in a 'SqlExpr'. This function is a shorthand@@ -623,12 +602,28 @@     where_ $ not_ $ isNothing field     f $ veryUnsafeCoerceSqlExprValue field +-- | Project an 'EntityField' of a nullable entity. The result type will be+-- 'Nullable', meaning that nested 'Maybe' won't be produced here.+--+-- As of v3.6.0.0, this will attempt to combine nested 'Maybe'. If you want to+-- keep nested 'Maybe', then see '??.'.+(?.) :: (PersistEntity val , PersistField typ)+    => SqlExpr (Maybe (Entity val))+    -> EntityField val typ+    -> SqlExpr (Value (Maybe (Nullable typ)))+ent ?. field = veryUnsafeCoerceSqlExprValue (ent ??. field)+ -- | Project a field of an entity that may be null.-(?.) :: ( PersistEntity val , PersistField typ)+--+-- This variant will produce a nested 'Maybe' if you select a 'Maybe' column.+-- If you want to collapse 'Maybe', see '?.'.+--+-- @since 3.6.0.0+(??.) :: ( PersistEntity val , PersistField typ)     => SqlExpr (Maybe (Entity val))     -> EntityField val typ     -> SqlExpr (Value (Maybe typ))-ERaw m f ?. field = just (ERaw m f ^. field)+ERaw m f ??. field = just (ERaw m f ^. field)  -- | Lift a constant value from Haskell-land to the query. val  :: PersistField typ => typ -> SqlExpr (Value typ)@@ -680,19 +675,53 @@ -- | Analogous to 'Just', promotes a value of type @typ@ into -- one of type @Maybe typ@.  It should hold that @'val' . Just -- === just . 'val'@.-just :: SqlExpr (Value typ) -> SqlExpr (Value (Maybe typ))+--+-- This function will try not to produce a nested 'Maybe'. This is in accord+-- with how SQL represents @NULL@. That means that @'just' . 'just' = 'just'@.+-- This behavior was changed in v3.6.0.0. If you want to produce nested 'Maybe',+-- see 'just''.+just+    :: (NullableFieldProjection typ typ')+    => SqlExpr (Value typ) -> SqlExpr (Value (Maybe typ')) just = veryUnsafeCoerceSqlExprValue +-- | Like 'just', but this function does not try to collapse nested 'Maybe'.+-- This may be useful if you have type inference problems with 'just'.+--+-- @since 3.6.0.0+just' :: SqlExpr (Value typ) -> SqlExpr (Value (Maybe typ))+just' = veryUnsafeCoerceSqlExprValue+ -- | @NULL@ value. nothing :: SqlExpr (Value (Maybe typ)) nothing = unsafeSqlValue "NULL"  -- | Join nested 'Maybe's in a 'Value' into one. This is useful when -- calling aggregate functions on nullable fields.-joinV :: SqlExpr (Value (Maybe (Maybe typ))) -> SqlExpr (Value (Maybe typ))+--+-- As of v3.6.0.0, this function will attempt to work on both @'SqlExpr'+-- ('Value' ('Maybe' a))@ as well as @'SqlExpr' ('Value' ('Maybe' ('Maybe' a)))@+-- inputs to make transitioning to 'NullableFieldProjection' easier. This may+-- make type inference worse in some cases. If you want the monomorphic variant,+-- see 'joinV''+joinV+    :: (NullableFieldProjection typ typ')+    => SqlExpr (Value (Maybe typ))+    -> SqlExpr (Value (Maybe typ')) joinV = veryUnsafeCoerceSqlExprValue +-- | Like 'joinV', but monomorphic: the input type only works on @'SqlExpr'+-- ('Value' (Maybe (Maybe a)))@.+--+-- This function may be useful if you have type inference issues with 'joinV'.+--+-- @since 3.6.0.0+joinV'+    :: SqlExpr (Value (Maybe (Maybe typ)))+    -> SqlExpr (Value (Maybe typ))+joinV' = veryUnsafeCoerceSqlExprValue + countHelper :: Num a => TLB.Builder -> TLB.Builder -> SqlExpr (Value typ) -> SqlExpr (Value a) countHelper open close v =     case v of@@ -886,9 +915,6 @@ between :: PersistField a => SqlExpr (Value a) -> (SqlExpr (Value a), SqlExpr (Value a)) -> SqlExpr (Value Bool) a `between` (b, c) = a >=. b &&. a <=. c -random_  :: (PersistField a, Num a) => SqlExpr (Value a)-random_  = unsafeSqlValue "RANDOM()"- round_   :: (PersistField a, Num a, PersistField b, Num b) => SqlExpr (Value a) -> SqlExpr (Value b) round_   = unsafeSqlFunction "ROUND" @@ -900,13 +926,22 @@  sum_     :: (PersistField a, PersistField b) => SqlExpr (Value a) -> SqlExpr (Value (Maybe b)) sum_     = unsafeSqlFunction "SUM"-min_     :: (PersistField a) => SqlExpr (Value a) -> SqlExpr (Value (Maybe a))-min_     = unsafeSqlFunction "MIN"-max_     :: (PersistField a) => SqlExpr (Value a) -> SqlExpr (Value (Maybe a))-max_     = unsafeSqlFunction "MAX"-avg_     :: (PersistField a, PersistField b) => SqlExpr (Value a) -> SqlExpr (Value (Maybe b))-avg_     = unsafeSqlFunction "AVG" +min_+    :: (PersistField a)+    => SqlExpr (Value a)+    -> SqlExpr (Value (Maybe (Nullable a)))+min_ = unsafeSqlFunction "MIN"++max_+    :: (PersistField a)+    => SqlExpr (Value a)+    -> SqlExpr (Value (Maybe (Nullable a)))+max_ = unsafeSqlFunction "MAX"++avg_ :: (PersistField a, PersistField b) => SqlExpr (Value a) -> SqlExpr (Value (Maybe b))+avg_ = unsafeSqlFunction "AVG"+ -- | Allow a number of one type to be used as one of another -- type via an implicit cast.  An explicit cast is not made, -- this function changes only the types on the Haskell side.@@ -940,7 +975,10 @@ -- documentation. -- -- @since 1.4.3-coalesce :: PersistField a => [SqlExpr (Value (Maybe a))] -> SqlExpr (Value (Maybe a))+coalesce+    :: (PersistField a, NullableFieldProjection a a')+    => [SqlExpr (Value (Maybe a))]+    -> SqlExpr (Value (Maybe a')) coalesce              = unsafeSqlFunctionParens "COALESCE"  -- | Like @coalesce@, but takes a non-nullable SqlExpression@@ -996,12 +1034,15 @@  -- | @ILIKE@ operator (case-insensitive @LIKE@). ----- Supported by PostgreSQL only.+-- Supported by PostgreSQL only. Deprecated in version 3.6.0 in favor of the+-- version available from "Database.Esqueleto.PostgreSQL". -- -- @since 2.2.3 ilike :: SqlString s => SqlExpr (Value s) -> SqlExpr (Value s) -> SqlExpr (Value Bool) ilike   = unsafeSqlBinOp    " ILIKE " +{-# DEPRECATED ilike "Since 3.6.0: `ilike` is only supported on Postgres. Please import it from 'Database.Esqueleto.PostgreSQL." #-}+ -- | The string @'%'@.  May be useful while using 'like' and -- concatenation ('concat_' or '++.', depending on your -- database).  Note that you always have to type the parenthesis,@@ -1014,13 +1055,19 @@ (%)     = unsafeSqlValue    "'%'"  -- | The @CONCAT@ function with a variable number of--- parameters.  Supported by MySQL and PostgreSQL.+-- parameters.  Supported by MySQL and PostgreSQL. SQLite supports this in+-- versions after 3.44.0, and @persistent-sqlite@ supports this in versions+-- @2.13.3.0@ and after. concat_ :: SqlString s => [SqlExpr (Value s)] -> SqlExpr (Value s) concat_ = unsafeSqlFunction "CONCAT"  -- | The @||@ string concatenation operator (named after -- Haskell's '++' in order to avoid naming clash with '||.').+-- -- Supported by SQLite and PostgreSQL.+--+-- MySQL support requires setting the SQL mode to @PIPES_AS_CONCAT@ or @ANSI@+-- - see <https://stackoverflow.com/a/24777235 this StackOverflow answer>. (++.) :: SqlString s => SqlExpr (Value s) -> SqlExpr (Value s) -> SqlExpr (Value s) (++.)   = unsafeSqlBinOp    " || " @@ -1167,13 +1214,13 @@ --        'from' $ \\p -> do --        'where_' (p '^.' PersonName '==.' 'val' \"Mike\")) --      'then_'---        ('sub_select' $+--        ('subSelect' $ --        'from' $ \\v -> do --        let sub = --                'from' $ \\c -> do --                'where_' (c '^.' PersonName '==.' 'val' \"Mike\") --                return (c '^.' PersonFavNum)---        'where_' (v '^.' PersonFavNum >. 'sub_select' sub)+--        'where_' ('just' (v '^.' PersonFavNum) >. 'subSelect' sub) --        return $ 'count' (v '^.' PersonName) +. 'val' (1 :: Int)) ] --    ('else_' $ 'val' (-1)) -- @@@ -1239,12 +1286,67 @@ toBaseId :: ToBaseId ent => SqlExpr (Value (Key ent)) -> SqlExpr (Value (Key (BaseEnt ent))) toBaseId = veryUnsafeCoerceSqlExprValue -{-# DEPRECATED random_ "Since 2.6.0: `random_` is not uniform across all databases! Please use a specific one such as 'Database.Esqueleto.PostgreSQL.random_', 'Database.Esqueleto.MySQL.random_', or 'Database.Esqueleto.SQLite.random_'" #-}+-- | Like 'toBaseId', but works on 'Maybe' keys.+--+-- @since 3.6.0.0+toBaseIdMaybe+    :: (ToBaseId ent)+    => SqlExpr (Value (Maybe (Key ent)))+    -> SqlExpr (Value (Maybe (Key (BaseEnt ent))))+toBaseIdMaybe = veryUnsafeCoerceSqlExprValue -{-# DEPRECATED rand "Since 2.6.0: `rand` ordering function is not uniform across all databases! To avoid accidental partiality it will be removed in the next major version." #-}+-- | The inverse of 'toBaseId'. Note that this is somewhat less "safe" than+-- 'toBaseId'. Calling 'toBaseId' will usually mean that a foreign key+-- constraint is present that guarantees the presence of the base ID.+-- 'fromBaseId' has no such guarantee. Consider the code example given in+-- 'toBaseId':+--+-- @+-- Bar+--   barNum Int+-- Foo+--   bar BarId+--   fooNum Int+--   Primary bar+-- @+--+-- @+-- instance ToBaseId Foo where+--   type BaseEnt Foo = Bar+--   toBaseIdWitness barId = FooKey barId+-- @+--+-- The type of 'toBaseId' for @Foo@ would be:+--+-- @+-- toBaseId :: SqlExpr (Value FooId) -> SqlExpr (Value BarId)+-- @+--+-- The foreign key constraint on @Foo@ means that every @FooId@ points to+-- a @BarId@ in the database. However, 'fromBaseId' will not have this:+--+-- @+-- fromBaseId :: SqlExpr (Value BarId) -> SqlExpr (Value FooId)+-- @+--+-- @since 3.6.0.0+fromBaseId+    :: (ToBaseId ent)+    => SqlExpr (Value (Key (BaseEnt ent)))+    -> SqlExpr (Value (Key ent))+fromBaseId = veryUnsafeCoerceSqlExprValue +-- |  As 'fromBaseId', but works on 'Maybe' keys.+--+-- @since 3.6.0.0+fromBaseIdMaybe+    :: (ToBaseId ent)+    => SqlExpr (Value (Maybe (Key (BaseEnt ent))))+    -> SqlExpr (Value (Maybe (Key ent)))+fromBaseIdMaybe = veryUnsafeCoerceSqlExprValue+ -- Fixity declarations-infixl 9 ^.+infixl 9 ^., ?. infixl 7 *., /. infixl 6 +., -. infixr 5 ++.@@ -1591,6 +1693,32 @@       --       -- @since 2.2.7 +{-# DEPRECATED ForUpdate, ForUpdateSkipLocked "The constructors for 'LockingKind' are deprecated in v3.6.0.0. Instead, please refer to the smart constructors 'forUpdate' and 'forUpdateSkipLocked'." #-}+{-# DEPRECATED ForShare "The constructors for 'LockingKind' are deprecated in v3.6.0.0. Instead, please refer to the smart constructor 'forShare' exported from Database.Esqueleto.PostgreSQL." #-}+{-# DEPRECATED LockInShareMode "The constructors for 'LockingKind' are deprecated in v3.6.0.0. Instead, please refer to the smart constructors 'lockInShareMode' exported from Database.Esqueleto.MySQL." #-}++-- | @FOR UPDATE@ syntax.+--+-- Usage:+--+-- @+--  'locking' 'forUpdate'+-- @+--+-- @since 3.6.0.0+forUpdate :: LockingKind+forUpdate = ForUpdate++-- | @FOR UPDATE SKIP LOCKED@ syntax.+--+-- @+--  'locking' 'forUpdateSkipLocked'+-- @+--+-- @since 3.6.0.0+forUpdateSkipLocked :: LockingKind+forUpdateSkipLocked = ForUpdateSkipLocked+ -- | Postgres specific locking, used only internally -- -- @since 3.5.9.0@@ -2340,12 +2468,12 @@ entityAsValue     :: SqlExpr (Entity val)     -> SqlExpr (Value (Entity val))-entityAsValue = coerce+entityAsValue = veryUnsafeCoerceSqlExpr  entityAsValueMaybe     :: SqlExpr (Maybe (Entity val))     -> SqlExpr (Value (Maybe (Entity val)))-entityAsValueMaybe = coerce+entityAsValueMaybe = veryUnsafeCoerceSqlExpr  -- | An expression on the SQL backend. --@@ -2357,6 +2485,51 @@ -- interpolated by the SQL backend. data SqlExpr a = ERaw SqlExprMeta (NeedParens -> IdentInfo -> (TLB.Builder, [PersistValue])) +type role SqlExpr nominal++-- | The type @'SqlExpr' a@ represents a SQL expression that evaluates to+-- a value that can be parsed in Haskell to an @a@. There are often many+-- underlying SQL values that can parse exactly. The function+-- 'veryUnsafeCoerceSqlExpr' allows you to change this type.+--+-- There is no guarantee that the result works! To be safe, you want to provide+-- a local helper function that calls this at a tested type.+--+-- As an example, you may know that two types share an identical SQL+-- representation, and can be parsed exactly the same way. Perhaps you have+-- a @data SomeEnum@ which you represent as a @TEXT@ in Postgres, and you+-- want to treat it as a @TEXT@. You could define a top-level+-- type-restricted alias to this which allows this to be done safely:+--+-- @+--  enumToText :: SqlExpr (Value SomeEnum) -> SqlExpr (Value Text)+--  enumToText = veryUnsafeCoerceSqlExpr+-- @+--+-- Note that this is fragile: if you change the encoding of 'SomeEnum' to+-- be anything other than 'Text', then your code will fail at runtime.+--+-- @since 3.6.0.0+veryUnsafeCoerceSqlExpr :: SqlExpr a -> SqlExpr b+veryUnsafeCoerceSqlExpr (ERaw m k) = ERaw m k++-- | While 'veryUnsafeCoerceSqlExpr' allows you to coerce anything at all, this+-- requires that the two types are 'Coercible' in Haskell. This is not truly+-- safe: after all, the point of @newtype@ is to allow you to provide different+-- instances of classes like 'PersistFieldSql' and 'SqlSelect'. Using this may+-- break your code if you change the underlying SQL representation.+--+-- @since 3.6.0.0+unsafeCoerceSqlExpr :: (Coercible a b) => SqlExpr a -> SqlExpr b+unsafeCoerceSqlExpr = veryUnsafeCoerceSqlExpr++-- | Like 'unsafeCoerceSqlExpr' but for the common case where you are+-- coercing a 'Value'.+--+-- @since 3.6.0.0+unsafeCoerceSqlExprValue :: (Coercible a b) => SqlExpr (Value a) -> SqlExpr (Value b)+unsafeCoerceSqlExprValue = veryUnsafeCoerceSqlExpr+ -- | Folks often want the ability to promote a Haskell function into the -- 'SqlExpr' expression language - and naturally reach for 'fmap'. -- Unfortunately, this is impossible. We cannot send *functions* to the@@ -2472,12 +2645,44 @@ -- -- @since 3.5.4.0 instance-    (PersistEntity rec, PersistField typ, SymbolToField sym rec typ)+    (PersistEntity rec, PersistField typ, PersistField typ', SymbolToField sym rec typ+    , NullableFieldProjection typ typ'+    , HasField sym (SqlExpr (Maybe (Entity rec))) (SqlExpr (Value (Maybe typ')))+    )   =>-    HasField sym (SqlExpr (Maybe (Entity rec))) (SqlExpr (Value (Maybe typ)))+    HasField sym (SqlExpr (Maybe (Entity rec))) (SqlExpr (Value (Maybe typ')))   where-    getField expr = expr ?. symbolToField @sym+    getField expr = veryUnsafeCoerceSqlExpr (expr ?. symbolToField @sym) +-- | The 'NullableFieldProjection' type is used to determine whether+-- a 'Maybe' should be stripped off or not. This is used in the 'HasField'+-- for @'SqlExpr' ('Maybe' ('Entity' a))@ to allow you to only have+-- a single level of 'Maybe'.+--+-- @+-- MyTable+--   column         Int Maybe+--   someTableId    SomeTableId+--+--  select $ do+--      (_ :& maybeMyTable) <-+--          from $ table @SomeTable+--              `leftJoin` table @MyTable+--                  `on` do+--                      \(someTable :& maybeMyTable) ->+--                          just someTable.id ==. maybeMyTable.someTableId+--        where_ $ maybeMyTable.column ==. just (val 10)+--        pure maybeMyTable+-- @+--+-- Without this class, projecting a field with type @'Maybe' typ@ would+-- have resulted in a @'SqlExpr' ('Value' ('Maybe' ('Maybe' typ)))@.+--+-- @since 3.6.0.0+class NullableFieldProjection typ typ'+instance {-# incoherent #-} (typ ~ typ') => NullableFieldProjection (Maybe typ) typ'+instance {-# overlappable #-} (typ ~ typ') => NullableFieldProjection typ typ'+ -- | Data type to support from hack data PreprocessedFrom a = PreprocessedFrom a FromClause @@ -2826,14 +3031,20 @@ -- | (Internal) Coerce a value's type from 'SqlExpr (Value a)' to -- 'SqlExpr (Value b)'.  You should /not/ use this function -- unless you know what you're doing!+--+-- This is an alias for 'veryUnsafeCoerceSqlExpr' with the type fixed to+-- 'Value'. veryUnsafeCoerceSqlExprValue :: SqlExpr (Value a) -> SqlExpr (Value b)-veryUnsafeCoerceSqlExprValue = coerce+veryUnsafeCoerceSqlExprValue = veryUnsafeCoerceSqlExpr   -- | (Internal) Coerce a value's type from 'SqlExpr (ValueList -- a)' to 'SqlExpr (Value a)'.  Does not work with empty lists.+--+-- This is an alias for 'veryUnsafeCoerceSqlExpr', with the type fixed to+-- 'ValueList' and 'Value'. veryUnsafeCoerceSqlExprValueList :: SqlExpr (ValueList a) -> SqlExpr (Value a)-veryUnsafeCoerceSqlExprValueList = coerce+veryUnsafeCoerceSqlExprValueList = veryUnsafeCoerceSqlExpr   ----------------------------------------------------------------------@@ -3257,7 +3468,7 @@                 first (("SELECT DISTINCT ON (" <>) . (<> ") "))                 $ uncommas' (processExpr <$> exprs)       where-        processExpr e = materializeExpr info (coerce e :: SqlExpr (Value a))+        processExpr e = materializeExpr info (veryUnsafeCoerceSqlExpr e :: SqlExpr (Value a))     withCols v = v <> sqlSelectCols info ret     plain    v = (v, []) @@ -3513,7 +3724,7 @@  -- | You may return a possibly-@NULL@ 'Entity' from a 'select' query. instance PersistEntity a => SqlSelect (SqlExpr (Maybe (Entity a))) (Maybe (Entity a)) where-    sqlSelectCols info e = sqlSelectCols info (coerce e :: SqlExpr (Entity a))+    sqlSelectCols info e = sqlSelectCols info (veryUnsafeCoerceSqlExpr e :: SqlExpr (Entity a))     sqlSelectColCount = sqlSelectColCount . fromEMaybe       where         fromEMaybe :: Proxy (SqlExpr (Maybe e)) -> Proxy (SqlExpr e)@@ -4251,3 +4462,7 @@             (\(oneOld, manyOld) (_, manyNew) -> (oneOld, manyNew ++ manyOld ))             (entityKey one)             (entityVal one, [many])++type family Nullable a where+    Nullable (Maybe a) = a+    Nullable a =  a
src/Database/Esqueleto/Legacy.hs view
@@ -52,14 +52,14 @@     -- $gettingstarted      -- * @esqueleto@'s Language-    where_, on, groupBy, orderBy, rand, asc, desc, limit, offset+    where_, on, groupBy, orderBy, asc, desc, limit, offset              , distinct, distinctOn, don, distinctOnOrderBy, having, locking-             , sub_select, (^.), (?.)-             , val, isNothing, just, nothing, joinV, withNonNull+             , (^.), (?.)+             , val, isNothing, just, just', nothing, joinV, joinV', withNonNull              , countRows, count, countDistinct              , not_, (==.), (>=.), (>.), (<=.), (<.), (!=.), (&&.), (||.)              , between, (+.), (-.), (/.), (*.)-             , random_, round_, ceiling_, floor_+             , round_, ceiling_, floor_              , min_, max_, sum_, avg_, castNum, castNumM              , coalesce, coalesceDefault              , lower_, upper_, trim_, ltrim_, rtrim_, length_, left_, right_@@ -67,7 +67,7 @@              , subList_select, valList, justList              , in_, notIn, exists, notExists              , set, (=.), (+=.), (-=.), (*=.), (/=.)-             , case_, toBaseId+             , case_, toBaseId, fromBaseId, fromBaseIdMaybe, toBaseIdMaybe   , subSelect   , subSelectMaybe   , subSelectCount@@ -84,6 +84,8 @@   , OrderBy   , DistinctOn   , LockingKind(..)+  , forUpdate+  , forUpdateSkipLocked   , LockableEntity(..)   , SqlString     -- ** Joins
src/Database/Esqueleto/MySQL.hs view
@@ -5,6 +5,7 @@ -- @since 2.2.8 module Database.Esqueleto.MySQL     ( random_+    , lockInShareMode     ) where  import Database.Esqueleto.Internal.Internal hiding (random_)@@ -13,6 +14,18 @@ -- | (@random()@) Split out into database specific modules -- because MySQL uses `rand()`. ----- /Since: 2.6.0/+-- @since 2.6.0 random_ :: (PersistField a, Num a) => SqlExpr (Value a) random_ = unsafeSqlValue "RAND()"++-- | @LOCK IN SHARE MODE@ syntax.+--+-- Example:+--+-- @+--  'locking' 'lockInShareMode'+-- @+--+-- @since 3.6.0.0+lockInShareMode :: LockingKind+lockInShareMode = LockInShareMode
src/Database/Esqueleto/PostgreSQL.hs view
@@ -9,7 +9,7 @@  -- | This module contain PostgreSQL-specific functions. ----- @since: 2.2.8+-- @since 2.2.8 module Database.Esqueleto.PostgreSQL     ( AggMode(..)     , arrayAggDistinct@@ -24,7 +24,9 @@     , now_     , random_     , upsert+    , upsertMaybe     , upsertBy+    , upsertMaybeBy     , insertSelectWithConflict     , insertSelectWithConflictCount     , noWait@@ -32,10 +34,14 @@     , skipLocked     , forUpdateOf     , forNoKeyUpdateOf+    , forShare     , forShareOf     , forKeyShareOf     , filterWhere     , values+    , ilike+    , distinctOn+    , distinctOnOrderBy     , withMaterialized     , withNotMaterialized     , ascNullsFirst@@ -46,9 +52,6 @@     , unsafeSqlAggregateFunction     ) where -#if __GLASGOW_HASKELL__ < 804-import Data.Semigroup-#endif import Control.Arrow (first) import Control.Exception (throw) import Control.Monad (void)@@ -59,6 +62,7 @@ import qualified Data.List.NonEmpty as NE import Data.Maybe import Data.Proxy (Proxy(..))+import qualified Data.Text as Text import qualified Data.Text.Internal.Builder as TLB import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Builder as TLB@@ -69,10 +73,14 @@ import Database.Esqueleto.Experimental.From.SqlSetOperation import Database.Esqueleto.Experimental.ToAlias import Database.Esqueleto.Experimental.ToAliasReference-import Database.Esqueleto.Internal.Internal hiding (From(..), from, on, random_)+import Database.Esqueleto.Internal.Internal hiding+       (From(..), ilike, distinctOn, distinctOnOrderBy, from, on, random_) import Database.Esqueleto.Internal.PersistentImport hiding        (uniqueFields, upsert, upsertBy)+import Database.Persist (ConstraintNameDB(..), EntityNameDB(..))+import Database.Persist.Class (OnlyOneUniqueKey) import Database.Persist.SqlBackend+import GHC.Stack  -- | (@random()@) Split out into database specific modules -- because MySQL uses `rand()`.@@ -81,6 +89,69 @@ random_ :: (PersistField a, Num a) => SqlExpr (Value a) random_ = unsafeSqlValue "RANDOM()" +-- | @DISTINCT ON@.  Change the current @SELECT@ into+-- @SELECT DISTINCT ON (SqlExpressions)@.  For example:+--+-- @+-- select $ do+--   foo <- 'from' $ table \@Foo+--   'distinctOn' ['don' (foo ^. FooName), 'don' (foo ^. FooState)]+--   pure foo+-- @+--+-- You can also chain different calls to 'distinctOn'.  The+-- above is equivalent to:+--+-- @+-- select $ do+--   foo <- 'from' $ table \@Foo+--   'distinctOn' ['don' (foo ^. FooName)]+--   'distinctOn' ['don' (foo ^. FooState)]+--   pure foo+-- @+--+-- Each call to 'distinctOn' adds more SqlExpressions.  Calls to+-- 'distinctOn' override any calls to 'distinct'.+--+-- Note that PostgreSQL requires the SqlExpressions on @DISTINCT+-- ON@ to be the first ones to appear on a @ORDER BY@.  This is+-- not managed automatically by esqueleto, keeping its spirit+-- of trying to be close to raw SQL.+--+-- @since 3.6.0+distinctOn :: [SqlExpr DistinctOn] -> SqlQuery ()+distinctOn exprs = Q (W.tell mempty { sdDistinctClause = DistinctOn exprs })++-- | A convenience function that calls both 'distinctOn' and+-- 'orderBy'.  In other words,+--+-- @+-- 'distinctOnOrderBy' [asc foo, desc bar, desc quux]+-- @+--+-- is the same as:+--+-- @+-- 'distinctOn' [don foo, don  bar, don  quux]+-- 'orderBy'  [asc foo, desc bar, desc quux]+--   ...+-- @+--+-- @since 3.6.0.0+distinctOnOrderBy :: [SqlExpr OrderBy] -> SqlQuery ()+distinctOnOrderBy exprs = do+    distinctOn (toDistinctOn <$> exprs)+    orderBy exprs+  where+    toDistinctOn :: SqlExpr OrderBy -> SqlExpr DistinctOn+    toDistinctOn (ERaw m f) = ERaw m $ \p info ->+        let (b, vals) = f p info+        in  ( TLB.fromLazyText+              $ TL.replace " DESC" ""+              $ TL.replace " ASC" ""+              $ TLB.toLazyText b+            , vals )+ -- | Empty array literal. (@val []@) does unfortunately not work emptyArray :: SqlExpr (Value [a]) emptyArray = unsafeSqlValue "'{}'"@@ -198,6 +269,15 @@ now_ :: SqlExpr (Value UTCTime) now_ = unsafeSqlFunction "NOW" () +-- | Perform an @upsert@ operation on the given record.+--+-- If the record exists in the database already, then the updates will be+-- performed on that record. If the record does not exist, then the+-- provided record will be inserted.+--+-- If you wish to provide an empty list of updates (ie "if the record+-- exists, do nothing"), then you will need to call 'upsertMaybe'. Postgres+-- will not return anything if there are no modifications or inserts made. upsert     ::     ( MonadIO m@@ -208,29 +288,69 @@     )     => record     -- ^ new record to insert-    -> [SqlExpr (Entity record) -> SqlExpr Update]+    -> NE.NonEmpty (SqlExpr (Entity record) -> SqlExpr Update)     -- ^ updates to perform if the record already exists     -> R.ReaderT SqlBackend m (Entity record)     -- ^ the record in the database after the operation-upsert record updates = do-    uniqueKey <- onlyUnique record-    upsertBy uniqueKey record updates+upsert record =+    upsertBy (onlyUniqueP record) record -upsertBy+-- | Like 'upsert', but permits an empty list of updates to be performed.+--+-- If no updates are provided and the record already was present in the+-- database, then this will return 'Nothing'. If you want to fetch the+-- record out of the database, you can write:+--+-- @+--  mresult <- upsertMaybe record []+--  case mresult of+--      Nothing ->+--          'getBy' ('onlyUniqueP' record)+--      Just res ->+--          pure (Just res)+-- @+--+-- @since 3.6.0.0+upsertMaybe     ::-    (MonadIO m+    ( MonadIO m     , PersistEntity record+    , OnlyOneUniqueKey record+    , PersistRecordBackend record SqlBackend     , IsPersistBackend (PersistEntityBackend record)     )+    => record+    -- ^ new record to insert+    -> [SqlExpr (Entity record) -> SqlExpr Update]+    -- ^ updates to perform if the record already exists+    -> R.ReaderT SqlBackend m (Maybe (Entity record))+    -- ^ the record in the database after the operation+upsertMaybe rec upds = do+    upsertMaybeBy (onlyUniqueP rec) rec upds++-- | Attempt to insert a @record@ into the database. If the @record@+-- already exists for the given @'Unique' record@, then a list of updates+-- will be performed.+--+-- If you provide an empty list of updates, then this function will return+-- 'Nothing' if the record already exists in the database.+--+-- @since 3.6.0.0+upsertMaybeBy+    ::+    ( MonadIO m+    , PersistEntity record+    , IsPersistBackend (PersistEntityBackend record)+    )     => Unique record     -- ^ uniqueness constraint to find by     -> record     -- ^ new record to insert     -> [SqlExpr (Entity record) -> SqlExpr Update]     -- ^ updates to perform if the record already exists-    -> R.ReaderT SqlBackend m (Entity record)+    -> R.ReaderT SqlBackend m (Maybe (Entity record))     -- ^ the record in the database after the operation-upsertBy uniqueKey record updates = do+upsertMaybeBy uniqueKey record updates = do     sqlB <- R.ask     case getConnUpsertSql sqlB of         Nothing ->@@ -240,36 +360,70 @@         Just upsertSql ->             handler sqlB upsertSql   where-    addVals l = map toPersistValue (toPersistFields record) ++ l ++ persistUniqueToValues uniqueKey-    entDef = entityDef (Just record)-    updatesText conn = first builderToText $ renderUpdates conn updates-#if MIN_VERSION_persistent(2,11,0)+    addVals l =+        map toPersistValue (toPersistFields record) ++ l ++ case updates of+            [] ->+                []+            _ ->+                persistUniqueToValues uniqueKey+    entDef =+        entityDef (Just record)+    updatesText conn =+        first builderToText $ renderUpdates conn updates     uniqueFields = persistUniqueToFieldNames uniqueKey     handler sqlB upsertSql = do         let (updateText, updateVals) =                 updatesText sqlB-            queryText =+            queryTextUnmodified =                 upsertSql entDef uniqueFields updateText+            queryText =+                case updates of+                    [] ->+                        let+                            (okay, _bad) =+                                Text.breakOn "DO UPDATE" queryTextUnmodified+                            good =+                                okay <> "DO NOTHING RETURNING ??"+                        in+                            good+                    _ ->+                        queryTextUnmodified+             queryVals =                 addVals updateVals         xs <- rawSql queryText queryVals-        pure (head xs)-#else-    uDef = toUniqueDef uniqueKey-    handler conn f = fmap head $ uncurry rawSql $-        (***) (f entDef (uDef :| [])) addVals $ updatesText conn-#endif+        pure (listToMaybe xs) +upsertBy+    ::+    ( MonadIO m+    , PersistEntity record+    , IsPersistBackend (PersistEntityBackend record)+    , HasCallStack+    )+    => Unique record+    -- ^ uniqueness constraint to find by+    -> record+    -- ^ new record to insert+    -> NE.NonEmpty (SqlExpr (Entity record) -> SqlExpr Update)+    -- ^ updates to perform if the record already exists+    -> R.ReaderT SqlBackend m (Entity record)+    -- ^ the record in the database after the operation+upsertBy uniqueKey record updates = do+    mrec <- upsertMaybeBy uniqueKey record (NE.toList updates)+    case mrec of+        Nothing ->+            error "non-empty list of updates should have resulted in a row being returned"+        Just rec ->+            pure rec+ -- | Inserts into a table the results of a query similar to 'insertSelect' but allows -- to update values that violate a constraint during insertions. -- -- Example of usage: -- -- @--- share [ mkPersist sqlSettings---       , mkDeleteCascade sqlSettings---       , mkMigrate "migrate"---       ] [persistLowerCase|+-- 'mkPersist' 'sqlSettings' ['persistLowerCase'| --   Bar --     num Int --     deriving Eq Show@@ -279,17 +433,19 @@ --     deriving Eq Show -- |] ----- insertSelectWithConflict---   UniqueFoo -- (UniqueFoo undefined) or (UniqueFoo anyNumber) would also work---   (from $ \b ->---     return $ Foo <# (b ^. BarNum)---   )---   (\current excluded ->---     [FooNum =. (current ^. FooNum) +. (excluded ^. FooNum)]---   )+-- action = do+--     'insertSelectWithConflict'+--         UniqueFoo -- (UniqueFoo undefined) or (UniqueFoo anyNumber) would also work+--         (do+--             b <- from $ table \@Bar+--             return $ Foo <# (b ^. BarNum)+--         )+--         (\\current excluded ->+--             [FooNum =. (current ^. FooNum) +. (excluded ^. FooNum)]+--         ) -- @ ----- Inserts to table Foo all Bar.num values and in case of conflict SomeFooUnique,+-- Inserts to table @Foo@ all @Bar.num@ values and in case of conflict @SomeFooUnique@, -- the conflicting value is updated to the current plus the excluded. -- -- @since 3.1.3@@ -500,6 +656,18 @@ forShareOf lockableEntities onLockedBehavior =   putLocking $ PostgresLockingClauses [PostgresLockingKind PostgresForShare (Just $ LockingOfClause lockableEntities) onLockedBehavior] +-- | @FOR SHARE@ syntax for Postgres locking.+--+-- Example use:+--+-- @+--  'locking' 'forShare'+-- @+--+-- @since 3.6.0.0+forShare :: LockingKind+forShare = ForShare+ -- | `FOR KEY SHARE OF` syntax for postgres locking -- allows locking of specific tables with a key share lock in a view or join --@@ -507,6 +675,13 @@ forKeyShareOf :: LockableEntity a => a -> OnLockedBehavior -> SqlQuery () forKeyShareOf lockableEntities onLockedBehavior =   putLocking $ PostgresLockingClauses [PostgresLockingKind PostgresForKeyShare (Just $ LockingOfClause lockableEntities) onLockedBehavior]++-- | @ILIKE@ operator (case-insensitive @LIKE@).+--+-- @since 2.2.3+ilike :: SqlString s => SqlExpr (Value s) -> SqlExpr (Value s) -> SqlExpr (Value Bool)+ilike   = unsafeSqlBinOp    " ILIKE "+infixr 2 `ilike`  -- | @WITH@ @MATERIALIZED@ clause is used to introduce a -- [Common Table Expression (CTE)](https://en.wikipedia.org/wiki/Hierarchical_and_recursive_queries_in_SQL#Common_table_expression)
test/Common/Record.hs view
@@ -352,6 +352,35 @@                           } -> addr1 == addr2 -- The keys should match.                  _ -> False) +    itDb "can union a record query with a CTE reference and alias new columns" $ do+        -- The record's ToAlias instance allocates one ident per field; the+        -- CTE-reference branch allocates none. The enclosing query must not+        -- reuse the record's aliases (a variant of issue #299).+        setup+        records <- select $ do+            recordCTE <- with myRecordQuery+            result <- from $ do+                record <- from $ myRecordQuery `union_` from recordCTE+                pure (record, val (1 :: Int))+            pure result+        let sortedRecords = sortOn (\(MyRecord {myName}, _) -> myName) records+        liftIO $ map (\(MyRecord {myName}, extra) -> (myName, extra)) sortedRecords+          `shouldBe` [("Rebecca", Value 1), ("Some Guy", Value 1)]++    itDb "can select a record alongside one of its own fields in a subquery" $ do+        -- Field access on a record from an inner scope returns the stored+        -- reference, so the select list contains the same reference twice;+        -- each occurrence must get its own output alias.+        setup+        records <- select $ do+            result <- from $ do+                record <- from myRecordQuery+                pure (record, getField @"myName" record)+            pure result+        let sortedRecords = sortOn (\(MyRecord {myName}, _) -> myName) records+        liftIO $ map (\(MyRecord {myName}, dup) -> (myName, dup)) sortedRecords+          `shouldBe` [("Rebecca", Value "Rebecca"), ("Some Guy", Value "Some Guy")]+     itDb "can select user-modified records" $ do         setup         records <- select myModifiedRecordQuery
test/Common/Test.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-}@@ -32,7 +33,6 @@     ( tests     , testLocking     , testAscRandom-    , testRandomMath     , migrateAll     , migrateUnique     , cleanDB@@ -65,6 +65,7 @@     , DateTruncTest(..)     , DateTruncTestId     , Key(..)+    , assertJust     ) where  import Common.Test.Import hiding (from, on)@@ -160,19 +161,8 @@                   pure (n ^. NumbersInt)             setup             res <- select $ pure $ subSelect query-            eres <- try $ do-                select $ pure $ sub_select query             asserting $ do                 res `shouldBe` [Value (Just 1)]-                case eres of-                    Left (SomeException _) ->-                        -- We should receive an exception, but the different database-                        -- libraries throw different exceptions. Hooray.-                        pure ()-                    Right v ->-                        -- This shouldn't happen, but in sqlite land, many things are-                        -- possible.-                        v `shouldBe` [Value 1]          itDb "is safe for queries that may not return anything" $ do             let query =@@ -184,21 +174,8 @@             res <- select $ pure $ subSelect query             transactionUndo -            eres <- try $ do-                select $ pure $ sub_select query-             asserting $ do                 res `shouldBe` [Value $ Just 1]-                case eres of-                    Left (_ :: PersistException) ->-                        -- We expect to receive this exception. However, sqlite evidently has-                        -- no problems with itDb, so we can't *require* that the exception is-                        -- thrown. Sigh.-                        pure ()-                    Right v ->-                        -- This shouldn't happen, but in sqlite land, many things are-                        -- possible.-                        v `shouldBe` [Value 1]      describe "subSelectList" $ do         itDb "is safe on empty databases as well as good databases" $ do@@ -405,7 +382,7 @@                             , (p2e, p2e)                             ] -        itDb "works for a self-join via sub_select" $ do+        itDb "works for a self-join via subSelect" $ do             p1k <- insert p1             p2k <- insert p2             _f1k <- insert (Follow p1k p2k)@@ -416,7 +393,7 @@                          from $ \followB -> do                          where_ $ followA ^. FollowFollower ==. followB ^. FollowFollowed                          return $ followB ^. FollowFollower-                   where_ $ followA ^. FollowFollowed ==. sub_select subquery+                   where_ $ just (followA ^. FollowFollowed) ==. subSelect subquery                    return followA             asserting $ length ret `shouldBe` 2 @@ -744,7 +721,7 @@         _ <- insert' p3         let q = do                 (name, age) <--                  Experimental.from $ SubQuery $ do+                  Experimental.from $ do                       p <- Experimental.from $ Table @Person                       return ( p ^. PersonName, p ^. PersonAge)                 orderBy [ asc age ]@@ -765,7 +742,7 @@                                                        lord ^. LordId ==. deed ^. DeedOwnerId)                 return (lord ^. LordId, deed ^. DeedId)             q' = do-                 (lordId, deedId) <- Experimental.from $ SubQuery q+                 (lordId, deedId) <- Experimental.from q                  groupBy (lordId)                  return (lordId, count deedId)         (ret :: [(Value (Key Lord), Value Int)]) <- select q'@@ -788,7 +765,7 @@                 return (lord ^. LordId, count (deed ^. DeedId))          (ret :: [(Value Int)]) <- select $ do-                 (lordId, deedCount) <- Experimental.from $ SubQuery q+                 (lordId, deedCount) <- Experimental.from q                  where_ $ deedCount >. val (3 :: Int)                  return (count lordId) @@ -1135,12 +1112,12 @@                return p         asserting $ ret `shouldBe` [ p1e, p3e, p2e ] -    itDb "works with a sub_select" $ do+    itDb "works with a subSelect" $ do         [p1k, p2k, p3k, p4k] <- mapM insert [p1, p2, p3, p4]         [b1k, b2k, b3k, b4k] <- mapM (insert . BlogPost "") [p1k, p2k, p3k, p4k]         ret <- select $                from $ \b -> do-               orderBy [desc $ sub_select $+               orderBy [desc $ subSelect $                                from $ \p -> do                                where_ (p ^. PersonId ==. b ^. BlogPostAuthorId)                                return (p ^. PersonName)@@ -1244,8 +1221,8 @@                  let sub =                          from $ \p -> do                          where_ (p ^. PersonId ==. b ^. BlogPostAuthorId)-                         return $ p ^. PersonAge-                 return $ coalesceDefault [sub_select sub] (val (42 :: Int))+                         pure $ p ^. PersonAge+                 return $ coalesceDefault [joinV $ subSelect sub] (val (42 :: Int))         asserting $ ret `shouldBe` [ Value (36 :: Int)                                 , Value 42                                 , Value 17@@ -1288,7 +1265,7 @@               where_ (b ^. BlogPostAuthorId ==. p ^. PersonId)               return countRows         ()  <- update $ \p -> do-               set p [ PersonAge =. just (sub_select (blogPostsBy p)) ]+               set p [ PersonAge =. subSelect (blogPostsBy p) ]         ret <- select $                from $ \p -> do                orderBy [ asc (p ^. PersonName) ]@@ -1557,31 +1534,6 @@         asserting $ ret `shouldBe` [Value (3::Int)]         asserting $ cnt `shouldBe` 3 ----testRandomMath :: SpecDb-testRandomMath = describe "random_ math" $-    itDb "rand returns result in random order" $-      do-        replicateM_ 20 $ do-          _ <- insert p1-          _ <- insert p2-          _ <- insert p3-          _ <- insert p4-          _ <- insert $ Person "Jane"  Nothing Nothing 0-          _ <- insert $ Person "Mark"  Nothing Nothing 0-          _ <- insert $ Person "Sarah" Nothing Nothing 0-          insert $ Person "Paul"  Nothing Nothing 0-        ret1 <- fmap (map unValue) $ select $ from $ \p -> do-                  orderBy [rand]-                  return (p ^. PersonId)-        ret2 <- fmap (map unValue) $ select $ from $ \p -> do-                  orderBy [rand]-                  return (p ^. PersonId)--        asserting $ (ret1 == ret2) `shouldBe` False- testMathFunctions :: SpecDb testMathFunctions = do   describe "Math-related functions" $ do@@ -1639,16 +1591,16 @@                   (exists $ from $ \p -> do                       where_ (p ^. PersonName ==. val "Mike"))                 then_-                  (sub_select $ from $ \v -> do+                  (subSelect $ from $ \v -> do                       let sub =                               from $ \c -> do                               where_ (c ^. PersonName ==. val "Mike")                               return (c ^. PersonFavNum)-                      where_ (v ^. PersonFavNum >. sub_select sub)+                      where_ (just (v ^. PersonFavNum) >. subSelect sub)                       return $ count (v ^. PersonName) +. val (1 :: Int)) ]-              (else_ $ val (-1))+              (else_ $ just $ val (-1)) -        asserting $ ret `shouldBe` [ Value (3) ]+        asserting $ ret `shouldBe` [ Value (Just 3) ]  testLocking :: SpecDb testLocking = do@@ -2370,7 +2322,7 @@         insert_ p3         -- Pretend this isnt all posts         upperNames <- select $ do-          author <- Experimental.from $ SelectQuery $ Experimental.from $ Table @Person+          author <- Experimental.from $ Experimental.from $ Table @Person           pure $ upper_ $ author ^. PersonName          asserting $ upperNames `shouldMatchList` [ Value "JOHN"@@ -2536,7 +2488,7 @@                 pure bp.title     describe "with SqlExpr (Maybe (Entity rec))" $ do         itDb "lets you project from a Maybe record" $ do-            select $ do+            void $ select $ do                 p :& mbp <- Experimental.from $                     table @Person                     `leftJoin` table @BlogPost@@ -2545,6 +2497,34 @@                                 just p.id ==. mbp.authorId                 pure (p.id, mbp.title) +    itDb "joins Maybe together" $ do+        void $ select $ do+            deed :& lord <-+                Experimental.from $+                    table @Deed+                    `leftJoin` table @Lord+                        `Experimental.on` do+                            \(deed :& lord) ->+                                lord.id ==. just deed.ownerId+            where_ $ lord.dogs >=. just (val 10)+            where_ $ joinV lord.dogs >=. just (just (val 10))+            where_ $ lord.dogs >=. just (val (Just 10))++    itDb "i didn't bork ?." $ do+        weights <- select $ do+            (pro :& per) <- Experimental.from $+                table @Profile+                    `leftJoin` table @Person+                        `Experimental.on` do+                            \(pro :& per) ->+                                just (pro ^. #person) ==. per ?. #id+                                &&. just pro.person ==. per ?. PersonId+            pure $ per ?. #weight+        asserting $ do+            weights `shouldBe` ([] :: [Value (Maybe Int)])+++ #else     it "is only supported in GHC 9.2 or above" $ \_ -> do         pending@@ -2573,3 +2553,5 @@                 pure (person, blogPost, profile, reply)             asserting noExceptions +assertJust :: HasCallStack => Maybe a -> IO a+assertJust = maybe (expectationFailure "Expected Just, got Nothing" >> error "asdf") pure
test/Common/Test/CTE.hs view
@@ -8,6 +8,99 @@  testCTE :: SpecDb testCTE = describe "CTE" $ do+    itDb "aliases new columns after a union whose left branch is a CTE reference" $ do+        -- Mirror image of the test below: the left branch allocates fewer+        -- idents than the right one.+        let q :: SqlQuery (SqlExpr (Value Int), SqlExpr (Value Int), SqlExpr (Value Int))+            q = do+                bCte <- with $ do+                    b <- from $ table @B+                    pure (b ^. BK, b ^. BV)+                (k, v, extra) <- from $ do+                    (k, v) <- from $+                        from bCte+                        `union_`+                        (do+                            b <- from $ table @B+                            pure (b ^. BK, b ^. BV))+                    pure (k, v, val (42 :: Int))+                pure (k, v, extra)+        insert_ $ B { bK = 1, bV = 3 }+        ret <- select q+        asserting $ do+            ret `shouldMatchList`+                [ (Value 1, Value 3, Value 42)+                ]++    itDb "aliases a repeated reference in a subquery select list" $ do+        -- Each occurrence needs its own output alias, or outer references+        -- to the duplicated column name are ambiguous.+        let q :: SqlQuery (SqlExpr (Value Int), SqlExpr (Value Int))+            q = do+                (a, b) <- from $ do+                    (x, _) <- from $ do+                        b <- from $ table @B+                        pure (b ^. BK, b ^. BV)+                    pure (x, x)+                pure (a, b)+        insert_ $ B { bK = 1, bV = 3 }+        ret <- select q+        asserting $ do+            ret `shouldMatchList`+                [ (Value 1, Value 1)+                ]++    itDb "layered CTEs over subqueries with repeated references" $ do+        let recentTransactions :: SqlQuery (SqlExpr (Value Int), SqlExpr (Value (Maybe Int)), SqlExpr (Value Int))+            recentTransactions = do+                b <- from $ table @B+                limit 10+                pure (b ^. BK, just (b ^. BV), b ^. BK)+            getPrev tr = with $ do+                (k, mr, _) <- tr+                pure (k, mr, just (k +. val 1), just (k +. val 2))+            filterRuns tr = do+                prev <- getPrev tr+                (k, mr, p1, _) <- from prev+                where_ $ isNothing_ p1 ||. p1 !=. just (val 0)+                pure (k, mr, k)+            inferTime tr = do+                let removed = from $ filterRuns tr+                prev <- getPrev removed+                (k, mr, _, p2) <- from prev+                let newT = coalesceDefault [p2] k+                pure (k, mr, newT)+            q :: SqlQuery (SqlExpr (Value Int), SqlExpr (Value (Maybe Int)), SqlExpr (Value Int))+            q = do+                recent <- with $ inferTime (from recentTransactions)+                sent <- with $ do+                    (k, mr, t) <- from recent+                    where_ $ k >=. val 0+                    pure (k, just (coalesceDefault [mr] (val 0)), t)+                recRecs <- with $ distinct $ do+                    (_, mr, _) <- from sent+                    pure mr+                recRecTx <- with $ do+                    (mr :& b) <- from $ recRecs+                        `innerJoin` table @B+                            `on` do+                                \(mr :& b) -> mr ==. just (b ^. BV)+                    pure (b ^. BK, mr, b ^. BV)+                deduped <- with $ do+                    r@(k, _, _) <- from recRecTx+                    where_ $ not_ $ exists $ do+                        (k2, _, _) <- from sent+                        where_ $ k2 ==. k+                    pure r+                (k, mr, t) <- from $ from sent `union_` from deduped+                pure (k, mr, t)+        insert_ $ B { bK = 1, bV = 3 }+        ret <- select q+        asserting $ do+            ret `shouldMatchList`+                [ (Value 1, Value (Just 3), Value 3)+                ]+     itDb "can refer to the same CTE twice" $ do         let q :: SqlQuery (SqlExpr (Value Int), SqlExpr (Value Int))             q = do@@ -32,4 +125,28 @@         asserting $ do             ret `shouldMatchList`                 [ (Value 1, Value (2 + 3 + 3))+                ]++    itDb "aliases new columns after a union with a CTE reference" $ do+        -- The right branch allocates fewer idents than the left one; the+        -- enclosing query must not reuse the left branch's idents for later+        -- aliases (a variant of issue #299).+        let q :: SqlQuery (SqlExpr (Value Int), SqlExpr (Value Int), SqlExpr (Value Int))+            q = do+                bCte <- with $ do+                    b <- from $ table @B+                    pure (b ^. BK, b ^. BV)+                (k, v, extra) <- from $ do+                    (k, v) <- from $+                        (do+                            b <- from $ table @B+                            pure (b ^. BK, b ^. BV))+                        `union_` from bCte+                    pure (k, v, val (42 :: Int))+                pure (k, v, extra)+        insert_ $ B { bK = 1, bV = 3 }+        ret <- select q+        asserting $ do+            ret `shouldMatchList`+                [ (Value 1, Value 3, Value 42)                 ]
test/Common/Test/Models.hs view
@@ -207,4 +207,3 @@ instance ToBaseId ArticleMetadata where     type BaseEnt ArticleMetadata = Article     toBaseIdWitness articleId = ArticleMetadataKey articleId-
test/PostgreSQL/Test.hs view
@@ -57,6 +57,37 @@ import Common.Test.Import hiding (from, on) import PostgreSQL.MigrateJSON +spec :: Spec+spec = beforeAll mkConnectionPool $ do+    tests++    describe "PostgreSQL specific tests" $ do+        testAscRandom random_+        testSelectDistinctOn+        testPostgresModule+        testPostgresqlOneAscOneDesc+        testPostgresqlTwoAscFields+        testPostgresqlSum+        testPostgresqlRandom+        testPostgresqlUpdate+        testPostgresqlCoalesce+        testPostgresqlTextFunctions+        testInsertUniqueViolation+        testUpsert+        testInsertSelectWithConflict+        testFilterWhere+        testCommonTableExpressions+        setDatabaseState insertJsonValues cleanJSON+            $ describe "PostgreSQL JSON tests" $ do+                testJSONInsertions+                testJSONOperators+        testLateralQuery+        testValuesExpression+        testSubselectAliasingBehavior+        testPostgresqlLocking+        testPostgresqlNullsOrdering++ returningType :: forall a m . m a -> m a returningType a = a @@ -1044,18 +1075,29 @@       sqlErrorHint = ""}  testUpsert :: SpecDb-testUpsert =-  describe "Upsert test" $ do+testUpsert = describe "Upsert test" $ do     itDb "Upsert can insert like normal" $  do-      u1e <- EP.upsert u1 [OneUniqueName =. val "fifth"]-      liftIO $ entityVal u1e `shouldBe` u1+        u1e <- EP.upsert u1 (pure (OneUniqueName =. val "fifth"))+        liftIO $ entityVal u1e `shouldBe` u1     itDb "Upsert performs update on collision" $  do-      u1e <- EP.upsert u1 [OneUniqueName =. val "fifth"]-      liftIO $ entityVal u1e `shouldBe` u1-      u2e <- EP.upsert u2 [OneUniqueName =. val "fifth"]-      liftIO $ entityVal u2e `shouldBe` u2-      u3e <- EP.upsert u3 [OneUniqueName =. val "fifth"]-      liftIO $ entityVal u3e `shouldBe` u1{oneUniqueName="fifth"}+        u1e <- EP.upsert u1 (pure (OneUniqueName =. val "fifth"))+        liftIO $ entityVal u1e `shouldBe` u1+        u2e <- EP.upsert u2 (pure (OneUniqueName =. val "fifth"))+        liftIO $ entityVal u2e `shouldBe` u2+        u3e <- EP.upsert u3 (pure (OneUniqueName =. val "fifth"))+        liftIO $ entityVal u3e `shouldBe` u1{oneUniqueName="fifth"}+    describe "With no updates" $ do+        itDb "Works with no updates" $ do+            _ <- EP.upsertMaybe u1 []+            pure ()+        itDb "Works with no updates, twice" $ do+            mu1  <- EP.upsertMaybe u1 []+            Entity u1Key u1' <- liftIO $ assertJust mu1+            mu2 <- EP.upsertMaybe u1 { oneUniqueName = "Something Else" } []+            asserting $ do+                mu2 `shouldBe` Nothing+            -- liftIO $ do+            --     u1 `shouldBe` u1'  testInsertSelectWithConflict :: SpecDb testInsertSelectWithConflict =@@ -1621,6 +1663,72 @@                     pure (str, val @Int 1)             asserting noExceptions +        itDb "keeps a CTE declared inside a set operation branch scoped to it" $ do+            -- The branch declaring the CTE must render parenthesized (a bare+            -- WITH after UNION is invalid SQL), and the CTE's idents must not+            -- leak into the enclosing query.+            let lordQuery = do+                    l <- Experimental.from $ table @Lord+                    pure (l ^. LordCounty)+                lordCteQuery = do+                    lordCte <- with lordQuery+                    l <- Experimental.from lordCte+                    pure l+            _ <- select $+                Experimental.from $ do+                    (county, _) <- Experimental.from $ do+                        c <- Experimental.from $ lordQuery `union_` lordCteQuery+                        pure (c, val @Int 1)+                    pure (county, val @Int 2)+            -- The declaring branch may also be the first operand.+            _ <- select $+                Experimental.from $ do+                    c <- Experimental.from $ lordCteQuery `union_` lordQuery+                    pure (c, val @Int 1)+            asserting noExceptions++        itDb "allocates fresh idents for a lateral join after a right-heavy union" $ do+            -- The left branch selects only references to a CTE, so the right+            -- branch consumes more idents than the left one. Idents allocated+            -- after the union (here, inside a correlated lateral subquery)+            -- must not collide with either branch's.+            lid <- insert l1+            let lordQuery = do+                    l <- Experimental.from $ table @Lord+                    pure (l ^. LordId, l ^. LordDogs)+            result <- select $ do+                lordCte <- with lordQuery+                (lordId, dogs) :& dogs2 <-+                    Experimental.from $+                        (Experimental.from lordCte `union_` lordQuery)+                        `CrossJoin` \(k, _) -> do+                            l2 <- Experimental.from $ table @Lord+                            where_ $ l2 ^. LordId ==. k+                            pure (l2 ^. LordDogs)+                pure (lordId, dogs, dogs2)+            asserting $ result `shouldMatchList`+                [ (Value lid, Value (Just 36), Value (Just 36)) ]++        itDb "separates a branch CTE from CTEs declared after the union" $ do+            -- The right branch declares its own CTE; a CTE introduced in the+            -- enclosing query afterwards must get a distinct ident and+            -- resolve independently of the branch-scoped one.+            lid <- insert l1+            let lordQuery = do+                    l <- Experimental.from $ table @Lord+                    pure (l ^. LordId, l ^. LordDogs)+                branchCteQuery = do+                    c <- with lordQuery+                    Experimental.from c+            result <- select $ do+                (k, d) <- Experimental.from $ lordQuery `union_` branchCteQuery+                outerCte <- with lordQuery+                (k2, d2) <- Experimental.from outerCte+                where_ $ k2 ==. k+                pure (k, d, d2)+            asserting $ result `shouldMatchList`+                [ (Value lid, Value (Just 36), Value (Just 36)) ]+ testPostgresqlNullsOrdering :: SpecDb testPostgresqlNullsOrdering = do   describe "Postgresql NULLS orderings work" $ do@@ -1730,43 +1838,6 @@     f $ just (v ^. JsonValue)     return v ---------------- JSON --------------- JSON --------------- JSON ------------------------------- JSON --------------- JSON --------------- JSON ------------------------------- JSON --------------- JSON --------------- JSON -------------------spec :: Spec-spec = beforeAll mkConnectionPool $ do-    tests--    describe "PostgreSQL specific tests" $ do-        testAscRandom random_-        testRandomMath-        testSelectDistinctOn-        testPostgresModule-        testPostgresqlOneAscOneDesc-        testPostgresqlTwoAscFields-        testPostgresqlSum-        testPostgresqlRandom-        testPostgresqlUpdate-        testPostgresqlCoalesce-        testPostgresqlTextFunctions-        testInsertUniqueViolation-        testUpsert-        testInsertSelectWithConflict-        testFilterWhere-        testCommonTableExpressions-        setDatabaseState insertJsonValues cleanJSON-            $ describe "PostgreSQL JSON tests" $ do-                testJSONInsertions-                testJSONOperators-        testLateralQuery-        testValuesExpression-        testSubselectAliasingBehavior-        testPostgresqlLocking-        testPostgresqlNullsOrdering- insertJsonValues :: SqlPersistT IO () insertJsonValues = do     insertIt Null@@ -1807,12 +1878,12 @@         then             runStderrLoggingT $                 createPostgresqlPool-                "host=localhost port=5432 user=esqutest password=esqutest dbname=esqutest"+                "host=127.0.0.1 port=5432 user=esqutest password=esqutest dbname=esqutest"                 4         else             runNoLoggingT $                 createPostgresqlPool-                "host=localhost port=5432 user=esqutest password=esqutest dbname=esqutest"+                "host=127.0.0.1 port=5432 user=esqutest password=esqutest dbname=esqutest"                 4     flip runSqlPool pool $ do         migrateIt
test/SQLite/Test.hs view
@@ -134,7 +134,6 @@      describe "SQLite specific tests" $ do       testAscRandom random_-      testRandomMath       testSqliteRandom       testSqliteSum       testSqliteTwoAscFields
test/Spec.hs view
@@ -19,4 +19,3 @@             sequential $ MySQL.spec         describe "Postgresql" $ do             sequential $ Postgres.spec-