packages feed

cql (empty) → 3.0.1

raw patch · 17 files changed

+3153/−0 lines, 17 filesdep +Decimaldep +QuickCheckdep +basesetup-changed

Dependencies added: Decimal, QuickCheck, base, bytestring, cereal, cql, iproute, network, tasty, tasty-quickcheck, template-haskell, text, time, transformers, uuid

Files

+ LICENSE view
@@ -0,0 +1,409 @@+Mozilla Public License Version 2.0+==================================++1. Definitions+--------------++1.1. "Contributor"+    means each individual or legal entity that creates, contributes to+    the creation of, or owns Covered Software.++1.2. "Contributor Version"+    means the combination of the Contributions of others (if any) used+    by a Contributor and that particular Contributor's Contribution.++1.3. "Contribution"+    means Covered Software of a particular Contributor.++1.4. "Covered Software"+    means Source Code Form to which the initial Contributor has attached+    the notice in Exhibit A, the Executable Form of such Source Code+    Form, and Modifications of such Source Code Form, in each case+    including portions thereof.++1.5. "Incompatible With Secondary Licenses"+    means++    (a) that the initial Contributor has attached the notice described+        in Exhibit B to the Covered Software; or++    (b) that the Covered Software was made available under the terms of+        version 1.1 or earlier of the License, but not also under the+        terms of a Secondary License.++1.6. "Executable Form"+    means any form of the work other than Source Code Form.++1.7. "Larger Work"+    means a work that combines Covered Software with other material, in+    a separate file or files, that is not Covered Software.++1.8. "License"+    means this document.++1.9. "Licensable"+    means having the right to grant, to the maximum extent possible,+    whether at the time of the initial grant or subsequently, any and+    all of the rights conveyed by this License.++1.10. "Modifications"+    means any of the following:++    (a) any file in Source Code Form that results from an addition to,+        deletion from, or modification of the contents of Covered+        Software; or++    (b) any new file in Source Code Form that contains any Covered+        Software.++1.11. "Patent Claims" of a Contributor+    means any patent claim(s), including without limitation, method,+    process, and apparatus claims, in any patent Licensable by such+    Contributor that would be infringed, but for the grant of the+    License, by the making, using, selling, offering for sale, having+    made, import, or transfer of either its Contributions or its+    Contributor Version.++1.12. "Secondary License"+    means either the GNU General Public License, Version 2.0, the GNU+    Lesser General Public License, Version 2.1, the GNU Affero General+    Public License, Version 3.0, or any later versions of those+    licenses.++1.13. "Source Code Form"+    means the form of the work preferred for making modifications.++1.14. "You" (or "Your")+    means an individual or a legal entity exercising rights under this+    License. For legal entities, "You" includes any entity that+    controls, is controlled by, or is under common control with You. For+    purposes of this definition, "control" means (a) the power, direct+    or indirect, to cause the direction or management of such entity,+    whether by contract or otherwise, or (b) ownership of more than+    fifty percent (50%) of the outstanding shares or beneficial+    ownership of such entity.++2. License Grants and Conditions+--------------------------------++2.1. Grants++Each Contributor hereby grants You a world-wide, royalty-free,+non-exclusive license:++(a) under intellectual property rights (other than patent or trademark)+    Licensable by such Contributor to use, reproduce, make available,+    modify, display, perform, distribute, and otherwise exploit its+    Contributions, either on an unmodified basis, with Modifications, or+    as part of a Larger Work; and++(b) under Patent Claims of such Contributor to make, use, sell, offer+    for sale, have made, import, and otherwise transfer either its+    Contributions or its Contributor Version.++2.2. Effective Date++The licenses granted in Section 2.1 with respect to any Contribution+become effective for each Contribution on the date the Contributor first+distributes such Contribution.++2.3. Limitations on Grant Scope++The licenses granted in this Section 2 are the only rights granted under+this License. No additional rights or licenses will be implied from the+distribution or licensing of Covered Software under this License.+Notwithstanding Section 2.1(b) above, no patent license is granted by a+Contributor:++(a) for any code that a Contributor has removed from Covered Software;+    or++(b) for infringements caused by: (i) Your and any other third party's+    modifications of Covered Software, or (ii) the combination of its+    Contributions with other software (except as part of its Contributor+    Version); or++(c) under Patent Claims infringed by Covered Software in the absence of+    its Contributions.++This License does not grant any rights in the trademarks, service marks,+or logos of any Contributor (except as may be necessary to comply with+the notice requirements in Section 3.4).++2.4. Subsequent Licenses++No Contributor makes additional grants as a result of Your choice to+distribute the Covered Software under a subsequent version of this+License (see Section 10.2) or under the terms of a Secondary License (if+permitted under the terms of Section 3.3).++2.5. Representation++Each Contributor represents that the Contributor believes its+Contributions are its original creation(s) or it has sufficient rights+to grant the rights to its Contributions conveyed by this License.++2.6. Fair Use++This License is not intended to limit any rights You have under+applicable copyright doctrines of fair use, fair dealing, or other+equivalents.++2.7. Conditions++Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted+in Section 2.1.++3. Responsibilities+-------------------++3.1. Distribution of Source Form++All distribution of Covered Software in Source Code Form, including any+Modifications that You create or to which You contribute, must be under+the terms of this License. You must inform recipients that the Source+Code Form of the Covered Software is governed by the terms of this+License, and how they can obtain a copy of this License. You may not+attempt to alter or restrict the recipients' rights in the Source Code+Form.++3.2. Distribution of Executable Form++If You distribute Covered Software in Executable Form then:++(a) such Covered Software must also be made available in Source Code+    Form, as described in Section 3.1, and You must inform recipients of+    the Executable Form how they can obtain a copy of such Source Code+    Form by reasonable means in a timely manner, at a charge no more+    than the cost of distribution to the recipient; and++(b) You may distribute such Executable Form under the terms of this+    License, or sublicense it under different terms, provided that the+    license for the Executable Form does not attempt to limit or alter+    the recipients' rights in the Source Code Form under this License.++3.3. Distribution of a Larger Work++You may create and distribute a Larger Work under terms of Your choice,+provided that You also comply with the requirements of this License for+the Covered Software. If the Larger Work is a combination of Covered+Software with a work governed by one or more Secondary Licenses, and the+Covered Software is not Incompatible With Secondary Licenses, this+License permits You to additionally distribute such Covered Software+under the terms of such Secondary License(s), so that the recipient of+the Larger Work may, at their option, further distribute the Covered+Software under the terms of either this License or such Secondary+License(s).++3.4. Notices++You may not remove or alter the substance of any license notices+(including copyright notices, patent notices, disclaimers of warranty,+or limitations of liability) contained within the Source Code Form of+the Covered Software, except that You may alter any license notices to+the extent required to remedy known factual inaccuracies.++3.5. Application of Additional Terms++You may choose to offer, and to charge a fee for, warranty, support,+indemnity or liability obligations to one or more recipients of Covered+Software. However, You may do so only on Your own behalf, and not on+behalf of any Contributor. You must make it absolutely clear that any+such warranty, support, indemnity, or liability obligation is offered by+You alone, and You hereby agree to indemnify every Contributor for any+liability incurred by such Contributor as a result of warranty, support,+indemnity or liability terms You offer. You may include additional+disclaimers of warranty and limitations of liability specific to any+jurisdiction.++4. Inability to Comply Due to Statute or Regulation+---------------------------------------------------++If it is impossible for You to comply with any of the terms of this+License with respect to some or all of the Covered Software due to+statute, judicial order, or regulation then You must: (a) comply with+the terms of this License to the maximum extent possible; and (b)+describe the limitations and the code they affect. Such description must+be placed in a text file included with all distributions of the Covered+Software under this License. Except to the extent prohibited by statute+or regulation, such description must be sufficiently detailed for a+recipient of ordinary skill to be able to understand it.++5. Termination+--------------++5.1. The rights granted under this License will terminate automatically+if You fail to comply with any of its terms. However, if You become+compliant, then the rights granted under this License from a particular+Contributor are reinstated (a) provisionally, unless and until such+Contributor explicitly and finally terminates Your grants, and (b) on an+ongoing basis, if such Contributor fails to notify You of the+non-compliance by some reasonable means prior to 60 days after You have+come back into compliance. Moreover, Your grants from a particular+Contributor are reinstated on an ongoing basis if such Contributor+notifies You of the non-compliance by some reasonable means, this is the+first time You have received notice of non-compliance with this License+from such Contributor, and You become compliant prior to 30 days after+Your receipt of the notice.++5.2. If You initiate litigation against any entity by asserting a patent+infringement claim (excluding declaratory judgment actions,+counter-claims, and cross-claims) alleging that a Contributor Version+directly or indirectly infringes any patent, then the rights granted to+You by any and all Contributors for the Covered Software under Section+2.1 of this License shall terminate.++5.3. In the event of termination under Sections 5.1 or 5.2 above, all+end user license agreements (excluding distributors and resellers) which+have been validly granted by You or Your distributors under this License+prior to termination shall survive termination.++************************************************************************+*                                                                      *+*  6. Disclaimer of Warranty                                           *+*  -------------------------                                           *+*                                                                      *+*  Covered Software is provided under this License on an "as is"       *+*  basis, without warranty of any kind, either expressed, implied, or  *+*  statutory, including, without limitation, warranties that the       *+*  Covered Software is free of defects, merchantable, fit for a        *+*  particular purpose or non-infringing. The entire risk as to the     *+*  quality and performance of the Covered Software is with You.        *+*  Should any Covered Software prove defective in any respect, You     *+*  (not any Contributor) assume the cost of any necessary servicing,   *+*  repair, or correction. This disclaimer of warranty constitutes an   *+*  essential part of this License. No use of any Covered Software is   *+*  authorized under this License except under this disclaimer.         *+*                                                                      *+************************************************************************++************************************************************************+*                                                                      *+*  7. Limitation of Liability                                          *+*  --------------------------                                          *+*                                                                      *+*  Under no circumstances and under no legal theory, whether tort      *+*  (including negligence), contract, or otherwise, shall any           *+*  Contributor, or anyone who distributes Covered Software as          *+*  permitted above, be liable to You for any direct, indirect,         *+*  special, incidental, or consequential damages of any character      *+*  including, without limitation, damages for lost profits, loss of    *+*  goodwill, work stoppage, computer failure or malfunction, or any    *+*  and all other commercial damages or losses, even if such party      *+*  shall have been informed of the possibility of such damages. This   *+*  limitation of liability shall not apply to liability for death or   *+*  personal injury resulting from such party's negligence to the       *+*  extent applicable law prohibits such limitation. Some               *+*  jurisdictions do not allow the exclusion or limitation of           *+*  incidental or consequential damages, so this exclusion and          *+*  limitation may not apply to You.                                    *+*                                                                      *+************************************************************************++8. Litigation+-------------++Any litigation relating to this License may be brought only in the+courts of a jurisdiction where the defendant maintains its principal+place of business and such litigation shall be governed by laws of that+jurisdiction, without reference to its conflict-of-law provisions.+Nothing in this Section shall prevent a party's ability to bring+cross-claims or counter-claims.++9. Miscellaneous+----------------++This License represents the complete agreement concerning the subject+matter hereof. If any provision of this License is held to be+unenforceable, such provision shall be reformed only to the extent+necessary to make it enforceable. Any law or regulation which provides+that the language of a contract shall be construed against the drafter+shall not be used to construe this License against a Contributor.++10. Versions of the License+---------------------------++10.1. New Versions++Mozilla Foundation is the license steward. Except as provided in Section+10.3, no one other than the license steward has the right to modify or+publish new versions of this License. Each version will be given a+distinguishing version number.++10.2. Effect of New Versions++You may distribute the Covered Software under the terms of the version+of the License under which You originally received the Covered Software,+or under the terms of any subsequent version published by the license+steward.++10.3. Modified Versions++If you create software not governed by this License, and you want to+create a new license for such software, you may create and use a+modified version of this License if you rename the license and remove+any references to the name of the license steward (except to note that+such modified license differs from this License).++10.4. Distributing Source Code Form that is Incompatible With Secondary+Licenses++If You choose to distribute Source Code Form that is Incompatible With+Secondary Licenses under the terms of this version of the License, the+notice described in Exhibit B of this License must be attached.++Exhibit A - Source Code Form License Notice+-------------------------------------------++  This Source Code Form is subject to the terms of the Mozilla Public+  License, v. 2.0. If a copy of the MPL was not distributed with this+  file, You can obtain one at http://mozilla.org/MPL/2.0/.++If it is not possible or desirable to put the notice in a particular+file, then You may include the notice in a location (such as a LICENSE+file in a relevant directory) where a recipient would be likely to look+for such a notice.++You may add additional accurate notices of copyright ownership.++Exhibit B - "Incompatible With Secondary Licenses" Notice+---------------------------------------------------------++  This Source Code Form is "Incompatible With Secondary Licenses", as+  defined by the Mozilla Public License, v. 2.0.++=============================================================================++Cereal License++Copyright (c) Lennart Kolmodin, Galois, 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:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. 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.++3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE 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 AUTHORS 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,9 @@+CQL Binary Protocol Implementation+==================================++This Haskell library implements Cassandra's CQL Binary Protocol [Version 2][1]+and [Version 3][2]. It provides encoding and decoding functionality as well as+representations of the various protocol related types.++[1]: https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v2.spec+[2]: https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v3.spec
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cql.cabal view
@@ -0,0 +1,89 @@+name:                 cql+version:              3.0.1+synopsis:             Cassandra CQL binary protocol.+stability:            experimental+license:              OtherLicense+license-file:         LICENSE+author:               Toralf Wittner, Roman S. Borschel+maintainer:           Toralf Wittner <tw@dtex.org>+copyright:            2014 Toralf Wittner, Roman S. Borschel+homepage:             https://github.com/twittner/cql/+bug-reports:          https://github.com/twittner/cql/issues+category:             Database+build-type:           Simple+cabal-version:        >= 1.10+extra-source-files:   README.md++description:+    Implementation of Cassandra's CQL Binary Protocol+    <https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v2.spec Version 2>+    and+    <https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v3.spec Version 3>.+    .+    It provides encoding and decoding functionality as well as representations+    of the various protocol related types.+    .+    Thus it can serve as a building block for writing Cassandra drivers, such+    as <http://hackage.haskell.org/package/cql-io cql-io>.++source-repository head+    type:             git+    location:         git@github.com:twittner/cql.git++library+    default-language: Haskell2010+    hs-source-dirs:   src+    ghc-options:      -Wall -O2 -fwarn-tabs -funbox-strict-fields+    ghc-prof-options: -prof -auto-all++    exposed-modules:+        Database.CQL.Protocol+        Database.CQL.Protocol.Internal++    other-modules:+        Database.CQL.Protocol.Class+        Database.CQL.Protocol.Codec+        Database.CQL.Protocol.Record+        Database.CQL.Protocol.Tuple+        Database.CQL.Protocol.Tuple.TH+        Database.CQL.Protocol.Types+        Database.CQL.Protocol.Header+        Database.CQL.Protocol.Request+        Database.CQL.Protocol.Response++    build-depends:+          base             >= 4.5    && < 5.0+        , bytestring       >= 0.10   && < 1.0+        , cereal           >= 0.3    && < 0.5+        , Decimal          >= 0.3    && < 1.0+        , iproute          >= 1.3    && < 1.4+        , network          >= 2.4    && < 3.0+        , text             >= 0.11   && < 2.0+        , template-haskell+        , time             >= 1.4    && < 2.0+        , transformers     >= 0.3    && < 0.5+        , uuid             >= 1.2.6  && < 2.0++test-suite cql-tests+    type:             exitcode-stdio-1.0+    default-language: Haskell2010+    main-is:          Main.hs+    hs-source-dirs:   test+    ghc-options:      -threaded -Wall -O2 -fwarn-tabs+    other-modules:    Tests++    build-depends:+          base+        , bytestring+        , cereal+        , cql+        , Decimal+        , iproute+        , network+        , QuickCheck+        , tasty            >= 0.8+        , tasty-quickcheck >= 0.8+        , text+        , time+        , uuid+
+ src/Database/CQL/Protocol.hs view
@@ -0,0 +1,152 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++-- | The CQL native protocol is a binary frame-based protocol where+-- each frame has a 'Header', a 'Length' and a body. The protocol+-- distinguishes 'Request's and 'Response's.+--+-- Some usage examples:+--+-- __Constructing and Serialising a Request__+--+-- @+-- let q = QueryString "select peer from system.peers where data_center = ? and rack = ?"+--     p = QueryParams One False ("uk-1", "r-0") Nothing Nothing Nothing+--     r = RqQuery (Query q p :: Query R (Text, Text) (Identity IP))+--     i = mkStreamId 0+-- in pack V3 noCompression False i r+-- @+--+-- __Deserialising a Response__+--+-- @+-- -- assuming 'bh' contains the raw header byte string and 'bb' the raw+-- -- body byte string.+-- case header V3 bh of+--     Left  e -> ...+--     Right h -> unpack noCompression h bb+-- @+--+-- __A generic query processing function__+--+-- @+-- query :: (Tuple a, Tuple b) => Version -> Socket -> QueryString k a b -> QueryParams a -> IO (Response k a b)+-- query v s q p = do+--     let i = mkStreamId 0+--     sendToServer s (pack v noCompression False i (RqQuery (Query q p)))+--     b <- recv (if v == V3 then 9 else 8) s+--     h <- either (throwIO . MyException) return (header v b)+--     when (headerType h == RqHeader) $+--         throwIO UnexpectedRequestHeader+--     let len = lengthRepr (bodyLength h)+--     x <- recv (fromIntegral len) s+--     case unpack noCompression h x of+--         Left e              -> throwIO $ AnotherException e+--         Right (RsError _ e) -> throwIO e+--         Right response      -> return response+-- @+--+module Database.CQL.Protocol+    ( -- * Cql type-class+      Cql (..)++      -- * Basic type definitions+    , module Database.CQL.Protocol.Types++      -- * Header+    , Header     (..)+    , HeaderType (..)+    , header++      -- ** Length+    , Length (..)++      -- ** StreamId+    , StreamId+    , mkStreamId+    , fromStreamId++      -- ** Flags+    , Flags+    , compress+    , tracing+    , isSet++      -- * Request+    , Request (..)+    , getOpCode+    , pack++      -- ** Options+    , Options (..)++      -- ** Startup+    , Startup (..)++      -- ** Auth Response+    , AuthResponse (..)++      -- ** Register+    , Register  (..)+    , EventType (..)++      -- ** Query+    , Query             (..)+    , QueryParams       (..)+    , SerialConsistency (..)++      -- ** Batch+    , Batch      (..)+    , BatchQuery (..)+    , BatchType  (..)++      -- ** Prepare+    , Prepare (..)++      -- ** Execute+    , Execute (..)++      -- * Response+    , Response (..)+    , unpack++      -- ** Ready+    , Ready (..)++      -- ** Authenticate+    , Authenticate  (..)+    , AuthChallenge (..)+    , AuthSuccess   (..)++      -- ** Result+    , Result     (..)+    , MetaData   (..)+    , ColumnSpec (..)++      -- ** Supported+    , Supported  (..)++      -- ** Event+    , Event          (..)+    , TopologyChange (..)+    , SchemaChange   (..)+    , StatusChange   (..)+    , Change         (..)++      -- ** Error+    , Error     (..)+    , WriteType (..)++      -- * Tuple and Record+    , module Database.CQL.Protocol.Tuple+    , module Database.CQL.Protocol.Record+    ) where++import Database.CQL.Protocol.Class+import Database.CQL.Protocol.Header+import Database.CQL.Protocol.Record+import Database.CQL.Protocol.Request+import Database.CQL.Protocol.Response+import Database.CQL.Protocol.Tuple+import Database.CQL.Protocol.Types
+ src/Database/CQL/Protocol/Class.hs view
@@ -0,0 +1,227 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++{-# LANGUAGE FlexibleInstances   #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Database.CQL.Protocol.Class (Cql (..)) where++import Control.Applicative+import Control.Arrow+import Data.Decimal+import Data.Int+import Data.IP+import Data.Text (Text)+import Data.Time+import Data.Time.Clock.POSIX+import Data.UUID (UUID)+import Database.CQL.Protocol.Types++-- | A type that can be converted from and to some CQL 'Value'.+--+-- This type-class is used to map custom types to Cassandra data-types.+-- For example:+--+-- @+-- newtype MyType = MyType Int32+--+-- instance Cql MyType where+--     ctype              = Tagged IntColumn+--     toCql (MyType i)   = CqlInt i+--     fromCql (CqlInt i) = Right (MyType i)+--     fromCql _          = Left "Expected CqlInt"+-- @+class Cql a where+    -- | the column-type of @a@+    ctype :: Tagged a ColumnType+    -- | map @a@ to some CQL data-type+    toCql :: a -> Value+    -- | map a CQL value back to @a@+    fromCql :: Value -> Either String a++------------------------------------------------------------------------------+-- Bool++instance Cql Bool where+    ctype = Tagged BooleanColumn+    toCql = CqlBoolean+    fromCql (CqlBoolean b) = Right b+    fromCql _              = Left "Expected CqlBoolean."++------------------------------------------------------------------------------+-- Int32++instance Cql Int32 where+    ctype = Tagged IntColumn+    toCql = CqlInt+    fromCql (CqlInt i) = Right i+    fromCql _          = Left "Expected CqlInt."++------------------------------------------------------------------------------+-- Int64++instance Cql Int64 where+    ctype = Tagged BigIntColumn+    toCql = CqlBigInt+    fromCql (CqlBigInt i) = Right i+    fromCql _             = Left "Expected CqlBigInt."++------------------------------------------------------------------------------+-- Integer++instance Cql Integer where+    ctype = Tagged VarIntColumn+    toCql = CqlVarInt+    fromCql (CqlVarInt i) = Right i+    fromCql _             = Left "Expected CqlVarInt."++------------------------------------------------------------------------------+-- Float++instance Cql Float where+    ctype = Tagged FloatColumn+    toCql = CqlFloat+    fromCql (CqlFloat f) = Right f+    fromCql _            = Left "Expected CqlFloat."++------------------------------------------------------------------------------+-- Double++instance Cql Double where+    ctype = Tagged DoubleColumn+    toCql = CqlDouble+    fromCql (CqlDouble d) = Right d+    fromCql _             = Left "Expected CqlDouble."++------------------------------------------------------------------------------+-- Decimal++instance Cql Decimal where+    ctype = Tagged DecimalColumn+    toCql = CqlDecimal+    fromCql (CqlDecimal d) = Right d+    fromCql _              = Left "Expected CqlDecimal."++------------------------------------------------------------------------------+-- Text++instance Cql Text where+    ctype = Tagged TextColumn+    toCql = CqlText+    fromCql (CqlText s) = Right s+    fromCql _           = Left "Expected CqlText."++------------------------------------------------------------------------------+-- Ascii++instance Cql Ascii where+    ctype = Tagged AsciiColumn+    toCql (Ascii a) = CqlAscii a+    fromCql (CqlAscii a) = Right $ Ascii a+    fromCql _            = Left "Expected CqlAscii."++------------------------------------------------------------------------------+-- IP Address++instance Cql IP where+    ctype = Tagged InetColumn+    toCql = CqlInet+    fromCql (CqlInet i) = Right i+    fromCql _           = Left "Expected CqlInet."++------------------------------------------------------------------------------+-- UUID++instance Cql UUID where+    ctype = Tagged UuidColumn+    toCql = CqlUuid+    fromCql (CqlUuid u) = Right u+    fromCql _           = Left "Expected CqlUuid."++------------------------------------------------------------------------------+-- UTCTime++instance Cql UTCTime where+    ctype = Tagged TimestampColumn++    toCql = CqlTimestamp+          . truncate+          . (* 1000)+          . utcTimeToPOSIXSeconds++    fromCql (CqlTimestamp t) =+        let (s, ms)     = t `divMod` 1000+            UTCTime a b = posixSecondsToUTCTime (fromIntegral s)+            ps          = fromIntegral ms * 1000000000+        in Right $ UTCTime a (b + picosecondsToDiffTime ps)+    fromCql _                = Left "Expected CqlTimestamp."++------------------------------------------------------------------------------+-- Blob++instance Cql Blob where+    ctype = Tagged BlobColumn+    toCql (Blob b) = CqlBlob b+    fromCql (CqlBlob b) = Right $ Blob b+    fromCql _           = Left "Expected CqlBlob."++------------------------------------------------------------------------------+-- Counter++instance Cql Counter where+    ctype = Tagged CounterColumn+    toCql (Counter c) = CqlCounter c+    fromCql (CqlCounter c) = Right $ Counter c+    fromCql _              = Left "Expected CqlCounter."++------------------------------------------------------------------------------+-- TimeUuid++instance Cql TimeUuid where+    ctype = Tagged TimeUuidColumn+    toCql (TimeUuid u) = CqlTimeUuid u+    fromCql (CqlTimeUuid t) = Right $ TimeUuid t+    fromCql _               = Left "Expected TimeUuid."++------------------------------------------------------------------------------+-- [a]++instance Cql a => Cql [a] where+    ctype = Tagged (ListColumn (untag (ctype :: Tagged a ColumnType)))+    toCql = CqlList . map toCql+    fromCql (CqlList l) = mapM fromCql l+    fromCql _           = Left "Expected CqlList."++------------------------------------------------------------------------------+-- Maybe a++-- | Please note that due to the fact that Cassandra internally represents+-- empty collection type values (i.e. lists, maps and sets) as @null@, we+-- can not distinguish @Just []@ from @Nothing@ on response decoding.+instance Cql a => Cql (Maybe a) where+    ctype = Tagged (MaybeColumn (untag (ctype :: Tagged a ColumnType)))+    toCql = CqlMaybe . fmap toCql+    fromCql (CqlMaybe (Just m)) = Just <$> fromCql m+    fromCql (CqlMaybe Nothing)  = Right Nothing+    fromCql _                   = Left "Expected CqlMaybe."++------------------------------------------------------------------------------+-- Map a b++instance (Cql a, Cql b) => Cql (Map a b) where+    ctype = Tagged $ MapColumn+        (untag (ctype :: Tagged a ColumnType))+        (untag (ctype :: Tagged b ColumnType))+    toCql (Map m)      = CqlMap $ map (toCql *** toCql) m+    fromCql (CqlMap m) = Map <$> mapM (\(k, v) -> (,) <$> fromCql k <*> fromCql v) m+    fromCql _          = Left "Expected CqlMap."++------------------------------------------------------------------------------+-- Set a++instance Cql a => Cql (Set a) where+    ctype = Tagged (SetColumn (untag (ctype :: Tagged a ColumnType)))+    toCql (Set a) = CqlSet $ map toCql a+    fromCql (CqlSet a) = Set <$> mapM fromCql a+    fromCql _          = Left "Expected CqlSet."
+ src/Database/CQL/Protocol/Codec.hs view
@@ -0,0 +1,625 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++{-# LANGUAGE BangPatterns      #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections     #-}++module Database.CQL.Protocol.Codec+    ( encodeByte+    , decodeByte++    , encodeSignedByte+    , decodeSignedByte++    , encodeShort+    , decodeShort++    , encodeSignedShort+    , decodeSignedShort++    , encodeInt+    , decodeInt++    , encodeString+    , decodeString++    , encodeLongString+    , decodeLongString++    , encodeBytes+    , decodeBytes++    , encodeShortBytes+    , decodeShortBytes++    , encodeUUID+    , decodeUUID++    , encodeList+    , decodeList++    , encodeMap+    , decodeMap++    , encodeMultiMap+    , decodeMultiMap++    , encodeSockAddr+    , decodeSockAddr++    , encodeConsistency+    , decodeConsistency++    , encodeOpCode+    , decodeOpCode++    , encodeColumnType+    , decodeColumnType++    , encodePagingState+    , decodePagingState++    , decodeKeyspace+    , decodeTable+    , decodeQueryId++    , putValue+    , getValue+    ) where++import Control.Applicative+import Control.Monad+import Data.Bits+import Data.ByteString (ByteString)+import Data.Decimal+import Data.Int+import Data.IP+import Data.List (unfoldr)+import Data.Text (Text)+import Data.UUID (UUID)+import Data.Word+import Data.Serialize hiding (decode, encode)+import Database.CQL.Protocol.Types+import Network.Socket (SockAddr (..), PortNumber (..))++import qualified Data.ByteString         as B+import qualified Data.ByteString.Lazy    as LB+import qualified Data.Text.Encoding      as T+import qualified Data.Text.Lazy          as LT+import qualified Data.Text.Lazy.Encoding as LT+import qualified Data.UUID               as UUID++------------------------------------------------------------------------------+-- Byte++encodeByte :: Putter Word8+encodeByte = put++decodeByte :: Get Word8+decodeByte = get++------------------------------------------------------------------------------+-- Signed Byte++encodeSignedByte :: Putter Int8+encodeSignedByte = put++decodeSignedByte :: Get Int8+decodeSignedByte = get++------------------------------------------------------------------------------+-- Short++encodeShort :: Putter Word16+encodeShort = put++decodeShort :: Get Word16+decodeShort = get++------------------------------------------------------------------------------+-- Signed Short++encodeSignedShort :: Putter Int16+encodeSignedShort = put++decodeSignedShort :: Get Int16+decodeSignedShort = get++------------------------------------------------------------------------------+-- Int++encodeInt :: Putter Int32+encodeInt = put++decodeInt :: Get Int32+decodeInt = get++------------------------------------------------------------------------------+-- String++encodeString :: Putter Text+encodeString = encodeShortBytes . T.encodeUtf8++decodeString :: Get Text+decodeString = T.decodeUtf8 <$> decodeShortBytes++------------------------------------------------------------------------------+-- Long String++encodeLongString :: Putter LT.Text+encodeLongString = encodeBytes . LT.encodeUtf8++decodeLongString :: Get LT.Text+decodeLongString = do+    n <- get :: Get Int32+    LT.decodeUtf8 <$> getLazyByteString (fromIntegral n)++------------------------------------------------------------------------------+-- Bytes++encodeBytes :: Putter LB.ByteString+encodeBytes bs = do+    put (fromIntegral (LB.length bs) :: Int32)+    putLazyByteString bs++decodeBytes :: Get (Maybe LB.ByteString)+decodeBytes = do+    n <- get :: Get Int32+    if n < 0+        then return Nothing+        else Just <$> getLazyByteString (fromIntegral n)++------------------------------------------------------------------------------+-- Short Bytes++encodeShortBytes :: Putter ByteString+encodeShortBytes bs = do+    put (fromIntegral (B.length bs) :: Word16)+    putByteString bs++decodeShortBytes :: Get ByteString+decodeShortBytes = do+    n <- get :: Get Word16+    getByteString (fromIntegral n)++------------------------------------------------------------------------------+-- UUID++encodeUUID :: Putter UUID+encodeUUID = putLazyByteString . UUID.toByteString++decodeUUID :: Get UUID+decodeUUID = do+    uuid <- UUID.fromByteString <$> getLazyByteString 16+    maybe (fail "decode-uuid: invalid") return uuid++------------------------------------------------------------------------------+-- String List++encodeList :: Putter [Text]+encodeList sl = do+    put (fromIntegral (length sl) :: Word16)+    mapM_ encodeString sl++decodeList :: Get [Text]+decodeList = do+    n <- get :: Get Word16+    replicateM (fromIntegral n) decodeString++------------------------------------------------------------------------------+-- String Map++encodeMap :: Putter [(Text, Text)]+encodeMap m = do+    put (fromIntegral (length m) :: Word16)+    forM_ m $ \(k, v) -> encodeString k >> encodeString v++decodeMap :: Get [(Text, Text)]+decodeMap = do+    n <- get :: Get Word16+    replicateM (fromIntegral n) ((,) <$> decodeString <*> decodeString)++------------------------------------------------------------------------------+-- String Multi-Map++encodeMultiMap :: Putter [(Text, [Text])]+encodeMultiMap mm = do+    put (fromIntegral (length mm) :: Word16)+    forM_ mm $ \(k, v) -> encodeString k >> encodeList v++decodeMultiMap :: Get [(Text, [Text])]+decodeMultiMap = do+    n <- get :: Get Word16+    replicateM (fromIntegral n) ((,) <$> decodeString <*> decodeList)++------------------------------------------------------------------------------+-- Inet Address++encodeSockAddr :: Putter SockAddr+encodeSockAddr (SockAddrInet p a) = do+    putWord8 4+    putWord32le a+    putWord32be (fromIntegral p)+encodeSockAddr (SockAddrInet6 p _ (a, b, c, d) _) = do+    putWord8 16+    putWord32host a+    putWord32host b+    putWord32host c+    putWord32host d+    putWord32be (fromIntegral p)+encodeSockAddr (SockAddrUnix _) = fail "encode-socket: unix address not allowed"++decodeSockAddr :: Get SockAddr+decodeSockAddr = do+    n <- getWord8+    case n of+        4  -> do+            i <- getIPv4+            p <- getPort+            return $ SockAddrInet p i+        16 -> do+            i <- getIPv6+            p <- getPort+            return $ SockAddrInet6 p 0 i 0+        _  -> fail $ "decode-socket: unknown: " ++ show n+  where+    getPort :: Get PortNumber+    getPort = fromIntegral <$> getWord32be++    getIPv4 :: Get Word32+    getIPv4 = getWord32le++    getIPv6 :: Get (Word32, Word32, Word32, Word32)+    getIPv6 = (,,,) <$> getWord32host <*> getWord32host <*> getWord32host <*> getWord32host++------------------------------------------------------------------------------+-- Consistency++encodeConsistency :: Putter Consistency+encodeConsistency Any         = encodeShort 0x00+encodeConsistency One         = encodeShort 0x01+encodeConsistency Two         = encodeShort 0x02+encodeConsistency Three       = encodeShort 0x03+encodeConsistency Quorum      = encodeShort 0x04+encodeConsistency All         = encodeShort 0x05+encodeConsistency LocalQuorum = encodeShort 0x06+encodeConsistency EachQuorum  = encodeShort 0x07+encodeConsistency Serial      = encodeShort 0x08+encodeConsistency LocalSerial = encodeShort 0x09+encodeConsistency LocalOne    = encodeShort 0x0A++decodeConsistency :: Get Consistency+decodeConsistency = decodeShort >>= mapCode+      where+        mapCode 0x00 = return Any+        mapCode 0x01 = return One+        mapCode 0x02 = return Two+        mapCode 0x03 = return Three+        mapCode 0x04 = return Quorum+        mapCode 0x05 = return All+        mapCode 0x06 = return LocalQuorum+        mapCode 0x07 = return EachQuorum+        mapCode 0x08 = return Serial+        mapCode 0x09 = return LocalSerial+        mapCode 0x10 = return LocalOne+        mapCode code = fail $ "decode-consistency: unknown: " ++ show code++------------------------------------------------------------------------------+-- OpCode++encodeOpCode :: Putter OpCode+encodeOpCode OcError         = encodeByte 0x00+encodeOpCode OcStartup       = encodeByte 0x01+encodeOpCode OcReady         = encodeByte 0x02+encodeOpCode OcAuthenticate  = encodeByte 0x03+encodeOpCode OcOptions       = encodeByte 0x05+encodeOpCode OcSupported     = encodeByte 0x06+encodeOpCode OcQuery         = encodeByte 0x07+encodeOpCode OcResult        = encodeByte 0x08+encodeOpCode OcPrepare       = encodeByte 0x09+encodeOpCode OcExecute       = encodeByte 0x0A+encodeOpCode OcRegister      = encodeByte 0x0B+encodeOpCode OcEvent         = encodeByte 0x0C+encodeOpCode OcBatch         = encodeByte 0x0D+encodeOpCode OcAuthChallenge = encodeByte 0x0E+encodeOpCode OcAuthResponse  = encodeByte 0x0F+encodeOpCode OcAuthSuccess   = encodeByte 0x10++decodeOpCode :: Get OpCode+decodeOpCode = decodeByte >>= mapCode+  where+    mapCode 0x00 = return OcError+    mapCode 0x01 = return OcStartup+    mapCode 0x02 = return OcReady+    mapCode 0x03 = return OcAuthenticate+    mapCode 0x05 = return OcOptions+    mapCode 0x06 = return OcSupported+    mapCode 0x07 = return OcQuery+    mapCode 0x08 = return OcResult+    mapCode 0x09 = return OcPrepare+    mapCode 0x0A = return OcExecute+    mapCode 0x0B = return OcRegister+    mapCode 0x0C = return OcEvent+    mapCode 0x0D = return OcBatch+    mapCode 0x0E = return OcAuthChallenge+    mapCode 0x0F = return OcAuthResponse+    mapCode 0x10 = return OcAuthSuccess+    mapCode word = fail $ "decode-opcode: unknown: " ++ show word++------------------------------------------------------------------------------+-- ColumnType++encodeColumnType :: Putter ColumnType+encodeColumnType (CustomColumn x)   = encodeShort 0x0000 >> encodeString x+encodeColumnType AsciiColumn        = encodeShort 0x0001+encodeColumnType BigIntColumn       = encodeShort 0x0002+encodeColumnType BlobColumn         = encodeShort 0x0003+encodeColumnType BooleanColumn      = encodeShort 0x0004+encodeColumnType CounterColumn      = encodeShort 0x0005+encodeColumnType DecimalColumn      = encodeShort 0x0006+encodeColumnType DoubleColumn       = encodeShort 0x0007+encodeColumnType FloatColumn        = encodeShort 0x0008+encodeColumnType IntColumn          = encodeShort 0x0009+encodeColumnType TextColumn         = encodeShort 0x000A+encodeColumnType TimestampColumn    = encodeShort 0x000B+encodeColumnType UuidColumn         = encodeShort 0x000C+encodeColumnType VarCharColumn      = encodeShort 0x000D+encodeColumnType VarIntColumn       = encodeShort 0x000E+encodeColumnType TimeUuidColumn     = encodeShort 0x000F+encodeColumnType InetColumn         = encodeShort 0x0010+encodeColumnType (MaybeColumn x)    = encodeColumnType x+encodeColumnType (ListColumn x)     = encodeShort 0x0020 >> encodeColumnType x+encodeColumnType (MapColumn  x y)   = encodeShort 0x0021 >> encodeColumnType x >> encodeColumnType y+encodeColumnType (SetColumn  x)     = encodeShort 0x0022 >> encodeColumnType x+encodeColumnType (TupleColumn xs)   = encodeShort 0x0031 >> mapM_ encodeColumnType xs+encodeColumnType (UdtColumn k n xs) = do+    encodeShort 0x0030+    encodeString (unKeyspace k)+    encodeString n+    encodeShort (fromIntegral (length xs))+    forM_ xs $ \(x, t) -> encodeString x >> encodeColumnType t++decodeColumnType :: Get ColumnType+decodeColumnType = decodeShort >>= toType+  where+    toType 0x0000 = CustomColumn <$> decodeString+    toType 0x0001 = return AsciiColumn+    toType 0x0002 = return BigIntColumn+    toType 0x0003 = return BlobColumn+    toType 0x0004 = return BooleanColumn+    toType 0x0005 = return CounterColumn+    toType 0x0006 = return DecimalColumn+    toType 0x0007 = return DoubleColumn+    toType 0x0008 = return FloatColumn+    toType 0x0009 = return IntColumn+    toType 0x000A = return TextColumn+    toType 0x000B = return TimestampColumn+    toType 0x000C = return UuidColumn+    toType 0x000D = return VarCharColumn+    toType 0x000E = return VarIntColumn+    toType 0x000F = return TimeUuidColumn+    toType 0x0010 = return InetColumn+    toType 0x0020 = ListColumn  <$> (decodeShort >>= toType)+    toType 0x0021 = MapColumn   <$> (decodeShort >>= toType) <*> (decodeShort >>= toType)+    toType 0x0022 = SetColumn   <$> (decodeShort >>= toType)+    toType 0x0030 = UdtColumn   <$> (Keyspace <$> decodeString) <*> decodeString <*> do+        n <- fromIntegral <$> decodeShort+        replicateM n ((,) <$> decodeString <*> (decodeShort >>= toType))+    toType 0x0031 = TupleColumn <$> do+        n <- fromIntegral <$> decodeShort+        replicateM n (decodeShort >>= toType)+    toType other  = fail $ "decode-type: unknown: " ++ show other++------------------------------------------------------------------------------+-- Paging State++encodePagingState :: Putter PagingState+encodePagingState (PagingState s) = encodeBytes s++decodePagingState :: Get (Maybe PagingState)+decodePagingState = liftM PagingState <$> decodeBytes++------------------------------------------------------------------------------+-- Value++putValue :: Version -> Putter Value+putValue V3 (CqlList x)        = toBytes 4 $ do+    encodeInt (fromIntegral (length x))+    mapM_ (toBytes 4 . putNative) x+putValue V2 (CqlList x)        = toBytes 4 $ do+    encodeShort (fromIntegral (length x))+    mapM_ (toBytes 2 . putNative) x+putValue V3 (CqlSet x)         = toBytes 4 $ do+    encodeInt (fromIntegral (length x))+    mapM_ (toBytes 4 . putNative) x+putValue V2 (CqlSet x)         = toBytes 4 $ do+    encodeShort (fromIntegral (length x))+    mapM_ (toBytes 2 . putNative) x+putValue V3 (CqlMap x)         = toBytes 4 $ do+    encodeInt (fromIntegral (length x))+    forM_ x $ \(k, v) -> toBytes 4 (putNative k) >> toBytes 4 (putNative v)+putValue V2 (CqlMap x)         = toBytes 4 $ do+    encodeShort (fromIntegral (length x))+    forM_ x $ \(k, v) -> toBytes 2 (putNative k) >> toBytes 2 (putNative v)+putValue V3 (CqlTuple x)       = mapM_ (toBytes 4 . putValue V3) x+putValue V3 (CqlUdt x)         = mapM_ (toBytes 4 . putValue V3 . snd) x+putValue _ (CqlMaybe Nothing)  = put (-1 :: Int32)+putValue v (CqlMaybe (Just x)) = putValue v x+putValue _ value               = toBytes 4 $ putNative value++putNative :: Putter Value+putNative (CqlCustom x)    = putLazyByteString x+putNative (CqlBoolean x)   = putWord8 $ if x then 1 else 0+putNative (CqlInt x)       = put x+putNative (CqlBigInt x)    = put x+putNative (CqlFloat x)     = putFloat32be x+putNative (CqlDouble x)    = putFloat64be x+putNative (CqlText x)      = putByteString (T.encodeUtf8 x)+putNative (CqlUuid x)      = encodeUUID x+putNative (CqlTimeUuid x)  = encodeUUID x+putNative (CqlTimestamp x) = put x+putNative (CqlAscii x)     = putByteString (T.encodeUtf8 x)+putNative (CqlBlob x)      = putLazyByteString x+putNative (CqlCounter x)   = put x+putNative (CqlInet x)      = case x of+    IPv4 i -> putWord32le (toHostAddress i)+    IPv6 i -> do+        let (a, b, c, d) = toHostAddress6 i+        putWord32host a+        putWord32host b+        putWord32host c+        putWord32host d+putNative (CqlVarInt x)    = integer2bytes x+putNative (CqlDecimal x)   = do+    put (fromIntegral (decimalPlaces x) :: Int32)+    integer2bytes (decimalMantissa x)+putNative v@(CqlList  _)   = fail $ "putNative: collection type: " ++ show v+putNative v@(CqlSet   _)   = fail $ "putNative: collection type: " ++ show v+putNative v@(CqlMap   _)   = fail $ "putNative: collection type: " ++ show v+putNative v@(CqlMaybe _)   = fail $ "putNative: collection type: " ++ show v+putNative v@(CqlTuple _)   = fail $ "putNative: tuple type: " ++ show v+putNative v@(CqlUdt   _)   = fail $ "putNative: UDT: " ++ show v++-- Note: Empty lists, maps and sets are represented as null in cassandra.+getValue :: Version -> ColumnType -> Get Value+getValue V3 (ListColumn t)    = CqlList <$> (getList $ do+    len <- decodeInt+    replicateM (fromIntegral len) (withBytes 4 (getNative t)))+getValue V2 (ListColumn t)    = CqlList <$> (getList $ do+    len <- decodeShort+    replicateM (fromIntegral len) (withBytes 2 (getNative t)))+getValue V3 (SetColumn t)     = CqlSet <$> (getList $ do+    len <- decodeInt+    replicateM (fromIntegral len) (withBytes 4 (getNative t)))+getValue V2 (SetColumn t)     = CqlSet <$> (getList $ do+    len <- decodeShort+    replicateM (fromIntegral len) (withBytes 2 (getNative t)))+getValue V3 (MapColumn t u)   = CqlMap <$> (getList $ do+    len <- decodeInt+    replicateM (fromIntegral len)+               ((,) <$> withBytes 4 (getNative t) <*> withBytes 4 (getNative u)))+getValue V2 (MapColumn t u)   = CqlMap <$> (getList $ do+    len <- decodeShort+    replicateM (fromIntegral len)+               ((,) <$> withBytes 2 (getNative t) <*> withBytes 2 (getNative u)))+getValue V3 (TupleColumn t)   = CqlTuple <$> mapM (getValue V3) t+getValue V3 (UdtColumn _ _ x) = CqlUdt <$> do+    let (n, t) = unzip x+    zip n <$> mapM (getValue V3) t+getValue v (MaybeColumn t)    = do+    n <- lookAhead (get :: Get Int32)+    if n < 0+        then uncheckedSkip 4 >> return (CqlMaybe Nothing)+        else CqlMaybe . Just <$> getValue v t+getValue _ colType            = withBytes 4 $ getNative colType++getNative :: ColumnType -> Get Value+getNative (CustomColumn _) = CqlCustom <$> remainingBytesLazy+getNative BooleanColumn    = CqlBoolean . (/= 0) <$> getWord8+getNative IntColumn        = CqlInt <$> get+getNative BigIntColumn     = CqlBigInt <$> get+getNative FloatColumn      = CqlFloat  <$> getFloat32be+getNative DoubleColumn     = CqlDouble <$> getFloat64be+getNative TextColumn       = CqlText . T.decodeUtf8 <$> remainingBytes+getNative VarCharColumn    = CqlText . T.decodeUtf8 <$> remainingBytes+getNative AsciiColumn      = CqlAscii . T.decodeUtf8 <$> remainingBytes+getNative BlobColumn       = CqlBlob <$> remainingBytesLazy+getNative UuidColumn       = CqlUuid <$> decodeUUID+getNative TimeUuidColumn   = CqlTimeUuid <$> decodeUUID+getNative TimestampColumn  = CqlTimestamp <$> get+getNative CounterColumn    = CqlCounter <$> get+getNative InetColumn       = CqlInet <$> do+    len <- remaining+    case len of+        4  -> IPv4 . fromHostAddress <$> getWord32le+        16 -> do+            a <- (,,,) <$> getWord32host <*> getWord32host <*> getWord32host <*> getWord32host+            return $ IPv6 (fromHostAddress6 a)+        n  -> fail $ "getNative: invalid Inet length: " ++ show n+getNative VarIntColumn     = CqlVarInt <$> bytes2integer+getNative DecimalColumn    = do+    x <- get :: Get Int32+    y <- bytes2integer+    return (CqlDecimal (Decimal (fromIntegral x) y))+getNative c@(ListColumn  _)   = fail $ "getNative: collection type: " ++ show c+getNative c@(SetColumn   _)   = fail $ "getNative: collection type: " ++ show c+getNative c@(MapColumn _ _)   = fail $ "getNative: collection type: " ++ show c+getNative c@(MaybeColumn _)   = fail $ "getNative: collection type: " ++ show c+getNative c@(TupleColumn _)   = fail $ "getNative: tuple type: " ++ show c+getNative c@(UdtColumn _ _ _) = fail $ "getNative: udt: " ++ show c++getList :: Get [a] -> Get [a]+getList m = do+    n <- lookAhead (get :: Get Int32)+    if n < 0 then uncheckedSkip 4 >> return []+             else withBytes 4 m++withBytes :: Int -> Get a -> Get a+withBytes s p = do+    n <- case s of+        2 -> fromIntegral <$> (get :: Get Word16)+        4 -> fromIntegral <$> (get :: Get Int32)+        _ -> fail $ "withBytes: invalid size: " ++ show s+    when (n < 0) $+        fail "withBytes: null"+    b <- getBytes n+    case runGet p b of+        Left  e -> fail $ "withBytes: " ++ e+        Right x -> return x++remainingBytes :: Get ByteString+remainingBytes = remaining >>= getByteString . fromIntegral++remainingBytesLazy :: Get LB.ByteString+remainingBytesLazy = remaining >>= getLazyByteString . fromIntegral++toBytes :: Int -> Put -> Put+toBytes s p = do+    let bytes = runPut p+    case s of+        2 -> put (fromIntegral (B.length bytes) :: Word16)+        _ -> put (fromIntegral (B.length bytes) :: Int32)+    putByteString bytes++-- 'integer2bytes' and 'bytes2integer' implementations are taken+-- from cereal's instance declaration of 'Serialize' for 'Integer'+-- except that no distinction between small and large integers is made.+-- Cf. to LICENSE for copyright details.+integer2bytes :: Putter Integer+integer2bytes n = do+    put sign+    put (unroll (abs n))+  where+    sign = fromIntegral (signum n) :: Word8++    unroll :: Integer -> [Word8]+    unroll = unfoldr step+      where+        step 0 = Nothing+        step i = Just (fromIntegral i, i `shiftR` 8)++bytes2integer :: Get Integer+bytes2integer = do+    sign  <- get+    bytes <- get+    let v = roll bytes+    return $! if sign == (1 :: Word8) then v else - v+  where+    roll :: [Word8] -> Integer+    roll = foldr unstep 0+      where+        unstep b a = a `shiftL` 8 .|. fromIntegral b++------------------------------------------------------------------------------+-- Various++decodeKeyspace :: Get Keyspace+decodeKeyspace = Keyspace <$> decodeString++decodeTable :: Get Table+decodeTable = Table <$> decodeString++decodeQueryId :: Get (QueryId k a b)+decodeQueryId = QueryId <$> decodeShortBytes
+ src/Database/CQL/Protocol/Header.hs view
@@ -0,0 +1,162 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++module Database.CQL.Protocol.Header+    ( Header     (..)+    , HeaderType (..)+    , header+    , encodeHeader+    , decodeHeader++      -- ** Length+    , Length     (..)+    , encodeLength+    , decodeLength++      -- ** StreamId+    , StreamId+    , mkStreamId+    , fromStreamId+    , encodeStreamId+    , decodeStreamId++      -- ** Flags+    , Flags+    , compress+    , tracing+    , isSet+    , encodeFlags+    , decodeFlags+    ) where++import Control.Applicative+import Data.Bits+import Data.ByteString.Lazy (ByteString)+import Data.Int+import Data.Monoid+import Data.Serialize+import Data.Word+import Database.CQL.Protocol.Codec+import Database.CQL.Protocol.Types++-- | Protocol frame header.+data Header = Header+    { headerType :: !HeaderType+    , version    :: !Version+    , flags      :: !Flags+    , streamId   :: !StreamId+    , opCode     :: !OpCode+    , bodyLength :: !Length+    } deriving Show++data HeaderType+    = RqHeader -- ^ A request frame header.+    | RsHeader -- ^ A response frame header.+    deriving Show++encodeHeader :: Version -> HeaderType -> Flags -> StreamId -> OpCode -> Length -> PutM ()+encodeHeader v t f i o l = do+    encodeByte $ case t of+        RqHeader -> mapVersion v+        RsHeader -> mapVersion v `setBit` 7+    encodeFlags f+    encodeStreamId v i+    encodeOpCode o+    encodeLength l++decodeHeader :: Version -> Get Header+decodeHeader v = do+    b <- getWord8+    Header (mapHeaderType b)+        <$> toVersion (b .&. 0x7F)+        <*> decodeFlags+        <*> decodeStreamId v+        <*> decodeOpCode+        <*> decodeLength++mapHeaderType :: Word8 -> HeaderType+mapHeaderType b = if b `testBit` 7 then RsHeader else RqHeader++-- | Deserialise a frame header using the version specific decoding format.+header :: Version -> ByteString -> Either String Header+header v = runGetLazy (decodeHeader v)++------------------------------------------------------------------------------+-- Version++mapVersion :: Version -> Word8+mapVersion V3 = 3+mapVersion V2 = 2++toVersion :: Word8 -> Get Version+toVersion 2 = return V2+toVersion 3 = return V3+toVersion w = fail $ "decode-version: unknown: " ++ show w++------------------------------------------------------------------------------+-- Length++-- | The type denoting a protocol frame length.+newtype Length = Length { lengthRepr :: Int32 } deriving (Eq, Show)++encodeLength :: Putter Length+encodeLength (Length x) = encodeInt x++decodeLength :: Get Length+decodeLength = Length <$> decodeInt++------------------------------------------------------------------------------+-- StreamId++-- | Streams allow multiplexing of requests over a single communication+-- channel. The 'StreamId' correlates 'Request's with 'Response's.+newtype StreamId = StreamId Int16 deriving (Eq, Show)++-- | Create a StreamId from the given integral value. In version 2,+-- a StreamId is an 'Int8' and in version 3 an 'Int16'.+mkStreamId :: Integral i => i -> StreamId+mkStreamId = StreamId . fromIntegral++-- | Convert the stream ID to an integer.+fromStreamId :: StreamId -> Int+fromStreamId (StreamId i) = fromIntegral i++encodeStreamId :: Version -> Putter StreamId+encodeStreamId V3 (StreamId x) = encodeSignedShort (fromIntegral x)+encodeStreamId V2 (StreamId x) = encodeSignedByte (fromIntegral x)++decodeStreamId :: Version -> Get StreamId+decodeStreamId V3 = StreamId <$> decodeSignedShort+decodeStreamId V2 = StreamId . fromIntegral <$> decodeSignedByte++------------------------------------------------------------------------------+-- Flags++-- | Type representing header flags. Flags form a monoid and can be used+-- as in @compress <> tracing <> mempty@.+newtype Flags = Flags Word8 deriving (Eq, Show)++instance Monoid Flags where+    mempty = Flags 0+    mappend (Flags a) (Flags b) = Flags (a .|. b)++encodeFlags :: Putter Flags+encodeFlags (Flags x) = encodeByte x++decodeFlags :: Get Flags+decodeFlags = Flags <$> decodeByte++-- | Compression flag. If set, the frame body is compressed.+compress :: Flags+compress = Flags 1++-- | Tracing flag. If a request support tracing and the tracing flag was set,+-- the response to this request will have the tracing flag set and contain+-- tracing information.+tracing :: Flags+tracing = Flags 2++-- | Check if a particular flag is present.+isSet :: Flags -> Flags -> Bool+isSet (Flags a) (Flags b) = a .&. b == a
+ src/Database/CQL/Protocol/Internal.hs view
@@ -0,0 +1,38 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++-- | Like "Database.CQL.Protocol" but exports the whole+-- encode/decode machinery for all types.+module Database.CQL.Protocol.Internal+    ( -- * Cql type-class+      module Database.CQL.Protocol.Class++      -- * Basic type definitions+    , module Database.CQL.Protocol.Types++      -- * Header+    , module Database.CQL.Protocol.Header++      -- * Request+    , module Database.CQL.Protocol.Request++      -- * Response+    , module Database.CQL.Protocol.Response++      -- * Tuple and Record+    , module Database.CQL.Protocol.Tuple+    , module Database.CQL.Protocol.Record++      -- * Codec+    , module Database.CQL.Protocol.Codec+    ) where++import Database.CQL.Protocol.Class+import Database.CQL.Protocol.Codec+import Database.CQL.Protocol.Header+import Database.CQL.Protocol.Record+import Database.CQL.Protocol.Request+import Database.CQL.Protocol.Response+import Database.CQL.Protocol.Tuple+import Database.CQL.Protocol.Types
+ src/Database/CQL/Protocol/Record.hs view
@@ -0,0 +1,117 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++{-# LANGUAGE CPP          #-}+{-# LANGUAGE TypeFamilies #-}++module Database.CQL.Protocol.Record+    ( Record    (..)+    , TupleType+    , recordInstance+    ) where++import Control.Monad+import Language.Haskell.TH++typeSynDecl :: Name -> [Type] -> Type -> Dec+#if __GLASGOW_HASKELL__ < 708+typeSynDecl = TySynInstD+#else+typeSynDecl x y z = TySynInstD x (TySynEqn y z)+#endif++type family TupleType a++-- | Record/Tuple conversion.+-- For example:+--+-- @+-- data Peer = Peer+--     { peerAddr :: IP+--     , peerRPC  :: IP+--     , peerDC   :: Text+--     , peerRack :: Text+--     } deriving Show+--+-- recordInstance ''Peer+--+-- map asRecord \<$\> performQuery "SELECT peer, rpc_address, data_center, rack FROM system.peers"+-- @+--+-- The generated type-class instance maps between record and tuple constructors:+--+-- @+-- type instance TupleType Peer = (IP, IP, Text, Text)+--+-- instance Record Peer where+--     asTuple (Peer a b c d) = (a, b, c, d)+--     asRecord (a, b, c, d)  = Peer a b c d+-- @+--+class Record a where+    asTuple  :: a -> TupleType a+    asRecord :: TupleType a -> a++recordInstance :: Name -> Q [Dec]+recordInstance n = do+    i <- reify n+    case i of+        TyConI d -> start d+        _        -> fail "expecting record type"++start :: Dec -> Q [Dec]+start (DataD _ tname _ cons _) = do+    unless (length cons == 1) $+        fail "expecting single data constructor"+    tt <- tupleType (head cons)+    at <- asTupleDecl (head cons)+    ar <- asRecrdDecl (head cons)+    return+        [ typeSynDecl (mkName "TupleType") [ConT tname] tt+        , InstanceD [] (ConT (mkName "Record") $: ConT tname)+            [ FunD (mkName "asTuple")  [at]+            , FunD (mkName "asRecord") [ar]+            ]+        ]+start _ = fail "unsupported data type"++tupleType :: Con -> Q Type+tupleType c = do+    let tt = types c+    return $ foldl1 ($:) (TupleT (length tt) : types c)+  where+    types (NormalC _ tt) = map snd tt+    types (RecC _ tt)    = map (\(_, _, t) -> t) tt+    types _              = fail "record and normal constructors only"++asTupleDecl ::Con -> Q Clause+asTupleDecl c =+    case c of+        (NormalC n t) -> go n t+        (RecC    n t) -> go n t+        _             -> fail "record and normal constructors only"+  where+    go n t = do+        vars <- replicateM (length t) (newName "a")+        return $ Clause [ConP n (map VarP vars)] (body vars) []+    body = NormalB . TupE . map VarE++asRecrdDecl ::Con -> Q Clause+asRecrdDecl c =+    case c of+        (NormalC n t) -> go n t+        (RecC    n t) -> go n t+        _             -> fail "record and normal constructors only"+  where+    go n t = do+        vars <- replicateM (length t) (newName "a")+        return $ Clause [TupP (map VarP vars)] (body n vars) []+    body n v = NormalB $ foldl1 ($$) (ConE n : map VarE v)++($$) :: Exp -> Exp -> Exp+($$) = AppE++($:) :: Type -> Type -> Type+($:) = AppT+
+ src/Database/CQL/Protocol/Request.hs view
@@ -0,0 +1,334 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++{-# LANGUAGE GADTs               #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving  #-}++module Database.CQL.Protocol.Request+    ( Request           (..)+    , pack+    , encodeRequest+    , getOpCode++      -- ** Options+    , Options           (..)+    , encodeOptions++      -- ** Startup+    , Startup           (..)+    , encodeStartup++      -- ** Auth Response+    , AuthResponse      (..)+    , encodeAuthResponse++      -- ** Register+    , Register          (..)+    , EventType         (..)+    , encodeRegister+    , encodeEventType++      -- ** Query+    , Query             (..)+    , QueryParams       (..)+    , SerialConsistency (..)+    , encodeQuery+    , encodeQueryParams++      -- ** Batch+    , Batch             (..)+    , BatchQuery        (..)+    , BatchType         (..)+    , encodeBatch+    , encodeBatchType+    , encodeBatchQuery++      -- ** Prepare+    , Prepare           (..)+    , encodePrepare++      -- ** Execute+    , Execute           (..)+    , encodeExecute+    ) where++import Control.Applicative+import Control.Monad (when)+import Data.Bits+import Data.ByteString.Lazy (ByteString)+import Data.Foldable (traverse_)+import Data.Int+import Data.Text (Text)+import Data.Maybe (isJust)+import Data.Monoid+import Data.Serialize hiding (decode, encode)+import Data.Word+import Database.CQL.Protocol.Tuple+import Database.CQL.Protocol.Codec+import Database.CQL.Protocol.Types+import Database.CQL.Protocol.Header++import qualified Data.ByteString.Lazy as LB++------------------------------------------------------------------------------+-- Request++-- | The type corresponding to the protocol request frame.+--+-- The type parameter 'k' denotes the kind of request. It is present to allow+-- distinguishing read operations from write operations. Use 'R' for read,+-- 'W' for write and 'S' for schema related operations.+--+-- 'a' represents the argument type and 'b' the return type of this request.+data Request k a b+    = RqStartup  !Startup+    | RqOptions  !Options+    | RqRegister !Register+    | RqBatch    !Batch+    | RqAuthResp !AuthResponse+    | RqPrepare  !(Prepare k a b)+    | RqQuery    !(Query k a b)+    | RqExecute  !(Execute k a b)+    deriving Show++encodeRequest :: Tuple a => Version -> Putter (Request k a b)+encodeRequest _ (RqStartup  r) = encodeStartup r+encodeRequest _ (RqOptions  r) = encodeOptions r+encodeRequest _ (RqRegister r) = encodeRegister r+encodeRequest v (RqBatch    r) = encodeBatch v r+encodeRequest _ (RqAuthResp r) = encodeAuthResponse r+encodeRequest _ (RqPrepare  r) = encodePrepare r+encodeRequest v (RqQuery    r) = encodeQuery v r+encodeRequest v (RqExecute  r) = encodeExecute v r++-- | Serialise the given request, optionally using compression.+-- The result is either an error description in case of failure or a binary+-- protocol frame, including 'Header', 'Length' and body.+pack :: Tuple a+     => Version       -- ^ protocol version, which determines the encoding+     -> Compression   -- ^ compression to use+     -> Bool          -- ^ enable/disable tracing+     -> StreamId      -- ^ the stream Id to use+     -> Request k a b -- ^ the actual request to serialise+     -> Either String ByteString+pack v c t i r = do+    body <- runCompression c (runPutLazy $ encodeRequest v r)+    let len = Length . fromIntegral $ LB.length body+    return . runPutLazy $ do+        encodeHeader v RqHeader mkFlags i (getOpCode r) len+        putLazyByteString body+  where+    runCompression f x = maybe compressError return (shrink f $ x)+    compressError      = Left "pack: compression failure"++    mkFlags = (if t then tracing else mempty)+        <> (if algorithm c /= None then compress else mempty)++-- | Get the protocol 'OpCode' corresponding to the given 'Request'.+getOpCode :: Request k a b -> OpCode+getOpCode (RqQuery _)    = OcQuery+getOpCode (RqExecute _)  = OcExecute+getOpCode (RqPrepare _)  = OcPrepare+getOpCode (RqBatch _)    = OcBatch+getOpCode (RqRegister _) = OcRegister+getOpCode (RqOptions _)  = OcOptions+getOpCode (RqStartup _)  = OcStartup+getOpCode (RqAuthResp _) = OcAuthResponse++------------------------------------------------------------------------------+-- STARTUP++-- | A startup request which is used when initialising a connection to the+-- server. It specifies the CQL version to use and optionally the+-- compression algorithm.+data Startup = Startup !CqlVersion !CompressionAlgorithm deriving Show++encodeStartup :: Putter Startup+encodeStartup (Startup v c) =+    encodeMap $ ("CQL_VERSION", mapVersion v) : mapCompression c+  where+    mapVersion :: CqlVersion -> Text+    mapVersion Cqlv300        = "3.0.0"+    mapVersion (CqlVersion s) = s++    mapCompression :: CompressionAlgorithm -> [(Text, Text)]+    mapCompression Snappy = [("COMPRESSION", "snappy")]+    mapCompression LZ4    = [("COMPRESSION", "lz4")]+    mapCompression None   = []++------------------------------------------------------------------------------+-- AUTH_RESPONSE++-- | A request send in response to a previous authentication challenge.+newtype AuthResponse = AuthResponse LB.ByteString deriving Show++encodeAuthResponse :: Putter AuthResponse+encodeAuthResponse (AuthResponse b) = encodeBytes b++------------------------------------------------------------------------------+-- OPTIONS++-- | An options request, send prior to 'Startup' to request the server's+-- startup options.+data Options = Options deriving Show++encodeOptions :: Putter Options+encodeOptions _ = return ()++------------------------------------------------------------------------------+-- QUERY++-- | A CQL query (select, insert, etc.).+data Query k a b = Query !(QueryString k a b) !(QueryParams a) deriving Show++encodeQuery :: Tuple a => Version -> Putter (Query k a b)+encodeQuery v (Query (QueryString s) p) =+    encodeLongString s >> encodeQueryParams v p++------------------------------------------------------------------------------+-- EXECUTE++-- | Executes a prepared query.+data Execute k a b = Execute !(QueryId k a b) !(QueryParams a) deriving Show++encodeExecute :: Tuple a => Version -> Putter (Execute k a b)+encodeExecute v (Execute (QueryId q) p) =+    encodeShortBytes q >> encodeQueryParams v p++------------------------------------------------------------------------------+-- PREPARE++-- | Prepare a query for later execution (cf. 'Execute').+newtype Prepare k a b = Prepare (QueryString k a b) deriving Show++encodePrepare :: Putter (Prepare k a b)+encodePrepare (Prepare (QueryString p)) = encodeLongString p++------------------------------------------------------------------------------+-- REGISTER++-- | Register's the connection this request is made through, to receive+-- server events.+newtype Register = Register [EventType] deriving Show++encodeRegister :: Putter Register+encodeRegister (Register t) = do+    encodeShort (fromIntegral (length t))+    mapM_ encodeEventType t++-- | Event types to register.+data EventType+    = TopologyChangeEvent -- ^ events related to change in the cluster topology+    | StatusChangeEvent   -- ^ events related to change of node status+    | SchemaChangeEvent   -- ^ events related to schema change+    deriving Show++encodeEventType :: Putter EventType+encodeEventType TopologyChangeEvent = encodeString "TOPOLOGY_CHANGE"+encodeEventType StatusChangeEvent   = encodeString "STATUS_CHANGE"+encodeEventType SchemaChangeEvent   = encodeString "SCHEMA_CHANGE"++------------------------------------------------------------------------------+-- BATCH++-- | Allows executing a list of queries (prepared or not) as a batch.+data Batch = Batch+    { batchType              :: !BatchType+    , batchQuery             :: [BatchQuery]+    , batchConsistency       :: !Consistency+    , batchSerialConsistency :: Maybe SerialConsistency+    } deriving Show++data BatchType+    = BatchLogged   -- ^ default, uses a batch log for atomic application+    | BatchUnLogged -- ^ skip the batch log+    | BatchCounter  -- ^ used for batched counter updates+    deriving (Show)++encodeBatch :: Version -> Putter Batch+encodeBatch v (Batch t q c s) = do+    encodeBatchType t+    encodeShort (fromIntegral (length q))+    mapM_ (encodeBatchQuery v) q+    encodeConsistency c+    when (v == V3) $ do+        put batchFlags+        traverse_ encodeConsistency (mapCons <$> s)+  where+    batchFlags :: Word8+    batchFlags = if isJust s then 0x10 else 0x0++encodeBatchType :: Putter BatchType+encodeBatchType BatchLogged   = putWord8 0+encodeBatchType BatchUnLogged = putWord8 1+encodeBatchType BatchCounter  = putWord8 2++-- | A GADT to unify queries and prepared queries both of which can be used+-- in batch requests.+data BatchQuery where+    BatchQuery :: (Show a, Tuple a, Tuple b)+               => !(QueryString W a b)+               -> !a+               -> BatchQuery++    BatchPrepared :: (Show a, Tuple a, Tuple b)+                  => !(QueryId W a b)+                  -> !a+                  -> BatchQuery++deriving instance Show BatchQuery++encodeBatchQuery :: Version -> Putter BatchQuery+encodeBatchQuery n (BatchQuery (QueryString q) v) = do+    putWord8 0+    encodeLongString q+    store n v+encodeBatchQuery n (BatchPrepared (QueryId i) v)  = do+    putWord8 1+    encodeShortBytes i+    store n v++------------------------------------------------------------------------------+-- Query Parameters++-- | Query parameters.+data QueryParams a = QueryParams+    { consistency       :: !Consistency -- ^ consistency leven to use+    , skipMetaData      :: !Bool        -- ^ skip metadata in response+    , values            :: a            -- ^ query arguments+    , pageSize          :: Maybe Int32  -- ^ desired result set size+    , queryPagingState  :: Maybe PagingState+    , serialConsistency :: Maybe SerialConsistency+    } deriving Show++-- | Consistency level for the serial phase of conditional updates.+data SerialConsistency+    = SerialConsistency+    | LocalSerialConsistency+    deriving Show++encodeQueryParams :: forall a. Tuple a => Version -> Putter (QueryParams a)+encodeQueryParams v p = do+    encodeConsistency (consistency p)+    put queryFlags+    store v (values p)+    traverse_ encodeInt         (pageSize p)+    traverse_ encodePagingState (queryPagingState p)+    traverse_ encodeConsistency (mapCons <$> serialConsistency p)+  where+    queryFlags :: Word8+    queryFlags =+            (if hasValues                    then 0x01 else 0x0)+        .|. (if skipMetaData p               then 0x02 else 0x0)+        .|. (if isJust (pageSize p)          then 0x04 else 0x0)+        .|. (if isJust (queryPagingState p)  then 0x08 else 0x0)+        .|. (if isJust (serialConsistency p) then 0x10 else 0x0)++    hasValues = untag (count :: Tagged a Int) /= 0++mapCons :: SerialConsistency -> Consistency+mapCons SerialConsistency      = Serial+mapCons LocalSerialConsistency = LocalSerial
+ src/Database/CQL/Protocol/Response.hs view
@@ -0,0 +1,431 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++{-# LANGUAGE DeriveDataTypeable  #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Database.CQL.Protocol.Response+    ( Response            (..)+    , unpack++      -- ** Ready+    , Ready               (..)+    , decodeReady++      -- ** Authenticate+    , Authenticate        (..)+    , AuthChallenge       (..)+    , AuthSuccess         (..)+    , decodeAuthenticate+    , decodeAuthChallenge+    , decodeAuthSuccess++      -- ** Result+    , Result              (..)+    , MetaData            (..)+    , ColumnSpec          (..)+    , decodeResult+    , decodeMetaData++      -- ** Supported+    , Supported           (..)+    , decodeSupported++      -- ** Event+    , Event               (..)+    , TopologyChange      (..)+    , SchemaChange        (..)+    , StatusChange        (..)+    , Change              (..)+    , decodeSchemaChange+    , decodeChange+    , decodeEvent+    , decodeTopologyChange+    , decodeStatusChange++      -- ** Error+    , Error               (..)+    , WriteType           (..)+    , decodeError+    , decodeWriteType+    ) where++import Control.Applicative+import Control.Exception (Exception)+import Control.Monad+import Data.Bits+import Data.ByteString (ByteString)+import Data.Int+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import Data.Serialize hiding (Result)+import Data.Typeable+import Data.UUID (UUID)+import Data.Word+import Database.CQL.Protocol.Tuple+import Database.CQL.Protocol.Codec+import Database.CQL.Protocol.Types+import Database.CQL.Protocol.Header+import Network.Socket (SockAddr)++import qualified Data.ByteString.Lazy as LB+import qualified Data.Text            as T++------------------------------------------------------------------------------+-- Response++-- | The type corresponding to the protocol response frame.+--+-- The type parameter 'k' denotes the kind of response. It is present to allow+-- distinguishing read operations from write operations. Use 'R' for read,+-- 'W' for write and 'S' for schema related operations.+--+-- 'a' represents the argument type and 'b' the return type of this+-- response.+data Response k a b+    = RsError         (Maybe UUID) !Error+    | RsReady         (Maybe UUID) !Ready+    | RsAuthenticate  (Maybe UUID) !Authenticate+    | RsAuthChallenge (Maybe UUID) !AuthChallenge+    | RsAuthSuccess   (Maybe UUID) !AuthSuccess+    | RsSupported     (Maybe UUID) !Supported+    | RsResult        (Maybe UUID) !(Result k a b)+    | RsEvent         (Maybe UUID) !Event+    deriving Show++-- | Deserialise a 'Response' from the given 'ByteString'.+unpack :: (Tuple a, Tuple b)+       => Compression+       -> Header+       -> LB.ByteString+       -> Either String (Response k a b)+unpack c h b = do+    let f = flags h+    let v = version h+    x <- if compress `isSet` f then deflate c b else return b+    flip runGetLazy x $ do+        t <- if tracing `isSet` f then Just <$> decodeUUID else return Nothing+        message v t (opCode h)+  where+    message _ t OcError         = RsError         t <$> decodeError+    message _ t OcReady         = RsReady         t <$> decodeReady+    message _ t OcAuthenticate  = RsAuthenticate  t <$> decodeAuthenticate+    message _ t OcSupported     = RsSupported     t <$> decodeSupported+    message v t OcResult        = RsResult        t <$> decodeResult v+    message v t OcEvent         = RsEvent         t <$> decodeEvent v+    message _ t OcAuthChallenge = RsAuthChallenge t <$> decodeAuthChallenge+    message _ t OcAuthSuccess   = RsAuthSuccess   t <$> decodeAuthSuccess+    message _ _ other           = fail $ "decode-response: unknown: " ++ show other++    deflate f x  = maybe deflateError return (expand f x)+    deflateError = Left "unpack: decompression failure"++------------------------------------------------------------------------------+-- AUTHENTICATE++-- | The server requires authentication.+newtype Authenticate = Authenticate Text deriving Show++decodeAuthenticate :: Get Authenticate+decodeAuthenticate = Authenticate <$> decodeString++------------------------------------------------------------------------------+-- AUTH_CHALLENGE++-- | A server-send authentication challenge.+newtype AuthChallenge = AuthChallenge (Maybe LB.ByteString) deriving Show++decodeAuthChallenge :: Get AuthChallenge+decodeAuthChallenge = AuthChallenge <$> decodeBytes++------------------------------------------------------------------------------+-- AUTH_SUCCESS++-- | Indicates the success of an authentication phase.+newtype AuthSuccess = AuthSuccess (Maybe LB.ByteString) deriving Show++decodeAuthSuccess :: Get AuthSuccess+decodeAuthSuccess = AuthSuccess <$> decodeBytes++------------------------------------------------------------------------------+-- READY++-- | The server is ready to process queries. Response of a 'Startup'+-- request.+data Ready = Ready deriving Show++decodeReady :: Get Ready+decodeReady = return Ready++------------------------------------------------------------------------------+-- SUPPORTED++-- | The startup options supported by the server. Response of an 'Options'+-- request.+data Supported = Supported [CompressionAlgorithm] [CqlVersion] deriving Show++decodeSupported :: Get Supported+decodeSupported = do+    opt <- decodeMultiMap+    cmp <- mapM toCompression . fromMaybe [] $ lookup "COMPRESSION" opt+    let v = map toVersion . fromMaybe [] $ lookup "CQL_VERSION" opt+    return $ Supported cmp v+  where+    toCompression "snappy" = return Snappy+    toCompression "lz4"    = return LZ4+    toCompression other    = fail $+        "decode-supported: unknown compression: " ++ show other++    toVersion "3.0.0" = Cqlv300+    toVersion other   = CqlVersion other++------------------------------------------------------------------------------+-- RESULT++-- | Query response.+data Result k a b+    = VoidResult+    | RowsResult         !MetaData [b]+    | SetKeyspaceResult  !Keyspace+    | PreparedResult     !(QueryId k a b) !MetaData !MetaData+    | SchemaChangeResult !SchemaChange+    deriving (Show)++-- | Part of a @RowsResult@. Describes the result set.+data MetaData = MetaData+    { columnCount :: !Int32+    , pagingState :: Maybe PagingState+    , columnSpecs :: [ColumnSpec]+    } deriving (Show)++-- | The column specification. Part of 'MetaData' unless 'skipMetaData' in+-- 'QueryParams' was True.+data ColumnSpec = ColumnSpec+    { keyspace   :: !Keyspace+    , table      :: !Table+    , columnName :: !Text+    , columnType :: !ColumnType+    } deriving (Show)++decodeResult :: forall k a b. (Tuple a, Tuple b) => Version -> Get (Result k a b)+decodeResult v = decodeInt >>= go+  where+    go 0x1 = return VoidResult+    go 0x2 = do+        m <- decodeMetaData+        n <- decodeInt+        let c = untag (count :: Tagged b Int)+        unless (columnCount m == fromIntegral c) $+            fail $ "column count: "+                ++ show (columnCount m)+                ++ " =/= "+                ++ show c+        let typecheck = untag (check :: Tagged b ([ColumnType] -> [ColumnType]))+        let ctypes    = map columnType (columnSpecs m)+        let expected  = typecheck ctypes+        let message   = "expected: " ++ show expected ++ ", but got " ++ show ctypes+        unless (null expected) $+            fail $ "column-type error: " ++ message+        RowsResult m <$> replicateM (fromIntegral n) (tuple v)+    go 0x3 = SetKeyspaceResult <$> decodeKeyspace+    go 0x4 = PreparedResult <$> decodeQueryId <*> decodeMetaData <*> decodeMetaData+    go 0x5 = SchemaChangeResult <$> decodeSchemaChange v+    go int = fail $ "decode-result: unknown: " ++ show int++decodeMetaData :: Get MetaData+decodeMetaData = do+    f <- decodeInt+    n <- decodeInt+    p <- if hasMorePages f then decodePagingState else return Nothing+    if hasNoMetaData f+        then return $ MetaData n p []+        else MetaData n p <$> decodeSpecs n (hasGlobalSpec f)+  where+    hasGlobalSpec f = f `testBit` 0+    hasMorePages  f = f `testBit` 1+    hasNoMetaData f = f `testBit` 2++    decodeSpecs n True = do+        k <- decodeKeyspace+        t <- decodeTable+        replicateM (fromIntegral n) $ ColumnSpec k t+            <$> decodeString+            <*> decodeColumnType++    decodeSpecs n False =+        replicateM (fromIntegral n) $ ColumnSpec+            <$> decodeKeyspace+            <*> decodeTable+            <*> decodeString+            <*> decodeColumnType++------------------------------------------------------------------------------+-- SCHEMA_CHANGE++data SchemaChange+    = SchemaCreated !Change+    | SchemaUpdated !Change+    | SchemaDropped !Change+    deriving Show++data Change+    = KeyspaceChange !Keyspace+    | TableChange    !Keyspace !Table+    | TypeChange     !Keyspace !Text+    deriving Show++decodeSchemaChange :: Version -> Get SchemaChange+decodeSchemaChange v = decodeString >>= fromString+  where+    fromString "CREATED" = SchemaCreated <$> decodeChange v+    fromString "UPDATED" = SchemaUpdated <$> decodeChange v+    fromString "DROPPED" = SchemaDropped <$> decodeChange v+    fromString other     = fail $ "decode-schema-change: unknown: " ++ show other++decodeChange :: Version -> Get Change+decodeChange V3 = decodeString >>= fromString+  where+    fromString "KEYSPACE" = KeyspaceChange <$> decodeKeyspace+    fromString "TABLE"    = TableChange    <$> decodeKeyspace <*> decodeTable+    fromString "TYPE"     = TypeChange     <$> decodeKeyspace <*> decodeString+    fromString other      = fail $ "decode-change: unknown: " ++ show other+decodeChange V2 = do+    k <- decodeKeyspace+    t <- decodeTable+    return $ if T.null (unTable t) then KeyspaceChange k else TableChange k t++------------------------------------------------------------------------------+-- EVENT++-- | Messages send by the server without request, if the connection has+-- been 'Register'ed to receive such events.+data Event+    = TopologyEvent !TopologyChange !SockAddr+    | StatusEvent   !StatusChange   !SockAddr+    | SchemaEvent   !SchemaChange+    deriving Show++data TopologyChange = NewNode | RemovedNode deriving (Eq, Ord, Show)+data StatusChange   = Up | Down deriving (Eq, Ord, Show)++decodeEvent :: Version -> Get Event+decodeEvent v = decodeString >>= decodeByType+  where+    decodeByType "TOPOLOGY_CHANGE" = TopologyEvent <$> decodeTopologyChange <*> decodeSockAddr+    decodeByType "STATUS_CHANGE"   = StatusEvent   <$> decodeStatusChange <*> decodeSockAddr+    decodeByType "SCHEMA_CHANGE"   = SchemaEvent   <$> decodeSchemaChange v+    decodeByType other             = fail $ "decode-event: unknown: " ++ show other++decodeTopologyChange :: Get TopologyChange+decodeTopologyChange = decodeString >>= fromString+  where+    fromString "NEW_NODE"     = return NewNode+    fromString "REMOVED_NODE" = return RemovedNode+    fromString other          = fail $+        "decode-topology: unknown: "  ++ show other++decodeStatusChange :: Get StatusChange+decodeStatusChange = decodeString >>= fromString+  where+    fromString "UP"   = return Up+    fromString "DOWN" = return Down+    fromString other  = fail $+        "decode-status-change: unknown: " ++ show other++-----------------------------------------------------------------------------+-- ERROR++-- | Error response.+data Error+    = AlreadyExists   !Text !Keyspace !Table+    | BadCredentials  !Text+    | ConfigError     !Text+    | Invalid         !Text+    | IsBootstrapping !Text+    | Overloaded      !Text+    | ProtocolError   !Text+    | ServerError     !Text+    | SyntaxError     !Text+    | TruncateError   !Text+    | Unauthorized    !Text+    | Unprepared      !Text !ByteString+    | Unavailable+        { unavailMessage     :: !Text+        , unavailConsistency :: !Consistency+        , unavailNumRequired :: !Int32+        , unavailNumAlive    :: !Int32+        }+    | ReadTimeout+        { rTimeoutMessage     :: !Text+        , rTimeoutConsistency :: !Consistency+        , rTimeoutNumAck      :: !Int32+        , rTimeoutNumRequired :: !Int32+        , rTimeoutDataPresent :: !Bool+        }+    | WriteTimeout+        { wTimeoutMessage     :: !Text+        , wTimeoutConsistency :: !Consistency+        , wTimeoutNumAck      :: !Int32+        , wTimeoutNumRequired :: !Int32+        , wTimeoutWriteType   :: !WriteType+        }+    deriving (Eq, Show, Typeable)++instance Exception Error++data WriteType+    = WriteSimple+    | WriteBatch+    | WriteBatchLog+    | WriteUnloggedBatch+    | WriteCounter+    deriving (Eq, Show)++decodeError :: Get Error+decodeError = do+    code <- decodeInt+    msg  <- decodeString+    toError code msg+  where+    toError :: Int32 -> Text -> Get Error+    toError 0x0000 m = return $ ServerError m+    toError 0x000A m = return $ ProtocolError m+    toError 0x0100 m = return $ BadCredentials m+    toError 0x1000 m = Unavailable m <$> decodeConsistency <*> decodeInt <*> decodeInt+    toError 0x1001 m = return $ Overloaded m+    toError 0x1002 m = return $ IsBootstrapping m+    toError 0x1003 m = return $ TruncateError m+    toError 0x1100 m = WriteTimeout m+        <$> decodeConsistency+        <*> decodeInt+        <*> decodeInt+        <*> decodeWriteType+    toError 0x1200 m = ReadTimeout m+        <$> decodeConsistency+        <*> decodeInt+        <*> decodeInt+        <*> (bool <$> decodeByte)+    toError 0x2000 m = return $ SyntaxError m+    toError 0x2100 m = return $ Unauthorized m+    toError 0x2200 m = return $ Invalid m+    toError 0x2300 m = return $ ConfigError m+    toError 0x2400 m = AlreadyExists m <$> decodeKeyspace <*> decodeTable+    toError 0x2500 m = Unprepared m <$> decodeShortBytes+    toError code _   = fail $ "decode-error: unknown: " ++ show code++    bool :: Word8 -> Bool+    bool 0 = False+    bool _ = True++decodeWriteType :: Get WriteType+decodeWriteType = decodeString >>= fromString+  where+    fromString "SIMPLE"          = return WriteSimple+    fromString "BATCH"           = return WriteBatch+    fromString "BATCH_LOG"       = return WriteBatchLog+    fromString "UNLOGGED_BATCH"  = return WriteUnloggedBatch+    fromString "COUNTER"         = return WriteCounter+    fromString unknown           = fail $+        "decode: unknown write-type: " ++ show unknown
+ src/Database/CQL/Protocol/Tuple.hs view
@@ -0,0 +1,27 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++{-# LANGUAGE ScopedTypeVariables  #-}+{-# LANGUAGE TemplateHaskell      #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++-- | A tuple represents the types of multiple cassandra columns. It is used+-- to check that column-types match.+module Database.CQL.Protocol.Tuple+    ( Tuple+    , count+    , check+    , tuple+    , store+    ) where++import Control.Applicative+import Data.Serialize+import Data.Word+import Database.CQL.Protocol.Class+import Database.CQL.Protocol.Codec (putValue)+import Database.CQL.Protocol.Types+import Database.CQL.Protocol.Tuple.TH++genInstances 48
+ src/Database/CQL/Protocol/Tuple/TH.hs view
@@ -0,0 +1,153 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++{-# LANGUAGE ScopedTypeVariables #-}++module Database.CQL.Protocol.Tuple.TH where++import Control.Applicative+import Control.Monad+import Data.Functor.Identity+import Data.Serialize+import Data.Word+import Database.CQL.Protocol.Class+import Database.CQL.Protocol.Codec (putValue, getValue)+import Database.CQL.Protocol.Types+import Language.Haskell.TH++-- Database.CQL.Protocol.Tuple does not export 'PrivateTuple' but only+-- 'Tuple' effectively turning 'Tuple' into a closed type-class.+class PrivateTuple a where+    count :: Tagged a Int+    check :: Tagged a ([ColumnType] -> [ColumnType])+    tuple :: Version -> Get a+    store :: Version -> Putter a++class PrivateTuple a => Tuple a++------------------------------------------------------------------------------+-- Manual instances++instance PrivateTuple () where+    count   = Tagged 0+    check   = Tagged $ const []+    tuple _ = return ()+    store _ = const $ return ()++instance Tuple ()++instance Cql a => PrivateTuple (Identity a) where+    count   = Tagged 1+    check   = Tagged $ typecheck [untag (ctype :: Tagged a ColumnType)]+    tuple v = Identity <$> element v ctype+    store v (Identity a) = do+        put (1 :: Word16)+        putValue v (toCql a)++instance Cql a => Tuple (Identity a)++------------------------------------------------------------------------------+-- Templated instances++genInstances :: Int -> Q [Dec]+genInstances n = join <$> mapM tupleInstance [2 .. n]++tupleInstance :: Int -> Q [Dec]+tupleInstance n = do+    let cql = mkName "Cql"+    vnames <- replicateM n (newName "a")+    let vtypes    = map VarT vnames+    let tupleType = foldl1 ($:) (TupleT n : vtypes)+    let ctx       = map (\t -> ClassP cql [t]) vtypes+    td <- tupleDecl n+    sd <- storeDecl n+    return+        [ InstanceD ctx (tcon "PrivateTuple" $: tupleType)+            [ FunD (mkName "count") [countDecl n]+            , FunD (mkName "check") [checkDecl vnames]+            , FunD (mkName "tuple") [td]+            , FunD (mkName "store") [sd]+            ]+        , InstanceD ctx (tcon "Tuple" $: tupleType) []+        ]++countDecl :: Int -> Clause+countDecl n = Clause [] (NormalB body) []+  where+    body = con "Tagged" $$ litInt n++-- check = Tagged $+--     typecheck [ untag (ctype :: Tagged x ColumnType)+--               , untag (ctype :: Tagged y ColumnType)+--               , ...+--               ])+checkDecl :: [Name] -> Clause+checkDecl names = Clause [] (NormalB body) []+  where+    body  = con "Tagged" $$ (var "typecheck" $$ ListE (map fn names))+    fn n  = var "untag" $$ SigE (var "ctype") (tty n)+    tty n = tcon "Tagged" $: VarT n $: tcon "ColumnType"++-- tuple v = (,)  <$> element v ctype <*> element v ctype+-- tuple v = (,,) <$> element v ctype <*> element v ctype <*> element v ctype+-- ...+tupleDecl :: Int -> Q Clause+tupleDecl n = do+    let v = mkName "v"+    Clause [VarP v] (NormalB $ body v) <$> comb+  where+    body v = UInfixE (var "combine") (var "<$>") (foldl1 star (elts v))+    elts v = replicate n (var "element" $$ VarE v $$ var "ctype")+    star   = flip UInfixE (var "<*>")+    comb   = do+        names <- replicateM n (newName "x")+        let f = NormalB $ TupE (map VarE names)+        return [ FunD (mkName "combine") [Clause (map VarP names) f []] ]++-- store v (a, b) = put (2 :: Word16) >> putValue v (toCql a) >> putValue v (toCql b)+storeDecl :: Int -> Q Clause+storeDecl n = do+    let v = mkName "v"+    names <- replicateM n (newName "k")+    return $ Clause [VarP v, TupP (map VarP names)] (NormalB $ body v names) []+  where+    body x names = DoE (NoBindS size : map (NoBindS . value x) names)+    size         = var "put" $$ SigE (litInt n) (tcon "Word16")+    value x v    = var "putValue" $$ VarE x $$ (var "toCql" $$ VarE v)++------------------------------------------------------------------------------+-- Helpers++litInt :: Integral i => i -> Exp+litInt = LitE . IntegerL . fromIntegral++var, con :: String -> Exp+var = VarE . mkName+con = ConE . mkName++tcon :: String -> Type+tcon = ConT . mkName++($$) :: Exp -> Exp -> Exp+($$) = AppE++($:) :: Type -> Type -> Type+($:) = AppT++------------------------------------------------------------------------------+-- Implementation helpers++element :: Cql a => Version -> Tagged a ColumnType -> Get a+element v t = getValue v (untag t) >>= either fail return . fromCql++typecheck :: [ColumnType] -> [ColumnType] -> [ColumnType]+typecheck rr cc = if and (zipWith (===) rr cc) then [] else rr+  where+    (MaybeColumn a) === b               = a === b+    (ListColumn  a) === (ListColumn  b) = a === b+    (SetColumn   a) === (SetColumn   b) = a === b+    (MapColumn a b) === (MapColumn c d) = a === c && b === d+    TextColumn      === VarCharColumn   = True+    VarCharColumn   === TextColumn      = True+    a               === b               = a == b
+ src/Database/CQL/Protocol/Types.hs view
@@ -0,0 +1,215 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++module Database.CQL.Protocol.Types where++import Data.ByteString (ByteString)+import Data.Text (Text, pack, unpack)+import Data.Decimal+import Data.Int+import Data.IP+import Data.String+import Data.UUID (UUID)++import qualified Data.ByteString.Lazy as LB+import qualified Data.List            as List+import qualified Data.Text.Lazy       as LT++newtype Keyspace = Keyspace+    { unKeyspace :: Text } deriving (Eq, Show)++newtype Table = Table+    { unTable :: Text } deriving (Eq, Show)++-- | Opaque token passed to the server to continue result paging.+newtype PagingState = PagingState+    { unPagingState :: LB.ByteString } deriving (Eq, Show)++-- | ID representing a prepared query.+newtype QueryId k a b = QueryId+    { unQueryId :: ByteString } deriving (Eq, Show)++newtype QueryString k a b = QueryString+    { unQueryString :: LT.Text } deriving (Eq, Show)++instance IsString (QueryString k a b) where+    fromString = QueryString . LT.pack++-- | CQL binary protocol version.+data Version+    = V2 -- ^ version 2+    | V3 -- ^ version 3+    deriving (Eq, Show)++-- | The CQL version (not the binary protocol version).+data CqlVersion+    = Cqlv300+    | CqlVersion !Text+    deriving (Eq, Show)++data CompressionAlgorithm+    = Snappy+    | LZ4+    | None+    deriving (Eq, Show)++data Compression = Compression+    { algorithm :: !CompressionAlgorithm+    , shrink    :: LB.ByteString -> Maybe LB.ByteString+    , expand    :: LB.ByteString -> Maybe LB.ByteString+    }++instance Show Compression where+    show = show . algorithm++noCompression :: Compression+noCompression = Compression None Just Just++-- | Consistency level.+data Consistency+    = Any+    | One+    | Two+    | Three+    | Quorum+    | All+    | LocalQuorum+    | EachQuorum+    | Serial+    | LocalOne+    | LocalSerial+    deriving (Eq, Show)++-- | An opcode is a tag to distinguish protocol frame bodies.+data OpCode+    = OcError+    | OcStartup+    | OcReady+    | OcAuthenticate+    | OcOptions+    | OcSupported+    | OcQuery+    | OcResult+    | OcPrepare+    | OcExecute+    | OcRegister+    | OcEvent+    | OcBatch+    | OcAuthChallenge+    | OcAuthResponse+    | OcAuthSuccess+    deriving (Eq, Show)++-- | The type of a single CQL column.+data ColumnType+    = CustomColumn !Text+    | AsciiColumn+    | BigIntColumn+    | BlobColumn+    | BooleanColumn+    | CounterColumn+    | DecimalColumn+    | DoubleColumn+    | FloatColumn+    | IntColumn+    | TextColumn+    | TimestampColumn+    | UuidColumn+    | VarCharColumn+    | VarIntColumn+    | TimeUuidColumn+    | InetColumn+    | MaybeColumn !ColumnType+    | ListColumn  !ColumnType+    | SetColumn   !ColumnType+    | MapColumn   !ColumnType !ColumnType+    | TupleColumn [ColumnType]+    | UdtColumn   !Keyspace !Text [(Text, ColumnType)]+    deriving (Eq)++instance Show ColumnType where+    show (CustomColumn a)  = unpack a+    show AsciiColumn       = "ascii"+    show BigIntColumn      = "bigint"+    show BlobColumn        = "blob"+    show BooleanColumn     = "boolean"+    show CounterColumn     = "counter"+    show DecimalColumn     = "decimal"+    show DoubleColumn      = "double"+    show FloatColumn       = "float"+    show IntColumn         = "int"+    show TextColumn        = "text"+    show TimestampColumn   = "timestamp"+    show UuidColumn        = "uuid"+    show VarCharColumn     = "varchar"+    show VarIntColumn      = "varint"+    show TimeUuidColumn    = "timeuuid"+    show InetColumn        = "inet"+    show (MaybeColumn a)   = show a ++ "?"+    show (ListColumn a)    = showString "list<" . shows a . showString ">" $ ""+    show (SetColumn a)     = showString "set<" . shows a . showString ">" $ ""+    show (MapColumn a b)   = showString "map<"+                           . shows a+                           . showString ", "+                           . shows b+                           . showString ">"+                           $ ""+    show (TupleColumn a)   = showString "tuple<"+                           . showString (List.intercalate ", " (map show a))+                           . showString ">"+                           $ ""+    show (UdtColumn k n f) = showString (unpack (unKeyspace k))+                           . showString "."+                           . showString (unpack n)+                           . showString "<"+                           . shows (List.intercalate ", " (map show f))+                           . showString ">"+                           $ ""++newtype Ascii    = Ascii    { fromAscii    :: Text          } deriving (Eq, Ord, Show)+newtype Blob     = Blob     { fromBlob     :: LB.ByteString } deriving (Eq, Ord, Show)+newtype Counter  = Counter  { fromCounter  :: Int64         } deriving (Eq, Ord, Show)+newtype TimeUuid = TimeUuid { fromTimeUuid :: UUID          } deriving (Eq, Ord, Show)+newtype Set a    = Set      { fromSet      :: [a]           } deriving (Show)+newtype Map a b  = Map      { fromMap      :: [(a, b)]      } deriving (Show)++instance IsString Ascii where+    fromString = Ascii . pack++-- | A CQL value. The various constructors correspond to CQL data-types for+-- individual columns in Cassandra.+data Value+    = CqlCustom    !LB.ByteString+    | CqlBoolean   !Bool+    | CqlInt       !Int32+    | CqlBigInt    !Int64+    | CqlVarInt    !Integer+    | CqlFloat     !Float+    | CqlDecimal   !Decimal+    | CqlDouble    !Double+    | CqlText      !Text+    | CqlInet      !IP+    | CqlUuid      !UUID+    | CqlTimestamp !Int64+    | CqlAscii     !Text+    | CqlBlob      !LB.ByteString+    | CqlCounter   !Int64+    | CqlTimeUuid  !UUID+    | CqlMaybe     (Maybe Value)+    | CqlList      [Value]+    | CqlSet       [Value]+    | CqlMap       [(Value, Value)]+    | CqlTuple     [Value]             -- ^ binary protocol version >= 3+    | CqlUdt       [(Text, Value)]     -- ^ binary protocol version >= 3+    deriving (Eq, Show)++-- | Tag some value with a phantom type.+newtype Tagged a b = Tagged { untag :: b }++retag :: Tagged a c -> Tagged b c+retag = Tagged . untag++data R+data W+data S
+ test/Main.hs view
@@ -0,0 +1,11 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++module Main where++import Test.Tasty+import Tests++main :: IO ()+main = defaultMain tests
+ test/Tests.hs view
@@ -0,0 +1,152 @@+-- This Source Code Form is subject to the terms of the Mozilla Public+-- License, v. 2.0. If a copy of the MPL was not distributed with this+-- file, You can obtain one at http://mozilla.org/MPL/2.0/.++{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE OverloadedStrings    #-}++{-# OPTIONS_GHC -fno-warn-orphans #-}++module Tests where++import Control.Applicative hiding (many)+import Control.Arrow+import Database.CQL.Protocol+import Database.CQL.Protocol.Internal+import Data.Decimal+import Data.Int+import Data.IP+import Data.Maybe+import Data.Serialize+import Data.Time+import Data.Time.Clock.POSIX+import Data.UUID+import Test.QuickCheck+import Test.Tasty+import Test.Tasty.QuickCheck++import qualified Data.ByteString.Lazy as LB+import qualified Data.Text            as T++tests :: TestTree+tests = testGroup "Codec"+    [ testProperty "V2: getValue . putValue = id" (getPutIdentity V2)+    , testProperty "V3: getValue . putValue = id" (getPutIdentity V3)+    , testProperty "toCql . fromCql = id"     toCqlFromCqlIdentity+    , testGroup "Integrals"+        [ testProperty "Int Codec"     $ integralCodec (elements [-512..512]) IntColumn CqlInt+        , testProperty "BigInt Codec"  $ integralCodec (elements [-512..512]) BigIntColumn CqlBigInt+        , testProperty "Integer Codec" $ integralCodec (elements [-512..512]) VarIntColumn CqlVarInt+        ]+    ]++getPutIdentity :: Version -> Value -> Property+getPutIdentity v x =+    let t = typeof x+        y = runGet (getValue v t) (runPut (putValue v x))+    in Right x === y++integralCodec :: Show a => Gen a -> ColumnType -> (a -> Value) -> Property+integralCodec g t f = forAll g $ \i ->+    let x = f i in Right x === runGet (getValue V3 t) (runPut (putValue V3 x))++toCqlFromCqlIdentity :: Value -> Property+toCqlFromCqlIdentity x@(CqlBoolean _)   = (toCql <$> (fromCql x :: Either String Bool))     === Right x+toCqlFromCqlIdentity x@(CqlInt _)       = (toCql <$> (fromCql x :: Either String Int32))    === Right x+toCqlFromCqlIdentity x@(CqlBigInt _)    = (toCql <$> (fromCql x :: Either String Int64))    === Right x+toCqlFromCqlIdentity x@(CqlFloat _)     = (toCql <$> (fromCql x :: Either String Float))    === Right x+toCqlFromCqlIdentity x@(CqlDouble _)    = (toCql <$> (fromCql x :: Either String Double))   === Right x+toCqlFromCqlIdentity x@(CqlText _)      = (toCql <$> (fromCql x :: Either String T.Text))   === Right x+toCqlFromCqlIdentity x@(CqlInet _)      = (toCql <$> (fromCql x :: Either String IP))       === Right x+toCqlFromCqlIdentity x@(CqlUuid _)      = (toCql <$> (fromCql x :: Either String UUID))     === Right x+toCqlFromCqlIdentity x@(CqlTimestamp _) = (toCql <$> (fromCql x :: Either String UTCTime))  === Right x+toCqlFromCqlIdentity x@(CqlAscii _)     = (toCql <$> (fromCql x :: Either String Ascii))    === Right x+toCqlFromCqlIdentity x@(CqlBlob _)      = (toCql <$> (fromCql x :: Either String Blob))     === Right x+toCqlFromCqlIdentity x@(CqlCounter _)   = (toCql <$> (fromCql x :: Either String Counter))  === Right x+toCqlFromCqlIdentity x@(CqlTimeUuid _)  = (toCql <$> (fromCql x :: Either String TimeUuid)) === Right x+toCqlFromCqlIdentity x@(CqlVarInt _)    = (toCql <$> (fromCql x :: Either String Integer))  === Right x+toCqlFromCqlIdentity x@(CqlDecimal _)   = (toCql <$> (fromCql x :: Either String Decimal))  === Right x+toCqlFromCqlIdentity _                  = True === True++typeof :: Value -> ColumnType+typeof (CqlBoolean _)      = BooleanColumn+typeof (CqlInt _)          = IntColumn+typeof (CqlBigInt _)       = BigIntColumn+typeof (CqlVarInt _)       = VarIntColumn+typeof (CqlFloat _)        = FloatColumn+typeof (CqlDecimal _)      = DecimalColumn+typeof (CqlDouble _)       = DoubleColumn+typeof (CqlText _)         = TextColumn+typeof (CqlInet _)         = InetColumn+typeof (CqlUuid _)         = UuidColumn+typeof (CqlTimestamp _)    = TimestampColumn+typeof (CqlAscii _)        = AsciiColumn+typeof (CqlBlob _)         = BlobColumn+typeof (CqlCounter _)      = CounterColumn+typeof (CqlTimeUuid _)     = TimeUuidColumn+typeof (CqlMaybe Nothing)  = MaybeColumn (CustomColumn "a")+typeof (CqlMaybe (Just a)) = MaybeColumn (typeof a)+typeof (CqlList [])        = ListColumn  (CustomColumn "a")+typeof (CqlList (x:_))     = ListColumn  (typeof x)+typeof (CqlSet  [])        = SetColumn (CustomColumn "a")+typeof (CqlSet  (x:_))     = SetColumn (typeof x)+typeof (CqlMap  [])        = MapColumn (CustomColumn "a") (CustomColumn "b")+typeof (CqlMap  ((x,y):_)) = MapColumn (typeof x) (typeof y)+typeof (CqlCustom _)       = CustomColumn "a"+typeof (CqlTuple x)        = TupleColumn (map typeof x)+typeof (CqlUdt   x)        = UdtColumn (Keyspace "") "" (map (second typeof) x)++instance Arbitrary Value where+    arbitrary = oneof+        [ simple+        , CqlMaybe <$> oneof [Just <$> simple, return Nothing]+        , CqlList  <$> many+        , CqlSet   <$> many+        , CqlMap   <$> (zip <$> many <*> many)+        ]+      where+        many   = simple >>= listOf . return+        simple = oneof+            [ CqlAscii     <$> arbitrary+            , CqlBigInt    <$> arbitrary+            , CqlBlob      <$> arbitrary+            , CqlBoolean   <$> arbitrary+            , CqlCounter   <$> arbitrary+            , CqlCustom    <$> arbitrary+            , CqlDouble    <$> arbitrary+            , CqlFloat     <$> arbitrary+            , CqlInet      <$> arbitrary+            , CqlInt       <$> arbitrary+            , CqlTimeUuid  <$> arbitrary+            , CqlTimestamp <$> arbitrary+            , CqlUuid      <$> arbitrary+            , CqlText      <$> arbitrary+            , CqlDecimal   <$> arbitrary+            , CqlVarInt    <$> arbitrary+            ]++instance Arbitrary LB.ByteString where+    arbitrary = LB.pack <$> arbitrary++instance Arbitrary T.Text where+    arbitrary = T.pack <$> arbitrary++instance Arbitrary IP where+    arbitrary = oneof+        [ IPv4 . fromHostAddress  <$> arbitrary+        , IPv6 . fromHostAddress6 <$> arbitrary+        ]++instance Bounded UUID where+    minBound = nil+    maxBound = fromJust $ fromString "ffffffff-ffff-4fff-bfff-ffffffffffff"++instance Arbitrary UUID where+    arbitrary = arbitraryBoundedRandom++instance Arbitrary UTCTime where+    arbitrary = posixSecondsToUTCTime . fromIntegral+        <$> (arbitrary :: Gen Int64)++instance Arbitrary (DecimalRaw Integer) where+    arbitrary = Decimal <$> arbitrary <*> arbitrary