2424
2525#include " duckdb/catalog/catalog.hpp"
2626#include " duckdb/common/arrow/arrow_converter.hpp"
27+ #include " duckdb/common/arrow/arrow_wrapper.hpp"
2728
29+ #include " paimon/catalog/catalog.h"
30+ #include " paimon/catalog/identifier.h"
2831#include " paimon/commit_context.h"
2932#include " paimon/file_store_commit.h"
3033#include " paimon/file_store_write.h"
3134#include " paimon/record_batch.h"
35+ #include " paimon/schema/schema.h"
3236#include " paimon/write_context.h"
3337
38+ #include " paimon_catalog.hpp"
3439#include " paimon_insert.hpp"
3540#include " paimon_schema_entry.hpp"
3641
@@ -41,10 +46,11 @@ namespace duckdb {
4146
4247PhysicalPaimonInsert::PhysicalPaimonInsert (PhysicalPlan &physical_plan, LogicalOperator &op, SchemaCatalogEntry &schema,
4348 unique_ptr<BoundCreateTableInfo> info, string table_path,
44- map<string, string> paimon_options, idx_t estimated_cardinality)
49+ map<string, string> paimon_options, vector<string> part_keys,
50+ idx_t estimated_cardinality)
4551 : PhysicalOperator(physical_plan, PhysicalOperatorType::EXTENSION , {LogicalType::BIGINT }, estimated_cardinality),
4652 schema (&schema), info(std::move(info)), table_path(std::move(table_path)),
47- paimon_options (std::move(paimon_options)) {
53+ paimon_options (std::move(paimon_options)), part_keys(std::move(part_keys)) {
4854}
4955
5056// ---------------------------------------------------------------------------
@@ -57,6 +63,10 @@ struct PaimonInsertGlobalState : public GlobalSinkState {
5763 std::vector<std::shared_ptr<paimon::CommitMessage>> all_commit_messages;
5864 idx_t insert_count = 0 ;
5965 bool finished = false ;
66+
67+ vector<string> part_key_names;
68+ vector<idx_t > part_col_idxs;
69+ string null_part_name = " __DEFAULT_PARTITION__" ;
6070};
6171
6272struct PaimonInsertLocalState : public LocalSinkState {
@@ -77,6 +87,45 @@ unique_ptr<GlobalSinkState> PhysicalPaimonInsert::GetGlobalSinkState(ClientConte
7787 paimon_schema.CreateTable (txn, *info);
7888 }
7989
90+ if (!part_keys.empty ()) {
91+ auto &paimon_catalog = schema->catalog .Cast <PaimonCatalog>();
92+ auto &catalog = paimon_catalog.GetPaimonCatalog ();
93+ auto schema_name = info ? info->Base ().schema : schema->name ;
94+ auto table_name = info ? info->Base ().table : string ();
95+
96+ if (table_name.empty ()) {
97+ auto last_slash = table_path.rfind (' /' );
98+ table_name = (last_slash != string::npos) ? table_path.substr (last_slash + 1 ) : table_path;
99+ }
100+
101+ auto schema_result = catalog.LoadTableSchema (paimon::Identifier (schema_name, table_name));
102+ if (!schema_result.ok ()) {
103+ throw IOException (schema_result.status ().ToString ());
104+ }
105+
106+ auto table_schema = schema_result.value ();
107+ auto data_schema = std::dynamic_pointer_cast<paimon::DataSchema>(table_schema);
108+ if (!data_schema) {
109+ throw IOException (" Failed to resolve Paimon data schema for partitioned insert" );
110+ }
111+
112+ auto &table_options = data_schema->Options ();
113+ auto default_name = table_options.find (paimon::Options::PARTITION_DEFAULT_NAME );
114+ if (default_name != table_options.end ()) {
115+ state->null_part_name = default_name->second ;
116+ }
117+
118+ auto field_names = table_schema->FieldNames ();
119+ state->part_key_names = part_keys;
120+ for (auto &part_key : part_keys) {
121+ auto it = std::find (field_names.begin (), field_names.end (), part_key);
122+ if (it == field_names.end ()) {
123+ throw IOException (" Partition key \" %s\" not found in Paimon table schema" , part_key);
124+ }
125+ state->part_col_idxs .push_back (std::distance (field_names.begin (), it));
126+ }
127+ }
128+
80129 return std::move (state);
81130}
82131
@@ -101,22 +150,16 @@ unique_ptr<LocalSinkState> PhysicalPaimonInsert::GetLocalSinkState(ExecutionCont
101150 return std::move (lstate);
102151}
103152
104- SinkResultType PhysicalPaimonInsert::Sink (ExecutionContext &context, DataChunk &chunk, OperatorSinkInput &input) const {
105- auto &lstate = input.local_state .Cast <PaimonInsertLocalState>();
106-
107- ArrowArray out_array {};
108- auto client_props = context.client .GetClientProperties ();
109- ArrowConverter::ToArrowArray (chunk, &out_array, client_props, {});
110- struct ArrowArrayGuard {
111- ArrowArray &array;
112- ~ArrowArrayGuard () {
113- if (array.release ) {
114- array.release (&array);
115- }
116- }
117- } arrow_guard {out_array};
153+ static void WriteChunkWithPartition (PaimonInsertLocalState &lstate, DataChunk &chunk, ClientContext &client,
154+ const std::map<string, string> &partition) {
155+ ArrowArrayWrapper arrow_wrapper;
156+ auto client_props = client.GetClientProperties ();
157+ ArrowConverter::ToArrowArray (chunk, &arrow_wrapper.arrow_array , client_props, {});
118158
119- paimon::RecordBatchBuilder batch_builder (&out_array);
159+ paimon::RecordBatchBuilder batch_builder (&arrow_wrapper.arrow_array );
160+ if (!partition.empty ()) {
161+ batch_builder.SetPartition (partition).SetBucket (0 );
162+ }
120163 auto batch_result = batch_builder.Finish ();
121164 if (!batch_result.ok ()) {
122165 throw IOException (batch_result.status ().ToString ());
@@ -126,8 +169,66 @@ SinkResultType PhysicalPaimonInsert::Sink(ExecutionContext &context, DataChunk &
126169 if (!status.ok ()) {
127170 throw IOException (status.ToString ());
128171 }
172+ }
173+
174+ SinkResultType PhysicalPaimonInsert::Sink (ExecutionContext &context, DataChunk &chunk, OperatorSinkInput &input) const {
175+ auto &gstate = input.global_state .Cast <PaimonInsertGlobalState>();
176+ auto &lstate = input.local_state .Cast <PaimonInsertLocalState>();
177+
178+ if (gstate.part_col_idxs .empty ()) {
179+ WriteChunkWithPartition (lstate, chunk, context.client , {});
180+ lstate.local_count += chunk.size ();
181+ return SinkResultType::NEED_MORE_INPUT ;
182+ }
183+
184+ auto num_rows = chunk.size ();
185+ auto &part_idxs = gstate.part_col_idxs ;
186+ auto &part_names = gstate.part_key_names ;
187+ D_ASSERT (part_idxs.size () == part_names.size ());
188+
189+ vector<UnifiedVectorFormat> part_formats (part_idxs.size ());
190+ for (idx_t i = 0 ; i < part_idxs.size (); i++) {
191+ chunk.data [part_idxs[i]].ToUnifiedFormat (num_rows, part_formats[i]);
192+ }
193+
194+ std::map<std::vector<string>, vector<idx_t >> partition_groups;
195+ for (idx_t row = 0 ; row < num_rows; row++) {
196+ std::vector<string> key;
197+ key.reserve (part_idxs.size ());
198+ for (idx_t i = 0 ; i < part_idxs.size (); i++) {
199+ auto idx = part_formats[i].sel ->get_index (row);
200+ if (!part_formats[i].validity .RowIsValid (idx)) {
201+ key.push_back (gstate.null_part_name );
202+ } else {
203+ key.push_back (chunk.GetValue (part_idxs[i], row).ToString ());
204+ }
205+ }
206+ auto entry = partition_groups.emplace (std::move (key), vector<idx_t >());
207+ entry.first ->second .push_back (row);
208+ }
209+
210+ for (auto &entry : partition_groups) {
211+ auto &key_values = entry.first ;
212+ auto &row_indices = entry.second ;
213+
214+ std::map<string, string> partition;
215+ for (idx_t i = 0 ; i < part_names.size (); i++) {
216+ partition[part_names[i]] = key_values[i];
217+ }
218+
219+ SelectionVector sel (row_indices.size ());
220+ for (idx_t i = 0 ; i < row_indices.size (); i++) {
221+ sel.set_index (i, row_indices[i]);
222+ }
223+
224+ DataChunk sub_chunk;
225+ sub_chunk.Initialize (Allocator::DefaultAllocator (), chunk.GetTypes ());
226+ sub_chunk.Slice (chunk, sel, row_indices.size ());
227+
228+ WriteChunkWithPartition (lstate, sub_chunk, context.client , partition);
229+ }
129230
130- lstate.local_count += chunk. size () ;
231+ lstate.local_count += num_rows ;
131232 return SinkResultType::NEED_MORE_INPUT ;
132233}
133234
0 commit comments