/src/duckdb/extension/json/json_schema_inference.cpp
Line | Count | Source |
1 | | #include "json_schema_inference.hpp" |
2 | | |
3 | | #include "duckdb/common/enum_util.hpp" |
4 | | #include "json_geojson.hpp" |
5 | | #include "json_scan.hpp" |
6 | | #include "json_transform.hpp" |
7 | | #include "duckdb/common/types/timestamp.hpp" |
8 | | |
9 | | namespace duckdb { |
10 | | |
11 | 740 | static bool IsNumeric(LogicalTypeId type) { |
12 | 740 | return type == LogicalTypeId::DOUBLE || type == LogicalTypeId::UBIGINT || type == LogicalTypeId::BIGINT || |
13 | 179 | type == LogicalTypeId::HUGEINT; |
14 | 740 | } |
15 | | |
16 | 104 | static LogicalTypeId MaxNumericType(const LogicalTypeId &a, const LogicalTypeId &b) { |
17 | 104 | D_ASSERT(a != b); |
18 | 104 | if (a == LogicalTypeId::DOUBLE || b == LogicalTypeId::DOUBLE) { |
19 | 99 | return LogicalTypeId::DOUBLE; |
20 | 99 | } |
21 | 5 | if (a == LogicalTypeId::HUGEINT || b == LogicalTypeId::HUGEINT) { |
22 | 2 | return LogicalTypeId::HUGEINT; |
23 | 2 | } |
24 | | // One is BIGINT and the other is UBIGINT - need HUGEINT to represent both ranges |
25 | 3 | if ((a == LogicalTypeId::BIGINT && b == LogicalTypeId::UBIGINT) || |
26 | 3 | (a == LogicalTypeId::UBIGINT && b == LogicalTypeId::BIGINT)) { |
27 | 3 | return LogicalTypeId::HUGEINT; |
28 | 3 | } |
29 | 0 | return LogicalTypeId::BIGINT; |
30 | 3 | } |
31 | | |
32 | | JSONStructureNode::JSONStructureNode(const bool case_insensitive_keys_p) |
33 | 10.1k | : count(0), null_count(0), case_insensitive_keys(case_insensitive_keys_p) { |
34 | 10.1k | } |
35 | | |
36 | | JSONStructureNode::JSONStructureNode(const char *key_ptr, const size_t key_len, const bool case_insensitive_keys_p) |
37 | 80 | : JSONStructureNode(case_insensitive_keys_p) { |
38 | 80 | key = make_uniq<string>(key_ptr, key_len); |
39 | 80 | } |
40 | | |
41 | 8 | JSONStructureNode JSONStructureNode::EmptyLike(const JSONStructureNode &parent) { |
42 | 8 | return JSONStructureNode(parent.case_insensitive_keys); |
43 | 8 | } |
44 | | |
45 | 0 | static void SwapJSONStructureNode(JSONStructureNode &a, JSONStructureNode &b) noexcept { |
46 | 0 | std::swap(a.key, b.key); |
47 | 0 | std::swap(a.initialized, b.initialized); |
48 | 0 | std::swap(a.descriptions, b.descriptions); |
49 | 0 | std::swap(a.count, b.count); |
50 | 0 | std::swap(a.null_count, b.null_count); |
51 | 0 | std::swap(a.case_insensitive_keys, b.case_insensitive_keys); |
52 | 0 | } |
53 | | |
54 | 0 | JSONStructureNode::JSONStructureNode(JSONStructureNode &&other) noexcept { |
55 | 0 | SwapJSONStructureNode(*this, other); |
56 | 0 | } |
57 | | |
58 | 0 | JSONStructureNode &JSONStructureNode::operator=(JSONStructureNode &&other) noexcept { |
59 | 0 | SwapJSONStructureNode(*this, other); |
60 | 0 | return *this; |
61 | 0 | } |
62 | | |
63 | 4.54k | JSONStructureDescription &JSONStructureNode::GetOrCreateDescription(const LogicalTypeId type) { |
64 | 4.54k | if (descriptions.empty()) { |
65 | | // Empty, just put this type in there |
66 | 3.91k | descriptions.emplace_back(type, case_insensitive_keys); |
67 | 3.91k | return descriptions.back(); |
68 | 3.91k | } |
69 | | |
70 | 628 | if (descriptions.size() == 1 && descriptions[0].type == LogicalTypeId::SQLNULL) { |
71 | | // Only a NULL in there, override |
72 | 0 | descriptions[0].type = type; |
73 | 0 | return descriptions[0]; |
74 | 0 | } |
75 | | |
76 | 628 | if (type == LogicalTypeId::SQLNULL) { |
77 | | // 'descriptions' is non-empty, so let's not add NULL |
78 | 0 | return descriptions.back(); |
79 | 0 | } |
80 | | |
81 | | // Check if type is already in there or if we can merge numerics |
82 | 628 | const auto is_numeric = IsNumeric(type); |
83 | 732 | for (auto &description : descriptions) { |
84 | 732 | if (type == description.type) { |
85 | 480 | return description; |
86 | 480 | } |
87 | 252 | if (is_numeric && IsNumeric(description.type)) { |
88 | 104 | description.type = MaxNumericType(type, description.type); |
89 | 104 | return description; |
90 | 104 | } |
91 | 252 | } |
92 | | // Type was not there, create a new description |
93 | 44 | descriptions.emplace_back(type, case_insensitive_keys); |
94 | 44 | return descriptions.back(); |
95 | 628 | } |
96 | | |
97 | 2.41k | bool JSONStructureNode::ContainsVarchar() const { |
98 | 2.41k | if (descriptions.size() != 1) { |
99 | | // We can't refine types if we have more than 1 description (yet), defaults to JSON type for now |
100 | 106 | return false; |
101 | 106 | } |
102 | 2.31k | auto &description = descriptions[0]; |
103 | 2.31k | if (description.type == LogicalTypeId::VARCHAR) { |
104 | 757 | return true; |
105 | 757 | } |
106 | 1.55k | for (auto &child : description.children) { |
107 | 685 | if (child.ContainsVarchar()) { |
108 | 22 | return true; |
109 | 22 | } |
110 | 685 | } |
111 | | |
112 | 1.53k | return false; |
113 | 1.55k | } |
114 | | |
115 | | void JSONStructureNode::InitializeCandidateTypes(const idx_t max_depth, const bool convert_strings_to_integers, |
116 | 384 | const bool user_specified_timestamp_format, const idx_t depth) { |
117 | 384 | if (depth >= max_depth) { |
118 | 0 | return; |
119 | 0 | } |
120 | 384 | if (descriptions.size() != 1) { |
121 | | // We can't refine types if we have more than 1 description (yet), defaults to JSON type for now |
122 | 0 | return; |
123 | 0 | } |
124 | 384 | auto &description = descriptions[0]; |
125 | 384 | if (description.type == LogicalTypeId::VARCHAR && !initialized) { |
126 | | // We loop through the candidate types and format templates from back to front. |
127 | | // TIMESTAMP_TZ is only a candidate when the user did not pass timestamp_format=; never infer that |
128 | | // from the date-format map (auto-detect can also converge on a single format). |
129 | | // Trial order puts TIMESTAMP_TZ before TIMESTAMP so back-to-front tries TZ first (issue #14919). |
130 | 373 | if (convert_strings_to_integers) { |
131 | 0 | if (user_specified_timestamp_format) { |
132 | 0 | description.candidate_types = {LogicalTypeId::UUID, LogicalTypeId::BIGINT, LogicalTypeId::TIMESTAMP, |
133 | 0 | LogicalTypeId::DATE, LogicalTypeId::TIME}; |
134 | 0 | } else { |
135 | 0 | description.candidate_types = {LogicalTypeId::UUID, LogicalTypeId::BIGINT, |
136 | 0 | LogicalTypeId::TIMESTAMP, LogicalTypeId::TIMESTAMP_TZ, |
137 | 0 | LogicalTypeId::DATE, LogicalTypeId::TIME}; |
138 | 0 | } |
139 | 373 | } else if (user_specified_timestamp_format) { |
140 | 0 | description.candidate_types = {LogicalTypeId::UUID, LogicalTypeId::TIMESTAMP, LogicalTypeId::DATE, |
141 | 0 | LogicalTypeId::TIME}; |
142 | 373 | } else { |
143 | 373 | description.candidate_types = {LogicalTypeId::UUID, LogicalTypeId::TIMESTAMP, LogicalTypeId::TIMESTAMP_TZ, |
144 | 373 | LogicalTypeId::DATE, LogicalTypeId::TIME}; |
145 | 373 | } |
146 | 373 | initialized = true; |
147 | 373 | } else { |
148 | 11 | for (auto &child : description.children) { |
149 | 11 | child.InitializeCandidateTypes(max_depth, convert_strings_to_integers, user_specified_timestamp_format, |
150 | 11 | depth + 1); |
151 | 11 | } |
152 | 11 | } |
153 | 384 | } |
154 | | |
155 | | void JSONStructureNode::RefineCandidateTypes(yyjson_val *vals[], const idx_t val_count, Vector &string_vector, |
156 | | ArenaAllocator &allocator, MutableDateFormatMap &date_format_map, |
157 | 384 | const bool user_specified_timestamp_format) { |
158 | 384 | if (descriptions.size() != 1) { |
159 | | // We can't refine types if we have more than 1 description (yet), defaults to JSON type for now |
160 | 0 | return; |
161 | 0 | } |
162 | 384 | if (!ContainsVarchar()) { |
163 | 0 | return; |
164 | 0 | } |
165 | 384 | auto &description = descriptions[0]; |
166 | 384 | switch (description.type) { |
167 | 5 | case LogicalTypeId::LIST: |
168 | 5 | return RefineCandidateTypesArray(vals, val_count, string_vector, allocator, date_format_map, |
169 | 5 | user_specified_timestamp_format); |
170 | 6 | case LogicalTypeId::STRUCT: |
171 | 6 | return RefineCandidateTypesObject(vals, val_count, string_vector, allocator, date_format_map, |
172 | 6 | user_specified_timestamp_format); |
173 | 373 | case LogicalTypeId::VARCHAR: |
174 | 373 | return RefineCandidateTypesString(vals, val_count, string_vector, date_format_map, |
175 | 373 | user_specified_timestamp_format); |
176 | 0 | default: |
177 | 0 | return; |
178 | 384 | } |
179 | 384 | } |
180 | | |
181 | | void JSONStructureNode::RefineCandidateTypesArray(yyjson_val *vals[], const idx_t val_count, Vector &string_vector, |
182 | | ArenaAllocator &allocator, MutableDateFormatMap &date_format_map, |
183 | 5 | const bool user_specified_timestamp_format) { |
184 | 5 | D_ASSERT(descriptions.size() == 1 && descriptions[0].type == LogicalTypeId::LIST); |
185 | 5 | auto &desc = descriptions[0]; |
186 | 5 | D_ASSERT(desc.children.size() == 1); |
187 | 5 | auto &child = desc.children[0]; |
188 | | |
189 | 5 | idx_t total_list_size = 0; |
190 | 10 | for (idx_t i = 0; i < val_count; i++) { |
191 | 5 | if (vals[i] && !unsafe_yyjson_is_null(vals[i])) { |
192 | 5 | D_ASSERT(yyjson_is_arr(vals[i])); |
193 | 5 | total_list_size += unsafe_yyjson_get_len(vals[i]); |
194 | 5 | } |
195 | 5 | } |
196 | | |
197 | 5 | idx_t offset = 0; |
198 | 5 | auto child_vals = |
199 | 5 | reinterpret_cast<yyjson_val **>(allocator.AllocateAligned(total_list_size * sizeof(yyjson_val *))); |
200 | | |
201 | 5 | size_t idx, max; |
202 | 5 | yyjson_val *child_val; |
203 | 10 | for (idx_t i = 0; i < val_count; i++) { |
204 | 5 | if (vals[i] && !unsafe_yyjson_is_null(vals[i])) { |
205 | 9 | yyjson_arr_foreach(vals[i], idx, max, child_val) { |
206 | 9 | child_vals[offset++] = child_val; |
207 | 9 | } |
208 | 5 | } |
209 | 5 | } |
210 | 5 | child.RefineCandidateTypes(child_vals, total_list_size, string_vector, allocator, date_format_map, |
211 | 5 | user_specified_timestamp_format); |
212 | 5 | } |
213 | | |
214 | | void JSONStructureNode::RefineCandidateTypesObject(yyjson_val *vals[], const idx_t val_count, Vector &string_vector, |
215 | | ArenaAllocator &allocator, MutableDateFormatMap &date_format_map, |
216 | 6 | const bool user_specified_timestamp_format) { |
217 | 6 | D_ASSERT(descriptions.size() == 1 && descriptions[0].type == LogicalTypeId::STRUCT); |
218 | 6 | auto &desc = descriptions[0]; |
219 | | |
220 | 6 | const idx_t child_count = desc.children.size(); |
221 | 6 | vector<yyjson_val **> child_vals; |
222 | 6 | child_vals.reserve(child_count); |
223 | 12 | for (idx_t child_idx = 0; child_idx < child_count; child_idx++) { |
224 | 6 | child_vals.emplace_back( |
225 | 6 | reinterpret_cast<yyjson_val **>(allocator.AllocateAligned(val_count * sizeof(yyjson_val *)))); |
226 | 6 | } |
227 | | |
228 | 6 | const auto found_keys = reinterpret_cast<bool *>(allocator.AllocateAligned(sizeof(bool) * child_count)); |
229 | | |
230 | 6 | const auto &key_map = desc.key_map; |
231 | 6 | size_t idx, max; |
232 | 6 | yyjson_val *child_key, *child_val; |
233 | 12 | for (idx_t i = 0; i < val_count; i++) { |
234 | 6 | if (vals[i] && !unsafe_yyjson_is_null(vals[i])) { |
235 | 6 | idx_t found_key_count = 0; |
236 | 6 | memset(found_keys, false, child_count); |
237 | | |
238 | 6 | D_ASSERT(yyjson_is_obj(vals[i])); |
239 | 6 | yyjson_obj_foreach(vals[i], idx, max, child_key, child_val) { |
240 | 6 | D_ASSERT(yyjson_is_str(child_key)); |
241 | 6 | const auto key_ptr = unsafe_yyjson_get_str(child_key); |
242 | 6 | const auto key_len = unsafe_yyjson_get_len(child_key); |
243 | 6 | auto it = key_map.find({key_ptr, key_len}); |
244 | 6 | D_ASSERT(it != key_map.end()); |
245 | 6 | const auto child_idx = it->second; |
246 | 6 | child_vals[child_idx][i] = child_val; |
247 | 6 | found_key_count += !found_keys[child_idx]; |
248 | 6 | found_keys[child_idx] = true; |
249 | 6 | } |
250 | | |
251 | 6 | if (found_key_count != child_count) { |
252 | | // Set child val to nullptr so recursion doesn't break |
253 | 0 | for (idx_t child_idx = 0; child_idx < child_count; child_idx++) { |
254 | 0 | if (!found_keys[child_idx]) { |
255 | 0 | child_vals[child_idx][i] = nullptr; |
256 | 0 | } |
257 | 0 | } |
258 | 0 | } |
259 | 6 | } else { |
260 | 0 | for (idx_t child_idx = 0; child_idx < child_count; child_idx++) { |
261 | 0 | child_vals[child_idx][i] = nullptr; |
262 | 0 | } |
263 | 0 | } |
264 | 6 | } |
265 | | |
266 | 12 | for (idx_t child_idx = 0; child_idx < child_count; child_idx++) { |
267 | 6 | desc.children[child_idx].RefineCandidateTypes(child_vals[child_idx], val_count, string_vector, allocator, |
268 | 6 | date_format_map, user_specified_timestamp_format); |
269 | 6 | } |
270 | 6 | } |
271 | | |
272 | | void JSONStructureNode::RefineCandidateTypesString(yyjson_val *vals[], const idx_t val_count, Vector &string_vector, |
273 | | MutableDateFormatMap &date_format_map, |
274 | 373 | const bool user_specified_timestamp_format) { |
275 | 373 | D_ASSERT(descriptions.size() == 1 && descriptions[0].type == LogicalTypeId::VARCHAR); |
276 | 373 | if (descriptions[0].candidate_types.empty()) { |
277 | 0 | return; |
278 | 0 | } |
279 | 373 | static JSONTransformOptions OPTIONS; |
280 | 373 | JSONTransform::GetStringVector(vals, val_count, LogicalType::SQLNULL, string_vector, OPTIONS); |
281 | 373 | EliminateCandidateTypes(val_count, string_vector, date_format_map, user_specified_timestamp_format); |
282 | 373 | } |
283 | | |
284 | | // Single commutative transition table, shared by per-vector refinement and parallel merges, |
285 | | // so thread scheduling cannot change the detected type. UNKNOWN is the identity, MIXED is sticky. |
286 | 368 | static TimestampOffsetState MergeTimestampOffsetState(const TimestampOffsetState a, const TimestampOffsetState b) { |
287 | 368 | if (a == TimestampOffsetState::UNKNOWN) { |
288 | 368 | return b; |
289 | 368 | } |
290 | 0 | if (b == TimestampOffsetState::UNKNOWN) { |
291 | 0 | return a; |
292 | 0 | } |
293 | 0 | if (a != b) { |
294 | 0 | return TimestampOffsetState::MIXED; |
295 | 0 | } |
296 | 0 | return a; |
297 | 0 | } |
298 | | |
299 | | // Date::TryConvertDate reuses has_offset for specials; only Z / ±HH[:MM] is a real offset. |
300 | 3 | static bool TimestampStringHasUtcOffset(const string_t &value) { |
301 | 3 | const auto str = value.GetData(); |
302 | 3 | const auto len = value.GetSize(); |
303 | 3 | idx_t time_start = 0; |
304 | 18 | for (idx_t i = 0; i < len; i++) { |
305 | 15 | if (str[i] == 'T' || str[i] == ' ') { |
306 | 0 | time_start = i + 1; |
307 | 0 | break; |
308 | 0 | } |
309 | 15 | } |
310 | 3 | if (time_start == 0 || time_start >= len) { |
311 | 3 | return false; |
312 | 3 | } |
313 | 0 | for (idx_t i = time_start; i < len; i++) { |
314 | 0 | if (str[i] == 'Z') { |
315 | 0 | return true; |
316 | 0 | } |
317 | 0 | if (str[i] == '+' || str[i] == '-') { |
318 | 0 | idx_t pos = i; |
319 | 0 | int hh, mm, ss; |
320 | 0 | return Timestamp::TryParseUTCOffset(str, pos, len, hh, mm, ss); |
321 | 0 | } |
322 | 0 | } |
323 | 0 | return false; |
324 | 0 | } |
325 | | |
326 | | // Update the sticky offset state from this vector's non-null strings. |
327 | | // MIXED → VARCHAR: neither TIMESTAMPTZ (would invent session TZ for naive values) |
328 | | // nor TIMESTAMP (would drop offsets) is safe. |
329 | | static void UpdateTimestampOffsetState(JSONStructureDescription &description, const Vector &string_vector, |
330 | 373 | const idx_t count) { |
331 | 373 | const auto strings = FlatVector::GetData<string_t>(string_vector); |
332 | 373 | const auto &validity = FlatVector::Validity(string_vector); |
333 | 750 | for (idx_t i = 0; i < count; i++) { |
334 | 377 | if (!validity.RowIsValid(i)) { |
335 | 0 | continue; // nulls leave the state unchanged |
336 | 0 | } |
337 | 377 | timestamp_t ts; |
338 | 377 | bool has_offset = false; |
339 | 377 | string_t tz(nullptr, 0); |
340 | 377 | auto res = |
341 | 377 | Timestamp::TryConvertTimestampTZ(strings[i].GetData(), strings[i].GetSize(), ts, true, has_offset, tz); |
342 | 377 | if (res != TimestampCastResult::SUCCESS && res != TimestampCastResult::STRICT_UTC) { |
343 | 373 | continue; // not a timestamp string — other candidates handle it |
344 | 373 | } |
345 | 4 | if (!ts.IsFinite()) { |
346 | 0 | continue; // ±infinity is valid for TIMESTAMP and TIMESTAMPTZ |
347 | 0 | } |
348 | 4 | if (has_offset && !TimestampStringHasUtcOffset(strings[i])) { |
349 | 3 | continue; // date special such as epoch, not a real offset |
350 | 3 | } |
351 | 1 | description.timestamp_offset_state = MergeTimestampOffsetState( |
352 | 1 | description.timestamp_offset_state, |
353 | 1 | has_offset ? TimestampOffsetState::WITH_OFFSET : TimestampOffsetState::WITHOUT_OFFSET); |
354 | 1 | if (description.timestamp_offset_state == TimestampOffsetState::MIXED) { |
355 | 0 | return; |
356 | 0 | } |
357 | 1 | } |
358 | 373 | } |
359 | | |
360 | | void JSONStructureNode::EliminateCandidateTypes(const idx_t vec_count, Vector &string_vector, |
361 | | MutableDateFormatMap &date_format_map, |
362 | 373 | const bool user_specified_timestamp_format) { |
363 | 373 | D_ASSERT(descriptions.size() == 1 && descriptions[0].type == LogicalTypeId::VARCHAR); |
364 | 373 | auto &description = descriptions[0]; |
365 | 373 | auto &candidate_types = description.candidate_types; |
366 | | |
367 | | // Monotonic across vectors: once MIXED, refuse to guess. |
368 | 373 | if (!user_specified_timestamp_format) { |
369 | 373 | UpdateTimestampOffsetState(description, string_vector, vec_count); |
370 | 373 | if (description.timestamp_offset_state == TimestampOffsetState::MIXED) { |
371 | 0 | candidate_types.clear(); |
372 | 0 | return; |
373 | 0 | } |
374 | 373 | } |
375 | | |
376 | 2.18k | while (true) { |
377 | 2.17k | if (candidate_types.empty()) { |
378 | 358 | return; |
379 | 358 | } |
380 | 1.81k | const auto type = candidate_types.back(); |
381 | 1.81k | Vector result_vector(type, vec_count); |
382 | 1.81k | if (type == LogicalTypeId::TIMESTAMP_TZ) { |
383 | | // Defense in depth: InitializeCandidateTypes already omits TZ when the user set timestamp_format=. |
384 | 359 | if (user_specified_timestamp_format) { |
385 | 0 | candidate_types.pop_back(); |
386 | 0 | continue; |
387 | 0 | } |
388 | | // Only accept TIMESTAMPTZ when every non-null value seen so far carries an offset/Z. |
389 | | // UNKNOWN (e.g., null-only vectors) and WITHOUT_OFFSET drop TZ and try TIMESTAMP (etc.). |
390 | 359 | if (description.timestamp_offset_state != TimestampOffsetState::WITH_OFFSET) { |
391 | 359 | candidate_types.pop_back(); |
392 | 359 | continue; |
393 | 359 | } |
394 | 0 | string error_message; |
395 | 0 | if (!VectorOperations::DefaultTryCast(string_vector, result_vector, vec_count, &error_message, true)) { |
396 | 0 | candidate_types.pop_back(); |
397 | 0 | } else { |
398 | 0 | return; |
399 | 0 | } |
400 | 1.46k | } else if (date_format_map.HasFormats(type)) { |
401 | 729 | if (EliminateCandidateFormats(vec_count, string_vector, result_vector, date_format_map)) { |
402 | 3 | return; |
403 | 726 | } else { |
404 | 726 | candidate_types.pop_back(); |
405 | 726 | } |
406 | 731 | } else { |
407 | 731 | string error_message; |
408 | 731 | if (!VectorOperations::DefaultTryCast(string_vector, result_vector, vec_count, &error_message, true)) { |
409 | 728 | candidate_types.pop_back(); |
410 | 728 | } else { |
411 | 3 | return; |
412 | 3 | } |
413 | 731 | } |
414 | 1.81k | } |
415 | 373 | } |
416 | | |
417 | | template <class OP, class T> |
418 | 6.46k | bool TryParse(const Vector &string_vector, StrpTimeFormat &format, const idx_t count) { |
419 | 6.46k | const auto strings = FlatVector::GetData<string_t>(string_vector); |
420 | 6.46k | const auto &validity = FlatVector::Validity(string_vector); |
421 | | |
422 | 6.46k | T result; |
423 | 6.46k | string error_message; |
424 | 6.46k | if (validity.CannotHaveNull()) { |
425 | 0 | for (idx_t i = 0; i < count; i++) { |
426 | 0 | if (!OP::template Operation<T>(format, strings[i], result, error_message)) { |
427 | 0 | return false; |
428 | 0 | } |
429 | 0 | } |
430 | 6.46k | } else { |
431 | 6.47k | for (idx_t i = 0; i < count; i++) { |
432 | 6.46k | if (validity.RowIsValid(i)) { |
433 | 6.46k | if (!OP::template Operation<T>(format, strings[i], result, error_message)) { |
434 | 6.45k | return false; |
435 | 6.45k | } |
436 | 6.46k | } |
437 | 6.46k | } |
438 | 6.46k | } |
439 | 12 | return true; |
440 | 6.46k | } bool duckdb::TryParse<duckdb::TryParseDate, duckdb::date_t>(duckdb::Vector const&, duckdb::StrpTimeFormat&, unsigned long) Line | Count | Source | 418 | 2.16k | bool TryParse(const Vector &string_vector, StrpTimeFormat &format, const idx_t count) { | 419 | 2.16k | const auto strings = FlatVector::GetData<string_t>(string_vector); | 420 | 2.16k | const auto &validity = FlatVector::Validity(string_vector); | 421 | | | 422 | 2.16k | T result; | 423 | 2.16k | string error_message; | 424 | 2.16k | if (validity.CannotHaveNull()) { | 425 | 0 | for (idx_t i = 0; i < count; i++) { | 426 | 0 | if (!OP::template Operation<T>(format, strings[i], result, error_message)) { | 427 | 0 | return false; | 428 | 0 | } | 429 | 0 | } | 430 | 2.16k | } else { | 431 | 2.17k | for (idx_t i = 0; i < count; i++) { | 432 | 2.16k | if (validity.RowIsValid(i)) { | 433 | 2.16k | if (!OP::template Operation<T>(format, strings[i], result, error_message)) { | 434 | 2.15k | return false; | 435 | 2.15k | } | 436 | 2.16k | } | 437 | 2.16k | } | 438 | 2.16k | } | 439 | 11 | return true; | 440 | 2.16k | } |
bool duckdb::TryParse<duckdb::TryParseTimeStamp, duckdb::timestamp_base_t<1000000l, false> >(duckdb::Vector const&, duckdb::StrpTimeFormat&, unsigned long) Line | Count | Source | 418 | 4.29k | bool TryParse(const Vector &string_vector, StrpTimeFormat &format, const idx_t count) { | 419 | 4.29k | const auto strings = FlatVector::GetData<string_t>(string_vector); | 420 | 4.29k | const auto &validity = FlatVector::Validity(string_vector); | 421 | | | 422 | 4.29k | T result; | 423 | 4.29k | string error_message; | 424 | 4.29k | if (validity.CannotHaveNull()) { | 425 | 0 | for (idx_t i = 0; i < count; i++) { | 426 | 0 | if (!OP::template Operation<T>(format, strings[i], result, error_message)) { | 427 | 0 | return false; | 428 | 0 | } | 429 | 0 | } | 430 | 4.29k | } else { | 431 | 4.29k | for (idx_t i = 0; i < count; i++) { | 432 | 4.29k | if (validity.RowIsValid(i)) { | 433 | 4.29k | if (!OP::template Operation<T>(format, strings[i], result, error_message)) { | 434 | 4.29k | return false; | 435 | 4.29k | } | 436 | 4.29k | } | 437 | 4.29k | } | 438 | 4.29k | } | 439 | 1 | return true; | 440 | 4.29k | } |
|
441 | | |
442 | | bool JSONStructureNode::EliminateCandidateFormats(const idx_t vec_count, Vector &string_vector, |
443 | 729 | const Vector &result_vector, MutableDateFormatMap &date_format_map) { |
444 | 729 | D_ASSERT(descriptions.size() == 1 && descriptions[0].type == LogicalTypeId::VARCHAR); |
445 | | |
446 | 729 | const auto type = result_vector.GetType().id(); |
447 | 729 | auto i = date_format_map.NumberOfFormats(type); |
448 | 7.17k | for (; i != 0; i--) { |
449 | 6.46k | StrpTimeFormat format; |
450 | 6.46k | if (!date_format_map.GetFormatAtIndex(type, i - 1, format)) { |
451 | 0 | continue; |
452 | 0 | } |
453 | | |
454 | 6.46k | bool success; |
455 | 6.46k | switch (type) { |
456 | 2.16k | case LogicalTypeId::DATE: |
457 | 2.16k | success = TryParse<TryParseDate, date_t>(string_vector, format, vec_count); |
458 | 2.16k | break; |
459 | 4.29k | case LogicalTypeId::TIMESTAMP: |
460 | 4.29k | success = TryParse<TryParseTimeStamp, timestamp_t>(string_vector, format, vec_count); |
461 | 4.29k | break; |
462 | 0 | default: |
463 | 0 | throw InternalException("No date/timestamp formats for %s", EnumUtil::ToString(type)); |
464 | 6.46k | } |
465 | | |
466 | 6.45k | if (success) { |
467 | 3 | return true; |
468 | 3 | } |
469 | 6.45k | } |
470 | | |
471 | 717 | return false; |
472 | 729 | } |
473 | | |
474 | | JSONStructureDescription::JSONStructureDescription(const LogicalTypeId type_p, const bool case_insensitive_keys) |
475 | 3.96k | : type(type_p), |
476 | 3.96k | key_map(0, JSONKeyOptionalCaseHash {case_insensitive_keys}, JSONKeyOptionalCaseEquality {case_insensitive_keys}) { |
477 | 3.96k | } |
478 | | |
479 | 44 | static void SwapJSONStructureDescription(JSONStructureDescription &a, JSONStructureDescription &b) noexcept { |
480 | 44 | std::swap(a.type, b.type); |
481 | 44 | std::swap(a.key_map, b.key_map); |
482 | 44 | std::swap(a.children, b.children); |
483 | 44 | std::swap(a.candidate_types, b.candidate_types); |
484 | 44 | std::swap(a.has_large_ubigint, b.has_large_ubigint); |
485 | 44 | std::swap(a.timestamp_offset_state, b.timestamp_offset_state); |
486 | 44 | } |
487 | | |
488 | 44 | JSONStructureDescription::JSONStructureDescription(JSONStructureDescription &&other) noexcept { |
489 | 44 | SwapJSONStructureDescription(*this, other); |
490 | 44 | } |
491 | | |
492 | 0 | JSONStructureDescription &JSONStructureDescription::operator=(JSONStructureDescription &&other) noexcept { |
493 | 0 | SwapJSONStructureDescription(*this, other); |
494 | 0 | return *this; |
495 | 0 | } |
496 | | |
497 | 1.34k | JSONStructureNode &JSONStructureDescription::GetOrCreateChild() { |
498 | 1.34k | D_ASSERT(type == LogicalTypeId::LIST); |
499 | 1.34k | if (children.empty()) { |
500 | 1.33k | children.emplace_back(key_map.hash_function().case_insensitive); |
501 | 1.33k | } |
502 | 1.34k | D_ASSERT(children.size() == 1); |
503 | 1.34k | return children.back(); |
504 | 1.34k | } |
505 | | |
506 | 88 | JSONStructureNode &JSONStructureDescription::GetOrCreateChild(const char *key_ptr, const size_t key_size) { |
507 | | // Check if there is already a child with the same key |
508 | 88 | const JSONKey temp_key {key_ptr, key_size}; |
509 | 88 | const auto it = key_map.find(temp_key); |
510 | 88 | if (it != key_map.end()) { |
511 | 8 | return children[it->second]; // Found it |
512 | 8 | } |
513 | | |
514 | | // Didn't find, create a new child (the first spelling of a key names it when keys are case-insensitive) |
515 | 80 | children.emplace_back(key_ptr, key_size, key_map.hash_function().case_insensitive); |
516 | 80 | const auto &persistent_key_string = *children.back().key; |
517 | 80 | JSONKey new_key {persistent_key_string.c_str(), persistent_key_string.length()}; |
518 | 80 | key_map.emplace(new_key, children.size() - 1); |
519 | 80 | return children.back(); |
520 | 88 | } |
521 | | |
522 | | JSONStructureNode &JSONStructureDescription::GetOrCreateChild(yyjson_val *key, yyjson_val *val, |
523 | | const bool ignore_errors, const bool detect_geojson, |
524 | 44 | const idx_t depth) { |
525 | 44 | D_ASSERT(yyjson_is_str(key)); |
526 | 44 | auto &child = GetOrCreateChild(unsafe_yyjson_get_str(key), unsafe_yyjson_get_len(key)); |
527 | 44 | JSONStructure::ExtractStructure(val, child, ignore_errors, detect_geojson, depth); |
528 | 44 | return child; |
529 | 44 | } |
530 | | |
531 | | static void ExtractStructureArray(yyjson_val *arr, JSONStructureNode &node, const bool ignore_errors, |
532 | 673 | const bool detect_geojson, const idx_t depth) { |
533 | 673 | D_ASSERT(yyjson_is_arr(arr)); |
534 | 673 | auto &description = node.GetOrCreateDescription(LogicalTypeId::LIST); |
535 | 673 | auto &child = description.GetOrCreateChild(); |
536 | | |
537 | 673 | size_t idx, max; |
538 | 673 | yyjson_val *val; |
539 | 1.16k | yyjson_arr_foreach(arr, idx, max, val) { |
540 | 1.16k | JSONStructure::ExtractStructure(val, child, ignore_errors, detect_geojson, depth + 1); |
541 | 1.16k | } |
542 | 673 | } |
543 | | |
544 | | static void ExtractStructureObject(yyjson_val *obj, JSONStructureNode &node, const bool ignore_errors, |
545 | 85 | const bool detect_geojson, const idx_t depth) { |
546 | 85 | D_ASSERT(yyjson_is_obj(obj)); |
547 | 85 | auto &description = node.GetOrCreateDescription(LogicalTypeId::STRUCT); |
548 | | |
549 | | // Keep track of keys so we can detect duplicates |
550 | 85 | unordered_set<string> obj_keys; |
551 | 85 | case_insensitive_set_t ci_obj_keys; |
552 | | |
553 | 85 | size_t idx, max; |
554 | 85 | yyjson_val *key, *val; |
555 | 85 | yyjson_obj_foreach(obj, idx, max, key, val) { |
556 | 44 | const string obj_key(unsafe_yyjson_get_str(key), unsafe_yyjson_get_len(key)); |
557 | 44 | auto insert_result = obj_keys.insert(obj_key); |
558 | 44 | if (!ignore_errors && !insert_result.second) { // Exact match |
559 | 0 | JSONCommon::ThrowValFormatError("Duplicate key \"" + obj_key + "\" in object %s", obj); |
560 | 0 | } |
561 | 44 | insert_result = ci_obj_keys.insert(obj_key); |
562 | 44 | if (!ignore_errors && !insert_result.second) { // Case-insensitive match |
563 | 0 | JSONCommon::ThrowValFormatError("Duplicate key (different case) \"" + obj_key + "\" and \"" + |
564 | 0 | *insert_result.first + "\" in object %s", |
565 | 0 | obj); |
566 | 0 | } |
567 | 44 | description.GetOrCreateChild(key, val, ignore_errors, detect_geojson, depth + 1); |
568 | 44 | } |
569 | 85 | } |
570 | | |
571 | 1.79k | static void ExtractStructureVal(yyjson_val *val, JSONStructureNode &node) { |
572 | 1.79k | D_ASSERT(!yyjson_is_arr(val) && !yyjson_is_obj(val)); |
573 | 1.79k | const auto val_type = JSONCommon::ValTypeToLogicalTypeId(val); |
574 | 1.79k | auto &desc = node.GetOrCreateDescription(val_type); |
575 | 1.79k | if (val_type == LogicalTypeId::UBIGINT && |
576 | 576 | unsafe_yyjson_get_uint(val) > static_cast<uint64_t>(NumericLimits<int64_t>::Maximum())) { |
577 | 29 | desc.has_large_ubigint = true; |
578 | 29 | } |
579 | 1.79k | } |
580 | | |
581 | | void JSONStructure::ExtractStructure(yyjson_val *val, JSONStructureNode &node, const bool ignore_errors, |
582 | 2.55k | const bool detect_geojson, const idx_t depth) { |
583 | 2.55k | if (depth >= JSONCommon::MAX_RECURSION_DEPTH) { |
584 | 0 | throw InvalidInputException("JSON exceeds maximum recursion depth of %d", JSONCommon::MAX_RECURSION_DEPTH); |
585 | 0 | } |
586 | 2.55k | node.count++; |
587 | 2.55k | const auto tag = yyjson_get_tag(val); |
588 | 2.55k | if (tag == (YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE)) { |
589 | 4 | node.null_count++; |
590 | 4 | } |
591 | | |
592 | 2.55k | switch (tag) { |
593 | 673 | case YYJSON_TYPE_ARR | YYJSON_SUBTYPE_NONE: |
594 | 673 | return ExtractStructureArray(val, node, ignore_errors, detect_geojson, depth); |
595 | 85 | case YYJSON_TYPE_OBJ | YYJSON_SUBTYPE_NONE: |
596 | | // A GeoJSON geometry is a leaf: it becomes GEOMETRY rather than a struct of type/coordinates. Detecting it |
597 | | // here (rather than on the finished tree) means geometries of different kinds still merge into one type. |
598 | 85 | if (detect_geojson && JSONGeometry::IsGeoJSONGeometry(val)) { |
599 | 0 | node.GetOrCreateDescription(LogicalTypeId::GEOMETRY); |
600 | 0 | return; |
601 | 0 | } |
602 | 85 | return ExtractStructureObject(val, node, ignore_errors, detect_geojson, depth); |
603 | 1.79k | default: |
604 | 1.79k | return ExtractStructureVal(val, node); |
605 | 2.55k | } |
606 | 2.55k | } |
607 | | |
608 | | static LogicalType StructureToTypeArray(ClientContext &context, const JSONStructureNode &node, const idx_t max_depth, |
609 | | const double field_appearance_threshold, const idx_t map_inference_threshold, |
610 | 646 | const idx_t depth, const LogicalType &null_type) { |
611 | 646 | D_ASSERT(node.descriptions.size() == 1 && node.descriptions[0].type == LogicalTypeId::LIST); |
612 | 646 | const auto &desc = node.descriptions[0]; |
613 | 646 | D_ASSERT(desc.children.size() == 1); |
614 | | |
615 | 646 | return LogicalType::LIST(JSONStructure::StructureToType(context, desc.children[0], max_depth, |
616 | 646 | field_appearance_threshold, map_inference_threshold, |
617 | 646 | depth + 1, null_type)); |
618 | 646 | } |
619 | | |
620 | 673 | static void MergeNodeArray(JSONStructureNode &merged, const JSONStructureDescription &child_desc) { |
621 | 673 | D_ASSERT(child_desc.type == LogicalTypeId::LIST); |
622 | 673 | auto &merged_desc = merged.GetOrCreateDescription(LogicalTypeId::LIST); |
623 | 673 | auto &merged_child = merged_desc.GetOrCreateChild(); |
624 | 673 | for (auto &list_child : child_desc.children) { |
625 | 673 | JSONStructure::MergeNodes(merged_child, list_child); |
626 | 673 | } |
627 | 673 | } |
628 | | |
629 | 85 | static void MergeNodeObject(JSONStructureNode &merged, const JSONStructureDescription &child_desc) { |
630 | 85 | D_ASSERT(child_desc.type == LogicalTypeId::STRUCT); |
631 | 85 | auto &merged_desc = merged.GetOrCreateDescription(LogicalTypeId::STRUCT); |
632 | 85 | for (auto &struct_child : child_desc.children) { |
633 | 44 | const auto &struct_child_key = *struct_child.key; |
634 | 44 | auto &merged_child = merged_desc.GetOrCreateChild(struct_child_key.c_str(), struct_child_key.length()); |
635 | 44 | JSONStructure::MergeNodes(merged_child, struct_child); |
636 | 44 | } |
637 | 85 | } |
638 | | |
639 | | static void MergeNodeVal(JSONStructureNode &merged, const JSONStructureDescription &child_desc, |
640 | 1.23k | const bool node_initialized) { |
641 | 1.23k | D_ASSERT(child_desc.type != LogicalTypeId::LIST && child_desc.type != LogicalTypeId::STRUCT); |
642 | 1.23k | auto &merged_desc = merged.GetOrCreateDescription(child_desc.type); |
643 | 1.23k | if (child_desc.has_large_ubigint) { |
644 | 29 | merged_desc.has_large_ubigint = true; |
645 | 29 | } |
646 | 1.23k | if (merged_desc.type != LogicalTypeId::VARCHAR || !node_initialized || merged.descriptions.size() != 1) { |
647 | 866 | return; |
648 | 866 | } |
649 | | // Sticky offset state across parallel auto-detect tasks |
650 | 367 | merged_desc.timestamp_offset_state = |
651 | 367 | MergeTimestampOffsetState(merged_desc.timestamp_offset_state, child_desc.timestamp_offset_state); |
652 | 367 | if (merged_desc.timestamp_offset_state == TimestampOffsetState::MIXED) { |
653 | 0 | merged_desc.candidate_types.clear(); // mixed offset/plain → VARCHAR |
654 | 0 | merged.initialized = true; |
655 | 0 | return; |
656 | 0 | } |
657 | 367 | if (!merged.initialized) { |
658 | 367 | merged_desc.candidate_types = child_desc.candidate_types; |
659 | 367 | } else if (merged_desc.candidate_types.empty() != child_desc.candidate_types.empty() // both empty or neither empty |
660 | 0 | || (!merged_desc.candidate_types.empty() && |
661 | 0 | merged_desc.candidate_types.back() != child_desc.candidate_types.back())) { // non-empty: check type |
662 | 0 | merged_desc.candidate_types.clear(); // Not the same, default to VARCHAR |
663 | 0 | } |
664 | | |
665 | 367 | merged.initialized = true; |
666 | 367 | } |
667 | | |
668 | 2.15k | void JSONStructure::MergeNodes(JSONStructureNode &merged, const JSONStructureNode &node) { |
669 | 2.15k | merged.count += node.count; |
670 | 2.15k | merged.null_count += node.null_count; |
671 | 2.15k | for (const auto &child_desc : node.descriptions) { |
672 | 1.99k | switch (child_desc.type) { |
673 | 673 | case LogicalTypeId::LIST: |
674 | 673 | MergeNodeArray(merged, child_desc); |
675 | 673 | break; |
676 | 85 | case LogicalTypeId::STRUCT: |
677 | 85 | MergeNodeObject(merged, child_desc); |
678 | 85 | break; |
679 | 1.23k | default: |
680 | 1.23k | MergeNodeVal(merged, child_desc, node.initialized); |
681 | 1.23k | break; |
682 | 1.99k | } |
683 | 1.99k | } |
684 | 2.15k | } |
685 | | |
686 | | static double CalculateTypeSimilarity(const LogicalType &merged, const LogicalType &type, idx_t max_depth, idx_t depth); |
687 | | |
688 | | static double CalculateMapAndStructSimilarity(const LogicalType &map_type, const LogicalType &struct_type, |
689 | 0 | const bool swapped, const idx_t max_depth, const idx_t depth) { |
690 | 0 | const auto &map_value_type = MapType::ValueType(map_type); |
691 | 0 | const auto &struct_child_types = StructType::GetChildTypes(struct_type); |
692 | 0 | double total_similarity = 0; |
693 | 0 | for (const auto &struct_child_type : struct_child_types) { |
694 | 0 | const auto similarity = |
695 | 0 | swapped ? CalculateTypeSimilarity(struct_child_type.second, map_value_type, max_depth, depth + 1) |
696 | 0 | : CalculateTypeSimilarity(map_value_type, struct_child_type.second, max_depth, depth + 1); |
697 | 0 | if (similarity < 0) { |
698 | 0 | return similarity; |
699 | 0 | } |
700 | 0 | total_similarity += similarity; |
701 | 0 | } |
702 | 0 | return total_similarity / static_cast<double>(struct_child_types.size()); |
703 | 0 | } |
704 | | |
705 | | static double CalculateTypeSimilarity(const LogicalType &merged, const LogicalType &type, const idx_t max_depth, |
706 | 0 | const idx_t depth) { |
707 | 0 | if (depth >= max_depth || merged.id() == LogicalTypeId::SQLNULL || type.id() == LogicalTypeId::SQLNULL) { |
708 | 0 | return 1; |
709 | 0 | } |
710 | 0 | if (merged.IsJSONType()) { |
711 | | // Incompatible types |
712 | 0 | return -1; |
713 | 0 | } |
714 | 0 | if (type.IsJSONType() || merged == type) { |
715 | 0 | return 1; |
716 | 0 | } |
717 | | |
718 | 0 | switch (merged.id()) { |
719 | 0 | case LogicalTypeId::STRUCT: { |
720 | 0 | if (type.id() == LogicalTypeId::MAP) { |
721 | | // This can happen for empty structs/maps ("{}"), or in rare cases where an inconsistent struct becomes |
722 | | // consistent when merged, but does not have enough children to be considered a map. |
723 | 0 | return CalculateMapAndStructSimilarity(type, merged, true, max_depth, depth); |
724 | 0 | } else if (type.id() != LogicalTypeId::STRUCT) { |
725 | 0 | return -1; |
726 | 0 | } |
727 | | |
728 | | // Only structs can be merged into a struct |
729 | 0 | D_ASSERT(type.id() == LogicalTypeId::STRUCT); |
730 | 0 | const auto &merged_child_types = StructType::GetChildTypes(merged); |
731 | 0 | const auto &type_child_types = StructType::GetChildTypes(type); |
732 | |
|
733 | 0 | identifier_map_t<const LogicalType &> merged_child_types_map; |
734 | 0 | for (const auto &merged_child : merged_child_types) { |
735 | 0 | merged_child_types_map.emplace(merged_child.first, merged_child.second); |
736 | 0 | } |
737 | |
|
738 | 0 | double total_similarity = 0; |
739 | 0 | for (const auto &type_child_type : type_child_types) { |
740 | 0 | const auto it = merged_child_types_map.find(type_child_type.first); |
741 | 0 | if (it == merged_child_types_map.end()) { |
742 | 0 | return -1; |
743 | 0 | } |
744 | 0 | const auto similarity = CalculateTypeSimilarity(it->second, type_child_type.second, max_depth, depth + 1); |
745 | 0 | if (similarity < 0) { |
746 | 0 | return similarity; |
747 | 0 | } |
748 | 0 | total_similarity += similarity; |
749 | 0 | } |
750 | 0 | return total_similarity / static_cast<double>(merged_child_types.size()); |
751 | 0 | } |
752 | 0 | case LogicalTypeId::MAP: { |
753 | 0 | if (type.id() == LogicalTypeId::MAP) { |
754 | 0 | return CalculateTypeSimilarity(MapType::ValueType(merged), MapType::ValueType(type), max_depth, depth + 1); |
755 | 0 | } |
756 | | |
757 | | // Only maps and structs can be merged into a map |
758 | 0 | if (type.id() != LogicalTypeId::STRUCT) { |
759 | 0 | return -1; |
760 | 0 | } |
761 | 0 | return CalculateMapAndStructSimilarity(merged, type, false, max_depth, depth); |
762 | 0 | } |
763 | 0 | case LogicalTypeId::LIST: { |
764 | | // Only lists can be merged into a list |
765 | 0 | if (type.id() != LogicalTypeId::LIST) { |
766 | 0 | return -1; |
767 | 0 | } |
768 | 0 | D_ASSERT(type.id() == LogicalTypeId::LIST); |
769 | 0 | const auto &merged_child_type = ListType::GetChildType(merged); |
770 | 0 | const auto &type_child_type = ListType::GetChildType(type); |
771 | 0 | return CalculateTypeSimilarity(merged_child_type, type_child_type, max_depth, depth + 1); |
772 | 0 | } |
773 | 0 | default: |
774 | | // This is only reachable if type has been inferred using candidate_types, but candidate_types were not |
775 | | // consistent among all map values |
776 | 0 | return 1; |
777 | 0 | } |
778 | 0 | } |
779 | | |
780 | | static bool IsStructureInconsistent(const JSONStructureDescription &desc, const idx_t sample_count, |
781 | 20 | const idx_t null_count, const double field_appearance_threshold) { |
782 | 20 | D_ASSERT(sample_count > null_count); |
783 | 20 | double total_child_counts = 0; |
784 | 20 | for (const auto &child : desc.children) { |
785 | 20 | total_child_counts += static_cast<double>(child.count) / static_cast<double>(sample_count - null_count); |
786 | 20 | } |
787 | 20 | const auto avg_occurrence = total_child_counts / static_cast<double>(desc.children.size()); |
788 | 20 | return avg_occurrence < field_appearance_threshold; |
789 | 20 | } |
790 | | |
791 | | static LogicalType GetMergedType(ClientContext &context, const JSONStructureNode &node, const idx_t max_depth, |
792 | | const double field_appearance_threshold, const idx_t map_inference_threshold, |
793 | 8 | const idx_t depth, const LogicalType &null_type) { |
794 | 8 | D_ASSERT(node.descriptions.size() == 1); |
795 | 8 | auto &desc = node.descriptions[0]; |
796 | 8 | auto merged = JSONStructureNode::EmptyLike(node); |
797 | 8 | for (const auto &child : desc.children) { |
798 | 8 | JSONStructure::MergeNodes(merged, child); |
799 | 8 | } |
800 | 8 | return JSONStructure::StructureToType(context, merged, max_depth, field_appearance_threshold, |
801 | 8 | map_inference_threshold, depth + 1, null_type); |
802 | 8 | } |
803 | | |
804 | | static LogicalType StructureToTypeObject(ClientContext &context, const JSONStructureNode &node, const idx_t max_depth, |
805 | | const double field_appearance_threshold, const idx_t map_inference_threshold, |
806 | 69 | const idx_t depth, const LogicalType &null_type) { |
807 | 69 | D_ASSERT(node.descriptions.size() == 1 && node.descriptions[0].type == LogicalTypeId::STRUCT); |
808 | 69 | auto &desc = node.descriptions[0]; |
809 | | |
810 | 69 | if (desc.children.empty()) { |
811 | 41 | if (map_inference_threshold != DConstants::INVALID_INDEX) { |
812 | | // Empty struct - use MAP(VARCHAR, JSON) as a generic container; |
813 | | // JSON() is safe for all output formats and is not affected by ExchangeNullType |
814 | 41 | return LogicalType::MAP(LogicalType::VARCHAR, LogicalType::JSON()); |
815 | 41 | } else { |
816 | 0 | return LogicalType::JSON(); |
817 | 0 | } |
818 | 41 | } |
819 | | |
820 | | // An empty key can't be a STRUCT member name, so such objects can only be a MAP or JSON |
821 | 28 | bool has_empty_key = false; |
822 | 28 | for (auto &child : desc.children) { |
823 | 28 | D_ASSERT(child.key); |
824 | 28 | has_empty_key = has_empty_key || child.key->empty(); |
825 | 28 | } |
826 | 28 | if (has_empty_key && map_inference_threshold == DConstants::INVALID_INDEX) { |
827 | 0 | return LogicalType::JSON(); |
828 | 0 | } |
829 | | |
830 | | // If it's an inconsistent object we also just do MAP with the best-possible, recursively-merged value type |
831 | 28 | if (has_empty_key || (map_inference_threshold != DConstants::INVALID_INDEX && |
832 | 20 | IsStructureInconsistent(desc, node.count, node.null_count, field_appearance_threshold))) { |
833 | 8 | return LogicalType::MAP(LogicalType::VARCHAR, |
834 | 8 | GetMergedType(context, node, max_depth, field_appearance_threshold, |
835 | 8 | map_inference_threshold, depth + 1, null_type)); |
836 | 8 | } |
837 | | |
838 | | // We have a consistent object |
839 | 20 | child_list_t<LogicalType> child_types; |
840 | 20 | child_types.reserve(desc.children.size()); |
841 | 20 | for (auto &child : desc.children) { |
842 | 20 | D_ASSERT(child.key); |
843 | 20 | child_types.emplace_back(*child.key, |
844 | 20 | JSONStructure::StructureToType(context, child, max_depth, field_appearance_threshold, |
845 | 20 | map_inference_threshold, depth + 1, null_type)); |
846 | 20 | } |
847 | | |
848 | | // If we have many children and all children have similar-enough types we infer map |
849 | 20 | if (desc.children.size() >= map_inference_threshold) { |
850 | 0 | LogicalType map_value_type = GetMergedType(context, node, max_depth, field_appearance_threshold, |
851 | 0 | map_inference_threshold, depth + 1, LogicalTypeId::SQLNULL); |
852 | |
|
853 | 0 | double total_similarity = 0; |
854 | 0 | for (const auto &child_type : child_types) { |
855 | 0 | const auto similarity = CalculateTypeSimilarity(map_value_type, child_type.second, max_depth, depth + 1); |
856 | 0 | if (similarity < 0) { |
857 | 0 | total_similarity = similarity; |
858 | 0 | break; |
859 | 0 | } |
860 | 0 | total_similarity += similarity; |
861 | 0 | } |
862 | 0 | const auto avg_similarity = total_similarity / static_cast<double>(child_types.size()); |
863 | 0 | if (avg_similarity >= 0.8) { |
864 | 0 | if (null_type != LogicalTypeId::SQLNULL) { |
865 | 0 | map_value_type = GetMergedType(context, node, max_depth, field_appearance_threshold, |
866 | 0 | map_inference_threshold, depth + 1, null_type); |
867 | 0 | } |
868 | 0 | return LogicalType::MAP(LogicalType::VARCHAR, map_value_type); |
869 | 0 | } |
870 | 0 | } |
871 | | |
872 | 20 | return LogicalType::STRUCT(child_types); |
873 | 20 | } |
874 | | |
875 | 361 | static LogicalType StructureToTypeString(const JSONStructureNode &node) { |
876 | 361 | D_ASSERT(node.descriptions.size() == 1 && node.descriptions[0].type == LogicalTypeId::VARCHAR); |
877 | 361 | auto &desc = node.descriptions[0]; |
878 | 361 | if (desc.candidate_types.empty()) { |
879 | 358 | return LogicalTypeId::VARCHAR; |
880 | 358 | } |
881 | 3 | return desc.candidate_types.back(); |
882 | 361 | } |
883 | | |
884 | | LogicalType JSONStructure::StructureToType(ClientContext &context, const JSONStructureNode &node, const idx_t max_depth, |
885 | | const double field_appearance_threshold, const idx_t map_inference_threshold, |
886 | 2.10k | const idx_t depth, const LogicalType &null_type) { |
887 | 2.10k | if (depth >= max_depth) { |
888 | 0 | return LogicalType::JSON(); |
889 | 0 | } |
890 | 2.10k | if (node.descriptions.empty()) { |
891 | 191 | return LogicalType::JSON(); // no data observed, fall back to generic JSON type |
892 | 191 | } |
893 | 1.91k | if (node.descriptions.size() != 1) { // Inconsistent types, so we resort to JSON |
894 | 11 | return LogicalType::JSON(); |
895 | 11 | } |
896 | 1.90k | auto &desc = node.descriptions[0]; |
897 | 1.90k | D_ASSERT(desc.type != LogicalTypeId::INVALID); |
898 | 1.90k | switch (desc.type) { |
899 | 646 | case LogicalTypeId::LIST: |
900 | 646 | return StructureToTypeArray(context, node, max_depth, field_appearance_threshold, map_inference_threshold, |
901 | 646 | depth, null_type); |
902 | 69 | case LogicalTypeId::STRUCT: |
903 | 69 | return StructureToTypeObject(context, node, max_depth, field_appearance_threshold, map_inference_threshold, |
904 | 69 | depth, null_type); |
905 | 361 | case LogicalTypeId::VARCHAR: |
906 | 361 | return StructureToTypeString(node); |
907 | 382 | case LogicalTypeId::UBIGINT: |
908 | 382 | if (desc.has_large_ubigint) { |
909 | 29 | return LogicalTypeId::HUGEINT; |
910 | 29 | } |
911 | 353 | return LogicalTypeId::BIGINT; |
912 | 4 | case LogicalTypeId::SQLNULL: |
913 | 4 | return LogicalType::SQLNULL; |
914 | 0 | case LogicalTypeId::GEOMETRY: |
915 | 0 | return LogicalType::GEOMETRY(); |
916 | 441 | default: |
917 | 441 | return desc.type; |
918 | 1.90k | } |
919 | 1.90k | } |
920 | | |
921 | | } // namespace duckdb |