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.
@@ -0,0 +1,22 @@
1
+ module Iceberg
2
+ class Result
3
+ include Enumerable
4
+
5
+ attr_reader :columns, :rows
6
+
7
+ def initialize(columns, rows)
8
+ @columns = columns
9
+ @rows = rows
10
+ end
11
+
12
+ def each
13
+ @rows.each do |row|
14
+ yield @columns.zip(row).to_h
15
+ end
16
+ end
17
+
18
+ def empty?
19
+ rows.empty?
20
+ end
21
+ end
22
+ end
@@ -1,7 +1,10 @@
1
1
  module Iceberg
2
2
  class S3TablesCatalog < Catalog
3
- def initialize(arn:)
4
- @catalog = RbCatalog.new_s3tables(arn)
3
+ def initialize(arn:, default_namespace: nil)
4
+ _initialize(
5
+ RbCatalog.new_s3tables(arn),
6
+ default_namespace:
7
+ )
5
8
  end
6
9
  end
7
10
  end
@@ -2,8 +2,11 @@ module Iceberg
2
2
  class SqlCatalog < Catalog
3
3
  # warehouse is default storage location
4
4
  # name is stored in SQL table
5
- def initialize(uri:, warehouse:, name: "main", properties: {})
6
- @catalog = RbCatalog.new_sql(uri, warehouse, name, properties)
5
+ def initialize(uri:, warehouse:, name: "main", properties: {}, default_namespace: nil)
6
+ _initialize(
7
+ RbCatalog.new_sql(uri, warehouse, name, properties),
8
+ default_namespace:
9
+ )
7
10
  end
8
11
  end
9
12
  end
data/lib/iceberg/table.rb CHANGED
@@ -5,93 +5,154 @@ module Iceberg
5
5
  @catalog = catalog
6
6
  end
7
7
 
8
+ def refresh
9
+ @table = @catalog.load_table(@table.identifier)
10
+ end
11
+
8
12
  def format_version
9
- @table.format_version
13
+ metadata.format_version
10
14
  end
11
15
 
12
16
  def uuid
13
- @table.uuid
17
+ metadata.uuid
14
18
  end
15
19
 
16
20
  def location
17
- @table.location
21
+ metadata.location
18
22
  end
19
23
 
20
24
  def last_sequence_number
21
- @table.last_sequence_number
25
+ metadata.last_sequence_number
22
26
  end
23
27
 
24
28
  def next_sequence_number
25
- @table.next_sequence_number
29
+ metadata.next_sequence_number
26
30
  end
27
31
 
28
32
  def last_column_id
29
- @table.last_column_id
33
+ metadata.last_column_id
30
34
  end
31
35
 
32
36
  def last_partition_id
33
- @table.last_partition_id
37
+ metadata.last_partition_id
38
+ end
39
+
40
+ def last_updated_at
41
+ ms = metadata.last_updated_ms
42
+ Time.at(ms / 1000, ms % 1000, :millisecond)
34
43
  end
35
44
 
36
45
  def schemas
37
- @table.schemas
46
+ metadata.schemas
38
47
  end
39
48
 
40
49
  def schema_by_id(schema_id)
41
- @table.schema_by_id(schema_id)
50
+ metadata.schema_by_id(schema_id)
42
51
  end
43
52
 
44
53
  def current_schema
45
- @table.current_schema
54
+ metadata.current_schema
46
55
  end
47
56
  alias_method :schema, :current_schema
48
57
 
49
58
  def current_schema_id
50
- @table.current_schema_id
59
+ metadata.current_schema_id
51
60
  end
52
61
  alias_method :schema_id, :current_schema_id
53
62
 
63
+ def partition_specs
64
+ metadata.partition_specs
65
+ end
66
+
67
+ def partition_spec_by_id(partition_spec_id)
68
+ metadata.partition_spec_by_id(partition_spec_id)
69
+ end
70
+
71
+ def default_partition_spec
72
+ metadata.default_partition_spec
73
+ end
74
+
54
75
  def default_partition_spec_id
55
- @table.default_partition_spec_id
76
+ metadata.default_partition_spec_id
56
77
  end
57
78
 
58
79
  def snapshots
59
- @table.snapshots
80
+ metadata.snapshots
60
81
  end
61
82
 
62
83
  def snapshot_by_id(snapshot_id)
63
- @table.snapshot_by_id(snapshot_id)
84
+ metadata.snapshot_by_id(snapshot_id)
64
85
  end
65
86
 
66
87
  def history
67
- @table.history
88
+ metadata.history
68
89
  end
69
90
 
70
91
  def metadata_log
71
- @table.metadata_log
92
+ metadata.metadata_log
72
93
  end
73
94
 
74
95
  def current_snapshot
75
- @table.current_snapshot
96
+ metadata.current_snapshot
76
97
  end
77
98
 
78
99
  def current_snapshot_id
79
- @table.current_snapshot_id
100
+ metadata.current_snapshot_id
101
+ end
102
+
103
+ def sort_orders
104
+ metadata.sort_orders
105
+ end
106
+
107
+ def default_sort_order
108
+ metadata.default_sort_order
109
+ end
110
+
111
+ def default_sort_order_id
112
+ metadata.default_sort_order_id
80
113
  end
81
114
 
82
115
  def properties
83
- @table.properties
116
+ metadata.properties
117
+ end
118
+
119
+ def statistics
120
+ metadata.statistics
121
+ end
122
+
123
+ def partition_statistics
124
+ metadata.partition_statistics
125
+ end
126
+
127
+ def statistics_for_snapshot(snapshot_id)
128
+ metadata.statistics_for_snapshot(snapshot_id)
129
+ end
130
+
131
+ def partition_statistics_for_snapshot(snapshot_id)
132
+ metadata.partition_statistics_for_snapshot(snapshot_id)
133
+ end
134
+
135
+ def encryption_keys
136
+ metadata.encryption_keys
137
+ end
138
+
139
+ def next_row_id
140
+ metadata.next_row_id
84
141
  end
85
142
 
86
143
  def scan(snapshot_id: nil)
87
144
  TableScan.new(@table.scan(snapshot_id), self)
88
145
  end
89
146
 
147
+ def to_a(snapshot_id: nil)
148
+ scan(snapshot_id: snapshot_id).to_a
149
+ end
150
+
90
151
  def to_polars(snapshot_id: nil, storage_options: nil)
91
152
  require "polars-df"
92
153
 
93
- if Gem::Version.new(Polars::VERSION) < Gem::Version.new("0.23")
94
- raise "Requires polars-df >= 0.23"
154
+ if Gem::Version.new(Polars::VERSION) < Gem::Version.new("0.26.1")
155
+ raise "Requires polars-df >= 0.26.1"
95
156
  end
96
157
 
97
158
  Polars.scan_iceberg(self, snapshot_id:, storage_options:)
@@ -99,6 +160,7 @@ module Iceberg
99
160
 
100
161
  def append(df)
101
162
  check_catalog
163
+ df = ArrowRecordBatch.new(df, schema.arrow_c_schema) if df.is_a?(Array)
102
164
  @table = @table.append(df.arrow_c_stream, @catalog)
103
165
  nil
104
166
  end
@@ -110,6 +172,10 @@ module Iceberg
110
172
 
111
173
  private
112
174
 
175
+ def metadata
176
+ @table.metadata
177
+ end
178
+
113
179
  def check_catalog
114
180
  raise Error, "Read-only table" if @catalog.nil?
115
181
  end
@@ -1,7 +1,9 @@
1
1
  module Iceberg
2
2
  class TableDefinition
3
3
  TYPES = %w[
4
- boolean int long float double date timestamp timestamptz string uuid binary
4
+ boolean int long float double decimal date time
5
+ timestamp timestamptz timestamp_nano timestamptz_nano
6
+ string uuid fixed binary
5
7
  ]
6
8
 
7
9
  TYPE_ALIASES = {
@@ -21,17 +23,57 @@ module Iceberg
21
23
  end
22
24
  end
23
25
 
24
- def column(name, type, null: true, default: nil, comment: nil)
25
- type = type.to_s
26
- @fields << {
27
- id: @fields.size + 1,
26
+ def column(name, type, null: true, default: nil, comment: nil, limit: nil, precision: nil, scale: nil)
27
+ unless type.is_a?(Type)
28
+ type = type.to_s
29
+ type =
30
+ case TYPE_ALIASES.fetch(type, type)
31
+ when "boolean"
32
+ BooleanType.new
33
+ when "int"
34
+ IntType.new
35
+ when "long"
36
+ LongType.new
37
+ when "float"
38
+ FloatType.new
39
+ when "double"
40
+ DoubleType.new
41
+ when "decimal"
42
+ DecimalType.new(precision, scale)
43
+ when "date"
44
+ DateType.new
45
+ when "time"
46
+ TimeType.new
47
+ when "timestamp"
48
+ TimestampType.new
49
+ when "timestamptz"
50
+ TimestamptzType.new
51
+ when "timestamp_nano"
52
+ TimestampNanoType.new
53
+ when "timestamptz_nano"
54
+ TimestamptzNanoType.new
55
+ when "string"
56
+ StringType.new
57
+ when "uuid"
58
+ UUIDType.new
59
+ when "fixed"
60
+ FixedType.new(limit)
61
+ when "binary"
62
+ BinaryType.new
63
+ else
64
+ type
65
+ end
66
+ end
67
+
68
+ @fields << NestedField.new(
69
+ field_id: @fields.size + 1,
28
70
  name: name.to_s,
29
- type: TYPE_ALIASES.fetch(type, type),
71
+ field_type: type,
30
72
  required: !null,
31
73
  doc: comment,
32
74
  # no need for initial default (and not supported until v3)
33
75
  write_default: default
34
- }
76
+ )
35
77
  end
36
78
  end
37
79
  end
@@ -14,5 +14,19 @@ module Iceberg
14
14
  def snapshot
15
15
  @scan.snapshot
16
16
  end
17
+
18
+ def collect
19
+ @scan.collect
20
+ end
21
+
22
+ def to_a
23
+ collect.to_a
24
+ end
25
+
26
+ def to_arrow
27
+ require "nanoarrow"
28
+
29
+ Nanoarrow::Array.new(@scan)
30
+ end
17
31
  end
18
32
  end
@@ -0,0 +1,64 @@
1
+ module Iceberg
2
+ class Transform
3
+ def ==(other)
4
+ other.is_a?(self.class)
5
+ end
6
+
7
+ def inspect
8
+ "#<#{self.class.name}>"
9
+ end
10
+ end
11
+
12
+ class IdentityTransform < Transform
13
+ end
14
+
15
+ class BucketTransform < Transform
16
+ attr_reader :num_buckets
17
+
18
+ def initialize(num_buckets)
19
+ @num_buckets = num_buckets
20
+ end
21
+
22
+ def ==(other)
23
+ other.is_a?(self.class) && other.num_buckets == @num_buckets
24
+ end
25
+
26
+ def inspect
27
+ "#<#{self.class.name} num_buckets=#{@num_buckets.inspect}>"
28
+ end
29
+ end
30
+
31
+ class TruncateTransform < Transform
32
+ attr_reader :width
33
+
34
+ def initialize(width)
35
+ @width = width
36
+ end
37
+
38
+ def ==(other)
39
+ other.is_a?(self.class) && other.width == @width
40
+ end
41
+
42
+ def inspect
43
+ "#<#{self.class.name} width=#{@width.inspect}>"
44
+ end
45
+ end
46
+
47
+ class YearTransform < Transform
48
+ end
49
+
50
+ class MonthTransform < Transform
51
+ end
52
+
53
+ class DayTransform < Transform
54
+ end
55
+
56
+ class HourTransform < Transform
57
+ end
58
+
59
+ class VoidTransform < Transform
60
+ end
61
+
62
+ class UnknownTransform < Transform
63
+ end
64
+ end
@@ -0,0 +1,137 @@
1
+ module Iceberg
2
+ class Type
3
+ def inspect
4
+ "#<#{self.class.name}>"
5
+ end
6
+ end
7
+
8
+ class PrimitiveType < Type
9
+ def ==(other)
10
+ other.is_a?(self.class)
11
+ end
12
+ end
13
+
14
+ class BooleanType < PrimitiveType
15
+ end
16
+
17
+ class IntType < PrimitiveType
18
+ end
19
+
20
+ class LongType < PrimitiveType
21
+ end
22
+
23
+ class FloatType < PrimitiveType
24
+ end
25
+
26
+ class DoubleType < PrimitiveType
27
+ end
28
+
29
+ class DecimalType < PrimitiveType
30
+ attr_reader :precision, :scale
31
+
32
+ def initialize(precision, scale)
33
+ @precision = precision
34
+ @scale = scale
35
+ end
36
+
37
+ def ==(other)
38
+ other.is_a?(self.class) && other.precision == @precision && other.scale == @scale
39
+ end
40
+
41
+ def inspect
42
+ "#<#{self.class.name} precision=#{@precision.inspect}, scale=#{@scale.inspect}>"
43
+ end
44
+ end
45
+
46
+ class DateType < PrimitiveType
47
+ end
48
+
49
+ class TimeType < PrimitiveType
50
+ end
51
+
52
+ class TimestampType < PrimitiveType
53
+ end
54
+
55
+ class TimestamptzType < PrimitiveType
56
+ end
57
+
58
+ class TimestampNanoType < PrimitiveType
59
+ end
60
+
61
+ class TimestamptzNanoType < PrimitiveType
62
+ end
63
+
64
+ class StringType < PrimitiveType
65
+ end
66
+
67
+ class UUIDType < PrimitiveType
68
+ end
69
+
70
+ class FixedType < PrimitiveType
71
+ attr_reader :length
72
+
73
+ def initialize(length)
74
+ @length = length
75
+ end
76
+
77
+ def ==(other)
78
+ other.is_a?(self.class) && other.length == @length
79
+ end
80
+
81
+ def inspect
82
+ "#<#{self.class.name} length=#{@length.inspect}>"
83
+ end
84
+ end
85
+
86
+ class BinaryType < PrimitiveType
87
+ end
88
+
89
+ class StructType < Type
90
+ attr_reader :fields
91
+
92
+ def initialize(*fields)
93
+ @fields = fields
94
+ end
95
+
96
+ def ==(other)
97
+ other.is_a?(self.class) && other.fields == @fields
98
+ end
99
+
100
+ def inspect
101
+ "#<#{self.class.name} fields=#{@fields.inspect}>"
102
+ end
103
+ end
104
+
105
+ class ListType < Type
106
+ attr_reader :element_field
107
+
108
+ def initialize(element_field)
109
+ @element_field = element_field
110
+ end
111
+
112
+ def ==(other)
113
+ other.is_a?(self.class) && other.element_field == @element_field
114
+ end
115
+
116
+ def inspect
117
+ "#<#{self.class.name} element_field=#{@element_field.inspect}>"
118
+ end
119
+ end
120
+
121
+ class MapType < Type
122
+ attr_reader :key_field, :value_field
123
+
124
+ def initialize(key_field, value_field)
125
+ @key_field = key_field
126
+ @value_field = value_field
127
+ end
128
+
129
+ def ==(other)
130
+ other.is_a?(self.class) && other.key_field == @key_field && other.value_field == @value_field
131
+ end
132
+
133
+ def inspect
134
+ "#<#{self.class.name} key_field=#{@key_field.inspect}, value_field=#{@value_field.inspect}>"
135
+ end
136
+ end
137
+ end
@@ -1,3 +1,3 @@
1
1
  module Iceberg
2
- VERSION = "0.11.2"
2
+ VERSION = "0.12.0"
3
3
  end
data/lib/iceberg.rb CHANGED
@@ -5,13 +5,18 @@ rescue LoadError
5
5
  require "iceberg/iceberg"
6
6
  end
7
7
 
8
+ # stdlib
9
+ require "date"
10
+
8
11
  # modules
9
12
  require_relative "iceberg/catalog"
10
- require_relative "iceberg/schema"
13
+ require_relative "iceberg/result"
11
14
  require_relative "iceberg/table"
12
15
  require_relative "iceberg/table_scan"
13
16
  require_relative "iceberg/static_table"
14
17
  require_relative "iceberg/table_definition"
18
+ require_relative "iceberg/transforms"
19
+ require_relative "iceberg/types"
15
20
  require_relative "iceberg/version"
16
21
 
17
22
  # catalogs
@@ -25,14 +30,14 @@ module Iceberg
25
30
  class Error < StandardError; end
26
31
  class InvalidDataError < Error; end
27
32
  class NamespaceAlreadyExistsError < Error; end
28
- class NamespaceNotFoundError < Error; end
33
+ class NoSuchNamespaceError < Error; end
34
+ class NoSuchTableError < Error; end
29
35
  class TableAlreadyExistsError < Error; end
30
- class TableNotFoundError < Error; end
31
36
  class UnsupportedFeatureError < Error; end
32
37
 
33
38
  class Todo < Error
34
- def message
35
- "not implemented yet"
39
+ def initialize(message = "not implemented yet")
40
+ super(message)
36
41
  end
37
42
  end
38
43
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iceberg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.2
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
@@ -38,12 +38,21 @@ files:
38
38
  - ext/iceberg/Cargo.toml
39
39
  - ext/iceberg/extconf.rb
40
40
  - ext/iceberg/src/arrow.rs
41
+ - ext/iceberg/src/batch.rs
42
+ - ext/iceberg/src/capsule.rs
41
43
  - ext/iceberg/src/catalog.rs
44
+ - ext/iceberg/src/encryption.rs
42
45
  - ext/iceberg/src/error.rs
43
46
  - ext/iceberg/src/lib.rs
47
+ - ext/iceberg/src/partitioning.rs
48
+ - ext/iceberg/src/result.rs
44
49
  - ext/iceberg/src/ruby.rs
45
50
  - ext/iceberg/src/runtime.rs
46
51
  - ext/iceberg/src/scan.rs
52
+ - ext/iceberg/src/schema.rs
53
+ - ext/iceberg/src/snapshot.rs
54
+ - ext/iceberg/src/sorting.rs
55
+ - ext/iceberg/src/statistics.rs
47
56
  - ext/iceberg/src/table.rs
48
57
  - ext/iceberg/src/utils.rs
49
58
  - lib/iceberg.rb
@@ -51,13 +60,15 @@ files:
51
60
  - lib/iceberg/glue_catalog.rb
52
61
  - lib/iceberg/memory_catalog.rb
53
62
  - lib/iceberg/rest_catalog.rb
63
+ - lib/iceberg/result.rb
54
64
  - lib/iceberg/s3_tables_catalog.rb
55
- - lib/iceberg/schema.rb
56
65
  - lib/iceberg/sql_catalog.rb
57
66
  - lib/iceberg/static_table.rb
58
67
  - lib/iceberg/table.rb
59
68
  - lib/iceberg/table_definition.rb
60
69
  - lib/iceberg/table_scan.rb
70
+ - lib/iceberg/transforms.rb
71
+ - lib/iceberg/types.rb
61
72
  - lib/iceberg/version.rb
62
73
  homepage: https://github.com/ankane/iceberg-ruby
63
74
  licenses:
@@ -77,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
77
88
  - !ruby/object:Gem::Version
78
89
  version: '0'
79
90
  requirements: []
80
- rubygems_version: 4.0.10
91
+ rubygems_version: 4.0.14
81
92
  specification_version: 4
82
93
  summary: Apache Iceberg for Ruby
83
94
  test_files: []
@@ -1,10 +0,0 @@
1
- module Iceberg
2
- class Schema
3
- attr_reader :fields, :schema_id
4
-
5
- def initialize(fields, schema_id: nil)
6
- @fields = fields
7
- @schema_id = schema_id
8
- end
9
- end
10
- end