Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Implement new TypeInfo system and fix array conversion

  • Loading branch information...
commit 136a6f89822fa355c9400f60110d96c8050bcb5e 1 parent fb39887
@lpsmith authored
View
2  postgresql-simple.cabal
@@ -35,6 +35,7 @@ Library
Database.PostgreSQL.Simple.ToRow
Database.PostgreSQL.Simple.Transaction
Database.PostgreSQL.Simple.TypeInfo
+ Database.PostgreSQL.Simple.TypeInfo.Static
Database.PostgreSQL.Simple.Types
Database.PostgreSQL.Simple.Errors
-- Other-modules:
@@ -43,6 +44,7 @@ Library
Other-modules:
Database.PostgreSQL.Simple.Compat
Database.PostgreSQL.Simple.Time.Implementation
+ Database.PostgreSQL.Simple.TypeInfo.Types
Build-depends:
attoparsec >= 0.8.5.3,
View
48 src/Database/PostgreSQL/Simple/FromField.hs
@@ -73,7 +73,9 @@ module Database.PostgreSQL.Simple.FromField
, Field
, typename
- , typeinfo
+ , TypeInfo(..)
+ , typeInfo
+ , typeInfoByOid
, name
, tableOid
, tableColumn
@@ -94,7 +96,6 @@ import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.Int (Int16, Int32, Int64)
import Data.List (foldl')
-import Data.Maybe (fromMaybe)
import Data.Ratio (Ratio)
import Data.Time ( UTCTime, ZonedTime, LocalTime, Day, TimeOfDay )
import Data.Typeable (Typeable, typeOf)
@@ -106,9 +107,9 @@ import Database.PostgreSQL.Simple.BuiltinTypes
import Database.PostgreSQL.Simple.Compat
import Database.PostgreSQL.Simple.Ok
import Database.PostgreSQL.Simple.Types (Binary(..), Null(..))
-import Database.PostgreSQL.Simple.TypeInfo
+import Database.PostgreSQL.Simple.TypeInfo as TypeInfo
import Database.PostgreSQL.Simple.Time
-import Database.PostgreSQL.Simple.Arrays
+import Database.PostgreSQL.Simple.Arrays as Arrays
import qualified Database.PostgreSQL.LibPQ as PQ
import qualified Data.ByteString as SB
import qualified Data.ByteString.Char8 as B8
@@ -179,12 +180,16 @@ class FromField a where
-- meta-schema.
typename :: Field -> Conversion ByteString
-typename field = typname . typ <$> typeinfo field
+typename field = typname <$> typeInfo field
-typeinfo :: Field -> Conversion TypeInfo
-typeinfo Field{..} = Conversion $ \conn -> do
+typeInfo :: Field -> Conversion TypeInfo
+typeInfo Field{..} = Conversion $ \conn -> do
Ok <$> (getTypeInfo conn =<< PQ.ftype result column)
+typeInfoByOid :: PQ.Oid -> Conversion TypeInfo
+typeInfoByOid oid = Conversion $ \conn -> do
+ Ok <$> (getTypeInfo conn oid)
+
-- | Returns the name of the column. This is often determined by a table
-- definition, but it can be set using an @as@ clause.
@@ -205,7 +210,7 @@ tableOid Field{..} = toMaybeOid (unsafeDupablePerformIO (PQ.ftable result column
else Just x
-- | If the column has a table associated with it, this returns the number
--- of the associated table column. Numbering starts from 0. Analogous
+-- off the associated table column. Numbering starts from 0. Analogous
-- to libpq's @PQftablecol@.
tableColumn :: Field -> Int
@@ -352,14 +357,27 @@ instance (FromField a, FromField b) => FromField (Either a b) where
<|> (Left <$> fromField f dat)
instance (FromField a, Typeable a) => FromField (Vector a) where
- fromField f dat = either (returnError ConversionFailed f)
- (V.fromList <$>)
- (parseOnly (fromArray ',' f) (maybe "" id dat))
-
-fromArray :: (FromField a) => Char -> Field -> Parser (Conversion [a])
-fromArray delim f = sequence . (parseIt <$>) <$> array delim
+ fromField f mdat = do
+ info <- typeInfo f
+ case info of
+ TypeInfo.Array{} ->
+ case mdat of
+ Nothing -> returnError UnexpectedNull f ""
+ Just dat -> do
+ case parseOnly (fromArray info f) dat of
+ Left err -> returnError ConversionFailed f err
+ Right conv -> V.fromList <$> conv
+ _ -> returnError Incompatible f ""
+
+fromArray :: (FromField a)
+ => TypeInfo -> Field -> Parser (Conversion [a])
+fromArray typeInfo f = sequence . (parseIt <$>) <$> array delim
where
- parseIt item = (fromField f . Just . fmt delim) item
+ delim = typdelim (typelem typeInfo)
+ fElem = f{ typeOid = typoid (typelem typeInfo) }
+ parseIt item = (fromField f' . Just . fmt delim) item
+ where f' | Arrays.Array _ <- item = f
+ | otherwise = fElem
newtype Compat = Compat Word64
View
2  src/Database/PostgreSQL/Simple/FromRow.hs
@@ -79,7 +79,7 @@ getTypeInfoByCol Row{..} col =
Ok <$> getTypeInfo conn oid
getTypenameByCol :: Row -> PQ.Column -> Conversion ByteString
-getTypenameByCol row col = typname . typ <$> getTypeInfoByCol row col
+getTypenameByCol row col = typname <$> getTypeInfoByCol row col
fieldWith :: FieldParser a -> RowParser a
fieldWith fieldP = RP $ do
View
1  src/Database/PostgreSQL/Simple/FromRow.hs-boot
@@ -5,3 +5,4 @@ import {-# SOURCE #-} Database.PostgreSQL.Simple.FromField
class FromRow a
instance (FromField a, FromField b, FromField c, FromField d) => FromRow (a,b,c,d)
+instance (FromField a, FromField b, FromField c, FromField d, FromField e) => FromRow (a,b,c,d,e)
View
11 src/Database/PostgreSQL/Simple/Internal.hs
@@ -39,6 +39,7 @@ import qualified Database.PostgreSQL.LibPQ as PQ
import Database.PostgreSQL.LibPQ(ExecStatus(..))
import Database.PostgreSQL.Simple.Ok
import Database.PostgreSQL.Simple.Types (Query(..))
+import Database.PostgreSQL.Simple.TypeInfo.Types(TypeInfo)
import Control.Monad.Trans.State.Strict
import Control.Monad.Trans.Reader
@@ -56,17 +57,11 @@ data Field = Field {
-- to libpq's @PQftype@.
}
-data NamedOid = NamedOid { typoid :: !PQ.Oid
- , typname :: !ByteString
- } deriving Show
-
-data TypeInfo = TypeInfo { typ :: !NamedOid
- , typelem :: !(Maybe NamedOid)
- } deriving Show
+type TypeInfoCache = IntMap.IntMap TypeInfo
data Connection = Connection {
connectionHandle :: {-# UNPACK #-} !(MVar PQ.Connection)
- , connectionObjects :: {-# UNPACK #-} !(MVar (IntMap.IntMap TypeInfo))
+ , connectionObjects :: {-# UNPACK #-} !(MVar TypeInfoCache)
}
data SqlError = SqlError {
View
63 src/Database/PostgreSQL/Simple/TypeInfo.hs
@@ -12,10 +12,10 @@
module Database.PostgreSQL.Simple.TypeInfo
( getTypeInfo
- , NamedOid(..)
, TypeInfo(..)
) where
+import qualified Data.ByteString.Char8 as B8
import qualified Data.IntMap as IntMap
import Control.Concurrent.MVar
import Control.Exception (throw)
@@ -24,33 +24,40 @@ import qualified Database.PostgreSQL.LibPQ as PQ
import {-# SOURCE #-} Database.PostgreSQL.Simple
import Database.PostgreSQL.Simple.Internal
import Database.PostgreSQL.Simple.Types
-import Database.PostgreSQL.Simple.BuiltinTypes
+import Database.PostgreSQL.Simple.TypeInfo.Types
+import Database.PostgreSQL.Simple.TypeInfo.Static
getTypeInfo :: Connection -> PQ.Oid -> IO TypeInfo
getTypeInfo conn@Connection{..} oid =
- case oid2typname oid of
- Just name -> return $! TypeInfo { typ = NamedOid oid name
- , typelem = Nothing
- }
- Nothing -> modifyMVar connectionObjects $ \oidmap -> do
- case IntMap.lookup (oid2int oid) oidmap of
- Just typeinfo -> return (oidmap, typeinfo)
- Nothing -> do
- names <- query conn "SELECT p.oid, p.typname, c.oid, c.typname\
- \ FROM pg_type AS p LEFT OUTER JOIN pg_type AS c\
- \ ON c.oid = p.typelem\
- \ WHERE p.oid = ?"
- (Only oid)
- typinf <- case names of
- [] -> return $ throw (fatalError "invalid type oid")
- [(pOid, pTypName, mbCOid, mbCTypName)] ->
- return $! TypeInfo { typ = NamedOid pOid pTypName
- , typelem = do
- cOid <- mbCOid
- cTypName <- mbCTypName
- return $ NamedOid cOid cTypName
- }
- _ -> fail "typename query returned more than one result"
- -- oid is a primary key, so the query should
- -- never return more than one result
- return (IntMap.insert (oid2int oid) typinf oidmap, typinf)
+ case staticTypeInfo oid of
+ Just name -> return name
+ Nothing -> modifyMVar connectionObjects $ getTypeInfo' conn oid
+
+getTypeInfo' :: Connection -> PQ.Oid -> TypeInfoCache
+ -> IO (TypeInfoCache, TypeInfo)
+getTypeInfo' conn oid oidmap =
+ case IntMap.lookup (oid2int oid) oidmap of
+ Just typeinfo -> return (oidmap, typeinfo)
+ Nothing -> do
+ names <- query conn "SELECT oid, typcategory, typdelim, typname, typelem\
+ \ FROM pg_type WHERE oid = ?"
+ (Only oid)
+ (oidmap', typeInfo) <-
+ case names of
+ [] -> return $ throw (fatalError "invalid type oid")
+ [(typoid, typcategory_, typdelim_, typname, typelem_)] -> do
+ let !typcategory = B8.index typcategory_ 0
+ !typdelim = B8.index typdelim_ 0
+ if typcategory == 'A'
+ then do
+ (oidmap', typelem) <- getTypeInfo' conn typelem_ oidmap
+ let !typeInfo = Array{..}
+ return (oidmap', typeInfo)
+ else do
+ let !typeInfo = Basic{..}
+ return (oidmap, typeInfo)
+ _ -> fail "typename query returned more than one result"
+ -- oid is a primary key, so the query should
+ -- never return more than one result
+ let !oidmap'' = IntMap.insert (oid2int oid) typeInfo oidmap'
+ return $! (oidmap'', typeInfo)
View
151 src/Database/PostgreSQL/Simple/TypeInfo/Static.hs
@@ -6,6 +6,9 @@
-- Maintainer: Leon P Smith <leon@melding-monads.com>
-- Stability: experimental
--
+-- Note that this module is semi-internal, and you probably want to use
+-- Database.PostgreSQL.Simple.TypeInfo instead.
+--
------------------------------------------------------------------------------
-- Note that this file is generated by tools/GenTypeInfo.hs, and should
@@ -62,16 +65,8 @@ module Database.PostgreSQL.Simple.TypeInfo.Static
, uuid
) where
-import Data.ByteString (ByteString)
import Database.PostgreSQL.LibPQ (Oid(..))
-
-data TypeInfo = TypeInfo
- { typoid :: {-# UNPACK #-} !Oid
- , typcategory :: {-# UNPACK #-} !Char
- , typdelim :: {-# UNPACK #-} !Char
- , typelem :: !(Maybe TypeInfo)
- , typname :: !ByteString
- }
+import Database.PostgreSQL.Simple.TypeInfo.Types
staticTypeInfo :: Oid -> Maybe TypeInfo
staticTypeInfo (Oid x) = case x of
@@ -124,415 +119,369 @@ staticTypeInfo (Oid x) = case x of
_ -> Nothing
bool :: TypeInfo
-bool = TypeInfo {
+bool = Basic {
typoid = Oid 16,
typcategory = 'B',
typdelim = ',',
- typelem = Nothing,
typname = "bool"
}
bytea :: TypeInfo
-bytea = TypeInfo {
+bytea = Basic {
typoid = Oid 17,
typcategory = 'U',
typdelim = ',',
- typelem = Nothing,
typname = "bytea"
}
char :: TypeInfo
-char = TypeInfo {
+char = Basic {
typoid = Oid 18,
typcategory = 'S',
typdelim = ',',
- typelem = Nothing,
typname = "char"
}
name :: TypeInfo
-name = TypeInfo {
+name = Basic {
typoid = Oid 19,
typcategory = 'S',
typdelim = ',',
- typelem = Just char,
typname = "name"
}
int8 :: TypeInfo
-int8 = TypeInfo {
+int8 = Basic {
typoid = Oid 20,
typcategory = 'N',
typdelim = ',',
- typelem = Nothing,
typname = "int8"
}
int2 :: TypeInfo
-int2 = TypeInfo {
+int2 = Basic {
typoid = Oid 21,
typcategory = 'N',
typdelim = ',',
- typelem = Nothing,
typname = "int2"
}
int4 :: TypeInfo
-int4 = TypeInfo {
+int4 = Basic {
typoid = Oid 23,
typcategory = 'N',
typdelim = ',',
- typelem = Nothing,
typname = "int4"
}
regproc :: TypeInfo
-regproc = TypeInfo {
+regproc = Basic {
typoid = Oid 24,
typcategory = 'N',
typdelim = ',',
- typelem = Nothing,
typname = "regproc"
}
text :: TypeInfo
-text = TypeInfo {
+text = Basic {
typoid = Oid 25,
typcategory = 'S',
typdelim = ',',
- typelem = Nothing,
typname = "text"
}
oid :: TypeInfo
-oid = TypeInfo {
+oid = Basic {
typoid = Oid 26,
typcategory = 'N',
typdelim = ',',
- typelem = Nothing,
typname = "oid"
}
tid :: TypeInfo
-tid = TypeInfo {
+tid = Basic {
typoid = Oid 27,
typcategory = 'U',
typdelim = ',',
- typelem = Nothing,
typname = "tid"
}
xid :: TypeInfo
-xid = TypeInfo {
+xid = Basic {
typoid = Oid 28,
typcategory = 'U',
typdelim = ',',
- typelem = Nothing,
typname = "xid"
}
cid :: TypeInfo
-cid = TypeInfo {
+cid = Basic {
typoid = Oid 29,
typcategory = 'U',
typdelim = ',',
- typelem = Nothing,
typname = "cid"
}
xml :: TypeInfo
-xml = TypeInfo {
+xml = Basic {
typoid = Oid 142,
typcategory = 'U',
typdelim = ',',
- typelem = Nothing,
typname = "xml"
}
point :: TypeInfo
-point = TypeInfo {
+point = Basic {
typoid = Oid 600,
typcategory = 'G',
typdelim = ',',
- typelem = Just float8,
typname = "point"
}
lseg :: TypeInfo
-lseg = TypeInfo {
+lseg = Basic {
typoid = Oid 601,
typcategory = 'G',
typdelim = ',',
- typelem = Just point,
typname = "lseg"
}
path :: TypeInfo
-path = TypeInfo {
+path = Basic {
typoid = Oid 602,
typcategory = 'G',
typdelim = ',',
- typelem = Nothing,
typname = "path"
}
box :: TypeInfo
-box = TypeInfo {
+box = Basic {
typoid = Oid 603,
typcategory = 'G',
typdelim = ';',
- typelem = Just point,
typname = "box"
}
polygon :: TypeInfo
-polygon = TypeInfo {
+polygon = Basic {
typoid = Oid 604,
typcategory = 'G',
typdelim = ',',
- typelem = Nothing,
typname = "polygon"
}
line :: TypeInfo
-line = TypeInfo {
+line = Basic {
typoid = Oid 628,
typcategory = 'G',
typdelim = ',',
- typelem = Just float8,
typname = "line"
}
cidr :: TypeInfo
-cidr = TypeInfo {
+cidr = Basic {
typoid = Oid 650,
typcategory = 'I',
typdelim = ',',
- typelem = Nothing,
typname = "cidr"
}
float4 :: TypeInfo
-float4 = TypeInfo {
+float4 = Basic {
typoid = Oid 700,
typcategory = 'N',
typdelim = ',',
- typelem = Nothing,
typname = "float4"
}
float8 :: TypeInfo
-float8 = TypeInfo {
+float8 = Basic {
typoid = Oid 701,
typcategory = 'N',
typdelim = ',',
- typelem = Nothing,
typname = "float8"
}
abstime :: TypeInfo
-abstime = TypeInfo {
+abstime = Basic {
typoid = Oid 702,
typcategory = 'D',
typdelim = ',',
- typelem = Nothing,
typname = "abstime"
}
reltime :: TypeInfo
-reltime = TypeInfo {
+reltime = Basic {
typoid = Oid 703,
typcategory = 'T',
typdelim = ',',
- typelem = Nothing,
typname = "reltime"
}
tinterval :: TypeInfo
-tinterval = TypeInfo {
+tinterval = Basic {
typoid = Oid 704,
typcategory = 'T',
typdelim = ',',
- typelem = Nothing,
typname = "tinterval"
}
unknown :: TypeInfo
-unknown = TypeInfo {
+unknown = Basic {
typoid = Oid 705,
typcategory = 'X',
typdelim = ',',
- typelem = Nothing,
typname = "unknown"
}
circle :: TypeInfo
-circle = TypeInfo {
+circle = Basic {
typoid = Oid 718,
typcategory = 'G',
typdelim = ',',
- typelem = Nothing,
typname = "circle"
}
money :: TypeInfo
-money = TypeInfo {
+money = Basic {
typoid = Oid 790,
typcategory = 'N',
typdelim = ',',
- typelem = Nothing,
typname = "money"
}
macaddr :: TypeInfo
-macaddr = TypeInfo {
+macaddr = Basic {
typoid = Oid 829,
typcategory = 'U',
typdelim = ',',
- typelem = Nothing,
typname = "macaddr"
}
inet :: TypeInfo
-inet = TypeInfo {
+inet = Basic {
typoid = Oid 869,
typcategory = 'I',
typdelim = ',',
- typelem = Nothing,
typname = "inet"
}
bpchar :: TypeInfo
-bpchar = TypeInfo {
+bpchar = Basic {
typoid = Oid 1042,
typcategory = 'S',
typdelim = ',',
- typelem = Nothing,
typname = "bpchar"
}
varchar :: TypeInfo
-varchar = TypeInfo {
+varchar = Basic {
typoid = Oid 1043,
typcategory = 'S',
typdelim = ',',
- typelem = Nothing,
typname = "varchar"
}
date :: TypeInfo
-date = TypeInfo {
+date = Basic {
typoid = Oid 1082,
typcategory = 'D',
typdelim = ',',
- typelem = Nothing,
typname = "date"
}
time :: TypeInfo
-time = TypeInfo {
+time = Basic {
typoid = Oid 1083,
typcategory = 'D',
typdelim = ',',
- typelem = Nothing,
typname = "time"
}
timestamp :: TypeInfo
-timestamp = TypeInfo {
+timestamp = Basic {
typoid = Oid 1114,
typcategory = 'D',
typdelim = ',',
- typelem = Nothing,
typname = "timestamp"
}
timestamptz :: TypeInfo
-timestamptz = TypeInfo {
+timestamptz = Basic {
typoid = Oid 1184,
typcategory = 'D',
typdelim = ',',
- typelem = Nothing,
typname = "timestamptz"
}
interval :: TypeInfo
-interval = TypeInfo {
+interval = Basic {
typoid = Oid 1186,
typcategory = 'T',
typdelim = ',',
- typelem = Nothing,
typname = "interval"
}
timetz :: TypeInfo
-timetz = TypeInfo {
+timetz = Basic {
typoid = Oid 1266,
typcategory = 'D',
typdelim = ',',
- typelem = Nothing,
typname = "timetz"
}
bit :: TypeInfo
-bit = TypeInfo {
+bit = Basic {
typoid = Oid 1560,
typcategory = 'V',
typdelim = ',',
- typelem = Nothing,
typname = "bit"
}
varbit :: TypeInfo
-varbit = TypeInfo {
+varbit = Basic {
typoid = Oid 1562,
typcategory = 'V',
typdelim = ',',
- typelem = Nothing,
typname = "varbit"
}
numeric :: TypeInfo
-numeric = TypeInfo {
+numeric = Basic {
typoid = Oid 1700,
typcategory = 'N',
typdelim = ',',
- typelem = Nothing,
typname = "numeric"
}
refcursor :: TypeInfo
-refcursor = TypeInfo {
+refcursor = Basic {
typoid = Oid 1790,
typcategory = 'U',
typdelim = ',',
- typelem = Nothing,
typname = "refcursor"
}
record :: TypeInfo
-record = TypeInfo {
+record = Basic {
typoid = Oid 2249,
typcategory = 'P',
typdelim = ',',
- typelem = Nothing,
typname = "record"
}
void :: TypeInfo
-void = TypeInfo {
+void = Basic {
typoid = Oid 2278,
typcategory = 'P',
typdelim = ',',
- typelem = Nothing,
typname = "void"
}
uuid :: TypeInfo
-uuid = TypeInfo {
+uuid = Basic {
typoid = Oid 2950,
typcategory = 'U',
typdelim = ',',
- typelem = Nothing,
typname = "uuid"
}
View
30 src/Database/PostgreSQL/Simple/TypeInfo/Types.hs
@@ -0,0 +1,30 @@
+------------------------------------------------------------------------------
+-- |
+-- Module: Database.PostgreSQL.Simple.TypeInfo.Types
+-- Copyright: (c) 2013 Leon P Smith
+-- License: BSD3
+-- Maintainer: Leon P Smith <leon@melding-monads.com>
+-- Stability: experimental
+--
+------------------------------------------------------------------------------
+
+module Database.PostgreSQL.Simple.TypeInfo.Types where
+
+import Data.ByteString(ByteString)
+import Database.PostgreSQL.LibPQ(Oid)
+
+data TypeInfo
+
+ = Basic { typoid :: {-# UNPACK #-} !Oid
+ , typcategory :: {-# UNPACK #-} !Char
+ , typdelim :: {-# UNPACK #-} !Char
+ , typname :: !ByteString
+ }
+
+ | Array { typoid :: {-# UNPACK #-} !Oid
+ , typcategory :: {-# UNPACK #-} !Char
+ , typdelim :: {-# UNPACK #-} !Char
+ , typname :: !ByteString
+ , typelem :: !TypeInfo
+ }
+ deriving (Show)
View
18 tools/GenTypeInfo.hs
@@ -209,6 +209,9 @@ renderFile byOid byName names = ([longstring|
-- Maintainer: Leon P Smith <leon@melding-monads.com>
-- Stability: experimental
--
+-- Note that this module is semi-internal, and you probably want to use
+-- Database.PostgreSQL.Simple.TypeInfo instead.
+--
------------------------------------------------------------------------------
-- Note that this file is generated by tools/GenTypeInfo.hs, and should
@@ -217,20 +220,12 @@ renderFile byOid byName names = ([longstring|
module Database.PostgreSQL.Simple.TypeInfo.Static
( TypeInfo(..)
, staticTypeInfo
-|] ++ concat [ " , " ++ bs (hs name) ++ "\n"
+|] ++ concat [ " , " ++ bs (hs name) ++ "\n"
| name <- names ] ++ [longstring|
) where
-import Data.ByteString (ByteString)
import Database.PostgreSQL.LibPQ (Oid(..))
-
-data TypeInfo = TypeInfo
- { typoid :: {-# UNPACK #-} !Oid
- , typcategory :: {-# UNPACK #-} !Char
- , typdelim :: {-# UNPACK #-} !Char
- , typelem :: !(Maybe TypeInfo)
- , typname :: !ByteString
- }
+import Database.PostgreSQL.Simple.TypeInfo.Types
staticTypeInfo :: Oid -> Maybe TypeInfo
staticTypeInfo (Oid x) = case x of
@@ -244,11 +239,10 @@ staticTypeInfo (Oid x) = case x of
++ concat [concat
[ "\n"
, bs (hs name), " :: TypeInfo\n"
- , bs (hs name), " = TypeInfo {\n"
+ , bs (hs name), " = Basic {\n"
, " typoid = ", fromString (show typoid), ",\n"
, " typcategory = '", Blaze.fromChar typcategory, "',\n"
, " typdelim = '", Blaze.fromChar typdelim, "',\n"
- , " typelem = ", renderElem byOid typelem, ",\n"
, " typname = \"", bs typname, "\"\n"
, " }\n"
]
Please sign in to comment.
Something went wrong with that request. Please try again.