postgresql-placeholder-converter (empty) → 0.1.0.0
raw patch · 9 files changed
+296/−0 lines, 9 filesdep +attoparsecdep +basedep +bytestringsetup-changed
Dependencies added: attoparsec, base, bytestring, hspec, mtl, postgresql-placeholder-converter, utf8-string
Files
- CHANGELOG.md +5/−0
- LICENSE +29/−0
- README.md +5/−0
- Setup.hs +2/−0
- postgresql-placeholder-converter.cabal +58/−0
- src/Database/PostgreSQL/Placeholder/Convert.hs +11/−0
- src/Database/PostgreSQL/Placeholder/Convert/QuestionToDollar.hs +160/−0
- test/Database/PostgreSQL/Placeholder/ConvertSpec.hs +25/−0
- test/Spec.hs +1/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history++## 0.1.0.0 -- 2020-06-29++- Released.
+ LICENSE view
@@ -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.
+ README.md view
@@ -0,0 +1,5 @@+# postgresql-placeholder-converter++[](https://github.com/kakkun61/postgresql-placeholder-converter/actions?query=workflow%3Atest)++[](http://hackage.haskell.org/package/postgresql-placeholder-converter)
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ postgresql-placeholder-converter.cabal view
@@ -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
+ src/Database/PostgreSQL/Placeholder/Convert.hs view
@@ -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
+ src/Database/PostgreSQL/Placeholder/Convert/QuestionToDollar.hs view
@@ -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
+ test/Database/PostgreSQL/Placeholder/ConvertSpec.hs view
@@ -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$"
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}