ladybug 0.22.0.dev20261024__tar.gz → 0.22.3__tar.gz
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.
- sdist/PKG-INFO +1 -1
- sdist/ladybug-source/CMakeLists.txt +2 -2
- sdist/ladybug-source/pixi.toml +9 -1
- sdist/ladybug-source/src/binder/bind/bind_ddl.cpp +30 -18
- sdist/ladybug-source/src/binder/bind/bind_file_scan.cpp +3 -3
- sdist/ladybug-source/src/binder/bind/copy/bind_copy_from.cpp +22 -14
- sdist/ladybug-source/src/binder/bind_expression/bind_case_expression.cpp +32 -12
- sdist/ladybug-source/src/binder/query/query_graph.cpp +10 -4
- sdist/ladybug-source/src/c_api/version.cpp +1 -1
- sdist/ladybug-source/src/catalog/catalog_entry/node_table_catalog_entry.cpp +2 -0
- sdist/ladybug-source/src/catalog/catalog_entry/rel_group_catalog_entry.cpp +2 -0
- sdist/ladybug-source/src/common/enums/storage_format.cpp +22 -2
- sdist/ladybug-source/src/common/serializer/buffered_file.cpp +23 -11
- sdist/ladybug-source/src/common/vector/value_vector.cpp +1 -1
- sdist/ladybug-source/src/expression_evaluator/lambda_evaluator.cpp +163 -9
- sdist/ladybug-source/src/expression_evaluator/list_slice_info.cpp +0 -33
- sdist/ladybug-source/src/function/list/list_filter.cpp +1 -7
- sdist/ladybug-source/src/function/list/list_slice_function.cpp +19 -2
- sdist/ladybug-source/src/function/list/list_sort_function.cpp +14 -2
- sdist/ladybug-source/src/function/list/list_transform.cpp +0 -4
- sdist/ladybug-source/src/function/list/quantifier_functions.cpp +0 -6
- sdist/ladybug-source/src/function/path/length_function.cpp +4 -1
- sdist/ladybug-source/src/function/string/CMakeLists.txt +1 -0
- sdist/ladybug-source/src/function/string/base_pad_function.cpp +39 -0
- sdist/ladybug-source/src/function/string/concat_ws.cpp +4 -17
- sdist/ladybug-source/src/function/string/regex_full_match_function.cpp +3 -6
- sdist/ladybug-source/src/function/string/regex_replace_function.cpp +6 -7
- sdist/ladybug-source/src/function/string/split_part.cpp +0 -5
- sdist/ladybug-source/src/function/string/string_split_function.cpp +2 -3
- sdist/ladybug-source/src/function/vector_string_functions.cpp +20 -3
- sdist/ladybug-source/src/graph/on_disk_graph.cpp +7 -1
- sdist/ladybug-source/src/include/binder/query/query_graph.h +2 -0
- sdist/ladybug-source/src/include/common/enums/storage_format.h +2 -1
- sdist/ladybug-source/src/include/common/types/string_t.h +4 -0
- sdist/ladybug-source/src/include/expression_evaluator/lambda_evaluator.h +4 -0
- sdist/ladybug-source/src/include/expression_evaluator/list_slice_info.h +10 -7
- sdist/ladybug-source/src/include/function/function.h +4 -0
- sdist/ladybug-source/src/include/function/list/functions/list_len_function.h +3 -3
- sdist/ladybug-source/src/include/function/string/functions/base_pad_function.h +19 -0
- sdist/ladybug-source/src/include/function/string/functions/base_regexp_function.h +7 -0
- sdist/ladybug-source/src/include/function/string/functions/repeat_function.h +1 -1
- sdist/ladybug-source/src/include/function/string/functions/substr_function.h +17 -6
- sdist/ladybug-source/src/include/optimizer/remove_unnecessary_distinct_optimizer.h +4 -3
- sdist/ladybug-source/src/include/optimizer/remove_unnecessary_order_by_optimizer.h +10 -9
- sdist/ladybug-source/src/include/parser/visitor/statement_read_write_analyzer.h +1 -0
- sdist/ladybug-source/src/include/planner/operator/sip/side_way_info_passing.h +6 -5
- sdist/ladybug-source/src/include/processor/operator/intersect/intersect.h +9 -3
- sdist/ladybug-source/src/include/processor/operator/scan/scan_rel_table.h +5 -0
- sdist/ladybug-source/src/include/storage/storage_version_info.h +2 -1
- sdist/ladybug-source/src/include/storage/table/columnar_node_table_base.h +5 -3
- sdist/ladybug-source/src/include/storage/table/columnar_rel_table_base.h +3 -1
- sdist/ladybug-source/src/include/storage/table/external_table_hooks.h +102 -0
- sdist/ladybug-source/src/include/storage/table/ice_disk_rel_table.h +3 -1
- sdist/ladybug-source/src/include/storage/table/rel_table.h +3 -1
- sdist/ladybug-source/src/optimizer/acc_hash_join_optimizer.cpp +74 -3
- sdist/ladybug-source/src/optimizer/bool_folding_optimizer.cpp +12 -2
- sdist/ladybug-source/src/optimizer/filter_push_down_optimizer.cpp +4 -0
- sdist/ladybug-source/src/optimizer/limit_push_down_optimizer.cpp +18 -3
- sdist/ladybug-source/src/optimizer/optimizer.cpp +8 -8
- sdist/ladybug-source/src/optimizer/remove_unnecessary_distinct_optimizer.cpp +12 -7
- sdist/ladybug-source/src/optimizer/remove_unnecessary_join_optimizer.cpp +2 -1
- sdist/ladybug-source/src/optimizer/remove_unnecessary_order_by_optimizer.cpp +31 -3
- sdist/ladybug-source/src/planner/plan/append_join.cpp +1 -1
- sdist/ladybug-source/src/planner/plan/plan_join_order.cpp +43 -2
- sdist/ladybug-source/src/processor/map/map_extend.cpp +18 -4
- sdist/ladybug-source/src/processor/map/map_intersect.cpp +8 -2
- sdist/ladybug-source/src/processor/operator/intersect/intersect.cpp +32 -26
- sdist/ladybug-source/src/processor/operator/scan/scan_multi_rel_tables.cpp +33 -1
- sdist/ladybug-source/src/processor/operator/scan/scan_node_table.cpp +23 -6
- sdist/ladybug-source/src/processor/operator/scan/scan_rel_table.cpp +17 -3
- sdist/ladybug-source/src/processor/operator/simple/import_db.cpp +6 -0
- sdist/ladybug-source/src/storage/file_handle.cpp +3 -1
- sdist/ladybug-source/src/storage/storage_manager.cpp +35 -3
- sdist/ladybug-source/src/storage/table/CMakeLists.txt +1 -0
- sdist/ladybug-source/src/storage/table/column.cpp +2 -1
- sdist/ladybug-source/src/storage/table/external_table_hooks.cpp +88 -0
- sdist/ladybug.egg-info/PKG-INFO +1 -1
- sdist/pyproject.toml +1 -1
sdist/PKG-INFO
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
cmake_minimum_required(VERSION 3.15)
|
|
2
2
|
|
|
3
|
-
project(Lbug VERSION 0.22.
|
|
3
|
+
project(Lbug VERSION 0.22.3 LANGUAGES CXX C)
|
|
4
4
|
|
|
5
5
|
option(SINGLE_THREADED "Single-threaded mode" FALSE)
|
|
6
6
|
if(SINGLE_THREADED)
|
|
@@ -553,7 +553,7 @@ unset(LBUG_LINK_LIBRARIES)
|
|
|
553
553
|
|
|
554
554
|
add_definitions(-DLBUG_ROOT_DIRECTORY="${PROJECT_SOURCE_DIR}")
|
|
555
555
|
add_definitions(-DLBUG_CMAKE_VERSION="${CMAKE_PROJECT_VERSION}")
|
|
556
|
-
add_definitions(-DLBUG_EXTENSION_VERSION="
|
|
556
|
+
add_definitions(-DLBUG_EXTENSION_VERSION="0.22.0")
|
|
557
557
|
|
|
558
558
|
if(BUILD_LBUG OR LBUG_API_USE_PRECOMPILED_LIB)
|
|
559
559
|
include_directories(
|
sdist/ladybug-source/pixi.toml
CHANGED
|
@@ -5,5 +5,13 @@ platforms = ["linux-64", "linux-aarch64", "osx-64", "osx-arm64", "win-64"]
|
|
|
5
5
|
|
|
6
6
|
[dependencies]
|
|
7
7
|
libadbc-driver-manager = ">=1.11.0,<2"
|
|
8
|
-
libarrow
|
|
8
|
+
# Keep the conda libarrow on 25.x: the algo extension builds against the
|
|
9
|
+
# system Apache Arrow 26 (libarrow.so.2600 from packages.apache.org), while
|
|
10
|
+
# the pixi env only needs Arrow 25 for libadbc-driver-manager. Letting this
|
|
11
|
+
# float to >=26 shadows the system 2600 at runtime via LD_LIBRARY_PATH, and
|
|
12
|
+
# conda-forge's libarrow 26.0.0 currently resolves with simdjson 5.0.8
|
|
13
|
+
# (libsimdjson.so.35) while its DT_NEEDED asks for libsimdjson.so.34,
|
|
14
|
+
# breaking `LOAD EXTENSION 'algo'` with "libsimdjson.so.34: cannot open
|
|
15
|
+
# shared object file". Revisit once conda-forge's arrow/simdjson are coherent.
|
|
16
|
+
libarrow = ">=25.0.0,<26"
|
|
9
17
|
llvm-openmp = ">=22.1.8,<23"
|
|
@@ -324,10 +324,11 @@ BoundCreateTableInfo Binder::bindCreateRelTableGroupInfo(const CreateTableInfo*
|
|
|
324
324
|
if (!storage.empty()) {
|
|
325
325
|
auto dotPos = storage.find('.');
|
|
326
326
|
// Check if storage is database.table format by verifying the attached database exists
|
|
327
|
-
// Handle special case where
|
|
327
|
+
// Handle special case where columnar storage paths (e.g. icebug-disk, lance)
|
|
328
|
+
// could contain a dot. Only unformatted storage is probed as database.table.
|
|
328
329
|
// Otherwise, treat as file path (e.g., "dataset/demo-db/icebug-disk/demo" or
|
|
329
330
|
// "data.parquet")
|
|
330
|
-
if (storageFormat
|
|
331
|
+
if (storageFormat == StorageFormat::NONE && dotPos != std::string::npos) {
|
|
331
332
|
std::string dbName = storage.substr(0, dotPos);
|
|
332
333
|
std::string tableName = storage.substr(dotPos + 1);
|
|
333
334
|
if (!dbName.empty()) {
|
|
@@ -422,22 +423,25 @@ BoundCreateTableInfo Binder::bindCreateRelTableGroupInfo(const CreateTableInfo*
|
|
|
422
423
|
}
|
|
423
424
|
}
|
|
424
425
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
426
|
+
auto getEntryStorageFormat = [](TableCatalogEntry* entry) {
|
|
427
|
+
return entry->getType() == CatalogEntryType::NODE_TABLE_ENTRY ?
|
|
428
|
+
entry->ptrCast<NodeTableCatalogEntry>()->getStorageFormat() :
|
|
429
|
+
StorageFormat::NONE;
|
|
430
|
+
};
|
|
431
|
+
const auto srcStorageFormat = getEntryStorageFormat(srcEntry);
|
|
432
|
+
const auto dstStorageFormat = getEntryStorageFormat(dstEntry);
|
|
433
|
+
|
|
434
|
+
// We don't allow mixing columnar-format tables (e.g. icebug-disk, lance) with
|
|
435
|
+
// tables of another format. A rel table must use the same storage format as the
|
|
436
|
+
// node tables it connects.
|
|
437
|
+
if (storageFormat != srcStorageFormat || storageFormat != dstStorageFormat) {
|
|
438
|
+
const auto oddFormat = storageFormat != StorageFormat::NONE ? storageFormat :
|
|
439
|
+
srcStorageFormat != StorageFormat::NONE ? srcStorageFormat :
|
|
440
|
+
dstStorageFormat;
|
|
441
|
+
const auto oddName = StorageFormatUtils::toString(oddFormat);
|
|
442
|
+
throw BinderException(std::format("Cannot mix {} tables with non-{} tables in CREATE "
|
|
443
|
+
"REL TABLE.",
|
|
444
|
+
oddName, oddName));
|
|
441
445
|
}
|
|
442
446
|
|
|
443
447
|
// Use the actual shadow table IDs, not FOREIGN_TABLE_ID
|
|
@@ -906,6 +910,14 @@ std::unique_ptr<BoundStatement> Binder::bindAlterFromToConnection(
|
|
|
906
910
|
auto info = alter.getInfo();
|
|
907
911
|
auto extraInfo = info->extraInfo->constPtrCast<ExtraAddFromToConnection>();
|
|
908
912
|
auto tableName = info->tableName;
|
|
913
|
+
// Only a rel table has FROM/TO connections; Alter would cast any other entry to one.
|
|
914
|
+
auto catalog = Catalog::Get(*clientContext);
|
|
915
|
+
auto transaction = transaction::Transaction::Get(*clientContext);
|
|
916
|
+
if (catalog->containsTable(transaction, tableName) &&
|
|
917
|
+
catalog->getTableCatalogEntry(transaction, tableName)->getType() !=
|
|
918
|
+
CatalogEntryType::REL_GROUP_ENTRY) {
|
|
919
|
+
throw BinderException(std::format("{} is not of type REL.", tableName));
|
|
920
|
+
}
|
|
909
921
|
auto [srcTableEntry, srcDbName] = bindNodeTableEntry(extraInfo->srcTableName);
|
|
910
922
|
auto [dstTableEntry, dstDbName] = bindNodeTableEntry(extraInfo->dstTableName);
|
|
911
923
|
auto srcTableID = srcTableEntry->getTableID();
|
|
@@ -198,9 +198,9 @@ std::unique_ptr<BoundBaseScanSource> Binder::bindQueryScanSource(const BaseScanS
|
|
|
198
198
|
throw BinderException(std::format("Query returns {} columns but {} columns were expected.",
|
|
199
199
|
columns.size(), columnNames.size()));
|
|
200
200
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
201
|
+
// The columns are matched to columnNames by position when the COPY operator is bound. They
|
|
202
|
+
// must not be renamed here: a RETURN list can bind the same Expression object more than once
|
|
203
|
+
// (e.g. RETURN i, i), and setAlias would leave that shared object holding one name only.
|
|
204
204
|
auto scanInfo = BoundQueryScanSourceInfo(bindParsingOptions(options));
|
|
205
205
|
return std::make_unique<BoundQueryScanSource>(std::move(boundStatement), std::move(scanInfo));
|
|
206
206
|
}
|
|
@@ -116,18 +116,26 @@ static void bindExpectedRelColumns(const RelGroupCatalogEntry& entry,
|
|
|
116
116
|
const CopyFromColumnInfo& info, std::vector<std::string>& columnNames,
|
|
117
117
|
std::vector<LogicalType>& columnTypes);
|
|
118
118
|
|
|
119
|
+
// Returns the source column that feeds a table property. A scan source names its leading columns
|
|
120
|
+
// from expectedColumnNames by position, so the position decides which column a property reads.
|
|
121
|
+
// The name of the bound expression cannot be used for this: a query source may return one shared
|
|
122
|
+
// Expression object for several columns (e.g. RETURN i, i). Columns past the expected ones carry
|
|
123
|
+
// copy warnings. A source bound without expected names, such as a table whose only column is
|
|
124
|
+
// SERIAL, keeps the names it read from the file and is matched by those names.
|
|
119
125
|
static std::pair<ColumnEvaluateType, std::shared_ptr<Expression>> matchColumnExpression(
|
|
120
|
-
const expression_vector& columns, const
|
|
121
|
-
ExpressionBinder& expressionBinder) {
|
|
122
|
-
for (auto
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
const expression_vector& columns, const std::vector<std::string>& expectedColumnNames,
|
|
127
|
+
const PropertyDefinition& property, ExpressionBinder& expressionBinder) {
|
|
128
|
+
for (auto i = 0u; i < columns.size(); ++i) {
|
|
129
|
+
auto columnName =
|
|
130
|
+
i < expectedColumnNames.size() ? expectedColumnNames[i] : columns[i]->toString();
|
|
131
|
+
if (property.getName() != columnName) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
auto& column = columns[i];
|
|
135
|
+
if (column->dataType == property.getType()) {
|
|
136
|
+
return {ColumnEvaluateType::REFERENCE, column};
|
|
130
137
|
}
|
|
138
|
+
return {ColumnEvaluateType::CAST, expressionBinder.forceCast(column, property.getType())};
|
|
131
139
|
}
|
|
132
140
|
return {ColumnEvaluateType::DEFAULT, expressionBinder.bindExpression(*property.defaultExpr)};
|
|
133
141
|
}
|
|
@@ -163,8 +171,8 @@ BoundCopyFromInfo Binder::bindCopyNodeFromInfo(std::string tableName,
|
|
|
163
171
|
expression_vector columns;
|
|
164
172
|
std::vector<ColumnEvaluateType> evaluateTypes;
|
|
165
173
|
for (auto& property : properties) {
|
|
166
|
-
auto [evaluateType, column] =
|
|
167
|
-
|
|
174
|
+
auto [evaluateType, column] = matchColumnExpression(boundSource->getColumns(),
|
|
175
|
+
expectedColumnNames, property, expressionBinder);
|
|
168
176
|
columns.push_back(column);
|
|
169
177
|
evaluateTypes.push_back(evaluateType);
|
|
170
178
|
}
|
|
@@ -247,8 +255,8 @@ BoundCopyFromInfo Binder::bindCopyRelFromInfo(std::string tableName,
|
|
|
247
255
|
ColumnEvaluateType::REFERENCE, ColumnEvaluateType::REFERENCE};
|
|
248
256
|
for (auto i = 1u; i < properties.size(); ++i) { // skip internal ID
|
|
249
257
|
auto& property = properties[i];
|
|
250
|
-
auto [evaluateType, column] =
|
|
251
|
-
|
|
258
|
+
auto [evaluateType, column] = matchColumnExpression(boundSource->getColumns(),
|
|
259
|
+
expectedColumnNames, property, expressionBinder);
|
|
252
260
|
columnExprs.push_back(column);
|
|
253
261
|
evaluateTypes.push_back(evaluateType);
|
|
254
262
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
#include <algorithm>
|
|
2
|
+
|
|
1
3
|
#include "binder/binder.h"
|
|
2
4
|
#include "binder/expression/case_expression.h"
|
|
3
5
|
#include "binder/expression/expression_util.h"
|
|
4
6
|
#include "binder/expression_binder.h"
|
|
7
|
+
#include "function/cast/vector_cast_functions.h"
|
|
5
8
|
#include "parser/expression/parsed_case_expression.h"
|
|
6
9
|
|
|
7
10
|
using namespace lbug::common;
|
|
@@ -10,23 +13,40 @@ using namespace lbug::parser;
|
|
|
10
13
|
namespace lbug {
|
|
11
14
|
namespace binder {
|
|
12
15
|
|
|
16
|
+
// Whether implicitCastIfNecessary would accept casting the expression to the type.
|
|
17
|
+
static bool canImplicitlyCast(const Expression& expression, const LogicalType& type) {
|
|
18
|
+
return expression.dataType == type || type.containsAny() ||
|
|
19
|
+
ExpressionUtil::canCastStatically(expression, type) ||
|
|
20
|
+
function::CastFunction::hasImplicitCast(expression.dataType, type);
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
std::shared_ptr<Expression> ExpressionBinder::bindCaseExpression(
|
|
14
24
|
const ParsedExpression& parsedExpression) {
|
|
15
25
|
auto& parsedCaseExpression = parsedExpression.constCast<ParsedCaseExpression>();
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
// The result type is the common type of every THEN and the ELSE, as for coalesce.
|
|
27
|
+
expression_vector results;
|
|
18
28
|
for (auto i = 0u; i < parsedCaseExpression.getNumCaseAlternative(); ++i) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
results.push_back(
|
|
30
|
+
bindExpression(*parsedCaseExpression.getCaseAlternative(i)->thenExpression));
|
|
31
|
+
}
|
|
32
|
+
if (parsedCaseExpression.hasElseExpression()) {
|
|
33
|
+
results.push_back(bindExpression(*parsedCaseExpression.getElseExpression()));
|
|
24
34
|
}
|
|
25
|
-
|
|
26
|
-
if (resultType
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
auto resultType = LogicalType::ANY();
|
|
36
|
+
if (!ExpressionUtil::tryCombineDataType(results, resultType) ||
|
|
37
|
+
!std::all_of(results.begin(), results.end(),
|
|
38
|
+
[&](const auto& result) { return canImplicitlyCast(*result, resultType); })) {
|
|
39
|
+
// No common type: take the last typed THEN, else the ELSE, and cast the rest to it.
|
|
40
|
+
resultType = LogicalType::ANY();
|
|
41
|
+
for (auto i = 0u; i < parsedCaseExpression.getNumCaseAlternative(); ++i) {
|
|
42
|
+
if (results[i]->getDataType().getLogicalTypeID() != LogicalTypeID::ANY) {
|
|
43
|
+
resultType = results[i]->getDataType().copy();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (resultType.getLogicalTypeID() == LogicalTypeID::ANY &&
|
|
47
|
+
parsedCaseExpression.hasElseExpression()) {
|
|
48
|
+
resultType = results.back()->getDataType().copy();
|
|
49
|
+
}
|
|
30
50
|
}
|
|
31
51
|
auto name = binder->getUniqueExpressionName(parsedExpression.getRawName());
|
|
32
52
|
// bind ELSE ...
|
|
@@ -99,16 +99,22 @@ std::vector<uint32_t> SubqueryGraph::getConnectedNodePos(const SubqueryGraph& nb
|
|
|
99
99
|
|
|
100
100
|
std::unordered_set<uint32_t> SubqueryGraph::getNodePositionsIgnoringNodeSelector() const {
|
|
101
101
|
std::unordered_set<uint32_t> result;
|
|
102
|
+
const auto nodes = getNodesSelectorWithRelEndpoints();
|
|
102
103
|
for (auto nodePos = 0u; nodePos < queryGraph.getNumQueryNodes(); ++nodePos) {
|
|
103
|
-
if (
|
|
104
|
+
if (nodes[nodePos]) {
|
|
104
105
|
result.insert(nodePos);
|
|
105
106
|
}
|
|
106
107
|
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
std::bitset<MAX_NUM_QUERY_VARIABLES> SubqueryGraph::getNodesSelectorWithRelEndpoints() const {
|
|
112
|
+
auto result = queryNodesSelector;
|
|
107
113
|
for (auto relPos = 0u; relPos < queryGraph.getNumQueryRels(); ++relPos) {
|
|
108
|
-
auto rel = queryGraph.getQueryRel(relPos);
|
|
109
114
|
if (queryRelsSelector[relPos]) {
|
|
110
|
-
|
|
111
|
-
result
|
|
115
|
+
auto rel = queryGraph.getQueryRel(relPos);
|
|
116
|
+
result[queryGraph.getQueryNodeIdx(rel->getSrcNodeName())] = true;
|
|
117
|
+
result[queryGraph.getQueryNodeIdx(rel->getDstNodeName())] = true;
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
return result;
|
|
@@ -7,7 +7,7 @@ char* lbug_get_version() {
|
|
|
7
7
|
LBUG_C_API_GUARD_BEGIN
|
|
8
8
|
auto version = lbug::main::Version::getVersion();
|
|
9
9
|
if (version == nullptr || version[0] == '\0') {
|
|
10
|
-
version = "0.
|
|
10
|
+
version = "0.22.3";
|
|
11
11
|
}
|
|
12
12
|
return convertToOwnedCString(version);
|
|
13
13
|
LBUG_C_API_GUARD_END(nullptr)
|
|
@@ -31,6 +31,8 @@ static void upgradeLegacyStorageFormat(const std::string& storage,
|
|
|
31
31
|
const auto lowerStorage = common::StringUtils::getLower(storage);
|
|
32
32
|
if (lowerStorage.ends_with("parquet")) {
|
|
33
33
|
storageFormat = common::StorageFormat::ICEBUG_DISK;
|
|
34
|
+
} else if (lowerStorage.ends_with(".lance")) {
|
|
35
|
+
storageFormat = common::StorageFormat::LANCE;
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -23,6 +23,8 @@ static void upgradeLegacyStorageFormat(const std::string& storage,
|
|
|
23
23
|
const auto lowerStorage = common::StringUtils::getLower(storage);
|
|
24
24
|
if (lowerStorage.ends_with("parquet")) {
|
|
25
25
|
storageFormat = common::StorageFormat::ICEBUG_DISK;
|
|
26
|
+
} else if (lowerStorage.ends_with(".lance")) {
|
|
27
|
+
storageFormat = common::StorageFormat::LANCE;
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -10,8 +10,28 @@ StorageFormat StorageFormatUtils::fromString(const std::string& str) {
|
|
|
10
10
|
if (str == "icebug-disk") {
|
|
11
11
|
return StorageFormat::ICEBUG_DISK;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
if (str == "lance") {
|
|
14
|
+
return StorageFormat::LANCE;
|
|
15
|
+
}
|
|
16
|
+
throw BinderException(std::format(
|
|
17
|
+
"Unsupported storage format '{}'. Valid options are: icebug-disk, lance.", str));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
std::string StorageFormatUtils::toString(StorageFormat format) {
|
|
21
|
+
switch (format) {
|
|
22
|
+
case StorageFormat::NONE: {
|
|
23
|
+
return "none";
|
|
24
|
+
}
|
|
25
|
+
case StorageFormat::ICEBUG_DISK: {
|
|
26
|
+
return "icebug-disk";
|
|
27
|
+
}
|
|
28
|
+
case StorageFormat::LANCE: {
|
|
29
|
+
return "lance";
|
|
30
|
+
}
|
|
31
|
+
default: {
|
|
32
|
+
return "unknown";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
} // namespace common
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#include "common/serializer/buffered_file.h"
|
|
2
2
|
|
|
3
|
+
#include <algorithm>
|
|
3
4
|
#include <cstring>
|
|
4
5
|
|
|
5
6
|
#include "common/assert.h"
|
|
@@ -78,20 +79,31 @@ void BufferedFileReader::read(uint8_t* data, uint64_t size) {
|
|
|
78
79
|
// Clear read buffer.
|
|
79
80
|
fileOffset -= bufferSize;
|
|
80
81
|
fileOffset += bufferOffset;
|
|
82
|
+
bufferOffset = 0;
|
|
83
|
+
bufferSize = 0;
|
|
84
|
+
// readFromFile silently short-reads at EOF, so check the range first and throw a
|
|
85
|
+
// catchable error instead of returning uninitialized bytes. A torn WAL tail (e.g. a
|
|
86
|
+
// crash or ENOSPC mid-commit) must surface as a replay failure, not a native crash.
|
|
87
|
+
if (fileOffset > fileSize || size > fileSize - fileOffset) {
|
|
88
|
+
throw RuntimeException(
|
|
89
|
+
std::format("Reading past the end of the file {} with size {} at offset {}",
|
|
90
|
+
fileInfo.path, fileSize, fileOffset));
|
|
91
|
+
}
|
|
81
92
|
fileInfo.readFromFile(data, size, fileOffset);
|
|
82
93
|
fileOffset += size;
|
|
83
|
-
bufferOffset = bufferSize;
|
|
84
|
-
} else if (bufferOffset + size <= bufferSize) {
|
|
85
|
-
memcpy(data, &buffer[bufferOffset], size);
|
|
86
|
-
bufferOffset += size;
|
|
87
94
|
} else {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
// Loop across page boundaries: the final page of a torn file may hold fewer bytes
|
|
96
|
+
// than requested, and copying the full remainder would overrun the heap buffer.
|
|
97
|
+
while (size > 0) {
|
|
98
|
+
if (bufferOffset >= bufferSize) {
|
|
99
|
+
readNextPage();
|
|
100
|
+
}
|
|
101
|
+
const auto toCopy = std::min(size, bufferSize - bufferOffset);
|
|
102
|
+
memcpy(data, &buffer[bufferOffset], toCopy);
|
|
103
|
+
bufferOffset += toCopy;
|
|
104
|
+
data += toCopy;
|
|
105
|
+
size -= toCopy;
|
|
106
|
+
}
|
|
95
107
|
}
|
|
96
108
|
}
|
|
97
109
|
|
|
@@ -643,7 +643,7 @@ void ListVector::copyListEntryAndBufferMetaData(ValueVector& vector,
|
|
|
643
643
|
for (auto i = 0u; i < otherSelVector.getSelSize(); ++i) {
|
|
644
644
|
auto pos = selVector[i];
|
|
645
645
|
auto otherPos = otherSelVector[i];
|
|
646
|
-
vector.setNull(pos, other.isNull(
|
|
646
|
+
vector.setNull(pos, other.isNull(otherPos));
|
|
647
647
|
if (!other.isNull(otherPos)) {
|
|
648
648
|
vector.setValue(pos, other.getValue<list_entry_t>(otherPos));
|
|
649
649
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#include "expression_evaluator/lambda_evaluator.h"
|
|
2
2
|
|
|
3
|
+
#include <cstring>
|
|
4
|
+
|
|
3
5
|
#include "binder/expression/lambda_expression.h"
|
|
4
6
|
#include "common/exception/runtime.h"
|
|
5
7
|
#include "expression_evaluator/expression_evaluator_visitor.h"
|
|
@@ -18,6 +20,141 @@ using namespace lbug::storage;
|
|
|
18
20
|
namespace lbug {
|
|
19
21
|
namespace evaluator {
|
|
20
22
|
|
|
23
|
+
// Points dst's lists at src's list data, so evaluators that keep a list's data vector when they
|
|
24
|
+
// are compiled, such as properties(), read the elements that gatherSlice references.
|
|
25
|
+
static void shareListData(const ValueVector& dst, const ValueVector& src) {
|
|
26
|
+
switch (src.dataType.getPhysicalType()) {
|
|
27
|
+
case PhysicalTypeID::STRUCT: {
|
|
28
|
+
const auto& dstFields = StructVector::getFieldVectors(&dst);
|
|
29
|
+
const auto& srcFields = StructVector::getFieldVectors(&src);
|
|
30
|
+
for (auto i = 0u; i < srcFields.size(); ++i) {
|
|
31
|
+
shareListData(*dstFields[i], *srcFields[i]);
|
|
32
|
+
}
|
|
33
|
+
} break;
|
|
34
|
+
case PhysicalTypeID::LIST:
|
|
35
|
+
case PhysicalTypeID::ARRAY: {
|
|
36
|
+
// Sharing the data vector also shares the structs and lists inside it.
|
|
37
|
+
ListVector::setDataVector(&dst, ListVector::getSharedDataVector(&src));
|
|
38
|
+
} break;
|
|
39
|
+
case PhysicalTypeID::ANY:
|
|
40
|
+
case PhysicalTypeID::BOOL:
|
|
41
|
+
case PhysicalTypeID::INT64:
|
|
42
|
+
case PhysicalTypeID::INT32:
|
|
43
|
+
case PhysicalTypeID::INT16:
|
|
44
|
+
case PhysicalTypeID::INT8:
|
|
45
|
+
case PhysicalTypeID::UINT64:
|
|
46
|
+
case PhysicalTypeID::UINT32:
|
|
47
|
+
case PhysicalTypeID::UINT16:
|
|
48
|
+
case PhysicalTypeID::UINT8:
|
|
49
|
+
case PhysicalTypeID::INT128:
|
|
50
|
+
case PhysicalTypeID::DOUBLE:
|
|
51
|
+
case PhysicalTypeID::FLOAT:
|
|
52
|
+
case PhysicalTypeID::INTERVAL:
|
|
53
|
+
case PhysicalTypeID::INTERNAL_ID:
|
|
54
|
+
case PhysicalTypeID::ALP_EXCEPTION_FLOAT:
|
|
55
|
+
case PhysicalTypeID::ALP_EXCEPTION_DOUBLE:
|
|
56
|
+
case PhysicalTypeID::UINT128:
|
|
57
|
+
case PhysicalTypeID::STRING:
|
|
58
|
+
case PhysicalTypeID::JSON:
|
|
59
|
+
case PhysicalTypeID::POINTER:
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static_assert(sizeof(bool) == sizeof(uint8_t), "BOOL values are gathered as uint8_t");
|
|
65
|
+
static_assert(sizeof(float) == sizeof(uint32_t), "FLOAT values are gathered as uint32_t");
|
|
66
|
+
static_assert(sizeof(double) == sizeof(uint64_t), "DOUBLE values are gathered as uint64_t");
|
|
67
|
+
static_assert(sizeof(uint128_t) == sizeof(int128_t), "UINT128 values are gathered as int128_t");
|
|
68
|
+
|
|
69
|
+
template<typename T>
|
|
70
|
+
static void gatherValues(ValueVector& dst, const ValueVector& src, const SelectionVector& srcSel) {
|
|
71
|
+
DASSERT(src.getNumBytesPerValue() == sizeof(T));
|
|
72
|
+
auto* dstValues = reinterpret_cast<T*>(dst.getData());
|
|
73
|
+
const auto* srcValues = reinterpret_cast<const T*>(src.getData());
|
|
74
|
+
for (sel_t i = 0; i < srcSel.getSelSize(); ++i) {
|
|
75
|
+
dstValues[i] = srcValues[srcSel[i]];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static void gatherBytes(ValueVector& dst, const ValueVector& src, const SelectionVector& srcSel) {
|
|
80
|
+
const auto numBytes = src.getNumBytesPerValue();
|
|
81
|
+
for (sel_t i = 0; i < srcSel.getSelSize(); ++i) {
|
|
82
|
+
memcpy(dst.getData() + i * numBytes, src.getData() + srcSel[i] * numBytes, numBytes);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Copies the values of src at srcSel's positions to positions 0 to srcSel.getSelSize() - 1 of dst.
|
|
87
|
+
// Strings and the elements of lists are referenced rather than copied, which holds because src is
|
|
88
|
+
// not modified while the lambda evaluates the slice.
|
|
89
|
+
static void gatherSlice(ValueVector& dst, const SelectionVector& dstSel, const ValueVector& src,
|
|
90
|
+
const SelectionVector& srcSel) {
|
|
91
|
+
if (src.hasNoNullsGuarantee()) {
|
|
92
|
+
dst.setAllNonNull();
|
|
93
|
+
} else {
|
|
94
|
+
for (sel_t i = 0; i < srcSel.getSelSize(); ++i) {
|
|
95
|
+
dst.setNull(i, src.isNull(srcSel[i]));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
switch (src.dataType.getPhysicalType()) {
|
|
99
|
+
case PhysicalTypeID::STRUCT: {
|
|
100
|
+
const auto& dstFields = StructVector::getFieldVectors(&dst);
|
|
101
|
+
const auto& srcFields = StructVector::getFieldVectors(&src);
|
|
102
|
+
for (auto i = 0u; i < srcFields.size(); ++i) {
|
|
103
|
+
gatherSlice(*dstFields[i], dstSel, *srcFields[i], srcSel);
|
|
104
|
+
}
|
|
105
|
+
} break;
|
|
106
|
+
case PhysicalTypeID::LIST:
|
|
107
|
+
case PhysicalTypeID::ARRAY: {
|
|
108
|
+
// Evaluators compiled against dst's data vector at init would read stale elements.
|
|
109
|
+
if (ListVector::getDataVector(&dst) != ListVector::getDataVector(&src)) {
|
|
110
|
+
throw RuntimeException("A list lambda's input replaced its data vector after init.");
|
|
111
|
+
}
|
|
112
|
+
ListVector::copyListEntryAndBufferMetaData(dst, dstSel, src, srcSel);
|
|
113
|
+
} break;
|
|
114
|
+
case PhysicalTypeID::BOOL:
|
|
115
|
+
case PhysicalTypeID::INT8:
|
|
116
|
+
case PhysicalTypeID::UINT8: {
|
|
117
|
+
gatherValues<uint8_t>(dst, src, srcSel);
|
|
118
|
+
} break;
|
|
119
|
+
case PhysicalTypeID::INT16:
|
|
120
|
+
case PhysicalTypeID::UINT16: {
|
|
121
|
+
gatherValues<uint16_t>(dst, src, srcSel);
|
|
122
|
+
} break;
|
|
123
|
+
case PhysicalTypeID::INT32:
|
|
124
|
+
case PhysicalTypeID::UINT32:
|
|
125
|
+
case PhysicalTypeID::FLOAT: {
|
|
126
|
+
gatherValues<uint32_t>(dst, src, srcSel);
|
|
127
|
+
} break;
|
|
128
|
+
case PhysicalTypeID::INT64:
|
|
129
|
+
case PhysicalTypeID::UINT64:
|
|
130
|
+
case PhysicalTypeID::DOUBLE: {
|
|
131
|
+
gatherValues<uint64_t>(dst, src, srcSel);
|
|
132
|
+
} break;
|
|
133
|
+
case PhysicalTypeID::INT128:
|
|
134
|
+
case PhysicalTypeID::UINT128: {
|
|
135
|
+
gatherValues<int128_t>(dst, src, srcSel);
|
|
136
|
+
} break;
|
|
137
|
+
case PhysicalTypeID::INTERVAL: {
|
|
138
|
+
gatherValues<interval_t>(dst, src, srcSel);
|
|
139
|
+
} break;
|
|
140
|
+
case PhysicalTypeID::INTERNAL_ID: {
|
|
141
|
+
gatherValues<internalID_t>(dst, src, srcSel);
|
|
142
|
+
} break;
|
|
143
|
+
case PhysicalTypeID::STRING:
|
|
144
|
+
case PhysicalTypeID::JSON: {
|
|
145
|
+
gatherValues<string_t>(dst, src, srcSel);
|
|
146
|
+
} break;
|
|
147
|
+
// Not list element types: ANY and POINTER vectors are never built, and ALP exceptions exist
|
|
148
|
+
// only in storage.
|
|
149
|
+
case PhysicalTypeID::ANY:
|
|
150
|
+
case PhysicalTypeID::POINTER:
|
|
151
|
+
case PhysicalTypeID::ALP_EXCEPTION_FLOAT:
|
|
152
|
+
case PhysicalTypeID::ALP_EXCEPTION_DOUBLE: {
|
|
153
|
+
gatherBytes(dst, src, srcSel);
|
|
154
|
+
} break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
21
158
|
void ListLambdaEvaluator::init(const ResultSet& resultSet, ClientContext* clientContext) {
|
|
22
159
|
for (auto& child : children) {
|
|
23
160
|
child->init(resultSet, clientContext);
|
|
@@ -30,20 +167,23 @@ void ListLambdaEvaluator::init(const ResultSet& resultSet, ClientContext* client
|
|
|
30
167
|
auto evaluators = collector.getEvaluators();
|
|
31
168
|
auto lambdaVarState = std::make_shared<DataChunkState>();
|
|
32
169
|
memoryManager = MemoryManager::Get(*clientContext);
|
|
170
|
+
const auto& elementType = ListType::getChildType(listInputVector->dataType);
|
|
171
|
+
if (listLambdaType != ListLambdaType::LIST_REDUCE && !evaluators.empty()) {
|
|
172
|
+
sliceParamVector = std::make_shared<ValueVector>(elementType.copy(), memoryManager);
|
|
173
|
+
}
|
|
33
174
|
for (auto& evaluator : evaluators) {
|
|
34
|
-
//
|
|
35
|
-
// The
|
|
36
|
-
// For list_reduce:
|
|
37
|
-
// We should create two vectors for each lambda variable resultVector since we are going to
|
|
38
|
-
// update the list elements during execution.
|
|
175
|
+
// list_reduce updates each lambda variable separately during execution, so each gets its
|
|
176
|
+
// own vector. The other lambdas read every use of their variable from sliceParamVector.
|
|
39
177
|
evaluator->resultVector =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
std::make_shared<ValueVector>(
|
|
43
|
-
ListType::getChildType(listInputVector->dataType).copy(), memoryManager);
|
|
178
|
+
sliceParamVector != nullptr ?
|
|
179
|
+
sliceParamVector :
|
|
180
|
+
std::make_shared<ValueVector>(elementType.copy(), memoryManager);
|
|
44
181
|
evaluator->resultVector->state = lambdaVarState;
|
|
45
182
|
lambdaParamEvaluators.push_back(evaluator->ptrCast<LambdaParamEvaluator>());
|
|
46
183
|
}
|
|
184
|
+
if (sliceParamVector != nullptr) {
|
|
185
|
+
shareListData(*sliceParamVector, *ListVector::getDataVector(listInputVector));
|
|
186
|
+
}
|
|
47
187
|
lambdaRootEvaluator->init(resultSet, clientContext);
|
|
48
188
|
resolveResultVector(resultSet, memoryManager);
|
|
49
189
|
params.push_back(children[0]->resultVector);
|
|
@@ -63,10 +203,24 @@ void ListLambdaEvaluator::evaluateInternal() {
|
|
|
63
203
|
auto selVectors = SelectionVector::fromValueVectors(params);
|
|
64
204
|
do {
|
|
65
205
|
sliceInfo.nextSlice();
|
|
206
|
+
if (sliceParamVector != nullptr) {
|
|
207
|
+
copySliceToParamVector(sliceInfo, *inputVector);
|
|
208
|
+
}
|
|
66
209
|
execFunc(params, selVectors, *resultVector, resultVector->getSelVectorPtr(), &bindData);
|
|
67
210
|
} while (!sliceInfo.done());
|
|
68
211
|
}
|
|
69
212
|
|
|
213
|
+
// Many evaluators read an operand at the position they write its result to. The lambda variable
|
|
214
|
+
// therefore holds the slice's elements at positions 0 to sliceSize - 1, where the vectors computed
|
|
215
|
+
// from it hold their values, instead of selecting them at their offsets in the list data vector.
|
|
216
|
+
void ListLambdaEvaluator::copySliceToParamVector(const ListSliceInfo& sliceInfo,
|
|
217
|
+
const ValueVector& listVector) const {
|
|
218
|
+
auto& paramSelVector = sliceParamVector->state->getSelVectorUnsafe();
|
|
219
|
+
paramSelVector.setToUnfiltered(sliceInfo.getSliceSize());
|
|
220
|
+
gatherSlice(*sliceParamVector, paramSelVector, *ListVector::getDataVector(&listVector),
|
|
221
|
+
sliceInfo.getDataSelVector());
|
|
222
|
+
}
|
|
223
|
+
|
|
70
224
|
void ListLambdaEvaluator::evaluate() {
|
|
71
225
|
DASSERT(children.size() == 1);
|
|
72
226
|
children[0]->evaluate();
|