iceberg 0.11.2-aarch64-linux → 0.12.0-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,7 +31,7 @@ catalog.create_namespace("main")
31
31
  Create a table
32
32
 
33
33
  ```ruby
34
- catalog.create_table("main.events") do |t|
34
+ catalog.create_table("events") do |t|
35
35
  t.bigint "id"
36
36
  t.float "value"
37
37
  end
@@ -41,14 +41,14 @@ Or with [Polars](https://github.com/ankane/ruby-polars)
41
41
 
42
42
  ```ruby
43
43
  df = Polars::DataFrame.new({"id" => [1, 2], "value" => [3.0, 4.0]})
44
- table = catalog.create_table("main.events", schema: df.schema)
44
+ table = catalog.create_table("events", schema: df.schema)
45
45
  table.append(df)
46
46
  ```
47
47
 
48
48
  Load a table
49
49
 
50
50
  ```ruby
51
- table = catalog.load_table("main.events")
51
+ table = catalog.load_table("events")
52
52
  ```
53
53
 
54
54
  Query a table
@@ -67,6 +67,26 @@ Iceberg::RestCatalog.new(
67
67
  )
68
68
  ```
69
69
 
70
+ ### S3 Tables
71
+
72
+ [unreleased]
73
+
74
+ ```ruby
75
+ Iceberg::S3TablesCatalog.new(
76
+ arn: "arn:aws:s3tables:..."
77
+ )
78
+ ```
79
+
80
+ ### Glue
81
+
82
+ [unreleased]
83
+
84
+ ```ruby
85
+ Iceberg::GlueCatalog.new(
86
+ warehouse: "s3://my-bucket"
87
+ )
88
+ ```
89
+
70
90
  ### SQL
71
91
 
72
92
  ```ruby
@@ -133,7 +153,7 @@ catalog.list_tables("main")
133
153
  Create a table
134
154
 
135
155
  ```ruby
136
- catalog.create_table("main.events") do |t|
156
+ catalog.create_table("events") do |t|
137
157
  t.integer "id"
138
158
  t.float "value"
139
159
  end
@@ -142,31 +162,31 @@ end
142
162
  Load a table
143
163
 
144
164
  ```ruby
145
- catalog.load_table("main.events")
165
+ catalog.load_table("events")
146
166
  ```
147
167
 
148
168
  Check if a table exists
149
169
 
150
170
  ```ruby
151
- catalog.table_exists?("main.events")
171
+ catalog.table_exists?("events")
152
172
  ```
153
173
 
154
174
  Rename a table
155
175
 
156
176
  ```ruby
157
- catalog.rename_table("main.events", "main.events2")
177
+ catalog.rename_table("events", "events2")
158
178
  ```
159
179
 
160
180
  Register a table
161
181
 
162
182
  ```ruby
163
- catalog.register_table("main.events", "metadata.json")
183
+ catalog.register_table("events", "metadata.json")
164
184
  ```
165
185
 
166
186
  Drop a table
167
187
 
168
188
  ```ruby
169
- catalog.drop_table("main.events")
189
+ catalog.drop_table("events")
170
190
  ```
171
191
 
172
192
  ## Static Tables
@@ -196,7 +216,7 @@ To get started with development:
196
216
  git clone https://github.com/ankane/iceberg-ruby.git
197
217
  cd iceberg-ruby
198
218
  bundle install
199
- bundle exec rake compile
219
+ bundle exec rake compile:dev
200
220
 
201
221
  # memory catalog
202
222
  bundle exec rake test:memory
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,79 @@ 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
+ Table.new(@catalog.create_table(with_namespace(table_name), schema, location, partition_spec, sort_order, properties), @catalog)
68
70
  end
69
71
 
70
72
  def load_table(table_name)
71
- Table.new(@catalog.load_table(table_name), @catalog)
73
+ Table.new(@catalog.load_table(with_namespace(table_name)), @catalog)
72
74
  end
73
75
 
74
76
  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"))
77
+ @catalog.drop_table(with_namespace(table_name))
78
+ rescue NoSuchTableError => e
79
+ if !if_exists
79
80
  raise e
80
81
  end
81
82
  nil
82
83
  end
83
84
 
85
+ def purge_table(table_name)
86
+ @catalog.purge_table(with_namespace(table_name))
87
+ end
88
+
84
89
  def table_exists?(table_name)
85
- @catalog.table_exists?(table_name)
86
- rescue NamespaceNotFoundError
90
+ @catalog.table_exists?(with_namespace(table_name))
91
+ rescue NoSuchNamespaceError
87
92
  false
88
93
  end
89
94
 
90
95
  def rename_table(table_name, new_name)
91
- @catalog.rename_table(table_name, new_name)
96
+ @catalog.rename_table(with_namespace(table_name), with_namespace(new_name))
92
97
  end
93
98
 
94
99
  def register_table(table_name, metadata_location)
95
- @catalog.register_table(table_name, metadata_location)
100
+ @catalog.register_table(with_namespace(table_name), metadata_location)
96
101
  end
97
102
 
98
- def sql(sql)
103
+ def sql(sql, params = [])
99
104
  # requires datafusion feature
100
- raise Todo unless @catalog.respond_to?(:sql)
105
+ raise Todo unless @catalog.respond_to?(:session_context)
101
106
 
102
- @catalog.sql(sql)
107
+ session_context.sql(sql, params)
103
108
  end
104
109
 
105
110
  # hide internal state
106
111
  def inspect
107
112
  to_s
108
113
  end
114
+
115
+ private
116
+
117
+ def with_namespace(table_name)
118
+ if @default_namespace && table_name.is_a?(String) && !table_name.include?(".")
119
+ [@default_namespace, table_name]
120
+ else
121
+ table_name
122
+ end
123
+ end
124
+
125
+ def session_context
126
+ @session_context ||= @catalog.session_context(@default_namespace)
127
+ end
109
128
  end
110
129
  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
@@ -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