ladybug 0.22.0.dev20261022__tar.gz → 0.22.0.dev20261024__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.
Files changed (67) hide show
  1. sdist/PKG-INFO +1 -1
  2. sdist/ladybug-source/CMakeLists.txt +1 -1
  3. sdist/ladybug-source/src/binder/ddl/property_definition.cpp +10 -0
  4. sdist/ladybug-source/src/c_api/value.cpp +87 -15
  5. sdist/ladybug-source/src/catalog/catalog.cpp +6 -4
  6. sdist/ladybug-source/src/catalog/property_definition_collection.cpp +5 -1
  7. sdist/ladybug-source/src/common/vector/value_vector.cpp +6 -0
  8. sdist/ladybug-source/src/function/table/cache_column.cpp +3 -1
  9. sdist/ladybug-source/src/include/binder/ddl/property_definition.h +1 -0
  10. sdist/ladybug-source/src/include/catalog/catalog.h +4 -1
  11. sdist/ladybug-source/src/include/common/constants.h +2 -2
  12. sdist/ladybug-source/src/include/optimizer/remove_unnecessary_join_optimizer.h +2 -1
  13. sdist/ladybug-source/src/include/planner/join_order/cost_model.h +2 -3
  14. sdist/ladybug-source/src/include/planner/operator/logical_hash_join.h +1 -1
  15. sdist/ladybug-source/src/include/processor/operator/ddl/drop.h +3 -1
  16. sdist/ladybug-source/src/include/processor/operator/persistent/reader/parquet/parquet_reader.h +5 -0
  17. sdist/ladybug-source/src/include/processor/result/factorized_table.h +3 -0
  18. sdist/ladybug-source/src/include/storage/table/csr_chunked_node_group.h +14 -0
  19. sdist/ladybug-source/src/include/storage/table/csr_node_group.h +12 -4
  20. sdist/ladybug-source/src/include/storage/table/node_group.h +8 -2
  21. sdist/ladybug-source/src/include/storage/table/node_table.h +2 -3
  22. sdist/ladybug-source/src/include/storage/table/rel_table_data.h +17 -3
  23. sdist/ladybug-source/src/include/storage/table/version_info.h +3 -1
  24. sdist/ladybug-source/src/optimizer/remove_unnecessary_join_optimizer.cpp +83 -2
  25. sdist/ladybug-source/src/parser/transform/transform_expression.cpp +4 -4
  26. sdist/ladybug-source/src/planner/join_order/cost_model.cpp +2 -3
  27. sdist/ladybug-source/src/planner/plan/plan_join_order.cpp +14 -7
  28. sdist/ladybug-source/src/processor/operator/ddl/drop.cpp +6 -3
  29. sdist/ladybug-source/src/processor/operator/persistent/node_batch_insert.cpp +59 -0
  30. sdist/ladybug-source/src/processor/operator/persistent/reader/parquet/column_reader.cpp +1 -1
  31. sdist/ladybug-source/src/processor/operator/persistent/reader/parquet/list_column_reader.cpp +4 -1
  32. sdist/ladybug-source/src/processor/operator/persistent/reader/parquet/parquet_reader.cpp +4 -0
  33. sdist/ladybug-source/src/processor/operator/persistent/rel_batch_insert.cpp +17 -5
  34. sdist/ladybug-source/src/processor/result/factorized_table.cpp +7 -2
  35. sdist/ladybug-source/src/storage/predicate/column_predicate.cpp +16 -3
  36. sdist/ladybug-source/src/storage/table/chunked_node_group.cpp +19 -9
  37. sdist/ladybug-source/src/storage/table/column_chunk.cpp +2 -2
  38. sdist/ladybug-source/src/storage/table/csr_chunked_node_group.cpp +23 -4
  39. sdist/ladybug-source/src/storage/table/csr_node_group.cpp +297 -112
  40. sdist/ladybug-source/src/storage/table/node_group.cpp +10 -1
  41. sdist/ladybug-source/src/storage/table/node_table.cpp +7 -2
  42. sdist/ladybug-source/src/storage/table/rel_table.cpp +19 -9
  43. sdist/ladybug-source/src/storage/table/rel_table_data.cpp +17 -13
  44. sdist/ladybug-source/src/storage/table/table.cpp +0 -2
  45. sdist/ladybug-source/src/storage/table/version_info.cpp +8 -4
  46. sdist/ladybug-source/tools/python_api/src_cpp/include/cached_import/py_cached_import.h +2 -0
  47. sdist/ladybug-source/tools/python_api/src_cpp/include/cached_import/py_cached_modules.h +18 -0
  48. sdist/ladybug-source/tools/python_api/src_cpp/py_connection.cpp +146 -5
  49. sdist/ladybug-source/tools/python_api/src_py/_backend.py +26 -2
  50. sdist/ladybug-source/tools/python_api/src_py/_lbug_capi.py +3 -6
  51. sdist/ladybug-source/tools/python_api/src_py/connection.py +63 -7
  52. sdist/ladybug-source/tools/python_api/test/capi_xfails.py +7 -0
  53. sdist/ladybug-source/tools/python_api/test/test_backend_errors.py +34 -0
  54. sdist/ladybug-source/tools/python_api/test/test_connection_close.py +22 -2
  55. sdist/ladybug-source/tools/python_api/test/test_keyboard_interrupt.py +125 -0
  56. sdist/ladybug-source/tools/python_api/test/test_timeout.py +91 -0
  57. sdist/ladybug.egg-info/PKG-INFO +1 -1
  58. sdist/ladybug.egg-info/SOURCES.txt +2 -0
  59. sdist/pyproject.toml +1 -1
  60. sdist/src_py/_backend.py +26 -2
  61. sdist/src_py/_lbug_capi.py +3 -6
  62. sdist/src_py/connection.py +63 -7
  63. sdist/test/capi_xfails.py +7 -0
  64. sdist/test/test_backend_errors.py +34 -0
  65. sdist/test/test_connection_close.py +22 -2
  66. sdist/test/test_keyboard_interrupt.py +125 -0
  67. sdist/test/test_timeout.py +91 -0
sdist/PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ladybug
3
- Version: 0.22.0.dev20261022
3
+ Version: 0.22.0.dev20261024
4
4
  Summary: Highly scalable, extremely fast, easy-to-use embeddable graph database
5
5
  Home-page: https://github.com/LadybugDB/ladybug
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  cmake_minimum_required(VERSION 3.15)
2
2
 
3
- project(Lbug VERSION 0.22.0.20261022 LANGUAGES CXX C)
3
+ project(Lbug VERSION 0.22.0.20261024 LANGUAGES CXX C)
4
4
 
5
5
  option(SINGLE_THREADED "Single-threaded mode" FALSE)
6
6
  if(SINGLE_THREADED)
@@ -15,6 +15,16 @@ PropertyDefinition::PropertyDefinition(ColumnDefinition columnDefinition)
15
15
  defaultExpr = std::make_unique<ParsedLiteralExpression>(Value::createNullValue(), "NULL");
16
16
  }
17
17
 
18
+ bool PropertyDefinition::hasDefault() const {
19
+ if (defaultExpr == nullptr) {
20
+ return false;
21
+ }
22
+ if (defaultExpr->getExpressionType() == common::ExpressionType::LITERAL) {
23
+ return !defaultExpr->constCast<parser::ParsedLiteralExpression>().getValue().isNull();
24
+ }
25
+ return !defaultExpr->getRawName().empty();
26
+ }
27
+
18
28
  void PropertyDefinition::serialize(Serializer& serializer) const {
19
29
  serializer.serializeValue(columnDefinition.name);
20
30
  columnDefinition.type.serialize(serializer);
@@ -3,6 +3,7 @@
3
3
  #include "c_api/helpers.h"
4
4
  #include "c_api/lbug.h"
5
5
  #include "common/constants.h"
6
+ #include "common/type_utils.h"
6
7
  #include "common/types/types.h"
7
8
  #include "common/types/value/nested.h"
8
9
  #include "common/types/value/node.h"
@@ -40,7 +41,7 @@ static bool canCopyValueForNested(const Value& value, const LogicalType& targetT
40
41
  if (value.getDataType() == targetType) {
41
42
  return true;
42
43
  }
43
- if (value.isNull() && value.getDataType().getLogicalTypeID() == LogicalTypeID::ANY) {
44
+ if (value.isNull()) {
44
45
  return true;
45
46
  }
46
47
  if (isEmptyNestedWithAny(value)) {
@@ -51,8 +52,45 @@ static bool canCopyValueForNested(const Value& value, const LogicalType& targetT
51
52
  targetType.getLogicalTypeID() == LogicalTypeID::FLOAT) {
52
53
  return true;
53
54
  }
54
- return value.getDataType().getLogicalTypeID() == targetType.getLogicalTypeID() &&
55
- value.getDataType().containsAny();
55
+ if (LogicalTypeUtils::isIntegral(value.getDataType()) &&
56
+ LogicalTypeUtils::isIntegral(targetType)) {
57
+ return true;
58
+ }
59
+ if (value.getDataType().getLogicalTypeID() != targetType.getLogicalTypeID()) {
60
+ return false;
61
+ }
62
+ const auto* nested = &value;
63
+ if (targetType.getLogicalTypeID() == LogicalTypeID::LIST) {
64
+ const auto& childType = ListType::getChildType(targetType);
65
+ for (auto i = 0u; i < NestedVal::getChildrenSize(nested); ++i) {
66
+ if (!canCopyValueForNested(*NestedVal::getChildVal(nested, i), childType)) {
67
+ return false;
68
+ }
69
+ }
70
+ return true;
71
+ }
72
+ if (targetType.getLogicalTypeID() == LogicalTypeID::MAP) {
73
+ const auto& keyType = MapType::getKeyType(targetType);
74
+ const auto& valueType = MapType::getValueType(targetType);
75
+ for (auto i = 0u; i < NestedVal::getChildrenSize(nested); ++i) {
76
+ auto* mapEntry = NestedVal::getChildVal(nested, i);
77
+ if (!canCopyValueForNested(*NestedVal::getChildVal(mapEntry, 0), keyType) ||
78
+ !canCopyValueForNested(*NestedVal::getChildVal(mapEntry, 1), valueType)) {
79
+ return false;
80
+ }
81
+ }
82
+ return true;
83
+ }
84
+ if (targetType.getLogicalTypeID() == LogicalTypeID::STRUCT) {
85
+ for (auto i = 0u; i < NestedVal::getChildrenSize(nested); ++i) {
86
+ if (!canCopyValueForNested(*NestedVal::getChildVal(nested, i),
87
+ StructType::getFieldType(targetType, static_cast<struct_field_idx_t>(i)))) {
88
+ return false;
89
+ }
90
+ }
91
+ return true;
92
+ }
93
+ return value.getDataType().containsAny();
56
94
  }
57
95
 
58
96
  static std::string incompatibleParameterTypeMessage(const LogicalType& left,
@@ -69,28 +107,54 @@ static lbug_value* createCAPIValue(std::unique_ptr<Value> value) {
69
107
  return c_value;
70
108
  }
71
109
 
110
+ // The target comes from LogicalTypeUtils::tryGetMaxLogicalType, which joins two integer types only
111
+ // into one that holds both: implicit integer casts only widen, and a signed/unsigned pair becomes a
112
+ // wider signed type. So the cast below is exact. CreateListAllIntegerPairs pins this.
113
+ static std::unique_ptr<Value> castIntegerForNested(const Value& value,
114
+ const LogicalType& targetType) {
115
+ // A null of the target type keeps its exact type: SERIAL's default value is an INT64.
116
+ auto result = std::make_unique<Value>(Value::createNullValue(targetType));
117
+ result->setNull(false);
118
+ TypeUtils::visit(
119
+ value.getDataType(),
120
+ [&]<IntegerTypes SRC>(SRC) {
121
+ TypeUtils::visit(
122
+ targetType,
123
+ [&]<IntegerTypes DST>(DST) {
124
+ auto castValue = static_cast<DST>(value.getValue<SRC>());
125
+ result->copyFromRowLayout(reinterpret_cast<const uint8_t*>(&castValue));
126
+ },
127
+ [](auto) { UNREACHABLE_CODE; });
128
+ },
129
+ [](auto) { UNREACHABLE_CODE; });
130
+ return result;
131
+ }
132
+
72
133
  static std::unique_ptr<Value> copyValueForNested(const Value& value,
73
134
  const LogicalType& targetType) {
74
- if (value.isNull() && value.getDataType().getLogicalTypeID() == LogicalTypeID::ANY) {
135
+ if (value.getDataType() == targetType) {
136
+ return value.copy();
137
+ }
138
+ if (value.isNull()) {
75
139
  return std::make_unique<Value>(Value::createNullValue(targetType));
76
140
  }
77
- if (value.getDataType() != targetType && isEmptyNestedWithAny(value)) {
141
+ if (isEmptyNestedWithAny(value)) {
78
142
  return std::make_unique<Value>(Value::createDefaultValue(targetType));
79
143
  }
80
- if (value.getDataType() != targetType &&
81
- targetType.getLogicalTypeID() == LogicalTypeID::STRING) {
144
+ if (targetType.getLogicalTypeID() == LogicalTypeID::STRING) {
82
145
  return std::make_unique<Value>(Value::createValue<std::string>(value.toString()));
83
146
  }
84
- if (value.getDataType() != targetType &&
85
- targetType.getLogicalTypeID() == LogicalTypeID::DOUBLE) {
147
+ if (targetType.getLogicalTypeID() == LogicalTypeID::DOUBLE) {
86
148
  return std::make_unique<Value>(std::stod(value.toString()));
87
149
  }
88
- if (value.getDataType() != targetType &&
89
- targetType.getLogicalTypeID() == LogicalTypeID::FLOAT) {
150
+ if (targetType.getLogicalTypeID() == LogicalTypeID::FLOAT) {
90
151
  return std::make_unique<Value>(static_cast<float>(std::stof(value.toString())));
91
152
  }
92
- if (value.getDataType() != targetType &&
93
- value.getDataType().getLogicalTypeID() == LogicalTypeID::LIST &&
153
+ if (LogicalTypeUtils::isIntegral(value.getDataType()) &&
154
+ LogicalTypeUtils::isIntegral(targetType)) {
155
+ return castIntegerForNested(value, targetType);
156
+ }
157
+ if (value.getDataType().getLogicalTypeID() == LogicalTypeID::LIST &&
94
158
  targetType.getLogicalTypeID() == LogicalTypeID::LIST) {
95
159
  std::vector<std::unique_ptr<Value>> children;
96
160
  const auto& childType = ListType::getChildType(targetType);
@@ -100,8 +164,7 @@ static std::unique_ptr<Value> copyValueForNested(const Value& value,
100
164
  }
101
165
  return std::make_unique<Value>(targetType.copy(), std::move(children));
102
166
  }
103
- if (value.getDataType() != targetType &&
104
- value.getDataType().getLogicalTypeID() == LogicalTypeID::MAP &&
167
+ if (value.getDataType().getLogicalTypeID() == LogicalTypeID::MAP &&
105
168
  targetType.getLogicalTypeID() == LogicalTypeID::MAP) {
106
169
  std::vector<std::unique_ptr<Value>> children;
107
170
  const auto& keyType = MapType::getKeyType(targetType);
@@ -121,6 +184,15 @@ static std::unique_ptr<Value> copyValueForNested(const Value& value,
121
184
  }
122
185
  return std::make_unique<Value>(targetType.copy(), std::move(children));
123
186
  }
187
+ if (value.getDataType().getLogicalTypeID() == LogicalTypeID::STRUCT &&
188
+ targetType.getLogicalTypeID() == LogicalTypeID::STRUCT) {
189
+ std::vector<std::unique_ptr<Value>> children;
190
+ for (auto i = 0u; i < NestedVal::getChildrenSize(&value); ++i) {
191
+ children.push_back(copyValueForNested(*NestedVal::getChildVal(&value, i),
192
+ StructType::getFieldType(targetType, static_cast<struct_field_idx_t>(i))));
193
+ }
194
+ return std::make_unique<Value>(targetType.copy(), std::move(children));
195
+ }
124
196
  return value.copy();
125
197
  }
126
198
 
@@ -15,6 +15,7 @@
15
15
  #include "common/partition_routing_hook.h"
16
16
  #include "common/serializer/deserializer.h"
17
17
  #include "common/serializer/serializer.h"
18
+ #include "common/string_utils.h"
18
19
  #include "extension/extension_manager.h"
19
20
  #include "function/function_collection.h"
20
21
  #include "main/client_context.h"
@@ -587,7 +588,7 @@ std::vector<FunctionCatalogEntry*> Catalog::getFunctionEntries(
587
588
  }
588
589
 
589
590
  bool Catalog::containsMacro(const Transaction* transaction, const std::string& macroName) const {
590
- return macros->containsEntry(transaction, macroName);
591
+ return macros->containsEntry(transaction, StringUtils::getUpper(macroName));
591
592
  }
592
593
 
593
594
  function::ScalarMacroFunction* Catalog::getScalarMacroFunction(const Transaction* transaction,
@@ -627,11 +628,12 @@ std::vector<std::string> Catalog::getMacroNames(const Transaction* transaction)
627
628
  }
628
629
 
629
630
  void Catalog::dropMacro(Transaction* transaction, std::string& name) {
630
- if (!containsMacro(transaction, name)) {
631
+ auto macroName = StringUtils::getUpper(name);
632
+ if (!macros->containsEntry(transaction, macroName)) {
631
633
  throw CatalogException{std::format("Macro {} doesn't exist.", name)};
632
634
  }
633
- auto entry = getFunctionEntry(transaction, name);
634
- macros->dropEntry(transaction, name, entry->getOID());
635
+ auto entry = macros->getEntry(transaction, macroName);
636
+ macros->dropEntry(transaction, macroName, entry->getOID());
635
637
  }
636
638
 
637
639
  void Catalog::registerBuiltInFunctions() {
@@ -134,7 +134,11 @@ std::string PropertyDefinitionCollection::toCypher() const {
134
134
  if (typeStr.find("MAP") != std::string::npos) {
135
135
  StringUtils::replaceAll(typeStr, " ", ",");
136
136
  }
137
- ss << common::StringUtils::quoteIdentifier(def.getName()) << " " << typeStr << ",";
137
+ ss << common::StringUtils::quoteIdentifier(def.getName()) << " " << typeStr;
138
+ if (dataType.getLogicalTypeID() != LogicalTypeID::SERIAL && def.hasDefault()) {
139
+ ss << " DEFAULT " << def.getDefaultExpressionName();
140
+ }
141
+ ss << ",";
138
142
  }
139
143
  return ss.str();
140
144
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  #include <numeric>
4
4
 
5
+ #include "common/assert.h"
5
6
  #include "common/exception/runtime.h"
6
7
  #include "common/null_buffer.h"
7
8
  #include "common/serializer/deserializer.h"
@@ -26,6 +27,11 @@ ValueVector::ValueVector(LogicalType dataType, storage::MemoryManager* memoryMan
26
27
  "Data type is expected to be resolved during binding.");
27
28
  // LCOV_EXCL_STOP
28
29
  }
30
+ // Strings too long to inline live in overflow blocks from the memory manager; nested types
31
+ // pass it to their child vectors, so the check covers strings at any depth.
32
+ DASSERT(
33
+ memoryManager != nullptr || (this->dataType.getPhysicalType() != PhysicalTypeID::STRING &&
34
+ this->dataType.getPhysicalType() != PhysicalTypeID::JSON));
29
35
  numBytesPerValue = getDataTypeSize(this->dataType);
30
36
  initializeValueBuffer();
31
37
  auxiliaryBuffer = AuxiliaryBufferFactory::getAuxiliaryBuffer(this->dataType, memoryManager);
@@ -6,6 +6,7 @@
6
6
  #include "function/table/bind_input.h"
7
7
  #include "function/table/simple_table_function.h"
8
8
  #include "processor/execution_context.h"
9
+ #include "storage/buffer_manager/memory_manager.h"
9
10
  #include "storage/local_cached_column.h"
10
11
  #include "storage/partition_storage_registry.h"
11
12
  #include "storage/storage_manager.h"
@@ -98,7 +99,8 @@ struct CacheArrayColumnLocalState final : TableFuncLocalState {
98
99
  ->cast<storage::NodeTable>();
99
100
  dataChunk.insert(0, std::make_shared<ValueVector>(LogicalType::INTERNAL_ID()));
100
101
  dataChunk.insert(1,
101
- std::make_shared<ValueVector>(table.getColumn(columnID).getDataType().copy()));
102
+ std::make_shared<ValueVector>(table.getColumn(columnID).getDataType().copy(),
103
+ storage::MemoryManager::Get(context)));
102
104
  std::vector<column_id_t> columnIDs;
103
105
  columnIDs.push_back(columnID);
104
106
  scanState =
@@ -33,6 +33,7 @@ struct LBUG_API PropertyDefinition {
33
33
  std::string getName() const { return columnDefinition.name; }
34
34
  const common::LogicalType& getType() const { return columnDefinition.type; }
35
35
  std::string getDefaultExpressionName() const { return defaultExpr->getRawName(); }
36
+ bool hasDefault() const;
36
37
  void rename(const std::string& newName) { columnDefinition.name = newName; }
37
38
 
38
39
  void serialize(common::Serializer& serializer) const;
@@ -190,9 +190,11 @@ public:
190
190
 
191
191
  // ----------------------------- Macro ----------------------------
192
192
 
193
- // Check if macro entry exists.
193
+ // Macro names are stored uppercased (see bindCreateMacro). containsMacro and dropMacro
194
+ // uppercase the given name the same way; the catalog set itself only folds ASCII letters.
194
195
  bool containsMacro(const transaction::Transaction* transaction,
195
196
  const std::string& macroName) const;
197
+ // Takes the stored (uppercased) name.
196
198
  void addScalarMacroFunction(transaction::Transaction* transaction, std::string name,
197
199
  std::unique_ptr<function::ScalarMacroFunction> macro);
198
200
  ScalarMacroCatalogEntry* getScalarMacroCatalogEntry(const transaction::Transaction* transaction,
@@ -200,6 +202,7 @@ public:
200
202
  void dropMacroEntry(transaction::Transaction* transaction, const lbug::common::oid_t macroID);
201
203
  void dropMacroEntry(transaction::Transaction* transaction,
202
204
  const ScalarMacroCatalogEntry* entry);
205
+ // Takes the stored (uppercased) name.
203
206
  function::ScalarMacroFunction* getScalarMacroFunction(
204
207
  const transaction::Transaction* transaction, const std::string& name) const;
205
208
  std::vector<std::string> getMacroNames(const transaction::Transaction* transaction) const;
@@ -178,8 +178,8 @@ struct PlannerKnobs {
178
178
  // cost vs per-output-pair cost is ~1000:1, so seed cardinality dominates direction choice.
179
179
  static constexpr uint64_t RECURSIVE_EXTEND_SOURCE_COST = 1000;
180
180
  // Lookup join (per-tuple PK lookup instead of scan + hash join) pays off only when the
181
- // outer side is small: each outer row costs one index probe/payload fetch, so cap the
182
- // estimated outer cardinality this rewrite applies to (Q1's Person fetch: ~9 rows).
181
+ // outer side is small: each flat outer row costs one index probe/payload fetch, so cap the
182
+ // estimated number of flat outer rows this rewrite applies to (Q1's Person fetch: ~9 rows).
183
183
  static constexpr uint64_t LOOKUP_JOIN_MAX_OUTER_CARDINALITY = 1024;
184
184
  // Lookup join eligibility additionally requires the outer plan to be free of
185
185
  // unmodeled fan-out (no recursion/aggregation/etc.) and caps the extend chain
@@ -15,7 +15,8 @@ namespace optimizer {
15
15
  * E(e) S(b)
16
16
  * |
17
17
  * S(a)
18
- * This optimizer prunes such redundant joins.
18
+ * This optimizer prunes such redundant joins. A join whose key an OPTIONAL MATCH can leave
19
+ * NULL is not redundant: it drops the rows with a NULL key, so it stays.
19
20
  */
20
21
  class RemoveUnnecessaryJoinOptimizer : public LogicalOperatorVisitor {
21
22
  public:
@@ -22,11 +22,10 @@ public:
22
22
  const LogicalPlan& probe, const LogicalPlan& build);
23
23
  static uint64_t computeIntersectCost(const LogicalPlan& probePlan,
24
24
  const std::vector<LogicalPlan>& buildPlans);
25
- // Lookup join: one PK probe/payload fetch per outer row on top of the outer plan.
25
+ // Lookup join: one PK probe/payload fetch per flat outer row on top of the outer plan.
26
26
  // Like the INL-join precedent, this only pays off for small outer cardinalities,
27
27
  // which callers enforce via PlannerKnobs::LOOKUP_JOIN_MAX_OUTER_CARDINALITY.
28
- static uint64_t computeLookupJoinCost(const LogicalPlan& outerPlan,
29
- cardinality_t outerCardinality);
28
+ static uint64_t computeLookupJoinCost(const LogicalPlan& outerPlan, cardinality_t numProbes);
30
29
  };
31
30
 
32
31
  } // namespace planner
@@ -37,7 +37,7 @@ public:
37
37
  static binder::expression_vector getJoinNodeIDs(
38
38
  const std::vector<join_condition_t>& joinConditions);
39
39
 
40
- std::vector<join_condition_t> getJoinConditions() const { return joinConditions; }
40
+ const std::vector<join_condition_t>& getJoinConditions() const { return joinConditions; }
41
41
  common::JoinType getJoinType() const { return joinType; }
42
42
  bool hasMark() const { return mark != nullptr; }
43
43
  std::shared_ptr<binder::Expression> getMark() const { return mark; }
@@ -42,7 +42,9 @@ private:
42
42
  void dropMacro(const main::ClientContext* context);
43
43
  void dropGraph(const main::ClientContext* context);
44
44
  void dropIndex(const main::ClientContext* context);
45
- void handleMacroExistence(const main::ClientContext* context);
45
+ // Returns false if the macro is missing and IF EXISTS was given; throws if it is missing
46
+ // otherwise.
47
+ bool handleMacroExistence(const main::ClientContext* context);
46
48
  void dropRelGroup(const main::ClientContext* context);
47
49
 
48
50
  private:
@@ -11,6 +11,10 @@
11
11
  #include "resizable_buffer.h"
12
12
 
13
13
  namespace lbug {
14
+ namespace storage {
15
+ class MemoryManager;
16
+ } // namespace storage
17
+
14
18
  namespace processor {
15
19
 
16
20
  struct ParquetReaderPrefetchConfig {
@@ -58,6 +62,7 @@ public:
58
62
  const common::LogicalType& getColumnType(uint32_t idx) const { return columnTypes[idx]; }
59
63
 
60
64
  lbug_parquet::format::FileMetaData* getMetadata() const { return metadata.get(); }
65
+ storage::MemoryManager* getMemoryManager() const;
61
66
 
62
67
  private:
63
68
  std::unique_ptr<lbug_apache::thrift::protocol::TProtocol> createThriftProtocol(
@@ -45,6 +45,9 @@ public:
45
45
  }
46
46
  return data;
47
47
  }
48
+ // Gives back the claimed bytes past the first numBytes. They may still hold old tuples, so
49
+ // they are zeroed again if claimed later.
50
+ void truncate(uint64_t numBytes);
48
51
  void resetNumTuplesAndFreeSize();
49
52
  void resetToZero();
50
53
 
@@ -1,6 +1,8 @@
1
1
  #pragma once
2
2
 
3
3
  #include <algorithm>
4
+ #include <utility>
5
+ #include <vector>
4
6
 
5
7
  #include "storage/enums/residency_state.h"
6
8
  #include "storage/table/chunked_node_group.h"
@@ -62,6 +64,18 @@ struct LBUG_API InMemChunkedCSRHeader {
62
64
  bool randomLookup = false;
63
65
  // Logical header index stored at position 0. A full-group header uses 0.
64
66
  common::offset_t indexBase = 0;
67
+ // Scan-private cache: a full on-disk header, or the [start, end) index ranges last decoded
68
+ // for a bound-node batch. Nodes at or past headerLimit, the on-disk header size, have no
69
+ // lists and read as empty. Checkpoint headers leave these at the defaults.
70
+ bool wholeHeader = false;
71
+ common::offset_t headerLimit = 0;
72
+ std::vector<std::pair<common::offset_t, common::offset_t>> decodedRanges;
73
+
74
+ // Scans read the header only for nodes of the current bound-node batch (plus the group's end
75
+ // after a whole-header decode), and CSRNodeGroup::initializeScanState covers each batch by
76
+ // the decode it runs for it or by checking these ranges. The getters assert it; an uncovered
77
+ // read returns stale bytes from a gap, or the last entry at or past numValues.
78
+ bool coversNode(common::offset_t nodeOffset) const;
65
79
 
66
80
  InMemChunkedCSRHeader(MemoryManager& memoryManager, bool enableCompression, uint64_t capacity);
67
81
  InMemChunkedCSRHeader(std::unique_ptr<ColumnChunkData> offset,
@@ -145,7 +145,8 @@ struct CSRNodeGroupScanState final : NodeGroupScanState {
145
145
  // The number of rows (i.e., rels) that have been scanned so far in current node group.
146
146
  common::row_idx_t numCachedRows;
147
147
  common::row_idx_t nextCachedRowToScan;
148
- // Entries and spans decoded so far for the current node group's header.
148
+ // Entries decoded and partial decodes (spans or cluster sets) so far for the current node
149
+ // group's header.
149
150
  common::offset_t numHeaderEntriesDecoded = 0;
150
151
  common::idx_t numHeaderSpansDecoded = 0;
151
152
 
@@ -178,6 +179,12 @@ struct CSRNodeGroupScanState final : NodeGroupScanState {
178
179
  // node vector's chunk state, which holds the served parents in its selection vector. See
179
180
  // docs/multi_parent_lifetime.md.
180
181
  bool tryScanCachedTuplesPacked(RelTableScanState& tableScanState);
182
+
183
+ // Copies rows into the cache starting in the current bound node's list and running through
184
+ // the next lists up to a gap (see refillEnd), or moves past that node when its list is empty
185
+ // or already scanned. False once the batch has no rows left.
186
+ bool refillCache(const transaction::Transaction* transaction,
187
+ RelTableScanState& tableScanState);
181
188
  };
182
189
 
183
190
  struct CSRNodeGroupCheckpointState final : NodeGroupCheckpointState {
@@ -264,6 +271,7 @@ public:
264
271
  return NodeGroup::hasRows() ||
265
272
  (persistentChunkGroup && persistentChunkGroup->getNumRows() != 0);
266
273
  }
274
+ void rollbackInsert(common::row_idx_t startRow) override;
267
275
 
268
276
  ChunkedNodeGroup* getPersistentChunkedGroup() const { return persistentChunkGroup.get(); }
269
277
  const CSRIndex* getCSRIndex() const { return csrIndex.get(); }
@@ -286,8 +294,8 @@ private:
286
294
  static void initScanForCommittedInMem(RelTableScanState& relScanState,
287
295
  CSRNodeGroupScanState& nodeGroupScanState);
288
296
 
289
- void updateCSRIndex(common::offset_t boundNodeOffsetInGroup, common::row_idx_t startRow,
290
- common::length_t length) const;
297
+ static void updateCSRIndex(CSRIndex& index, common::offset_t boundNodeOffsetInGroup,
298
+ common::row_idx_t startRow, common::length_t length);
291
299
 
292
300
  NodeGroupScanResult scanCommittedPersistent(const transaction::Transaction* transaction,
293
301
  RelTableScanState& tableState, CSRNodeGroupScanState& nodeGroupScanState) const;
@@ -313,7 +321,7 @@ private:
313
321
  const transaction::Transaction* transaction, CSRNodeGroupScanPin& pin) const;
314
322
  void pruneRetainedSnapshotsNoLock(const std::vector<common::transaction_t>& openStartTSs);
315
323
  bool needsRetainedSnapshotNoLock(const std::vector<common::transaction_t>& openStartTSs) const;
316
- void ensureCSRIndex();
324
+ CSRIndex& getCSRIndexForAppend(const common::UniqLock& lock);
317
325
 
318
326
  void checkpointInMemOnly(const common::UniqLock& lock, NodeGroupCheckpointState& state);
319
327
  void checkpointInMemAndOnDisk(const common::UniqLock& lock, NodeGroupCheckpointState& state);
@@ -25,7 +25,8 @@ struct NodeGroupScanState {
25
25
  // Index of committed but not yet checkpointed chunked group to scan.
26
26
  common::idx_t chunkedGroupIdx = 0;
27
27
  // Batches to scan without filtering on predicates first, after a batch where that kept too
28
- // many rows, and the length of the last such pause.
28
+ // many rows, and the length of the last such pause. Reset on entering a node group, not a
29
+ // chunked group: an in-memory chunked group holds a single batch.
29
30
  uint8_t numBatchesWithoutEarlyFilter = 0;
30
31
  uint8_t earlyFilterBackoff = 0;
31
32
  common::row_idx_t nextRowToScan = 0;
@@ -37,6 +38,11 @@ struct NodeGroupScanState {
37
38
  explicit NodeGroupScanState() {}
38
39
  explicit NodeGroupScanState(common::idx_t numChunks) { chunkStates.resize(numChunks); }
39
40
 
41
+ void resetEarlyFilterBackoff() {
42
+ numBatchesWithoutEarlyFilter = 0;
43
+ earlyFilterBackoff = 0;
44
+ }
45
+
40
46
  virtual ~NodeGroupScanState() = default;
41
47
  DELETE_COPY_DEFAULT_MOVE(NodeGroupScanState);
42
48
 
@@ -189,7 +195,7 @@ public:
189
195
 
190
196
  void applyFuncToChunkedGroups(version_record_handler_op_t func, common::row_idx_t startRow,
191
197
  common::row_idx_t numRows, common::transaction_t commitTS) const;
192
- void rollbackInsert(common::row_idx_t startRow);
198
+ virtual void rollbackInsert(common::row_idx_t startRow);
193
199
  void reclaimStorage(PageAllocator& pageAllocator) const;
194
200
  virtual void reclaimStorage(PageAllocator& pageAllocator, const common::UniqLock& lock) const;
195
201
 
@@ -274,9 +274,8 @@ private:
274
274
  std::optional<uint64_t> queryID = std::nullopt) const;
275
275
 
276
276
  private:
277
- // Protects the `columns` vector from concurrent access during checkpoint
278
- // column vacuum. Readers/writers take shared_lock; checkpoint takes
279
- // unique_lock during the brief column swap.
277
+ // Guards the `columns` vector: checkpoint's column vacuum swaps it and ALTER TABLE ADD grows
278
+ // it under a unique lock; readers take a shared lock.
280
279
  mutable std::shared_mutex schemaMtx;
281
280
  std::vector<std::unique_ptr<Column>> columns;
282
281
  std::unique_ptr<NodeGroupCollection> nodeGroups;
@@ -1,6 +1,7 @@
1
1
  #pragma once
2
2
 
3
3
  #include <cmath>
4
+ #include <shared_mutex>
4
5
 
5
6
  #include "common/enums/rel_direction.h"
6
7
  #include "common/enums/rel_multiplicity.h"
@@ -71,12 +72,20 @@ public:
71
72
  bool checkIfNodeHasRels(transaction::Transaction* transaction,
72
73
  common::ValueVector* srcNodeIDVector) const;
73
74
 
74
- Column* getNbrIDColumn() const { return columns[NBR_ID_COLUMN_ID].get(); }
75
+ Column* getNbrIDColumn() const { return getColumn(NBR_ID_COLUMN_ID); }
75
76
  Column* getCSROffsetColumn() const { return csrHeaderColumns.offset.get(); }
76
77
  Column* getCSRLengthColumn() const { return csrHeaderColumns.length.get(); }
77
- common::column_id_t getNumColumns() const { return columns.size(); }
78
- Column* getColumn(common::column_id_t columnID) const { return columns[columnID].get(); }
78
+ common::column_id_t getNumColumns() const {
79
+ std::shared_lock lck{columnsMtx};
80
+ return columns.size();
81
+ }
82
+ Column* getColumn(common::column_id_t columnID) const {
83
+ std::shared_lock lck{columnsMtx};
84
+ DASSERT(columnID < columns.size());
85
+ return columns[columnID].get();
86
+ }
79
87
  std::vector<const Column*> getColumns() const {
88
+ std::shared_lock lck{columnsMtx};
80
89
  std::vector<const Column*> result;
81
90
  result.reserve(columns.size());
82
91
  for (const auto& column : columns) {
@@ -139,6 +148,7 @@ private:
139
148
  }
140
149
 
141
150
  std::vector<common::LogicalType> getColumnTypes() const {
151
+ std::shared_lock lck{columnsMtx};
142
152
  std::vector<common::LogicalType> types;
143
153
  types.reserve(columns.size());
144
154
  for (const auto& column : columns) {
@@ -161,6 +171,10 @@ private:
161
171
  std::unique_ptr<NodeGroupCollection> nodeGroups;
162
172
 
163
173
  CSRHeaderColumns csrHeaderColumns;
174
+ // Guards the `columns` vector, as NodeTable::schemaMtx guards the node table's: checkpoint
175
+ // swaps it and ALTER TABLE ADD grows it under a unique lock while other connections' scans
176
+ // read it under a shared lock. A Column* stays valid for every column a checkpoint keeps.
177
+ mutable std::shared_mutex columnsMtx;
164
178
  std::vector<std::unique_ptr<Column>> columns;
165
179
 
166
180
  PersistentVersionRecordHandler persistentVersionRecordHandler;
@@ -32,6 +32,7 @@ public:
32
32
  void clearVectorInfo(common::idx_t vectorIdx);
33
33
 
34
34
  bool hasDeletions() const;
35
+ // Rows whose delete the transaction can see. Insert stamps are not checked.
35
36
  common::row_idx_t getNumDeletions(const transaction::Transaction* transaction,
36
37
  common::row_idx_t startRow, common::length_t numRows) const;
37
38
  common::row_idx_t getNumVisibleRows(const transaction::Transaction* transaction,
@@ -39,7 +40,8 @@ public:
39
40
  // Rows whose insert the transaction can see, deleted or not.
40
41
  common::row_idx_t getNumInsertedRows(const transaction::Transaction* transaction,
41
42
  common::row_idx_t startRow, common::length_t numRows) const;
42
- // Appends the rows in [startRow, startRow + numRows) whose delete the transaction can see.
43
+ // Appends to `rows` the rows in [startRow, startRow + numRows) whose delete the transaction can
44
+ // see, as row indices in the group, not offsets from startRow. Insert stamps are not checked.
43
45
  void getDeletedRows(const transaction::Transaction* transaction, common::row_idx_t startRow,
44
46
  common::length_t numRows, std::vector<common::row_idx_t>& rows) const;
45
47
  bool hasInsertions() const;