packages feed

jsonsql 0.1.0.1 → 0.1.2.0

raw patch · 5 files changed

+92/−53 lines, 5 files

Files

Main.hs view
@@ -18,6 +18,7 @@ import qualified Data.Attoparsec.Lazy as LA hiding (Result, parseOnly) import Data.Attoparsec.ByteString.Char8 (endOfLine, sepBy) import Data.Attoparsec.Text +import qualified Data.Attoparsec.Text as AttoT import qualified Data.HashMap.Lazy as HM import qualified Data.Vector as V import Data.Scientific @@ -58,8 +59,9 @@       chunks :: [Chunk]        chunks = parseText template       results = mconcat $ map (evalText chunks) vs-  TL.putStrLn . B.toLazyText $ results+  TL.putStr . B.toLazyText $ results + decodeStream :: (FromJSON a) => BL.ByteString -> [a] decodeStream bs = case decodeWith json bs of     (Just x, xs) | xs == mempty -> [x]@@ -76,18 +78,17 @@                       _ -> (Nothing, r)) $ fromJSON v'  -data ArrayFormat = ArrayFormat {-    arrDelimiter :: Text-  , arrPrefixStr :: Text  -- can me mempty-  , arrPostFixStr :: Text-  } deriving (Show, Eq)+data ArrayFormat = ArrayFormat Text deriving (Show, Eq)  data Chunk = Pass Text | Expr KeyPath deriving (Show, Eq)-data KeyPath = KeyPath [Key] ArrayFormat deriving (Show, Eq) -- Text fields are array item prefix and postfix strings, which is given at end-data Key = Key Text | Index Int deriving (Eq, Show)+data KeyPath = KeyPath [Key] (Maybe ArrayFormat) +      deriving (Show, Eq) +data Key = Key Text | Index Int | RootKey deriving (Eq, Show)  evalText :: [Chunk] -> Value -> B.Builder-evalText xs v = mconcat $ map (B.fromText . evalChunk v) xs+evalText xs v = +    let line = mconcat $ map (B.fromText . evalChunk v) xs+    in line <> B.fromText "\n"  evalChunk :: Value -> Chunk -> Text evalChunk v (Pass s) = s@@ -101,11 +102,12 @@  -- evaluates the a JS key path against a Value context to a leaf Value evalKeyPath :: KeyPath -> Value -> Value+evalKeyPath (KeyPath [RootKey] _) x = x -- print the root object evalKeyPath (KeyPath [] _) x@(String _) = x evalKeyPath (KeyPath [] _) x@Null = x evalKeyPath (KeyPath [] _) x@(Number _) = x evalKeyPath (KeyPath [] _) x@(Bool _) = x-evalKeyPath (KeyPath [] _) x@(Object _) = x+evalKeyPath (KeyPath [] _) x@(Object _) = x -- print object evalKeyPath (KeyPath (Key key:ks) a) (Object s) =      case (HM.lookup key s) of         Just x          -> evalKeyPath (KeyPath ks a) x@@ -115,11 +117,14 @@       in case e of          Just e' -> evalKeyPath (KeyPath ks a) e'         Nothing -> Null  -evalKeyPath k@(KeyPath _ ArrayFormat {..}) (Array v) = +evalKeyPath (KeyPath [] Nothing) x@(Array _) = x  -- print literal object+evalKeyPath (KeyPath [] (Just (ArrayFormat a))) (Array v) =        let vs = V.toList v-          f =  (\v' -> escapeText $ arrPrefixStr <> evalToUnescapedText k v' <> arrPostFixStr)-          result = mconcat . intersperse arrDelimiter $ map f vs -      in (String result)+      in String $ mconcat . intersperse a $ map valToUnescapedText $ vs +evalKeyPath k@(KeyPath _ a) (Array v) = +      let vs = V.toList v+          f = evalKeyPath k+      in evalKeyPath (KeyPath [] a) (Array $ V.fromList $ map f vs) evalKeyPath (KeyPath (Index _:_) _ ) _ = Null evalKeyPath _ _ = Null @@ -140,13 +145,20 @@     case floatingOrInteger x of         Left float -> T.pack . show $ float         Right int -> T.pack . show $ int-valToText x@(Object _) = error $ "Cannot interpolate " ++ show x+valToText x@(Object _) = literalJSON x+valToText x@(Array _) = literalJSON x  escapeStringLiteral :: String -> String escapeStringLiteral ('\'':xs) = '\'': ('\'' : escapeStringLiteral xs) escapeStringLiteral (x:xs) = x : escapeStringLiteral xs escapeStringLiteral [] = [] ++literalJSON :: Value -> Text+literalJSON x = +    let t = T.decodeUtf8 . Prelude.head . BL.toChunks. encode $ x+    in valToText . String $ t+ parseText :: Text -> [Chunk] parseText = either error id . parseOnly (many textChunk) @@ -158,7 +170,7 @@ exprChunk = do     try (char ':')     x <- notChar ':'-    xs <- takeWhile1 identifierChar+    xs <- AttoT.takeWhile identifierChar     arrFormat <- pArrayFormat     let kp = parseKeyPath (T.singleton x <> xs) arrFormat     return $ Expr kp@@ -166,30 +178,33 @@ passChunk :: Parser Chunk passChunk = Pass <$> takeWhile1 (notInClass ":") -parseKeyPath :: Text -> ArrayFormat -> KeyPath+parseKeyPath :: Text -> Maybe ArrayFormat -> KeyPath parseKeyPath s a = case parseOnly pKeys s of     Left err -> error $ "Error parsing key path: " ++ T.unpack s ++ " error: " ++ err      Right res -> KeyPath res a  pKeys :: Parser [Key] pKeys = do-    keys <- sepBy1 pKeyOrIndex (takeWhile1 $ inClass ".[") +    -- if the first character is '.', then it's an empty keypath+    -- which evaluates to the whole objject+    keys <- rootKey <|> (sepBy1 pKeyOrIndex (takeWhile1 $ inClass ".["))     return keys  --- | syntax is {delimiter-string!prefix-string!postfix-string}--- immediately after last key--- e.g. {,!!}-pArrayFormat :: Parser ArrayFormat+rootKey :: Parser [Key]+rootKey = char '.' >> return [RootKey]++-- | syntax is [delimiter-string] immediately after last key++pArrayFormat :: Parser (Maybe ArrayFormat) pArrayFormat = do +  let leftDelim = '<'+      rightDelim = '>'   try (do-       char '{'-       delimiter <- T.pack <$> manyTill anyChar (char '!')-       pre <- T.pack <$> manyTill anyChar (char '!')-       post <- T.pack <$> manyTill anyChar (char '}')-       return $ ArrayFormat delimiter pre post)-  <|> pure defArrayFormat--defArrayFormat = ArrayFormat "," "" ""+       char leftDelim+       delimiter <- T.pack <$> manyTill anyChar (char rightDelim)+       -- char rightDelim+       return . Just $ ArrayFormat delimiter)+  <|> pure Nothing  pKeyOrIndex = pIndex <|> pKey 
README.md view
@@ -63,12 +63,12 @@  ## Array joining -If a key path evaluates to an array of values, the values are converted-into strings, joined by a delimiter, and then output as a string. The-default delimiter is a comma:+If a key path evaluates to an array of values, specify a `<delimiter>` to have+the the values converted into strings, joined by the delimiter, and then+output as a string.   ```-INSERT into titles (title, year, rating, stars, created) +INSERT into titles (title, year, rating, stars<,>, created)  VALUES (:title, :year, :ratings.imdb, :stars.name, DEFAULT); ``` @@ -79,35 +79,29 @@ VALUES ('Interstellar', 2014, 8.9, 'Matthew McConaughey,Anne Hathaway', DEFAULT); ``` -A key path that terminates in an array can be followed by an array formatting expression:--```-{delimiter-string!prefix-sring!postfix-string}-```+## JSON literal interpolation +If a key path terminates in a JSON object or array (without a array delimiter+specified), a JSON literal will be interpolated:  template: ```-INSERT into titles (title, year, rating, stars, created) -VALUES (:title, :year, :ratings.imdb, :stars.name{;!$!$}, DEFAULT);+INSERT into titles (title, stars) +VALUES (:title, :stars); ```  output: ```-INSERT into titles (title, year, rating, stars, created)-VALUES ('Terminator 2: ''Judgment Day''', 1991, 8.5, '$Arnold Schwarzenegger$;$Linda Hamilton$', DEFAULT);-INSERT into titles (title, year, rating, stars, created)-VALUES ('Interstellar', 2014, 8.9, '$Matthew McConaughey$;$Anne Hathaway$', DEFAULT);+INSERT into titles (title, stars)+VALUES ('Terminator 2: ''Judgment Day''', '[{"name":"Arnold Schwarzenegger"},{"name":"Linda Hamilton"}]');+INSERT into titles (title, stars) +VALUES ('Interstellar', '[{"name":"Matthew McConaughey"},{"name":"Anne Hathaway"}]'); ``` -Adding a prefix and postfix may be useful if you want to mark strings-for downstream pipeline processing with tools like `sed` before reaching-the database.+To interpolate the base object as a literal JSON string, use `:.` as the keypath. -The usefulness of this feature may be obscure. But the author needed it to-change an array of strings like `["apple","banana","pear"]` into a string field-containing a series of integer IDs like `'1,2,3'`. This type of field was then -indexed by the Sphinx search engine in a multi-valued attribute.+Do NOT put quotes around the placeholder for a literal JSON interpolation.+  ## Author 
jsonsql.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                jsonsql-version:             0.1.0.1+version:             0.1.2.0 synopsis:            Interpolate JSON object values into SQL strings -- description:          homepage:            https://github.com/danchoi/jsonsql
+ stack.yaml view
@@ -0,0 +1,6 @@+flags: {}+packages:+- '.'+extra-deps: +- string-qq-0.0.2+resolver: lts-2.16
+ test.json view
@@ -0,0 +1,24 @@+{+  "title": "Terminator 2: 'Judgment Day'",+  "year": 1991,+  "stars": [+    {"name": "Arnold Schwarzenegger"},+    {"name": "Linda Hamilton"}+  ],+  "ratings": {+    "imdb": 8.5+  },+  "created": "2014-12-04T10:10:10Z"+  +}+{+  "title": "Interstellar",+  "year": 2014,+  "stars": [+    {"name":"Matthew McConaughey"},+    {"name":"Anne Hathaway"}+  ],+  "ratings": {+    "imdb": 8.9+  }+}