duckdb 0.7.2-dev865.0 → 0.7.2-dev886.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/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.7.2-dev865.0",
5
+ "version": "0.7.2-dev886.0",
6
6
  "description": "DuckDB node.js API",
7
7
  "gypfile": true,
8
8
  "dependencies": {
@@ -131,6 +131,9 @@ static DefaultMacro internal_macros[] = {
131
131
  // nested list aggregates
132
132
  {DEFAULT_SCHEMA, "list_histogram", {"l", nullptr}, "list_aggr(l, 'histogram')"},
133
133
 
134
+ // date functions
135
+ {DEFAULT_SCHEMA, "date_add", {"date", "interval", nullptr}, "date + interval"},
136
+
134
137
  {nullptr, nullptr, {nullptr}, nullptr}
135
138
  };
136
139
 
@@ -46,32 +46,59 @@ bool DuckCatalog::IsDuckCatalog() {
46
46
  //===--------------------------------------------------------------------===//
47
47
  // Schema
48
48
  //===--------------------------------------------------------------------===//
49
- CatalogEntry *DuckCatalog::CreateSchema(CatalogTransaction transaction, CreateSchemaInfo *info) {
50
- D_ASSERT(!info->schema.empty());
49
+ CatalogEntry *DuckCatalog::CreateSchemaInternal(CatalogTransaction transaction, CreateSchemaInfo *info) {
51
50
  DependencyList dependencies;
52
51
  auto entry = make_unique<DuckSchemaEntry>(this, info->schema, info->internal);
53
52
  auto result = entry.get();
54
53
  if (!schemas->CreateEntry(transaction, info->schema, std::move(entry), dependencies)) {
55
- if (info->on_conflict == OnCreateConflict::ERROR_ON_CONFLICT) {
54
+ return nullptr;
55
+ }
56
+ return (CatalogEntry *)result;
57
+ }
58
+
59
+ CatalogEntry *DuckCatalog::CreateSchema(CatalogTransaction transaction, CreateSchemaInfo *info) {
60
+ D_ASSERT(!info->schema.empty());
61
+ auto result = CreateSchemaInternal(transaction, info);
62
+ if (!result) {
63
+ switch (info->on_conflict) {
64
+ case OnCreateConflict::ERROR_ON_CONFLICT:
56
65
  throw CatalogException("Schema with name %s already exists!", info->schema);
57
- } else {
58
- D_ASSERT(info->on_conflict == OnCreateConflict::IGNORE_ON_CONFLICT);
66
+ case OnCreateConflict::REPLACE_ON_CONFLICT: {
67
+ DropInfo drop_info;
68
+ drop_info.type = CatalogType::SCHEMA_ENTRY;
69
+ drop_info.catalog = info->catalog;
70
+ drop_info.name = info->schema;
71
+ DropSchema(transaction, &drop_info);
72
+ result = CreateSchemaInternal(transaction, info);
73
+ if (!result) {
74
+ throw InternalException("Failed to create schema entry in CREATE_OR_REPLACE");
75
+ }
76
+ break;
77
+ }
78
+ case OnCreateConflict::IGNORE_ON_CONFLICT:
79
+ break;
80
+ default:
81
+ throw InternalException("Unsupported OnCreateConflict for CreateSchema");
59
82
  }
60
83
  return nullptr;
61
84
  }
62
85
  return result;
63
86
  }
64
87
 
65
- void DuckCatalog::DropSchema(ClientContext &context, DropInfo *info) {
88
+ void DuckCatalog::DropSchema(CatalogTransaction transaction, DropInfo *info) {
66
89
  D_ASSERT(!info->name.empty());
67
90
  ModifyCatalog();
68
- if (!schemas->DropEntry(GetCatalogTransaction(context), info->name, info->cascade)) {
91
+ if (!schemas->DropEntry(transaction, info->name, info->cascade)) {
69
92
  if (!info->if_exists) {
70
93
  throw CatalogException("Schema with name \"%s\" does not exist!", info->name);
71
94
  }
72
95
  }
73
96
  }
74
97
 
98
+ void DuckCatalog::DropSchema(ClientContext &context, DropInfo *info) {
99
+ DropSchema(GetCatalogTransaction(context), info);
100
+ }
101
+
75
102
  void DuckCatalog::ScanSchemas(ClientContext &context, std::function<void(CatalogEntry *)> callback) {
76
103
  schemas->Scan(GetCatalogTransaction(context), [&](CatalogEntry *entry) { callback(entry); });
77
104
  }
@@ -197,6 +197,60 @@ list<ColumnDataCollection> BoxRenderer::FetchRenderCollections(ClientContext &co
197
197
  return collections;
198
198
  }
199
199
 
200
+ list<ColumnDataCollection> BoxRenderer::PivotCollections(ClientContext &context, list<ColumnDataCollection> input,
201
+ vector<string> &column_names,
202
+ vector<LogicalType> &result_types, idx_t row_count) {
203
+ auto &top = input.front();
204
+ auto &bottom = input.back();
205
+
206
+ vector<LogicalType> varchar_types;
207
+ vector<string> new_names;
208
+ new_names.emplace_back("Column");
209
+ new_names.emplace_back("Type");
210
+ varchar_types.emplace_back(LogicalType::VARCHAR);
211
+ varchar_types.emplace_back(LogicalType::VARCHAR);
212
+ for (idx_t r = 0; r < top.Count(); r++) {
213
+ new_names.emplace_back("Row " + to_string(r + 1));
214
+ varchar_types.emplace_back(LogicalType::VARCHAR);
215
+ }
216
+ for (idx_t r = 0; r < bottom.Count(); r++) {
217
+ auto row_index = row_count - bottom.Count() + r + 1;
218
+ new_names.emplace_back("Row " + to_string(row_index));
219
+ varchar_types.emplace_back(LogicalType::VARCHAR);
220
+ }
221
+ //
222
+ DataChunk row_chunk;
223
+ row_chunk.Initialize(Allocator::DefaultAllocator(), varchar_types);
224
+ std::list<ColumnDataCollection> result;
225
+ result.emplace_back(context, varchar_types);
226
+ result.emplace_back(context, varchar_types);
227
+ auto &res_coll = result.front();
228
+ ColumnDataAppendState append_state;
229
+ res_coll.InitializeAppend(append_state);
230
+ for (idx_t c = 0; c < top.ColumnCount(); c++) {
231
+ vector<column_t> column_ids {c};
232
+ auto row_index = row_chunk.size();
233
+ idx_t current_index = 0;
234
+ row_chunk.SetValue(current_index++, row_index, column_names[c]);
235
+ row_chunk.SetValue(current_index++, row_index, RenderType(result_types[c]));
236
+ for (auto &collection : input) {
237
+ for (auto &chunk : collection.Chunks(column_ids)) {
238
+ for (idx_t r = 0; r < chunk.size(); r++) {
239
+ row_chunk.SetValue(current_index++, row_index, chunk.GetValue(0, r));
240
+ }
241
+ }
242
+ }
243
+ row_chunk.SetCardinality(row_chunk.size() + 1);
244
+ if (row_chunk.size() == STANDARD_VECTOR_SIZE || c + 1 == top.ColumnCount()) {
245
+ res_coll.Append(append_state, row_chunk);
246
+ row_chunk.Reset();
247
+ }
248
+ }
249
+ column_names = std::move(new_names);
250
+ result_types = std::move(varchar_types);
251
+ return result;
252
+ }
253
+
200
254
  string ConvertRenderValue(const string &input) {
201
255
  return StringUtil::Replace(StringUtil::Replace(input, "\n", "\\n"), string("\0", 1), "\\0");
202
256
  }
@@ -213,11 +267,10 @@ string BoxRenderer::GetRenderValue(ColumnDataRowCollection &rows, idx_t c, idx_t
213
267
  }
214
268
  }
215
269
 
216
- vector<idx_t> BoxRenderer::ComputeRenderWidths(const vector<string> &names, const ColumnDataCollection &result,
270
+ vector<idx_t> BoxRenderer::ComputeRenderWidths(const vector<string> &names, const vector<LogicalType> &result_types,
217
271
  list<ColumnDataCollection> &collections, idx_t min_width,
218
272
  idx_t max_width, vector<idx_t> &column_map, idx_t &total_length) {
219
- auto column_count = result.ColumnCount();
220
- auto &result_types = result.Types();
273
+ auto column_count = result_types.size();
221
274
 
222
275
  vector<idx_t> widths;
223
276
  widths.reserve(column_count);
@@ -357,13 +410,15 @@ void BoxRenderer::RenderHeader(const vector<string> &names, const vector<Logical
357
410
  ss << std::endl;
358
411
 
359
412
  // render the types
360
- for (idx_t c = 0; c < column_count; c++) {
361
- auto column_idx = column_map[c];
362
- auto type = column_idx == SPLIT_COLUMN ? "" : RenderType(result_types[column_idx]);
363
- RenderValue(ss, type, widths[c]);
413
+ if (config.render_mode == RenderMode::ROWS) {
414
+ for (idx_t c = 0; c < column_count; c++) {
415
+ auto column_idx = column_map[c];
416
+ auto type = column_idx == SPLIT_COLUMN ? "" : RenderType(result_types[column_idx]);
417
+ RenderValue(ss, type, widths[c]);
418
+ }
419
+ ss << config.VERTICAL;
420
+ ss << std::endl;
364
421
  }
365
- ss << config.VERTICAL;
366
- ss << std::endl;
367
422
 
368
423
  // render the line under the header
369
424
  ss << config.LMIDDLE;
@@ -390,12 +445,14 @@ void BoxRenderer::RenderValues(const list<ColumnDataCollection> &collections, co
390
445
  auto column_count = column_map.size();
391
446
 
392
447
  vector<ValueRenderAlignment> alignments;
393
- for (idx_t c = 0; c < column_count; c++) {
394
- auto column_idx = column_map[c];
395
- if (column_idx == SPLIT_COLUMN) {
396
- alignments.push_back(ValueRenderAlignment::MIDDLE);
397
- } else {
398
- alignments.push_back(TypeAlignment(result_types[column_idx]));
448
+ if (config.render_mode == RenderMode::ROWS) {
449
+ for (idx_t c = 0; c < column_count; c++) {
450
+ auto column_idx = column_map[c];
451
+ if (column_idx == SPLIT_COLUMN) {
452
+ alignments.push_back(ValueRenderAlignment::MIDDLE);
453
+ } else {
454
+ alignments.push_back(TypeAlignment(result_types[column_idx]));
455
+ }
399
456
  }
400
457
  }
401
458
 
@@ -409,13 +466,28 @@ void BoxRenderer::RenderValues(const list<ColumnDataCollection> &collections, co
409
466
  } else {
410
467
  str = GetRenderValue(rows, column_idx, r);
411
468
  }
412
- RenderValue(ss, str, widths[c], alignments[c]);
469
+ ValueRenderAlignment alignment;
470
+ if (config.render_mode == RenderMode::ROWS) {
471
+ alignment = alignments[c];
472
+ } else {
473
+ if (c < 2) {
474
+ alignment = ValueRenderAlignment::LEFT;
475
+ } else if (c == SPLIT_COLUMN) {
476
+ alignment = ValueRenderAlignment::MIDDLE;
477
+ } else {
478
+ alignment = ValueRenderAlignment::RIGHT;
479
+ }
480
+ }
481
+ RenderValue(ss, str, widths[c], alignment);
413
482
  }
414
483
  ss << config.VERTICAL;
415
484
  ss << std::endl;
416
485
  }
417
486
 
418
487
  if (bottom_rows > 0) {
488
+ if (config.render_mode == RenderMode::COLUMNS) {
489
+ throw InternalException("Columns render mode does not support bottom rows");
490
+ }
419
491
  // render the bottom rows
420
492
  // first render the divider
421
493
  auto brows = bottom_collection.GetRows();
@@ -607,15 +679,19 @@ void BoxRenderer::Render(ClientContext &context, const vector<string> &names, co
607
679
 
608
680
  // fetch the top and bottom render collections from the result
609
681
  auto collections = FetchRenderCollections(context, result, top_rows, bottom_rows);
610
-
611
- auto &result_types = result.Types();
682
+ auto column_names = names;
683
+ auto result_types = result.Types();
684
+ if (config.render_mode == RenderMode::COLUMNS) {
685
+ collections = PivotCollections(context, std::move(collections), column_names, result_types, row_count);
686
+ }
612
687
 
613
688
  // for each column, figure out the width
614
689
  // start off by figuring out the name of the header by looking at the column name and column type
615
690
  idx_t min_width = has_hidden_rows || row_count == 0 ? minimum_row_length : 0;
616
691
  vector<idx_t> column_map;
617
692
  idx_t total_length;
618
- auto widths = ComputeRenderWidths(names, result, collections, min_width, max_width, column_map, total_length);
693
+ auto widths =
694
+ ComputeRenderWidths(column_names, result_types, collections, min_width, max_width, column_map, total_length);
619
695
 
620
696
  // render boundaries for the individual columns
621
697
  vector<idx_t> boundaries;
@@ -631,7 +707,7 @@ void BoxRenderer::Render(ClientContext &context, const vector<string> &names, co
631
707
 
632
708
  // now begin rendering
633
709
  // first render the header
634
- RenderHeader(names, result_types, column_map, widths, boundaries, total_length, row_count > 0, ss);
710
+ RenderHeader(column_names, result_types, column_map, widths, boundaries, total_length, row_count > 0, ss);
635
711
 
636
712
  // render the values, if there are any
637
713
  RenderValues(collections, column_map, widths, result_types, ss);
@@ -649,10 +725,20 @@ void BoxRenderer::Render(ClientContext &context, const vector<string> &names, co
649
725
  }
650
726
  }
651
727
  idx_t column_count = column_map.size();
652
- if (has_hidden_columns) {
653
- column_count--;
654
- column_count_str += " (" + to_string(column_count) + " shown)";
728
+ if (config.render_mode == RenderMode::COLUMNS) {
729
+ if (has_hidden_columns) {
730
+ has_hidden_rows = true;
731
+ shown_str = " (" + to_string(column_count - 3) + " shown)";
732
+ } else {
733
+ shown_str = string();
734
+ }
735
+ } else {
736
+ if (has_hidden_columns) {
737
+ column_count--;
738
+ column_count_str += " (" + to_string(column_count) + " shown)";
739
+ }
655
740
  }
741
+
656
742
  RenderRowCount(std::move(row_count_str), std::move(shown_str), column_count_str, boundaries, has_hidden_rows,
657
743
  has_hidden_columns, total_length, row_count, column_count, minimum_row_length, ss);
658
744
  }
@@ -1,8 +1,8 @@
1
1
  #ifndef DUCKDB_VERSION
2
- #define DUCKDB_VERSION "0.7.2-dev865"
2
+ #define DUCKDB_VERSION "0.7.2-dev886"
3
3
  #endif
4
4
  #ifndef DUCKDB_SOURCE_ID
5
- #define DUCKDB_SOURCE_ID "61325fdd83"
5
+ #define DUCKDB_SOURCE_ID "7d73c6e6a8"
6
6
  #endif
7
7
  #include "duckdb/function/table/system_functions.hpp"
8
8
  #include "duckdb/main/database.hpp"
@@ -59,8 +59,9 @@ public:
59
59
  DUCKDB_API string GetDBPath() override;
60
60
 
61
61
  private:
62
+ DUCKDB_API void DropSchema(CatalogTransaction transaction, DropInfo *info);
62
63
  DUCKDB_API void DropSchema(ClientContext &context, DropInfo *info) override;
63
-
64
+ CatalogEntry *CreateSchemaInternal(CatalogTransaction transaction, CreateSchemaInfo *info);
64
65
  void Verify() override;
65
66
 
66
67
  private:
@@ -18,6 +18,7 @@ class ColumnDataCollection;
18
18
  class ColumnDataRowCollection;
19
19
 
20
20
  enum class ValueRenderAlignment { LEFT, MIDDLE, RIGHT };
21
+ enum class RenderMode { ROWS, COLUMNS };
21
22
 
22
23
  struct BoxRendererConfig {
23
24
  // a max_width of 0 means we default to the terminal width
@@ -30,7 +31,10 @@ struct BoxRendererConfig {
30
31
  // the max col width determines the maximum size of a single column
31
32
  // note that the max col width is only used if the result does not fit on the screen
32
33
  idx_t max_col_width = 20;
34
+ //! how to render NULL values
33
35
  string null_value = "NULL";
36
+ //! Whether or not to render row-wise or column-wise
37
+ RenderMode render_mode = RenderMode::ROWS;
34
38
 
35
39
  #ifndef DUCKDB_ASCII_TREE_RENDERER
36
40
  const char *LTCORNER = "\342\224\214"; // "┌";
@@ -94,10 +98,12 @@ private:
94
98
  string RenderType(const LogicalType &type);
95
99
  ValueRenderAlignment TypeAlignment(const LogicalType &type);
96
100
  string GetRenderValue(ColumnDataRowCollection &rows, idx_t c, idx_t r);
97
-
98
101
  list<ColumnDataCollection> FetchRenderCollections(ClientContext &context, const ColumnDataCollection &result,
99
102
  idx_t top_rows, idx_t bottom_rows);
100
- vector<idx_t> ComputeRenderWidths(const vector<string> &names, const ColumnDataCollection &result,
103
+ list<ColumnDataCollection> PivotCollections(ClientContext &context, list<ColumnDataCollection> input,
104
+ vector<string> &column_names, vector<LogicalType> &result_types,
105
+ idx_t row_count);
106
+ vector<idx_t> ComputeRenderWidths(const vector<string> &names, const vector<LogicalType> &result_types,
101
107
  list<ColumnDataCollection> &collections, idx_t min_width, idx_t max_width,
102
108
  vector<idx_t> &column_map, idx_t &total_length);
103
109
  void RenderHeader(const vector<string> &names, const vector<LogicalType> &result_types,
@@ -67,6 +67,8 @@ private:
67
67
  unique_ptr<LogicalOperator> PushdownSingleJoin(unique_ptr<LogicalOperator> op, unordered_set<idx_t> &left_bindings,
68
68
  unordered_set<idx_t> &right_bindings);
69
69
 
70
+ //! Push any remaining filters into a LogicalFilter at this level
71
+ unique_ptr<LogicalOperator> PushFinalFilters(unique_ptr<LogicalOperator> op);
70
72
  // Finish pushing down at this operator, creating a LogicalFilter to store any of the stored filters and recursively
71
73
  // pushing down into its children (if any)
72
74
  unique_ptr<LogicalOperator> FinishPushdown(unique_ptr<LogicalOperator> op);
@@ -105,13 +105,7 @@ void FilterPushdown::GenerateFilters() {
105
105
  });
106
106
  }
107
107
 
108
- unique_ptr<LogicalOperator> FilterPushdown::FinishPushdown(unique_ptr<LogicalOperator> op) {
109
- // unhandled type, first perform filter pushdown in its children
110
- for (auto &child : op->children) {
111
- FilterPushdown pushdown(optimizer);
112
- child = pushdown.Rewrite(std::move(child));
113
- }
114
- // now push any existing filters
108
+ unique_ptr<LogicalOperator> FilterPushdown::PushFinalFilters(unique_ptr<LogicalOperator> op) {
115
109
  if (filters.empty()) {
116
110
  // no filters to push
117
111
  return op;
@@ -124,6 +118,16 @@ unique_ptr<LogicalOperator> FilterPushdown::FinishPushdown(unique_ptr<LogicalOpe
124
118
  return std::move(filter);
125
119
  }
126
120
 
121
+ unique_ptr<LogicalOperator> FilterPushdown::FinishPushdown(unique_ptr<LogicalOperator> op) {
122
+ // unhandled type, first perform filter pushdown in its children
123
+ for (auto &child : op->children) {
124
+ FilterPushdown pushdown(optimizer);
125
+ child = pushdown.Rewrite(std::move(child));
126
+ }
127
+ // now push any existing filters
128
+ return PushFinalFilters(std::move(op));
129
+ }
130
+
127
131
  void FilterPushdown::Filter::ExtractBindings() {
128
132
  bindings.clear();
129
133
  LogicalJoin::GetExpressionBindings(*filter, bindings);
@@ -122,16 +122,7 @@ unique_ptr<LogicalOperator> FilterPushdown::PushdownLeftJoin(unique_ptr<LogicalO
122
122
  right_pushdown.GenerateFilters();
123
123
  op->children[0] = left_pushdown.Rewrite(std::move(op->children[0]));
124
124
  op->children[1] = right_pushdown.Rewrite(std::move(op->children[1]));
125
- if (filters.empty()) {
126
- // no filters to push
127
- return op;
128
- }
129
- auto filter = make_unique<LogicalFilter>();
130
- for (auto &f : filters) {
131
- filter->expressions.push_back(std::move(f->filter));
132
- }
133
- filter->children.push_back(std::move(op));
134
- return std::move(filter);
125
+ return PushFinalFilters(std::move(op));
135
126
  }
136
127
 
137
128
  } // namespace duckdb
@@ -77,7 +77,7 @@ unique_ptr<LogicalOperator> FilterPushdown::PushdownMarkJoin(unique_ptr<LogicalO
77
77
  }
78
78
  op->children[0] = left_pushdown.Rewrite(std::move(op->children[0]));
79
79
  op->children[1] = right_pushdown.Rewrite(std::move(op->children[1]));
80
- return FinishPushdown(std::move(op));
80
+ return PushFinalFilters(std::move(op));
81
81
  }
82
82
 
83
83
  } // namespace duckdb
@@ -23,7 +23,7 @@ unique_ptr<LogicalOperator> FilterPushdown::PushdownSingleJoin(unique_ptr<Logica
23
23
  }
24
24
  op->children[0] = left_pushdown.Rewrite(std::move(op->children[0]));
25
25
  op->children[1] = right_pushdown.Rewrite(std::move(op->children[1]));
26
- return FinishPushdown(std::move(op));
26
+ return PushFinalFilters(std::move(op));
27
27
  }
28
28
 
29
29
  } // namespace duckdb
@@ -10,6 +10,7 @@
10
10
  #include "duckdb/parser/statement/drop_statement.hpp"
11
11
  #include "duckdb/parser/parsed_data/drop_info.hpp"
12
12
  #include "duckdb/parser/expression/cast_expression.hpp"
13
+ #include "duckdb/parser/expression/function_expression.hpp"
13
14
  #include "duckdb/parser/result_modifier.hpp"
14
15
  #include "duckdb/parser/tableref/subqueryref.hpp"
15
16
 
@@ -99,15 +100,35 @@ unique_ptr<SQLStatement> Transformer::CreatePivotStatement(unique_ptr<SQLStateme
99
100
  unique_ptr<QueryNode> Transformer::TransformPivotStatement(duckdb_libpgquery::PGSelectStmt *stmt) {
100
101
  auto pivot = stmt->pivot;
101
102
  auto source = TransformTableRefNode(pivot->source);
102
- auto columns = TransformPivotList(pivot->columns);
103
103
 
104
104
  auto select_node = make_unique<SelectNode>();
105
105
  // handle the CTEs
106
106
  if (stmt->withClause) {
107
107
  TransformCTE(reinterpret_cast<duckdb_libpgquery::PGWithClause *>(stmt->withClause), select_node->cte_map);
108
108
  }
109
+ if (!pivot->columns) {
110
+ // no pivot columns - not actually a pivot
111
+ select_node->from_table = std::move(source);
112
+ if (pivot->groups) {
113
+ auto groups = TransformStringList(pivot->groups);
114
+ GroupingSet set;
115
+ for (idx_t gr = 0; gr < groups.size(); gr++) {
116
+ auto &group = groups[gr];
117
+ auto colref = make_unique<ColumnRefExpression>(group);
118
+ select_node->select_list.push_back(colref->Copy());
119
+ select_node->groups.group_expressions.push_back(std::move(colref));
120
+ set.insert(gr);
121
+ }
122
+ select_node->groups.grouping_sets.push_back(std::move(set));
123
+ }
124
+ if (pivot->aggrs) {
125
+ TransformExpressionList(*pivot->aggrs, select_node->select_list);
126
+ }
127
+ return std::move(select_node);
128
+ }
109
129
 
110
130
  // generate CREATE TYPE statements for each of the columns that do not have an IN list
131
+ auto columns = TransformPivotList(pivot->columns);
111
132
  auto pivot_idx = PivotEntryCount();
112
133
  for (idx_t c = 0; c < columns.size(); c++) {
113
134
  auto &col = columns[c];
@@ -131,13 +152,17 @@ unique_ptr<QueryNode> Transformer::TransformPivotStatement(duckdb_libpgquery::PG
131
152
 
132
153
  auto pivot_ref = make_unique<PivotRef>();
133
154
  pivot_ref->source = std::move(source);
134
- if (pivot->aggrs) {
135
- TransformExpressionList(*pivot->aggrs, pivot_ref->aggregates);
155
+ if (pivot->unpivots) {
156
+ pivot_ref->unpivot_names = TransformStringList(pivot->unpivots);
136
157
  } else {
137
- if (!pivot->unpivots) {
138
- throw InternalException("No unpivots and no aggrs");
158
+ if (pivot->aggrs) {
159
+ TransformExpressionList(*pivot->aggrs, pivot_ref->aggregates);
160
+ } else {
161
+ // pivot but no aggregates specified - push a count star
162
+ vector<unique_ptr<ParsedExpression>> children;
163
+ auto function = make_unique<FunctionExpression>("count_star", std::move(children));
164
+ pivot_ref->aggregates.push_back(std::move(function));
139
165
  }
140
- pivot_ref->unpivot_names = TransformStringList(pivot->unpivots);
141
166
  }
142
167
  if (pivot->groups) {
143
168
  pivot_ref->groups = TransformStringList(pivot->groups);
@@ -56,7 +56,7 @@ static void ConstructPivots(PivotRef &ref, idx_t pivot_idx, vector<unique_ptr<Pa
56
56
  auto &function = (FunctionExpression &)*copy;
57
57
  // add the filter and alias to the aggregate function
58
58
  function.filter = expr->Copy();
59
- if (ref.aggregates.size() > 1) {
59
+ if (ref.aggregates.size() > 1 || !function.alias.empty()) {
60
60
  // if there are multiple aggregates specified we add the name of the aggregate as well
61
61
  function.alias = name + "_" + function.GetName();
62
62
  } else {