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.
@@ -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
- ThriftFileTransport(Allocator &allocator, FileHandle &handle_p)
6922
- : allocator(allocator), handle(handle_p), location(0) {
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
- if (prefetched_data && location >= prefetch_location &&
6927
- location + len < prefetch_location + prefetched_data->GetSize()) {
6928
- memcpy(buf, prefetched_data->get() + location - prefetch_location, len);
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
- handle.Read(buf, len, location);
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
- void Prefetch(idx_t pos, idx_t len) {
6937
- prefetch_location = pos;
6938
- prefetched_data = allocator.Allocate(len);
6939
- handle.Read(prefetched_data->get(), len, prefetch_location);
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
- prefetched_data.reset();
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
- unique_ptr<AllocatedData> prefetched_data;
6963
- idx_t prefetch_location;
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
- Napi::Value value;
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
- // TODO templateroo here
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;`).each((err, row) => {
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, { months: 0, days: 0, micros: 60 * 1000 * 1000});
31
- assert.deepEqual(row.days, { months: 0, days: 5, micros: 0});
32
- assert.deepEqual(row.months, {months: 4, days: 0, micros: 0});
33
- assert.deepEqual(row.combined, {months: 4, days: 5, micros: 60 * 1000 * 1000});
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
  });
@@ -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 >= 1000);
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 >= 1000);
590
+ assert.ok(res[0].approx_count_distinct >= 950);
591
591
  done(err);
592
592
  });
593
593
  });