-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathfluss.hpp
More file actions
1358 lines (1145 loc) · 46.7 KB
/
Copy pathfluss.hpp
File metadata and controls
1358 lines (1145 loc) · 46.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#pragma once
#include <chrono>
#include <cstdint>
#include <limits>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
// Forward declare Arrow classes to avoid including heavy Arrow headers in header
namespace arrow {
class RecordBatch;
}
namespace fluss {
namespace ffi {
struct Connection;
struct Admin;
struct Table;
struct AppendWriter;
struct WriteResult;
struct LogScanner;
struct UpsertWriter;
struct Lookuper;
struct ScanResultInner;
struct GenericRowInner;
struct LookupResultInner;
} // namespace ffi
/// Named constants for Fluss API error codes.
///
/// Server API errors have error_code > 0 or == -1.
/// Client-side errors have error_code == CLIENT_ERROR (-2).
/// These constants match the Rust core FlussError enum and are stable across protocol versions.
/// New server error codes work automatically (error_code is a raw int, not a closed enum) —
/// these constants are convenience names, not an exhaustive list.
struct ErrorCode {
/// Client-side error (not from server API protocol). Check error_message for details.
static constexpr int CLIENT_ERROR = -2;
/// No error.
static constexpr int NONE = 0;
/// The server experienced an unexpected error when processing the request.
static constexpr int UNKNOWN_SERVER_ERROR = -1;
/// The server disconnected before a response was received.
static constexpr int NETWORK_EXCEPTION = 1;
/// The version of API is not supported.
static constexpr int UNSUPPORTED_VERSION = 2;
/// This message has failed its CRC checksum, exceeds the valid size, or is otherwise corrupt.
static constexpr int CORRUPT_MESSAGE = 3;
/// The database does not exist.
static constexpr int DATABASE_NOT_EXIST = 4;
/// The database is not empty.
static constexpr int DATABASE_NOT_EMPTY = 5;
/// The database already exists.
static constexpr int DATABASE_ALREADY_EXIST = 6;
/// The table does not exist.
static constexpr int TABLE_NOT_EXIST = 7;
/// The table already exists.
static constexpr int TABLE_ALREADY_EXIST = 8;
/// The schema does not exist.
static constexpr int SCHEMA_NOT_EXIST = 9;
/// Exception occurred while storing data for log in server.
static constexpr int LOG_STORAGE_EXCEPTION = 10;
/// Exception occurred while storing data for kv in server.
static constexpr int KV_STORAGE_EXCEPTION = 11;
/// Not leader or follower.
static constexpr int NOT_LEADER_OR_FOLLOWER = 12;
/// The record is too large.
static constexpr int RECORD_TOO_LARGE_EXCEPTION = 13;
/// The record is corrupt.
static constexpr int CORRUPT_RECORD_EXCEPTION = 14;
/// The client has attempted to perform an operation on an invalid table.
static constexpr int INVALID_TABLE_EXCEPTION = 15;
/// The client has attempted to perform an operation on an invalid database.
static constexpr int INVALID_DATABASE_EXCEPTION = 16;
/// The replication factor is larger than the number of available tablet servers.
static constexpr int INVALID_REPLICATION_FACTOR = 17;
/// Produce request specified an invalid value for required acks.
static constexpr int INVALID_REQUIRED_ACKS = 18;
/// The log offset is out of range.
static constexpr int LOG_OFFSET_OUT_OF_RANGE_EXCEPTION = 19;
/// The table is not a primary key table.
static constexpr int NON_PRIMARY_KEY_TABLE_EXCEPTION = 20;
/// The table or bucket does not exist.
static constexpr int UNKNOWN_TABLE_OR_BUCKET_EXCEPTION = 21;
/// The update version is invalid.
static constexpr int INVALID_UPDATE_VERSION_EXCEPTION = 22;
/// The coordinator is invalid.
static constexpr int INVALID_COORDINATOR_EXCEPTION = 23;
/// The leader epoch is invalid.
static constexpr int FENCED_LEADER_EPOCH_EXCEPTION = 24;
/// The request timed out.
static constexpr int REQUEST_TIME_OUT = 25;
/// The general storage exception.
static constexpr int STORAGE_EXCEPTION = 26;
/// The server did not attempt to execute this operation.
static constexpr int OPERATION_NOT_ATTEMPTED_EXCEPTION = 27;
/// Records are written to the server already, but to fewer in-sync replicas than required.
static constexpr int NOT_ENOUGH_REPLICAS_AFTER_APPEND_EXCEPTION = 28;
/// Messages are rejected since there are fewer in-sync replicas than required.
static constexpr int NOT_ENOUGH_REPLICAS_EXCEPTION = 29;
/// Get file access security token exception.
static constexpr int SECURITY_TOKEN_EXCEPTION = 30;
/// The tablet server received an out of order sequence batch.
static constexpr int OUT_OF_ORDER_SEQUENCE_EXCEPTION = 31;
/// The tablet server received a duplicate sequence batch.
static constexpr int DUPLICATE_SEQUENCE_EXCEPTION = 32;
/// The tablet server could not locate the writer metadata.
static constexpr int UNKNOWN_WRITER_ID_EXCEPTION = 33;
/// The requested column projection is invalid.
static constexpr int INVALID_COLUMN_PROJECTION = 34;
/// The requested target column to write is invalid.
static constexpr int INVALID_TARGET_COLUMN = 35;
/// The partition does not exist.
static constexpr int PARTITION_NOT_EXISTS = 36;
/// The table is not partitioned.
static constexpr int TABLE_NOT_PARTITIONED_EXCEPTION = 37;
/// The timestamp is invalid.
static constexpr int INVALID_TIMESTAMP_EXCEPTION = 38;
/// The config is invalid.
static constexpr int INVALID_CONFIG_EXCEPTION = 39;
/// The lake storage is not configured.
static constexpr int LAKE_STORAGE_NOT_CONFIGURED_EXCEPTION = 40;
/// The kv snapshot does not exist.
static constexpr int KV_SNAPSHOT_NOT_EXIST = 41;
/// The partition already exists.
static constexpr int PARTITION_ALREADY_EXISTS = 42;
/// The partition spec is invalid.
static constexpr int PARTITION_SPEC_INVALID_EXCEPTION = 43;
/// There is no currently available leader for the given partition.
static constexpr int LEADER_NOT_AVAILABLE_EXCEPTION = 44;
/// Exceed the maximum number of partitions.
static constexpr int PARTITION_MAX_NUM_EXCEPTION = 45;
/// Authentication failed.
static constexpr int AUTHENTICATE_EXCEPTION = 46;
/// Security is disabled.
static constexpr int SECURITY_DISABLED_EXCEPTION = 47;
/// Authorization failed.
static constexpr int AUTHORIZATION_EXCEPTION = 48;
/// Exceed the maximum number of buckets.
static constexpr int BUCKET_MAX_NUM_EXCEPTION = 49;
/// The tiering epoch is invalid.
static constexpr int FENCED_TIERING_EPOCH_EXCEPTION = 50;
/// Authentication failed with retriable exception.
static constexpr int RETRIABLE_AUTHENTICATE_EXCEPTION = 51;
/// The server rack info is invalid.
static constexpr int INVALID_SERVER_RACK_INFO_EXCEPTION = 52;
/// The lake snapshot does not exist.
static constexpr int LAKE_SNAPSHOT_NOT_EXIST = 53;
/// The lake table already exists.
static constexpr int LAKE_TABLE_ALREADY_EXIST = 54;
/// The new ISR contains at least one ineligible replica.
static constexpr int INELIGIBLE_REPLICA_EXCEPTION = 55;
/// The alter table is invalid.
static constexpr int INVALID_ALTER_TABLE_EXCEPTION = 56;
/// Deletion operations are disabled on this table.
static constexpr int DELETION_DISABLED_EXCEPTION = 57;
};
struct Date {
int32_t days_since_epoch{0};
static Date FromDays(int32_t days) { return {days}; }
static Date FromYMD(int year, int month, int day);
int Year() const;
int Month() const;
int Day() const;
};
struct Time {
static constexpr int32_t kMillisPerSecond = 1000;
static constexpr int32_t kMillisPerMinute = 60 * kMillisPerSecond;
static constexpr int32_t kMillisPerHour = 60 * kMillisPerMinute;
int32_t millis_since_midnight{0};
static Time FromMillis(int32_t ms) { return {ms}; }
static Time FromHMS(int hour, int minute, int second, int millis = 0) {
return {hour * kMillisPerHour + minute * kMillisPerMinute + second * kMillisPerSecond +
millis};
}
int Hour() const { return millis_since_midnight / kMillisPerHour; }
int Minute() const { return (millis_since_midnight % kMillisPerHour) / kMillisPerMinute; }
int Second() const { return (millis_since_midnight % kMillisPerMinute) / kMillisPerSecond; }
int Millis() const { return millis_since_midnight % kMillisPerSecond; }
};
struct Timestamp {
static constexpr int32_t kMaxNanoOfMillisecond = 999999;
static constexpr int64_t kNanosPerMilli = 1000000;
int64_t epoch_millis{0};
int32_t nano_of_millisecond{0};
static Timestamp FromMillis(int64_t ms) { return {ms, 0}; }
static Timestamp FromMillisNanos(int64_t ms, int32_t nanos) {
if (nanos < 0) nanos = 0;
if (nanos > kMaxNanoOfMillisecond) nanos = kMaxNanoOfMillisecond;
return {ms, nanos};
}
static Timestamp FromTimePoint(std::chrono::system_clock::time_point tp) {
auto duration = tp.time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count();
auto ms = ns / kNanosPerMilli;
auto nano_of_ms = static_cast<int32_t>(ns % kNanosPerMilli);
if (nano_of_ms < 0) {
nano_of_ms += kNanosPerMilli;
ms -= 1;
}
return {ms, nano_of_ms};
}
};
enum class ChangeType {
AppendOnly = 0,
Insert = 1,
UpdateBefore = 2,
UpdateAfter = 3,
Delete = 4,
};
enum class TypeId {
Unknown = 0,
Boolean = 1,
TinyInt = 2,
SmallInt = 3,
Int = 4,
BigInt = 5,
Float = 6,
Double = 7,
String = 8,
Bytes = 9,
Date = 10,
Time = 11,
Timestamp = 12,
TimestampLtz = 13,
Decimal = 14,
Char = 15,
Binary = 16,
};
class DataType {
public:
explicit DataType(TypeId id, int32_t p = 0, int32_t s = 0)
: id_(id), precision_(p), scale_(s) {}
static DataType Boolean() { return DataType(TypeId::Boolean); }
static DataType TinyInt() { return DataType(TypeId::TinyInt); }
static DataType SmallInt() { return DataType(TypeId::SmallInt); }
static DataType Int() { return DataType(TypeId::Int); }
static DataType BigInt() { return DataType(TypeId::BigInt); }
static DataType Float() { return DataType(TypeId::Float); }
static DataType Double() { return DataType(TypeId::Double); }
static DataType String() { return DataType(TypeId::String); }
static DataType Bytes() { return DataType(TypeId::Bytes); }
static DataType Date() { return DataType(TypeId::Date); }
static DataType Time() { return DataType(TypeId::Time); }
static DataType Timestamp(int32_t precision = 6) {
return DataType(TypeId::Timestamp, precision, 0);
}
static DataType TimestampLtz(int32_t precision = 6) {
return DataType(TypeId::TimestampLtz, precision, 0);
}
static DataType Decimal(int32_t precision, int32_t scale) {
return DataType(TypeId::Decimal, precision, scale);
}
static DataType Char(int32_t length) { return DataType(TypeId::Char, length, 0); }
static DataType Binary(int32_t length) { return DataType(TypeId::Binary, length, 0); }
TypeId id() const { return id_; }
int32_t precision() const { return precision_; }
int32_t scale() const { return scale_; }
private:
TypeId id_;
int32_t precision_{0};
int32_t scale_{0};
};
constexpr int64_t EARLIEST_OFFSET = -2;
enum class OffsetType {
Earliest = 0,
Latest = 1,
Timestamp = 2,
};
struct OffsetSpec {
OffsetType type;
int64_t timestamp{0};
static OffsetSpec Earliest() { return {OffsetType::Earliest, 0}; }
static OffsetSpec Latest() { return {OffsetType::Latest, 0}; }
static OffsetSpec Timestamp(int64_t ts) { return {OffsetType::Timestamp, ts}; }
};
struct Result {
int32_t error_code{0};
std::string error_message;
bool Ok() const { return error_code == 0; }
};
struct TablePath {
std::string database_name;
std::string table_name;
TablePath() = default;
TablePath(std::string db, std::string tbl)
: database_name(std::move(db)), table_name(std::move(tbl)) {}
std::string ToString() const { return database_name + "." + table_name; }
};
struct Column {
std::string name;
DataType data_type;
std::string comment;
};
struct Schema {
std::vector<Column> columns;
std::vector<std::string> primary_keys;
class Builder {
public:
Builder& AddColumn(std::string name, DataType type, std::string comment = "") {
columns_.push_back({std::move(name), std::move(type), std::move(comment)});
return *this;
}
Builder& SetPrimaryKeys(std::vector<std::string> keys) {
primary_keys_ = std::move(keys);
return *this;
}
Schema Build() { return Schema{std::move(columns_), std::move(primary_keys_)}; }
private:
std::vector<Column> columns_;
std::vector<std::string> primary_keys_;
};
static Builder NewBuilder() { return Builder(); }
};
struct TableDescriptor {
Schema schema;
std::vector<std::string> partition_keys;
int32_t bucket_count{0};
std::vector<std::string> bucket_keys;
std::unordered_map<std::string, std::string> properties;
std::unordered_map<std::string, std::string> custom_properties;
std::string comment;
class Builder {
public:
Builder& SetSchema(Schema s) {
schema_ = std::move(s);
return *this;
}
Builder& SetPartitionKeys(std::vector<std::string> keys) {
partition_keys_ = std::move(keys);
return *this;
}
Builder& SetBucketCount(int32_t count) {
bucket_count_ = count;
return *this;
}
Builder& SetBucketKeys(std::vector<std::string> keys) {
bucket_keys_ = std::move(keys);
return *this;
}
Builder& SetProperty(std::string key, std::string value) {
properties_[std::move(key)] = std::move(value);
return *this;
}
Builder& SetCustomProperty(std::string key, std::string value) {
custom_properties_[std::move(key)] = std::move(value);
return *this;
}
Builder& SetLogFormat(std::string format) {
return SetProperty("table.log.format", std::move(format));
}
Builder& SetKvFormat(std::string format) {
return SetProperty("table.kv.format", std::move(format));
}
Builder& SetComment(std::string comment) {
comment_ = std::move(comment);
return *this;
}
TableDescriptor Build() {
return TableDescriptor{std::move(schema_), std::move(partition_keys_),
bucket_count_, std::move(bucket_keys_),
std::move(properties_), std::move(custom_properties_),
std::move(comment_)};
}
private:
Schema schema_;
std::vector<std::string> partition_keys_;
int32_t bucket_count_{0};
std::vector<std::string> bucket_keys_;
std::unordered_map<std::string, std::string> properties_;
std::unordered_map<std::string, std::string> custom_properties_;
std::string comment_;
};
static Builder NewBuilder() { return Builder(); }
};
struct TableInfo {
int64_t table_id;
int32_t schema_id;
TablePath table_path;
int64_t created_time;
int64_t modified_time;
std::vector<std::string> primary_keys;
std::vector<std::string> bucket_keys;
std::vector<std::string> partition_keys;
int32_t num_buckets;
bool has_primary_key;
bool is_partitioned;
std::unordered_map<std::string, std::string> properties;
std::unordered_map<std::string, std::string> custom_properties;
std::string comment;
Schema schema;
};
namespace detail {
struct ColumnInfo {
size_t index;
TypeId type_id;
};
using ColumnMap = std::unordered_map<std::string, ColumnInfo>;
inline size_t ResolveColumn(const ColumnMap& map, const std::string& name) {
auto it = map.find(name);
if (it == map.end()) {
throw std::runtime_error("Unknown column '" + name + "'");
}
return it->second.index;
}
/// CRTP mixin that adds name-based getters to any class with index-based getters.
/// Derived must provide: `size_t Resolve(const std::string&) const`
/// and all the index-based getters (IsNull(idx), GetBool(idx), etc.).
template <typename Derived>
struct NamedGetters {
bool IsNull(const std::string& n) const { return Self().IsNull(Self().Resolve(n)); }
bool GetBool(const std::string& n) const { return Self().GetBool(Self().Resolve(n)); }
int32_t GetInt32(const std::string& n) const { return Self().GetInt32(Self().Resolve(n)); }
int64_t GetInt64(const std::string& n) const { return Self().GetInt64(Self().Resolve(n)); }
float GetFloat32(const std::string& n) const { return Self().GetFloat32(Self().Resolve(n)); }
double GetFloat64(const std::string& n) const { return Self().GetFloat64(Self().Resolve(n)); }
std::string_view GetString(const std::string& n) const {
return Self().GetString(Self().Resolve(n));
}
std::pair<const uint8_t*, size_t> GetBytes(const std::string& n) const {
return Self().GetBytes(Self().Resolve(n));
}
fluss::Date GetDate(const std::string& n) const { return Self().GetDate(Self().Resolve(n)); }
fluss::Time GetTime(const std::string& n) const { return Self().GetTime(Self().Resolve(n)); }
fluss::Timestamp GetTimestamp(const std::string& n) const {
return Self().GetTimestamp(Self().Resolve(n));
}
std::string GetDecimalString(const std::string& n) const {
return Self().GetDecimalString(Self().Resolve(n));
}
private:
const Derived& Self() const { return static_cast<const Derived&>(*this); }
};
struct ScanData {
ffi::ScanResultInner* raw;
ColumnMap columns;
ScanData(ffi::ScanResultInner* r, ColumnMap cols) : raw(r), columns(std::move(cols)) {}
~ScanData();
ScanData(const ScanData&) = delete;
ScanData& operator=(const ScanData&) = delete;
};
} // namespace detail
class GenericRow {
public:
GenericRow();
explicit GenericRow(size_t field_count);
~GenericRow() noexcept;
GenericRow(const GenericRow&) = delete;
GenericRow& operator=(const GenericRow&) = delete;
GenericRow(GenericRow&& other) noexcept;
GenericRow& operator=(GenericRow&& other) noexcept;
bool Available() const;
void Reset();
// ── Index-based setters ──────────────────────────────────────────
void SetNull(size_t idx);
void SetBool(size_t idx, bool v);
void SetInt32(size_t idx, int32_t v);
void SetInt64(size_t idx, int64_t v);
void SetFloat32(size_t idx, float v);
void SetFloat64(size_t idx, double v);
void SetString(size_t idx, std::string v);
void SetBytes(size_t idx, std::vector<uint8_t> v);
void SetDate(size_t idx, fluss::Date d);
void SetTime(size_t idx, fluss::Time t);
void SetTimestampNtz(size_t idx, fluss::Timestamp ts);
void SetTimestampLtz(size_t idx, fluss::Timestamp ts);
void SetDecimal(size_t idx, const std::string& value);
// ── Name-based setters (require schema — see Table::NewRow()) ───
void Set(const std::string& name, std::nullptr_t) { SetNull(Resolve(name)); }
void Set(const std::string& name, bool v) { SetBool(Resolve(name), v); }
void Set(const std::string& name, int32_t v) { SetInt32(Resolve(name), v); }
void Set(const std::string& name, int64_t v) { SetInt64(Resolve(name), v); }
void Set(const std::string& name, float v) { SetFloat32(Resolve(name), v); }
void Set(const std::string& name, double v) { SetFloat64(Resolve(name), v); }
// const char* overload to prevent "string literal" -> bool conversion
void Set(const std::string& name, const char* v) {
auto [idx, type] = ResolveColumn(name);
if (type == TypeId::Decimal) {
SetDecimal(idx, v);
} else if (type == TypeId::String) {
SetString(idx, v);
} else {
throw std::runtime_error("GenericRow::Set: column '" + name +
"' is not a string or decimal column");
}
}
void Set(const std::string& name, std::string v) {
auto [idx, type] = ResolveColumn(name);
if (type == TypeId::Decimal) {
SetDecimal(idx, v);
} else if (type == TypeId::String) {
SetString(idx, std::move(v));
} else {
throw std::runtime_error("GenericRow::Set: column '" + name +
"' is not a string or decimal column");
}
}
void Set(const std::string& name, std::vector<uint8_t> v) {
SetBytes(Resolve(name), std::move(v));
}
void Set(const std::string& name, fluss::Date d) { SetDate(Resolve(name), d); }
void Set(const std::string& name, fluss::Time t) { SetTime(Resolve(name), t); }
void Set(const std::string& name, fluss::Timestamp ts) {
auto [idx, type] = ResolveColumn(name);
if (type == TypeId::TimestampLtz) {
SetTimestampLtz(idx, ts);
} else if (type == TypeId::Timestamp) {
SetTimestampNtz(idx, ts);
} else {
throw std::runtime_error("GenericRow::Set: column '" + name +
"' is not a timestamp column");
}
}
private:
friend class Table;
friend class AppendWriter;
friend class UpsertWriter;
friend class Lookuper;
using ColumnInfo = detail::ColumnInfo;
using ColumnMap = detail::ColumnMap;
size_t Resolve(const std::string& name) const { return ResolveColumn(name).index; }
const ColumnInfo& ResolveColumn(const std::string& name) const {
if (!column_map_) {
throw std::runtime_error(
"GenericRow: name-based Set() requires a schema. "
"Use Table::NewRow() to create a schema-aware row.");
}
auto it = column_map_->find(name);
if (it == column_map_->end()) {
throw std::runtime_error("GenericRow: unknown column '" + name + "'");
}
return it->second;
}
void Destroy() noexcept;
ffi::GenericRowInner* inner_{nullptr};
std::shared_ptr<ColumnMap> column_map_;
};
/// Read-only row view for scan results. Zero-copy access to string and bytes data.
///
/// RowView shares ownership of the underlying scan data via reference counting,
/// so it can safely outlive the ScanRecords that produced it.
class RowView : public detail::NamedGetters<RowView> {
friend struct detail::NamedGetters<RowView>;
public:
RowView(std::shared_ptr<const detail::ScanData> data, size_t bucket_idx, size_t rec_idx)
: data_(std::move(data)), bucket_idx_(bucket_idx), rec_idx_(rec_idx) {}
// ── Index-based getters ──────────────────────────────────────────
size_t FieldCount() const;
TypeId GetType(size_t idx) const;
bool IsNull(size_t idx) const;
bool GetBool(size_t idx) const;
int32_t GetInt32(size_t idx) const;
int64_t GetInt64(size_t idx) const;
float GetFloat32(size_t idx) const;
double GetFloat64(size_t idx) const;
std::string_view GetString(size_t idx) const;
std::pair<const uint8_t*, size_t> GetBytes(size_t idx) const;
fluss::Date GetDate(size_t idx) const;
fluss::Time GetTime(size_t idx) const;
fluss::Timestamp GetTimestamp(size_t idx) const;
bool IsDecimal(size_t idx) const;
std::string GetDecimalString(size_t idx) const;
// Name-based getters inherited from detail::NamedGetters<RowView>
using detail::NamedGetters<RowView>::IsNull;
using detail::NamedGetters<RowView>::GetBool;
using detail::NamedGetters<RowView>::GetInt32;
using detail::NamedGetters<RowView>::GetInt64;
using detail::NamedGetters<RowView>::GetFloat32;
using detail::NamedGetters<RowView>::GetFloat64;
using detail::NamedGetters<RowView>::GetString;
using detail::NamedGetters<RowView>::GetBytes;
using detail::NamedGetters<RowView>::GetDate;
using detail::NamedGetters<RowView>::GetTime;
using detail::NamedGetters<RowView>::GetTimestamp;
using detail::NamedGetters<RowView>::GetDecimalString;
private:
size_t Resolve(const std::string& name) const {
if (!data_) {
throw std::runtime_error("RowView: name-based access not available");
}
return detail::ResolveColumn(data_->columns, name);
}
std::shared_ptr<const detail::ScanData> data_;
size_t bucket_idx_;
size_t rec_idx_;
};
/// Identifies a specific bucket, optionally within a partition.
struct TableBucket {
int64_t table_id;
int32_t bucket_id;
std::optional<int64_t> partition_id;
bool operator==(const TableBucket& other) const {
return table_id == other.table_id && bucket_id == other.bucket_id &&
partition_id == other.partition_id;
}
bool operator!=(const TableBucket& other) const { return !(*this == other); }
};
/// A single scan record. Contains metadata and a RowView for field access.
///
/// ScanRecord is a value type that can be freely copied, stored, and
/// accumulated across multiple Poll() calls.
struct ScanRecord {
int64_t offset;
int64_t timestamp;
ChangeType change_type;
RowView row;
};
/// A bundle of scan records belonging to a single bucket.
///
/// BucketRecords is a value type — it shares ownership of the underlying scan data
/// via reference counting, so it can safely outlive the ScanRecords that produced it.
class BucketRecords {
public:
BucketRecords(std::shared_ptr<const detail::ScanData> data, TableBucket bucket,
size_t bucket_idx, size_t count)
: data_(std::move(data)),
bucket_(std::move(bucket)),
bucket_idx_(bucket_idx),
count_(count) {}
/// The bucket these records belong to.
const TableBucket& Bucket() const { return bucket_; }
/// Number of records in this bucket.
size_t Size() const { return count_; }
bool Empty() const { return count_ == 0; }
/// Access a record by its position within this bucket (0-based).
ScanRecord operator[](size_t idx) const;
class Iterator {
public:
ScanRecord operator*() const;
Iterator& operator++() {
++idx_;
return *this;
}
bool operator!=(const Iterator& other) const { return idx_ != other.idx_; }
private:
friend class BucketRecords;
Iterator(const BucketRecords* owner, size_t idx) : owner_(owner), idx_(idx) {}
const BucketRecords* owner_;
size_t idx_;
};
Iterator begin() const { return Iterator(this, 0); }
Iterator end() const { return Iterator(this, count_); }
private:
std::shared_ptr<const detail::ScanData> data_;
TableBucket bucket_;
size_t bucket_idx_;
size_t count_;
};
class ScanRecords {
public:
ScanRecords() noexcept = default;
~ScanRecords() noexcept = default;
ScanRecords(const ScanRecords&) = delete;
ScanRecords& operator=(const ScanRecords&) = delete;
ScanRecords(ScanRecords&&) noexcept = default;
ScanRecords& operator=(ScanRecords&&) noexcept = default;
/// Total number of records across all buckets.
size_t Count() const;
bool IsEmpty() const;
/// Number of distinct buckets with records.
size_t BucketCount() const;
/// List of distinct buckets that have records.
std::vector<TableBucket> Buckets() const;
/// Get records for a specific bucket.
///
/// Returns an empty BucketRecords if the bucket is not present (matches Rust/Java).
/// Note: O(B) linear scan. For iteration over all buckets, prefer BucketAt(idx).
BucketRecords Records(const TableBucket& bucket) const;
/// Get records by bucket index (0-based). O(1).
///
/// Throws std::out_of_range if idx >= BucketCount().
BucketRecords BucketAt(size_t idx) const;
/// Flat iterator over all records across all buckets (matches Java Iterable<ScanRecord>).
class Iterator {
public:
ScanRecord operator*() const;
Iterator& operator++();
bool operator!=(const Iterator& other) const {
return bucket_idx_ != other.bucket_idx_ || rec_idx_ != other.rec_idx_;
}
private:
friend class ScanRecords;
Iterator(const ScanRecords* owner, size_t bucket_idx, size_t rec_idx)
: owner_(owner), bucket_idx_(bucket_idx), rec_idx_(rec_idx) {}
const ScanRecords* owner_;
size_t bucket_idx_;
size_t rec_idx_;
};
Iterator begin() const;
Iterator end() const { return Iterator(this, BucketCount(), 0); }
private:
friend class LogScanner;
ScanRecord RecordAt(size_t bucket, size_t rec_idx) const;
std::shared_ptr<const detail::ScanData> data_;
};
class ArrowRecordBatch {
public:
std::shared_ptr<arrow::RecordBatch> GetArrowRecordBatch() const { return batch_; }
bool Available() const;
// Get number of rows in the batch
int64_t NumRows() const;
// Get ScanBatch metadata
int64_t GetTableId() const;
int64_t GetPartitionId() const;
int32_t GetBucketId() const;
int64_t GetBaseOffset() const;
int64_t GetLastOffset() const;
private:
friend class LogScanner;
explicit ArrowRecordBatch(std::shared_ptr<arrow::RecordBatch> batch, int64_t table_id,
int64_t partition_id, int32_t bucket_id,
int64_t base_offset) noexcept;
std::shared_ptr<arrow::RecordBatch> batch_{nullptr};
int64_t table_id_;
int64_t partition_id_;
int32_t bucket_id_;
int64_t base_offset_;
};
struct ArrowRecordBatches {
std::vector<std::unique_ptr<ArrowRecordBatch>> batches;
size_t Size() const { return batches.size(); }
bool Empty() const { return batches.empty(); }
const std::unique_ptr<ArrowRecordBatch>& operator[](size_t idx) const { return batches[idx]; }
auto begin() const { return batches.begin(); }
auto end() const { return batches.end(); }
};
struct BucketOffset {
int64_t table_id;
int64_t partition_id;
int32_t bucket_id;
int64_t offset;
};
struct BucketSubscription {
int32_t bucket_id;
int64_t offset;
};
struct PartitionBucketSubscription {
int64_t partition_id;
int32_t bucket_id;
int64_t offset;
};
struct LakeSnapshot {
int64_t snapshot_id;
std::vector<BucketOffset> bucket_offsets;
};
struct PartitionInfo {
int64_t partition_id;
std::string partition_name;
};
struct ServerNode {
int32_t id;
std::string host;
uint32_t port;
std::string server_type;
std::string uid;
};
/// Descriptor for create_database (optional). Leave comment and properties empty for default.
struct DatabaseDescriptor {
std::string comment;
std::unordered_map<std::string, std::string> properties;
};
/// Metadata returned by GetDatabaseInfo.
struct DatabaseInfo {
std::string database_name;
std::string comment;
std::unordered_map<std::string, std::string> properties;
int64_t created_time{0};
int64_t modified_time{0};
};
/// Read-only result for lookup operations.
class LookupResult : public detail::NamedGetters<LookupResult> {
friend struct detail::NamedGetters<LookupResult>;
public:
LookupResult() noexcept;
~LookupResult() noexcept;
LookupResult(const LookupResult&) = delete;
LookupResult& operator=(const LookupResult&) = delete;
LookupResult(LookupResult&& other) noexcept;
LookupResult& operator=(LookupResult&& other) noexcept;
bool Found() const;
size_t FieldCount() const;
// ── Index-based getters ──────────────────────────────────────────
TypeId GetType(size_t idx) const;
bool IsNull(size_t idx) const;
bool GetBool(size_t idx) const;
int32_t GetInt32(size_t idx) const;
int64_t GetInt64(size_t idx) const;
float GetFloat32(size_t idx) const;
double GetFloat64(size_t idx) const;
std::string_view GetString(size_t idx) const;
std::pair<const uint8_t*, size_t> GetBytes(size_t idx) const;
fluss::Date GetDate(size_t idx) const;
fluss::Time GetTime(size_t idx) const;
fluss::Timestamp GetTimestamp(size_t idx) const;
bool IsDecimal(size_t idx) const;
std::string GetDecimalString(size_t idx) const;
// Name-based getters inherited from detail::NamedGetters<LookupResult>
using detail::NamedGetters<LookupResult>::IsNull;
using detail::NamedGetters<LookupResult>::GetBool;
using detail::NamedGetters<LookupResult>::GetInt32;
using detail::NamedGetters<LookupResult>::GetInt64;
using detail::NamedGetters<LookupResult>::GetFloat32;
using detail::NamedGetters<LookupResult>::GetFloat64;
using detail::NamedGetters<LookupResult>::GetString;
using detail::NamedGetters<LookupResult>::GetBytes;
using detail::NamedGetters<LookupResult>::GetDate;
using detail::NamedGetters<LookupResult>::GetTime;
using detail::NamedGetters<LookupResult>::GetTimestamp;
using detail::NamedGetters<LookupResult>::GetDecimalString;
private:
friend class Lookuper;
size_t Resolve(const std::string& name) const {
if (!column_map_) {
BuildColumnMap();
}
return detail::ResolveColumn(*column_map_, name);
}
void Destroy() noexcept;
void BuildColumnMap() const;
ffi::LookupResultInner* inner_{nullptr};
mutable std::shared_ptr<detail::ColumnMap> column_map_;
};
class AppendWriter;
class UpsertWriter;
class Lookuper;
class WriteResult;
class LogScanner;
class Admin;
class Table;
class TableAppend;
class TableUpsert;
class TableLookup;
class TableScan;
struct Configuration {
// Coordinator server address
std::string bootstrap_servers{"127.0.0.1:9123"};
// Max request size in bytes (10 MB)
int32_t writer_request_max_size{10 * 1024 * 1024};
// Writer acknowledgment mode: "all", "0", "1", or "-1"
std::string writer_acks{"all"};
// Max number of writer retries
int32_t writer_retries{std::numeric_limits<int32_t>::max()};
// Writer batch size in bytes (2 MB)
int32_t writer_batch_size{2 * 1024 * 1024};
// Bucket assigner for tables without bucket keys: "sticky" or "round_robin"
std::string writer_bucket_no_key_assigner{"sticky"};
// Number of remote log batches to prefetch during scanning
size_t scanner_remote_log_prefetch_num{4};
// Number of threads for downloading remote log data
size_t remote_file_download_thread_num{3};
// Remote log read concurrency within one file (streaming read path)
size_t scanner_remote_log_read_concurrency{4};
// Maximum number of records returned in a single call to Poll() for LogScanner
size_t scanner_log_max_poll_records{500};
int64_t writer_batch_timeout_ms{100};