diff --git a/library/PostgresqlSyntax/Extras/HeadedMegaparsec.hs b/library/PostgresqlSyntax/Extras/HeadedMegaparsec.hs
--- a/library/PostgresqlSyntax/Extras/HeadedMegaparsec.hs
+++ b/library/PostgresqlSyntax/Extras/HeadedMegaparsec.hs
@@ -20,7 +20,19 @@
 -- * Executors
 
 run :: (Ord err, VisualStream strm, TraversableStream strm, Megaparsec.ShowErrorComponent err) => HeadedParsec err strm a -> strm -> Either String a
-run p = first Megaparsec.errorBundlePretty . Megaparsec.runParser (toParsec p <* Megaparsec.eof) ""
+run p = first Megaparsec.errorBundlePretty . runParser p
+
+-- |
+-- Run the parser but returns all the error messages separated, along with their offset in the parsed message.
+runParserWithErrorPos :: (Show (Megaparsec.Token strm), Show e, Ord e, VisualStream strm, TraversableStream strm, Megaparsec.ShowErrorComponent e) => HeadedParsec e strm a -> strm -> Either (NonEmpty (Int, String)) a
+runParserWithErrorPos p s = case runParser p s of
+  Left err -> Left (extractor <$> Megaparsec.bundleErrors err)
+  Right v -> Right v
+  where
+    extractor x = (Megaparsec.errorOffset x, Megaparsec.parseErrorPretty x)
+
+runParser :: (Ord e, VisualStream strm, TraversableStream strm) => HeadedParsec e strm a -> strm -> Either (Megaparsec.ParseErrorBundle strm e) a
+runParser p = Megaparsec.runParser (toParsec p <* Megaparsec.eof) ""
 
 -- * Primitives
 
diff --git a/library/PostgresqlSyntax/Parsing.hs b/library/PostgresqlSyntax/Parsing.hs
--- a/library/PostgresqlSyntax/Parsing.hs
+++ b/library/PostgresqlSyntax/Parsing.hs
@@ -54,6 +54,9 @@
 run :: Parser a -> Text -> Either String a
 run = Extras.run
 
+runWithPosError :: Parser a -> Text -> Either (NonEmpty (Int, String)) a
+runWithPosError = Extras.runParserWithErrorPos
+
 -- * Helpers
 
 commaSeparator :: Parser ()
@@ -115,6 +118,23 @@
          in collectChunks mempty
   return tail
 
+-- |
+-- >>> testParser dollarQuotedSconst "$$it's good$$"
+-- "it's good"
+dollarQuotedSconst :: Parser Text
+dollarQuotedSconst = do
+  char '$'
+  quoteTag <- takeWhileP Nothing (/= '$')
+  let terminator = Megaparsec.chunk $ "$" <> quoteTag <> "$"
+  char '$'
+  endHead
+  tail <-
+    parse $ do
+      body <- many $ Megaparsec.notFollowedBy terminator *> Megaparsec.anySingle
+      terminator
+      return $ Text.pack body
+  return tail
+
 atEnd :: Parser a -> Parser a
 atEnd p = space *> p <* endHead <* space <* eof
 
@@ -1552,7 +1572,7 @@
 
 fconst = float
 
-sconst = quotedString '\''
+sconst = quotedString '\'' <|> dollarQuotedSconst
 
 constTypename =
   asum
diff --git a/postgresql-syntax.cabal b/postgresql-syntax.cabal
--- a/postgresql-syntax.cabal
+++ b/postgresql-syntax.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: postgresql-syntax
-version: 0.4.1.3
+version: 0.4.2
 category: Database, PostgreSQL, Parsing
 synopsis: PostgreSQL AST parsing and rendering
 description:
diff --git a/tasty-test/Main.hs b/tasty-test/Main.hs
--- a/tasty-test/Main.hs
+++ b/tasty-test/Main.hs
@@ -1,5 +1,6 @@
 module Main where
 
+import qualified Data.List.NonEmpty as NonEmpty
 import qualified Data.Text as Text
 import qualified PostgresqlSyntax.Parsing as Parsing
 import Test.Tasty
@@ -56,6 +57,38 @@
                       "inet",
                       "json",
                       "jsonb"
+                    ],
+                  testParserOnAllInputs
+                    "sconst"
+                    Parsing.sconst
+                    [ "'it''s good'",
+                      "$$it's good$$",
+                      "$x$it's good$x$"
                     ]
+                ],
+        testGroup "Error reporting"
+          $ let testParserOnAllInputs parserName parser inputs res =
+                  testCase parserName
+                    $ forM_ inputs
+                    $ \input -> case Parsing.runWithPosError parser input of
+                      Left err -> show (NonEmpty.head err) @?= res
+                      Right _ -> return ()
+             in [ testParserOnAllInputs
+                    "Typo in FROM keyword"
+                    Parsing.preparableStmt
+                    [ "select i :: int8 fom auth.user as u\n\
+                      \inner join edgenode.usere_provider as p\n\
+                      \on u.id = p.user_id\n\
+                      \inner join edgenode.provider_branch as b\n\
+                      \on b.provider_fk = p.provider_id"
+                    ]
+                    "(20,\"offset=20:\\nunexpected space\\nexpecting end of input\\n\")",
+                  testParserOnAllInputs
+                    "Typo in NOT keyword"
+                    Parsing.preparableStmt
+                    [ "select i :: int8 from auth.user as u\n\
+                      \WHERE u.id IS NO NULL && TRUE"
+                    ]
+                    "(53,\"offset=53:\\nexpecting white space\\n\")"
                 ]
       ]
