Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/duckdb/extension/json/json_common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,14 @@ static inline idx_t ReadInteger(const char *ptr, const char *const end, idx_t &i
static inline JSONKeyReadResult ReadKey(const char *ptr, const char *const end) {
D_ASSERT(ptr != end);
if (*ptr == '*') { // Wildcard
if (*(ptr + 1) == '*') {
if (ptr + 1 != end && *(ptr + 1) == '*') {
return JSONKeyReadResult::RecWildCard();
}
return JSONKeyReadResult::WildCard();
}
bool recursive = false;
if (*ptr == '.') {
char next = *(ptr + 1);
const char next = ptr + 1 == end ? '\0' : *(ptr + 1);
if (next == '*') {
return JSONKeyReadResult::RecWildCard();
}
Expand All @@ -151,6 +151,10 @@ static inline JSONKeyReadResult ReadKey(const char *ptr, const char *const end)
ptr++;
recursive = true;
}
if (ptr == end) {
// recursive '.' with no key following it
return JSONKeyReadResult::Empty();
}
bool escaped = false;
if (*ptr == '"') {
ptr++; // Skip past opening '"'
Expand Down
42 changes: 33 additions & 9 deletions src/duckdb/extension/json/json_functions/json_create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ struct JSONCopyFormatOptions {
optional_ptr<const Expression> timestamptz_ns_format_expression;
};

static JSONCopyFormatOptions ClientFormatOptions(ClientContext &context) {
JSONCopyFormatOptions result;
result.context = context;
return result;
}

struct JSONCopyToJSONFunctionData : public FunctionData {
public:
JSONCopyToJSONFunctionData(StructNames const_struct_names_p, bool has_date_format_p, string date_format_string_p,
Expand Down Expand Up @@ -792,7 +798,18 @@ static void CreateValuesTimestampNS(yyjson_mut_doc *doc, yyjson_mut_val *vals[],

static void CreateValuesTimestampTZ(yyjson_mut_doc *doc, yyjson_mut_val *vals[], Vector &value_v, idx_t count,
const JSONCopyFormatOptions &options) {
CreateValuesFormatted(doc, vals, value_v, count, bool(options.timestamp_format), [&](Vector &string_vector) {
if (!options.timestamp_format) {
// no COPY format was given - use the full cast set so that the session time zone is applied
Vector string_vector(LogicalType::VARCHAR, count);
if (options.context) {
VectorOperations::Cast(*options.context.get_mutable(), value_v, string_vector, count, true);
} else {
VectorOperations::DefaultCast(value_v, string_vector, count);
}
TemplatedCreateValues<string_t, string_t>(doc, vals, string_vector, count);
return;
}
CreateValuesFormatted(doc, vals, value_v, count, true, [&](Vector &string_vector) {
auto format_expression = value_v.GetType().id() == LogicalTypeId::TIMESTAMP_TZ_NS
? options.timestamptz_ns_format_expression
: options.timestamptz_format_expression;
Expand Down Expand Up @@ -973,6 +990,7 @@ static void ObjectFunction(DataChunk &args, ExpressionState &state, Vector &resu
const auto &info = func_expr.BindInfo()->Cast<JSONCreateFunctionData>();
auto &lstate = JSONFunctionLocalState::ResetAndGet(state);
auto alc = lstate.json_allocator->GetYYAlc();
auto options = ClientFormatOptions(state.GetContext());

// Initialize values
const idx_t count = args.size();
Expand All @@ -987,7 +1005,7 @@ static void ObjectFunction(DataChunk &args, ExpressionState &state, Vector &resu
for (idx_t pair_idx = 0; pair_idx < args.data.size() / 2; pair_idx++) {
Vector &key_v = args.data[pair_idx * 2];
Vector &value_v = args.data[pair_idx * 2 + 1];
CreateKeyValuePairs(info.const_struct_names, doc, objs, vals, key_v, value_v, count);
CreateKeyValuePairs(info.const_struct_names, doc, objs, vals, key_v, value_v, count, options);
}
// Write JSON values to string
auto objects = FlatVector::GetDataMutable<string_t>(result);
Expand All @@ -1002,6 +1020,7 @@ static void ArrayFunction(DataChunk &args, ExpressionState &state, Vector &resul
const auto &info = func_expr.BindInfo()->Cast<JSONCreateFunctionData>();
auto &lstate = JSONFunctionLocalState::ResetAndGet(state);
auto alc = lstate.json_allocator->GetYYAlc();
auto options = ClientFormatOptions(state.GetContext());

// Initialize arrays
const idx_t count = args.size();
Expand All @@ -1014,7 +1033,7 @@ static void ArrayFunction(DataChunk &args, ExpressionState &state, Vector &resul
auto vals = JSONCommon::AllocateArray<yyjson_mut_val *>(doc, count);
// Loop through args
for (auto &v : args.data) {
CreateValues(info.const_struct_names, doc, vals, v, count);
CreateValues(info.const_struct_names, doc, vals, v, count, options);
for (idx_t i = 0; i < count; i++) {
yyjson_mut_arr_append(arrs[i], vals[i]);
}
Expand Down Expand Up @@ -1060,8 +1079,9 @@ static void ToJSONFunction(DataChunk &args, ExpressionState &state, Vector &resu
const auto &info = func_expr.BindInfo()->Cast<JSONCreateFunctionData>();
auto &lstate = JSONFunctionLocalState::ResetAndGet(state);
auto alc = lstate.json_allocator->GetYYAlc();
auto options = ClientFormatOptions(state.GetContext());

ToJSONFunctionInternal(info.const_struct_names, args.data[0], args.size(), result, alc);
ToJSONFunctionInternal(info.const_struct_names, args.data[0], args.size(), result, alc, options);
}

static void JSONCopyToJSONFunction(DataChunk &args, ExpressionState &state, Vector &result) {
Expand Down Expand Up @@ -1184,31 +1204,35 @@ ScalarFunctionSet JSONFunctions::GetRowToJSONFunction() {

struct NestedToJSONCastData : public BoundCastData {
public:
NestedToJSONCastData() {
explicit NestedToJSONCastData(optional_ptr<ClientContext> client) : client(client) {
}

unique_ptr<BoundCastData> Copy() const override {
auto result = make_uniq<NestedToJSONCastData>();
auto result = make_uniq<NestedToJSONCastData>(client);
result->const_struct_names = const_struct_names.Copy();
return std::move(result);
}

public:
optional_ptr<ClientContext> client;
StructNames const_struct_names;
};

static bool AnyToJSONCast(Vector &source, Vector &result, idx_t count, CastParameters &parameters) {
auto &lstate = parameters.local_state->Cast<JSONFunctionLocalState>();
lstate.json_allocator->Reset();
auto alc = lstate.json_allocator->GetYYAlc();
const auto &names = parameters.cast_data->Cast<NestedToJSONCastData>().const_struct_names;
auto &cast_data = parameters.cast_data->Cast<NestedToJSONCastData>();
const auto &names = cast_data.const_struct_names;

ToJSONFunctionInternal(names, source, count, result, alc);
JSONCopyFormatOptions options;
options.context = cast_data.client;
ToJSONFunctionInternal(names, source, count, result, alc, options);
return true;
}

static BoundCastInfo AnyToJSONCastBind(BindCastInput &input, const LogicalType &source, const LogicalType &target) {
auto cast_data = make_uniq<NestedToJSONCastData>();
auto cast_data = make_uniq<NestedToJSONCastData>(input.context);
GetJSONType(cast_data->const_struct_names, source);
return BoundCastInfo(AnyToJSONCast, std::move(cast_data), JSONFunctionLocalState::InitCastLocalState);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class ParquetDecimalUtils {
template <class PHYSICAL_TYPE>
static PHYSICAL_TYPE ReadDecimalValue(const_data_ptr_t pointer, idx_t size, const ParquetColumnSchema &) {
PHYSICAL_TYPE res = 0;
if (size == 0) {
// empty byte array - value is zero, and there is no sign byte to read
return res;
}

auto res_ptr = (uint8_t *)&res;
bool positive = (*pointer & 0x80) == 0;
Expand Down
26 changes: 22 additions & 4 deletions src/duckdb/extension/parquet/parquet_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "duckdb/common/types/data_chunk.hpp"
#include "duckdb/common/types/vector.hpp"
#include "duckdb/common/vector_size.hpp"
#include "duckdb/function/scalar/struct_utils.hpp"
#include "duckdb/logging/log_type.hpp"
#include "duckdb/logging/logger.hpp"
#include "duckdb/main/client_context.hpp"
Expand Down Expand Up @@ -1483,8 +1484,8 @@ static FilterPropagateResult CheckParquetFloatFilter(ClientContext &context, Col
return FilterPropagateResult::NO_PRUNING_POSSIBLE;
}

static bool TryGetListBloomFilterLeaf(ColumnReader &column_reader, const Expression &expr,
optional_ptr<ColumnReader> &leaf_reader) {
static bool TryGetNestedBloomFilterLeaf(ColumnReader &column_reader, const Expression &expr,
optional_ptr<ColumnReader> &leaf_reader) {
if (expr.GetExpressionClass() == ExpressionClass::BOUND_REF) {
if (expr.Cast<BoundReferenceExpression>().Index() != 0) {
return false;
Expand All @@ -1498,14 +1499,31 @@ static bool TryGetListBloomFilterLeaf(ColumnReader &column_reader, const Express

auto &function = expr.Cast<BoundFunctionExpression>();
if (function.GetChildren().empty() ||
!TryGetListBloomFilterLeaf(column_reader, *function.GetChildren()[0], leaf_reader)) {
!TryGetNestedBloomFilterLeaf(column_reader, *function.GetChildren()[0], leaf_reader)) {
return false;
}

// Handle LIST type.
if (leaf_reader->Type().id() == LogicalTypeId::LIST &&
(function.Function().GetName() == "list_extract" || function.Function().GetName() == "array_extract")) {
leaf_reader = &leaf_reader->Cast<ListColumnReader>().GetChildReader();
return true;
}

// Handle STRUCT type.
if (leaf_reader->Type().id() == LogicalTypeId::STRUCT) {
idx_t child_idx;
if (!TryGetStructExtractChildIndex(function, child_idx)) {
return false;
}
auto &struct_reader = leaf_reader->Cast<StructColumnReader>();
if (child_idx >= struct_reader.child_readers.size() || !struct_reader.child_readers[child_idx]) {
return false;
}
leaf_reader = struct_reader.child_readers[child_idx].get();
return true;
}

return false;
}

Expand Down Expand Up @@ -1553,7 +1571,7 @@ static bool TryGetBloomFilterLeaf(ColumnReader &column_reader, const TableFilter
return false;
}

if (!TryGetListBloomFilterLeaf(column_reader, *column_expr, leaf_reader) ||
if (!TryGetNestedBloomFilterLeaf(column_reader, *column_expr, leaf_reader) ||
leaf_reader->Type() != constant->GetValue().type()) {
return false;
}
Expand Down
39 changes: 33 additions & 6 deletions src/duckdb/extension/parquet/parquet_writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -913,16 +913,43 @@ struct DecimalStatsUnifier : public NumericStatsUnifier<T> {
if (stats.empty()) {
return string();
}

auto stats_data = const_data_ptr_cast(stats.data());

if (sizeof(T) == sizeof(hugeint_t)) {
auto _schema = ParquetColumnSchema();
auto numeric_val = ParquetDecimalUtils::ReadDecimalValue<hugeint_t>(stats_data, stats.size(), _schema);
auto schema = ParquetColumnSchema(); // schema unused for FLBA/hugeint_t
auto numeric_val = ParquetDecimalUtils::ReadDecimalValue<hugeint_t>(stats_data, stats.size(), schema);
return Value::DECIMAL(numeric_val, width, scale).ToString();
} else {
auto numeric_val = Load<T>(stats_data);
return Value::DECIMAL(numeric_val, width, scale).ToString();
return Value::DECIMAL(Load<T>(stats_data), width, scale).ToString();
}
}

void UnifyMinMax(const string &new_min, const string &new_max) override {
if (sizeof(T) != sizeof(hugeint_t)) {
// INT32/INT64-backed decimals are little-endian; the base compare is correct.
BaseNumericStatsUnifier<T>::UnifyMinMax(new_min, new_max);
} else {
// FLBA decimal stats are big-endian two's complement (most significant byte
// first), so a native little-endian Load would compare the wrong value; decode
// the bytes into a hugeint_t before comparing.
auto decode = [](const string &stats) {
auto schema = ParquetColumnSchema(); // schema unused for FLBA/hugeint_t
return ParquetDecimalUtils::ReadDecimalValue<hugeint_t>(const_data_ptr_cast(stats.data()), stats.size(),
schema);
};

if (!this->min_is_set) {
this->global_min = new_min;
this->min_is_set = true;
} else if (LessThan::Operation(decode(new_min), decode(this->global_min))) {
this->global_min = new_min;
}

if (!this->max_is_set) {
this->global_max = new_max;
this->max_is_set = true;
} else if (GreaterThan::Operation(decode(new_max), decode(this->global_max))) {
this->global_max = new_max;
}
}
}
};
Expand Down
4 changes: 4 additions & 0 deletions src/duckdb/extension/parquet/reader/decimal_column_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ template <>
double ParquetDecimalUtils::ReadDecimalValue(const_data_ptr_t pointer, idx_t size,
const ParquetColumnSchema &schema_ele) {
double res = 0;
if (size == 0) {
// empty byte array - value is zero, and there is no sign byte to read
return res;
}
bool positive = (*pointer & 0x80) == 0;
for (idx_t i = 0; i < size; i += 8) {
auto byte_size = MinValue<idx_t>(sizeof(uint64_t), size - i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ static uint8_t EncodeMetadataHeader(idx_t byte_length) {
uint8_t header_byte = 0;
//! Set 'version' to 1
header_byte |= static_cast<uint8_t>(1);
//! Set 'sorted_strings' to 1
header_byte |= static_cast<uint8_t>(1) << 4;
//! NOTE: keep 'sorted_strings' at 0, we don't always sort the key strings
//! Set 'offset_size_minus_one' to byte_length-1
header_byte |= (static_cast<uint8_t>(byte_length) - 1) << 6;

Expand Down
53 changes: 45 additions & 8 deletions src/duckdb/src/catalog/catalog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,13 +556,19 @@ vector<SimilarCatalogEntry> Catalog::SimilarEntriesInSchemas(ClientContext &cont
}

vector<CatalogSearchEntry> GetCatalogEntries(CatalogEntryRetriever &retriever, const Identifier &catalog,
const Identifier &schema) {
const Identifier &schema, CatalogType lookup_type = CatalogType::INVALID) {
auto &context = retriever.GetContext();
vector<CatalogSearchEntry> entries;
auto &search_path = retriever.GetSearchPath();
if (IsInvalidCatalog(catalog) && IsInvalidSchema(schema)) {
// no catalog or schema provided - scan the entire search path
entries = search_path.Get();
// no catalog or schema provided - scan the entire search path.
if (lookup_type == CatalogType::INVALID || lookup_type == CatalogType::TABLE_ENTRY) {
entries = search_path.Get();
} else {
// for non-table lookups, resolve implicit catalogs that requested precedence to their default schema in
// place
entries = search_path.GetWithPrecedenceSchemas(context);
}
} else if (IsInvalidCatalog(catalog)) {
auto catalogs = search_path.GetCatalogsForSchema(schema);
for (auto &catalog_name : catalogs) {
Expand Down Expand Up @@ -971,7 +977,7 @@ CatalogEntryLookup Catalog::TryLookupEntryAcrossCatalogs(CatalogEntryRetriever &
lookups.emplace_back(*catalog_entry, lookup_info);
return TryLookupEntry(retriever, lookups, lookup_info, if_not_found, false);
}
auto entries = GetCatalogEntries(retriever, catalog, schema);
auto entries = GetCatalogEntries(retriever, catalog, schema, lookup_info.GetCatalogType());
vector<CatalogLookup> final_lookups;
lookups.reserve(entries.size());
for (auto &entry : entries) {
Expand All @@ -997,9 +1003,9 @@ CatalogEntryLookup Catalog::TryLookupEntryAcrossCatalogs(CatalogEntryRetriever &
lookups.emplace_back(std::move(lookup));
}

bool allow_default_table_lookup = catalog.empty() && schema.empty();
bool allow_default_lookup = catalog.empty() && schema.empty();

return TryLookupEntry(retriever, lookups, lookup_info, if_not_found, allow_default_table_lookup);
return TryLookupEntry(retriever, lookups, lookup_info, if_not_found, allow_default_lookup);
}

CatalogEntryLookup Catalog::LookupEntry(CatalogEntryRetriever &retriever, const EntryLookupInfo &lookup_info,
Expand Down Expand Up @@ -1041,7 +1047,7 @@ static void ThrowDefaultTableAmbiguityException(CatalogEntryLookup &base_lookup,

CatalogEntryLookup Catalog::TryLookupEntry(CatalogEntryRetriever &retriever, const vector<CatalogLookup> &lookups,
const EntryLookupInfo &lookup_info, OnEntryNotFound if_not_found,
bool allow_default_table_lookup) {
bool allow_default_lookup) {
auto &context = retriever.GetContext();
reference_set_t<SchemaCatalogEntry> schemas;
bool all_errors = true;
Expand All @@ -1065,7 +1071,7 @@ CatalogEntryLookup Catalog::TryLookupEntry(CatalogEntryRetriever &retriever, con

// Special case for tables: we do a second lookup searching for catalogs with default tables that also match this
// lookup
if (lookup_info.GetCatalogType() == CatalogType::TABLE_ENTRY && allow_default_table_lookup) {
if (lookup_info.GetCatalogType() == CatalogType::TABLE_ENTRY && allow_default_lookup) {
if (!result.Found()) {
result = TryLookupDefaultTable(retriever, lookup_info, false);
if (result.error.HasError()) {
Expand All @@ -1081,6 +1087,15 @@ CatalogEntryLookup Catalog::TryLookupEntry(CatalogEntryRetriever &retriever, con
}
}

// Special case for non-table entries (functions, macros, types): Fall back to the default schema of any
// catalog flagged as an implicit search catalog. Ignored if the schema already had precedence.
if (lookup_info.GetCatalogType() != CatalogType::TABLE_ENTRY && allow_default_lookup && !result.Found()) {
result = TryLookupDefaultSchema(retriever, lookup_info);
if (result.error.HasError()) {
error_data = std::move(result.error);
}
}

if (result.Found()) {
return result;
}
Expand Down Expand Up @@ -1156,6 +1171,28 @@ CatalogEntryLookup Catalog::TryLookupDefaultTable(CatalogEntryRetriever &retriev
return {nullptr, nullptr, ErrorData()};
}

CatalogEntryLookup Catalog::TryLookupDefaultSchema(CatalogEntryRetriever &retriever,
const EntryLookupInfo &lookup_info) {
// look for the entry in the default schema of every catalog that was flagged as an implicit search catalog
auto &search_path = retriever.GetSearchPath();
for (auto &implicit_catalog : search_path.GetImplicitSearchCatalogs()) {
auto catalog_entry = GetCatalogEntry(retriever, implicit_catalog.GetCatalog());
if (!catalog_entry) {
continue;
}
auto transaction = catalog_entry->GetCatalogTransaction(retriever.GetContext());
EntryLookupInfo default_schema_lookup(lookup_info, QualifiedName(catalog_entry->GetName(),
Identifier(catalog_entry->GetDefaultSchema()),
lookup_info.GetEntryIdentifier()));
auto result = catalog_entry->TryLookupEntryInternal(transaction, default_schema_lookup);
if (result.Found() || result.error.HasError()) {
return result;
}
}

return {nullptr, nullptr, ErrorData()};
}

optional_ptr<CatalogEntry> Catalog::GetEntry(CatalogEntryRetriever &retriever, const EntryLookupInfo &lookup_info,
OnEntryNotFound if_not_found) {
auto result = TryLookupEntryAcrossCatalogs(retriever, lookup_info, if_not_found);
Expand Down
Loading
Loading