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 +4 -4
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +31 -0
- data/Gemfile.lock +1 -1
- data/README.md +2 -1
- data/altertable-lakehouse.gemspec +1 -1
- data/lib/altertable/lakehouse/adapters.rb +6 -6
- data/lib/altertable/lakehouse/client.rb +58 -21
- data/lib/altertable/lakehouse/errors.rb +21 -0
- data/lib/altertable/lakehouse/version.rb +1 -1
- data/rbi/altertable/lakehouse.rbi +31 -2
- data/sig/altertable/lakehouse/client.rbs +18 -2
- data/sig/altertable/lakehouse/errors.rbs +16 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79141f78a6f427133d3eade8a3e78d697d7526eab2d767fbafa7d29d3d2954fb
|
|
4
|
+
data.tar.gz: 1fae0c3ce116fe23afbb8611ee272999273cd7fa5f4687cc9a4fdd32a14605f4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e2d5f5564023af9db4070e39af8b23f113ac8cf5b4dda09e4443e4a5cd343cefb3c26e30d5754e5576ccf72dace589a4bd412992e0b2499922f46b443ae54fd8
|
|
7
|
+
data.tar.gz: '0877c8558d85087f5678b1df8c48646b7f68684f331e649c8bd4b7d6c8b5a72657656d9e57d6eeef41c98b4e2d4d937c38953ed29f999ef2e9bf393e57f82d87'
|
data/.rubocop.yml
CHANGED
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
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
|
|
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.
|
|
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: {}, &
|
|
150
|
-
request(Net::HTTP::Get, path, params: params, headers: headers, &
|
|
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: {}, &
|
|
154
|
-
request(Net::HTTP::Post, path, body: body, params: params, headers: headers, &
|
|
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: {}, &
|
|
158
|
-
request(Net::HTTP::Delete, path, params: params, headers: headers, &
|
|
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: {}, **
|
|
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), **
|
|
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: {}, **
|
|
63
|
-
req_body = Models::QueryRequest.new(statement: statement, **
|
|
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: {}, **
|
|
81
|
-
result = query(statement: statement, headers: headers, **
|
|
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
|
|
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
|
|
296
|
-
# line 1:
|
|
297
|
-
# line 2:
|
|
298
|
-
# line 3+:
|
|
299
|
-
#
|
|
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 =
|
|
320
|
+
@columns = column_descriptors(item)
|
|
321
|
+
@column_names = @columns.map(&:name)
|
|
308
322
|
else
|
|
309
|
-
if
|
|
310
|
-
block.call(@
|
|
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
|
-
#
|
|
322
|
-
#
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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])
|
|
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.
|
|
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:
|
|
120
|
+
@columns: Array[ColumnDescriptor]?
|
|
121
|
+
@column_names: Array[String]?
|
|
112
122
|
|
|
113
123
|
attr_reader metadata: untyped
|
|
114
|
-
attr_reader columns:
|
|
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.
|
|
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.
|
|
252
|
+
version: 3.2.0
|
|
253
253
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
254
254
|
requirements:
|
|
255
255
|
- - ">="
|