| 1 | #include "duckdb/function/table/table_scan.hpp" |
| 2 | |
| 3 | #include "duckdb/catalog/catalog_entry/duck_table_entry.hpp" |
| 4 | #include "duckdb/common/field_writer.hpp" |
| 5 | #include "duckdb/common/mutex.hpp" |
| 6 | #include "duckdb/main/client_config.hpp" |
| 7 | #include "duckdb/optimizer/matcher/expression_matcher.hpp" |
| 8 | #include "duckdb/planner/expression/bound_between_expression.hpp" |
| 9 | #include "duckdb/planner/expression_iterator.hpp" |
| 10 | #include "duckdb/planner/operator/logical_get.hpp" |
| 11 | #include "duckdb/storage/data_table.hpp" |
| 12 | #include "duckdb/transaction/local_storage.hpp" |
| 13 | #include "duckdb/transaction/duck_transaction.hpp" |
| 14 | #include "duckdb/main/attached_database.hpp" |
| 15 | #include "duckdb/catalog/dependency_list.hpp" |
| 16 | #include "duckdb/function/function_set.hpp" |
| 17 | #include "duckdb/storage/table/scan_state.hpp" |
| 18 | |
| 19 | namespace duckdb { |
| 20 | |
| 21 | //===--------------------------------------------------------------------===// |
| 22 | // Table Scan |
| 23 | //===--------------------------------------------------------------------===// |
| 24 | bool TableScanParallelStateNext(ClientContext &context, const FunctionData *bind_data_p, |
| 25 | LocalTableFunctionState *local_state, GlobalTableFunctionState *gstate); |
| 26 | |
| 27 | struct TableScanLocalState : public LocalTableFunctionState { |
| 28 | //! The current position in the scan |
| 29 | TableScanState scan_state; |
| 30 | //! The DataChunk containing all read columns (even filter columns that are immediately removed) |
| 31 | DataChunk all_columns; |
| 32 | }; |
| 33 | |
| 34 | static storage_t GetStorageIndex(TableCatalogEntry &table, column_t column_id) { |
| 35 | if (column_id == DConstants::INVALID_INDEX) { |
| 36 | return column_id; |
| 37 | } |
| 38 | auto &col = table.GetColumn(idx: LogicalIndex(column_id)); |
| 39 | return col.StorageOid(); |
| 40 | } |
| 41 | |
| 42 | struct TableScanGlobalState : public GlobalTableFunctionState { |
| 43 | TableScanGlobalState(ClientContext &context, const FunctionData *bind_data_p) { |
| 44 | D_ASSERT(bind_data_p); |
| 45 | auto &bind_data = bind_data_p->Cast<TableScanBindData>(); |
| 46 | max_threads = bind_data.table.GetStorage().MaxThreads(context); |
| 47 | } |
| 48 | |
| 49 | ParallelTableScanState state; |
| 50 | idx_t max_threads; |
| 51 | |
| 52 | vector<idx_t> projection_ids; |
| 53 | vector<LogicalType> scanned_types; |
| 54 | |
| 55 | idx_t MaxThreads() const override { |
| 56 | return max_threads; |
| 57 | } |
| 58 | |
| 59 | bool CanRemoveFilterColumns() const { |
| 60 | return !projection_ids.empty(); |
| 61 | } |
| 62 | }; |
| 63 | |
| 64 | static unique_ptr<LocalTableFunctionState> TableScanInitLocal(ExecutionContext &context, TableFunctionInitInput &input, |
| 65 | GlobalTableFunctionState *gstate) { |
| 66 | auto result = make_uniq<TableScanLocalState>(); |
| 67 | auto &bind_data = input.bind_data->Cast<TableScanBindData>(); |
| 68 | vector<column_t> column_ids = input.column_ids; |
| 69 | for (auto &col : column_ids) { |
| 70 | auto storage_idx = GetStorageIndex(table&: bind_data.table, column_id: col); |
| 71 | col = storage_idx; |
| 72 | } |
| 73 | result->scan_state.Initialize(column_ids: std::move(column_ids), table_filters: input.filters.get()); |
| 74 | TableScanParallelStateNext(context&: context.client, bind_data_p: input.bind_data.get(), local_state: result.get(), gstate); |
| 75 | if (input.CanRemoveFilterColumns()) { |
| 76 | auto &tsgs = gstate->Cast<TableScanGlobalState>(); |
| 77 | result->all_columns.Initialize(context&: context.client, types: tsgs.scanned_types); |
| 78 | } |
| 79 | return std::move(result); |
| 80 | } |
| 81 | |
| 82 | unique_ptr<GlobalTableFunctionState> TableScanInitGlobal(ClientContext &context, TableFunctionInitInput &input) { |
| 83 | |
| 84 | D_ASSERT(input.bind_data); |
| 85 | auto &bind_data = input.bind_data->Cast<TableScanBindData>(); |
| 86 | auto result = make_uniq<TableScanGlobalState>(args&: context, args: input.bind_data.get()); |
| 87 | bind_data.table.GetStorage().InitializeParallelScan(context, state&: result->state); |
| 88 | if (input.CanRemoveFilterColumns()) { |
| 89 | result->projection_ids = input.projection_ids; |
| 90 | const auto &columns = bind_data.table.GetColumns(); |
| 91 | for (const auto &col_idx : input.column_ids) { |
| 92 | if (col_idx == COLUMN_IDENTIFIER_ROW_ID) { |
| 93 | result->scanned_types.emplace_back(args: LogicalType::ROW_TYPE); |
| 94 | } else { |
| 95 | result->scanned_types.push_back(x: columns.GetColumn(index: LogicalIndex(col_idx)).Type()); |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | return std::move(result); |
| 100 | } |
| 101 | |
| 102 | static unique_ptr<BaseStatistics> TableScanStatistics(ClientContext &context, const FunctionData *bind_data_p, |
| 103 | column_t column_id) { |
| 104 | auto &bind_data = bind_data_p->Cast<TableScanBindData>(); |
| 105 | auto &local_storage = LocalStorage::Get(context, catalog&: bind_data.table.catalog); |
| 106 | if (local_storage.Find(table&: bind_data.table.GetStorage())) { |
| 107 | // we don't emit any statistics for tables that have outstanding transaction-local data |
| 108 | return nullptr; |
| 109 | } |
| 110 | return bind_data.table.GetStatistics(context, column_id); |
| 111 | } |
| 112 | |
| 113 | static void TableScanFunc(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { |
| 114 | auto &bind_data = data_p.bind_data->Cast<TableScanBindData>(); |
| 115 | auto &gstate = data_p.global_state->Cast<TableScanGlobalState>(); |
| 116 | auto &state = data_p.local_state->Cast<TableScanLocalState>(); |
| 117 | auto &transaction = DuckTransaction::Get(context, catalog&: bind_data.table.catalog); |
| 118 | auto &storage = bind_data.table.GetStorage(); |
| 119 | do { |
| 120 | if (bind_data.is_create_index) { |
| 121 | storage.CreateIndexScan(state&: state.scan_state, result&: output, |
| 122 | type: TableScanType::TABLE_SCAN_COMMITTED_ROWS_OMIT_PERMANENTLY_DELETED); |
| 123 | } else if (gstate.CanRemoveFilterColumns()) { |
| 124 | state.all_columns.Reset(); |
| 125 | storage.Scan(transaction, result&: state.all_columns, state&: state.scan_state); |
| 126 | output.ReferenceColumns(other&: state.all_columns, column_ids: gstate.projection_ids); |
| 127 | } else { |
| 128 | storage.Scan(transaction, result&: output, state&: state.scan_state); |
| 129 | } |
| 130 | if (output.size() > 0) { |
| 131 | return; |
| 132 | } |
| 133 | if (!TableScanParallelStateNext(context, bind_data_p: data_p.bind_data.get(), local_state: data_p.local_state.get(), |
| 134 | gstate: data_p.global_state.get())) { |
| 135 | return; |
| 136 | } |
| 137 | } while (true); |
| 138 | } |
| 139 | |
| 140 | bool TableScanParallelStateNext(ClientContext &context, const FunctionData *bind_data_p, |
| 141 | LocalTableFunctionState *local_state, GlobalTableFunctionState *global_state) { |
| 142 | auto &bind_data = bind_data_p->Cast<TableScanBindData>(); |
| 143 | auto ¶llel_state = global_state->Cast<TableScanGlobalState>(); |
| 144 | auto &state = local_state->Cast<TableScanLocalState>(); |
| 145 | auto &storage = bind_data.table.GetStorage(); |
| 146 | |
| 147 | return storage.NextParallelScan(context, state&: parallel_state.state, scan_state&: state.scan_state); |
| 148 | } |
| 149 | |
| 150 | double TableScanProgress(ClientContext &context, const FunctionData *bind_data_p, |
| 151 | const GlobalTableFunctionState *gstate_p) { |
| 152 | auto &bind_data = bind_data_p->Cast<TableScanBindData>(); |
| 153 | auto &gstate = gstate_p->Cast<TableScanGlobalState>(); |
| 154 | auto &storage = bind_data.table.GetStorage(); |
| 155 | idx_t total_rows = storage.GetTotalRows(); |
| 156 | if (total_rows == 0) { |
| 157 | //! Table is either empty or smaller than a vector size, so it is finished |
| 158 | return 100; |
| 159 | } |
| 160 | idx_t scanned_rows = gstate.state.scan_state.processed_rows; |
| 161 | scanned_rows += gstate.state.local_state.processed_rows; |
| 162 | auto percentage = 100 * (double(scanned_rows) / total_rows); |
| 163 | if (percentage > 100) { |
| 164 | //! In case the last chunk has less elements than STANDARD_VECTOR_SIZE, if our percentage is over 100 |
| 165 | //! It means we finished this table. |
| 166 | return 100; |
| 167 | } |
| 168 | return percentage; |
| 169 | } |
| 170 | |
| 171 | idx_t TableScanGetBatchIndex(ClientContext &context, const FunctionData *bind_data_p, |
| 172 | LocalTableFunctionState *local_state, GlobalTableFunctionState *gstate_p) { |
| 173 | auto &state = local_state->Cast<TableScanLocalState>(); |
| 174 | if (state.scan_state.table_state.row_group) { |
| 175 | return state.scan_state.table_state.batch_index; |
| 176 | } |
| 177 | if (state.scan_state.local_state.row_group) { |
| 178 | return state.scan_state.table_state.batch_index + state.scan_state.local_state.batch_index; |
| 179 | } |
| 180 | return 0; |
| 181 | } |
| 182 | |
| 183 | BindInfo TableScanGetBindInfo(const FunctionData *bind_data) { |
| 184 | return BindInfo(ScanType::TABLE); |
| 185 | } |
| 186 | |
| 187 | void TableScanDependency(DependencyList &entries, const FunctionData *bind_data_p) { |
| 188 | auto &bind_data = bind_data_p->Cast<TableScanBindData>(); |
| 189 | entries.AddDependency(entry&: bind_data.table); |
| 190 | } |
| 191 | |
| 192 | unique_ptr<NodeStatistics> TableScanCardinality(ClientContext &context, const FunctionData *bind_data_p) { |
| 193 | auto &bind_data = bind_data_p->Cast<TableScanBindData>(); |
| 194 | auto &local_storage = LocalStorage::Get(context, catalog&: bind_data.table.catalog); |
| 195 | auto &storage = bind_data.table.GetStorage(); |
| 196 | idx_t estimated_cardinality = storage.info->cardinality + local_storage.AddedRows(table&: bind_data.table.GetStorage()); |
| 197 | return make_uniq<NodeStatistics>(args&: storage.info->cardinality, args&: estimated_cardinality); |
| 198 | } |
| 199 | |
| 200 | //===--------------------------------------------------------------------===// |
| 201 | // Index Scan |
| 202 | //===--------------------------------------------------------------------===// |
| 203 | struct IndexScanGlobalState : public GlobalTableFunctionState { |
| 204 | explicit IndexScanGlobalState(data_ptr_t row_id_data) : row_ids(LogicalType::ROW_TYPE, row_id_data) { |
| 205 | } |
| 206 | |
| 207 | Vector row_ids; |
| 208 | ColumnFetchState fetch_state; |
| 209 | TableScanState local_storage_state; |
| 210 | vector<storage_t> column_ids; |
| 211 | bool finished; |
| 212 | }; |
| 213 | |
| 214 | static unique_ptr<GlobalTableFunctionState> IndexScanInitGlobal(ClientContext &context, TableFunctionInitInput &input) { |
| 215 | auto &bind_data = input.bind_data->Cast<TableScanBindData>(); |
| 216 | data_ptr_t row_id_data = nullptr; |
| 217 | if (!bind_data.result_ids.empty()) { |
| 218 | row_id_data = (data_ptr_t)&bind_data.result_ids[0]; // NOLINT - this is not pretty |
| 219 | } |
| 220 | auto result = make_uniq<IndexScanGlobalState>(args&: row_id_data); |
| 221 | auto &local_storage = LocalStorage::Get(context, catalog&: bind_data.table.catalog); |
| 222 | |
| 223 | result->column_ids.reserve(n: input.column_ids.size()); |
| 224 | for (auto &id : input.column_ids) { |
| 225 | result->column_ids.push_back(x: GetStorageIndex(table&: bind_data.table, column_id: id)); |
| 226 | } |
| 227 | result->local_storage_state.Initialize(column_ids: result->column_ids, table_filters: input.filters.get()); |
| 228 | local_storage.InitializeScan(table&: bind_data.table.GetStorage(), state&: result->local_storage_state.local_state, table_filters: input.filters); |
| 229 | |
| 230 | result->finished = false; |
| 231 | return std::move(result); |
| 232 | } |
| 233 | |
| 234 | static void IndexScanFunction(ClientContext &context, TableFunctionInput &data_p, DataChunk &output) { |
| 235 | auto &bind_data = data_p.bind_data->Cast<TableScanBindData>(); |
| 236 | auto &state = data_p.global_state->Cast<IndexScanGlobalState>(); |
| 237 | auto &transaction = DuckTransaction::Get(context, catalog&: bind_data.table.catalog); |
| 238 | auto &local_storage = LocalStorage::Get(transaction); |
| 239 | |
| 240 | if (!state.finished) { |
| 241 | bind_data.table.GetStorage().Fetch(transaction, result&: output, column_ids: state.column_ids, row_ids: state.row_ids, |
| 242 | fetch_count: bind_data.result_ids.size(), state&: state.fetch_state); |
| 243 | state.finished = true; |
| 244 | } |
| 245 | if (output.size() == 0) { |
| 246 | local_storage.Scan(state&: state.local_storage_state.local_state, column_ids: state.column_ids, result&: output); |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | static void RewriteIndexExpression(Index &index, LogicalGet &get, Expression &expr, bool &rewrite_possible) { |
| 251 | if (expr.type == ExpressionType::BOUND_COLUMN_REF) { |
| 252 | auto &bound_colref = expr.Cast<BoundColumnRefExpression>(); |
| 253 | // bound column ref: rewrite to fit in the current set of bound column ids |
| 254 | bound_colref.binding.table_index = get.table_index; |
| 255 | column_t referenced_column = index.column_ids[bound_colref.binding.column_index]; |
| 256 | // search for the referenced column in the set of column_ids |
| 257 | for (idx_t i = 0; i < get.column_ids.size(); i++) { |
| 258 | if (get.column_ids[i] == referenced_column) { |
| 259 | bound_colref.binding.column_index = i; |
| 260 | return; |
| 261 | } |
| 262 | } |
| 263 | // column id not found in bound columns in the LogicalGet: rewrite not possible |
| 264 | rewrite_possible = false; |
| 265 | } |
| 266 | ExpressionIterator::EnumerateChildren( |
| 267 | expression&: expr, callback: [&](Expression &child) { RewriteIndexExpression(index, get, expr&: child, rewrite_possible); }); |
| 268 | } |
| 269 | |
| 270 | void TableScanPushdownComplexFilter(ClientContext &context, LogicalGet &get, FunctionData *bind_data_p, |
| 271 | vector<unique_ptr<Expression>> &filters) { |
| 272 | auto &bind_data = bind_data_p->Cast<TableScanBindData>(); |
| 273 | auto &table = bind_data.table; |
| 274 | auto &storage = table.GetStorage(); |
| 275 | |
| 276 | auto &config = ClientConfig::GetConfig(context); |
| 277 | if (!config.enable_optimizer) { |
| 278 | // we only push index scans if the optimizer is enabled |
| 279 | return; |
| 280 | } |
| 281 | if (bind_data.is_index_scan) { |
| 282 | return; |
| 283 | } |
| 284 | if (filters.empty()) { |
| 285 | // no indexes or no filters: skip the pushdown |
| 286 | return; |
| 287 | } |
| 288 | // behold |
| 289 | storage.info->indexes.Scan(callback: [&](Index &index) { |
| 290 | // first rewrite the index expression so the ColumnBindings align with the column bindings of the current table |
| 291 | |
| 292 | if (index.unbound_expressions.size() > 1) { |
| 293 | // NOTE: index scans are not (yet) supported for compound index keys |
| 294 | return false; |
| 295 | } |
| 296 | |
| 297 | auto index_expression = index.unbound_expressions[0]->Copy(); |
| 298 | bool rewrite_possible = true; |
| 299 | RewriteIndexExpression(index, get, expr&: *index_expression, rewrite_possible); |
| 300 | if (!rewrite_possible) { |
| 301 | // could not rewrite! |
| 302 | return false; |
| 303 | } |
| 304 | |
| 305 | Value low_value, high_value, equal_value; |
| 306 | ExpressionType low_comparison_type = ExpressionType::INVALID, high_comparison_type = ExpressionType::INVALID; |
| 307 | // try to find a matching index for any of the filter expressions |
| 308 | for (auto &filter : filters) { |
| 309 | auto &expr = *filter; |
| 310 | |
| 311 | // create a matcher for a comparison with a constant |
| 312 | ComparisonExpressionMatcher matcher; |
| 313 | // match on a comparison type |
| 314 | matcher.expr_type = make_uniq<ComparisonExpressionTypeMatcher>(); |
| 315 | // match on a constant comparison with the indexed expression |
| 316 | matcher.matchers.push_back(x: make_uniq<ExpressionEqualityMatcher>(args&: *index_expression)); |
| 317 | matcher.matchers.push_back(x: make_uniq<ConstantExpressionMatcher>()); |
| 318 | |
| 319 | matcher.policy = SetMatcher::Policy::UNORDERED; |
| 320 | |
| 321 | vector<reference<Expression>> bindings; |
| 322 | if (matcher.Match(expr_&: expr, bindings)) { |
| 323 | // range or equality comparison with constant value |
| 324 | // we can use our index here |
| 325 | // bindings[0] = the expression |
| 326 | // bindings[1] = the index expression |
| 327 | // bindings[2] = the constant |
| 328 | auto &comparison = bindings[0].get().Cast<BoundComparisonExpression>(); |
| 329 | auto constant_value = bindings[2].get().Cast<BoundConstantExpression>().value; |
| 330 | auto comparison_type = comparison.type; |
| 331 | if (comparison.left->type == ExpressionType::VALUE_CONSTANT) { |
| 332 | // the expression is on the right side, we flip them around |
| 333 | comparison_type = FlipComparisonExpression(type: comparison_type); |
| 334 | } |
| 335 | if (comparison_type == ExpressionType::COMPARE_EQUAL) { |
| 336 | // equality value |
| 337 | // equality overrides any other bounds so we just break here |
| 338 | equal_value = constant_value; |
| 339 | break; |
| 340 | } else if (comparison_type == ExpressionType::COMPARE_GREATERTHANOREQUALTO || |
| 341 | comparison_type == ExpressionType::COMPARE_GREATERTHAN) { |
| 342 | // greater than means this is a lower bound |
| 343 | low_value = constant_value; |
| 344 | low_comparison_type = comparison_type; |
| 345 | } else { |
| 346 | // smaller than means this is an upper bound |
| 347 | high_value = constant_value; |
| 348 | high_comparison_type = comparison_type; |
| 349 | } |
| 350 | } else if (expr.type == ExpressionType::COMPARE_BETWEEN) { |
| 351 | // BETWEEN expression |
| 352 | auto &between = expr.Cast<BoundBetweenExpression>(); |
| 353 | if (!between.input->Equals(other: *index_expression)) { |
| 354 | // expression doesn't match the current index expression |
| 355 | continue; |
| 356 | } |
| 357 | if (between.lower->type != ExpressionType::VALUE_CONSTANT || |
| 358 | between.upper->type != ExpressionType::VALUE_CONSTANT) { |
| 359 | // not a constant comparison |
| 360 | continue; |
| 361 | } |
| 362 | low_value = (between.lower->Cast<BoundConstantExpression>()).value; |
| 363 | low_comparison_type = between.lower_inclusive ? ExpressionType::COMPARE_GREATERTHANOREQUALTO |
| 364 | : ExpressionType::COMPARE_GREATERTHAN; |
| 365 | high_value = (between.upper->Cast<BoundConstantExpression>()).value; |
| 366 | high_comparison_type = between.upper_inclusive ? ExpressionType::COMPARE_LESSTHANOREQUALTO |
| 367 | : ExpressionType::COMPARE_LESSTHAN; |
| 368 | break; |
| 369 | } |
| 370 | } |
| 371 | if (!equal_value.IsNull() || !low_value.IsNull() || !high_value.IsNull()) { |
| 372 | // we can scan this index using this predicate: try a scan |
| 373 | auto &transaction = Transaction::Get(context, catalog&: bind_data.table.catalog); |
| 374 | unique_ptr<IndexScanState> index_state; |
| 375 | if (!equal_value.IsNull()) { |
| 376 | // equality predicate |
| 377 | index_state = |
| 378 | index.InitializeScanSinglePredicate(transaction, value: equal_value, expression_type: ExpressionType::COMPARE_EQUAL); |
| 379 | } else if (!low_value.IsNull() && !high_value.IsNull()) { |
| 380 | // two-sided predicate |
| 381 | index_state = index.InitializeScanTwoPredicates(transaction, low_value, low_expression_type: low_comparison_type, high_value, |
| 382 | high_expression_type: high_comparison_type); |
| 383 | } else if (!low_value.IsNull()) { |
| 384 | // less than predicate |
| 385 | index_state = index.InitializeScanSinglePredicate(transaction, value: low_value, expression_type: low_comparison_type); |
| 386 | } else { |
| 387 | D_ASSERT(!high_value.IsNull()); |
| 388 | index_state = index.InitializeScanSinglePredicate(transaction, value: high_value, expression_type: high_comparison_type); |
| 389 | } |
| 390 | if (index.Scan(transaction, table: storage, state&: *index_state, STANDARD_VECTOR_SIZE, result_ids&: bind_data.result_ids)) { |
| 391 | // use an index scan! |
| 392 | bind_data.is_index_scan = true; |
| 393 | get.function = TableScanFunction::GetIndexScanFunction(); |
| 394 | } else { |
| 395 | bind_data.result_ids.clear(); |
| 396 | } |
| 397 | return true; |
| 398 | } |
| 399 | return false; |
| 400 | }); |
| 401 | } |
| 402 | |
| 403 | string TableScanToString(const FunctionData *bind_data_p) { |
| 404 | auto &bind_data = bind_data_p->Cast<TableScanBindData>(); |
| 405 | string result = bind_data.table.name; |
| 406 | return result; |
| 407 | } |
| 408 | |
| 409 | static void TableScanSerialize(FieldWriter &writer, const FunctionData *bind_data_p, const TableFunction &function) { |
| 410 | auto &bind_data = bind_data_p->Cast<TableScanBindData>(); |
| 411 | |
| 412 | writer.WriteString(val: bind_data.table.schema.name); |
| 413 | writer.WriteString(val: bind_data.table.name); |
| 414 | writer.WriteField<bool>(element: bind_data.is_index_scan); |
| 415 | writer.WriteField<bool>(element: bind_data.is_create_index); |
| 416 | writer.WriteList<row_t>(elements: bind_data.result_ids); |
| 417 | writer.WriteString(val: bind_data.table.schema.catalog.GetName()); |
| 418 | } |
| 419 | |
| 420 | static unique_ptr<FunctionData> TableScanDeserialize(PlanDeserializationState &state, FieldReader &reader, |
| 421 | TableFunction &function) { |
| 422 | auto schema_name = reader.ReadRequired<string>(); |
| 423 | auto table_name = reader.ReadRequired<string>(); |
| 424 | auto is_index_scan = reader.ReadRequired<bool>(); |
| 425 | auto is_create_index = reader.ReadRequired<bool>(); |
| 426 | auto result_ids = reader.ReadRequiredList<row_t>(); |
| 427 | auto catalog_name = reader.ReadField<string>(INVALID_CATALOG); |
| 428 | |
| 429 | auto &catalog_entry = Catalog::GetEntry<TableCatalogEntry>(context&: state.context, catalog_name, schema_name, name: table_name); |
| 430 | if (catalog_entry.type != CatalogType::TABLE_ENTRY) { |
| 431 | throw SerializationException("Cant find table for %s.%s" , schema_name, table_name); |
| 432 | } |
| 433 | |
| 434 | auto result = make_uniq<TableScanBindData>(args&: catalog_entry.Cast<DuckTableEntry>()); |
| 435 | result->is_index_scan = is_index_scan; |
| 436 | result->is_create_index = is_create_index; |
| 437 | result->result_ids = std::move(result_ids); |
| 438 | return std::move(result); |
| 439 | } |
| 440 | |
| 441 | TableFunction TableScanFunction::GetIndexScanFunction() { |
| 442 | TableFunction scan_function("index_scan" , {}, IndexScanFunction); |
| 443 | scan_function.init_local = nullptr; |
| 444 | scan_function.init_global = IndexScanInitGlobal; |
| 445 | scan_function.statistics = TableScanStatistics; |
| 446 | scan_function.dependency = TableScanDependency; |
| 447 | scan_function.cardinality = TableScanCardinality; |
| 448 | scan_function.pushdown_complex_filter = nullptr; |
| 449 | scan_function.to_string = TableScanToString; |
| 450 | scan_function.table_scan_progress = nullptr; |
| 451 | scan_function.get_batch_index = nullptr; |
| 452 | scan_function.projection_pushdown = true; |
| 453 | scan_function.filter_pushdown = false; |
| 454 | scan_function.serialize = TableScanSerialize; |
| 455 | scan_function.deserialize = TableScanDeserialize; |
| 456 | return scan_function; |
| 457 | } |
| 458 | |
| 459 | TableFunction TableScanFunction::GetFunction() { |
| 460 | TableFunction scan_function("seq_scan" , {}, TableScanFunc); |
| 461 | scan_function.init_local = TableScanInitLocal; |
| 462 | scan_function.init_global = TableScanInitGlobal; |
| 463 | scan_function.statistics = TableScanStatistics; |
| 464 | scan_function.dependency = TableScanDependency; |
| 465 | scan_function.cardinality = TableScanCardinality; |
| 466 | scan_function.pushdown_complex_filter = TableScanPushdownComplexFilter; |
| 467 | scan_function.to_string = TableScanToString; |
| 468 | scan_function.table_scan_progress = TableScanProgress; |
| 469 | scan_function.get_batch_index = TableScanGetBatchIndex; |
| 470 | scan_function.get_batch_info = TableScanGetBindInfo; |
| 471 | scan_function.projection_pushdown = true; |
| 472 | scan_function.filter_pushdown = true; |
| 473 | scan_function.filter_prune = true; |
| 474 | scan_function.serialize = TableScanSerialize; |
| 475 | scan_function.deserialize = TableScanDeserialize; |
| 476 | return scan_function; |
| 477 | } |
| 478 | |
| 479 | optional_ptr<TableCatalogEntry> TableScanFunction::GetTableEntry(const TableFunction &function, |
| 480 | const optional_ptr<FunctionData> bind_data_p) { |
| 481 | if (function.function != TableScanFunc || !bind_data_p) { |
| 482 | return nullptr; |
| 483 | } |
| 484 | auto &bind_data = bind_data_p->Cast<TableScanBindData>(); |
| 485 | return &bind_data.table; |
| 486 | } |
| 487 | |
| 488 | void TableScanFunction::RegisterFunction(BuiltinFunctions &set) { |
| 489 | TableFunctionSet table_scan_set("seq_scan" ); |
| 490 | table_scan_set.AddFunction(function: GetFunction()); |
| 491 | set.AddFunction(set: std::move(table_scan_set)); |
| 492 | |
| 493 | set.AddFunction(function: GetIndexScanFunction()); |
| 494 | } |
| 495 | |
| 496 | void BuiltinFunctions::RegisterTableScanFunctions() { |
| 497 | TableScanFunction::RegisterFunction(set&: *this); |
| 498 | } |
| 499 | |
| 500 | } // namespace duckdb |
| 501 | |