diff --git a/Changelog.md b/Changelog.md
new file mode 100644
--- /dev/null
+++ b/Changelog.md
@@ -0,0 +1,13 @@
+# Change log for esqueleto-postgis project
+
+## Version 1.0.0 
++ add st_contains
++ add st_intersects
++ add st_point
++ add custom datatype to map to persistent.
++ bunch of roundtrip tests and sanity tests for added functions
+
+## Version 0.0.0 
+
+import [template](https://github.com/jappeace/template).
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Jappie Klooster
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Readme.md b/Readme.md
new file mode 100644
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,79 @@
+[![https://jappieklooster.nl](https://img.shields.io/badge/blog-jappieklooster.nl-lightgrey)](https://jappieklooster.nl/tag/haskell.html)
+[![Githbu actions build status](https://img.shields.io/github/workflow/status/jappeace/haskell-template-project/Test)](https://github.com/jappeace/haskell-template-project/actions)
+[![Jappiejappie](https://img.shields.io/badge/discord-jappiejappie-black?logo=discord)](https://discord.gg/Hp4agqy)
+[![Hackage version](https://img.shields.io/hackage/v/esqueleto-postgis.svg?label=Hackage)](https://hackage.haskell.org/package/esqueleto-postgis) 
+
+> Show me the place where space is not.
+
+Implement (partial) postgis functionality for esqueleto.
+https://postgis.net/
+
+uses wkt-geom to get a persistent instance,
+then maps that to a custom datatype 'PostgisGeometry' which is valid
+for roundtripping.
+
+Then the esqueleto combinators are defined around this datatype.
+
+# Tutorial
+you can specify some posgis geometry,
+use the point to nidicate dimensions, 
+pointxy = 2 dimensions
+pointxyz = 3, pointxyzm = 4.
+The library forces you to work in the same dimensions.
+
+You can specify a table with the custom datatype, which will have geometry as sql type:
+
+```haskell
+share
+  [mkPersist sqlSettings, mkMigrate "migrateAll"]
+  [persistUpperCase|
+  Unit sql=unit
+    geom       (PostgisGeometry PointXY)
+    deriving Eq Show
+|]
+```
+
+then you can simply query on tat datatype:
+
+```
+test = testCase ("it finds the one unit with st_contains") $ do
+            result <- runDB $ do
+              _ <- insert $
+                Unit
+                  { unitGeom = Polygon $ makePolygon (PointXY 0 0) (PointXY 0 2) (PointXY 2 2) $ Seq.fromList [(PointXY 2 0)]
+                  }
+
+              selectOne $ do
+                unit <- from $ table @Unit
+                where_ $ unit ^. UnitGeom `st_contains` (val $ Point (PointXY 1 1))
+                pure countRows
+
+            -- expectation, the result should be 1
+            unValue <$> result @?= (Just (1 :: Int)),
+```
+
+# Contributing
+contributions are welcome.
+There are still many bindings missing!
+
+# Hacking
+
+### Tools
+Enter the nix shell.
+```
+nix develop
+```
+You can checkout the makefile to see what's available:
+```
+cat makefile
+```
+
+### Running
+```
+make run
+```
+
+### Fast filewatch which runs tests
+```
+make ghcid
+```
diff --git a/esqueleto-postgis.cabal b/esqueleto-postgis.cabal
new file mode 100644
--- /dev/null
+++ b/esqueleto-postgis.cabal
@@ -0,0 +1,95 @@
+cabal-version:      3.0
+
+-- This file has been generated from package.yaml by hpack version 0.34.7.
+--
+-- see: https://github.com/sol/hpack
+
+name:           esqueleto-postgis
+version:        1.0.0
+homepage:       https://github.com/jappeace/esqueleto-postgis#readme
+bug-reports:    https://github.com/jappeace/esqueleto-postgis/issues
+author:         Jappie Klooster
+maintainer:     jappieklooster@hotmail.com
+copyright:      2024 Jappie Klooster
+license:        MIT
+synopsis:       postgis bindings for esqueleto.
+description:       postgis bindings for esqueleto. Makes postgres a spatial database but now typesafely
+category: Database
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    Readme.md
+    LICENSE
+extra-doc-files:
+    Changelog.md
+
+source-repository head
+  type: git
+  location: https://github.com/jappeace/esqueleto-postgis
+
+common common-options
+  default-extensions: 
+      EmptyCase
+      FlexibleContexts
+      FlexibleInstances
+      InstanceSigs
+      MultiParamTypeClasses
+      LambdaCase
+      MultiWayIf
+      NamedFieldPuns
+      TupleSections
+      DeriveFoldable
+      DeriveFunctor
+      DeriveGeneric
+      DeriveLift
+      DeriveTraversable
+      DerivingStrategies
+      GeneralizedNewtypeDeriving
+      StandaloneDeriving
+      OverloadedStrings
+      TypeApplications
+      NumericUnderscores
+
+  ghc-options:
+    -Wall  -Wincomplete-uni-patterns
+    -Wincomplete-record-updates -Widentities -Wredundant-constraints
+    -Wcpp-undef -fwarn-tabs -Wpartial-fields
+    -fdefer-diagnostics -Wunused-packages
+    -fenable-th-splice-warnings
+    -fno-omit-yields
+
+  build-depends:
+      base >=4.9.1.0 && <4.20.0.0,
+      containers >= 0.6.5 && < 0.7,
+      esqueleto >= 3.5.10 && < 3.6,
+      text >= 1.2.5 && < 1.3,
+      persistent >= 2.13.3 && < 2.14,
+      geojson >= 4.1.1 && < 4.2,
+      wkt-geom >= 0.0.12 && < 0.1,
+
+
+  default-language: Haskell2010
+
+library
+  import: common-options
+  exposed-modules:
+      Database.Esqueleto.Postgis
+  hs-source-dirs:
+      src
+
+test-suite unit
+  import: common-options
+  type: exitcode-stdio-1.0
+  main-is: Test.hs
+  ghc-options: -Wno-unused-packages
+  hs-source-dirs:
+      test
+  build-depends:
+      tasty,
+      tasty-hunit,
+      tasty-quickcheck,
+      esqueleto-postgis,
+      persistent-postgresql,
+      resourcet,
+      monad-logger,
+      hedgehog,
diff --git a/src/Database/Esqueleto/Postgis.hs b/src/Database/Esqueleto/Postgis.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/Esqueleto/Postgis.hs
@@ -0,0 +1,227 @@
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# OPTIONS_GHC -Wno-orphans #-}
+
+-- | Haskell bindings for postgres postgis
+--   for a good explenation see <https://postgis.net/>
+module Database.Esqueleto.Postgis
+  ( PostgisGeometry (..),
+    makePolygon,
+
+    -- * functions
+    st_contains,
+    st_intersects,
+
+    -- * points
+    st_point,
+    st_point_xyz,
+    st_point_xyzm,
+  )
+where
+
+import Data.Bifunctor (first)
+import Data.Ewkb (parseHexByteString)
+import Data.Foldable (Foldable (toList), fold)
+import Data.Geospatial (GeoPoint (..), GeoPositionWithoutCRS (..), GeospatialGeometry, PointXY (..), PointXYZ, PointXYZM)
+import Data.Geospatial qualified as Geospatial
+import Data.Hex (Hex (..))
+import Data.LineString (LineString)
+import Data.LinearRing (LinearRing, makeLinearRing, toSeq)
+import Data.List qualified as List
+import Data.List.NonEmpty (nonEmpty)
+import Data.List.NonEmpty qualified as Non
+import Data.Sequence (Seq (..), (|>))
+import Data.Sequence qualified as Seq
+import Data.String (IsString (..))
+import Data.Text (Text, pack)
+import Data.Text.Lazy (toStrict)
+import Data.Text.Lazy.Builder (toLazyText)
+import Data.Text.Lazy.Builder qualified as Text
+import Database.Esqueleto.Experimental (SqlExpr, Value)
+import Database.Esqueleto.Internal.Internal (unsafeSqlFunction)
+import Database.Persist.Sql
+import GHC.Base (NonEmpty)
+import Data.Geospatial (PointXYZM(..))
+import Data.Geospatial (PointXYZ(..))
+
+tshow :: Show a => a -> Text
+tshow = pack . show
+
+-- | like 'GeospatialGeometry' but not partial, eg no empty geometries, also only works
+--   in a single dimention, eg PostgisGeometry PointXY can't work with PostgisGeometry PointXYZ.
+--   so PointXY indicates a 2 dimension space, and PointXYZ a three dimension space.
+data PostgisGeometry point
+  = Point point
+  | MultiPoint (NonEmpty point)
+  | Line (LineString point)
+  | Multiline (NonEmpty (LineString point))
+  | Polygon (LinearRing point)
+  | MultiPolygon (NonEmpty (LinearRing point))
+  | Collection (NonEmpty (PostgisGeometry point))
+  deriving (Show, Functor, Eq)
+
+data GeomErrors
+  = MismatchingDimensionsXYZ PointXYZ
+  | MismatchingDimensionsXYZM PointXYZM
+  | MismatchingDimensionsXY PointXY
+  | NoGeometry
+  | EmptyPoint
+  | NotImplemented
+  | EmptyMultiline
+  | EmptyMultiPoint
+  | NotEnoughElements
+  | EmptyMultipolygon
+  | EmptyCollection
+  deriving (Show)
+
+-- | checks if the first point is the last, and if not so makes it so.
+--   this is required for inserting into the database
+makePolygon :: (Eq point, Show point) => point -> point -> point -> Seq point -> LinearRing point
+makePolygon one two three other =
+  if Just one == last'
+    then makeLinearRing one two three other
+    else makeLinearRing one two three (other |> one)
+  where
+    last' = Seq.lookup (length other) other
+
+from2dGeoPositionWithoutCRSToPoint :: GeoPositionWithoutCRS -> Either GeomErrors PointXY
+from2dGeoPositionWithoutCRSToPoint = \case
+  GeoEmpty -> Left EmptyPoint
+  GeoPointXY x -> Right x
+  GeoPointXYZ x -> Left (MismatchingDimensionsXYZ x)
+  GeoPointXYZM x -> Left (MismatchingDimensionsXYZM x)
+
+from3dGeoPositionWithoutCRSToPoint :: GeoPositionWithoutCRS -> Either GeomErrors PointXYZ
+from3dGeoPositionWithoutCRSToPoint = \case
+  GeoEmpty -> Left EmptyPoint
+  GeoPointXY x -> Left (MismatchingDimensionsXY x)
+  GeoPointXYZ x -> Right x
+  GeoPointXYZM x -> Left (MismatchingDimensionsXYZM x)
+
+from4dGeoPositionWithoutCRSToPoint :: GeoPositionWithoutCRS -> Either GeomErrors PointXYZM
+from4dGeoPositionWithoutCRSToPoint = \case
+  GeoEmpty -> Left EmptyPoint
+  GeoPointXY x -> Left (MismatchingDimensionsXY x)
+  GeoPointXYZ x -> Left (MismatchingDimensionsXYZ x)
+  GeoPointXYZM x -> Right x
+
+renderPair :: PointXY -> Text.Builder
+renderPair (PointXY {..}) = fromString (show _xyX) <> " " <> fromString (show _xyY)
+
+
+renderXYZ :: PointXYZ -> Text.Builder
+renderXYZ (PointXYZ {..}) = fromString (show _xyzX) <> " " <> fromString (show _xyzY) <> " " <> fromString (show _xyzZ)
+
+
+renderXYZM :: PointXYZM -> Text.Builder
+renderXYZM (PointXYZM {..}) = fromString (show _xyzmX) <> " " <> fromString (show _xyzmY) <> " " <> fromString (show _xyzmZ) <> " " <> fromString (show _xyzmM)
+
+
+renderGeometry :: PostgisGeometry Text.Builder -> Text.Builder
+renderGeometry = \case
+  Point point -> "POINT(" <> point <> ")"
+  MultiPoint points -> "MULTIPOINT (" <> fold (Non.intersperse "," ((\x -> "(" <> x <> ")") <$> points)) <> ")"
+  Line line -> "LINESTRING(" <> renderLines line <> ")"
+  Multiline (multiline) -> "MULTILINESTRING(" <> fold (Non.intersperse "," ((\line -> "(" <> renderLines line <> ")") <$> multiline)) <> ")"
+  Polygon polygon -> "POLYGON((" <> renderLines polygon <> "))"
+  MultiPolygon multipolygon -> "MULTIPOLYGON(" <> fold (Non.intersperse "," ((\line -> "((" <> renderLines line <> "))") <$> multipolygon)) <> ")"
+  Collection collection -> "GEOMETRYCOLLECTION(" <> fold (Non.intersperse "," (renderGeometry <$> collection)) <> ")"
+
+renderLines :: Foldable f => f Text.Builder -> Text.Builder
+renderLines line = fold (List.intersperse "," $ toList line)
+
+from2dGeospatialGeometry :: (Eq a, Show a) => (GeoPositionWithoutCRS -> Either GeomErrors a) -> GeospatialGeometry -> Either GeomErrors (PostgisGeometry a)
+from2dGeospatialGeometry interpreter = \case
+  Geospatial.NoGeometry -> Left NoGeometry
+  Geospatial.Point (GeoPoint point) -> (Point <$> interpreter point)
+  Geospatial.MultiPoint (Geospatial.GeoMultiPoint points) -> do
+    list' <- sequence $ toList (interpreter <$> points)
+    case nonEmpty list' of
+      Nothing -> Left EmptyMultiPoint
+      Just x -> Right $ MultiPoint x
+  Geospatial.Line (Geospatial.GeoLine linestring) -> Line <$> traverse interpreter linestring
+  Geospatial.MultiLine (Geospatial.GeoMultiLine multiline) -> do
+    seqRes <- traverse (traverse interpreter) multiline
+    case Non.nonEmpty (toList seqRes) of
+      Just nonEmpty' -> Right $ Multiline nonEmpty'
+      Nothing -> Left EmptyMultiline
+  Geospatial.Polygon (Geospatial.GeoPolygon polygon) -> Polygon <$> (toLinearRing interpreter) polygon
+  Geospatial.MultiPolygon (Geospatial.GeoMultiPolygon multipolygon) -> do
+    seqRings <- traverse (toLinearRing interpreter) multipolygon
+    case Non.nonEmpty (toList seqRings) of
+      Just nonEmpty' -> Right $ MultiPolygon nonEmpty'
+      Nothing -> Left EmptyMultipolygon
+  Geospatial.Collection seq' -> do
+    seqs <- traverse (from2dGeospatialGeometry interpreter) seq'
+    case Non.nonEmpty (toList seqs) of
+      Just nonEmpty' -> Right $ Collection nonEmpty'
+      Nothing -> Left EmptyCollection
+
+
+toLinearRing :: (Eq a, Show a) => (GeoPositionWithoutCRS -> Either GeomErrors a) -> Seq (LinearRing GeoPositionWithoutCRS) -> Either GeomErrors (LinearRing a)
+toLinearRing interpreter polygon = do
+  aSeq <- traverse interpreter (foldMap toSeq polygon)
+  case aSeq of
+    (one :<| two :<| three :<| rem') -> Right $ makeLinearRing one two three rem'
+    _other -> Left NotEnoughElements
+
+instance PersistField (PostgisGeometry PointXY) where
+  toPersistValue geom =
+    PersistText $ toStrict $ toLazyText $ renderGeometry $ renderPair <$> geom
+  fromPersistValue (PersistLiteral_ Escaped bs) = do
+    result <- first pack $ parseHexByteString (Hex bs)
+    first tshow $ (from2dGeospatialGeometry from2dGeoPositionWithoutCRSToPoint) result
+  fromPersistValue other = Left ("PersistField.Polygon: invalid persist value:" <> tshow other)
+
+instance PersistField (PostgisGeometry PointXYZ) where
+  toPersistValue geom =
+    PersistText $ toStrict $ toLazyText $ renderGeometry $ renderXYZ <$> geom
+  fromPersistValue (PersistLiteral_ Escaped bs) = do
+    result <- first pack $ parseHexByteString (Hex bs)
+    first tshow $ (from2dGeospatialGeometry from3dGeoPositionWithoutCRSToPoint) result
+  fromPersistValue other = Left ("PersistField.Polygon: invalid persist value:" <> tshow other)
+
+instance PersistField (PostgisGeometry PointXYZM) where
+  toPersistValue geom =
+    PersistText $ toStrict $ toLazyText $ renderGeometry $ renderXYZM <$> geom
+  fromPersistValue (PersistLiteral_ Escaped bs) = do
+    result <- first pack $ parseHexByteString (Hex bs)
+    first tshow $ (from2dGeospatialGeometry from4dGeoPositionWithoutCRSToPoint) result
+  fromPersistValue other = Left ("PersistField.Polygon: invalid persist value:" <> tshow other)
+
+instance PersistFieldSql (PostgisGeometry PointXY) where
+  sqlType _ = SqlOther "geometry"
+
+instance PersistFieldSql (PostgisGeometry PointXYZ) where
+  sqlType _ = SqlOther "geometry"
+
+instance PersistFieldSql (PostgisGeometry PointXYZM) where
+  sqlType _ = SqlOther "geometry"
+
+-- | Returns TRUE if geometry A contains geometry B.
+--   https://postgis.net/docs/ST_Contains.html
+st_contains ::
+  -- | geom a
+  SqlExpr (Value (PostgisGeometry a)) ->
+  -- | geom b
+  SqlExpr (Value (PostgisGeometry a)) ->
+  SqlExpr (Value Bool)
+st_contains a b = unsafeSqlFunction "ST_CONTAINS" (a, b)
+
+-- | Returns true if two geometries intersect.
+--   Geometries intersect if they have any point in common.
+--   https://postgis.net/docs/ST_Intersects.html
+st_intersects ::
+  SqlExpr (Value (PostgisGeometry a)) ->
+  SqlExpr (Value (PostgisGeometry a)) ->
+  SqlExpr (Value Bool)
+st_intersects a b = unsafeSqlFunction "ST_Intersects" (a, b)
+
+st_point :: SqlExpr (Value Double) -> SqlExpr (Value Double) -> SqlExpr (Value (PostgisGeometry PointXY))
+st_point a b = unsafeSqlFunction "ST_POINT" (a, b)
+
+st_point_xyz :: SqlExpr (Value Double) -> SqlExpr (Value Double) -> SqlExpr (Value Double) -> SqlExpr (Value (PostgisGeometry PointXYZ))
+st_point_xyz a b c = unsafeSqlFunction "ST_POINT" (a, b, c)
+
+st_point_xyzm :: SqlExpr (Value Double) -> SqlExpr (Value Double) -> SqlExpr (Value Double) -> SqlExpr (Value Double) -> SqlExpr (Value (PostgisGeometry PointXYZM))
+st_point_xyzm a b c m = unsafeSqlFunction "ST_POINT" (a, b, c, m)
diff --git a/test/Test.hs b/test/Test.hs
new file mode 100644
--- /dev/null
+++ b/test/Test.hs
@@ -0,0 +1,208 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE UndecidableInstances #-}
+
+module Main where
+
+import Control.Monad.IO.Class
+import Control.Monad.Logger (MonadLogger (..), runStderrLoggingT)
+import Control.Monad.Trans.Resource (MonadThrow, ResourceT, runResourceT)
+import Data.Geospatial (PointXY (..), PointXYZ (..), PointXYZM (..))
+import Data.LineString (LineString, makeLineString)
+import Data.LinearRing (LinearRing)
+import Data.List.NonEmpty (NonEmpty)
+import Data.Sequence (Seq)
+import qualified Data.Sequence as Seq
+import Database.Esqueleto.Experimental
+import Database.Esqueleto.Postgis
+import Database.Persist
+import Database.Persist.Postgresql
+  ( ConnectionString,
+    withPostgresqlConn,
+  )
+import Database.Persist.TH
+  ( mkMigrate,
+    mkPersist,
+    persistUpperCase,
+    share,
+    sqlSettings,
+  )
+import Hedgehog
+import qualified Hedgehog.Gen as Gen
+import qualified Hedgehog.Range as Range
+import Test.Tasty
+import Test.Tasty.HUnit
+
+connString :: ConnectionString
+connString = "host=localhost port=5432 user=test dbname=test password=test"
+
+-- Test schema
+share
+  [mkPersist sqlSettings, mkMigrate "migrateAll"]
+  [persistUpperCase|
+  Unit sql=unit
+    geom       (PostgisGeometry PointXY)
+    deriving Eq Show
+
+  Unityz sql=unityz
+    geom       (PostgisGeometry PointXYZ)
+    deriving Eq Show
+
+  Unityzm sql=unityzm
+    geom       (PostgisGeometry PointXYZM)
+    deriving Eq Show
+|]
+
+initializeDB ::
+  (MonadIO m) =>
+  SqlPersistT (ResourceT m) ()
+initializeDB = do
+  runMigration migrateAll
+
+runDB :: (forall m. (MonadIO m, MonadLogger m, MonadThrow m) => SqlPersistT (ResourceT m) a) -> IO a
+runDB act =
+  runStderrLoggingT
+    . runResourceT
+    . withPostgresqlConn connString
+    . runSqlConn
+    $ (initializeDB >> act >>= \ret -> transactionUndo >> return ret)
+
+main :: IO ()
+main = defaultMain tests
+
+tests :: TestTree
+tests = testGroup "Tests" [unitTests]
+
+test' :: Gen (PostgisGeometry PointXY) -> TestTree
+test' gen =
+  testCase ("List comparison (different length)") $ do
+    someUnit <- Gen.sample (Unit <$> gen)
+    result <- runDB $ do
+      _ <- insert someUnit
+      selectList @(Unit) [] []
+    (entityVal <$> result) @?= [someUnit]
+
+testxyz :: Gen (PostgisGeometry PointXYZ) -> TestTree
+testxyz gen =
+  testCase ("List comparison (different length)") $ do
+    someUnit <- Gen.sample (Unityz <$> gen)
+    result <- runDB $ do
+      _ <-  insert someUnit
+      selectList @(Unityz) [] []
+    (entityVal <$> result) @?= [someUnit]
+
+testxyzm :: Gen (PostgisGeometry PointXYZM) -> TestTree
+testxyzm gen =
+  testCase ("List comparison (different length)") $ do
+    someUnit <- Gen.sample (Unityzm <$> gen)
+    result <- runDB $ do
+      _ <- insert someUnit
+      selectList @(Unityzm) [] []
+    (entityVal <$> result) @?= [someUnit]
+
+unitTests :: TestTree
+unitTests =
+  testGroup
+    "Unit tests"
+    [ testGroup "roundtrip tests xy" $
+        (test') <$> (genCollection genPointxy : genGeometry genPointxy),
+      testGroup "roundtrip tests xyz" $
+        (testxyz) <$> (genCollection genPointxyz : genGeometry genPointxyz),
+      testGroup "roundtrip tests xyzm" $
+        (testxyzm) <$> (genCollection genPointxyzm : genGeometry genPointxyzm),
+      testGroup "function bindings" $
+        [ testCase ("it finds the one unit with st_contains") $ do
+            result <- runDB $ do
+              _ <- insert $
+                Unit
+                  { unitGeom = Polygon $ makePolygon (PointXY 0 0) (PointXY 0 2) (PointXY 2 2) $ Seq.fromList [(PointXY 2 0)]
+                  }
+
+              selectOne $ do
+                unit <- from $ table @Unit
+                where_ $ unit ^. UnitGeom `st_contains` (val $ Point (PointXY 1 1))
+                pure countRows
+            unValue <$> result @?= (Just (1 :: Int)),
+          testCase ("it finds the one unit with intersection st_point") $ do
+            result <- runDB $ do
+              _ <- insert $
+                Unit
+                  { unitGeom = Polygon $ makePolygon (PointXY 0 0) (PointXY 0 2) (PointXY 2 2) $ Seq.fromList [(PointXY 2 0)]
+                  }
+
+              selectOne $ do
+                unit <- from $ table @Unit
+                where_ $ unit ^. UnitGeom `st_contains` (st_point (val 1) (val 1))
+                pure countRows
+            unValue <$> result @?= (Just (1 :: Int)),
+          testCase ("it finds the one unit with intersection st_intersects") $ do
+            result <- runDB $ do
+              _ <- insert $
+                Unit
+                  { unitGeom = Polygon $ makePolygon (PointXY 0 0) (PointXY 0 2) (PointXY 2 2) $ Seq.fromList [(PointXY 2 0)]
+                  }
+
+              selectOne $ do
+                unit <- from $ table @Unit
+                where_ $ unit ^. UnitGeom `st_intersects` (st_point (val 1) (val 1))
+                pure countRows
+            unValue <$> result @?= (Just (1 :: Int))
+        ]
+    ]
+
+genDouble :: Gen Double
+genDouble = Gen.double (Range.exponentialFloat (-10) 10)
+
+genPointxy :: Gen PointXY
+genPointxy = PointXY <$> genDouble <*> genDouble
+
+genPointxyz :: Gen PointXYZ
+genPointxyz = PointXYZ <$> genDouble <*> genDouble <*> genDouble
+
+genPointxyzm :: Gen PointXYZM
+genPointxyzm = PointXYZM <$> genDouble <*> genDouble <*> genDouble <*> genDouble
+
+genPoints :: Gen a -> Gen (NonEmpty a)
+genPoints genPoint = Gen.nonEmpty (Range.constant 1 10) genPoint
+
+genSeq :: Gen a -> Gen (Seq a)
+genSeq genPoint = Seq.fromList <$> Gen.list (Range.constant 0 10) genPoint
+
+genLineString :: Gen a -> Gen (LineString a)
+genLineString genPoint = makeLineString <$> genPoint <*> genPoint <*> genSeq genPoint
+
+genMultiLineString :: Gen a -> Gen (NonEmpty (LineString a))
+genMultiLineString genPoint = Gen.nonEmpty (Range.constant 1 10) (genLineString genPoint)
+
+genLinearring :: (Eq a, Show a) => Gen a -> Gen (LinearRing a)
+genLinearring genPoint = makePolygon <$> genPoint <*> genPoint <*> genPoint <*> genSeq genPoint
+
+genMultiLinearring :: (Eq a, Show a) => Gen a -> Gen (NonEmpty (LinearRing a))
+genMultiLinearring genPoint = Gen.nonEmpty (Range.constant 1 10) (genLinearring genPoint)
+
+genCollection :: (Eq a, Show a) => Gen a -> Gen (PostgisGeometry a)
+genCollection genPoint = Collection <$> Gen.nonEmpty (Range.constant 1 10) (Gen.choice (genGeometry genPoint))
+
+genGeometry :: (Eq a, Show a) => Gen a -> [Gen (PostgisGeometry a)]
+genGeometry genPoint =
+  [ -- pure NoGeometry
+    (Point <$> genPoint),
+    (MultiPoint <$> genPoints genPoint),
+    (Line <$> genLineString genPoint),
+    (Multiline <$> genMultiLineString genPoint),
+    (Polygon <$> genLinearring genPoint),
+    (MultiPolygon <$> genMultiLinearring genPoint)
+  ]
