blob: 41d3a5282b62556f4c6809fa88cbbdc7890b5f6b [file]
// 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 "format_v2/jni/jdbc_reader.h"
#include <memory>
#include <utility>
#include "common/cast_set.h"
#include "core/assert_cast.h"
#include "core/block/block.h"
#include "core/block/columns_with_type_and_name.h"
#include "core/column/column_nullable.h"
#include "core/data_type/data_type_nullable.h"
#include "core/data_type/data_type_string.h"
#include "exprs/function/simple_function_factory.h"
#include "exprs/vexpr_context.h"
#include "format_v2/table_reader.h"
#include "util/jdbc_utils.h"
namespace doris::format::jdbc {
Status validate_non_nullable_special_type_result(const IColumn& result, size_t rows) {
const auto* nullable = check_and_get_column<ColumnNullable>(&result);
if (UNLIKELY(nullable == nullptr)) {
return Status::InternalError("JDBC special-type CAST did not return a nullable column");
}
if (UNLIKELY(nullable->has_null(0, rows))) {
// CAST NULL represents invalid source data; stripping the null map would turn it into a
// valid-looking default for a NOT NULL destination.
return Status::DataQualityError(
"JDBC special-type CAST produced NULL for a non-nullable column");
}
return Status::OK();
}
std::string JdbcJniReader::connector_class() const {
return "org/apache/doris/jdbc/JdbcJniScanner";
}
Status JdbcJniReader::prepare_split(const format::SplitReadOptions& options) {
{
// End these scopes before JniTableReader enters the same counters; nested use would count
// this JDBC parameter preparation twice instead of extending the common lifecycle total.
SCOPED_TIMER(_profile.total_timer);
SCOPED_TIMER(_profile.prepare_split_timer);
SCOPED_TIMER(connector_total_timer());
_jdbc_params.clear();
if (options.current_range.__isset.table_format_params &&
options.current_range.table_format_params.table_format_type == "jdbc") {
_jdbc_params = std::map<std::string, std::string>(
options.current_range.table_format_params.jdbc_params.begin(),
options.current_range.table_format_params.jdbc_params.end());
}
}
return format::JniTableReader::prepare_split(options);
}
// need pass to the java side, so the java scanner can parse the params and construct the JDBC connection
Status JdbcJniReader::build_scanner_params(std::map<std::string, std::string>* params) const {
DORIS_CHECK(params != nullptr);
*params = _jdbc_params;
if (params->contains("jdbc_driver_url")) {
std::string resolved;
if (JdbcUtils::resolve_driver_url((*params)["jdbc_driver_url"], &resolved).ok()) {
(*params)["jdbc_driver_url"] = resolved;
}
}
return Status::OK();
}
Status JdbcJniReader::build_jni_columns(
std::vector<format::JniTableReader::JniColumn>* columns) const {
DORIS_CHECK(columns != nullptr);
columns->clear();
columns->reserve(_projected_columns.size());
for (size_t i = 0; i < _projected_columns.size(); ++i) {
const auto& table_column = _projected_columns[i];
const auto primitive_type = remove_nullable(table_column.type)->get_primitive_type();
columns->push_back({
.java_name = table_column.name,
.output_index = i,
.output_type = table_column.type,
.transfer_type = _transfer_type_for(table_column.type),
.replace_type = _replace_type_for(primitive_type),
});
}
return Status::OK();
}
Status JdbcJniReader::finalize_jni_block(Block* jni_block, Block* output_block, size_t* rows) {
DORIS_CHECK(jni_block != nullptr);
DORIS_CHECK(output_block != nullptr);
DORIS_CHECK(rows != nullptr);
const auto original_rows = *rows;
const auto& columns = jni_columns();
DORIS_CHECK(columns.size() == jni_block->columns());
for (size_t i = 0; i < columns.size(); ++i) {
const auto& column = columns[i];
DORIS_CHECK(column.output_type != nullptr);
DORIS_CHECK(column.output_index < output_block->columns());
if (_is_special_type(remove_nullable(column.output_type)->get_primitive_type())) {
RETURN_IF_ERROR(_cast_string_to_special_type(column, jni_block, i, output_block,
original_rows));
continue;
}
output_block->get_by_position(column.output_index).type = column.output_type;
output_block->replace_by_position(column.output_index,
jni_block->get_by_position(i).column);
}
DORIS_CHECK(output_block->rows() == original_rows);
if (!_conjuncts.empty()) {
RETURN_IF_ERROR(
VExprContext::filter_block(_conjuncts, output_block, output_block->columns()));
}
*rows = output_block->rows();
return Status::OK();
}
std::string JdbcJniReader::_replace_type_for(PrimitiveType type) const {
switch (type) {
case PrimitiveType::TYPE_BITMAP:
return "bitmap";
case PrimitiveType::TYPE_HLL:
return "hll";
case PrimitiveType::TYPE_QUANTILE_STATE:
return "quantile_state";
case PrimitiveType::TYPE_JSONB:
return "jsonb";
default:
return "not_replace";
}
}
bool JdbcJniReader::_is_special_type(PrimitiveType type) const {
return type == PrimitiveType::TYPE_BITMAP || type == PrimitiveType::TYPE_HLL ||
type == PrimitiveType::TYPE_QUANTILE_STATE || type == PrimitiveType::TYPE_JSONB;
}
DataTypePtr JdbcJniReader::_transfer_type_for(const DataTypePtr& output_type) const {
DORIS_CHECK(output_type != nullptr);
if (!_is_special_type(remove_nullable(output_type)->get_primitive_type())) {
return output_type;
}
DataTypePtr string_type = std::make_shared<DataTypeString>();
if (output_type->is_nullable()) {
string_type = make_nullable(string_type);
}
return string_type;
}
Status JdbcJniReader::_cast_string_to_special_type(const format::JniTableReader::JniColumn& column,
Block* jni_block, size_t jni_column_index,
Block* output_block, size_t rows) {
DORIS_CHECK(column.output_type != nullptr);
DORIS_CHECK(column.transfer_type != nullptr);
const auto target_type = column.output_type;
const auto target_type_name = target_type->get_name();
ColumnPtr input_column = jni_block->get_by_position(jni_column_index).column;
ColumnPtr cast_param = target_type->create_column_const_with_default_value(1);
ColumnsWithTypeAndName argument_template;
argument_template.reserve(2);
argument_template.emplace_back(std::move(input_column), column.transfer_type,
"java.sql.String");
argument_template.emplace_back(std::move(cast_param), target_type, target_type_name);
FunctionBasePtr cast_function = SimpleFunctionFactory::instance().get_function(
"CAST", argument_template, make_nullable(target_type));
if (cast_function == nullptr) {
return Status::InternalError("Failed to find CAST function for type {}", target_type_name);
}
Block cast_block(argument_template);
const auto result_idx = cast_set<uint32_t>(cast_block.columns());
cast_block.insert({nullptr, make_nullable(target_type), "cast_result"});
RETURN_IF_ERROR(
cast_function->execute(nullptr, cast_block, {0}, result_idx, cast_set<int>(rows)));
auto result_column = cast_block.get_by_position(result_idx).column;
output_block->get_by_position(column.output_index).type = target_type;
if (target_type->is_nullable()) {
output_block->replace_by_position(column.output_index, result_column);
} else {
RETURN_IF_ERROR(validate_non_nullable_special_type_result(*result_column, rows));
const auto* nullable_column = assert_cast<const ColumnNullable*>(result_column.get());
output_block->replace_by_position(column.output_index,
nullable_column->get_nested_column_ptr());
}
return Status::OK();
}
} // namespace doris::format::jdbc