quasardb 3.14.2.dev1__cp311-cp311-macosx_11_0_arm64.whl → 3.14.2.dev2__cp311-cp311-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of quasardb might be problematic. Click here for more details.
- quasardb/CMakeFiles/CMakeDirectoryInformation.cmake +1 -1
- quasardb/CMakeLists.txt +19 -12
- quasardb/Makefile +10 -10
- quasardb/__init__.py +33 -4
- quasardb/cluster.cpp +7 -1
- quasardb/cluster.hpp +111 -72
- quasardb/concepts.hpp +56 -12
- quasardb/continuous.cpp +84 -34
- quasardb/continuous.hpp +10 -7
- quasardb/convert/array.hpp +23 -6
- quasardb/convert/value.hpp +78 -7
- quasardb/date/CMakeFiles/CMakeDirectoryInformation.cmake +1 -1
- quasardb/date/CMakeFiles/Export/a52b05f964b070ee926bcad51d3288af/dateTargets.cmake +12 -12
- quasardb/date/Makefile +10 -10
- quasardb/date/dateConfigVersion.cmake +9 -2
- quasardb/date/dateTargets.cmake +3 -7
- quasardb/detail/invoke.hpp +0 -0
- quasardb/detail/retry.cpp +30 -0
- quasardb/detail/retry.hpp +147 -0
- quasardb/detail/sleep.hpp +53 -0
- quasardb/{writer.cpp → detail/writer.cpp} +68 -162
- quasardb/detail/writer.hpp +550 -0
- quasardb/error.hpp +76 -1
- quasardb/libqdb_api.dylib +0 -0
- quasardb/masked_array.hpp +9 -2
- quasardb/module.cpp +20 -4
- quasardb/numpy/__init__.py +58 -10
- quasardb/object_tracker.hpp +2 -3
- quasardb/options.hpp +32 -3
- quasardb/pandas/__init__.py +59 -102
- quasardb/properties.cpp +41 -0
- quasardb/properties.hpp +85 -0
- quasardb/pybind11/CMakeFiles/CMakeDirectoryInformation.cmake +1 -1
- quasardb/pybind11/Makefile +10 -10
- quasardb/quasardb.cpython-311-darwin.so +0 -0
- quasardb/range-v3/CMakeFiles/CMakeDirectoryInformation.cmake +1 -1
- quasardb/range-v3/CMakeFiles/Export/d94ef200eca10a819b5858b33e808f5b/range-v3-targets.cmake +12 -12
- quasardb/range-v3/CMakeFiles/range.v3.headers.dir/DependInfo.cmake +6 -2
- quasardb/range-v3/CMakeFiles/range.v3.headers.dir/build.make +2 -2
- quasardb/range-v3/Makefile +10 -10
- quasardb/range-v3/cmake_install.cmake +12 -0
- quasardb/range-v3/range-v3-config-version.cmake +9 -2
- quasardb/range-v3/range-v3-config.cmake +3 -7
- quasardb/reader.cpp +282 -0
- quasardb/reader.hpp +256 -0
- quasardb/table.cpp +4 -36
- quasardb/table.hpp +69 -28
- quasardb/traits.hpp +23 -0
- quasardb/writer.hpp +245 -287
- {quasardb-3.14.2.dev1.dist-info → quasardb-3.14.2.dev2.dist-info}/METADATA +7 -7
- {quasardb-3.14.2.dev1.dist-info → quasardb-3.14.2.dev2.dist-info}/RECORD +54 -48
- {quasardb-3.14.2.dev1.dist-info → quasardb-3.14.2.dev2.dist-info}/WHEEL +1 -1
- quasardb/reader/ts_row.hpp +0 -281
- quasardb/reader/ts_value.hpp +0 -245
- quasardb/table_reader.hpp +0 -220
- {quasardb-3.14.2.dev1.dist-info → quasardb-3.14.2.dev2.dist-info}/LICENSE.md +0 -0
- {quasardb-3.14.2.dev1.dist-info → quasardb-3.14.2.dev2.dist-info}/top_level.txt +0 -0
quasardb/writer.hpp
CHANGED
|
@@ -30,349 +30,262 @@
|
|
|
30
30
|
*/
|
|
31
31
|
#pragma once
|
|
32
32
|
|
|
33
|
-
#include "
|
|
34
|
-
#include "dispatch.hpp"
|
|
33
|
+
#include "concepts.hpp"
|
|
35
34
|
#include "error.hpp"
|
|
36
35
|
#include "logger.hpp"
|
|
36
|
+
#include "metrics.hpp"
|
|
37
37
|
#include "object_tracker.hpp"
|
|
38
|
-
#include "
|
|
39
|
-
#include "utils.hpp"
|
|
40
|
-
#include <variant>
|
|
38
|
+
#include "detail/writer.hpp"
|
|
41
39
|
#include <vector>
|
|
42
40
|
|
|
43
41
|
namespace qdb
|
|
44
42
|
{
|
|
45
|
-
|
|
46
|
-
namespace detail
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
using deduplicate = std::variant<std::vector<std::string>, bool>;
|
|
50
|
-
|
|
51
|
-
enum deduplication_mode_t
|
|
52
|
-
{
|
|
53
|
-
deduplication_mode_drop,
|
|
54
|
-
deduplication_mode_upsert
|
|
55
|
-
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
constexpr inline qdb_exp_batch_push_options_t to_push_options(enum detail::deduplication_mode_t mode)
|
|
59
|
-
{
|
|
60
|
-
switch (mode)
|
|
61
|
-
{
|
|
62
|
-
case deduplication_mode_drop:
|
|
63
|
-
return qdb_exp_batch_option_unique_drop;
|
|
64
|
-
case deduplication_mode_upsert:
|
|
65
|
-
return qdb_exp_batch_option_unique_upsert;
|
|
66
|
-
default:
|
|
67
|
-
return qdb_exp_batch_option_standard;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
struct deduplicate_options
|
|
72
|
-
{
|
|
73
|
-
detail::deduplicate columns_;
|
|
74
|
-
deduplication_mode_t mode_;
|
|
75
|
-
|
|
76
|
-
deduplicate_options()
|
|
77
|
-
{
|
|
78
|
-
columns_ = false;
|
|
79
|
-
mode_ = deduplication_mode_drop;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
deduplicate_options(deduplication_mode_t mode, detail::deduplicate columns)
|
|
83
|
-
: columns_{columns}
|
|
84
|
-
, mode_{mode} {};
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
using int64_column = std::vector<qdb_int_t>;
|
|
88
|
-
using double_column = std::vector<double>;
|
|
89
|
-
using timestamp_column = std::vector<qdb_timespec_t>;
|
|
90
|
-
using blob_column = std::vector<qdb_blob_t>;
|
|
91
|
-
using string_column = std::vector<qdb_string_t>;
|
|
92
|
-
|
|
93
|
-
using any_column =
|
|
94
|
-
std::variant<int64_column, double_column, timestamp_column, blob_column, string_column>;
|
|
95
|
-
|
|
96
|
-
template <qdb_ts_column_type_t T>
|
|
97
|
-
struct column_of_type;
|
|
98
|
-
|
|
99
|
-
template <qdb_ts_column_type_t T>
|
|
100
|
-
struct make_column;
|
|
101
|
-
|
|
102
|
-
#define COLUMN_OF_TYPE_DECL(TYPE, COLUMN) \
|
|
103
|
-
template <> \
|
|
104
|
-
struct column_of_type<TYPE> \
|
|
105
|
-
{ \
|
|
106
|
-
using value_type = COLUMN; \
|
|
107
|
-
}; \
|
|
108
|
-
\
|
|
109
|
-
template <> \
|
|
110
|
-
struct make_column<TYPE> \
|
|
111
|
-
{ \
|
|
112
|
-
any_column inline operator()() \
|
|
113
|
-
{ \
|
|
114
|
-
return COLUMN{}; \
|
|
115
|
-
}; \
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
COLUMN_OF_TYPE_DECL(qdb_ts_column_int64, int64_column);
|
|
119
|
-
COLUMN_OF_TYPE_DECL(qdb_ts_column_double, double_column);
|
|
120
|
-
COLUMN_OF_TYPE_DECL(qdb_ts_column_timestamp, timestamp_column);
|
|
121
|
-
COLUMN_OF_TYPE_DECL(qdb_ts_column_blob, blob_column);
|
|
122
|
-
COLUMN_OF_TYPE_DECL(qdb_ts_column_string, string_column);
|
|
123
|
-
COLUMN_OF_TYPE_DECL(qdb_ts_column_symbol, string_column);
|
|
124
|
-
|
|
125
|
-
#undef COLUMN_OF_TYPE_DECL
|
|
126
|
-
|
|
127
|
-
template <qdb_ts_column_type_t T>
|
|
128
|
-
std::vector<typename traits::qdb_column<T>::value_type> & access_column(
|
|
129
|
-
std::vector<any_column> & columns, size_t index)
|
|
130
|
-
{
|
|
131
|
-
using column_type = typename column_of_type<T>::value_type;
|
|
132
|
-
try
|
|
133
|
-
{
|
|
134
|
-
return std::get<column_type>(columns[index]);
|
|
135
|
-
}
|
|
136
|
-
catch (std::bad_variant_access const & /*e*/)
|
|
137
|
-
{
|
|
138
|
-
throw qdb::incompatible_type_exception{};
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
template <qdb_ts_column_type_t T>
|
|
143
|
-
std::size_t column_size(std::vector<any_column> & columns, size_t index)
|
|
43
|
+
class writer
|
|
144
44
|
{
|
|
145
|
-
return access_column<T>(columns, index).size();
|
|
146
|
-
}
|
|
147
45
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
std::get<value_type>(xs).clear();
|
|
155
|
-
}
|
|
156
|
-
};
|
|
46
|
+
using int64_column = detail::int64_column;
|
|
47
|
+
using double_column = detail::double_column;
|
|
48
|
+
using timestamp_column = detail::timestamp_column;
|
|
49
|
+
using blob_column = detail::blob_column;
|
|
50
|
+
using string_column = detail::string_column;
|
|
51
|
+
using any_column = detail::any_column;
|
|
157
52
|
|
|
158
|
-
class staged_table
|
|
159
|
-
{
|
|
160
53
|
public:
|
|
161
|
-
|
|
54
|
+
public:
|
|
55
|
+
writer(qdb::handle_ptr h)
|
|
162
56
|
: _logger("quasardb.writer")
|
|
163
|
-
,
|
|
164
|
-
{
|
|
165
|
-
_column_infos = table.list_columns();
|
|
166
|
-
|
|
167
|
-
std::transform(std::cbegin(_column_infos), std::cend(_column_infos),
|
|
168
|
-
std::back_inserter(_columns),
|
|
169
|
-
|
|
170
|
-
[](auto const & col) { return dispatch::by_column_type<detail::make_column>(col.type); });
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
~staged_table()
|
|
174
|
-
{
|
|
175
|
-
clear();
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
void set_index(py::array const & timestamps);
|
|
179
|
-
void set_blob_column(std::size_t index, const masked_array & xs);
|
|
180
|
-
void set_string_column(std::size_t index, const masked_array & xs);
|
|
181
|
-
void set_double_column(std::size_t index, masked_array_t<traits::float64_dtype> const & xs);
|
|
182
|
-
void set_int64_column(std::size_t index, masked_array_t<traits::int64_dtype> const & xs);
|
|
183
|
-
void set_timestamp_column(
|
|
184
|
-
std::size_t index, masked_array_t<traits::datetime64_ns_dtype> const & xs);
|
|
57
|
+
, _handle{h}
|
|
58
|
+
{}
|
|
185
59
|
|
|
186
|
-
|
|
60
|
+
// prevent copy because of the table object, use a unique_ptr of the batch in cluster
|
|
61
|
+
// to return the object
|
|
62
|
+
writer(const writer &) = delete;
|
|
187
63
|
|
|
188
|
-
|
|
64
|
+
~writer()
|
|
65
|
+
{}
|
|
189
66
|
|
|
190
|
-
|
|
191
|
-
detail::deduplicate_options const & deduplicate_options,
|
|
192
|
-
qdb_ts_range_t * ranges,
|
|
193
|
-
qdb_exp_batch_push_table_t & batch);
|
|
67
|
+
const std::vector<qdb_exp_batch_push_column_t> & prepare_columns();
|
|
194
68
|
|
|
195
|
-
|
|
196
|
-
|
|
69
|
+
template < //
|
|
70
|
+
qdb::concepts::writer_push_strategy PushStrategy, //
|
|
71
|
+
qdb::concepts::sleep_strategy SleepStrategy> //
|
|
72
|
+
void push(detail::writer_data const & data, py::kwargs kwargs)
|
|
197
73
|
{
|
|
198
|
-
|
|
199
|
-
}
|
|
74
|
+
qdb::object_tracker::scoped_capture capture{_object_tracker};
|
|
200
75
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
[](std::string const & column) -> qdb_string_t {
|
|
209
|
-
// Note, we're not copying any strings here. This works,
|
|
210
|
-
// because we pass the vector by reference, and it is "owned"
|
|
211
|
-
// by _push_impl, so the lifetime is longer.
|
|
212
|
-
return qdb_string_t{column.c_str(), column.size()};
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
out.options = detail::to_push_options(mode);
|
|
216
|
-
out.where_duplicate = where_duplicate.release();
|
|
217
|
-
out.where_duplicate_count = columns.size();
|
|
76
|
+
// We always want to have a push mode at this point
|
|
77
|
+
kwargs = detail::batch_push_mode::ensure(kwargs);
|
|
78
|
+
|
|
79
|
+
_push_impl<PushStrategy, SleepStrategy>( //
|
|
80
|
+
detail::staged_tables::index(data), //
|
|
81
|
+
kwargs //
|
|
82
|
+
); //
|
|
218
83
|
}
|
|
219
84
|
|
|
220
|
-
|
|
85
|
+
template < //
|
|
86
|
+
qdb::concepts::writer_push_strategy PushStrategy, //
|
|
87
|
+
qdb::concepts::sleep_strategy SleepStrategy> //
|
|
88
|
+
void push_async(detail::writer_data const & data, py::kwargs kwargs)
|
|
221
89
|
{
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
{
|
|
225
|
-
dispatch::by_column_type<detail::clear_column>(_column_infos[index].type, _columns[index]);
|
|
226
|
-
}
|
|
90
|
+
_logger.warn("writer.push_async() is deprecated, please invoke writer.push() directly and "
|
|
91
|
+
"provide the push mode as a kwarg");
|
|
227
92
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
_columns_data.clear();
|
|
93
|
+
return push<PushStrategy, SleepStrategy>(
|
|
94
|
+
data, detail::batch_push_mode::set(kwargs, qdb_exp_batch_push_async));
|
|
231
95
|
}
|
|
232
96
|
|
|
233
|
-
|
|
97
|
+
template < //
|
|
98
|
+
qdb::concepts::writer_push_strategy PushStrategy, //
|
|
99
|
+
qdb::concepts::sleep_strategy SleepStrategy> //
|
|
100
|
+
void push_fast(detail::writer_data const & data, py::kwargs kwargs)
|
|
234
101
|
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
tr.end.tv_nsec++;
|
|
241
|
-
|
|
242
|
-
return tr;
|
|
102
|
+
_logger.warn("writer.push_fast() is deprecated, please invoke writer.push() directly and "
|
|
103
|
+
"provide the push mode as a kwarg");
|
|
104
|
+
|
|
105
|
+
return push<PushStrategy, SleepStrategy>(
|
|
106
|
+
data, detail::batch_push_mode::set(kwargs, qdb_exp_batch_push_fast));
|
|
243
107
|
}
|
|
244
108
|
|
|
245
|
-
|
|
109
|
+
template < //
|
|
110
|
+
qdb::concepts::writer_push_strategy PushStrategy, //
|
|
111
|
+
qdb::concepts::sleep_strategy SleepStrategy> //
|
|
112
|
+
void push_truncate(detail::writer_data const & data, py::kwargs kwargs)
|
|
246
113
|
{
|
|
247
|
-
|
|
114
|
+
_logger.warn("writer.push_fast() is deprecated, please invoke writer.push() directly and "
|
|
115
|
+
"provide the push mode as a kwarg");
|
|
116
|
+
|
|
117
|
+
return push<PushStrategy, SleepStrategy>(
|
|
118
|
+
data, detail::batch_push_mode::set(kwargs, qdb_exp_batch_push_truncate));
|
|
119
|
+
|
|
120
|
+
// qdb::object_tracker::scoped_capture capture{_object_tracker};
|
|
121
|
+
// auto idx = detail::staged_tables::index(data);
|
|
122
|
+
|
|
123
|
+
// // As we are actively removing data, let's add an additional check to ensure the user
|
|
124
|
+
// // doesn't accidentally truncate his whole database without inserting anything.
|
|
125
|
+
// if (data.empty()) [[unlikely]]
|
|
126
|
+
// {
|
|
127
|
+
// throw qdb::invalid_argument_exception{
|
|
128
|
+
// "Writer is empty: you did not provide any rows to push."};
|
|
129
|
+
// }
|
|
130
|
+
|
|
131
|
+
// qdb_ts_range_t tr;
|
|
132
|
+
|
|
133
|
+
// if (kwargs.contains("range"))
|
|
134
|
+
// {
|
|
135
|
+
// tr = convert::value<py::tuple, qdb_ts_range_t>(py::cast<py::tuple>(kwargs["range"]));
|
|
136
|
+
// }
|
|
137
|
+
// else
|
|
138
|
+
// {
|
|
139
|
+
// // TODO(leon): support multiple tables for push truncate
|
|
140
|
+
// if (idx.size() != 1) [[unlikely]]
|
|
141
|
+
// {
|
|
142
|
+
// throw qdb::invalid_argument_exception{"Writer push truncate only supports a single "
|
|
143
|
+
// "table unless an explicit range is provided:
|
|
144
|
+
// you " "provided more than one table without" "
|
|
145
|
+
// an explicit range."};
|
|
146
|
+
// }
|
|
147
|
+
|
|
148
|
+
// detail::staged_table const & staged_table = idx.first();
|
|
149
|
+
// tr = staged_table.time_range();
|
|
150
|
+
// }
|
|
151
|
+
|
|
152
|
+
// const qdb_exp_batch_options_t options = {
|
|
153
|
+
// .mode = qdb_exp_batch_push_truncate, //
|
|
154
|
+
// .push_flags = detail::batch_push_flags::from_kwargs(kwargs) //
|
|
155
|
+
// };
|
|
156
|
+
|
|
157
|
+
// _push_impl<PushStrategy, SleepStrategy>( //
|
|
158
|
+
// std::move(idx), //
|
|
159
|
+
// options, //
|
|
160
|
+
// kwargs, //
|
|
161
|
+
// &tr //
|
|
162
|
+
// ); //
|
|
248
163
|
}
|
|
249
164
|
|
|
250
165
|
private:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
std::vector<any_column> _columns;
|
|
258
|
-
|
|
259
|
-
std::vector<qdb_exp_batch_push_column_t> _columns_data;
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
} // namespace detail
|
|
263
|
-
|
|
264
|
-
class writer
|
|
265
|
-
{
|
|
266
|
-
|
|
267
|
-
using int64_column = detail::int64_column;
|
|
268
|
-
using double_column = detail::double_column;
|
|
269
|
-
using timestamp_column = detail::timestamp_column;
|
|
270
|
-
using blob_column = detail::blob_column;
|
|
271
|
-
using string_column = detail::string_column;
|
|
272
|
-
using any_column = detail::any_column;
|
|
273
|
-
using staged_tables_t = std::map<std::string, detail::staged_table>;
|
|
274
|
-
|
|
275
|
-
public:
|
|
276
|
-
/**
|
|
277
|
-
* Convenience class that holds data that can be pushed to the writer. Makes it
|
|
278
|
-
* easier for the end-user to provide the data in the correct format, in a single
|
|
279
|
-
* function call, if they decide to use the low-level writer API themselves.
|
|
280
|
-
*/
|
|
281
|
-
class data
|
|
166
|
+
template < //
|
|
167
|
+
concepts::writer_push_strategy PushStrategy, //
|
|
168
|
+
concepts::sleep_strategy SleepStrategy> //
|
|
169
|
+
void _push_impl( //
|
|
170
|
+
detail::staged_tables && idx, //
|
|
171
|
+
py::kwargs kwargs) //
|
|
282
172
|
{
|
|
283
|
-
|
|
173
|
+
_handle->check_open();
|
|
284
174
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
{
|
|
288
|
-
qdb::table table;
|
|
289
|
-
py::array index;
|
|
290
|
-
py::list column_data;
|
|
291
|
-
};
|
|
175
|
+
// Ensure some default variables that are set
|
|
176
|
+
kwargs = detail::batch_push_flags::ensure(kwargs);
|
|
292
177
|
|
|
293
|
-
|
|
294
|
-
void append(qdb::table const & table, py::handle const & index, py::list const & column_data);
|
|
178
|
+
std::vector<qdb_ts_range_t> truncate_ranges{};
|
|
295
179
|
|
|
296
|
-
|
|
180
|
+
if (detail::batch_push_mode::from_kwargs(kwargs) == qdb_exp_batch_push_truncate)
|
|
181
|
+
[[unlikely]] // Unlikely because truncate isn't used much
|
|
297
182
|
{
|
|
298
|
-
|
|
183
|
+
kwargs = detail::batch_truncate_ranges::ensure(kwargs, idx);
|
|
184
|
+
truncate_ranges = detail::batch_truncate_ranges::from_kwargs(kwargs);
|
|
299
185
|
}
|
|
300
186
|
|
|
301
|
-
|
|
302
|
-
{
|
|
303
|
-
assert(empty() == false);
|
|
187
|
+
qdb_exp_batch_options_t options = detail::batch_options::from_kwargs(kwargs);
|
|
304
188
|
|
|
305
|
-
|
|
189
|
+
if (idx.empty()) [[unlikely]]
|
|
190
|
+
{
|
|
191
|
+
throw qdb::invalid_argument_exception{"No data written to batch writer."};
|
|
306
192
|
}
|
|
307
193
|
|
|
308
|
-
|
|
309
|
-
{
|
|
310
|
-
assert(empty() == false);
|
|
194
|
+
auto deduplicate_options = detail::deduplicate_options::from_kwargs(kwargs);
|
|
311
195
|
|
|
312
|
-
|
|
313
|
-
|
|
196
|
+
std::vector<qdb_exp_batch_push_table_t> batch;
|
|
197
|
+
batch.assign(idx.size(), qdb_exp_batch_push_table_t());
|
|
314
198
|
|
|
315
|
-
|
|
199
|
+
qdb_ts_range_t * truncate_ranges_{nullptr};
|
|
200
|
+
if (truncate_ranges.empty() == false) [[unlikely]]
|
|
316
201
|
{
|
|
317
|
-
|
|
202
|
+
truncate_ranges_ = truncate_ranges.data();
|
|
318
203
|
}
|
|
319
204
|
|
|
320
|
-
|
|
321
|
-
std::vector<value_type> xs_;
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
public:
|
|
325
|
-
writer(qdb::handle_ptr h)
|
|
326
|
-
: _logger("quasardb.writer")
|
|
327
|
-
, _handle{h}
|
|
328
|
-
{}
|
|
329
|
-
|
|
330
|
-
// prevent copy because of the table object, use a unique_ptr of the batch in cluster
|
|
331
|
-
// to return the object
|
|
332
|
-
writer(const writer &) = delete;
|
|
333
|
-
|
|
334
|
-
~writer()
|
|
335
|
-
{}
|
|
205
|
+
int cur = 0;
|
|
336
206
|
|
|
337
|
-
|
|
207
|
+
for (auto pos = idx.begin(); pos != idx.end(); ++pos)
|
|
208
|
+
{
|
|
209
|
+
std::string const & table_name = pos->first;
|
|
210
|
+
detail::staged_table & staged_table = pos->second;
|
|
211
|
+
auto & batch_table = batch.at(cur++);
|
|
212
|
+
|
|
213
|
+
staged_table.prepare_batch( //
|
|
214
|
+
options.mode, //
|
|
215
|
+
deduplicate_options, //
|
|
216
|
+
truncate_ranges_, //
|
|
217
|
+
batch_table);
|
|
218
|
+
|
|
219
|
+
if (batch_table.data.column_count == 0) [[unlikely]]
|
|
220
|
+
{
|
|
221
|
+
throw qdb::invalid_argument_exception{
|
|
222
|
+
"Writer is empty: you did not provide any columns to push."};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
_logger.debug("Pushing %d rows with %d columns in %s", batch_table.data.row_count,
|
|
226
|
+
batch_table.data.column_count, table_name);
|
|
227
|
+
}
|
|
338
228
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
229
|
+
_do_push<PushStrategy, SleepStrategy>( //
|
|
230
|
+
options, //
|
|
231
|
+
batch, //
|
|
232
|
+
PushStrategy::from_kwargs(kwargs), //
|
|
233
|
+
detail::retry_options::from_kwargs(kwargs) //
|
|
234
|
+
); //
|
|
235
|
+
}
|
|
343
236
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
237
|
+
template < //
|
|
238
|
+
concepts::writer_push_strategy PushStrategy, //
|
|
239
|
+
concepts::sleep_strategy SleepStrategy> //
|
|
240
|
+
void _do_push(qdb_exp_batch_options_t const & options,
|
|
241
|
+
std::vector<qdb_exp_batch_push_table_t> const & batch,
|
|
242
|
+
PushStrategy push_strategy,
|
|
243
|
+
detail::retry_options const & retry_options)
|
|
347
244
|
{
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
auto pos = staged_tables.lower_bound(table_name);
|
|
245
|
+
qdb_error_t err{qdb_e_ok};
|
|
351
246
|
|
|
352
|
-
// XXX(leon): can be optimized by using lower_bound and reusing the `pos` for insertion into
|
|
353
|
-
// the correct place.
|
|
354
|
-
if (pos == staged_tables.end() || pos->first != table_name) [[unlikely]]
|
|
355
247
|
{
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
248
|
+
// Make sure to measure the time it takes to do the actual push.
|
|
249
|
+
// This is in its own scoped block so that we only actually measure
|
|
250
|
+
// the push time, not e.g. retry time.
|
|
251
|
+
qdb::metrics::scoped_capture capture{"qdb_batch_push"};
|
|
252
|
+
|
|
253
|
+
err = push_strategy( //
|
|
254
|
+
*_handle, //
|
|
255
|
+
&options, //
|
|
256
|
+
batch.data(), //
|
|
257
|
+
nullptr, //
|
|
258
|
+
batch.size()); //
|
|
359
259
|
}
|
|
360
260
|
|
|
361
|
-
|
|
362
|
-
|
|
261
|
+
if (retry_options.should_retry(err))
|
|
262
|
+
[[unlikely]] // Unlikely, because err is most likely to be qdb_e_ok
|
|
263
|
+
{
|
|
264
|
+
if (err == qdb_e_async_pipe_full) [[likely]]
|
|
265
|
+
{
|
|
266
|
+
_logger.info("Async pipelines are currently full");
|
|
267
|
+
}
|
|
268
|
+
else
|
|
269
|
+
{
|
|
270
|
+
_logger.warn("A temporary error occurred");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
std::chrono::milliseconds delay = retry_options.delay;
|
|
274
|
+
_logger.info("Sleeping for %d milliseconds", delay.count());
|
|
275
|
+
|
|
276
|
+
SleepStrategy::sleep(delay);
|
|
277
|
+
|
|
278
|
+
// Now try again -- easier way to go about this is to enter recursion. Note how
|
|
279
|
+
// we permutate the retry_options, which automatically adjusts the amount of retries
|
|
280
|
+
// left and the next sleep duration.
|
|
281
|
+
_logger.warn("Retrying push operation, retries left: %d", retry_options.retries_left);
|
|
282
|
+
return _do_push<PushStrategy, SleepStrategy>(
|
|
283
|
+
options, batch, push_strategy, retry_options.next());
|
|
284
|
+
}
|
|
363
285
|
|
|
364
|
-
|
|
286
|
+
qdb::qdb_throw_if_error(*_handle, err);
|
|
365
287
|
}
|
|
366
288
|
|
|
367
|
-
static staged_tables_t _stage_tables(writer::data const & data);
|
|
368
|
-
|
|
369
|
-
void _push_impl(staged_tables_t & staged_tables,
|
|
370
|
-
qdb_exp_batch_push_mode_t mode,
|
|
371
|
-
detail::deduplicate_options deduplicate_options,
|
|
372
|
-
qdb_ts_range_t * ranges = nullptr);
|
|
373
|
-
|
|
374
|
-
detail::deduplicate_options _deduplicate_from_args(py::kwargs args);
|
|
375
|
-
|
|
376
289
|
private:
|
|
377
290
|
qdb::logger _logger;
|
|
378
291
|
qdb::handle_ptr _handle;
|
|
@@ -391,6 +304,51 @@ public:
|
|
|
391
304
|
// behavior
|
|
392
305
|
using writer_ptr = std::unique_ptr<writer>;
|
|
393
306
|
|
|
394
|
-
|
|
307
|
+
template < //
|
|
308
|
+
qdb::concepts::writer_push_strategy PushStrategy, //
|
|
309
|
+
qdb::concepts::sleep_strategy SleepStrategy> //
|
|
310
|
+
static void register_writer(py::module_ & m)
|
|
311
|
+
{
|
|
312
|
+
using PS = PushStrategy;
|
|
313
|
+
using SS = SleepStrategy;
|
|
314
|
+
|
|
315
|
+
namespace py = pybind11;
|
|
316
|
+
|
|
317
|
+
// Writer data
|
|
318
|
+
auto writer_data_c = py::class_<qdb::detail::writer_data>{m, "WriterData"};
|
|
319
|
+
writer_data_c.def(py::init())
|
|
320
|
+
.def("append", &qdb::detail::writer_data::append, py::arg("table"), py::arg("index"),
|
|
321
|
+
py::arg("column_data"), "Append new data")
|
|
322
|
+
.def("empty", &qdb::detail::writer_data::empty, "Returns true if underlying data is empty");
|
|
323
|
+
|
|
324
|
+
// Different push modes, makes it easy to convert to<>from native types, as we accept the push
|
|
325
|
+
// mode as a kwarg.
|
|
326
|
+
py::enum_<qdb_exp_batch_push_mode_t>{m, "WriterPushMode", py::arithmetic(), "Push Mode"} //
|
|
327
|
+
.value("Transactional", qdb_exp_batch_push_transactional) //
|
|
328
|
+
.value("Fast", qdb_exp_batch_push_fast) //
|
|
329
|
+
.value("Truncate", qdb_exp_batch_push_truncate) //
|
|
330
|
+
.value("Async", qdb_exp_batch_push_fast); //
|
|
331
|
+
|
|
332
|
+
// And the actual pinned writer
|
|
333
|
+
auto writer_c = py::class_<qdb::writer>{m, "Writer"};
|
|
334
|
+
|
|
335
|
+
// basic interface
|
|
336
|
+
writer_c.def(py::init<qdb::handle_ptr>()); //
|
|
337
|
+
|
|
338
|
+
writer_c.def_readwrite("_legacy_state", &qdb::writer::legacy_state_);
|
|
339
|
+
|
|
340
|
+
// push functions
|
|
341
|
+
writer_c
|
|
342
|
+
.def("push", &qdb::writer::push<PS, SS>, "Regular batch push") //
|
|
343
|
+
.def("push_async", &qdb::writer::push_async<PS, SS>,
|
|
344
|
+
"Asynchronous batch push that buffers data inside the QuasarDB daemon") //
|
|
345
|
+
.def("push_fast", &qdb::writer::push_fast<PS, SS>,
|
|
346
|
+
"Fast, in-place batch push that is efficient when doing lots of small, incremental "
|
|
347
|
+
"pushes.") //
|
|
348
|
+
.def("push_truncate", &qdb::writer::push_truncate<PS, SS>,
|
|
349
|
+
"Before inserting data, truncates any existing data. This is useful when you want your "
|
|
350
|
+
"insertions to be idempotent, e.g. in "
|
|
351
|
+
"case of a retry.");
|
|
352
|
+
}
|
|
395
353
|
|
|
396
354
|
} // namespace qdb
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: quasardb
|
|
3
|
-
Version: 3.14.2.
|
|
3
|
+
Version: 3.14.2.dev2
|
|
4
4
|
Summary: Python API for quasardb
|
|
5
5
|
Home-page: https://www.quasardb.net/
|
|
6
6
|
Author: quasardb SAS
|
|
@@ -14,22 +14,22 @@ Classifier: Intended Audience :: Information Technology
|
|
|
14
14
|
Classifier: Intended Audience :: Other Audience
|
|
15
15
|
Classifier: Intended Audience :: System Administrators
|
|
16
16
|
Classifier: Intended Audience :: Telecommunications Industry
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.7
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.8
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.9
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
23
|
Classifier: Topic :: Database
|
|
24
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
25
|
Classifier: License :: OSI Approved :: BSD License
|
|
26
26
|
License-File: LICENSE.md
|
|
27
27
|
Requires-Dist: numpy
|
|
28
28
|
Provides-Extra: pandas
|
|
29
|
-
Requires-Dist: pandas
|
|
29
|
+
Requires-Dist: pandas; extra == "pandas"
|
|
30
30
|
Provides-Extra: tests
|
|
31
|
-
Requires-Dist: pytest
|
|
32
|
-
Requires-Dist: pytest-runner
|
|
33
|
-
Requires-Dist: pytest-benchmark
|
|
34
|
-
Requires-Dist: teamcity-messages
|
|
31
|
+
Requires-Dist: pytest; extra == "tests"
|
|
32
|
+
Requires-Dist: pytest-runner; extra == "tests"
|
|
33
|
+
Requires-Dist: pytest-benchmark; extra == "tests"
|
|
34
|
+
Requires-Dist: teamcity-messages; extra == "tests"
|
|
35
35
|
|