diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history
+
+## 0.1.0.0 -- 2020-06-29
+
+- Released.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,29 @@
+Copyright (c) 2019, IIJ Innovation Institute Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+  * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in
+    the documentation and/or other materials provided with the
+    distribution.
+  * Neither the name of the copyright holders nor the names of its
+    contributors may be used to endorse or promote products derived
+    from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+# postgresql-placeholder-converter
+
+[![GitHub Actions](https://github.com/kakkun61/postgresql-placeholder-converter/workflows/test/badge.svg?branch=master)](https://github.com/kakkun61/postgresql-placeholder-converter/actions?query=workflow%3Atest)
+
+[![Hackage](https://matrix.hackage.haskell.org/api/v2/packages/postgresql-placeholder-converter/badge)](http://hackage.haskell.org/package/postgresql-placeholder-converter)
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/postgresql-placeholder-converter.cabal b/postgresql-placeholder-converter.cabal
new file mode 100644
--- /dev/null
+++ b/postgresql-placeholder-converter.cabal
@@ -0,0 +1,58 @@
+cabal-version:       2.2
+
+name:                postgresql-placeholder-converter
+version:             0.1.0.0
+author:              Kazuki Okamoto
+copyright:           2020 Kazuki Okamoto, 2019 IIJ Innovation Institute Inc.
+license:             BSD-3-Clause
+license-file:        LICENSE
+maintainer:          kazuki.okamoto@kakkun61.com
+category:            Database
+description:         This library provides functions to convert question mark style to dollar sign style of PostgreSQL SQL.
+synopsis:            Converter for question mark style and dollar sign style of PostgreSQL SQL.
+homepage:            https://github.com/kakkun61/postgresql-placeholder-converter
+bug-reports:         https://github.com/kakkun61/postgresql-placeholder-converter/issues
+
+build-type:          Simple
+
+extra-source-files:  README.md,
+                     CHANGELOG.md
+
+common common
+  build-depends:       base >= 4.12 && < 4.15
+  default-language:    Haskell2010
+
+library
+  import:              common
+  hs-source-dirs:      src
+  exposed-modules:     Database.PostgreSQL.Placeholder.Convert
+  other-modules:       Database.PostgreSQL.Placeholder.Convert.QuestionToDollar
+  build-depends:       attoparsec,
+                       bytestring,
+                       mtl,
+                       utf8-string
+  ghc-options:         -Wall
+                       -Wcompat
+                       -Wincomplete-uni-patterns
+                       -Wincomplete-record-updates
+                       -Wmonomorphism-restriction
+                       -Wmissing-exported-signatures
+                       -Wmissing-home-modules
+                       -Wmissing-import-lists
+                       -Widentities
+                       -Wredundant-constraints
+                       -Wno-name-shadowing
+
+test-suite test
+  import:             common
+  type:               exitcode-stdio-1.0
+  hs-source-dirs:     test
+  main-is:            Spec.hs
+  other-modules:      Database.PostgreSQL.Placeholder.ConvertSpec
+  ghc-options:        -threaded
+                      -rtsopts
+                      -with-rtsopts=-N
+                      -Wno-missing-import-lists
+  build-depends:      postgresql-placeholder-converter,
+                      hspec
+  build-tool-depends: hspec-discover:hspec-discover
diff --git a/src/Database/PostgreSQL/Placeholder/Convert.hs b/src/Database/PostgreSQL/Placeholder/Convert.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/PostgreSQL/Placeholder/Convert.hs
@@ -0,0 +1,11 @@
+module Database.PostgreSQL.Placeholder.Convert
+  ( convertQuestionMarkStyleToDollarSignStyle
+  , QD.splitQueries
+  ) where
+
+import qualified Database.PostgreSQL.Placeholder.Convert.QuestionToDollar as QD
+
+import Data.ByteString (ByteString)
+
+convertQuestionMarkStyleToDollarSignStyle :: ByteString-> Either String ByteString
+convertQuestionMarkStyleToDollarSignStyle = QD.convert
diff --git a/src/Database/PostgreSQL/Placeholder/Convert/QuestionToDollar.hs b/src/Database/PostgreSQL/Placeholder/Convert/QuestionToDollar.hs
new file mode 100644
--- /dev/null
+++ b/src/Database/PostgreSQL/Placeholder/Convert/QuestionToDollar.hs
@@ -0,0 +1,160 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Database.PostgreSQL.Placeholder.Convert.QuestionToDollar
+  ( convert
+  , splitQueries
+  ) where
+
+import           Control.Monad                    (void)
+import           Control.Monad.State.Strict       (StateT, evalStateT, lift, state)
+import qualified Data.Attoparsec.ByteString       as AP
+import qualified Data.Attoparsec.ByteString.Char8 as APC
+import           Data.Attoparsec.Combinator       ((<?>))
+import qualified Data.Attoparsec.Combinator       as AP
+import qualified Data.Attoparsec.Internal.Types   as API
+import           Data.ByteString                  (ByteString)
+import qualified Data.ByteString                  as BS
+import qualified Data.ByteString.UTF8             as BSU
+
+-- | Convert question mark style to dollar sign style of PostgreSQL SQL.
+convert :: ByteString-> Either String ByteString
+convert q =
+  flip AP.parseOnly q $
+    flip evalStateT 1 $
+      (mconcat <$>) $ do
+        ss <-
+          AP.many' $
+            AP.choice
+              [ question
+              , lift questionLiteral
+              , lift singleQuoteLiteral
+              , lift doubleQuoteLiteral
+              , lift dollarQuoteLiteral
+              , lift lineComment
+              , lift blockComment
+              , lift $ BS.pack . (:[]) <$> AP.anyWord8
+              ]
+        lift AP.endOfInput
+        pure ss
+
+singleQuoteLiteral :: AP.Parser BS.ByteString
+singleQuoteLiteral = oneCharQuoteLiteral '\'' <?> "singleQuoteLiteral"
+
+doubleQuoteLiteral :: AP.Parser BS.ByteString
+doubleQuoteLiteral = oneCharQuoteLiteral '"' <?> "doubleQuoteLiteral"
+
+oneCharQuoteLiteral :: Char -> AP.Parser BS.ByteString
+oneCharQuoteLiteral quote =
+  takeConsumed $ do
+    void $ APC.char quote
+    body
+    void $ APC.char quote
+  where
+    body = do
+      void $ APC.takeTill ((||) <$> (== '\\') <*> (== quote))
+      AP.option () $ do
+        void $ APC.char '\\'
+        void APC.anyChar -- It continues although it is something wrong if the character does not stand for escape sequence.
+        body
+
+dollarQuoteLiteral :: AP.Parser BS.ByteString
+dollarQuoteLiteral =
+  (<?> "dollarQuoteLiteral") $
+    takeConsumed $ do
+      quote <- dollarQuote
+      go quote
+  where
+    dollarQuote =
+      (<?> "dollarQuote") $
+        takeConsumed $ do
+          void $ APC.char '$'
+          void $ AP.many' $ APC.satisfy ((&&) <$> (/= ' ') <*> (/= '$'))
+          void $ APC.char '$'
+    go quote = go'
+      where
+        go' = do
+          void $ AP.many' $ APC.satisfy (/= '$')
+          AP.choice
+            [ void $ AP.string quote
+            , APC.char '$' *> go'
+            ]
+
+lineComment :: AP.Parser BS.ByteString
+lineComment =
+  (<?> "lineComment") $
+    takeConsumed $ do
+      void "--"
+      void $ AP.many' $ APC.satisfy ((&&) <$> (/= '\r') <*> (/= '\n'))
+      AP.option () $ void APC.endOfLine
+
+blockComment :: AP.Parser BS.ByteString
+blockComment =
+  (<?> "blockComment") $
+    takeConsumed $ do
+      void "/*"
+      go
+  where
+    go = do
+      void $
+        AP.many' $
+          AP.choice
+            [ void blockComment
+            , void $ APC.satisfy ((&&) <$> (/= '/') <*> (/= '*'))
+            ]
+      AP.choice
+        [ void "*/"
+        , AP.lookAhead "/*" *> go
+        , APC.satisfy ((||) <$> (== '/') <*> (== '*')) *> go
+        ]
+
+question :: StateT Int AP.Parser BS.ByteString
+question = do
+  void $ lift $ APC.char '?'
+  i <- getAndIncrement
+  pure $ "$" <> BSU.fromString (show i)
+  where
+    getAndIncrement :: StateT Int AP.Parser Int
+    getAndIncrement = state (\i -> (i, i + 1))
+
+questionLiteral :: AP.Parser BS.ByteString
+questionLiteral = takeConsumed "\\?"
+
+-- | Split a query including multiple queries separated by semicolon.
+splitQueries :: ByteString -> [ByteString]
+splitQueries q =
+  case AP.parseOnly parser q of
+    Right qs -> qs
+    Left _   -> [q]
+  where
+    parser =
+      (conv <$>) $
+        AP.many' $
+          AP.choice
+            [ APC.char ';' >> pure Nothing
+            , Just <$> singleQuoteLiteral
+            , Just <$> doubleQuoteLiteral
+            , Just <$> dollarQuoteLiteral
+            , Just <$> lineComment
+            , Just <$> blockComment
+            , Just . BS.pack . (:[]) <$> AP.anyWord8
+            ]
+    conv :: [Maybe BS.ByteString] -> [BS.ByteString]
+    conv =
+      foldr go []
+      where
+        go (Just s) (a:as) = (s <> a):as
+        go (Just s) []     = [s]
+        go Nothing acc     = "":acc
+
+takeConsumed :: AP.Parser a -> AP.Parser BS.ByteString
+takeConsumed p = do
+  n <-
+    AP.lookAhead $ do
+      API.Pos startPos <- currentPos
+      void p
+      API.Pos endPos <- currentPos
+      pure $ endPos - startPos
+  AP.take n
+
+currentPos :: AP.Parser API.Pos
+currentPos = API.Parser $ \t pos more _lose suc -> suc t pos more pos
diff --git a/test/Database/PostgreSQL/Placeholder/ConvertSpec.hs b/test/Database/PostgreSQL/Placeholder/ConvertSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Database/PostgreSQL/Placeholder/ConvertSpec.hs
@@ -0,0 +1,25 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Database.PostgreSQL.Placeholder.ConvertSpec (spec) where
+
+import Database.PostgreSQL.Placeholder.Convert
+
+import Test.Hspec
+
+spec :: Spec
+spec = do
+  describe "convertQuestionMarkStyleToDollarSignStyle" $ do
+    it "SELECT * FROM person" $ do
+      convertQuestionMarkStyleToDollarSignStyle "SELECT * FROM person" `shouldBe` Right "SELECT * FROM person"
+
+    it "SELECT * FROM person WHERE id = ?" $ do
+      convertQuestionMarkStyleToDollarSignStyle "SELECT * FROM person WHERE id = ?" `shouldBe` Right "SELECT * FROM person WHERE id = $1"
+
+    it "SELECT * FROM person WHERE name = '?'" $ do
+      convertQuestionMarkStyleToDollarSignStyle "SELECT * FROM person WHERE name = '?'" `shouldBe` Right "SELECT * FROM person WHERE name = '?'"
+
+    it "SELECT * FROM person WHERE name = $$?$$" $ do
+      convertQuestionMarkStyleToDollarSignStyle "SELECT * FROM person WHERE name = $$?$$" `shouldBe` Right "SELECT * FROM person WHERE name = $$?$$"
+
+    it "SELECT * FROM person WHERE name = $name$?$name$" $ do
+      convertQuestionMarkStyleToDollarSignStyle "SELECT * FROM person WHERE name = $name$?$name$" `shouldBe` Right "SELECT * FROM person WHERE name = $name$?$name$"
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
