duckdb 0.3.5-dev113.0 → 0.3.5-dev1181.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.
- package/configure +2 -2
- package/lib/duckdb-binding.js +2 -4
- package/package.json +1 -1
- package/src/connection.cpp +7 -0
- package/src/duckdb.cpp +38739 -29304
- package/src/duckdb.hpp +1643 -1139
- package/src/parquet-amalgamation.cpp +26968 -26773
- package/src/parquet-amalgamation.hpp +186 -16
- package/src/statement.cpp +85 -64
- package/test/data_type_support.test.js +63 -6
- package/test/prepare.test.js +2 -2
|
@@ -4625,7 +4625,7 @@ std::ostream& operator<<(std::ostream& out, const FileCryptoMetaData& obj);
|
|
|
4625
4625
|
// LICENSE_CHANGE_END
|
|
4626
4626
|
|
|
4627
4627
|
|
|
4628
|
-
|
|
4628
|
+
#include <list>
|
|
4629
4629
|
|
|
4630
4630
|
|
|
4631
4631
|
// LICENSE_CHANGE_BEGIN
|
|
@@ -6916,31 +6916,174 @@ protected:
|
|
|
6916
6916
|
|
|
6917
6917
|
namespace duckdb {
|
|
6918
6918
|
|
|
6919
|
+
// A ReadHead for prefetching data in a specific range
|
|
6920
|
+
struct ReadHead {
|
|
6921
|
+
ReadHead(idx_t location, uint64_t size) : location(location), size(size) {};
|
|
6922
|
+
// Hint info
|
|
6923
|
+
idx_t location;
|
|
6924
|
+
uint64_t size;
|
|
6925
|
+
|
|
6926
|
+
// Current info
|
|
6927
|
+
unique_ptr<AllocatedData> data;
|
|
6928
|
+
bool data_isset = false;
|
|
6929
|
+
|
|
6930
|
+
idx_t GetEnd() const {
|
|
6931
|
+
return size + location;
|
|
6932
|
+
}
|
|
6933
|
+
|
|
6934
|
+
void Allocate(Allocator &allocator) {
|
|
6935
|
+
data = allocator.Allocate(size);
|
|
6936
|
+
}
|
|
6937
|
+
};
|
|
6938
|
+
|
|
6939
|
+
// Comparator for ReadHeads that are either overlapping, adjacent, or within ALLOW_GAP bytes from each other
|
|
6940
|
+
struct ReadHeadComparator {
|
|
6941
|
+
static constexpr uint64_t ALLOW_GAP = 1 << 14; // 16 KiB
|
|
6942
|
+
bool operator()(const ReadHead *a, const ReadHead *b) const {
|
|
6943
|
+
auto a_start = a->location;
|
|
6944
|
+
auto a_end = a->location + a->size;
|
|
6945
|
+
auto b_start = b->location;
|
|
6946
|
+
|
|
6947
|
+
if (a_end <= NumericLimits<idx_t>::Maximum() - ALLOW_GAP) {
|
|
6948
|
+
a_end += ALLOW_GAP;
|
|
6949
|
+
}
|
|
6950
|
+
|
|
6951
|
+
return a_start < b_start && a_end < b_start;
|
|
6952
|
+
}
|
|
6953
|
+
};
|
|
6954
|
+
|
|
6955
|
+
// Two-step read ahead buffer
|
|
6956
|
+
// 1: register all ranges that will be read, merging ranges that are consecutive
|
|
6957
|
+
// 2: prefetch all registered ranges
|
|
6958
|
+
struct ReadAheadBuffer {
|
|
6959
|
+
ReadAheadBuffer(Allocator &allocator, FileHandle &handle, FileOpener &opener)
|
|
6960
|
+
: allocator(allocator), handle(handle), file_opener(opener) {
|
|
6961
|
+
}
|
|
6962
|
+
|
|
6963
|
+
// The list of read heads
|
|
6964
|
+
std::list<ReadHead> read_heads;
|
|
6965
|
+
// Set for merging consecutive ranges
|
|
6966
|
+
std::set<ReadHead *, ReadHeadComparator> merge_set;
|
|
6967
|
+
|
|
6968
|
+
Allocator &allocator;
|
|
6969
|
+
FileHandle &handle;
|
|
6970
|
+
FileOpener &file_opener;
|
|
6971
|
+
|
|
6972
|
+
idx_t total_size = 0;
|
|
6973
|
+
|
|
6974
|
+
// Add a read head to the prefetching list
|
|
6975
|
+
void AddReadHead(idx_t pos, uint64_t len, bool merge_buffers = true) {
|
|
6976
|
+
// Attempt to merge with existing
|
|
6977
|
+
if (merge_buffers) {
|
|
6978
|
+
ReadHead new_read_head {pos, len};
|
|
6979
|
+
auto lookup_set = merge_set.find(&new_read_head);
|
|
6980
|
+
if (lookup_set != merge_set.end()) {
|
|
6981
|
+
auto existing_head = *lookup_set;
|
|
6982
|
+
auto new_start = MinValue<idx_t>(existing_head->location, new_read_head.location);
|
|
6983
|
+
auto new_length = MaxValue<idx_t>(existing_head->GetEnd(), new_read_head.GetEnd()) - new_start;
|
|
6984
|
+
existing_head->location = new_start;
|
|
6985
|
+
existing_head->size = new_length;
|
|
6986
|
+
return;
|
|
6987
|
+
}
|
|
6988
|
+
}
|
|
6989
|
+
|
|
6990
|
+
read_heads.emplace_front(ReadHead(pos, len));
|
|
6991
|
+
total_size += len;
|
|
6992
|
+
auto &read_head = read_heads.front();
|
|
6993
|
+
|
|
6994
|
+
if (merge_buffers) {
|
|
6995
|
+
merge_set.insert(&read_head);
|
|
6996
|
+
}
|
|
6997
|
+
|
|
6998
|
+
if (read_head.GetEnd() > handle.GetFileSize()) {
|
|
6999
|
+
throw std::runtime_error("Prefetch registered for bytes outside file");
|
|
7000
|
+
}
|
|
7001
|
+
}
|
|
7002
|
+
|
|
7003
|
+
// Returns the relevant read head
|
|
7004
|
+
ReadHead *GetReadHead(idx_t pos) {
|
|
7005
|
+
for (auto &read_head : read_heads) {
|
|
7006
|
+
if (pos >= read_head.location && pos < read_head.GetEnd()) {
|
|
7007
|
+
return &read_head;
|
|
7008
|
+
}
|
|
7009
|
+
}
|
|
7010
|
+
return nullptr;
|
|
7011
|
+
}
|
|
7012
|
+
|
|
7013
|
+
// Prefetch all read heads
|
|
7014
|
+
void Prefetch() {
|
|
7015
|
+
for (auto &read_head : read_heads) {
|
|
7016
|
+
read_head.Allocate(allocator);
|
|
7017
|
+
|
|
7018
|
+
if (read_head.GetEnd() > handle.GetFileSize()) {
|
|
7019
|
+
throw std::runtime_error("Prefetch registered requested for bytes outside file");
|
|
7020
|
+
}
|
|
7021
|
+
|
|
7022
|
+
handle.Read(read_head.data->get(), read_head.size, read_head.location);
|
|
7023
|
+
read_head.data_isset = true;
|
|
7024
|
+
}
|
|
7025
|
+
}
|
|
7026
|
+
};
|
|
7027
|
+
|
|
6919
7028
|
class ThriftFileTransport : public duckdb_apache::thrift::transport::TVirtualTransport<ThriftFileTransport> {
|
|
6920
7029
|
public:
|
|
6921
|
-
|
|
6922
|
-
|
|
7030
|
+
static constexpr uint64_t PREFETCH_FALLBACK_BUFFERSIZE = 1000000;
|
|
7031
|
+
|
|
7032
|
+
ThriftFileTransport(Allocator &allocator, FileHandle &handle_p, FileOpener &opener, bool prefetch_mode_p)
|
|
7033
|
+
: handle(handle_p), location(0), allocator(allocator), ra_buffer(ReadAheadBuffer(allocator, handle_p, opener)),
|
|
7034
|
+
prefetch_mode(prefetch_mode_p) {
|
|
6923
7035
|
}
|
|
6924
7036
|
|
|
6925
7037
|
uint32_t read(uint8_t *buf, uint32_t len) {
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
7038
|
+
auto prefetch_buffer = ra_buffer.GetReadHead(location);
|
|
7039
|
+
if (prefetch_buffer != nullptr && location - prefetch_buffer->location + len <= prefetch_buffer->size) {
|
|
7040
|
+
D_ASSERT(location - prefetch_buffer->location + len <= prefetch_buffer->size);
|
|
7041
|
+
|
|
7042
|
+
if (!prefetch_buffer->data_isset) {
|
|
7043
|
+
prefetch_buffer->Allocate(allocator);
|
|
7044
|
+
handle.Read(prefetch_buffer->data->get(), prefetch_buffer->size, prefetch_buffer->location);
|
|
7045
|
+
prefetch_buffer->data_isset = true;
|
|
7046
|
+
}
|
|
7047
|
+
memcpy(buf, prefetch_buffer->data->get() + location - prefetch_buffer->location, len);
|
|
6929
7048
|
} else {
|
|
6930
|
-
|
|
7049
|
+
if (prefetch_mode && len < PREFETCH_FALLBACK_BUFFERSIZE && len > 0) {
|
|
7050
|
+
Prefetch(location, MinValue<uint64_t>(PREFETCH_FALLBACK_BUFFERSIZE, handle.GetFileSize() - location));
|
|
7051
|
+
auto prefetch_buffer_fallback = ra_buffer.GetReadHead(location);
|
|
7052
|
+
D_ASSERT(location - prefetch_buffer_fallback->location + len <= prefetch_buffer_fallback->size);
|
|
7053
|
+
memcpy(buf, prefetch_buffer_fallback->data->get() + location - prefetch_buffer_fallback->location, len);
|
|
7054
|
+
} else {
|
|
7055
|
+
handle.Read(buf, len, location);
|
|
7056
|
+
}
|
|
6931
7057
|
}
|
|
6932
7058
|
location += len;
|
|
6933
7059
|
return len;
|
|
6934
7060
|
}
|
|
6935
7061
|
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
7062
|
+
// Prefetch a single buffer
|
|
7063
|
+
void Prefetch(idx_t pos, uint64_t len) {
|
|
7064
|
+
RegisterPrefetch(pos, len, false);
|
|
7065
|
+
FinalizeRegistration();
|
|
7066
|
+
PrefetchRegistered();
|
|
7067
|
+
}
|
|
7068
|
+
|
|
7069
|
+
// Register a buffer for prefixing
|
|
7070
|
+
void RegisterPrefetch(idx_t pos, uint64_t len, bool can_merge = true) {
|
|
7071
|
+
ra_buffer.AddReadHead(pos, len, can_merge);
|
|
7072
|
+
}
|
|
7073
|
+
|
|
7074
|
+
// Prevents any further merges, should be called before PrefetchRegistered
|
|
7075
|
+
void FinalizeRegistration() {
|
|
7076
|
+
ra_buffer.merge_set.clear();
|
|
7077
|
+
}
|
|
7078
|
+
|
|
7079
|
+
// Prefetch all previously registered ranges
|
|
7080
|
+
void PrefetchRegistered() {
|
|
7081
|
+
ra_buffer.Prefetch();
|
|
6940
7082
|
}
|
|
6941
7083
|
|
|
6942
7084
|
void ClearPrefetch() {
|
|
6943
|
-
|
|
7085
|
+
ra_buffer.read_heads.clear();
|
|
7086
|
+
ra_buffer.merge_set.clear();
|
|
6944
7087
|
}
|
|
6945
7088
|
|
|
6946
7089
|
void SetLocation(idx_t location_p) {
|
|
@@ -6955,12 +7098,17 @@ public:
|
|
|
6955
7098
|
}
|
|
6956
7099
|
|
|
6957
7100
|
private:
|
|
6958
|
-
Allocator &allocator;
|
|
6959
7101
|
FileHandle &handle;
|
|
6960
7102
|
idx_t location;
|
|
6961
7103
|
|
|
6962
|
-
|
|
6963
|
-
|
|
7104
|
+
Allocator &allocator;
|
|
7105
|
+
|
|
7106
|
+
// Multi-buffer prefetch
|
|
7107
|
+
ReadAheadBuffer ra_buffer;
|
|
7108
|
+
|
|
7109
|
+
// Whether the prefetch mode is enabled. In this mode the DirectIO flag of the handle will be set and the parquet
|
|
7110
|
+
// reader will manage the read buffering.
|
|
7111
|
+
bool prefetch_mode;
|
|
6964
7112
|
};
|
|
6965
7113
|
|
|
6966
7114
|
} // namespace duckdb
|
|
@@ -7417,8 +7565,13 @@ public:
|
|
|
7417
7565
|
idx_t MaxDefine() const;
|
|
7418
7566
|
idx_t MaxRepeat() const;
|
|
7419
7567
|
|
|
7568
|
+
virtual idx_t FileOffset() const;
|
|
7569
|
+
virtual uint64_t TotalCompressedSize();
|
|
7420
7570
|
virtual idx_t GroupRowsAvailable();
|
|
7421
7571
|
|
|
7572
|
+
// register the range this reader will touch for prefetching
|
|
7573
|
+
virtual void RegisterPrefetch(ThriftFileTransport &transport, bool allow_merge);
|
|
7574
|
+
|
|
7422
7575
|
virtual unique_ptr<BaseStatistics> Stats(const std::vector<ColumnChunk> &columns);
|
|
7423
7576
|
|
|
7424
7577
|
protected:
|
|
@@ -7433,6 +7586,9 @@ protected:
|
|
|
7433
7586
|
virtual void DictReference(Vector &result);
|
|
7434
7587
|
virtual void PlainReference(shared_ptr<ByteBuffer>, Vector &result);
|
|
7435
7588
|
|
|
7589
|
+
// applies any skips that were registered using Skip()
|
|
7590
|
+
virtual void ApplyPendingSkips(idx_t num_values);
|
|
7591
|
+
|
|
7436
7592
|
bool HasDefines() {
|
|
7437
7593
|
return max_define > 0;
|
|
7438
7594
|
}
|
|
@@ -7451,13 +7607,15 @@ protected:
|
|
|
7451
7607
|
ParquetReader &reader;
|
|
7452
7608
|
LogicalType type;
|
|
7453
7609
|
|
|
7610
|
+
idx_t pending_skips = 0;
|
|
7611
|
+
|
|
7454
7612
|
private:
|
|
7455
7613
|
void PrepareRead(parquet_filter_t &filter);
|
|
7456
7614
|
void PreparePage(idx_t compressed_page_size, idx_t uncompressed_page_size);
|
|
7457
7615
|
void PrepareDataPage(PageHeader &page_hdr);
|
|
7458
7616
|
void PreparePageV2(PageHeader &page_hdr);
|
|
7459
7617
|
|
|
7460
|
-
const duckdb_parquet::format::ColumnChunk *chunk;
|
|
7618
|
+
const duckdb_parquet::format::ColumnChunk *chunk = nullptr;
|
|
7461
7619
|
|
|
7462
7620
|
duckdb_apache::thrift::protocol::TProtocol *protocol;
|
|
7463
7621
|
idx_t page_rows_available;
|
|
@@ -7545,6 +7703,11 @@ class ChunkCollection;
|
|
|
7545
7703
|
class BaseStatistics;
|
|
7546
7704
|
class TableFilterSet;
|
|
7547
7705
|
|
|
7706
|
+
struct ParquetReaderPrefetchConfig {
|
|
7707
|
+
// Percentage of data in a row group span that should be scanned for enabling whole group prefetch
|
|
7708
|
+
static constexpr double WHOLE_GROUP_PREFETCH_MINIMUM_SCAN = 0.95;
|
|
7709
|
+
};
|
|
7710
|
+
|
|
7548
7711
|
struct ParquetReaderScanState {
|
|
7549
7712
|
vector<idx_t> group_idx_list;
|
|
7550
7713
|
int64_t current_group;
|
|
@@ -7560,6 +7723,9 @@ struct ParquetReaderScanState {
|
|
|
7560
7723
|
|
|
7561
7724
|
ResizeableBuffer define_buf;
|
|
7562
7725
|
ResizeableBuffer repeat_buf;
|
|
7726
|
+
|
|
7727
|
+
bool prefetch_mode = false;
|
|
7728
|
+
bool current_group_prefetched = false;
|
|
7563
7729
|
};
|
|
7564
7730
|
|
|
7565
7731
|
struct ParquetOptions {
|
|
@@ -7624,6 +7790,10 @@ private:
|
|
|
7624
7790
|
idx_t depth, idx_t max_define, idx_t max_repeat,
|
|
7625
7791
|
idx_t &next_schema_idx, idx_t &next_file_idx);
|
|
7626
7792
|
const duckdb_parquet::format::RowGroup &GetGroup(ParquetReaderScanState &state);
|
|
7793
|
+
uint64_t GetGroupCompressedSize(ParquetReaderScanState &state);
|
|
7794
|
+
idx_t GetGroupOffset(ParquetReaderScanState &state);
|
|
7795
|
+
// Group span is the distance between the min page offset and the max page offset plus the max page compressed size
|
|
7796
|
+
uint64_t GetGroupSpan(ParquetReaderScanState &state);
|
|
7627
7797
|
void PrepareRowGroupBuffer(ParquetReaderScanState &state, idx_t out_col_idx);
|
|
7628
7798
|
LogicalType DeriveLogicalType(const SchemaElement &s_ele);
|
|
7629
7799
|
|
package/src/statement.cpp
CHANGED
|
@@ -121,6 +121,89 @@ static duckdb::Value bind_parameter(const Napi::Value source) {
|
|
|
121
121
|
return duckdb::Value();
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
static Napi::Value convert_col_val(Napi::Env &env, duckdb::Value dval, duckdb::LogicalTypeId id) {
|
|
125
|
+
Napi::Value value;
|
|
126
|
+
|
|
127
|
+
// TODO templateroo here
|
|
128
|
+
switch (id) {
|
|
129
|
+
case duckdb::LogicalTypeId::BOOLEAN: {
|
|
130
|
+
value = Napi::Boolean::New(env, duckdb::BooleanValue::Get(dval));
|
|
131
|
+
} break;
|
|
132
|
+
case duckdb::LogicalTypeId::INTEGER: {
|
|
133
|
+
value = Napi::Number::New(env, duckdb::IntegerValue::Get(dval));
|
|
134
|
+
} break;
|
|
135
|
+
case duckdb::LogicalTypeId::FLOAT: {
|
|
136
|
+
value = Napi::Number::New(env, duckdb::FloatValue::Get(dval));
|
|
137
|
+
} break;
|
|
138
|
+
case duckdb::LogicalTypeId::DOUBLE: {
|
|
139
|
+
value = Napi::Number::New(env, duckdb::DoubleValue::Get(dval));
|
|
140
|
+
} break;
|
|
141
|
+
case duckdb::LogicalTypeId::BIGINT: {
|
|
142
|
+
value = Napi::Number::New(env, duckdb::BigIntValue::Get(dval));
|
|
143
|
+
} break;
|
|
144
|
+
case duckdb::LogicalTypeId::HUGEINT: {
|
|
145
|
+
value = Napi::Number::New(env, dval.GetValue<double>());
|
|
146
|
+
} break;
|
|
147
|
+
case duckdb::LogicalTypeId::DECIMAL: {
|
|
148
|
+
value = Napi::Number::New(env, dval.GetValue<double>());
|
|
149
|
+
} break;
|
|
150
|
+
case duckdb::LogicalTypeId::INTERVAL: {
|
|
151
|
+
auto interval = duckdb::IntervalValue::Get(dval);
|
|
152
|
+
auto object_value = Napi::Object::New(env);
|
|
153
|
+
object_value.Set("months", interval.months);
|
|
154
|
+
object_value.Set("days", interval.days);
|
|
155
|
+
object_value.Set("micros", interval.micros);
|
|
156
|
+
value = object_value;
|
|
157
|
+
} break;
|
|
158
|
+
#if (NAPI_VERSION > 4)
|
|
159
|
+
case duckdb::LogicalTypeId::DATE: {
|
|
160
|
+
const auto scale = duckdb::Interval::SECS_PER_DAY * duckdb::Interval::MSECS_PER_SEC;
|
|
161
|
+
value = Napi::Date::New(env, double(dval.GetValue<int32_t>() * scale));
|
|
162
|
+
} break;
|
|
163
|
+
case duckdb::LogicalTypeId::TIMESTAMP:
|
|
164
|
+
case duckdb::LogicalTypeId::TIMESTAMP_TZ: {
|
|
165
|
+
value = Napi::Date::New(env, double(dval.GetValue<int64_t>() / duckdb::Interval::MICROS_PER_MSEC));
|
|
166
|
+
} break;
|
|
167
|
+
#endif
|
|
168
|
+
case duckdb::LogicalTypeId::VARCHAR: {
|
|
169
|
+
value = Napi::String::New(env, duckdb::StringValue::Get(dval));
|
|
170
|
+
} break;
|
|
171
|
+
case duckdb::LogicalTypeId::BLOB: {
|
|
172
|
+
auto &blob = duckdb::StringValue::Get(dval);
|
|
173
|
+
value = Napi::Buffer<char>::Copy(env, blob.c_str(), blob.length());
|
|
174
|
+
} break;
|
|
175
|
+
case duckdb::LogicalTypeId::SQLNULL: {
|
|
176
|
+
value = env.Null();
|
|
177
|
+
} break;
|
|
178
|
+
case duckdb::LogicalTypeId::LIST: {
|
|
179
|
+
auto child_type = duckdb::ListType::GetChildType(dval.type());
|
|
180
|
+
auto &child_values = duckdb::ListValue::GetChildren(dval);
|
|
181
|
+
auto object_value = Napi::Array::New(env);
|
|
182
|
+
for (duckdb::idx_t child_idx = 0; child_idx < child_values.size(); child_idx++) {
|
|
183
|
+
auto child_value = child_values.at(child_idx);
|
|
184
|
+
object_value.Set(child_idx, convert_col_val(env, child_value, child_type.id()));
|
|
185
|
+
}
|
|
186
|
+
value = object_value;
|
|
187
|
+
} break;
|
|
188
|
+
case duckdb::LogicalTypeId::STRUCT: {
|
|
189
|
+
auto &child_types = duckdb::StructType::GetChildTypes(dval.type());
|
|
190
|
+
auto &child_values = duckdb::StructValue::GetChildren(dval);
|
|
191
|
+
auto object_value = Napi::Object::New(env);
|
|
192
|
+
for (duckdb::idx_t child_idx = 0; child_idx < child_values.size(); child_idx++) {
|
|
193
|
+
auto child_value = child_values.at(child_idx);
|
|
194
|
+
auto child_type = child_types.at(child_idx);
|
|
195
|
+
object_value.Set(child_type.first, convert_col_val(env, child_value, child_type.second.id()));
|
|
196
|
+
}
|
|
197
|
+
value = object_value;
|
|
198
|
+
} break;
|
|
199
|
+
default:
|
|
200
|
+
Napi::Error::New(env, "Data type is not supported " + dval.type().ToString()).ThrowAsJavaScriptException();
|
|
201
|
+
return env.Null();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
206
|
+
|
|
124
207
|
static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names, duckdb::DataChunk &chunk) {
|
|
125
208
|
Napi::EscapableHandleScope scope(env);
|
|
126
209
|
std::vector<Napi::String> node_names;
|
|
@@ -134,75 +217,13 @@ static Napi::Value convert_chunk(Napi::Env &env, std::vector<std::string> names,
|
|
|
134
217
|
Napi::Object row_result = Napi::Object::New(env);
|
|
135
218
|
|
|
136
219
|
for (duckdb::idx_t col_idx = 0; col_idx < chunk.ColumnCount(); col_idx++) {
|
|
137
|
-
|
|
138
|
-
// set up a new Napi::Object for some data types, e.g. INTERVAL
|
|
139
|
-
Napi::Object object_value;
|
|
140
|
-
|
|
141
|
-
bool is_object_value {false};
|
|
142
|
-
|
|
143
|
-
auto dval = chunk.GetValue(col_idx, row_idx);
|
|
220
|
+
duckdb::Value dval = chunk.GetValue(col_idx, row_idx);
|
|
144
221
|
if (dval.IsNull()) {
|
|
145
222
|
row_result.Set(node_names[col_idx], env.Null());
|
|
146
223
|
continue;
|
|
147
224
|
}
|
|
148
225
|
|
|
149
|
-
|
|
150
|
-
switch (chunk.data[col_idx].GetType().id()) {
|
|
151
|
-
case duckdb::LogicalTypeId::BOOLEAN: {
|
|
152
|
-
value = Napi::Boolean::New(env, duckdb::BooleanValue::Get(dval));
|
|
153
|
-
} break;
|
|
154
|
-
case duckdb::LogicalTypeId::INTEGER: {
|
|
155
|
-
value = Napi::Number::New(env, duckdb::IntegerValue::Get(dval));
|
|
156
|
-
} break;
|
|
157
|
-
case duckdb::LogicalTypeId::FLOAT: {
|
|
158
|
-
value = Napi::Number::New(env, duckdb::FloatValue::Get(dval));
|
|
159
|
-
} break;
|
|
160
|
-
case duckdb::LogicalTypeId::DOUBLE: {
|
|
161
|
-
value = Napi::Number::New(env, duckdb::DoubleValue::Get(dval));
|
|
162
|
-
} break;
|
|
163
|
-
case duckdb::LogicalTypeId::BIGINT: {
|
|
164
|
-
value = Napi::Number::New(env, duckdb::BigIntValue::Get(dval));
|
|
165
|
-
} break;
|
|
166
|
-
case duckdb::LogicalTypeId::HUGEINT: {
|
|
167
|
-
value = Napi::Number::New(env, dval.GetValue<double>());
|
|
168
|
-
} break;
|
|
169
|
-
case duckdb::LogicalTypeId::INTERVAL: {
|
|
170
|
-
auto interval = duckdb::IntervalValue::Get(dval);
|
|
171
|
-
is_object_value = true;
|
|
172
|
-
object_value = Napi::Object::New(env);
|
|
173
|
-
object_value.Set("months", interval.months);
|
|
174
|
-
object_value.Set("days", interval.days);
|
|
175
|
-
object_value.Set("micros", interval.micros);
|
|
176
|
-
} break;
|
|
177
|
-
#if (NAPI_VERSION > 4)
|
|
178
|
-
case duckdb::LogicalTypeId::DATE: {
|
|
179
|
-
const auto scale = duckdb::Interval::SECS_PER_DAY * duckdb::Interval::MSECS_PER_SEC;
|
|
180
|
-
value = Napi::Date::New(env, double(dval.GetValue<int32_t>() * scale));
|
|
181
|
-
} break;
|
|
182
|
-
case duckdb::LogicalTypeId::TIMESTAMP: {
|
|
183
|
-
value = Napi::Date::New(env, double(dval.GetValue<int64_t>() / duckdb::Interval::MICROS_PER_MSEC));
|
|
184
|
-
} break;
|
|
185
|
-
#endif
|
|
186
|
-
case duckdb::LogicalTypeId::VARCHAR: {
|
|
187
|
-
value = Napi::String::New(env, duckdb::StringValue::Get(dval));
|
|
188
|
-
} break;
|
|
189
|
-
case duckdb::LogicalTypeId::BLOB: {
|
|
190
|
-
auto &blob = duckdb::StringValue::Get(dval);
|
|
191
|
-
value = Napi::Buffer<char>::Copy(env, blob.c_str(), blob.length());
|
|
192
|
-
} break;
|
|
193
|
-
case duckdb::LogicalTypeId::SQLNULL: {
|
|
194
|
-
value = env.Null();
|
|
195
|
-
} break;
|
|
196
|
-
default:
|
|
197
|
-
Napi::Error::New(env, "Data type is not supported " + dval.type().ToString())
|
|
198
|
-
.ThrowAsJavaScriptException();
|
|
199
|
-
return env.Null();
|
|
200
|
-
}
|
|
201
|
-
if (is_object_value == true) {
|
|
202
|
-
row_result.Set(node_names[col_idx], object_value);
|
|
203
|
-
} else {
|
|
204
|
-
row_result.Set(node_names[col_idx], value);
|
|
205
|
-
}
|
|
226
|
+
row_result.Set(node_names[col_idx], convert_col_val(env, dval, chunk.data[col_idx].GetType().id()));
|
|
206
227
|
}
|
|
207
228
|
result.Set(row_idx, row_result);
|
|
208
229
|
}
|
|
@@ -21,16 +21,73 @@ describe("data type support", function () {
|
|
|
21
21
|
});
|
|
22
22
|
});
|
|
23
23
|
it("supports INTERVAL values", function (done) {
|
|
24
|
-
db.prepare(`SELECT
|
|
24
|
+
db.prepare(`SELECT
|
|
25
25
|
INTERVAL 1 MINUTE as minutes,
|
|
26
26
|
INTERVAL 5 DAY as days,
|
|
27
27
|
INTERVAL 4 MONTH as months,
|
|
28
|
-
INTERVAL 4 MONTH + INTERVAL 5 DAY + INTERVAL 1 MINUTE as combined;`
|
|
28
|
+
INTERVAL 4 MONTH + INTERVAL 5 DAY + INTERVAL 1 MINUTE as combined;`
|
|
29
|
+
).each((err, row) => {
|
|
29
30
|
assert(err === null);
|
|
30
|
-
assert.deepEqual(row.minutes, {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
assert.deepEqual(row.minutes, {
|
|
32
|
+
months: 0,
|
|
33
|
+
days: 0,
|
|
34
|
+
micros: 60 * 1000 * 1000,
|
|
35
|
+
});
|
|
36
|
+
assert.deepEqual(row.days, { months: 0, days: 5, micros: 0 });
|
|
37
|
+
assert.deepEqual(row.months, { months: 4, days: 0, micros: 0 });
|
|
38
|
+
assert.deepEqual(row.combined, {
|
|
39
|
+
months: 4,
|
|
40
|
+
days: 5,
|
|
41
|
+
micros: 60 * 1000 * 1000,
|
|
42
|
+
});
|
|
43
|
+
done();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
it("supports STRUCT values", function (done) {
|
|
47
|
+
db.prepare(`SELECT {'x': 1, 'y': 2, 'z': {'a': 'b'}} as struct`).each(
|
|
48
|
+
(err, row) => {
|
|
49
|
+
assert.deepEqual(row.struct, { x: 1, y: 2, z: { a: "b" } });
|
|
50
|
+
done();
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
it("supports LIST values", function (done) {
|
|
55
|
+
db.prepare(`SELECT ['duck', 'duck', 'goose'] as list`).each((err, row) => {
|
|
56
|
+
assert.deepEqual(row.list, ["duck", "duck", "goose"]);
|
|
57
|
+
done();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it("supports DATE values", function (done) {
|
|
61
|
+
db.prepare(`SELECT '2021-01-01'::DATE as dt;`).each((err, row) => {
|
|
62
|
+
assert(err === null);
|
|
63
|
+
assert.deepEqual(row.dt, new Date(Date.UTC(2021, 0, 1)));
|
|
64
|
+
done();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it("supports TIMESTAMP values", function (done) {
|
|
68
|
+
db.prepare(`SELECT '2021-01-01T00:00:00'::TIMESTAMP as ts;`).each((err, row) => {
|
|
69
|
+
assert(err === null);
|
|
70
|
+
assert.deepEqual(row.ts, new Date(Date.UTC(2021, 0, 1)));
|
|
71
|
+
done();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
it("supports TIMESTAMP WITH TIME ZONE values", function (done) {
|
|
75
|
+
db.prepare(`SELECT '2021-01-01T00:00:00Z'::TIMESTAMPTZ as tstz;`).each((err, row) => {
|
|
76
|
+
assert(err === null);
|
|
77
|
+
assert.deepEqual(row.tstz, new Date(Date.UTC(2021, 0, 1)));
|
|
78
|
+
done();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
it("supports DECIMAL values", function (done) {
|
|
82
|
+
db.run("CREATE TABLE decimal_table (d DECIMAL(24, 6))");
|
|
83
|
+
const stmt = db.prepare("INSERT INTO decimal_table VALUES (?)");
|
|
84
|
+
const values = [0, -1, 23534642362547.543463];
|
|
85
|
+
values.forEach((d) => {
|
|
86
|
+
stmt.run(d);
|
|
87
|
+
});
|
|
88
|
+
db.prepare("SELECT d from decimal_table;").all((err, res) => {
|
|
89
|
+
assert(err === null);
|
|
90
|
+
assert(res.every((v, i) => v.d === values[i]));
|
|
34
91
|
done();
|
|
35
92
|
});
|
|
36
93
|
});
|
package/test/prepare.test.js
CHANGED
|
@@ -543,7 +543,7 @@ describe('prepare', function() {
|
|
|
543
543
|
});
|
|
544
544
|
it("should aggregate approx_count_distinct(flt)", function (done) {
|
|
545
545
|
db.all("SELECT approx_count_distinct(flt) as approx_count_distinct FROM foo", function (err, res) {
|
|
546
|
-
assert.ok(res[0].approx_count_distinct >=
|
|
546
|
+
assert.ok(res[0].approx_count_distinct >= 950);
|
|
547
547
|
done(err);
|
|
548
548
|
});
|
|
549
549
|
});
|
|
@@ -587,7 +587,7 @@ describe('prepare', function() {
|
|
|
587
587
|
});
|
|
588
588
|
it("should aggregate approx_count_distinct(num)", function (done) {
|
|
589
589
|
db.all("SELECT approx_count_distinct(num) as approx_count_distinct FROM foo", function (err, res) {
|
|
590
|
-
assert.ok(res[0].approx_count_distinct >=
|
|
590
|
+
assert.ok(res[0].approx_count_distinct >= 950);
|
|
591
591
|
done(err);
|
|
592
592
|
});
|
|
593
593
|
});
|