altertable-lakehouse 0.8.0 → 0.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6dfae05324ea00439f1b6dba1529227cc10707bf59a1089b8342400f3a61407a
4
- data.tar.gz: 0c336d73acd7751552a6806dba593b3c0590b19e84d999c2631c326f08385d8e
3
+ metadata.gz: 79141f78a6f427133d3eade8a3e78d697d7526eab2d767fbafa7d29d3d2954fb
4
+ data.tar.gz: 1fae0c3ce116fe23afbb8611ee272999273cd7fa5f4687cc9a4fdd32a14605f4
5
5
  SHA512:
6
- metadata.gz: d7b17d9d68b20e4d6aed748ee3626fa6a89949a5e4311845f16181cfe9486ad23fdf915e21220f39b4f704122d9046102e3383fa150a958a195136016d877293
7
- data.tar.gz: eddc49cf94b2d6fc1e14fb2b2326385e4c79013ab7d324eeaa9961b9732295ce492698b7b4f16ca03700a55eddf5d5c95a7a877b66ffe9b88fcb3781546ec774
6
+ metadata.gz: e2d5f5564023af9db4070e39af8b23f113ac8cf5b4dda09e4443e4a5cd343cefb3c26e30d5754e5576ccf72dace589a4bd412992e0b2499922f46b443ae54fd8
7
+ data.tar.gz: '0877c8558d85087f5678b1df8c48646b7f68684f331e649c8bd4b7d6c8b5a72657656d9e57d6eeef41c98b4e2d4d937c38953ed29f999ef2e9bf393e57f82d87'
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.8.0"
2
+ ".": "0.9.0"
3
3
  }
data/.rubocop.yml CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  AllCops:
3
3
  NewCops: enable
4
- TargetRubyVersion: 3.0
4
+ TargetRubyVersion: 3.2
5
5
  Exclude:
6
6
  - 'vendor/**/*'
7
7
  - 'spec/**/*'
data/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@ All notable changes to this project will be documented in this file.
7
7
  ### Added
8
8
 
9
9
  * Support named and positional bind values through `QueryRequest#params`.
10
+ * `ColumnDescriptor` value objects (`Data`) for query columns, with `name` and `type` readers.
11
+
12
+ ### Changed
13
+
14
+ * Require Ruby 3.2 or newer (`ColumnDescriptor` is a `Data` class). CI already only tested 3.2+.
15
+
16
+ ### Fixed
17
+
18
+ * `QueryResult#columns` and `query_all[:columns]` return typed column descriptors again instead of bare names; rows stay keyed by column name.
19
+ * A malformed `/query` column line (anything other than an array of `{ "name", "type" }` string objects) now raises `ParseError` instead of passing through untyped.
20
+
21
+ ## [0.9.0](https://github.com/altertable-ai/altertable-lakehouse-ruby/compare/altertable-lakehouse/v0.8.2...altertable-lakehouse/v0.9.0) (2026-10-12)
22
+
23
+
24
+ ### Miscellaneous Chores
25
+
26
+ * release 0.9.0 ([#58](https://github.com/altertable-ai/altertable-lakehouse-ruby/issues/58)) ([55fff71](https://github.com/altertable-ai/altertable-lakehouse-ruby/commit/55fff719c9e988d07cedd1a820752001aa4b53ea))
27
+
28
+ ## [0.8.2](https://github.com/altertable-ai/altertable-lakehouse-ruby/compare/altertable-lakehouse/v0.8.1...altertable-lakehouse/v0.8.2) (2026-10-12)
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * **query:** restore typed column descriptors ([a16ea67](https://github.com/altertable-ai/altertable-lakehouse-ruby/commit/a16ea679f581bcedf665f1f7b487601b34f66432))
34
+
35
+ ## [0.8.1](https://github.com/altertable-ai/altertable-lakehouse-ruby/compare/altertable-lakehouse/v0.8.0...altertable-lakehouse/v0.8.1) (2026-10-07)
36
+
37
+
38
+ ### Bug Fixes
39
+
40
+ * **error:** implement proper query error parsing ([#54](https://github.com/altertable-ai/altertable-lakehouse-ruby/issues/54)) ([86f6512](https://github.com/altertable-ai/altertable-lakehouse-ruby/commit/86f6512cfc6c58822981d0ac4824b7479dd1f2f4))
10
41
 
11
42
  ## [0.8.0](https://github.com/altertable-ai/altertable-lakehouse-ruby/compare/altertable-lakehouse/v0.7.0...altertable-lakehouse/v0.8.0) (2026-10-07)
12
43
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- altertable-lakehouse (0.8.0)
4
+ altertable-lakehouse (0.9.0)
5
5
  base64
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -124,7 +124,8 @@ result = client.query_all(
124
124
  )
125
125
 
126
126
  puts result[:metadata] # Hash
127
- puts result[:columns] # Array of columns
127
+ puts result[:columns] # Array of ColumnDescriptor (#name, #type)
128
+ puts result[:columns].first.type # e.g. "INTEGER"
128
129
  result[:rows].each do |row|
129
130
  puts row
130
131
  end
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "Official Ruby client for Altertable Lakehouse API."
13
13
  spec.homepage = "https://github.com/altertable-ai/altertable-lakehouse-ruby"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = ">= 3.0.0"
15
+ spec.required_ruby_version = ">= 3.2.0"
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = "https://github.com/altertable-ai/altertable-lakehouse-ruby"
@@ -146,16 +146,16 @@ module Altertable
146
146
  @uri = URI.parse(@base_url)
147
147
  end
148
148
 
149
- def get(path, body: nil, params: {}, headers: {}, &block) # rubocop:disable Lint/UnusedMethodArgument
150
- request(Net::HTTP::Get, path, params: params, headers: headers, &block)
149
+ def get(path, body: nil, params: {}, headers: {}, &) # rubocop:disable Lint/UnusedMethodArgument
150
+ request(Net::HTTP::Get, path, params: params, headers: headers, &)
151
151
  end
152
152
 
153
- def post(path, body: nil, params: {}, headers: {}, &block)
154
- request(Net::HTTP::Post, path, body: body, params: params, headers: headers, &block)
153
+ def post(path, body: nil, params: {}, headers: {}, &)
154
+ request(Net::HTTP::Post, path, body: body, params: params, headers: headers, &)
155
155
  end
156
156
 
157
- def delete(path, body: nil, params: {}, headers: {}, &block) # rubocop:disable Lint/UnusedMethodArgument
158
- request(Net::HTTP::Delete, path, params: params, headers: headers, &block)
157
+ def delete(path, body: nil, params: {}, headers: {}, &) # rubocop:disable Lint/UnusedMethodArgument
158
+ request(Net::HTTP::Delete, path, params: params, headers: headers, &)
159
159
  end
160
160
 
161
161
  private
@@ -12,7 +12,7 @@ module Altertable
12
12
  DEFAULT_TIMEOUT = 10
13
13
  DEFAULT_OPEN_TIMEOUT = 5
14
14
 
15
- def initialize(username: nil, password: nil, basic_auth_token: nil, base_url: nil, timeout: nil, open_timeout: nil, user_agent: nil, adapter: nil, headers: {}, **options)
15
+ def initialize(username: nil, password: nil, basic_auth_token: nil, base_url: nil, timeout: nil, open_timeout: nil, user_agent: nil, adapter: nil, headers: {}, **)
16
16
  # 1. Try passed basic_auth_token
17
17
  # 2. Try passed username/password
18
18
  # 3. Try ENV["ALTERTABLE_BASIC_AUTH_TOKEN"]
@@ -40,7 +40,7 @@ module Altertable
40
40
  "User-Agent" => @user_agent
41
41
  }
42
42
 
43
- @adapter = select_adapter(adapter, base_url: @base_url, timeout: @timeout, open_timeout: @open_timeout, headers: default_headers.merge(headers), **options)
43
+ @adapter = select_adapter(adapter, base_url: @base_url, timeout: @timeout, open_timeout: @open_timeout, headers: default_headers.merge(headers), **)
44
44
  end
45
45
 
46
46
  # POST /append
@@ -59,8 +59,8 @@ module Altertable
59
59
  end
60
60
 
61
61
  # POST /query (streamed)
62
- def query(statement:, headers: {}, **options)
63
- req_body = Models::QueryRequest.new(statement: statement, **options).to_h.to_json
62
+ def query(statement:, headers: {}, **)
63
+ req_body = Models::QueryRequest.new(statement: statement, **).to_h.to_json
64
64
 
65
65
  enum = Enumerator.new do |yielder|
66
66
  buffer = ""
@@ -77,8 +77,8 @@ module Altertable
77
77
  end
78
78
 
79
79
  # POST /query (accumulated)
80
- def query_all(statement:, headers: {}, **options)
81
- result = query(statement: statement, headers: headers, **options)
80
+ def query_all(statement:, headers: {}, **)
81
+ result = query(statement: statement, headers: headers, **)
82
82
  rows = result.to_a # Accumulate
83
83
  {
84
84
  metadata: result.metadata,
@@ -278,36 +278,50 @@ module Altertable
278
278
  end
279
279
  end
280
280
 
281
+ ColumnDescriptor = Data.define(:name, :type)
282
+
281
283
  class QueryResult
282
284
  include Enumerable
283
285
 
284
286
  # metadata: the stream header object (first NDJSON line)
285
- # columns: array of column names or typed column descriptors (second NDJSON line)
287
+ # columns: ColumnDescriptor array (second NDJSON line)
286
288
  attr_reader :metadata, :columns
287
289
 
288
290
  def initialize(enum)
289
291
  @enum = enum
290
292
  @metadata = nil
291
293
  @columns = nil
294
+ @column_names = nil
292
295
  end
293
296
 
294
297
  def each(&block)
295
- # The real mock streams:
296
- # line 1: { "statement":…, "session_id":…, … } (header object)
297
- # line 2: ["col1", "col2", …] (column names array)
298
- # line 3+: [val1, val2, …] (row value arrays)
299
- # We zip each row array with the column names to produce a Hash.
298
+ # The mock streams NDJSON:
299
+ # line 1: metadata object
300
+ # line 2: column descriptors, or { "error": string }
301
+ # line 3+: row arrays, or { "error": string }
302
+ # Row arrays are zipped with column names into hashes.
300
303
  line_index = 0
301
304
 
302
305
  @enum.each do |item|
306
+ if line_index.positive? && (message = stream_error_message(item))
307
+ raise QueryError.new(
308
+ message,
309
+ line_index: line_index,
310
+ operation: "query",
311
+ http_method: "POST",
312
+ http_path: "/query"
313
+ )
314
+ end
315
+
303
316
  case line_index
304
317
  when 0
305
318
  @metadata = item
306
319
  when 1
307
- @columns = normalize_columns(item)
320
+ @columns = column_descriptors(item)
321
+ @column_names = @columns.map(&:name)
308
322
  else
309
- if @columns.is_a?(Array) && item.is_a?(Array)
310
- block.call(@columns.zip(item).to_h)
323
+ if item.is_a?(Array)
324
+ block.call(@column_names.zip(item).to_h)
311
325
  else
312
326
  block.call(item)
313
327
  end
@@ -318,16 +332,39 @@ module Altertable
318
332
 
319
333
  private
320
334
 
321
- # The query service emits typed column descriptors, such as
322
- # { "name" => "id", "type" => "INTEGER" }. Keep the public result
323
- # shape stable by exposing names and using them as row keys.
324
- def normalize_columns(columns)
325
- return columns unless columns.is_a?(Array)
335
+ # A post-metadata JSON object with an `error` string is a stream failure,
336
+ # not column schema or a row.
337
+ def stream_error_message(item)
338
+ return unless item.is_a?(Hash)
339
+
340
+ message = item["error"]
341
+ message if message.is_a?(String)
342
+ end
343
+
344
+ def column_descriptors(columns)
345
+ unless columns.is_a?(Array)
346
+ raise column_parse_error("expected an array of column objects, got #{columns.inspect}")
347
+ end
326
348
 
327
349
  columns.map do |column|
328
- column.is_a?(Hash) && column.key?("name") ? column["name"] : column
350
+ name = column["name"] if column.is_a?(Hash)
351
+ type = column["type"] if column.is_a?(Hash)
352
+ unless name.is_a?(String) && type.is_a?(String)
353
+ raise column_parse_error("expected a { \"name\", \"type\" } column object, got #{column.inspect}")
354
+ end
355
+
356
+ ColumnDescriptor.new(name: name, type: type)
329
357
  end
330
358
  end
359
+
360
+ def column_parse_error(message)
361
+ ParseError.new(
362
+ "Malformed /query column line: #{message}",
363
+ operation: "query",
364
+ http_method: "POST",
365
+ http_path: "/query"
366
+ )
367
+ end
331
368
  end
332
369
 
333
370
  end
@@ -21,6 +21,27 @@ module Altertable
21
21
  class TimeoutError < Error; end
22
22
  class SerializationError < Error; end
23
23
  class ParseError < Error; end
24
+
25
+ # Backend-emitted `{ "error": string }` line in a /query NDJSON stream.
26
+ # `line_index` is the zero-based index of that line.
27
+ class QueryError < Error
28
+ attr_reader :line_index
29
+
30
+ def initialize(message, line_index:, operation: nil, http_method: nil, http_path: nil, status_code: nil, retriable: false, request_id: nil, cause: nil)
31
+ super(
32
+ message,
33
+ operation: operation,
34
+ http_method: http_method,
35
+ http_path: http_path,
36
+ status_code: status_code,
37
+ retriable: retriable,
38
+ request_id: request_id,
39
+ cause: cause
40
+ )
41
+ @line_index = line_index
42
+ end
43
+ end
44
+
24
45
  class ApiError < Error; end
25
46
  class ConfigurationError < Error; end
26
47
  end
@@ -1,5 +1,5 @@
1
1
  module Altertable
2
2
  module Lakehouse
3
- VERSION = "0.8.0"
3
+ VERSION = "0.9.0"
4
4
  end
5
5
  end
@@ -47,6 +47,27 @@ module Altertable
47
47
  class TimeoutError < Error; end
48
48
  class SerializationError < Error; end
49
49
  class ParseError < Error; end
50
+
51
+ class QueryError < Error
52
+ sig { returns(Integer) }
53
+ def line_index; end
54
+
55
+ sig do
56
+ params(
57
+ message: String,
58
+ line_index: Integer,
59
+ operation: T.nilable(String),
60
+ http_method: T.nilable(String),
61
+ http_path: T.nilable(String),
62
+ status_code: T.nilable(Integer),
63
+ retriable: T::Boolean,
64
+ request_id: T.nilable(String),
65
+ cause: T.nilable(Exception)
66
+ ).void
67
+ end
68
+ def initialize(message, line_index:, operation: nil, http_method: nil, http_path: nil, status_code: nil, retriable: false, request_id: nil, cause: nil); end
69
+ end
70
+
50
71
  class ApiError < Error; end
51
72
  class ConfigurationError < Error; end
52
73
 
@@ -89,7 +110,7 @@ module Altertable
89
110
  sig { params(statement: String, headers: T::Hash[String, String], options: T.untyped).returns(::Altertable::Lakehouse::QueryResult) }
90
111
  def query(statement:, headers: {}, **options); end
91
112
 
92
- sig { params(statement: String, headers: T::Hash[String, String], options: T.untyped).returns(T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
113
+ sig { params(statement: String, headers: T::Hash[String, String], options: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
93
114
  def query_all(statement:, headers: {}, **options); end
94
115
 
95
116
  sig do
@@ -183,13 +204,21 @@ module Altertable
183
204
  def handle_response(resp); end
184
205
  end
185
206
 
207
+ class ColumnDescriptor < Data
208
+ sig { returns(String) }
209
+ def name; end
210
+
211
+ sig { returns(String) }
212
+ def type; end
213
+ end
214
+
186
215
  class QueryResult
187
216
  include Enumerable
188
217
 
189
218
  sig { returns(T.untyped) }
190
219
  def metadata; end
191
220
 
192
- sig { returns(T.untyped) }
221
+ sig { returns(T.nilable(T::Array[::Altertable::Lakehouse::ColumnDescriptor])) }
193
222
  def columns; end
194
223
 
195
224
  sig { params(enum: T.untyped).void }
@@ -103,18 +103,34 @@ module Altertable
103
103
  def handle_response: (Adapters::Response resp) -> untyped
104
104
  end
105
105
 
106
+ class ColumnDescriptor < ::Data
107
+ attr_reader name: String
108
+ attr_reader type: String
109
+
110
+ def self.new: (name: String, type: String) -> instance
111
+ | (String name, String type) -> instance
112
+ def to_h: () -> { name: String, type: String }
113
+ end
114
+
106
115
  class QueryResult
107
116
  include Enumerable
108
117
 
109
118
  @enum: Enumerator[untyped, untyped]
110
119
  @metadata: untyped
111
- @columns: untyped
120
+ @columns: Array[ColumnDescriptor]?
121
+ @column_names: Array[String]?
112
122
 
113
123
  attr_reader metadata: untyped
114
- attr_reader columns: untyped
124
+ attr_reader columns: Array[ColumnDescriptor]?
115
125
 
116
126
  def initialize: (Enumerator[untyped, untyped] enum) -> void
117
127
  def each: () { (untyped) -> void } -> void
128
+
129
+ private
130
+
131
+ def stream_error_message: (untyped item) -> String?
132
+ def column_descriptors: (untyped columns) -> Array[ColumnDescriptor]
133
+ def column_parse_error: (String message) -> ParseError
118
134
  end
119
135
  end
120
136
  end
@@ -39,6 +39,22 @@ module Altertable
39
39
  class ParseError < Error
40
40
  end
41
41
 
42
+ class QueryError < Error
43
+ attr_reader line_index: Integer
44
+
45
+ def initialize: (
46
+ String message,
47
+ line_index: Integer,
48
+ ?operation: String?,
49
+ ?http_method: String?,
50
+ ?http_path: String?,
51
+ ?status_code: Integer?,
52
+ ?retriable: bool,
53
+ ?request_id: String?,
54
+ ?cause: Exception?
55
+ ) -> void
56
+ end
57
+
42
58
  class ApiError < Error
43
59
  end
44
60
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: altertable-lakehouse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Altertable AI
@@ -249,7 +249,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
249
249
  requirements:
250
250
  - - ">="
251
251
  - !ruby/object:Gem::Version
252
- version: 3.0.0
252
+ version: 3.2.0
253
253
  required_rubygems_version: !ruby/object:Gem::Requirement
254
254
  requirements:
255
255
  - - ">="