sidorares / sidorares/node-mysql2

Can't connect to ClickHouse with default connection flags

Open
#3,988 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

needs investigation
Dominant language
TypeScript
Stars
4.4k
Forks
680
Avg merge
9h 7m
Merged PRs (30d)
59

Description

Hi, I'm trying to connect to a clickhouse server with this library, but the default configuration requires that the server supports the CONNECT_ATTRS and SESSION_TRACK flags, which clickhouse doesn't support. The clickhouse server sends:

capability flags:19433992=(connect with db, protocol 41, ssl, secure connection, plugin auth, plugin auth lenenc client data, deprecate eof)

We could do something like this:

diff --git a/lib/commands/client_handshake.js b/lib/commands/client_handshake.js
index ac0d8634..13010d8a 100644
--- a/lib/commands/client_handshake.js
+++ b/lib/commands/client_handshake.js
@@ -121,6 +121,8 @@ class ClientHandshake extends Command {
     connection.serverCapabilityFlags = this.handshake.capabilityFlags;
     connection.serverEncoding = CharsetToEncoding[this.handshake.characterSet];
     connection.connectionId = this.handshake.connectionId;
+    // Only use capabilities that both client and server support
+    this.clientFlags = this.clientFlags & this.handshake.capabilityFlags;
     const serverSSLSupport =
       this.handshake.capabilityFlags & ClientConstants.SSL;
     // multi factor authentication is enabled with the

But I'm not sure if that's a good idea, I'm not deeply familiar with this library. This would only use the capabilities both client and server support i.e. negotiation.

Logs

I've created a reproduction (requires clickhouse to be installed). It fails on master

$ clickhouse-server --version
ClickHouse server version 25.11.2.24.
$ git rev-parse HEAD
40ad043bcc89059f66e38dfb5e86533b949fef60
run-clickhouse
#!/usr/bin/env bash
# Set up a temporary ClickHouse server and run the integration tests against them

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SSL_CERTS_DIR="$SCRIPT_DIR/test/fixtures/ssl/certs"

dir=$(mktemp -d)
trap "rm -rf $dir" EXIT

# Set up ClickHouse
HTTP_PORT=$(python3 -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()')
TCP_PORT=$(python3 -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()')
MYSQL_PORT=$(python3 -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()')

mkdir -p "$dir/clickhouse/config"
mkdir -p "$dir/clickhouse/data"
mkdir -p "$dir/clickhouse/logs"

cat > "$dir/clickhouse/config/config.xml" <<EOF
<clickhouse>
    <logger>
        <level>warning</level>
        <log>$dir/clickhouse/logs/clickhouse-server.log</log>
        <errorlog>$dir/clickhouse/logs/clickhouse-server.err.log</errorlog>
    </logger>
    <http_port>$HTTP_PORT</http_port>
    <tcp_port>$TCP_PORT</tcp_port>
    <mysql_port>$MYSQL_PORT</mysql_port>
    <listen_host>127.0.0.1</listen_host>
    <path>$dir/clickhouse/data</path>
    <tmp_path>$dir/clickhouse/tmp</tmp_path>
    <user_files_path>$dir/clickhouse/user_files</user_files_path>
    <format_schema_path>$dir/clickhouse/format_schemas</format_schema_path>
    <users_config>$dir/clickhouse/config/users.xml</users_config>
    <!-- OpenSSL configuration required for TLS on MySQL protocol -->
    <openSSL>
        <server>
            <certificateFile>$SSL_CERTS_DIR/server-cert.pem</certificateFile>
            <privateKeyFile>$SSL_CERTS_DIR/server-key.pem</privateKeyFile>
            <caConfig>$SSL_CERTS_DIR/ca.pem</caConfig>
            <verificationMode>none</verificationMode>
            <loadDefaultCAFile>false</loadDefaultCAFile>
            <cacheSessions>true</cacheSessions>
            <disableProtocols>sslv2,sslv3</disableProtocols>
            <preferServerCiphers>true</preferServerCiphers>
        </server>
    </openSSL>
</clickhouse>
EOF

cat > "$dir/clickhouse/config/users.xml" <<EOF
<clickhouse>
  <logger>
    <console>1</console>
  </logger>
    <users>
        <default>
            <password></password>
            <networks>
                <ip>::/0</ip>
            </networks>
            <profile>default</profile>
            <quota>default</quota>
        </default>
    </users>
    <profiles>
        <default>
        </default>
    </profiles>
    <quotas>
        <default>
        </default>
    </quotas>
</clickhouse>
EOF

clickhouse-server --config-file="$dir/clickhouse/config/config.xml" --pid-file="$dir/clickhouse/clickhouse-server.pid" &
CLICKHOUSE_PID=$!

trap "kill $CLICKHOUSE_PID 2>/dev/null || true; rm -rf $dir" EXIT

for i in {1..30}; do
    if clickhouse-client --host 127.0.0.1 --port "$TCP_PORT" --query "SELECT 1" > /dev/null 2>&1; then
        break
    fi
    if [ "$i" -eq 30 ]; then
        echo "ClickHouse failed to start"
        exit 1
    fi
    sleep 1
done

clickhouse-client --host 127.0.0.1 --port "$TCP_PORT" --query "CREATE DATABASE IF NOT EXISTS testdb" > /dev/null 2>&1

export CLICKHOUSE_URL="http://127.0.0.1:$HTTP_PORT"
export CLICKHOUSE_MYSQL_PORT="$MYSQL_PORT"

# Run passed-in command

"${@}"
test/integration/connection/test-clickhouse-connection.test.cjs
'use strict';

const { assert } = require('poku');
const fs = require('node:fs');
const path = require('node:path');
const process = require('node:process');

// Skip test if not running with ClickHouse
if (!process.env.CLICKHOUSE_MYSQL_PORT) {
  console.log('Skipping ClickHouse test - CLICKHOUSE_MYSQL_PORT not set');
  process.exit(0);
}

const mysql = require('../../../index.js');

const sslConfig = {
  rejectUnauthorized: false,
  ca: fs.readFileSync(
    path.join(__dirname, '../../fixtures/ssl/certs/ca.pem'),
    'utf-8'
  ),
};

const connection = mysql.createConnection({
  host: '127.0.0.1',
  port: parseInt(process.env.CLICKHOUSE_MYSQL_PORT, 10),
  user: 'default',
  password: '',
  database: 'testdb',
  ssl: sslConfig,
});

connection.on('error', (err) => {
  console.error('Connection error:', err);
  process.exit(1);
});

// Test basic query with TLS
connection.query('SELECT 1 AS result', (err, rows, fields) => {
  assert.ifError(err);
  assert.deepEqual(rows, [{ result: 1 }]);
  assert.equal(fields[0].name, 'result');

  // Verify we're connected over TLS by checking the connection's encrypted property
  assert.equal(connection.stream.encrypted, true, 'Connection should be encrypted');

  // Test another query
  connection.query('SELECT 2 AS value', (err, rows, fields) => {
    assert.ifError(err);
    assert.deepEqual(rows, [{ value: 2 }]);
    assert.equal(fields[0].name, 'value');

    console.log('ClickHouse connection with TLS: OK');

    connection.end((err) => {
      assert.ifError(err);
      process.exit(0);
    });
  });
});

Test by running: ./run-clickhouse node test/integration/connection/test-clickhouse-connection.test.cjs

On master it fails with:

2025.12.22 13:28:26.711541 [ 42068898 ] {} <Error> MySQLHandler: DB::Exception: Packet payload is not fully read. Stopped after 70 bytes, while 50 bytes are in buffer.
✘ Expected no error, but received an error
      Code ERR_ASSERTION
  Operator ifError

AssertionError [ERR_ASSERTION]: ifError got unwanted exception: Connection lost: The server closed the connection.
    at message.message (/Users/bouke/src/github.com/sidorares/node-mysql2/node_modules/poku/lib/builders/assert.js:25:89)
    at processAssert (/Users/bouke/src/github.com/sidorares/node-mysql2/node_modules/poku/lib/services/assert.js:26:13)
    at Function.ifError (/Users/bouke/src/github.com/sidorares/node-mysql2/node_modules/poku/lib/builders/assert.js:25:71)
    at Query.onResult (/Users/bouke/src/github.com/sidorares/node-mysql2/test/integration/connection/test-clickhouse-connection.test.cjs:40:10)
    at Connection._notifyError (/Users/bouke/src/github.com/sidorares/node-mysql2/lib/base/connection.js:239:17)
    at Socket.<anonymous> (/Users/bouke/src/github.com/sidorares/node-mysql2/lib/base/connection.js:119:12)
    at Socket.<anonymous> (/Users/bouke/src/github.com/sidorares/node-mysql2/lib/base/connection.js:113:31)
    at Socket.emit (node:events:531:35)
    at TCP.<anonymous> (node:net:346:12) {
  generatedMessage: false,
  code: 'ERR_ASSERTION',
  actual: [Error],
  expected: null,
  operator: 'ifError',
  diff: 'simple'
}

Workaround

Doing the following also works

const connection = mysql.createConnection({
  ...
  // ClickHouse doesn't support CONNECT_ATTRS or SESSION_TRACK flags
  flags: '-CONNECT_ATTRS,-SESSION_TRACK',
});

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in lib/commands/client_handshake.js and review how client and server capability flags are handled during the handshake. Run ./run-clickhouse node test/integration/connection/test-clickhouse-connection.test.cjs with ClickHouse installed, then use test/integration/connection/test-clickhouse-connection.test.cjs to verify the TLS connection and both queries complete successfully.

Written by the indexing model from the issue text.

Assessment

Tech stack
clickhouse, javascript, mysql, nodejs
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.