Skip to content

Commit ae63ab4

Browse files
committed
Let the result readers read the response body directly
HttpResponse was a std::istream only to keep the reader interfaces from the Poco days, and AmortizedIStreamReader was the only consumer, reading through rdbuf() into a buffer of its own. Both ends are driver code now, so HttpResponse provides the three operations the format parsers need, eof(), get() and read(), with inline fast paths over the buffered data, and the parsers take an HttpResponse reference. This removes a copy of every byte of the body and AmortizedIStreamReader, as well as the std::ios specifics this needed: the exception mask to get errors out of std::istream, the ambiguous traits_type of the multiple stream bases, and the construction order of the virtual base. Errors are thrown from eof(), get() and read(), and again from every later call.
1 parent 6aaacd4 commit ae63ab4

10 files changed

Lines changed: 92 additions & 191 deletions

‎driver/CMakeLists.txt‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ add_library (${libname}-impl STATIC
7878

7979
utils/utils.h
8080
utils/iostream_debug_helpers.h
81-
utils/amortized_istream_reader.h
8281
utils/object_pool.h
8382
utils/string_pool.h
8483
utils/unicode_converter.h

‎driver/format/ODBCDriver2.cpp‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
ODBCDriver2ResultSet::ODBCDriver2ResultSet(
44
const std::string & timezone,
5-
AmortizedIStreamReader & stream,
5+
HttpResponse & stream,
66
std::unique_ptr<ResultMutator> && mutator
77
)
88
: ResultSet(stream, std::move(mutator))
@@ -263,10 +263,10 @@ void ODBCDriver2ResultSet::readValue(std::string & src, DataSourceType<DataSourc
263263

264264
ODBCDriver2ResultReader::ODBCDriver2ResultReader(
265265
const std::string & timezone_,
266-
std::istream & raw_stream,
266+
HttpResponse & stream_,
267267
std::unique_ptr<ResultMutator> && mutator
268268
)
269-
: ResultReader(timezone_, raw_stream, std::move(mutator))
269+
: ResultReader(timezone_, stream_, std::move(mutator))
270270
{
271271
if (stream.eof())
272272
return;

‎driver/format/ODBCDriver2.h‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class ODBCDriver2ResultSet
1010
public:
1111
explicit ODBCDriver2ResultSet(
1212
const std::string & timezone,
13-
AmortizedIStreamReader & stream,
13+
HttpResponse & stream,
1414
std::unique_ptr<ResultMutator> && mutator
1515
);
1616
virtual ~ODBCDriver2ResultSet() override = default;
@@ -69,7 +69,7 @@ class ODBCDriver2ResultReader
6969
public:
7070
explicit ODBCDriver2ResultReader(
7171
const std::string & timezone_,
72-
std::istream & raw_stream,
72+
HttpResponse & stream_,
7373
std::unique_ptr<ResultMutator> && mutator
7474
);
7575
virtual ~ODBCDriver2ResultReader() override = default;

‎driver/format/RowBinaryWithNamesAndTypes.cpp‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#include <ctime>
44

5-
RowBinaryWithNamesAndTypesResultSet::RowBinaryWithNamesAndTypesResultSet(const std::string & timezone, AmortizedIStreamReader & stream, std::unique_ptr<ResultMutator> && mutator)
5+
RowBinaryWithNamesAndTypesResultSet::RowBinaryWithNamesAndTypesResultSet(const std::string & timezone, HttpResponse & stream, std::unique_ptr<ResultMutator> && mutator)
66
: ResultSet(stream, std::move(mutator))
77
{
88
std::uint64_t num_columns = 0;
@@ -306,10 +306,10 @@ void RowBinaryWithNamesAndTypesResultSet::readValue(DataSourceType<DataSourceTyp
306306

307307
RowBinaryWithNamesAndTypesResultReader::RowBinaryWithNamesAndTypesResultReader(
308308
const std::string & timezone_,
309-
std::istream & raw_stream,
309+
HttpResponse & stream_,
310310
std::unique_ptr<ResultMutator> && mutator
311311
)
312-
: ResultReader(timezone_, raw_stream, std::move(mutator))
312+
: ResultReader(timezone_, stream_, std::move(mutator))
313313
{
314314
if (stream.eof())
315315
return;

‎driver/format/RowBinaryWithNamesAndTypes.h‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class RowBinaryWithNamesAndTypesResultSet
88
: public ResultSet
99
{
1010
public:
11-
explicit RowBinaryWithNamesAndTypesResultSet(const std::string & timezone, AmortizedIStreamReader & stream, std::unique_ptr<ResultMutator> && mutator);
11+
explicit RowBinaryWithNamesAndTypesResultSet(const std::string & timezone, HttpResponse & stream, std::unique_ptr<ResultMutator> && mutator);
1212
virtual ~RowBinaryWithNamesAndTypesResultSet() override = default;
1313

1414
protected:
@@ -77,7 +77,7 @@ class RowBinaryWithNamesAndTypesResultReader
7777
public:
7878
explicit RowBinaryWithNamesAndTypesResultReader(
7979
const std::string & timezone,
80-
std::istream & raw_stream,
80+
HttpResponse & stream_,
8181
std::unique_ptr<ResultMutator> && mutator
8282
);
8383
virtual ~RowBinaryWithNamesAndTypesResultReader() override = default;

‎driver/http_client.cpp‎

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <curl/curl.h>
55
#include <zstd.h>
66

7+
#include <algorithm>
78
#include <cctype>
89
#include <charconv>
910
#include <cstdio>
@@ -274,13 +275,10 @@ struct HttpResponse::Zstd {
274275
};
275276

276277
HttpResponse::HttpResponse(std::shared_ptr<HttpSession> session_, HttpRequest request_)
277-
: std::istream(static_cast<std::streambuf *>(this))
278-
, session(std::move(session_))
278+
: session(std::move(session_))
279279
, request(std::move(request_))
280280
, handles(std::make_unique<Handles>())
281281
{
282-
exceptions(std::ios::badbit); // failures thrown from underflow() reach the reader
283-
284282
const auto & settings = session->settings;
285283

286284
handles->multi = curl_multi_init();
@@ -391,12 +389,10 @@ std::string HttpResponse::getRedirectUrl() const {
391389
std::string HttpResponse::readAll(std::size_t limit) {
392390
std::string res;
393391
try {
394-
char buffer[4096];
395-
while (res.size() < limit) {
396-
const auto n = sgetn(buffer, sizeof(buffer));
397-
if (n <= 0)
398-
break;
399-
res.append(buffer, static_cast<std::size_t>(n));
392+
while (res.size() < limit && (pos < end || fill())) {
393+
const auto n = std::min(static_cast<std::size_t>(end - pos), limit - res.size());
394+
res.append(pos, n);
395+
pos += n;
400396
}
401397
}
402398
catch (...) {
@@ -501,11 +497,24 @@ void HttpResponse::pump() {
501497
curl_multi_poll(handles->multi, nullptr, 0, 200, nullptr);
502498
}
503499

504-
HttpResponse::int_type HttpResponse::underflow() {
505-
if (gptr() < egptr())
506-
return traits_type::to_int_type(*gptr());
500+
void HttpResponse::readSlow(char * dest, std::size_t count) {
501+
const auto requested = count;
502+
while (count > 0) {
503+
if (pos == end && !fill())
504+
throw std::runtime_error("Incomplete input stream, expected at least " + std::to_string(requested) + " more bytes");
505+
const auto n = std::min(static_cast<std::size_t>(end - pos), count);
506+
std::memcpy(dest, pos, n);
507+
dest += n;
508+
pos += n;
509+
count -= n;
510+
}
511+
}
507512

508-
setg(nullptr, nullptr, nullptr);
513+
bool HttpResponse::fill() {
514+
if (pos < end)
515+
return true;
516+
517+
pos = end = nullptr;
509518

510519
if (!error && !unsupported_encoding.empty())
511520
error = std::make_exception_ptr(std::runtime_error("Unsupported Content-Encoding: " + unsupported_encoding));
@@ -530,10 +539,10 @@ HttpResponse::int_type HttpResponse::underflow() {
530539
if (!done || result == CURLE_OK) {
531540
if (!zstd) {
532541
if (allowed > 0) {
533-
char * begin = raw.data() + raw_pos;
534-
setg(begin, begin, begin + allowed);
542+
pos = raw.data() + raw_pos;
543+
end = pos + allowed;
535544
raw_pos += allowed;
536-
return traits_type::to_int_type(*gptr());
545+
return true;
537546
}
538547
}
539548
else if (allowed > 0 || done) {
@@ -549,8 +558,9 @@ HttpResponse::int_type HttpResponse::underflow() {
549558
if (in.pos > 0 || out.pos > 0) // a call without progress returns a size hint, not 0
550559
zstd_frame_complete = (rc == 0);
551560
if (out.pos > 0) {
552-
setg(decoded.data(), decoded.data(), decoded.data() + out.pos);
553-
return traits_type::to_int_type(*gptr());
561+
pos = decoded.data();
562+
end = pos + out.pos;
563+
return true;
554564
}
555565
if (in.pos > 0)
556566
continue; // input consumed without output yet
@@ -561,7 +571,7 @@ HttpResponse::int_type HttpResponse::underflow() {
561571
finish();
562572
if (error)
563573
std::rethrow_exception(error);
564-
return traits_type::eof();
574+
return false;
565575
}
566576

567577
pump();

‎driver/http_client.h‎

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,18 @@
22

33
#include <chrono>
44
#include <cstdint>
5+
#include <cstring>
56
#include <exception>
6-
#include <istream>
77
#include <map>
88
#include <memory>
99
#include <stdexcept>
10-
#include <streambuf>
1110
#include <string>
1211
#include <vector>
1312

1413
// HTTP(S) client of the driver, built on libcurl.
1514
//
1615
// HttpSession is created per ODBC connection; HttpResponse per request. The response body is
17-
// read through HttpResponse, which is a std::istream that pulls data from the network lazily.
16+
// read directly from HttpResponse (eof/get/read), which pulls data from the network lazily.
1817
//
1918
// ClickHouse may report an error after it has already started sending a successful response
2019
// (a "mid-stream exception"): the exception text is appended to the body and the connection is
@@ -71,18 +70,16 @@ struct HttpRequest {
7170
};
7271

7372
/// A single POST request and its response. The constructor sends the request and blocks until
74-
/// the response headers are received, throwing HttpTransportError if that fails. The body is
75-
/// then read through the std::istream interface or directly through rdbuf(). Any failure while
76-
/// reading the body (transport error, broken compressed data, ClickHouse exception) is thrown from
77-
/// the read operation itself, and again from every following read. (The stream has std::ios::badbit
78-
/// set in exceptions(), so std::istream operations rethrow it too, but only once.)
79-
class HttpResponse
80-
: private std::streambuf
81-
, public std::istream
82-
{
73+
/// the response headers are received, throwing HttpTransportError if that fails. The body is then
74+
/// read with eof(), get() and read(). Any failure while reading the body (transport error, broken
75+
/// compressed data, ClickHouse exception) is thrown from these, and again from every later call.
76+
class HttpResponse {
8377
public:
8478
HttpResponse(std::shared_ptr<HttpSession> session, HttpRequest request);
85-
~HttpResponse() override;
79+
~HttpResponse();
80+
81+
HttpResponse(const HttpResponse &) = delete;
82+
HttpResponse & operator=(const HttpResponse &) = delete;
8683

8784
long getStatus() const { return status; }
8885
bool hasHeader(const std::string & name) const;
@@ -91,15 +88,37 @@ class HttpResponse
9188
/// The target of a redirect, resolved against the request URL. Empty if there is none.
9289
std::string getRedirectUrl() const;
9390

91+
/// True if the body has no more data. Waits for data, if necessary.
92+
bool eof() {
93+
return (pos == end && !fill());
94+
}
95+
96+
/// Reads one byte of the body; throws if there is none.
97+
char get() {
98+
if (pos == end && !fill())
99+
throw std::runtime_error("Incomplete input stream, expected at least 1 more byte");
100+
return *pos++;
101+
}
102+
103+
/// Reads exactly `count` bytes of the body; throws if there are fewer.
104+
void read(char * dest, std::size_t count) {
105+
if (static_cast<std::size_t>(end - pos) >= count) { // the common case
106+
std::memcpy(dest, pos, count);
107+
pos += count;
108+
return;
109+
}
110+
readSlow(dest, count);
111+
}
112+
94113
/// Reads the rest of the body (at most `limit` bytes). Never throws because of a failed transfer.
95114
std::string readAll(std::size_t limit);
96115

97116
private:
98-
// std::streambuf, std::istream and std::ios all define these.
99-
using traits_type = std::streambuf::traits_type;
100-
using int_type = std::streambuf::int_type;
101-
102-
int_type underflow() override;
117+
/// Makes the next part of the body available in [pos, end). Returns false at the end of the
118+
/// body, throws if the body cannot be read. Must only be called when [pos, end) is empty: it
119+
/// may move or overwrite the data that the window pointed to.
120+
bool fill();
121+
void readSlow(char * dest, std::size_t count);
103122

104123
void pump();
105124
void finish();
@@ -126,6 +145,9 @@ class HttpResponse
126145
std::string failed_tail; // undelivered (decoded) body bytes, if the transfer failed
127146
std::string unsupported_encoding;
128147

148+
const char * pos = nullptr; // the part of the body that can be read now, without waiting
149+
const char * end = nullptr;
150+
129151
std::string raw; // received body bytes, possibly compressed
130152
std::size_t raw_pos = 0; // first byte of `raw` not yet handed over to the decoder/reader
131153
std::size_t raw_total = 0;

‎driver/result_set.cpp‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ void ColumnInfo::updateTypeInfo() {
122122
}
123123
}
124124

125-
ResultSet::ResultSet(AmortizedIStreamReader & str, std::unique_ptr<ResultMutator> && mutator)
125+
ResultSet::ResultSet(HttpResponse & str, std::unique_ptr<ResultMutator> && mutator)
126126
: stream(str)
127127
, result_mutator(std::move(mutator))
128128
, string_pool(1000000)
@@ -303,11 +303,11 @@ void ResultSet::retireRow(Row && row) {
303303

304304
ResultReader::ResultReader(
305305
const std::string & timezone_,
306-
std::istream & raw_stream,
306+
HttpResponse & stream_,
307307
std::unique_ptr<ResultMutator> && mutator
308308
)
309309
: timezone(timezone_)
310-
, stream(raw_stream)
310+
, stream(stream_)
311311
, result_mutator(std::move(mutator))
312312
{
313313
}
@@ -330,17 +330,17 @@ std::unique_ptr<ResultMutator> ResultReader::releaseMutator() {
330330
std::unique_ptr<ResultReader> make_result_reader(
331331
const std::string & format,
332332
const std::string & timezone,
333-
std::istream & raw_stream,
333+
HttpResponse & stream,
334334
std::unique_ptr<ResultMutator> && mutator
335335
) {
336336
if (format == "ODBCDriver2") {
337-
return std::make_unique<ODBCDriver2ResultReader>(timezone, raw_stream, std::move(mutator));
337+
return std::make_unique<ODBCDriver2ResultReader>(timezone, stream, std::move(mutator));
338338
}
339339
else if (format == "RowBinaryWithNamesAndTypes") {
340340
if (!isLittleEndian())
341341
throw std::runtime_error("'" + format + "' format is supported only on little-endian platforms");
342342

343-
return std::make_unique<RowBinaryWithNamesAndTypesResultReader>(timezone, raw_stream, std::move(mutator));
343+
return std::make_unique<RowBinaryWithNamesAndTypesResultReader>(timezone, stream, std::move(mutator));
344344
}
345345

346346
throw std::runtime_error("'" + format + "' format is not supported");

‎driver/result_set.h‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#pragma once
22

33
#include "driver/platform/platform.h"
4+
#include "driver/http_client.h"
45
#include "driver/utils/utils.h"
56
#include "driver/utils/object_pool.h"
6-
#include "driver/utils/amortized_istream_reader.h"
77
#include "driver/utils/type_parser.h"
88
#include "driver/utils/type_info.h"
99

@@ -93,7 +93,7 @@ class ResultMutator {
9393

9494
class ResultSet {
9595
public:
96-
explicit ResultSet(AmortizedIStreamReader & str, std::unique_ptr<ResultMutator> && mutator);
96+
explicit ResultSet(HttpResponse & str, std::unique_ptr<ResultMutator> && mutator);
9797

9898
virtual ~ResultSet();
9999

@@ -119,7 +119,7 @@ class ResultSet {
119119
virtual bool readNextRow(Row & row) = 0;
120120

121121
protected:
122-
AmortizedIStreamReader & stream;
122+
HttpResponse & stream;
123123
std::unique_ptr<ResultMutator> result_mutator;
124124
DefaultConversionContext conversion_context;
125125
std::vector<ColumnInfo> columns_info;
@@ -137,7 +137,7 @@ class ResultReader {
137137
protected:
138138
explicit ResultReader(
139139
const std::string & timezone_,
140-
std::istream & stream,
140+
HttpResponse & stream,
141141
std::unique_ptr<ResultMutator> && mutator
142142
);
143143

@@ -153,15 +153,15 @@ class ResultReader {
153153

154154
protected:
155155
const std::string timezone;
156-
AmortizedIStreamReader stream;
156+
HttpResponse & stream;
157157
std::unique_ptr<ResultMutator> result_mutator;
158158
std::unique_ptr<ResultSet> result_set;
159159
};
160160

161161
std::unique_ptr<ResultReader> make_result_reader(
162162
const std::string & format,
163163
const std::string & timezone,
164-
std::istream & raw_stream,
164+
HttpResponse & stream,
165165
std::unique_ptr<ResultMutator> && mutator
166166
);
167167

0 commit comments

Comments
 (0)