Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ packages:
./library
./ecosystem/tls
./ecosystem/eventlog2ch
./ecosystem/PoC-clickhouse-proxy
./contribution
./contribution/modules/protocol-docs
./contribution/modules/visits
Expand Down
88 changes: 88 additions & 0 deletions ecosystem/PoC-clickhouse-proxy/ClickHouseProxy/TcpServer.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedRecordDot #-}
module ClickHouseProxy.TcpServer (
runTcpServer
) where

import ClickHaskell (ToChType (..))
import ClickHaskell.Internal
import ClickHaskell.Protocol (ClientPacket (..), ExceptionPacket (..), HelloPacket (..), Serializable (..), ServerPacket (..))
import Control.Concurrent (forkFinally)
import Control.Exception as E (bracket, bracketOnError)
import Control.Monad (forever, void)
import Data.Binary (Get)
import Data.Binary.Get (Decoder (..), runGetIncremental)
import Data.ByteString as BS (ByteString)
import Data.ByteString.Char8 as BS (putStrLn)
import Data.ByteString.Char8 as BS8 (pack)
import Data.List.NonEmpty as NE (head)
import Network.Socket


runTcpServer :: IO ()
runTcpServer =
runTCPServer Nothing "9000" \sock -> do
BS.putStrLn "Opening connection"
buffer <- mkBuffer =<< defaultMkBufferArgs "" sock
talk buffer
where
talk buffer = do
packet <- readBuffer buffer (deserialize @ClientPacket 54459)
case packet of
Hello hello -> do
Prelude.putStrLn $
"client: " <> show hello.client_name <> " " <>
"client_version_major: " <> show hello.client_version_major <> " " <>
"client_version_minor: " <> show hello.client_version_minor <> " " <>
"tcp_protocol_version: " <> "54459" <> " " <>
"default_database: " <> show hello.default_database <> " " <>
"user: " <> show hello.user <> " " <>
"pass: " <> show hello.pass <> " " <>
""
writeConn buffer
. serialize 54459
$ Exception MkExceptionPacket{
code = 0,
name = toChType @_ @ByteString $ BS8.pack "Test exception",
message = toChType @_ @ByteString $ BS8.pack "\"Hello, World!\"",
stack_trace = toChType @_ @ByteString $ BS8.pack "",
nested = 0
}
_other -> error "Unexpected packet"


runTCPServer :: Maybe HostName -> ServiceName -> (Socket -> IO a) -> IO a
runTCPServer mhost port server = do
addr <- resolve
E.bracket (open addr) close loop
where
resolve = do
let hints = defaultHints {
addrFlags = [AI_PASSIVE],
addrSocketType = Stream
}
NE.head <$> getAddrInfo (Just hints) mhost (Just port)
open addr =
E.bracketOnError (openSocket addr) close $ \sock -> do
setSocketOption sock ReuseAddr 1
withFdSocket sock setCloseOnExecIfNeeded
bind sock $ addrAddress addr
listen sock 1024
return sock
loop sock =
forever $ E.bracketOnError (accept sock) (close . fst)
$ \(conn, _peer) -> void $
forkFinally (server conn) (const $ gracefulClose conn 5000)


readBuffer :: Buffer -> Get a -> IO a
readBuffer MkBuffer{readBuff, writeBuff} parser = runBufferReader (runGetIncremental parser)
where
runBufferReader :: Decoder packet -> IO packet
runBufferReader dec = case dec of
(Partial decoder) -> readBuff >>= runBufferReader . decoder . Just
(Done leftover _consumed packet) -> packet <$ writeBuff leftover
(Fail _leftover _consumed msg) -> error ("Deserialization error: " <> msg)
28 changes: 28 additions & 0 deletions ecosystem/PoC-clickhouse-proxy/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
BSD 3-Clause License

Copyright (c) 2026, Dmitry Kovalev

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 copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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.
1 change: 1 addition & 0 deletions ecosystem/PoC-clickhouse-proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# clickhouse-proxy
43 changes: 43 additions & 0 deletions ecosystem/PoC-clickhouse-proxy/clickhouse-proxy.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
cabal-version: 3.0

name: clickhouse-proxy
version: 0.1
author: Dmitry Kovalev
maintainer: Dmitry Kovalev
category: ClickHouse
synopsis: ClickHouse proxy
description: ClickHouse TCP protocol proxy
homepage: https://github.com/KovalevDima/clickhouse-proxy
bug-reports: https://github.com/KovalevDima/clickhouse-proxy
license: BSD-3-Clause
license-file: LICENSE
copyright: 2026 Dmitry Kovalev
build-type: Simple

extra-doc-files:
README.md

source-repository head
type: git
location: https://github.com/KovalevDima/clickhouse-proxy

executable clickhouse-proxy
main-is: clickhouse-proxy.hs
hs-source-dirs: .
ghc-options:
-Wall
-Wunused-packages
build-depends:
-- Internal dependencies
ClickHaskell,
-- GHC included libraries
base >= 4.7 && <5,
bytestring,
binary,
-- External dependencies
network < 4,
other-modules:
ClickHouseProxy.TcpServer
default-language: Haskell2010
default-extensions:
BlockArguments
8 changes: 8 additions & 0 deletions ecosystem/PoC-clickhouse-proxy/clickhouse-proxy.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Main where

import ClickHouseProxy.TcpServer (runTcpServer)

main :: IO ()
main = do
putStrLn "Starting server"
runTcpServer
1 change: 1 addition & 0 deletions library/ClickHaskell.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Library
Exposed-Modules:
ClickHaskell
ClickHaskell.Protocol
ClickHaskell.Internal
Other-Modules:
ClickHaskell.Connection
ClickHaskell.Protocol.Client
Expand Down
8 changes: 8 additions & 0 deletions library/ClickHaskell/Internal.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module ClickHaskell.Internal (
Buffer(..),
mkBuffer,
defaultMkBufferArgs,
) where

-- Internal
import ClickHaskell.Connection
Loading