iceberg 0.11.2 → 0.12.0

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.
@@ -26,8 +26,12 @@ pub fn runtime() -> Handle {
26
26
  match Handle::try_current() {
27
27
  Ok(h) => h.clone(),
28
28
  _ => {
29
- let rt = RUNTIME.get_or_init(|| Runtime::new().unwrap());
29
+ let rt = tokio_runtime();
30
30
  rt.handle().clone()
31
31
  }
32
32
  }
33
33
  }
34
+
35
+ pub fn tokio_runtime() -> &'static Runtime {
36
+ RUNTIME.get_or_init(|| Runtime::new().unwrap())
37
+ }
@@ -1,19 +1,37 @@
1
- use futures::TryStreamExt;
2
- use iceberg::scan::TableScan;
3
- use magnus::{RArray, Ruby, Value};
4
1
  use std::sync::RwLock;
5
2
 
3
+ use arrow_array::RecordBatchIterator;
4
+ use arrow_array::ffi_stream::FFI_ArrowArrayStream;
5
+ use futures::TryStreamExt;
6
+ use iceberg::scan::{FileScanTask, TableScan};
7
+ use magnus::{IntoValue, RArray, Ruby, Value, value::ReprValue};
8
+
6
9
  use crate::RbResult;
10
+ use crate::capsule::RbCapsule;
7
11
  use crate::error::to_rb_err;
12
+ use crate::result::collect_batches;
8
13
  use crate::ruby::GvlExt;
9
14
  use crate::runtime::runtime;
10
- use crate::utils::rb_snapshot;
15
+ use crate::snapshot::RbSnapshot;
11
16
 
12
17
  #[magnus::wrap(class = "Iceberg::RbTableScan")]
13
18
  pub struct RbTableScan {
14
19
  pub scan: RwLock<TableScan>,
15
20
  }
16
21
 
22
+ #[magnus::wrap(class = "Iceberg::FileScanTask")]
23
+ pub struct RbFileScanTask {
24
+ pub scan: FileScanTask,
25
+ }
26
+
27
+ #[magnus::wrap(class = "Iceberg::DataFile")]
28
+ pub struct RbDataFile {
29
+ pub file_path: String,
30
+ pub record_count: Option<u64>,
31
+ pub file_size_in_bytes: u64,
32
+ pub equality_ids: Option<Vec<i32>>,
33
+ }
34
+
17
35
  impl RbTableScan {
18
36
  pub fn plan_files(ruby: &Ruby, rb_self: &Self) -> RbResult<RArray> {
19
37
  let plan_files: Vec<_> = ruby
@@ -27,32 +45,94 @@ impl RbTableScan {
27
45
 
28
46
  let files = ruby.ary_new();
29
47
  for v in plan_files {
30
- let file = ruby.hash_new();
31
- file.aset(ruby.to_symbol("start"), v.start)?;
32
- file.aset(ruby.to_symbol("length"), v.length)?;
33
- file.aset(ruby.to_symbol("record_count"), v.record_count)?;
34
- file.aset(ruby.to_symbol("data_file_path"), v.data_file_path)?;
35
- file.aset(ruby.to_symbol("project_field_ids"), v.project_field_ids)?;
36
-
37
- let deletes = ruby.ary_new();
38
- for d in v.deletes {
39
- let delete = ruby.hash_new();
40
- delete.aset(ruby.to_symbol("file_path"), d.file_path)?;
41
- delete.aset(ruby.to_symbol("partition_spec_id"), d.partition_spec_id)?;
42
- delete.aset(ruby.to_symbol("equality_ids"), d.equality_ids)?;
43
- deletes.push(delete)?;
44
- }
45
- file.aset(ruby.to_symbol("deletes"), deletes)?;
46
-
47
- files.push(file)?;
48
+ files.push(RbFileScanTask { scan: v })?;
48
49
  }
49
50
  Ok(files)
50
51
  }
51
52
 
52
- pub fn snapshot(ruby: &Ruby, rb_self: &Self) -> RbResult<Option<Value>> {
53
- match rb_self.scan.read().unwrap().snapshot() {
54
- Some(s) => Ok(Some(rb_snapshot(ruby, s)?)),
55
- None => Ok(None),
53
+ pub fn snapshot(&self) -> Option<RbSnapshot> {
54
+ self.scan.read().unwrap().snapshot().map(|v| v.into())
55
+ }
56
+
57
+ pub fn collect(ruby: &Ruby, rb_self: &Self) -> RbResult<Value> {
58
+ let runtime = runtime();
59
+ let scan = rb_self.scan.read().unwrap();
60
+ let stream = runtime.block_on(scan.to_arrow()).map_err(to_rb_err)?;
61
+ let batches: Vec<_> = runtime.block_on(stream.try_collect()).map_err(to_rb_err)?;
62
+ collect_batches(ruby, batches)
63
+ }
64
+
65
+ pub fn arrow_c_stream(&self) -> RbResult<RbCapsule> {
66
+ let runtime = runtime();
67
+ let scan = self.scan.read().unwrap();
68
+ let stream = runtime.block_on(scan.to_arrow()).map_err(to_rb_err)?;
69
+ let batches: Vec<_> = runtime.block_on(stream.try_collect()).map_err(to_rb_err)?;
70
+ let stream = if batches.is_empty() {
71
+ // TODO fix schema
72
+ FFI_ArrowArrayStream::empty()
73
+ } else {
74
+ let schema = batches[0].schema();
75
+ let reader = RecordBatchIterator::new(batches.into_iter().map(Ok), schema);
76
+ FFI_ArrowArrayStream::new(Box::new(reader))
77
+ };
78
+ Ok(RbCapsule::new(stream, Some("arrow_array_stream".into())))
79
+ }
80
+ }
81
+
82
+ impl RbFileScanTask {
83
+ pub fn file(&self) -> RbDataFile {
84
+ RbDataFile {
85
+ file_path: self.scan.data_file_path.clone(),
86
+ record_count: self.scan.record_count,
87
+ file_size_in_bytes: self.scan.file_size_in_bytes,
88
+ equality_ids: None,
56
89
  }
57
90
  }
91
+
92
+ pub fn delete_files(ruby: &Ruby, rb_self: &Self) -> RArray {
93
+ ruby.ary_from_iter(rb_self.scan.deletes.iter().map(|v| RbDataFile {
94
+ file_path: v.file_path.clone(),
95
+ record_count: None,
96
+ file_size_in_bytes: v.file_size_in_bytes,
97
+ equality_ids: v.equality_ids.clone(),
98
+ }))
99
+ }
100
+
101
+ pub fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
102
+ format!(
103
+ "#<Iceberg::FileScanTask file={}, delete_files={}>",
104
+ rb_self.file().into_value_with(ruby).inspect(),
105
+ Self::delete_files(ruby, rb_self)
106
+ .into_value_with(ruby)
107
+ .inspect(),
108
+ )
109
+ }
110
+ }
111
+
112
+ impl RbDataFile {
113
+ pub fn file_path(&self) -> &str {
114
+ &self.file_path
115
+ }
116
+
117
+ pub fn record_count(&self) -> Option<u64> {
118
+ self.record_count
119
+ }
120
+
121
+ pub fn file_size_in_bytes(&self) -> u64 {
122
+ self.file_size_in_bytes
123
+ }
124
+
125
+ pub fn equality_ids(&self) -> Option<Vec<i32>> {
126
+ self.equality_ids.clone()
127
+ }
128
+
129
+ pub fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
130
+ format!(
131
+ "#<Iceberg::DataFile file_path={}, record_count={}, file_size_in_bytes={}, equality_ids={}>",
132
+ rb_self.file_path().into_value_with(ruby).inspect(),
133
+ rb_self.record_count().into_value_with(ruby).inspect(),
134
+ rb_self.file_size_in_bytes().into_value_with(ruby).inspect(),
135
+ rb_self.equality_ids().into_value_with(ruby).inspect(),
136
+ )
137
+ }
58
138
  }
@@ -0,0 +1,305 @@
1
+ use std::sync::Arc;
2
+
3
+ use arrow_schema::Schema as ArrowSchema;
4
+ use arrow_schema::ffi::FFI_ArrowSchema;
5
+ use iceberg::arrow::{arrow_schema_to_schema_auto_assign_ids, schema_to_arrow_schema};
6
+ use iceberg::spec::{ListType, MapType, NestedField, PrimitiveType, Schema, StructType, Type};
7
+ use magnus::{
8
+ Error as RbErr, IntoValue, RArray, RClass, RHash, RModule, Ruby, TryConvert, Value, prelude::*,
9
+ };
10
+
11
+ use crate::RbResult;
12
+ use crate::capsule::RbCapsule;
13
+ use crate::error::to_rb_err;
14
+ use crate::utils::{Wrap, default_value, rb_literal};
15
+
16
+ #[magnus::wrap(class = "Iceberg::Schema")]
17
+ pub struct RbSchema {
18
+ pub(crate) schema: Schema,
19
+ }
20
+
21
+ #[magnus::wrap(class = "Iceberg::NestedField")]
22
+ pub struct RbNestedField {
23
+ pub(crate) field: Arc<NestedField>,
24
+ }
25
+
26
+ impl RbSchema {
27
+ pub fn new(args: &[Value]) -> RbResult<Self> {
28
+ let schema = if args.len() == 1
29
+ && let Ok(arrow_schema) =
30
+ args[0].funcall::<_, _, Wrap<ArrowSchema>>("arrow_c_schema", ())
31
+ {
32
+ arrow_schema_to_schema_auto_assign_ids(&arrow_schema.0).map_err(to_rb_err)?
33
+ } else {
34
+ let mut fields = Vec::new();
35
+ for rb_field in args {
36
+ fields.push(<&RbNestedField>::try_convert(*rb_field)?.field.clone());
37
+ }
38
+ Schema::builder()
39
+ .with_fields(fields)
40
+ .build()
41
+ .map_err(to_rb_err)?
42
+ };
43
+ Ok(Self { schema })
44
+ }
45
+
46
+ pub fn fields(ruby: &Ruby, rb_self: &Self) -> RArray {
47
+ ruby.ary_from_iter(
48
+ rb_self
49
+ .schema
50
+ .as_struct()
51
+ .fields()
52
+ .iter()
53
+ .map(RbNestedField::from),
54
+ )
55
+ }
56
+
57
+ pub fn schema_id(&self) -> i32 {
58
+ self.schema.schema_id()
59
+ }
60
+
61
+ pub fn identifier_field_ids(&self) -> Vec<i32> {
62
+ self.schema.identifier_field_ids().collect()
63
+ }
64
+
65
+ pub fn highest_field_id(&self) -> i32 {
66
+ self.schema.highest_field_id()
67
+ }
68
+
69
+ pub fn as_struct(ruby: &Ruby, rb_self: &Self) -> RbResult<Value> {
70
+ let iceberg = ruby.class_object().const_get::<_, RModule>("Iceberg")?;
71
+ let fields = Self::fields(ruby, rb_self);
72
+ iceberg
73
+ .const_get::<_, RClass>("StructType")?
74
+ .new_instance(unsafe { fields.as_slice() })
75
+ }
76
+
77
+ pub fn arrow_c_schema(&self) -> RbResult<RbCapsule> {
78
+ let schema = schema_to_arrow_schema(&self.schema).map_err(to_rb_err)?;
79
+ let schema = FFI_ArrowSchema::try_from(&schema).unwrap();
80
+ Ok(RbCapsule::new(schema, Some("arrow_schema".into())))
81
+ }
82
+
83
+ pub fn eq(&self, other: &Self) -> bool {
84
+ self.schema == other.schema
85
+ }
86
+
87
+ pub fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
88
+ format!(
89
+ "#<Iceberg::Schema fields={}, schema_id={}, identifier_field_ids={}>",
90
+ Self::fields(ruby, rb_self).inspect(),
91
+ rb_self.schema_id().into_value_with(ruby).inspect(),
92
+ rb_self
93
+ .identifier_field_ids()
94
+ .into_value_with(ruby)
95
+ .inspect(),
96
+ )
97
+ }
98
+ }
99
+
100
+ impl RbNestedField {
101
+ pub fn new(ruby: &Ruby, rb_field: RHash) -> RbResult<Self> {
102
+ let rb_type: Value = rb_field.aref(ruby.to_symbol("field_type"))?;
103
+ let field_type = match &*unsafe { rb_type.classname() } {
104
+ "Iceberg::BooleanType" => Type::Primitive(PrimitiveType::Boolean),
105
+ "Iceberg::IntType" => Type::Primitive(PrimitiveType::Int),
106
+ "Iceberg::LongType" => Type::Primitive(PrimitiveType::Long),
107
+ "Iceberg::FloatType" => Type::Primitive(PrimitiveType::Float),
108
+ "Iceberg::DoubleType" => Type::Primitive(PrimitiveType::Double),
109
+ "Iceberg::DecimalType" => {
110
+ let precision: u32 = rb_type.funcall("precision", ())?;
111
+ let scale: u32 = rb_type.funcall("scale", ())?;
112
+ Type::Primitive(PrimitiveType::Decimal { precision, scale })
113
+ }
114
+ "Iceberg::DateType" => Type::Primitive(PrimitiveType::Date),
115
+ "Iceberg::TimeType" => Type::Primitive(PrimitiveType::Time),
116
+ "Iceberg::TimestampType" => Type::Primitive(PrimitiveType::Timestamp),
117
+ "Iceberg::TimestamptzType" => Type::Primitive(PrimitiveType::Timestamptz),
118
+ "Iceberg::TimestampNanoType" => Type::Primitive(PrimitiveType::TimestampNs),
119
+ "Iceberg::TimestamptzNanoType" => Type::Primitive(PrimitiveType::TimestamptzNs),
120
+ "Iceberg::StringType" => Type::Primitive(PrimitiveType::String),
121
+ "Iceberg::UUIDType" => Type::Primitive(PrimitiveType::Uuid),
122
+ "Iceberg::FixedType" => {
123
+ let length: u64 = rb_type.funcall("length", ())?;
124
+ Type::Primitive(PrimitiveType::Fixed(length))
125
+ }
126
+ "Iceberg::BinaryType" => Type::Primitive(PrimitiveType::Binary),
127
+ "Iceberg::StructType" => {
128
+ let fields = rb_type
129
+ .funcall::<_, _, RArray>("fields", ())?
130
+ .typecheck::<&RbNestedField>()?
131
+ .into_iter()
132
+ .map(|v| v.field.clone())
133
+ .collect::<Vec<_>>();
134
+ Type::Struct(StructType::new(fields))
135
+ }
136
+ "Iceberg::ListType" => {
137
+ let element_field = rb_type
138
+ .funcall::<_, _, &RbNestedField>("element_field", ())?
139
+ .field
140
+ .clone();
141
+ Type::List(ListType::new(element_field))
142
+ }
143
+ "Iceberg::MapType" => {
144
+ let key_field = rb_type
145
+ .funcall::<_, _, &RbNestedField>("key_field", ())?
146
+ .field
147
+ .clone();
148
+ let value_field = rb_type
149
+ .funcall::<_, _, &RbNestedField>("value_field", ())?
150
+ .field
151
+ .clone();
152
+ Type::Map(MapType::new(key_field, value_field))
153
+ }
154
+ _ => {
155
+ return Err(RbErr::new(
156
+ ruby.exception_arg_error(),
157
+ format!("Type not supported: {}", rb_type),
158
+ ));
159
+ }
160
+ };
161
+
162
+ let initial_default = rb_field.aref(ruby.to_symbol("initial_default"))?;
163
+ let write_default = rb_field.aref(ruby.to_symbol("write_default"))?;
164
+
165
+ let initial_default = default_value(initial_default, &field_type)?;
166
+ let write_default = default_value(write_default, &field_type)?;
167
+
168
+ let field = NestedField {
169
+ id: rb_field.aref(ruby.to_symbol("field_id"))?,
170
+ name: rb_field.aref(ruby.to_symbol("name"))?,
171
+ required: rb_field.aref(ruby.to_symbol("required"))?,
172
+ field_type: field_type.into(),
173
+ doc: rb_field.aref(ruby.to_symbol("doc"))?,
174
+ initial_default,
175
+ write_default,
176
+ };
177
+
178
+ Ok(Self {
179
+ field: field.into(),
180
+ })
181
+ }
182
+
183
+ pub fn field_id(&self) -> i32 {
184
+ self.field.id
185
+ }
186
+
187
+ pub fn name(&self) -> &str {
188
+ &self.field.name
189
+ }
190
+
191
+ pub fn field_type(ruby: &Ruby, rb_self: &Self) -> RbResult<Value> {
192
+ let iceberg = ruby.class_object().const_get::<_, RModule>("Iceberg")?;
193
+ let field_type = &*rb_self.field.field_type;
194
+ let v = match field_type {
195
+ Type::Primitive(ty) => match ty {
196
+ PrimitiveType::Boolean => iceberg
197
+ .const_get::<_, RClass>("BooleanType")?
198
+ .new_instance(())?,
199
+ PrimitiveType::Int => iceberg
200
+ .const_get::<_, RClass>("IntType")?
201
+ .new_instance(())?,
202
+ PrimitiveType::Long => iceberg
203
+ .const_get::<_, RClass>("LongType")?
204
+ .new_instance(())?,
205
+ PrimitiveType::Float => iceberg
206
+ .const_get::<_, RClass>("FloatType")?
207
+ .new_instance(())?,
208
+ PrimitiveType::Double => iceberg
209
+ .const_get::<_, RClass>("DoubleType")?
210
+ .new_instance(())?,
211
+ PrimitiveType::Decimal { precision, scale } => iceberg
212
+ .const_get::<_, RClass>("DecimalType")?
213
+ .new_instance((*precision, *scale))?,
214
+ PrimitiveType::Date => iceberg
215
+ .const_get::<_, RClass>("DateType")?
216
+ .new_instance(())?,
217
+ PrimitiveType::Time => iceberg
218
+ .const_get::<_, RClass>("TimeType")?
219
+ .new_instance(())?,
220
+ PrimitiveType::Timestamp => iceberg
221
+ .const_get::<_, RClass>("TimestampType")?
222
+ .new_instance(())?,
223
+ PrimitiveType::Timestamptz => iceberg
224
+ .const_get::<_, RClass>("TimestamptzType")?
225
+ .new_instance(())?,
226
+ PrimitiveType::TimestampNs => iceberg
227
+ .const_get::<_, RClass>("TimestampNanoType")?
228
+ .new_instance(())?,
229
+ PrimitiveType::TimestamptzNs => iceberg
230
+ .const_get::<_, RClass>("TimestamptzNanoType")?
231
+ .new_instance(())?,
232
+ PrimitiveType::String => iceberg
233
+ .const_get::<_, RClass>("StringType")?
234
+ .new_instance(())?,
235
+ PrimitiveType::Uuid => iceberg
236
+ .const_get::<_, RClass>("UUIDType")?
237
+ .new_instance(())?,
238
+ PrimitiveType::Fixed(length) => iceberg
239
+ .const_get::<_, RClass>("FixedType")?
240
+ .new_instance((*length,))?,
241
+ PrimitiveType::Binary => iceberg
242
+ .const_get::<_, RClass>("BinaryType")?
243
+ .new_instance(())?,
244
+ },
245
+ Type::Struct(ty) => iceberg
246
+ .const_get::<_, RClass>("StructType")?
247
+ .new_instance((ruby.ary_from_iter(ty.fields().iter().map(RbNestedField::from)),))?,
248
+ Type::List(ty) => iceberg
249
+ .const_get::<_, RClass>("ListType")?
250
+ .new_instance((RbNestedField::from(&ty.element_field),))?,
251
+ Type::Map(ty) => iceberg.const_get::<_, RClass>("MapType")?.new_instance((
252
+ RbNestedField::from(&ty.key_field),
253
+ RbNestedField::from(&ty.value_field),
254
+ ))?,
255
+ };
256
+ Ok(v)
257
+ }
258
+
259
+ pub fn required(&self) -> bool {
260
+ self.field.required
261
+ }
262
+
263
+ pub fn doc(&self) -> Option<&str> {
264
+ self.field.doc.as_deref()
265
+ }
266
+
267
+ pub fn initial_default(ruby: &Ruby, rb_self: &Self) -> RbResult<Option<Value>> {
268
+ rb_self
269
+ .field
270
+ .initial_default
271
+ .as_ref()
272
+ .map(|v| rb_literal(ruby, v))
273
+ .transpose()
274
+ }
275
+
276
+ pub fn write_default(ruby: &Ruby, rb_self: &Self) -> RbResult<Option<Value>> {
277
+ rb_self
278
+ .field
279
+ .write_default
280
+ .as_ref()
281
+ .map(|v| rb_literal(ruby, v))
282
+ .transpose()
283
+ }
284
+
285
+ pub fn eq(&self, other: &Self) -> bool {
286
+ self.field == other.field
287
+ }
288
+
289
+ pub fn inspect(ruby: &Ruby, rb_self: &Self) -> RbResult<String> {
290
+ Ok(format!(
291
+ "#<Iceberg::NestedField field_id={}, name={}, field_type={}, required={}, doc={}, initial_default={}, write_default={}>",
292
+ rb_self.field_id().into_value_with(ruby).inspect(),
293
+ rb_self.name().into_value_with(ruby).inspect(),
294
+ Self::field_type(ruby, rb_self)?.inspect(),
295
+ rb_self.required().into_value_with(ruby).inspect(),
296
+ rb_self.doc().into_value_with(ruby).inspect(),
297
+ Self::initial_default(ruby, rb_self)?
298
+ .into_value_with(ruby)
299
+ .inspect(),
300
+ Self::write_default(ruby, rb_self)?
301
+ .into_value_with(ruby)
302
+ .inspect(),
303
+ ))
304
+ }
305
+ }
@@ -0,0 +1,85 @@
1
+ use iceberg::spec::{MetadataLog, Snapshot, SnapshotLog};
2
+ use magnus::{IntoValue, Ruby, value::ReprValue};
3
+
4
+ #[magnus::wrap(class = "Iceberg::Snapshot")]
5
+ pub struct RbSnapshot {
6
+ pub(crate) snapshot: Snapshot,
7
+ }
8
+
9
+ #[magnus::wrap(class = "Iceberg::SnapshotLogEntry")]
10
+ pub struct RbSnapshotLogEntry {
11
+ pub(crate) log: SnapshotLog,
12
+ }
13
+
14
+ #[magnus::wrap(class = "Iceberg::MetadataLogEntry")]
15
+ pub struct RbMetadataLogEntry {
16
+ pub(crate) log: MetadataLog,
17
+ }
18
+
19
+ impl RbSnapshot {
20
+ pub fn snapshot_id(&self) -> i64 {
21
+ self.snapshot.snapshot_id()
22
+ }
23
+
24
+ pub fn parent_snapshot_id(&self) -> Option<i64> {
25
+ self.snapshot.parent_snapshot_id()
26
+ }
27
+
28
+ pub fn sequence_number(&self) -> i64 {
29
+ self.snapshot.sequence_number()
30
+ }
31
+
32
+ pub fn manifest_list(&self) -> &str {
33
+ self.snapshot.manifest_list()
34
+ }
35
+
36
+ pub fn schema_id(&self) -> Option<i32> {
37
+ self.snapshot.schema_id()
38
+ }
39
+
40
+ pub fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
41
+ format!(
42
+ "#<Iceberg::Snapshot snapshot_id={}, parent_snapshot_id={}, sequence_number={}, schema_id={}>",
43
+ rb_self.snapshot_id().into_value_with(ruby).inspect(),
44
+ rb_self.parent_snapshot_id().into_value_with(ruby).inspect(),
45
+ rb_self.sequence_number().into_value_with(ruby).inspect(),
46
+ rb_self.schema_id().into_value_with(ruby).inspect(),
47
+ )
48
+ }
49
+ }
50
+
51
+ impl RbSnapshotLogEntry {
52
+ pub fn snapshot_id(&self) -> i64 {
53
+ self.log.snapshot_id
54
+ }
55
+
56
+ pub fn timestamp_ms(&self) -> i64 {
57
+ self.log.timestamp_ms
58
+ }
59
+
60
+ pub fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
61
+ format!(
62
+ "#<Iceberg::SnapshotLogEntry snapshot_id={}, timestamp_ms={}>",
63
+ rb_self.snapshot_id().into_value_with(ruby).inspect(),
64
+ rb_self.timestamp_ms().into_value_with(ruby).inspect(),
65
+ )
66
+ }
67
+ }
68
+
69
+ impl RbMetadataLogEntry {
70
+ pub fn metadata_file(&self) -> &str {
71
+ &self.log.metadata_file
72
+ }
73
+
74
+ pub fn timestamp_ms(&self) -> i64 {
75
+ self.log.timestamp_ms
76
+ }
77
+
78
+ pub fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
79
+ format!(
80
+ "#<Iceberg::MetadataLogEntry metadata_file={}, timestamp_ms={}>",
81
+ rb_self.metadata_file().into_value_with(ruby).inspect(),
82
+ rb_self.timestamp_ms().into_value_with(ruby).inspect(),
83
+ )
84
+ }
85
+ }
@@ -0,0 +1,122 @@
1
+ use iceberg::spec::{NullOrder, SortDirection, SortField, SortOrder, Transform};
2
+ use magnus::{IntoValue, RArray, RHash, Ruby, TryConvert, Value, value::ReprValue};
3
+
4
+ use crate::RbResult;
5
+ use crate::error::to_rb_err;
6
+ use crate::utils::{Wrap, rb_transform};
7
+
8
+ #[magnus::wrap(class = "Iceberg::SortOrder")]
9
+ pub struct RbSortOrder {
10
+ pub(crate) order: SortOrder,
11
+ }
12
+
13
+ #[magnus::wrap(class = "Iceberg::SortField")]
14
+ pub struct RbSortField {
15
+ pub(crate) field: SortField,
16
+ }
17
+
18
+ impl RbSortOrder {
19
+ pub fn new(args: &[Value]) -> RbResult<Self> {
20
+ let mut fields = Vec::new();
21
+ for v in args {
22
+ fields.push(<&RbSortField>::try_convert(*v)?.field.clone());
23
+ }
24
+ let order = SortOrder::builder()
25
+ .with_fields(fields)
26
+ .build_unbound()
27
+ .map_err(to_rb_err)?;
28
+ Ok(Self { order })
29
+ }
30
+
31
+ pub fn order_id(&self) -> i64 {
32
+ self.order.order_id
33
+ }
34
+
35
+ pub fn fields(ruby: &Ruby, rb_self: &Self) -> RArray {
36
+ ruby.ary_from_iter(
37
+ rb_self
38
+ .order
39
+ .fields
40
+ .iter()
41
+ .map(|v| RbSortField { field: v.clone() }),
42
+ )
43
+ }
44
+
45
+ pub fn eq(&self, other: &Self) -> bool {
46
+ self.order == other.order
47
+ }
48
+
49
+ pub fn inspect(ruby: &Ruby, rb_self: &Self) -> String {
50
+ format!(
51
+ "#<Iceberg::SortOrder order_id={}, fields={}>",
52
+ rb_self.order_id().into_value_with(ruby).inspect(),
53
+ Self::fields(ruby, rb_self).inspect(),
54
+ )
55
+ }
56
+ }
57
+
58
+ impl RbSortField {
59
+ pub fn new(ruby: &Ruby, ob: RHash) -> RbResult<Self> {
60
+ let transform = ob
61
+ .aref::<_, Wrap<Transform>>(ruby.to_symbol("transform"))?
62
+ .0;
63
+
64
+ let direction = ob
65
+ .aref::<_, Option<Wrap<SortDirection>>>(ruby.to_symbol("direction"))?
66
+ .map(|v| v.0)
67
+ .unwrap_or(SortDirection::Ascending);
68
+
69
+ let null_order = ob
70
+ .aref::<_, Option<Wrap<NullOrder>>>(ruby.to_symbol("null_order"))?
71
+ .map(|v| v.0)
72
+ .unwrap_or(if direction == SortDirection::Ascending {
73
+ NullOrder::First
74
+ } else {
75
+ NullOrder::Last
76
+ });
77
+
78
+ let field = SortField::builder()
79
+ .source_id(ob.aref(ruby.to_symbol("source_id"))?)
80
+ .transform(transform)
81
+ .direction(direction)
82
+ .null_order(null_order)
83
+ .build();
84
+ Ok(Self { field })
85
+ }
86
+
87
+ pub fn source_id(&self) -> i32 {
88
+ self.field.source_id
89
+ }
90
+
91
+ pub fn transform(&self) -> RbResult<Value> {
92
+ rb_transform(&self.field.transform)
93
+ }
94
+
95
+ pub fn direction(&self) -> &str {
96
+ match self.field.direction {
97
+ SortDirection::Ascending => "asc",
98
+ SortDirection::Descending => "desc",
99
+ }
100
+ }
101
+
102
+ pub fn null_order(&self) -> &str {
103
+ match self.field.null_order {
104
+ NullOrder::First => "first",
105
+ NullOrder::Last => "last",
106
+ }
107
+ }
108
+
109
+ pub fn eq(&self, other: &Self) -> bool {
110
+ self.field == other.field
111
+ }
112
+
113
+ pub fn inspect(ruby: &Ruby, rb_self: &Self) -> RbResult<String> {
114
+ Ok(format!(
115
+ "#<Iceberg::SortField source_id={}, transform={}, direction={}, null_order={}>",
116
+ rb_self.source_id().into_value_with(ruby).inspect(),
117
+ rb_self.transform()?.into_value_with(ruby).inspect(),
118
+ rb_self.direction().into_value_with(ruby).inspect(),
119
+ rb_self.null_order().into_value_with(ruby).inspect(),
120
+ ))
121
+ }
122
+ }