duckdb 0.8.2-dev3250.0 → 0.8.2-dev3300.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/duckdb/extension/parquet/column_reader.cpp +18 -4
  3. package/src/duckdb/extension/parquet/include/parquet_timestamp.hpp +1 -0
  4. package/src/duckdb/extension/parquet/include/parquet_writer.hpp +4 -0
  5. package/src/duckdb/extension/parquet/parquet_extension.cpp +3 -0
  6. package/src/duckdb/extension/parquet/parquet_statistics.cpp +18 -2
  7. package/src/duckdb/extension/parquet/parquet_timestamp.cpp +6 -0
  8. package/src/duckdb/extension/parquet/parquet_writer.cpp +69 -15
  9. package/src/duckdb/src/common/enum_util.cpp +5 -0
  10. package/src/duckdb/src/common/operator/cast_operators.cpp +57 -0
  11. package/src/duckdb/src/common/operator/string_cast.cpp +45 -8
  12. package/src/duckdb/src/common/types/time.cpp +105 -0
  13. package/src/duckdb/src/common/types/value.cpp +7 -8
  14. package/src/duckdb/src/common/types/vector.cpp +1 -1
  15. package/src/duckdb/src/execution/column_binding_resolver.cpp +7 -0
  16. package/src/duckdb/src/execution/operator/schema/{physical_create_index.cpp → physical_create_art_index.cpp} +37 -46
  17. package/src/duckdb/src/execution/physical_plan/plan_create_index.cpp +12 -4
  18. package/src/duckdb/src/function/cast/string_cast.cpp +2 -1
  19. package/src/duckdb/src/function/cast/time_casts.cpp +7 -6
  20. package/src/duckdb/src/function/table/version/pragma_version.cpp +2 -2
  21. package/src/duckdb/src/include/duckdb/catalog/catalog_entry/index_catalog_entry.hpp +1 -0
  22. package/src/duckdb/src/include/duckdb/common/enums/index_type.hpp +3 -2
  23. package/src/duckdb/src/include/duckdb/common/operator/cast_operators.hpp +22 -1
  24. package/src/duckdb/src/include/duckdb/common/operator/string_cast.hpp +1 -1
  25. package/src/duckdb/src/include/duckdb/common/types/datetime.hpp +46 -3
  26. package/src/duckdb/src/include/duckdb/common/types/time.hpp +5 -0
  27. package/src/duckdb/src/include/duckdb/common/types/value.hpp +2 -1
  28. package/src/duckdb/src/include/duckdb/execution/operator/schema/{physical_create_index.hpp → physical_create_art_index.hpp} +5 -5
  29. package/src/duckdb/src/include/duckdb/function/copy_function.hpp +6 -1
  30. package/src/duckdb/src/include/duckdb/function/udf_function.hpp +2 -1
  31. package/src/duckdb/src/include/duckdb/parser/parsed_data/create_index_info.hpp +5 -0
  32. package/src/duckdb/src/include/duckdb/planner/operator/logical_extension_operator.hpp +3 -0
  33. package/src/duckdb/src/include/duckdb/storage/data_table.hpp +1 -1
  34. package/src/duckdb/src/main/appender.cpp +3 -1
  35. package/src/duckdb/src/main/capi/result-c.cpp +3 -1
  36. package/src/duckdb/src/parser/parsed_data/create_index_info.cpp +14 -0
  37. package/src/duckdb/src/parser/transform/statement/transform_create_index.cpp +27 -13
  38. package/src/duckdb/src/planner/binder/statement/bind_export.cpp +29 -4
  39. package/src/duckdb/src/planner/operator/logical_extension_operator.cpp +15 -0
  40. package/src/duckdb/src/storage/index.cpp +3 -25
  41. package/src/duckdb/src/storage/local_storage.cpp +0 -1
  42. package/src/duckdb/src/storage/serialization/serialize_create_info.cpp +4 -0
  43. package/src/duckdb/src/storage/storage_info.cpp +1 -1
  44. package/src/duckdb/ub_src_execution_operator_schema.cpp +1 -1
  45. package/test/test_all_types.test.ts +1 -1
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "duckdb",
3
3
  "main": "./lib/duckdb.js",
4
4
  "types": "./lib/duckdb.d.ts",
5
- "version": "0.8.2-dev3250.0",
5
+ "version": "0.8.2-dev3300.0",
6
6
  "description": "DuckDB node.js API",
7
7
  "gypfile": true,
8
8
  "dependencies": {
@@ -507,13 +507,13 @@ idx_t ColumnReader::Read(uint64_t num_values, parquet_filter_t &filter, data_ptr
507
507
  // TODO keep this in the state
508
508
  auto read_buf = make_shared<ResizeableBuffer>();
509
509
 
510
- switch (type.InternalType()) {
511
- case PhysicalType::INT32:
510
+ switch (schema.type) {
511
+ case duckdb_parquet::format::Type::INT32:
512
512
  read_buf->resize(reader.allocator, sizeof(int32_t) * (read_now - null_count));
513
513
  dbp_decoder->GetBatch<int32_t>(read_buf->ptr, read_now - null_count);
514
514
 
515
515
  break;
516
- case PhysicalType::INT64:
516
+ case duckdb_parquet::format::Type::INT64:
517
517
  read_buf->resize(reader.allocator, sizeof(int64_t) * (read_now - null_count));
518
518
  dbp_decoder->GetBatch<int64_t>(read_buf->ptr, read_now - null_count);
519
519
  break;
@@ -1389,7 +1389,6 @@ unique_ptr<ColumnReader> ColumnReader::CreateReader(ParquetReader &reader, const
1389
1389
  return make_uniq<CallbackColumnReader<int32_t, date_t, ParquetIntToDate>>(reader, type_p, schema_p, file_idx_p,
1390
1390
  max_define, max_repeat);
1391
1391
  case LogicalTypeId::TIME:
1392
- case LogicalTypeId::TIME_TZ:
1393
1392
  if (schema_p.__isset.logicalType && schema_p.logicalType.__isset.TIME) {
1394
1393
  if (schema_p.logicalType.TIME.unit.__isset.MILLIS) {
1395
1394
  return make_uniq<CallbackColumnReader<int32_t, dtime_t, ParquetIntToTimeMs>>(
@@ -1413,6 +1412,21 @@ unique_ptr<ColumnReader> ColumnReader::CreateReader(ParquetReader &reader, const
1413
1412
  break;
1414
1413
  }
1415
1414
  }
1415
+ case LogicalTypeId::TIME_TZ:
1416
+ if (schema_p.__isset.logicalType && schema_p.logicalType.__isset.TIME) {
1417
+ if (schema_p.logicalType.TIME.unit.__isset.MICROS) {
1418
+ return make_uniq<CallbackColumnReader<int64_t, dtime_tz_t, ParquetIntToTimeTZ>>(
1419
+ reader, type_p, schema_p, file_idx_p, max_define, max_repeat);
1420
+ }
1421
+ } else if (schema_p.__isset.converted_type) {
1422
+ switch (schema_p.converted_type) {
1423
+ case ConvertedType::TIME_MICROS:
1424
+ return make_uniq<CallbackColumnReader<int64_t, dtime_tz_t, ParquetIntToTimeTZ>>(
1425
+ reader, type_p, schema_p, file_idx_p, max_define, max_repeat);
1426
+ default:
1427
+ break;
1428
+ }
1429
+ }
1416
1430
  case LogicalTypeId::BLOB:
1417
1431
  case LogicalTypeId::VARCHAR:
1418
1432
  return make_uniq<StringColumnReader>(reader, type_p, schema_p, file_idx_p, max_define, max_repeat);
@@ -25,5 +25,6 @@ date_t ParquetIntToDate(const int32_t &raw_date);
25
25
  dtime_t ParquetIntToTimeMs(const int32_t &raw_time);
26
26
  dtime_t ParquetIntToTime(const int64_t &raw_time);
27
27
  dtime_t ParquetIntToTimeNs(const int64_t &raw_time);
28
+ dtime_tz_t ParquetIntToTimeTZ(const int64_t &raw_time);
28
29
 
29
30
  } // namespace duckdb
@@ -75,7 +75,11 @@ public:
75
75
  return *writer;
76
76
  }
77
77
 
78
+ static bool TypeIsSupported(const LogicalType &type);
79
+
78
80
  private:
81
+ static bool DuckDBTypeToParquetTypeInternal(const LogicalType &duckdb_type,
82
+ duckdb_parquet::format::Type::type &type);
79
83
  string file_name;
80
84
  vector<LogicalType> sql_types;
81
85
  vector<string> column_names;
@@ -37,6 +37,7 @@
37
37
  #include "duckdb/storage/table/row_group.hpp"
38
38
  #include "duckdb/common/serializer/format_serializer.hpp"
39
39
  #include "duckdb/common/serializer/format_deserializer.hpp"
40
+
40
41
  #endif
41
42
 
42
43
  namespace duckdb {
@@ -826,6 +827,7 @@ unique_ptr<FunctionData> ParquetWriteBind(ClientContext &context, CopyInfo &info
826
827
  if (!row_group_size_bytes_set) {
827
828
  bind_data->row_group_size_bytes = bind_data->row_group_size * ParquetWriteBindData::BYTES_PER_ROW;
828
829
  }
830
+
829
831
  bind_data->sql_types = sql_types;
830
832
  bind_data->column_names = names;
831
833
  return std::move(bind_data);
@@ -1003,6 +1005,7 @@ void ParquetExtension::Load(DuckDB &db) {
1003
1005
  function.desired_batch_size = ParquetWriteDesiredBatchSize;
1004
1006
  function.serialize = ParquetCopySerialize;
1005
1007
  function.deserialize = ParquetCopyDeserialize;
1008
+ function.supports_type = ParquetWriter::TypeIsSupported;
1006
1009
 
1007
1010
  function.extension = "parquet";
1008
1011
  ExtensionUtil::RegisterFunction(db_instance, function);
@@ -154,8 +154,7 @@ Value ParquetStatisticsUtils::ConvertValue(const LogicalType &type,
154
154
  throw InternalException("Incorrect stats size for type DATE");
155
155
  }
156
156
  return Value::DATE(date_t(Load<int32_t>(stats_data)));
157
- case LogicalTypeId::TIME:
158
- case LogicalTypeId::TIME_TZ: {
157
+ case LogicalTypeId::TIME: {
159
158
  int64_t val;
160
159
  if (stats.size() == sizeof(int32_t)) {
161
160
  val = Load<int32_t>(stats_data);
@@ -182,6 +181,23 @@ Value ParquetStatisticsUtils::ConvertValue(const LogicalType &type,
182
181
  return Value::TIME(dtime_t(val));
183
182
  }
184
183
  }
184
+ case LogicalTypeId::TIME_TZ: {
185
+ int64_t val;
186
+ if (stats.size() == sizeof(int64_t)) {
187
+ val = Load<int64_t>(stats_data);
188
+ } else {
189
+ throw InternalException("Incorrect stats size for type TIMETZ");
190
+ }
191
+ if (schema_ele.__isset.logicalType && schema_ele.logicalType.__isset.TIME) {
192
+ // logical type
193
+ if (schema_ele.logicalType.TIME.unit.__isset.MICROS) {
194
+ return Value::TIMETZ(ParquetIntToTimeTZ(val));
195
+ } else {
196
+ throw InternalException("Time With Time Zone logicalType is set but unit is not defined");
197
+ }
198
+ }
199
+ return Value::TIMETZ(ParquetIntToTimeTZ(val));
200
+ }
185
201
  case LogicalTypeId::TIMESTAMP:
186
202
  case LogicalTypeId::TIMESTAMP_TZ: {
187
203
  if (schema_ele.type == Type::INT96) {
@@ -66,4 +66,10 @@ dtime_t ParquetIntToTimeNs(const int64_t &raw_time) {
66
66
  return Time::FromTimeNs(raw_time);
67
67
  }
68
68
 
69
+ dtime_tz_t ParquetIntToTimeTZ(const int64_t &raw_time) {
70
+ dtime_tz_t result;
71
+ result.bits = raw_time;
72
+ return result;
73
+ }
74
+
69
75
  } // namespace duckdb
@@ -76,27 +76,32 @@ private:
76
76
  Serializer &serializer;
77
77
  };
78
78
 
79
- Type::type ParquetWriter::DuckDBTypeToParquetType(const LogicalType &duckdb_type) {
79
+ bool ParquetWriter::DuckDBTypeToParquetTypeInternal(const LogicalType &duckdb_type, Type::type &parquet_type) {
80
80
  switch (duckdb_type.id()) {
81
81
  case LogicalTypeId::BOOLEAN:
82
- return Type::BOOLEAN;
82
+ parquet_type = Type::BOOLEAN;
83
+ break;
83
84
  case LogicalTypeId::TINYINT:
84
85
  case LogicalTypeId::SMALLINT:
85
86
  case LogicalTypeId::INTEGER:
86
87
  case LogicalTypeId::DATE:
87
- return Type::INT32;
88
+ parquet_type = Type::INT32;
89
+ break;
88
90
  case LogicalTypeId::BIGINT:
89
- return Type::INT64;
91
+ parquet_type = Type::INT64;
92
+ break;
90
93
  case LogicalTypeId::FLOAT:
91
- return Type::FLOAT;
94
+ parquet_type = Type::FLOAT;
95
+ break;
92
96
  case LogicalTypeId::DOUBLE:
93
97
  case LogicalTypeId::HUGEINT:
94
- return Type::DOUBLE;
98
+ parquet_type = Type::DOUBLE;
99
+ break;
95
100
  case LogicalTypeId::ENUM:
96
101
  case LogicalTypeId::BLOB:
97
102
  case LogicalTypeId::VARCHAR:
98
- case LogicalTypeId::BIT:
99
- return Type::BYTE_ARRAY;
103
+ parquet_type = Type::BYTE_ARRAY;
104
+ break;
100
105
  case LogicalTypeId::TIME:
101
106
  case LogicalTypeId::TIME_TZ:
102
107
  case LogicalTypeId::TIMESTAMP:
@@ -104,31 +109,80 @@ Type::type ParquetWriter::DuckDBTypeToParquetType(const LogicalType &duckdb_type
104
109
  case LogicalTypeId::TIMESTAMP_MS:
105
110
  case LogicalTypeId::TIMESTAMP_NS:
106
111
  case LogicalTypeId::TIMESTAMP_SEC:
107
- return Type::INT64;
112
+ parquet_type = Type::INT64;
113
+ break;
108
114
  case LogicalTypeId::UTINYINT:
109
115
  case LogicalTypeId::USMALLINT:
110
116
  case LogicalTypeId::UINTEGER:
111
- return Type::INT32;
117
+ parquet_type = Type::INT32;
118
+ break;
112
119
  case LogicalTypeId::UBIGINT:
113
- return Type::INT64;
120
+ parquet_type = Type::INT64;
121
+ break;
114
122
  case LogicalTypeId::INTERVAL:
115
123
  case LogicalTypeId::UUID:
116
- return Type::FIXED_LEN_BYTE_ARRAY;
124
+ parquet_type = Type::FIXED_LEN_BYTE_ARRAY;
125
+ break;
117
126
  case LogicalTypeId::DECIMAL:
118
127
  switch (duckdb_type.InternalType()) {
119
128
  case PhysicalType::INT16:
120
129
  case PhysicalType::INT32:
121
- return Type::INT32;
130
+ parquet_type = Type::INT32;
131
+ break;
122
132
  case PhysicalType::INT64:
123
- return Type::INT64;
133
+ parquet_type = Type::INT64;
134
+ break;
124
135
  case PhysicalType::INT128:
125
- return Type::FIXED_LEN_BYTE_ARRAY;
136
+ parquet_type = Type::FIXED_LEN_BYTE_ARRAY;
137
+ break;
126
138
  default:
127
139
  throw InternalException("Unsupported internal decimal type");
128
140
  }
141
+ break;
129
142
  default:
143
+ // Anything that is not supported returns false
144
+ return false;
145
+ }
146
+ return true;
147
+ }
148
+
149
+ Type::type ParquetWriter::DuckDBTypeToParquetType(const LogicalType &duckdb_type) {
150
+ Type::type result;
151
+ if (!DuckDBTypeToParquetTypeInternal(duckdb_type, result)) {
130
152
  throw NotImplementedException("Unimplemented type for Parquet \"%s\"", duckdb_type.ToString());
131
153
  }
154
+ return result;
155
+ }
156
+
157
+ bool ParquetWriter::TypeIsSupported(const LogicalType &type) {
158
+ Type::type unused;
159
+ auto id = type.id();
160
+ if (id == LogicalTypeId::LIST) {
161
+ auto &child_type = ListType::GetChildType(type);
162
+ return TypeIsSupported(child_type);
163
+ }
164
+ if (id == LogicalTypeId::STRUCT) {
165
+ auto &children = StructType::GetChildTypes(type);
166
+ for (auto &child : children) {
167
+ auto &child_type = child.second;
168
+ if (!TypeIsSupported(child_type)) {
169
+ return false;
170
+ }
171
+ }
172
+ return true;
173
+ }
174
+ if (id == LogicalTypeId::MAP) {
175
+ auto &key_type = MapType::KeyType(type);
176
+ auto &value_type = MapType::ValueType(type);
177
+ if (!TypeIsSupported(key_type)) {
178
+ return false;
179
+ }
180
+ if (!TypeIsSupported(value_type)) {
181
+ return false;
182
+ }
183
+ return true;
184
+ }
185
+ return DuckDBTypeToParquetTypeInternal(type, unused);
132
186
  }
133
187
 
134
188
  void ParquetWriter::SetSchemaProperties(const LogicalType &duckdb_type,
@@ -2345,6 +2345,8 @@ const char* EnumUtil::ToChars<IndexType>(IndexType value) {
2345
2345
  return "INVALID";
2346
2346
  case IndexType::ART:
2347
2347
  return "ART";
2348
+ case IndexType::EXTENSION:
2349
+ return "EXTENSION";
2348
2350
  default:
2349
2351
  throw NotImplementedException(StringUtil::Format("Enum value: '%d' not implemented", value));
2350
2352
  }
@@ -2358,6 +2360,9 @@ IndexType EnumUtil::FromString<IndexType>(const char *value) {
2358
2360
  if (StringUtil::Equals(value, "ART")) {
2359
2361
  return IndexType::ART;
2360
2362
  }
2363
+ if (StringUtil::Equals(value, "EXTENSION")) {
2364
+ return IndexType::EXTENSION;
2365
+ }
2361
2366
  throw NotImplementedException(StringUtil::Format("Enum value: '%s' not implemented", value));
2362
2367
  }
2363
2368
 
@@ -1272,6 +1272,27 @@ bool TryCast::Operation(dtime_t input, dtime_t &result, bool strict) {
1272
1272
  return true;
1273
1273
  }
1274
1274
 
1275
+ template <>
1276
+ bool TryCast::Operation(dtime_t input, dtime_tz_t &result, bool strict) {
1277
+ result = dtime_tz_t(input, 0);
1278
+ return true;
1279
+ }
1280
+
1281
+ //===--------------------------------------------------------------------===//
1282
+ // Cast From Time With Time Zone (Offset)
1283
+ //===--------------------------------------------------------------------===//
1284
+ template <>
1285
+ bool TryCast::Operation(dtime_tz_t input, dtime_tz_t &result, bool strict) {
1286
+ result = input;
1287
+ return true;
1288
+ }
1289
+
1290
+ template <>
1291
+ bool TryCast::Operation(dtime_tz_t input, dtime_t &result, bool strict) {
1292
+ result = input.time();
1293
+ return true;
1294
+ }
1295
+
1275
1296
  //===--------------------------------------------------------------------===//
1276
1297
  // Cast From Timestamps
1277
1298
  //===--------------------------------------------------------------------===//
@@ -1296,6 +1317,15 @@ bool TryCast::Operation(timestamp_t input, timestamp_t &result, bool strict) {
1296
1317
  return true;
1297
1318
  }
1298
1319
 
1320
+ template <>
1321
+ bool TryCast::Operation(timestamp_t input, dtime_tz_t &result, bool strict) {
1322
+ if (!Timestamp::IsFinite(input)) {
1323
+ return false;
1324
+ }
1325
+ result = dtime_tz_t(Timestamp::GetTime(input), 0);
1326
+ return true;
1327
+ }
1328
+
1299
1329
  //===--------------------------------------------------------------------===//
1300
1330
  // Cast from Interval
1301
1331
  //===--------------------------------------------------------------------===//
@@ -1583,6 +1613,33 @@ dtime_t Cast::Operation(string_t input) {
1583
1613
  return Time::FromCString(input.GetData(), input.GetSize());
1584
1614
  }
1585
1615
 
1616
+ //===--------------------------------------------------------------------===//
1617
+ // Cast To TimeTZ
1618
+ //===--------------------------------------------------------------------===//
1619
+ template <>
1620
+ bool TryCastErrorMessage::Operation(string_t input, dtime_tz_t &result, string *error_message, bool strict) {
1621
+ if (!TryCast::Operation<string_t, dtime_tz_t>(input, result, strict)) {
1622
+ HandleCastError::AssignError(Time::ConversionError(input), error_message);
1623
+ return false;
1624
+ }
1625
+ return true;
1626
+ }
1627
+
1628
+ template <>
1629
+ bool TryCast::Operation(string_t input, dtime_tz_t &result, bool strict) {
1630
+ idx_t pos;
1631
+ return Time::TryConvertTimeTZ(input.GetData(), input.GetSize(), pos, result, strict);
1632
+ }
1633
+
1634
+ template <>
1635
+ dtime_tz_t Cast::Operation(string_t input) {
1636
+ dtime_tz_t result;
1637
+ if (!TryCast::Operation(input, result, false)) {
1638
+ throw ConversionException(Time::ConversionError(input));
1639
+ }
1640
+ return result;
1641
+ }
1642
+
1586
1643
  //===--------------------------------------------------------------------===//
1587
1644
  // Cast To Timestamp
1588
1645
  //===--------------------------------------------------------------------===//
@@ -161,24 +161,61 @@ duckdb::string_t StringCast::Operation(duckdb::string_t input, Vector &result) {
161
161
  }
162
162
 
163
163
  template <>
164
- string_t StringCastTZ::Operation(dtime_t input, Vector &vector) {
164
+ string_t StringCastTZ::Operation(dtime_tz_t input, Vector &vector) {
165
165
  int32_t time[4];
166
- Time::Convert(input, time[0], time[1], time[2], time[3]);
166
+ Time::Convert(input.time(), time[0], time[1], time[2], time[3]);
167
167
 
168
- // format for timetz is TIME+00
169
168
  char micro_buffer[10];
170
169
  const auto time_length = TimeToStringCast::Length(time, micro_buffer);
171
- const idx_t length = time_length + 3;
170
+ idx_t length = time_length;
171
+
172
+ const auto offset = input.offset();
173
+ const bool negative = (offset < 0);
174
+ ++length;
175
+
176
+ auto ss = std::abs(offset);
177
+ const auto hh = ss / Interval::SECS_PER_HOUR;
178
+
179
+ const auto hh_length = (hh < 100) ? 2 : NumericHelper::UnsignedLength(uint32_t(hh));
180
+ length += hh_length;
181
+
182
+ ss %= Interval::SECS_PER_HOUR;
183
+ const auto mm = ss / Interval::SECS_PER_MINUTE;
184
+ if (mm) {
185
+ length += 3;
186
+ }
187
+
188
+ ss %= Interval::SECS_PER_MINUTE;
189
+ if (ss) {
190
+ length += 3;
191
+ }
172
192
 
173
193
  string_t result = StringVector::EmptyString(vector, length);
174
194
  auto data = result.GetDataWriteable();
175
195
 
176
196
  idx_t pos = 0;
177
- TimeToStringCast::Format(data + pos, length, time, micro_buffer);
197
+ TimeToStringCast::Format(data + pos, time_length, time, micro_buffer);
178
198
  pos += time_length;
179
- data[pos++] = '+';
180
- data[pos++] = '0';
181
- data[pos++] = '0';
199
+
200
+ data[pos++] = negative ? '-' : '+';
201
+ if (hh < 100) {
202
+ TimeToStringCast::FormatTwoDigits(data + pos, hh);
203
+ } else {
204
+ NumericHelper::FormatUnsigned(hh, data + pos + hh_length);
205
+ }
206
+ pos += hh_length;
207
+
208
+ if (mm) {
209
+ data[pos++] = ':';
210
+ TimeToStringCast::FormatTwoDigits(data + pos, mm);
211
+ pos += 2;
212
+ }
213
+
214
+ if (ss) {
215
+ data[pos++] = ':';
216
+ TimeToStringCast::FormatTwoDigits(data + pos, ss);
217
+ pos += 2;
218
+ }
182
219
 
183
220
  result.Finalize();
184
221
  return result;
@@ -129,6 +129,111 @@ bool Time::TryConvertTime(const char *buf, idx_t len, idx_t &pos, dtime_t &resul
129
129
  return true;
130
130
  }
131
131
 
132
+ bool Time::TryParseUTCOffset(const char *str, idx_t &pos, idx_t len, int32_t &offset) {
133
+ offset = 0;
134
+ if (pos == len || StringUtil::CharacterIsSpace(str[pos])) {
135
+ return true;
136
+ }
137
+
138
+ idx_t curpos = pos;
139
+ // Minimum of 3 characters
140
+ if (curpos + 3 > len) {
141
+ // no characters left to parse
142
+ return false;
143
+ }
144
+
145
+ const auto sign_char = str[curpos];
146
+ if (sign_char != '+' && sign_char != '-') {
147
+ // expected either + or -
148
+ return false;
149
+ }
150
+ curpos++;
151
+
152
+ int32_t hh = 0;
153
+ idx_t start = curpos;
154
+ for (; curpos < len; ++curpos) {
155
+ const auto c = str[curpos];
156
+ if (!StringUtil::CharacterIsDigit(c)) {
157
+ break;
158
+ }
159
+ hh = hh * 10 + (c - '0');
160
+ }
161
+ // HH is in [-1559,+1559] and must be at least two digits
162
+ if (curpos - start < 2 || hh > 1559) {
163
+ return false;
164
+ }
165
+
166
+ // optional minute specifier: expected ":MM"
167
+ int32_t mm = 0;
168
+ if (curpos + 3 <= len && str[curpos] == ':') {
169
+ ++curpos;
170
+ if (!Date::ParseDoubleDigit(str, len, curpos, mm) || mm >= Interval::MINS_PER_HOUR) {
171
+ return false;
172
+ }
173
+ }
174
+
175
+ // optional seconds specifier: expected ":SS"
176
+ int32_t ss = 0;
177
+ if (curpos + 3 <= len && str[curpos] == ':') {
178
+ ++curpos;
179
+ if (!Date::ParseDoubleDigit(str, len, curpos, ss) || ss >= Interval::SECS_PER_MINUTE) {
180
+ return false;
181
+ }
182
+ }
183
+
184
+ // Assemble the offset now that we know nothing went wrong
185
+ offset += hh * Interval::SECS_PER_HOUR;
186
+ offset += mm * Interval::SECS_PER_MINUTE;
187
+ offset += ss;
188
+ if (sign_char == '-') {
189
+ offset = -offset;
190
+ }
191
+
192
+ pos = curpos;
193
+
194
+ return true;
195
+ }
196
+
197
+ bool Time::TryConvertTimeTZ(const char *buf, idx_t len, idx_t &pos, dtime_tz_t &result, bool strict) {
198
+ dtime_t time_part;
199
+ if (!Time::TryConvertInternal(buf, len, pos, time_part, false)) {
200
+ if (!strict) {
201
+ // last chance, check if we can parse as timestamp
202
+ timestamp_t timestamp;
203
+ if (Timestamp::TryConvertTimestamp(buf, len, timestamp) == TimestampCastResult::SUCCESS) {
204
+ if (!Timestamp::IsFinite(timestamp)) {
205
+ return false;
206
+ }
207
+ result = dtime_tz_t(Timestamp::GetTime(timestamp), 0);
208
+ return true;
209
+ }
210
+ }
211
+ return false;
212
+ }
213
+
214
+ // We can't use Timestamp::TryParseUTCOffset because the colon is optional there but required here.
215
+ int32_t offset = 0;
216
+ if (!TryParseUTCOffset(buf, pos, len, offset)) {
217
+ return false;
218
+ }
219
+
220
+ // in strict mode, check remaining string for non-space characters
221
+ if (strict) {
222
+ // skip trailing spaces
223
+ while (pos < len && StringUtil::CharacterIsSpace(buf[pos])) {
224
+ pos++;
225
+ }
226
+ // check position. if end was not reached, non-space chars remaining
227
+ if (pos < len) {
228
+ return false;
229
+ }
230
+ }
231
+
232
+ result = dtime_tz_t(time_part, offset);
233
+
234
+ return true;
235
+ }
236
+
132
237
  string Time::ConversionError(const string &str) {
133
238
  return StringUtil::Format("time field value out of range: \"%s\", "
134
239
  "expected format is ([YYYY-MM-DD ]HH:MM:SS[.MS])",
@@ -231,7 +231,7 @@ Value Value::MinimumValue(const LogicalType &type) {
231
231
  case LogicalTypeId::TIMESTAMP_NS:
232
232
  return Value::TIMESTAMPNS(timestamp_t(NumericLimits<int64_t>::Minimum()));
233
233
  case LogicalTypeId::TIME_TZ:
234
- return Value::TIMETZ(dtime_t(0));
234
+ return Value::TIMETZ(dtime_tz_t(dtime_t(0), dtime_tz_t::MIN_OFFSET));
235
235
  case LogicalTypeId::TIMESTAMP_TZ:
236
236
  return Value::TIMESTAMPTZ(Timestamp::FromDatetime(
237
237
  Date::FromDate(Timestamp::MIN_YEAR, Timestamp::MIN_MONTH, Timestamp::MIN_DAY), dtime_t(0)));
@@ -300,7 +300,8 @@ Value Value::MaximumValue(const LogicalType &type) {
300
300
  case LogicalTypeId::TIMESTAMP_SEC:
301
301
  return MaximumValue(LogicalType::TIMESTAMP).DefaultCastAs(LogicalType::TIMESTAMP_S);
302
302
  case LogicalTypeId::TIME_TZ:
303
- return Value::TIMETZ(dtime_t(Interval::SECS_PER_DAY * Interval::MICROS_PER_SEC - 1));
303
+ return Value::TIMETZ(
304
+ dtime_tz_t(dtime_t(Interval::SECS_PER_DAY * Interval::MICROS_PER_SEC - 1), dtime_tz_t::MAX_OFFSET));
304
305
  case LogicalTypeId::TIMESTAMP_TZ:
305
306
  return MaximumValue(LogicalType::TIMESTAMP);
306
307
  case LogicalTypeId::FLOAT:
@@ -587,9 +588,9 @@ Value Value::TIME(dtime_t value) {
587
588
  return result;
588
589
  }
589
590
 
590
- Value Value::TIMETZ(dtime_t value) {
591
+ Value Value::TIMETZ(dtime_tz_t value) {
591
592
  Value result(LogicalType::TIME_TZ);
592
- result.value_.time = value;
593
+ result.value_.timetz = value;
593
594
  result.is_null = false;
594
595
  return result;
595
596
  }
@@ -955,8 +956,9 @@ T Value::GetValueInternal() const {
955
956
  case LogicalTypeId::DATE:
956
957
  return Cast::Operation<date_t, T>(value_.date);
957
958
  case LogicalTypeId::TIME:
958
- case LogicalTypeId::TIME_TZ:
959
959
  return Cast::Operation<dtime_t, T>(value_.time);
960
+ case LogicalTypeId::TIME_TZ:
961
+ return Cast::Operation<dtime_tz_t, T>(value_.timetz);
960
962
  case LogicalTypeId::TIMESTAMP:
961
963
  case LogicalTypeId::TIMESTAMP_TZ:
962
964
  return Cast::Operation<timestamp_t, T>(value_.timestamp);
@@ -1028,7 +1030,6 @@ int64_t Value::GetValue() const {
1028
1030
  case LogicalTypeId::TIMESTAMP_NS:
1029
1031
  case LogicalTypeId::TIMESTAMP_MS:
1030
1032
  case LogicalTypeId::TIME:
1031
- case LogicalTypeId::TIME_TZ:
1032
1033
  case LogicalTypeId::TIMESTAMP_TZ:
1033
1034
  return value_.bigint;
1034
1035
  default:
@@ -1146,8 +1147,6 @@ Value Value::Numeric(const LogicalType &type, int64_t value) {
1146
1147
  return Value::TIMESTAMPMS(timestamp_t(value));
1147
1148
  case LogicalTypeId::TIMESTAMP_SEC:
1148
1149
  return Value::TIMESTAMPSEC(timestamp_t(value));
1149
- case LogicalTypeId::TIME_TZ:
1150
- return Value::TIMETZ(dtime_t(value));
1151
1150
  case LogicalTypeId::TIMESTAMP_TZ:
1152
1151
  return Value::TIMESTAMPTZ(timestamp_t(value));
1153
1152
  case LogicalTypeId::ENUM:
@@ -477,7 +477,7 @@ Value Vector::GetValueInternal(const Vector &v_p, idx_t index_p) {
477
477
  case LogicalTypeId::TIME:
478
478
  return Value::TIME(reinterpret_cast<dtime_t *>(data)[index]);
479
479
  case LogicalTypeId::TIME_TZ:
480
- return Value::TIMETZ(reinterpret_cast<dtime_t *>(data)[index]);
480
+ return Value::TIMETZ(reinterpret_cast<dtime_tz_t *>(data)[index]);
481
481
  case LogicalTypeId::BIGINT:
482
482
  return Value::BIGINT(reinterpret_cast<int64_t *>(data)[index]);
483
483
  case LogicalTypeId::UTINYINT:
@@ -4,6 +4,7 @@
4
4
  #include "duckdb/planner/operator/logical_any_join.hpp"
5
5
  #include "duckdb/planner/operator/logical_create_index.hpp"
6
6
  #include "duckdb/planner/operator/logical_insert.hpp"
7
+ #include "duckdb/planner/operator/logical_extension_operator.hpp"
7
8
 
8
9
  #include "duckdb/planner/expression/bound_columnref_expression.hpp"
9
10
  #include "duckdb/planner/expression/bound_reference_expression.hpp"
@@ -91,6 +92,12 @@ void ColumnBindingResolver::VisitOperator(LogicalOperator &op) {
91
92
  bindings = op.GetColumnBindings();
92
93
  return;
93
94
  }
95
+ break;
96
+ }
97
+ case LogicalOperatorType::LOGICAL_EXTENSION_OPERATOR: {
98
+ auto &ext_op = op.Cast<LogicalExtensionOperator>();
99
+ ext_op.ResolveColumnBindings(*this, bindings);
100
+ return;
94
101
  }
95
102
  default:
96
103
  break;