github-actions[bot] commented on code in PR #68853:
URL: https://github.com/apache/doris/pull/68853#discussion_r4242705286
##########
be/src/format/json/new_json_reader.cpp:
##########
@@ -1147,7 +1147,26 @@ Status
NewJsonReader::_simdjson_write_data_to_column(simdjson::ondemand::value&
}
auto primitive_type = type_desc->get_primitive_type();
- if (_is_load || !is_complex_type(primitive_type)) {
+ if (primitive_type == TYPE_FILE) {
+ std::string_view json_str = simdjson::to_json_string(value);
+ Slice slice {json_str.data(), json_str.size()};
+ const auto status =
+ data_serde->deserialize_one_cell_from_json(*data_column_ptr,
slice, _serde_options);
+ if (!status.ok()) {
+ if (!_is_load) return status;
+ if (_params.strict_mode || nullable_column == nullptr) {
Review Comment:
[P1] Roll back the whole row when strict FILE decoding fails. If an earlier
JSON field such as `id` has already been appended, this branch reports the bad
FILE and returns OK with `valid=false` but appends no FILE value.
`_simdjson_set_column_value` then exits without truncating the earlier column;
the simple-JSON caller counts the row, so later valid rows have different
column offsets and FileScanner can hit its size assertion or process shifted
values. Reject the row atomically and test a bad top-level FILE after `id`,
followed by a valid row, with `strict_mode=true`.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/stats/ExpressionEstimation.java:
##########
@@ -155,8 +160,31 @@ public ColumnStatistic visit(Expression expr, Statistics
context) {
return expr.child(0).accept(this, context);
}
+ @Override
+ public ColumnStatistic visitIsNull(IsNull isNull, Statistics context) {
+ if (!isNull.child().getDataType().isFileType()) {
Review Comment:
[P2] Complement the Boolean range for FILE `IS NOT NULL`. SQL represents it
as `Not(IsNull(f))`, but the generic visitor copies the exact `IS NULL` range
computed here. For all-null FILE input, `Project(p := f IS NOT NULL)` therefore
records `p` as always true although every value is false. In `Filter(p IN
(FALSE, NULL)) -> Project(p := NOT ISNULL(f)) -> Limit -> UnionAll(typed-null
FILE arms)`, the IN estimator can exclude both options from that wrong [1,1]
range and estimate zero rows even though every row matches FALSE. Estimate the
Not form and cover all-null and no-null FILE projections.
##########
be/src/exec/common/data_gen_functions/vlist_file_tvf.cpp:
##########
@@ -0,0 +1,148 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "exec/common/data_gen_functions/vlist_file_tvf.h"
+
+#include <gen_cpp/PaloInternalService_types.h>
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <algorithm>
+#include <array>
+
+#include "core/data_type/data_type_file.h"
+#include "core/field.h"
+#include "core/value/file_value.h"
+#include "io/file_factory.h"
+#include "io/fs/s3_file_system.h"
+#include "runtime/runtime_state.h"
+#include "util/s3_uri.h"
+
+namespace doris {
+VListFileTVF::VListFileTVF(TupleId tuple_id, const TupleDescriptor* tuple_desc)
+ : VDataGenFunctionInf(tuple_id, tuple_desc) {}
+
+VListFileTVF::~VListFileTVF() = default;
+
+Status VListFileTVF::set_scan_ranges(const std::vector<TScanRangeParams>&
scan_ranges) {
+ DCHECK(scan_ranges.size() == 1);
+ const auto& params =
scan_ranges[0].scan_range.data_gen_scan_range.list_file_params;
+ const auto key_start = params.uri.find('/', 5);
+ S3URI uri(params.uri.substr(0, key_start));
+ RETURN_IF_ERROR(uri.parse());
+ _properties = params.resource.properties;
+ _path.bucket = uri.get_bucket();
+ _path.prefix = key_start == std::string::npos ? "" :
params.uri.substr(key_start + 1);
+ if (!_path.prefix.empty() && !_path.prefix.ends_with('/')) {
+ _path.prefix += '/';
+ }
+ _path.delimiter = params.recursive ? "" : "/";
Review Comment:
[P2] Preserve nonrecursive listing for Azure resources. This sets `/` as the
only depth control when `recursive=false`, but
`AzureObjStorageClient::list_objects_page` ignores `ObjStoragePath.delimiter`
and uses flat `ListBlobs`. The output loop filters only keys ending in `/`, so
a request for `dir/` includes `dir/deep/a.txt` alongside direct children on
Azure-enabled builds. Apply delimiter semantics in the Azure client or filter
descendants before emitting them, and cover an Azure resource with nested keys.
##########
fe/fe-core/src/main/java/org/apache/doris/nereids/stats/ExpressionEstimation.java:
##########
@@ -277,6 +360,10 @@ private ColumnStatistic castMinMax(ColumnStatistic
colStats, DataType targetType
@Override
public ColumnStatistic visitLiteral(Literal literal, Statistics context) {
+ if (literal.getDataType().isFileType() && literal.isNullLiteral()) {
Review Comment:
[P2] Estimate non-null FILE literals from their payload. This special case
covers only typed NULL; a folded non-null `FileLiteral` reaches the numeric
literal path below, where `getDouble()` becomes 0 and `avgSizeByte` is fixed at
the 16-byte slot width. A constant FILE with large `inline` bytes is therefore
severely undercosted in projections and one-row plans, and carries meaningless
numeric min/max and hot values. Use its six fields to derive byte size and
leave numeric extrema unavailable, as the other FILE statistics paths do.
##########
be/src/exec/common/data_gen_functions/vlist_file_tvf.cpp:
##########
@@ -0,0 +1,148 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "exec/common/data_gen_functions/vlist_file_tvf.h"
+
+#include <gen_cpp/PaloInternalService_types.h>
+#include <gen_cpp/PlanNodes_types.h>
+
+#include <algorithm>
+#include <array>
+
+#include "core/data_type/data_type_file.h"
+#include "core/field.h"
+#include "core/value/file_value.h"
+#include "io/file_factory.h"
+#include "io/fs/s3_file_system.h"
+#include "runtime/runtime_state.h"
+#include "util/s3_uri.h"
+
+namespace doris {
+VListFileTVF::VListFileTVF(TupleId tuple_id, const TupleDescriptor* tuple_desc)
+ : VDataGenFunctionInf(tuple_id, tuple_desc) {}
+
+VListFileTVF::~VListFileTVF() = default;
+
+Status VListFileTVF::set_scan_ranges(const std::vector<TScanRangeParams>&
scan_ranges) {
+ DCHECK(scan_ranges.size() == 1);
+ const auto& params =
scan_ranges[0].scan_range.data_gen_scan_range.list_file_params;
+ const auto key_start = params.uri.find('/', 5);
+ S3URI uri(params.uri.substr(0, key_start));
+ RETURN_IF_ERROR(uri.parse());
+ _properties = params.resource.properties;
+ _path.bucket = uri.get_bucket();
+ _path.prefix = key_start == std::string::npos ? "" :
params.uri.substr(key_start + 1);
+ if (!_path.prefix.empty() && !_path.prefix.ends_with('/')) {
+ _path.prefix += '/';
+ }
+ _path.delimiter = params.recursive ? "" : "/";
+ static constexpr std::array<std::string_view, 2> NAMES {"path", "file"};
+ _column_indices.clear();
+ for (const auto* slot : _tuple_desc->slots()) {
+ const auto* const field = std::ranges::find(NAMES, slot->col_name());
+ DCHECK(field != NAMES.end());
+ _column_indices.push_back(std::distance(NAMES.begin(), field));
+ }
+ return Status::OK();
+}
+
+Status VListFileTVF::_fetch_next_page(RuntimeState* state) {
+ RETURN_IF_CANCELLED(state);
+ if (!_filesystem) {
+ io::FSPropertiesRef properties(TFileType::FILE_S3);
+ properties.properties = &_properties;
+ io::FileDescription description;
+ description.path = "s3://" + _path.bucket;
+ auto filesystem = DORIS_TRY(FileFactory::create_fs(properties,
description));
+ _filesystem = std::static_pointer_cast<io::S3FileSystem>(filesystem);
+ RETURN_IF_CANCELLED(state);
+ }
+
+ RETURN_IF_CANCELLED(state);
+ const auto client = _filesystem->client_holder()->get();
+ // Release the previous page before the provider allocates its replacement.
+ _objects = std::vector<ObjectMeta>();
+ auto page = client->list_objects_page(_path, _continuation_token);
+ RETURN_IF_CANCELLED(state);
+ if (!page.resp.ok()) {
+ return {page.resp.status.code, std::move(page.resp.status.msg)};
+ }
+ _objects = std::move(page.objects);
+ _next_index = 0;
+ _continuation_token = std::move(page.continuation_token);
+ _has_more = page.has_more;
+ return Status::OK();
+}
+
+Status VListFileTVF::get_next(RuntimeState* state, Block* block, bool* eos) {
+ DCHECK(block->rows() == 0);
+ DCHECK(!_tuple_desc->slots().empty());
+ RETURN_IF_CANCELLED(state);
+ *eos = false;
+
+ // Retain the mutable owner locally. Only publish after remote I/O and
FILE validation succeed,
+ // so an error cannot leave a reused block with a moved-from column.
+ MutableColumns columns;
+ for (const auto* slot : _tuple_desc->slots()) {
+ columns.push_back(slot->get_empty_mutable_column());
+ }
+ auto& output = columns[0];
+ while (output->size() < static_cast<size_t>(state->batch_size())) {
+ if (_next_index == _objects.size()) {
+ if (!output->empty()) {
+ break;
+ }
+ if (!_has_more) {
+ *eos = true;
+ break;
+ }
+ RETURN_IF_ERROR(_fetch_next_page(state));
+ continue;
+ }
+ RETURN_IF_CANCELLED(state);
+ const auto& object = _objects[_next_index++];
+ if (object.key.ends_with('/')) {
+ continue;
+ }
+ File value(DataTypeFile::FIELD_COUNT);
+ value[0] = Field::create_field<TYPE_STRING>("s3://" + _path.bucket +
"/" + object.key);
Review Comment:
[P2] Make LIST_FILE paths round-trip through TO_FILE for raw S3 keys. This
concatenation emits keys such as `http://dir/a.txt` or `file?#.txt` unchanged,
but `TO_FILE` passes the resulting URI to `S3URI::parse()`, which rejects the
extra `://` or truncates the key at `?`/`#` before HEAD. The added LIST_FILE
tests allow these keys, so a listed path can fail or resolve a different
object. Use one lossless representation and cover those keys in the
LIST_FILE-to-TO_FILE test.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]