be/src/format_v2/table/adbc_reader.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include "format_v2/table/adbc_reader.h" |
19 | | |
20 | | #include <arrow-adbc/adbc.h> |
21 | | #include <arrow-adbc/adbc_driver_manager.h> |
22 | | #include <arrow/array/array_base.h> |
23 | | #include <arrow/c/bridge.h> |
24 | | #include <arrow/record_batch.h> |
25 | | |
26 | | #include <cstdint> |
27 | | #include <map> |
28 | | #include <memory> |
29 | | #include <string> |
30 | | #include <string_view> |
31 | | #include <utility> |
32 | | #include <vector> |
33 | | |
34 | | #include "common/cast_set.h" |
35 | | #include "common/check.h" |
36 | | #include "common/logging.h" |
37 | | #include "core/assert_cast.h" |
38 | | #include "core/block/block.h" |
39 | | #include "core/data_type/data_type.h" |
40 | | #include "core/data_type/data_type_array.h" |
41 | | #include "core/data_type/data_type_map.h" |
42 | | #include "core/data_type/data_type_nullable.h" |
43 | | #include "core/data_type/data_type_struct.h" |
44 | | #include "core/data_type/primitive_type.h" |
45 | | #include "core/data_type_serde/data_type_serde.h" |
46 | | #include "core/data_type_serde/data_type_variant_v2_serde.h" |
47 | | #include "format/arrow/arrow_array_normalizer.h" |
48 | | #include "format/parquet/arrow_memory_pool.h" |
49 | | #include "format_v2/column_mapper.h" |
50 | | #include "format_v2/materialized_reader_util.h" |
51 | | #include "runtime/descriptors.h" |
52 | | #include "runtime/exec_env.h" |
53 | | #include "runtime/file_scan_profile.h" |
54 | | #include "runtime/runtime_state.h" |
55 | | #include "util/adbc_driver_registry.h" |
56 | | #include "util/string_util.h" |
57 | | #include "util/timezone_utils.h" |
58 | | #include "util/url_coding.h" |
59 | | |
60 | | namespace doris::format::adbc { |
61 | | namespace { |
62 | | |
63 | | // Keys of TTableFormatFileDesc.adbc_params. Kept next to the code that reads them so the FE-side |
64 | | // producer and this consumer stay diffable. |
65 | | constexpr const char* kParamDriverPath = "driver_path"; |
66 | | constexpr const char* kParamDriverEntrypoint = "driver_entrypoint"; |
67 | | constexpr const char* kParamUri = "uri"; |
68 | | constexpr const char* kParamUsername = "username"; |
69 | | constexpr const char* kParamPassword = "password"; |
70 | | constexpr const char* kParamQuerySql = "query_sql"; |
71 | | // Base64 of one opaque partition descriptor the driver produced on FE. Mutually exclusive with |
72 | | // kParamQuerySql: a range either runs a statement here or reads one partition of a statement the |
73 | | // source has already run. |
74 | | constexpr const char* kParamPartitionDescriptor = "partition_descriptor"; |
75 | | // Anything under this prefix is an ADBC option name in full (the prefix is part of the option name, |
76 | | // e.g. "adbc.connection.autocommit") and is handed to the driver untouched. |
77 | | constexpr std::string_view kAdbcOptionPrefix = "adbc."; |
78 | | |
79 | | const std::string* find_param(const std::map<std::string, std::string>& params, |
80 | 141 | const std::string& key) { |
81 | 141 | const auto it = params.find(key); |
82 | 141 | return it == params.end() ? nullptr : &it->second; |
83 | 141 | } |
84 | | |
85 | 30 | Status validate_adbc_range(const TFileRangeDesc& range) { |
86 | 30 | if (!range.__isset.table_format_params || |
87 | 30 | range.table_format_params.table_format_type != "adbc") { |
88 | 0 | return Status::InvalidArgument("ADBC reader requires the adbc table format"); |
89 | 0 | } |
90 | 30 | if (!range.table_format_params.__isset.adbc_params) { |
91 | 1 | return Status::InvalidArgument("ADBC reader requires adbc_params"); |
92 | 1 | } |
93 | 29 | const auto& params = range.table_format_params.adbc_params; |
94 | 57 | for (const auto* key : {kParamDriverPath, kParamUri}) { |
95 | 57 | const auto* value = find_param(params, key); |
96 | 57 | if (value == nullptr || value->empty()) { |
97 | 2 | return Status::InvalidArgument("ADBC reader requires a non-empty '{}' parameter", key); |
98 | 2 | } |
99 | 57 | } |
100 | 27 | const auto* query_sql = find_param(params, kParamQuerySql); |
101 | 27 | const auto* partition = find_param(params, kParamPartitionDescriptor); |
102 | 27 | const bool has_query = query_sql != nullptr && !query_sql->empty(); |
103 | 27 | const bool has_partition = partition != nullptr && !partition->empty(); |
104 | | // Not a defensive nicety: reading a partition means the source has ALREADY run the statement, so |
105 | | // a range carrying both would let this reader run it a second time depending on which branch it |
106 | | // happened to take. FE refuses to build such a range; this refuses to act on one. |
107 | 27 | if (has_query == has_partition) { |
108 | 3 | return Status::InvalidArgument( |
109 | 3 | "ADBC reader requires exactly one of '{}' and '{}', but the range carries {}", |
110 | 3 | kParamQuerySql, kParamPartitionDescriptor, has_query ? "both" : "neither"); |
111 | 3 | } |
112 | 24 | return Status::OK(); |
113 | 27 | } |
114 | | |
115 | | Status arrow_type_mismatch(const std::string& path, const arrow::DataType& arrow_type, |
116 | 3 | const DataTypePtr& doris_type) { |
117 | 3 | return Status::InvalidArgument( |
118 | 3 | "ADBC Arrow type mismatch at '{}': runtime type '{}' does not match cached Doris " |
119 | 3 | "type '{}'", |
120 | 3 | path, arrow_type.ToString(), doris_type->get_name()); |
121 | 3 | } |
122 | | |
123 | 0 | int timestamp_scale(arrow::TimeUnit::type unit) { |
124 | 0 | switch (unit) { |
125 | 0 | case arrow::TimeUnit::SECOND: |
126 | 0 | return 0; |
127 | 0 | case arrow::TimeUnit::MILLI: |
128 | 0 | return 3; |
129 | 0 | case arrow::TimeUnit::MICRO: |
130 | 0 | case arrow::TimeUnit::NANO: |
131 | 0 | return 6; |
132 | 0 | } |
133 | 0 | return -1; |
134 | 0 | } |
135 | | |
136 | | Status validate_arrow_type(const arrow::DataType& arrow_type, const DataTypePtr& doris_type, |
137 | 19 | const std::string& path) { |
138 | 19 | DORIS_CHECK(doris_type != nullptr); |
139 | 19 | const auto nested_doris_type = remove_nullable(doris_type); |
140 | 19 | const auto primitive_type = nested_doris_type->get_primitive_type(); |
141 | 19 | const auto mismatch = [&]() { return arrow_type_mismatch(path, arrow_type, doris_type); }; |
142 | | |
143 | | // The FE maps the Variant extension to the V2 carrier, not to its binary storage fields. |
144 | 19 | if (primitive_type == TYPE_VARIANT) { |
145 | 1 | return DataTypeVariantV2SerDe::is_native_arrow_type(arrow_type) ? Status::OK() : mismatch(); |
146 | 1 | } |
147 | | |
148 | 18 | switch (arrow_type.id()) { |
149 | 0 | case arrow::Type::BOOL: |
150 | 0 | return primitive_type == TYPE_BOOLEAN ? Status::OK() : mismatch(); |
151 | 0 | case arrow::Type::INT8: |
152 | 0 | return primitive_type == TYPE_TINYINT ? Status::OK() : mismatch(); |
153 | 0 | case arrow::Type::UINT8: |
154 | 0 | return primitive_type == TYPE_SMALLINT ? Status::OK() : mismatch(); |
155 | 0 | case arrow::Type::INT16: |
156 | 0 | return primitive_type == TYPE_SMALLINT ? Status::OK() : mismatch(); |
157 | 0 | case arrow::Type::UINT16: |
158 | 0 | return primitive_type == TYPE_INT ? Status::OK() : mismatch(); |
159 | 3 | case arrow::Type::INT32: |
160 | 3 | return primitive_type == TYPE_INT ? Status::OK() : mismatch(); |
161 | 0 | case arrow::Type::UINT32: |
162 | 0 | return primitive_type == TYPE_BIGINT ? Status::OK() : mismatch(); |
163 | 3 | case arrow::Type::INT64: |
164 | 3 | return primitive_type == TYPE_BIGINT ? Status::OK() : mismatch(); |
165 | 0 | case arrow::Type::UINT64: |
166 | 0 | return primitive_type == TYPE_LARGEINT ? Status::OK() : mismatch(); |
167 | 0 | case arrow::Type::HALF_FLOAT: |
168 | 2 | case arrow::Type::FLOAT: |
169 | 2 | return primitive_type == TYPE_FLOAT ? Status::OK() : mismatch(); |
170 | 1 | case arrow::Type::DOUBLE: |
171 | 1 | return primitive_type == TYPE_DOUBLE ? Status::OK() : mismatch(); |
172 | 2 | case arrow::Type::STRING: |
173 | 2 | case arrow::Type::BINARY: |
174 | 2 | case arrow::Type::FIXED_SIZE_BINARY: |
175 | 2 | return primitive_type == TYPE_STRING ? Status::OK() : mismatch(); |
176 | 0 | case arrow::Type::DATE32: |
177 | 0 | return primitive_type == TYPE_DATEV2 ? Status::OK() : mismatch(); |
178 | 0 | case arrow::Type::DATE64: |
179 | 0 | return primitive_type == TYPE_DATETIMEV2 && nested_doris_type->get_scale() == 3 |
180 | 0 | ? Status::OK() |
181 | 0 | : mismatch(); |
182 | 0 | case arrow::Type::TIMESTAMP: { |
183 | 0 | const auto& timestamp = static_cast<const arrow::TimestampType&>(arrow_type); |
184 | 0 | const bool zoned = !timestamp.timezone().empty(); |
185 | 0 | const auto expected = zoned ? TYPE_TIMESTAMPTZ : TYPE_DATETIMEV2; |
186 | 0 | return primitive_type == expected && |
187 | 0 | nested_doris_type->get_scale() == timestamp_scale(timestamp.unit()) |
188 | 0 | ? Status::OK() |
189 | 0 | : mismatch(); |
190 | 2 | } |
191 | 0 | case arrow::Type::DECIMAL128: |
192 | 0 | case arrow::Type::DECIMAL256: { |
193 | 0 | const auto& decimal = static_cast<const arrow::DecimalType&>(arrow_type); |
194 | 0 | return is_decimal(primitive_type) && |
195 | 0 | nested_doris_type->get_precision() == decimal.precision() && |
196 | 0 | nested_doris_type->get_scale() == decimal.scale() |
197 | 0 | ? Status::OK() |
198 | 0 | : mismatch(); |
199 | 0 | } |
200 | 4 | case arrow::Type::LIST: |
201 | 5 | case arrow::Type::LARGE_LIST: |
202 | 5 | case arrow::Type::FIXED_SIZE_LIST: { |
203 | 5 | if (primitive_type != TYPE_ARRAY) { |
204 | 0 | return mismatch(); |
205 | 0 | } |
206 | 5 | const auto& list = static_cast<const arrow::BaseListType&>(arrow_type); |
207 | 5 | const auto& array = assert_cast<const DataTypeArray&>(*nested_doris_type); |
208 | 5 | return validate_arrow_type(*list.value_type(), array.get_nested_type(), path + ".element"); |
209 | 5 | } |
210 | 2 | case arrow::Type::STRUCT: { |
211 | 2 | if (primitive_type != TYPE_STRUCT) { |
212 | 0 | return mismatch(); |
213 | 0 | } |
214 | 2 | const auto& arrow_struct = static_cast<const arrow::StructType&>(arrow_type); |
215 | 2 | const auto& doris_struct = assert_cast<const DataTypeStruct&>(*nested_doris_type); |
216 | 2 | if (cast_set<size_t>(arrow_struct.num_fields()) != doris_struct.get_elements().size()) { |
217 | 0 | return mismatch(); |
218 | 0 | } |
219 | 4 | for (int field_idx = 0; field_idx < arrow_struct.num_fields(); ++field_idx) { |
220 | 3 | const auto& arrow_field = arrow_struct.field(field_idx); |
221 | 3 | const auto& doris_name = doris_struct.get_element_name(field_idx); |
222 | 3 | if (to_lower(arrow_field->name()) != to_lower(doris_name)) { |
223 | 1 | return Status::InvalidArgument( |
224 | 1 | "ADBC Arrow field mismatch at '{}': runtime field '{}' does not match " |
225 | 1 | "cached field '{}' at ordinal {}", |
226 | 1 | path, arrow_field->name(), doris_name, field_idx); |
227 | 1 | } |
228 | 2 | RETURN_IF_ERROR(validate_arrow_type(*arrow_field->type(), |
229 | 2 | doris_struct.get_element(field_idx), |
230 | 2 | path + "." + doris_name)); |
231 | 2 | } |
232 | 1 | return Status::OK(); |
233 | 2 | } |
234 | 0 | case arrow::Type::MAP: { |
235 | 0 | if (primitive_type != TYPE_MAP) { |
236 | 0 | return mismatch(); |
237 | 0 | } |
238 | 0 | const auto& arrow_map = static_cast<const arrow::MapType&>(arrow_type); |
239 | 0 | const auto& doris_map = assert_cast<const DataTypeMap&>(*nested_doris_type); |
240 | 0 | RETURN_IF_ERROR(validate_arrow_type(*arrow_map.key_type(), doris_map.get_key_type(), |
241 | 0 | path + ".key")); |
242 | 0 | return validate_arrow_type(*arrow_map.item_type(), doris_map.get_value_type(), |
243 | 0 | path + ".value"); |
244 | 0 | } |
245 | 0 | default: |
246 | 0 | return mismatch(); |
247 | 18 | } |
248 | 18 | } |
249 | | |
250 | | // Drivers allocate the strings inside AdbcError, so every populated error has to be released. |
251 | | class AdbcErrorGuard { |
252 | | public: |
253 | 10 | AdbcErrorGuard() = default; |
254 | 10 | ~AdbcErrorGuard() { reset(); } |
255 | | AdbcErrorGuard(const AdbcErrorGuard&) = delete; |
256 | | AdbcErrorGuard& operator=(const AdbcErrorGuard&) = delete; |
257 | | |
258 | 37 | AdbcError* get() { return &_error; } |
259 | | |
260 | 2 | std::string take_message() { |
261 | 2 | std::string message = _error.message != nullptr ? _error.message : ""; |
262 | 2 | reset(); |
263 | 2 | return message; |
264 | 2 | } |
265 | | |
266 | 47 | void reset() { |
267 | 47 | if (_error.release != nullptr) { |
268 | 1 | _error.release(&_error); |
269 | 1 | } |
270 | 47 | _error = ADBC_ERROR_INIT; |
271 | 47 | } |
272 | | |
273 | | private: |
274 | | AdbcError _error = ADBC_ERROR_INIT; |
275 | | }; |
276 | | |
277 | 2 | Status adbc_call_status(const char* what, AdbcStatusCode code, AdbcErrorGuard& error) { |
278 | 2 | const std::string message = error.take_message(); |
279 | | // AdbcStatusCode is a uint8_t, so spell out the name as well as the number. |
280 | 2 | return Status::InternalError("ADBC: {} failed ({}, code {}): {}", what, |
281 | 2 | AdbcStatusCodeMessage(code), static_cast<int>(code), |
282 | 2 | message.empty() ? "driver reported no message" : message); |
283 | 2 | } |
284 | | |
285 | | #define RETURN_IF_ADBC_ERROR(expr, what, error) \ |
286 | 27 | do { \ |
287 | 27 | const AdbcStatusCode adbc_call_code = (expr); \ |
288 | 27 | if (adbc_call_code != ADBC_STATUS_OK) { \ |
289 | 2 | return adbc_call_status((what), adbc_call_code, (error)); \ |
290 | 2 | } \ |
291 | 27 | (error).reset(); \ |
292 | 25 | } while (0) |
293 | | |
294 | | // The production stream: one ADBC database/connection/statement per scan range. |
295 | | // |
296 | | // P0 keeps them un-pooled on purpose. Reusing databases across ranges is a throughput optimization |
297 | | // that only pays off once multiple partitions run concurrently, and an unverifiable caching layer |
298 | | // added now would only obscure the functional path. |
299 | | class RealAdbcStream final : public AdbcStream { |
300 | | public: |
301 | 5 | explicit RealAdbcStream(const TFileRangeDesc& range) : _range(range) {} |
302 | 5 | ~RealAdbcStream() override { static_cast<void>(close()); } |
303 | | |
304 | 5 | Status open() { |
305 | 5 | RETURN_IF_ERROR(validate_adbc_range(_range)); |
306 | 5 | const auto& params = _range.table_format_params.adbc_params; |
307 | 5 | const std::string& driver_path = *find_param(params, kParamDriverPath); |
308 | 5 | const std::string& uri = *find_param(params, kParamUri); |
309 | 5 | const auto* entrypoint = find_param(params, kParamDriverEntrypoint); |
310 | | // validate_adbc_range has established that exactly one of these is present. |
311 | 5 | const auto* partition = find_param(params, kParamPartitionDescriptor); |
312 | | |
313 | 5 | RETURN_IF_ERROR(AdbcDriverRegistry::instance().get_or_load( |
314 | 5 | driver_path, entrypoint != nullptr ? *entrypoint : std::string(), &_driver)); |
315 | 4 | DORIS_CHECK(_driver != nullptr); |
316 | | |
317 | 4 | AdbcErrorGuard error; |
318 | 4 | RETURN_IF_ADBC_ERROR(_driver->DatabaseNew(&_database, error.get()), "DatabaseNew", error); |
319 | 4 | _database_created = true; |
320 | 4 | RETURN_IF_ERROR(_set_database_options(params, uri, error)); |
321 | 4 | RETURN_IF_ADBC_ERROR(_driver->DatabaseInit(&_database, error.get()), "DatabaseInit", error); |
322 | | |
323 | 4 | RETURN_IF_ADBC_ERROR(_driver->ConnectionNew(&_connection, error.get()), "ConnectionNew", |
324 | 4 | error); |
325 | 4 | _connection_created = true; |
326 | 4 | RETURN_IF_ADBC_ERROR(_driver->ConnectionInit(&_connection, &_database, error.get()), |
327 | 4 | "ConnectionInit", error); |
328 | | |
329 | 4 | if (partition != nullptr && !partition->empty()) { |
330 | 2 | RETURN_IF_ERROR(_read_partition(*partition, error)); |
331 | 2 | } else { |
332 | 2 | RETURN_IF_ERROR(_execute_query(*find_param(params, kParamQuerySql), error)); |
333 | 2 | } |
334 | | |
335 | | // Before Arrow ever sees it: the driver's stream may not clear its release callback, and |
336 | | // Arrow aborts the process when that happens. Both branches need it -- the Flight SQL |
337 | | // driver's ReadPartition stream breaks the contract exactly like its ExecuteQuery one. |
338 | 1 | enforce_stream_release_contract(&_c_stream); |
339 | | |
340 | 1 | auto reader = arrow::ImportRecordBatchReader(&_c_stream); |
341 | 1 | if (!reader.ok()) { |
342 | 0 | return Status::InternalError("ADBC: failed to import the result stream: {}", |
343 | 0 | reader.status().ToString()); |
344 | 0 | } |
345 | | // ImportRecordBatchReader moves the stream's contents; the reader owns it from here. |
346 | 1 | _reader = reader.MoveValueUnsafe(); |
347 | 1 | return Status::OK(); |
348 | 1 | } |
349 | | |
350 | 1 | Status next(std::shared_ptr<arrow::RecordBatch>* batch) override { |
351 | 1 | DORIS_CHECK(batch != nullptr); |
352 | 1 | if (_reader == nullptr) { |
353 | 0 | return Status::InternalError("ADBC: result stream is not open"); |
354 | 0 | } |
355 | 1 | std::shared_ptr<arrow::RecordBatch> next_batch; |
356 | 1 | const auto status = _reader->ReadNext(&next_batch); |
357 | 1 | if (!status.ok()) { |
358 | 0 | return Status::InternalError("ADBC: failed to read the next batch: {}", |
359 | 0 | status.ToString()); |
360 | 0 | } |
361 | 1 | *batch = std::move(next_batch); |
362 | 1 | return Status::OK(); |
363 | 1 | } |
364 | | |
365 | 6 | Status close() override { |
366 | 6 | Status result = Status::OK(); |
367 | | // Release in reverse order of creation. The reader owns the imported stream, so it has to |
368 | | // go before the statement that produced it. |
369 | 6 | _reader.reset(); |
370 | 6 | if (_c_stream.release != nullptr) { |
371 | | // Only reachable when the import itself failed; nothing else owns the stream then. |
372 | 0 | _c_stream.release(&_c_stream); |
373 | 0 | _c_stream = {}; |
374 | 0 | } |
375 | 6 | AdbcErrorGuard error; |
376 | 6 | if (_statement_created) { |
377 | 2 | const auto code = _driver->StatementRelease(&_statement, error.get()); |
378 | 2 | if (code != ADBC_STATUS_OK && result.ok()) { |
379 | 0 | result = adbc_call_status("StatementRelease", code, error); |
380 | 0 | } |
381 | 2 | error.reset(); |
382 | 2 | _statement_created = false; |
383 | 2 | } |
384 | 6 | if (_connection_created) { |
385 | 4 | const auto code = _driver->ConnectionRelease(&_connection, error.get()); |
386 | 4 | if (code != ADBC_STATUS_OK && result.ok()) { |
387 | 0 | result = adbc_call_status("ConnectionRelease", code, error); |
388 | 0 | } |
389 | 4 | error.reset(); |
390 | 4 | _connection_created = false; |
391 | 4 | } |
392 | 6 | if (_database_created) { |
393 | 4 | const auto code = _driver->DatabaseRelease(&_database, error.get()); |
394 | 4 | if (code != ADBC_STATUS_OK && result.ok()) { |
395 | 0 | result = adbc_call_status("DatabaseRelease", code, error); |
396 | 0 | } |
397 | 4 | error.reset(); |
398 | 4 | _database_created = false; |
399 | 4 | } |
400 | | // _driver itself is owned by AdbcDriverRegistry and is never released. |
401 | 6 | return result; |
402 | 6 | } |
403 | | |
404 | | private: |
405 | | // Runs the statement FE generated. One statement per range, so this range is the whole query. |
406 | 2 | Status _execute_query(const std::string& query_sql, AdbcErrorGuard& error) { |
407 | 2 | RETURN_IF_ADBC_ERROR(_driver->StatementNew(&_connection, &_statement, error.get()), |
408 | 2 | "StatementNew", error); |
409 | 2 | _statement_created = true; |
410 | 2 | RETURN_IF_ADBC_ERROR( |
411 | 2 | _driver->StatementSetSqlQuery(&_statement, query_sql.c_str(), error.get()), |
412 | 2 | "StatementSetSqlQuery", error); |
413 | 2 | int64_t rows_affected = -1; |
414 | 2 | RETURN_IF_ADBC_ERROR(_driver->StatementExecuteQuery(&_statement, &_c_stream, &rows_affected, |
415 | 2 | error.get()), |
416 | 2 | "StatementExecuteQuery", error); |
417 | 1 | return Status::OK(); |
418 | 2 | } |
419 | | |
420 | | // Reads one partition of a query FE already had the source execute. No statement is created: |
421 | | // ADBC reads a partition off a connection, and the whole point is that this can happen on a |
422 | | // different machine from the one that planned it. |
423 | 2 | Status _read_partition(const std::string& base64_descriptor, AdbcErrorGuard& error) { |
424 | 2 | std::string descriptor; |
425 | 2 | if (!base64_decode(base64_descriptor, &descriptor)) { |
426 | 1 | return Status::InvalidArgument("ADBC: the '{}' parameter is not valid base64", |
427 | 1 | kParamPartitionDescriptor); |
428 | 1 | } |
429 | 1 | RETURN_IF_ADBC_ERROR( |
430 | 1 | _driver->ConnectionReadPartition( |
431 | 1 | &_connection, reinterpret_cast<const uint8_t*>(descriptor.data()), |
432 | 1 | descriptor.size(), &_c_stream, error.get()), |
433 | 1 | "ConnectionReadPartition", error); |
434 | 0 | return Status::OK(); |
435 | 1 | } |
436 | | |
437 | | Status _set_database_options(const std::map<std::string, std::string>& params, |
438 | 4 | const std::string& uri, AdbcErrorGuard& error) { |
439 | 4 | RETURN_IF_ADBC_ERROR( |
440 | 4 | _driver->DatabaseSetOption(&_database, ADBC_OPTION_URI, uri.c_str(), error.get()), |
441 | 4 | "DatabaseSetOption(uri)", error); |
442 | 8 | for (const auto* key : {kParamUsername, kParamPassword}) { |
443 | 8 | const auto* value = find_param(params, key); |
444 | 8 | if (value == nullptr || value->empty()) { |
445 | 8 | continue; |
446 | 8 | } |
447 | 0 | RETURN_IF_ADBC_ERROR( |
448 | 0 | _driver->DatabaseSetOption(&_database, key, value->c_str(), error.get()), |
449 | 0 | "DatabaseSetOption(credentials)", error); |
450 | 0 | } |
451 | 12 | for (const auto& [key, value] : params) { |
452 | 12 | if (!key.starts_with(kAdbcOptionPrefix)) { |
453 | 12 | continue; |
454 | 12 | } |
455 | 0 | RETURN_IF_ADBC_ERROR( |
456 | 0 | _driver->DatabaseSetOption(&_database, key.c_str(), value.c_str(), error.get()), |
457 | 0 | "DatabaseSetOption(passthrough)", error); |
458 | 0 | } |
459 | 4 | return Status::OK(); |
460 | 4 | } |
461 | | |
462 | | const TFileRangeDesc _range; |
463 | | const AdbcDriver* _driver = nullptr; |
464 | | AdbcDatabase _database {}; |
465 | | AdbcConnection _connection {}; |
466 | | AdbcStatement _statement {}; |
467 | | ArrowArrayStream _c_stream {}; |
468 | | std::shared_ptr<arrow::RecordBatchReader> _reader; |
469 | | bool _database_created = false; |
470 | | bool _connection_created = false; |
471 | | bool _statement_created = false; |
472 | | }; |
473 | | |
474 | 5 | Status create_real_adbc_stream(const TFileRangeDesc& range, std::unique_ptr<AdbcStream>* out) { |
475 | 5 | DORIS_CHECK(out != nullptr); |
476 | 5 | auto stream = std::make_unique<RealAdbcStream>(range); |
477 | 5 | RETURN_IF_ERROR(stream->open()); |
478 | 1 | *out = std::move(stream); |
479 | 1 | return Status::OK(); |
480 | 5 | } |
481 | | |
482 | | ColumnDefinition adbc_child_definition(const std::string& name, DataTypePtr type, int32_t local_id); |
483 | | |
484 | | // Mirrors synthesize_remote_doris_children in remote_doris_reader.cpp. Both readers expose table |
485 | | // slots as file columns, so complex columns still need structural children for TableColumnMapper. |
486 | | // Kept separate rather than shared to avoid reshaping the already-shipped remote_doris reader. |
487 | 27 | std::vector<ColumnDefinition> synthesize_adbc_children(const DataTypePtr& type) { |
488 | 27 | std::vector<ColumnDefinition> children; |
489 | 27 | DORIS_CHECK(type != nullptr); |
490 | 27 | const auto nested_type = remove_nullable(type); |
491 | 27 | switch (nested_type->get_primitive_type()) { |
492 | 7 | case TYPE_ARRAY: { |
493 | 7 | const auto* array_type = assert_cast<const DataTypeArray*>(nested_type.get()); |
494 | 7 | children.push_back(adbc_child_definition("element", array_type->get_nested_type(), 0)); |
495 | 7 | break; |
496 | 0 | } |
497 | 0 | case TYPE_MAP: { |
498 | 0 | const auto* map_type = assert_cast<const DataTypeMap*>(nested_type.get()); |
499 | 0 | children.push_back(adbc_child_definition("key", map_type->get_key_type(), 0)); |
500 | 0 | children.push_back(adbc_child_definition("value", map_type->get_value_type(), 1)); |
501 | 0 | break; |
502 | 0 | } |
503 | 2 | case TYPE_STRUCT: { |
504 | 2 | const auto* struct_type = assert_cast<const DataTypeStruct*>(nested_type.get()); |
505 | 2 | children.reserve(struct_type->get_elements().size()); |
506 | 6 | for (size_t idx = 0; idx < struct_type->get_elements().size(); ++idx) { |
507 | 4 | children.push_back(adbc_child_definition(struct_type->get_element_name(idx), |
508 | 4 | struct_type->get_element(idx), |
509 | 4 | cast_set<int32_t>(idx))); |
510 | 4 | } |
511 | 2 | break; |
512 | 0 | } |
513 | 18 | default: |
514 | 18 | break; |
515 | 27 | } |
516 | 27 | return children; |
517 | 27 | } |
518 | | |
519 | | ColumnDefinition adbc_child_definition(const std::string& name, DataTypePtr type, |
520 | 11 | int32_t local_id) { |
521 | 11 | ColumnDefinition child; |
522 | 11 | child.identifier = Field::create_field<TYPE_STRING>(name); |
523 | 11 | child.local_id = local_id; |
524 | 11 | child.name = name; |
525 | 11 | child.type = std::move(type); |
526 | 11 | child.children = synthesize_adbc_children(child.type); |
527 | 11 | return child; |
528 | 11 | } |
529 | | |
530 | | // A stream that forwards everything to the driver's and, on release, does the one thing some |
531 | | // drivers forget: clear its own release callback. Heap-allocated because Arrow keeps only the |
532 | | // ArrowArrayStream it was handed, and the delegate has to outlive this function. |
533 | | struct DelegatingStream { |
534 | | ArrowArrayStream inner; |
535 | | }; |
536 | | |
537 | 1 | int delegating_get_schema(ArrowArrayStream* self, ArrowSchema* out) { |
538 | 1 | auto& inner = static_cast<DelegatingStream*>(self->private_data)->inner; |
539 | 1 | return inner.get_schema(&inner, out); |
540 | 1 | } |
541 | | |
542 | 2 | int delegating_get_next(ArrowArrayStream* self, ArrowArray* out) { |
543 | 2 | auto& inner = static_cast<DelegatingStream*>(self->private_data)->inner; |
544 | 2 | return inner.get_next(&inner, out); |
545 | 2 | } |
546 | | |
547 | 1 | const char* delegating_get_last_error(ArrowArrayStream* self) { |
548 | 1 | auto& inner = static_cast<DelegatingStream*>(self->private_data)->inner; |
549 | 1 | return inner.get_last_error != nullptr ? inner.get_last_error(&inner) : nullptr; |
550 | 1 | } |
551 | | |
552 | 3 | void delegating_release(ArrowArrayStream* self) { |
553 | 3 | auto* delegate = static_cast<DelegatingStream*>(self->private_data); |
554 | 3 | if (delegate->inner.release != nullptr) { |
555 | 3 | delegate->inner.release(&delegate->inner); |
556 | 3 | } |
557 | 3 | delete delegate; |
558 | 3 | self->private_data = nullptr; |
559 | | // What the driver failed to do, and what Arrow aborts the process over. |
560 | 3 | self->release = nullptr; |
561 | 3 | } |
562 | | |
563 | | } // namespace |
564 | | |
565 | 4 | void enforce_stream_release_contract(ArrowArrayStream* stream) { |
566 | 4 | DORIS_CHECK(stream != nullptr); |
567 | 4 | if (stream->release == nullptr) { |
568 | | // Already released; nothing to delegate to, and wrapping it would hand Arrow a stream |
569 | | // whose callbacks dereference a released delegate. |
570 | 1 | return; |
571 | 1 | } |
572 | 3 | auto* delegate = new DelegatingStream {.inner = *stream}; |
573 | 3 | *stream = ArrowArrayStream {.get_schema = delegating_get_schema, |
574 | 3 | .get_next = delegating_get_next, |
575 | 3 | .get_last_error = delegating_get_last_error, |
576 | 3 | .release = delegating_release, |
577 | 3 | .private_data = delegate}; |
578 | 3 | } |
579 | | |
580 | | AdbcFileReader::AdbcFileReader(std::shared_ptr<io::FileSystemProperties>& system_properties, |
581 | | std::unique_ptr<io::FileDescription>& file_description, |
582 | | std::shared_ptr<io::IOContext> io_ctx, RuntimeProfile* profile, |
583 | | const TFileRangeDesc& range, |
584 | | const std::vector<SlotDescriptor*>& file_slot_descs, |
585 | | AdbcStreamFactory stream_factory) |
586 | 25 | : FileReader(system_properties, file_description, std::move(io_ctx), profile), |
587 | 25 | _range(range), |
588 | 25 | _file_slot_descs(file_slot_descs), |
589 | 25 | _stream_factory(std::move(stream_factory)) { |
590 | 25 | DORIS_CHECK(TimezoneUtils::find_cctz_time_zone(TimezoneUtils::default_time_zone, _ctz)); |
591 | 25 | } |
592 | | |
593 | 25 | AdbcFileReader::~AdbcFileReader() { |
594 | 25 | static_cast<void>(close()); |
595 | 25 | } |
596 | | |
597 | 24 | void AdbcFileReader::_init_profile() { |
598 | 24 | if (_profile == nullptr) { |
599 | 0 | return; |
600 | 0 | } |
601 | 24 | const auto hierarchy = file_scan_profile::ensure_hierarchy(_profile); |
602 | 24 | _io_time = hierarchy.io; |
603 | 24 | static const char* adbc_profile = "AdbcFileReader"; |
604 | 24 | _total_time = |
605 | 24 | ADD_CHILD_TIMER_WITH_LEVEL(_profile, adbc_profile, file_scan_profile::FILE_READER, 1); |
606 | 24 | _open_stream_time = ADD_CHILD_TIMER_WITH_LEVEL(_profile, "AdbcOpenStreamTime", adbc_profile, 1); |
607 | 24 | _next_batch_time = ADD_CHILD_TIMER_WITH_LEVEL(_profile, "AdbcNextBatchTime", adbc_profile, 1); |
608 | 24 | _normalize_time = ADD_CHILD_TIMER_WITH_LEVEL(_profile, "AdbcNormalizeTime", adbc_profile, 1); |
609 | 24 | _materialize_time = |
610 | 24 | ADD_CHILD_TIMER_WITH_LEVEL(_profile, "AdbcMaterializeTime", adbc_profile, 1); |
611 | 24 | _filter_time = ADD_CHILD_TIMER_WITH_LEVEL(_profile, "AdbcFilterTime", adbc_profile, 1); |
612 | 24 | } |
613 | | |
614 | 24 | Status AdbcFileReader::init(RuntimeState* state) { |
615 | 24 | _init_profile(); |
616 | 24 | SCOPED_TIMER(_total_time); |
617 | 24 | _runtime_state = state; |
618 | 24 | RETURN_IF_ERROR(validate_adbc_range(_range)); |
619 | 18 | RETURN_IF_ERROR(_build_col_name_to_file_id()); |
620 | 18 | _eof = false; |
621 | 18 | return Status::OK(); |
622 | 18 | } |
623 | | |
624 | 14 | Status AdbcFileReader::get_schema(std::vector<ColumnDefinition>* file_schema) const { |
625 | 14 | SCOPED_TIMER(_total_time); |
626 | 14 | DORIS_CHECK(file_schema != nullptr); |
627 | 14 | file_schema->clear(); |
628 | 14 | file_schema->reserve(_file_slot_descs.size()); |
629 | 30 | for (size_t idx = 0; idx < _file_slot_descs.size(); ++idx) { |
630 | 16 | const auto* slot = _file_slot_descs[idx]; |
631 | 16 | DORIS_CHECK(slot != nullptr); |
632 | 16 | file_schema->push_back({ |
633 | 16 | .identifier = Field::create_field<TYPE_INT>(cast_set<int32_t>(idx)), |
634 | 16 | .local_id = cast_set<int32_t>(idx), |
635 | 16 | .name = slot->col_name(), |
636 | 16 | .type = slot->type(), |
637 | 16 | .children = synthesize_adbc_children(slot->type()), |
638 | 16 | }); |
639 | 16 | } |
640 | 14 | return Status::OK(); |
641 | 14 | } |
642 | | |
643 | | std::unique_ptr<TableColumnMapper> AdbcFileReader::create_column_mapper( |
644 | 2 | TableColumnMapperOptions options) const { |
645 | | // ADBC streams return complete Arrow roots, so TableReader must own every nested projection and |
646 | | // reorder after the full source shape has been materialized. |
647 | 2 | return std::make_unique<MaterializedColumnMapper>(std::move(options)); |
648 | 2 | } |
649 | | |
650 | 18 | Status AdbcFileReader::open(std::shared_ptr<FileScanRequest> request) { |
651 | 18 | SCOPED_TIMER(_total_time); |
652 | 18 | SCOPED_TIMER(_open_stream_time); |
653 | 18 | RETURN_IF_ERROR(FileReader::open(std::move(request))); |
654 | 18 | RETURN_IF_ERROR(_open_stream()); |
655 | 14 | _eof = false; |
656 | 14 | return Status::OK(); |
657 | 18 | } |
658 | | |
659 | 16 | Status AdbcFileReader::get_block(Block* file_block, size_t* rows, bool* eof) { |
660 | 16 | SCOPED_TIMER(_total_time); |
661 | 16 | DORIS_CHECK(file_block != nullptr); |
662 | 16 | DORIS_CHECK(rows != nullptr); |
663 | 16 | DORIS_CHECK(eof != nullptr); |
664 | 16 | if (_stream == nullptr) { |
665 | 0 | return Status::InternalError("ADBC reader is not open"); |
666 | 0 | } |
667 | 16 | if (_io_ctx != nullptr && _io_ctx->should_stop) { |
668 | | // Observe cancellation before entering a potentially blocking driver read. |
669 | 0 | RETURN_IF_ERROR(close()); |
670 | 0 | *rows = 0; |
671 | 0 | *eof = true; |
672 | 0 | return Status::OK(); |
673 | 0 | } |
674 | | |
675 | 16 | *rows = 0; |
676 | 16 | *eof = false; |
677 | 16 | std::shared_ptr<arrow::RecordBatch> batch; |
678 | 16 | { |
679 | 16 | SCOPED_TIMER(_io_time); |
680 | 16 | SCOPED_TIMER(_next_batch_time); |
681 | 16 | RETURN_IF_ERROR(_stream->next(&batch)); |
682 | 16 | } |
683 | 16 | if (batch == nullptr) { |
684 | 2 | *eof = true; |
685 | 2 | _eof = true; |
686 | 2 | return Status::OK(); |
687 | 2 | } |
688 | | |
689 | 14 | { |
690 | 14 | SCOPED_TIMER(_materialize_time); |
691 | 14 | RETURN_IF_ERROR(_materialize_record_batch(*batch, file_block, rows)); |
692 | 14 | } |
693 | 7 | _record_scan_rows(cast_set<int64_t>(*rows)); |
694 | 7 | { |
695 | 7 | SCOPED_TIMER(_filter_time); |
696 | 7 | RETURN_IF_ERROR( |
697 | 7 | apply_materialized_reader_filters(_request.get(), _io_ctx.get(), file_block, rows)); |
698 | 7 | } |
699 | 7 | return Status::OK(); |
700 | 7 | } |
701 | | |
702 | 36 | Status AdbcFileReader::close() { |
703 | 36 | SCOPED_TIMER(_total_time); |
704 | 36 | if (_stream != nullptr) { |
705 | 14 | RETURN_IF_ERROR(_stream->close()); |
706 | 14 | _stream.reset(); |
707 | 14 | } |
708 | 36 | _request.reset(); |
709 | 36 | _eof = true; |
710 | 36 | return Status::OK(); |
711 | 36 | } |
712 | | |
713 | 18 | Status AdbcFileReader::_open_stream() { |
714 | 18 | DORIS_CHECK(_stream == nullptr); |
715 | 18 | if (_stream_factory) { |
716 | 13 | RETURN_IF_ERROR(_stream_factory(_range, &_stream)); |
717 | 13 | } else { |
718 | 5 | RETURN_IF_ERROR(create_real_adbc_stream(_range, &_stream)); |
719 | 5 | } |
720 | 14 | DORIS_CHECK(_stream != nullptr); |
721 | 14 | return Status::OK(); |
722 | 18 | } |
723 | | |
724 | | Status AdbcFileReader::_materialize_record_batch(const arrow::RecordBatch& batch, Block* file_block, |
725 | 14 | size_t* rows) const { |
726 | 14 | DORIS_CHECK(file_block != nullptr); |
727 | 14 | DORIS_CHECK(rows != nullptr); |
728 | 14 | if (_request == nullptr) { |
729 | 0 | return Status::InternalError("ADBC reader is not open"); |
730 | 0 | } |
731 | | |
732 | 14 | if (_col_name_to_file_id.empty()) { |
733 | | // A pushed-down COUNT(*) projects no columns at all: the scan wants rows counted, no values. |
734 | | // Counting here rather than falling into the loop below is not an optimization -- every column |
735 | | // the source returns is unrequested by definition in this state, so the loop's unknown-column |
736 | | // check would reject the first one and fail a query that asked for nothing but a number. |
737 | | // FE sends a one-constant-column statement for this case, so the batch is narrow. |
738 | | // |
739 | | // Only the empty case is special-cased. An unrequested column arriving alongside requested ones |
740 | | // still fails: that means FE and this reader disagree about the projection, and it is the one |
741 | | // signal that the disagreement exists. |
742 | 1 | *rows = cast_set<size_t>(batch.num_rows()); |
743 | 1 | return Status::OK(); |
744 | 1 | } |
745 | | |
746 | 13 | ArrowMemoryPool<> local_arrow_pool; |
747 | 13 | arrow::MemoryPool* arrow_pool = ExecEnv::GetInstance()->arrow_memory_pool(); |
748 | 13 | if (arrow_pool == nullptr) { |
749 | | // Embedded and unit-test runtimes may omit ExecEnv memory initialization; keep conversions |
750 | | // on Doris' tracked allocator instead of falling back to Arrow's untracked default pool. |
751 | 13 | arrow_pool = &local_arrow_pool; |
752 | 13 | } |
753 | 13 | std::vector<bool> materialized_columns(file_block->columns(), false); |
754 | 22 | for (int arrow_idx = 0; arrow_idx < batch.num_columns(); ++arrow_idx) { |
755 | 16 | const std::string& column_name = batch.schema()->field(arrow_idx)->name(); |
756 | 16 | const auto file_id_it = _col_name_to_file_id.find(column_name); |
757 | 16 | if (file_id_it == _col_name_to_file_id.end()) { |
758 | 1 | return Status::InternalError("ADBC source returned unknown column {}", column_name); |
759 | 1 | } |
760 | 15 | const auto block_position_it = _request->local_positions.find(file_id_it->second); |
761 | 15 | if (block_position_it == _request->local_positions.end()) { |
762 | 0 | continue; |
763 | 0 | } |
764 | 15 | std::shared_ptr<arrow::Array> array; |
765 | 15 | { |
766 | 15 | SCOPED_TIMER(_normalize_time); |
767 | 15 | RETURN_IF_ERROR(normalize_arrow_array(batch.column(arrow_idx), arrow_pool, &array)); |
768 | 15 | } |
769 | 15 | RETURN_IF_ERROR(_materialize_arrow_column(column_name, array, batch.num_rows(), |
770 | 15 | file_id_it->second, block_position_it->second, |
771 | 15 | file_block)); |
772 | 9 | materialized_columns[block_position_it->second.value()] = true; |
773 | 9 | } |
774 | | |
775 | 9 | for (const auto& [file_column_id, block_position] : _request->local_positions) { |
776 | 9 | if (block_position.value() >= materialized_columns.size()) { |
777 | 0 | return Status::InternalError( |
778 | 0 | "ADBC requested block position {} out of range, block columns {}", |
779 | 0 | block_position.value(), materialized_columns.size()); |
780 | 0 | } |
781 | 9 | if (!materialized_columns[block_position.value()]) { |
782 | 0 | return Status::InternalError("ADBC source did not return requested file column id {}", |
783 | 0 | file_column_id.value()); |
784 | 0 | } |
785 | 9 | } |
786 | | |
787 | 6 | *rows = cast_set<size_t>(batch.num_rows()); |
788 | 6 | return Status::OK(); |
789 | 6 | } |
790 | | |
791 | | Status AdbcFileReader::_materialize_arrow_column(const std::string& column_name, |
792 | | const std::shared_ptr<arrow::Array>& array, |
793 | | int64_t num_rows, LocalColumnId file_column_id, |
794 | | const LocalIndex& block_position, |
795 | 15 | Block* file_block) const { |
796 | 15 | DORIS_CHECK(file_block != nullptr); |
797 | 15 | DORIS_CHECK(array != nullptr); |
798 | 15 | if (block_position.value() >= file_block->columns()) { |
799 | 0 | return Status::InternalError("ADBC block position {} out of range, block columns {}", |
800 | 0 | block_position.value(), file_block->columns()); |
801 | 0 | } |
802 | 15 | const auto& target_type = file_block->get_by_position(block_position.value()).type; |
803 | | |
804 | | // The cached FE target remains authoritative when a source schema changes mid-cache-window; |
805 | | // otherwise SerDes without a null map can silently turn source nulls into default values. |
806 | 15 | if (array->null_count() > 0 && !target_type->is_nullable()) { |
807 | 2 | return Status::InternalError( |
808 | 2 | "ADBC Arrow column '{}' contains {} null rows for non-nullable Doris type {}", |
809 | 2 | column_name, array->null_count(), target_type->get_name()); |
810 | 2 | } |
811 | | |
812 | | // An all-null column arrives with a type that says nothing about the column. |
813 | | // |
814 | | // A source that infers Arrow types from the VALUES it returns -- rather than from the declared |
815 | | // column type -- has nothing to infer from when every value in the result is null, and picks |
816 | | // whatever its default is. Measured against the SQLite driver: the same TEXT column comes back |
817 | | // as utf8 for `SELECT id, name FROM t1` and as int64 for |
818 | | // `SELECT id, name FROM t1 WHERE name IS NULL`, purely because the filter left only nulls. |
819 | | // Handing that to the serde fails with "Unsupported arrow type for string column: 9", and no |
820 | | // amount of care on the FE side avoids it: FE cannot know in advance which rows will survive. |
821 | | // |
822 | | // N nulls are what this array means whatever type it claims, so materialize them directly. The |
823 | | // check is narrow on purpose -- a column with even one non-null value keeps its real type and |
824 | | // still fails loudly on a genuine mismatch, which is the signal that FE and the source disagree |
825 | | // about the schema rather than about one result set. |
826 | | // |
827 | | // Only for a nullable target: substituting defaults into a NOT NULL column would turn a source |
828 | | // that wrongly sent nulls into silently wrong data, so that keeps failing in the serde. |
829 | 13 | if (array->null_count() == array->length() && target_type->is_nullable()) { |
830 | 1 | auto columns_guard = file_block->mutate_columns_scoped(); |
831 | 1 | auto& columns = columns_guard.mutable_columns(); |
832 | 1 | columns[block_position.value()]->insert_many_defaults(cast_set<size_t>(num_rows)); |
833 | 1 | return Status::OK(); |
834 | 1 | } |
835 | | |
836 | | // Validate the whole logical shape before acquiring mutable block columns. Struct SerDes map |
837 | | // children by ordinal, so a stale cache entry can otherwise produce plausible but wrong values. |
838 | 12 | RETURN_IF_ERROR(validate_arrow_type(*array->type(), target_type, column_name)); |
839 | | |
840 | 8 | auto columns_guard = file_block->mutate_columns_scoped(); |
841 | 8 | auto& columns = columns_guard.mutable_columns(); |
842 | 8 | try { |
843 | 8 | RETURN_IF_ERROR(target_type->get_serde()->read_column_from_arrow( |
844 | 8 | *columns[block_position.value()], array.get(), 0, num_rows, _ctz)); |
845 | 8 | } catch (const Exception& e) { |
846 | 0 | return Status::InternalError( |
847 | 0 | "Failed to convert ADBC Arrow column '{}' (file_column_id={}, arrow type={}) to " |
848 | 0 | "Doris block: {}", |
849 | 0 | column_name, file_column_id.value(), array->type()->ToString(), e.what()); |
850 | 0 | } |
851 | 8 | return Status::OK(); |
852 | 8 | } |
853 | | |
854 | 18 | Status AdbcFileReader::_build_col_name_to_file_id() { |
855 | 18 | _col_name_to_file_id.clear(); |
856 | 18 | _col_name_to_file_id.reserve(_file_slot_descs.size()); |
857 | 50 | for (size_t idx = 0; idx < _file_slot_descs.size(); ++idx) { |
858 | 32 | const auto* slot = _file_slot_descs[idx]; |
859 | 32 | DORIS_CHECK(slot != nullptr); |
860 | 32 | _col_name_to_file_id.emplace(slot->col_name(), LocalColumnId(cast_set<int32_t>(idx))); |
861 | 32 | } |
862 | 18 | return Status::OK(); |
863 | 18 | } |
864 | | |
865 | | AdbcReader::AdbcReader(AdbcStreamFactory stream_factory) |
866 | 1 | : _stream_factory(std::move(stream_factory)) {} |
867 | | |
868 | 1 | Status AdbcReader::init(TableReadOptions&& options) { |
869 | 1 | if (options.file_slot_descs == nullptr) { |
870 | 0 | return Status::InvalidArgument("ADBC reader requires file slot descriptors"); |
871 | 0 | } |
872 | 1 | return TableReader::init(std::move(options)); |
873 | 1 | } |
874 | | |
875 | 1 | Status AdbcReader::prepare_split(const SplitReadOptions& options) { |
876 | 1 | { |
877 | | // Keep protocol validation visible while avoiding overlap with TableReader's own scopes. |
878 | 1 | SCOPED_TIMER(_profile.total_timer); |
879 | 1 | SCOPED_TIMER(_profile.prepare_split_timer); |
880 | 1 | RETURN_IF_ERROR(validate_adbc_range(options.current_range)); |
881 | 1 | } |
882 | 1 | return TableReader::prepare_split(options); |
883 | 1 | } |
884 | | |
885 | 1 | Status AdbcReader::create_file_reader(std::unique_ptr<FileReader>* reader) { |
886 | 1 | DORIS_CHECK(reader != nullptr); |
887 | 1 | DORIS_CHECK(_file_slot_descs != nullptr); |
888 | 1 | *reader = std::make_unique<AdbcFileReader>(_system_properties, _current_task->data_file, |
889 | 1 | _io_ctx, _scanner_profile, _current_file_range_desc, |
890 | 1 | *_file_slot_descs, _stream_factory); |
891 | 1 | return Status::OK(); |
892 | 1 | } |
893 | | |
894 | | } // namespace doris::format::adbc |