iceberg 0.11.2-aarch64-linux → 0.12.1-aarch64-linux

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.
data/NOTICE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  Apache Iceberg
3
- Copyright 2017-2025 The Apache Software Foundation
3
+ Copyright 2017-2026 The Apache Software Foundation
4
4
 
5
5
  This product includes software developed at
6
6
  The Apache Software Foundation (http://www.apache.org/).
@@ -8,7 +8,7 @@ The Apache Software Foundation (http://www.apache.org/).
8
8
  --------------------------------------------------------------------------------
9
9
 
10
10
  Apache Iceberg Rust
11
- Copyright 2023-2024 The Apache Software Foundation
11
+ Copyright 2023-2026 The Apache Software Foundation
12
12
 
13
13
  This product includes software developed at
14
14
  The Apache Software Foundation (http://www.apache.org/).
data/README.md CHANGED
@@ -19,10 +19,10 @@ gem "iceberg"
19
19
  Create a client for an Iceberg catalog
20
20
 
21
21
  ```ruby
22
- catalog = Iceberg::RestCatalog.new(uri: "http://localhost:8181")
22
+ catalog = Iceberg::RestCatalog.new(uri: "http://localhost:8181", default_namespace: "main")
23
23
  ```
24
24
 
25
- Create a namespace
25
+ Create a namespace if needed
26
26
 
27
27
  ```ruby
28
28
  catalog.create_namespace("main")
@@ -31,30 +31,53 @@ catalog.create_namespace("main")
31
31
  Create a table
32
32
 
33
33
  ```ruby
34
- catalog.create_table("main.events") do |t|
35
- t.bigint "id"
36
- t.float "value"
37
- end
34
+ table =
35
+ catalog.create_table("events") do |t|
36
+ t.bigint "id"
37
+ t.float "value"
38
+ end
39
+ ```
40
+
41
+ Append data
42
+
43
+ ```ruby
44
+ table.append([{"id" => 1, "value" => 3.0}, {"id" => 2, "value" => 4.0}])
38
45
  ```
39
46
 
40
47
  Or with [Polars](https://github.com/ankane/ruby-polars)
41
48
 
42
49
  ```ruby
43
50
  df = Polars::DataFrame.new({"id" => [1, 2], "value" => [3.0, 4.0]})
44
- table = catalog.create_table("main.events", schema: df.schema)
51
+ table = catalog.create_table("events", schema: df.schema)
45
52
  table.append(df)
46
53
  ```
47
54
 
55
+ Or with [Nanoarrow](https://github.com/ankane/nanoarrow-ruby)
56
+
57
+ ```ruby
58
+ arr =
59
+ Nanoarrow::Array.new(
60
+ [{"id" => 1, "value" => 3.0}, {"id" => 2, "value" => 4.0}],
61
+ Nanoarrow.struct({"a" => Nanoarrow.int64, "b" => Nanoarrow.string})
62
+ )
63
+ table = catalog.create_table("events", schema: arr.schema)
64
+ table.append(arr)
65
+ ```
66
+
48
67
  Load a table
49
68
 
50
69
  ```ruby
51
- table = catalog.load_table("main.events")
70
+ table = catalog.load_table("events")
52
71
  ```
53
72
 
54
73
  Query a table
55
74
 
56
75
  ```ruby
76
+ table.to_a
77
+ # or
57
78
  table.to_polars.collect
79
+ # or
80
+ table.scan.to_arrow
58
81
  ```
59
82
 
60
83
  ## Catalog Types
@@ -67,6 +90,22 @@ Iceberg::RestCatalog.new(
67
90
  )
68
91
  ```
69
92
 
93
+ ### S3 Tables
94
+
95
+ ```ruby
96
+ Iceberg::S3TablesCatalog.new(
97
+ arn: "arn:aws:s3tables:..."
98
+ )
99
+ ```
100
+
101
+ ### Glue
102
+
103
+ ```ruby
104
+ Iceberg::GlueCatalog.new(
105
+ warehouse: "s3://my-bucket"
106
+ )
107
+ ```
108
+
70
109
  ### SQL
71
110
 
72
111
  ```ruby
@@ -133,7 +172,7 @@ catalog.list_tables("main")
133
172
  Create a table
134
173
 
135
174
  ```ruby
136
- catalog.create_table("main.events") do |t|
175
+ catalog.create_table("events") do |t|
137
176
  t.integer "id"
138
177
  t.float "value"
139
178
  end
@@ -142,31 +181,31 @@ end
142
181
  Load a table
143
182
 
144
183
  ```ruby
145
- catalog.load_table("main.events")
184
+ catalog.load_table("events")
146
185
  ```
147
186
 
148
187
  Check if a table exists
149
188
 
150
189
  ```ruby
151
- catalog.table_exists?("main.events")
190
+ catalog.table_exists?("events")
152
191
  ```
153
192
 
154
193
  Rename a table
155
194
 
156
195
  ```ruby
157
- catalog.rename_table("main.events", "main.events2")
196
+ catalog.rename_table("events", "events2")
158
197
  ```
159
198
 
160
199
  Register a table
161
200
 
162
201
  ```ruby
163
- catalog.register_table("main.events", "metadata.json")
202
+ catalog.register_table("events", "metadata.json")
164
203
  ```
165
204
 
166
205
  Drop a table
167
206
 
168
207
  ```ruby
169
- catalog.drop_table("main.events")
208
+ catalog.drop_table("events")
170
209
  ```
171
210
 
172
211
  ## Static Tables
@@ -196,7 +235,7 @@ To get started with development:
196
235
  git clone https://github.com/ankane/iceberg-ruby.git
197
236
  cd iceberg-ruby
198
237
  bundle install
199
- bundle exec rake compile
238
+ bundle exec rake compile:dev
200
239
 
201
240
  # memory catalog
202
241
  bundle exec rake test:memory
@@ -208,4 +247,10 @@ bundle exec rake test:rest
208
247
  # SQL catalog
209
248
  createdb iceberg_ruby_test
210
249
  bundle exec rake test:sql
250
+
251
+ # S3 Tables catalog
252
+ bundle exec rake test:s3tables
253
+
254
+ # Glue catalog
255
+ bundle exec rake test:glue
211
256
  ```
Binary file
Binary file
Binary file
@@ -1,14 +1,18 @@
1
1
  module Iceberg
2
2
  class Catalog
3
+ def _initialize(catalog, default_namespace:)
4
+ @catalog = catalog
5
+ @default_namespace = default_namespace
6
+ end
7
+
3
8
  def list_namespaces(parent = nil)
4
9
  @catalog.list_namespaces(parent)
5
10
  end
6
11
 
7
12
  def create_namespace(namespace, properties: {}, if_not_exists: nil)
8
13
  @catalog.create_namespace(namespace, properties)
9
- rescue Error => e
10
- # ideally all catalogs would use NamespaceAlreadyExistsError
11
- if !if_not_exists || (e.message != "Cannot create namespace" && !e.message.include?("already exists"))
14
+ rescue NamespaceAlreadyExistsError => e
15
+ if !if_not_exists
12
16
  raise e
13
17
  end
14
18
  nil
@@ -28,19 +32,18 @@ module Iceberg
28
32
 
29
33
  def drop_namespace(namespace, if_exists: nil)
30
34
  @catalog.drop_namespace(namespace)
31
- rescue Error => e
32
- # ideally all catalogs would use NamespaceNotFoundError
33
- if !if_exists || (e.message != "Tried to drop a namespace that does not exist" && !e.message.include?("No such namespace") && !e.message.include?("The specified namespace does not exist") && !e.message.include?("not found"))
35
+ rescue NoSuchNamespaceError => e
36
+ if !if_exists
34
37
  raise e
35
38
  end
36
39
  nil
37
40
  end
38
41
 
39
- def list_tables(namespace)
40
- @catalog.list_tables(namespace)
42
+ def list_tables(namespace = nil)
43
+ @catalog.list_tables(namespace || @default_namespace)
41
44
  end
42
45
 
43
- def create_table(table_name, schema: nil, location: nil)
46
+ def create_table(table_name, schema: nil, location: nil, partition_spec: nil, sort_order: nil, properties: {})
44
47
  if !schema.nil? && block_given?
45
48
  raise ArgumentError, "Must pass schema or block"
46
49
  end
@@ -48,63 +51,84 @@ module Iceberg
48
51
  if block_given?
49
52
  table_definition = TableDefinition.new
50
53
  yield table_definition
51
- schema = Schema.new(table_definition.fields)
52
- elsif schema.is_a?(Hash) || (defined?(Polars::Schema) && schema.is_a?(Polars::Schema))
53
- fields =
54
- schema.to_h.map.with_index do |(k, v), i|
55
- {
56
- id: i + 1,
57
- name: k.is_a?(Symbol) ? k.to_s : k,
58
- type: v,
59
- required: false
60
- }
61
- end
62
- schema = Schema.new(fields)
54
+ schema = Schema.new(*table_definition.fields)
55
+ elsif schema.is_a?(Schema)
56
+ # do nothing
57
+ elsif schema.respond_to?(:arrow_c_schema)
58
+ schema = Schema.new(schema)
59
+ elsif schema.is_a?(Hash)
60
+ table_definition = TableDefinition.new
61
+ schema.each do |k, v|
62
+ table_definition.column(k, v)
63
+ end
64
+ schema = Schema.new(*table_definition.fields)
63
65
  elsif schema.nil?
64
- schema = Schema.new([])
66
+ schema = Schema.new
65
67
  end
66
68
 
67
- Table.new(@catalog.create_table(table_name, schema, location), @catalog)
69
+ format_version = (properties["format-version"] || 2).to_i
70
+ if properties["format-version"] && (is_a?(MemoryCatalog) || is_a?(SqlCatalog))
71
+ properties = properties.except("format-version")
72
+ end
73
+
74
+ Table.new(@catalog.create_table(with_namespace(table_name), schema, location, partition_spec, sort_order, properties, format_version), @catalog)
68
75
  end
69
76
 
70
77
  def load_table(table_name)
71
- Table.new(@catalog.load_table(table_name), @catalog)
78
+ Table.new(@catalog.load_table(with_namespace(table_name)), @catalog)
72
79
  end
73
80
 
74
81
  def drop_table(table_name, if_exists: nil)
75
- @catalog.drop_table(table_name)
76
- rescue Error => e
77
- # ideally all catalogs would use TableNotFoundError
78
- if !if_exists || (e.message != "Tried to drop a table that does not exist" && !e.message.include?("No such table") && !e.message.include?("The specified table does not exist") && !e.message.include?("not found"))
82
+ @catalog.drop_table(with_namespace(table_name))
83
+ rescue NoSuchTableError => e
84
+ if !if_exists
79
85
  raise e
80
86
  end
81
87
  nil
82
88
  end
83
89
 
90
+ def purge_table(table_name)
91
+ @catalog.purge_table(with_namespace(table_name))
92
+ end
93
+
84
94
  def table_exists?(table_name)
85
- @catalog.table_exists?(table_name)
86
- rescue NamespaceNotFoundError
95
+ @catalog.table_exists?(with_namespace(table_name))
96
+ rescue NoSuchNamespaceError
87
97
  false
88
98
  end
89
99
 
90
100
  def rename_table(table_name, new_name)
91
- @catalog.rename_table(table_name, new_name)
101
+ @catalog.rename_table(with_namespace(table_name), with_namespace(new_name))
92
102
  end
93
103
 
94
104
  def register_table(table_name, metadata_location)
95
- @catalog.register_table(table_name, metadata_location)
105
+ @catalog.register_table(with_namespace(table_name), metadata_location)
96
106
  end
97
107
 
98
- def sql(sql)
108
+ def sql(sql, params = [])
99
109
  # requires datafusion feature
100
- raise Todo unless @catalog.respond_to?(:sql)
110
+ raise Todo unless @catalog.respond_to?(:session_context)
101
111
 
102
- @catalog.sql(sql)
112
+ session_context.sql(sql, params)
103
113
  end
104
114
 
105
115
  # hide internal state
106
116
  def inspect
107
117
  to_s
108
118
  end
119
+
120
+ private
121
+
122
+ def with_namespace(table_name)
123
+ if @default_namespace && table_name.is_a?(String) && !table_name.include?(".")
124
+ [@default_namespace, table_name]
125
+ else
126
+ table_name
127
+ end
128
+ end
129
+
130
+ def session_context
131
+ @session_context ||= @catalog.session_context(@default_namespace)
132
+ end
109
133
  end
110
134
  end
@@ -1,8 +1,11 @@
1
1
  module Iceberg
2
2
  class GlueCatalog < Catalog
3
3
  # warehouse is URI of S3 storage bucket
4
- def initialize(warehouse:)
5
- @catalog = RbCatalog.new_glue(warehouse)
4
+ def initialize(warehouse:, default_namespace: nil)
5
+ _initialize(
6
+ RbCatalog.new_glue(warehouse),
7
+ default_namespace:
8
+ )
6
9
  end
7
10
  end
8
11
  end
@@ -1,8 +1,11 @@
1
1
  module Iceberg
2
2
  class MemoryCatalog < Catalog
3
3
  # warehouse is default storage location
4
- def initialize(warehouse: nil)
5
- @catalog = RbCatalog.new_memory(warehouse)
4
+ def initialize(warehouse: nil, default_namespace: nil)
5
+ _initialize(
6
+ RbCatalog.new_memory(warehouse),
7
+ default_namespace:
8
+ )
6
9
  end
7
10
  end
8
11
  end
@@ -1,8 +1,11 @@
1
1
  module Iceberg
2
2
  class RestCatalog < Catalog
3
3
  # warehouse is passed to REST server
4
- def initialize(uri:, warehouse: nil, properties: {})
5
- @catalog = RbCatalog.new_rest(uri, warehouse, properties)
4
+ def initialize(uri:, warehouse: nil, properties: {}, default_namespace: nil)
5
+ _initialize(
6
+ RbCatalog.new_rest(uri, warehouse, properties),
7
+ default_namespace:
8
+ )
6
9
  end
7
10
  end
8
11
  end
@@ -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