rb_snowflake_client 1.6.2 → 1.7.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/.github/workflows/arby-review.yml +17 -5
- data/CHANGELOG.md +18 -0
- data/README.md +29 -0
- data/lib/ruby_snowflake/client/threaded_in_memory_strategy.rb +11 -21
- data/lib/ruby_snowflake/client.rb +29 -26
- data/lib/ruby_snowflake/errors.rb +21 -0
- data/lib/ruby_snowflake/partition_future.rb +35 -0
- data/lib/ruby_snowflake/streaming_result.rb +26 -19
- data/lib/ruby_snowflake/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 05c5fef52239a7d043627e57e3a205ef07092452b7c7a47779ad21150637ec66
|
|
4
|
+
data.tar.gz: f98da0cd8848820a984753175fd9c22d60f41d137da780de0d8d08f99fa4632a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f577b2fef1a891a37a0a793a53ed4d24d63e665fc5d6e114332f836a4f12a26494f002bf6a589c1b921df253ccac10d81ad122636e8a0d053f16efe4f1c3313d
|
|
7
|
+
data.tar.gz: 4cb2888d8274a883689b448ba6acf315b683ba2692612b1d3753921f082ee14cd5292216c436a4e7d4f5b3ef3893af3a946333a6abff710f42c2246400736855
|
|
@@ -10,13 +10,18 @@
|
|
|
10
10
|
# Nothing PR-controlled reaches the dispatch: only github.repository and the PR number, passed through env.
|
|
11
11
|
# No checkout, no PR code. The token is minted from the Arby Dispatcher app (Actions: write, installed only on
|
|
12
12
|
# the runner repo) and scoped to that one repo, so all it can do is start runs there. The run's own red/green
|
|
13
|
-
# status shows in the runner repo; on this PR the result is Arby's review.
|
|
14
|
-
#
|
|
13
|
+
# status shows in the runner repo; on this PR the result is Arby's review.
|
|
14
|
+
#
|
|
15
|
+
# Requesting a review: someone with write access requests `rinsed-review-bot` as a reviewer (also on drafts,
|
|
16
|
+
# and also on a PR by an author limit-approval-to doesn't cover, such as an outside contributor's fork PR,
|
|
17
|
+
# which is otherwise not reviewed). The dispatch still carries only the repo and PR number; the runner checks
|
|
18
|
+
# the PR itself for a pending request for Arby made by someone with write access. Such a review is
|
|
19
|
+
# comment-only unless limit-approval-to covers the author.
|
|
15
20
|
name: Arby review (public repo)
|
|
16
21
|
|
|
17
22
|
on:
|
|
18
23
|
pull_request_target:
|
|
19
|
-
types: [opened, synchronize, reopened, ready_for_review]
|
|
24
|
+
types: [opened, synchronize, reopened, ready_for_review, review_requested]
|
|
20
25
|
|
|
21
26
|
# Nothing by default; each job asks for exactly what it needs.
|
|
22
27
|
permissions: {}
|
|
@@ -24,8 +29,11 @@ permissions: {}
|
|
|
24
29
|
jobs:
|
|
25
30
|
dispatch:
|
|
26
31
|
name: Request Arby review
|
|
27
|
-
#
|
|
28
|
-
|
|
32
|
+
# A request aimed at Arby itself (a team request names no requested_reviewer), drafts included; otherwise
|
|
33
|
+
# non-drafts only, as in review.yml.
|
|
34
|
+
if: >-
|
|
35
|
+
(github.event.action == 'review_requested' && github.event.requested_reviewer.login == 'rinsed-review-bot')
|
|
36
|
+
|| (github.event.action != 'review_requested' && github.event.pull_request.draft == false)
|
|
29
37
|
runs-on: ubuntu-latest
|
|
30
38
|
timeout-minutes: 5
|
|
31
39
|
# Only the app token is used, so the job's own token needs nothing.
|
|
@@ -59,6 +67,10 @@ jobs:
|
|
|
59
67
|
dismiss:
|
|
60
68
|
# The required check is keyed on this name; keep it exactly as is.
|
|
61
69
|
name: Dismiss stale Arby approvals
|
|
70
|
+
# Runs on review requests too, though they add no commits. Skipping it there would report a newer,
|
|
71
|
+
# skipped check, which satisfies the required check even while the dismissal for the latest push failed
|
|
72
|
+
# or is still running. A request that cancels an in-flight run below is harmless: the new run reads the
|
|
73
|
+
# current head itself and dismisses everything the cancelled one would have.
|
|
62
74
|
concurrency:
|
|
63
75
|
group: arby-dismiss-stale-${{ github.event.pull_request.number }}
|
|
64
76
|
cancel-in-progress: true
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## [1.7.0] - 2026-10-22
|
|
11
|
+
### Added
|
|
12
|
+
- `query` accepts `parameters:` for Snowflake session parameters such as `QUERY_TAG` (#163, thanks Elayamani K)
|
|
13
|
+
- `*_OUTPUT_FORMAT` parameters raise `ArgumentError`: the client parses Snowflake's default date and time
|
|
14
|
+
encodings, and returns binary values in its default hex (#197)
|
|
15
|
+
### Changed
|
|
16
|
+
- A failed partition fetch that isn't a pool timeout raises `RubySnowflake::Error` with the original as `cause`,
|
|
17
|
+
not the `ConnectionStarvedError` the threaded path raised in 1.6.2: code that rescues only
|
|
18
|
+
`ConnectionStarvedError` (e.g. to retry) should rescue `RubySnowflake::Error` or check `cause` (#197)
|
|
19
|
+
### Fixed
|
|
20
|
+
- Streaming results no longer leak a thread per query (#163, thanks Nate Berkopec)
|
|
21
|
+
- Stopping a stream early doesn't wait for its prefetch to finish (#197)
|
|
22
|
+
- Threaded in-memory results no longer leak their threads (#197)
|
|
23
|
+
- A failed partition fetch always raises; it could yield a `nil` partition before (#197, thanks Justin Léger)
|
|
24
|
+
- A failed fetch or an interrupt cancels the partition fetches that haven't started (#197, thanks Justin Léger)
|
|
25
|
+
- No longer requires `benchmark`, which isn't a default gem from Ruby 4.0, so loading the client doesn't need it
|
|
26
|
+
in the bundle (#198)
|
|
27
|
+
|
|
10
28
|
## [1.6.2] - 2026-10-19
|
|
11
29
|
### Fixed
|
|
12
30
|
- Compatibility with `json` 3.0, where `JSON.parse` options are keyword-only: every query raised
|
data/README.md
CHANGED
|
@@ -160,6 +160,35 @@ client.query(query, bindings: bindings)
|
|
|
160
160
|
|
|
161
161
|
For additional information about binding parameters refer to snowflake documentation: https://docs.snowflake.com/en/developer-guide/sql-api/submitting-requests#using-bind-variables-in-a-statement
|
|
162
162
|
|
|
163
|
+
## Session parameters
|
|
164
|
+
|
|
165
|
+
You can set Snowflake session parameters on a per-query basis by passing a hash to the `parameters` option. These parameters affect query behavior and can be used to set things like query tags, timezones, and other session-level settings.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
client.query(
|
|
169
|
+
"SELECT * FROM BIGTABLE",
|
|
170
|
+
parameters: {
|
|
171
|
+
"QUERY_TAG" => "my_custom_tag",
|
|
172
|
+
"TIMEZONE" => "America/Los_Angeles"
|
|
173
|
+
}
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
The ones that are useful here are:
|
|
178
|
+
|
|
179
|
+
- `QUERY_TAG`: tags the query, e.g. so you can find it in `QUERY_HISTORY`
|
|
180
|
+
- `TIMEZONE`: sets the session time zone
|
|
181
|
+
- `USE_CACHED_RESULT`: set to `false` to skip Snowflake's result cache
|
|
182
|
+
- `CLIENT_RESULT_CHUNK_SIZE`: the maximum size, in MB, of each chunk of query results
|
|
183
|
+
|
|
184
|
+
A few others need care:
|
|
185
|
+
|
|
186
|
+
- The `*_OUTPUT_FORMAT` parameters raise an `ArgumentError`: the client parses Snowflake's default date, time and timestamp encodings, and returns binary values in its default hex.
|
|
187
|
+
- `MULTI_STATEMENT_COUNT` lets you run several statements in one query, but the result is only the parent statement's summary, not each statement's rows.
|
|
188
|
+
- `ROWS_PER_RESULTSET` caps the number of rows returned.
|
|
189
|
+
|
|
190
|
+
For a full list of available parameters, refer to the Snowflake documentation: https://docs.snowflake.com/en/developer-guide/sql-api/reference
|
|
191
|
+
|
|
163
192
|
## Instrumentation
|
|
164
193
|
|
|
165
194
|
If ActiveSupport is available, this library additionally emits [notification events](https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) around queries. You can subscribe to those to track timing, query counts, etc.
|
|
@@ -9,31 +9,21 @@ module RubySnowflake
|
|
|
9
9
|
result[0] = statement_json_body["data"]
|
|
10
10
|
|
|
11
11
|
thread_pool = Concurrent::FixedThreadPool.new(num_threads)
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
futures = []
|
|
13
|
+
begin
|
|
14
|
+
partitions.each_index do |index|
|
|
14
15
|
next if index == 0 # already have the first partition
|
|
15
|
-
[index, Concurrent::Future.execute(executor: thread_pool) { retreive_proc.call(index) }]
|
|
16
|
+
futures << [index, Concurrent::Future.execute(executor: thread_pool) { retreive_proc.call(index) }]
|
|
16
17
|
end
|
|
17
|
-
.each do |entry|
|
|
18
|
-
next if entry.nil? # 0th index
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if future.reason.is_a? RubySnowflake::Error
|
|
23
|
-
raise future.reason
|
|
24
|
-
else
|
|
25
|
-
raise ConnectionStarvedError.new(
|
|
26
|
-
"A partition request timed out. This is usually do to using the client in" \
|
|
27
|
-
"multiple threads. The client uses a connection thread pool and if too many" \
|
|
28
|
-
"requests are all done in threads at the same time, threads can get starved" \
|
|
29
|
-
"of access to connections. The solution for this is to either increase the " \
|
|
30
|
-
"max_connections parameter on the client or create a new client instance" \
|
|
31
|
-
"with it's own connection pool to snowflake per thread. Rejection reason: #{future.reason.message}"
|
|
32
|
-
)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
result[index] = future.value
|
|
19
|
+
futures.each do |index, future|
|
|
20
|
+
result[index] = PartitionFuture.value!(future)
|
|
36
21
|
end
|
|
22
|
+
ensure
|
|
23
|
+
# On a failure or interrupt, skip the fetches that haven't started
|
|
24
|
+
futures.each { |_index, future| future.cancel }
|
|
25
|
+
thread_pool.shutdown # no wait: in-flight fetches finish and return their connections
|
|
26
|
+
end
|
|
37
27
|
result
|
|
38
28
|
end
|
|
39
29
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "base64"
|
|
4
|
-
require "benchmark"
|
|
5
4
|
require "bigdecimal"
|
|
6
5
|
require "concurrent"
|
|
7
6
|
require "connection_pool"
|
|
@@ -19,33 +18,17 @@ rescue LoadError
|
|
|
19
18
|
# This isn't required
|
|
20
19
|
end
|
|
21
20
|
|
|
21
|
+
require_relative "errors"
|
|
22
22
|
require_relative "client/http_connection_wrapper"
|
|
23
23
|
require_relative "client/key_pair_jwt_auth_manager"
|
|
24
24
|
require_relative "client/single_thread_in_memory_strategy"
|
|
25
25
|
require_relative "client/streaming_result_strategy"
|
|
26
26
|
require_relative "client/threaded_in_memory_strategy"
|
|
27
|
+
require_relative "partition_future"
|
|
27
28
|
require_relative "result"
|
|
28
29
|
require_relative "streaming_result"
|
|
29
30
|
|
|
30
31
|
module RubySnowflake
|
|
31
|
-
class Error < StandardError
|
|
32
|
-
def initialize(details)
|
|
33
|
-
@details = details
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def message
|
|
37
|
-
@details.to_s
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
class BadResponseError < Error ; end
|
|
42
|
-
class ConnectionError < Error ; end
|
|
43
|
-
class ConnectionStarvedError < Error ; end
|
|
44
|
-
class MissingConfig < Error ; end
|
|
45
|
-
class RetryableBadResponseError < Error ; end
|
|
46
|
-
class RequestError < Error ; end
|
|
47
|
-
class QueryTimeoutError < Error ; end
|
|
48
|
-
|
|
49
32
|
class Client
|
|
50
33
|
DEFAULT_LOGGER = Logger.new(STDOUT)
|
|
51
34
|
DEFAULT_LOG_LEVEL = Logger::INFO
|
|
@@ -71,6 +54,9 @@ module RubySnowflake
|
|
|
71
54
|
VALID_RESPONSE_CODES = %w(200 202).freeze
|
|
72
55
|
POLLING_RESPONSE_CODE = "202"
|
|
73
56
|
POLLING_INTERVAL = 2 # seconds
|
|
57
|
+
# Row parses Snowflake's default date and time encodings and returns binary as its default hex
|
|
58
|
+
OUTPUT_FORMAT_PARAMETER = /_OUTPUT_FORMAT\z/i
|
|
59
|
+
private_constant :OUTPUT_FORMAT_PARAMETER
|
|
74
60
|
|
|
75
61
|
# can't be set after initialization
|
|
76
62
|
attr_reader :connection_timeout, :max_connections, :logger, :max_threads_per_query, :thread_scale_factor, :http_retries, :query_timeout, :default_role
|
|
@@ -151,11 +137,12 @@ module RubySnowflake
|
|
|
151
137
|
@_enable_polling_queries = false
|
|
152
138
|
end
|
|
153
139
|
|
|
154
|
-
def query(query, warehouse: nil, streaming: false, database: nil, schema: nil, bindings: nil, role: nil, query_name: nil, query_timeout: nil)
|
|
140
|
+
def query(query, warehouse: nil, streaming: false, database: nil, schema: nil, bindings: nil, role: nil, query_name: nil, query_timeout: nil, parameters: nil)
|
|
155
141
|
warehouse ||= @default_warehouse
|
|
156
142
|
database ||= @default_database
|
|
157
143
|
role ||= @default_role
|
|
158
144
|
query_timeout ||= @query_timeout
|
|
145
|
+
validate_parameters!(parameters)
|
|
159
146
|
|
|
160
147
|
with_instrumentation({ database:, schema:, warehouse:, query_name: }) do
|
|
161
148
|
query_start_time = Time.now.to_i
|
|
@@ -170,6 +157,8 @@ module RubySnowflake
|
|
|
170
157
|
"role" => role,
|
|
171
158
|
"timeout" => query_timeout
|
|
172
159
|
}
|
|
160
|
+
# Omit rather than send null, which Snowflake doesn't document for parameters
|
|
161
|
+
request_body["parameters"] = parameters unless parameters.nil?
|
|
173
162
|
|
|
174
163
|
response = request_with_auth_and_headers(
|
|
175
164
|
connection,
|
|
@@ -198,6 +187,18 @@ module RubySnowflake
|
|
|
198
187
|
private_class_method :env_option
|
|
199
188
|
|
|
200
189
|
private
|
|
190
|
+
def validate_parameters!(parameters)
|
|
191
|
+
return if parameters.nil?
|
|
192
|
+
raise ArgumentError, "parameters must be a Hash, got #{parameters.class}" unless parameters.is_a?(Hash)
|
|
193
|
+
|
|
194
|
+
output_format_key = parameters.keys.find { |key| key.to_s.match?(OUTPUT_FORMAT_PARAMETER) }
|
|
195
|
+
return if output_format_key.nil?
|
|
196
|
+
|
|
197
|
+
raise ArgumentError,
|
|
198
|
+
"The #{output_format_key} parameter is not supported: this client parses Snowflake's default " \
|
|
199
|
+
"date, time and timestamp encodings, and returns binary values in its default hex"
|
|
200
|
+
end
|
|
201
|
+
|
|
201
202
|
def connection_pool
|
|
202
203
|
@connection_pool ||= ConnectionPool.new(size: @max_connections, timeout: @connection_timeout) do
|
|
203
204
|
HttpConnectionWrapper.new(hostname, port).start
|
|
@@ -225,9 +226,10 @@ module RubySnowflake
|
|
|
225
226
|
sleep: lambda {|n| 2**n }, # 1, 2, 4, 8, etc
|
|
226
227
|
on: [RetryableBadResponseError, OpenSSL::SSL::SSLError],
|
|
227
228
|
log_method: retryable_log_method) do
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
230
|
+
response = connection.request(request)
|
|
231
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
232
|
+
logger.debug { "HTTP Request time: #{elapsed}" }
|
|
231
233
|
raise_on_bad_response(response)
|
|
232
234
|
response
|
|
233
235
|
end
|
|
@@ -330,9 +332,10 @@ module RubySnowflake
|
|
|
330
332
|
)
|
|
331
333
|
end
|
|
332
334
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
335
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
336
|
+
partition_json = JSON.parse(partition_response.body, **JSON_PARSE_OPTIONS)
|
|
337
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
338
|
+
logger.debug { "JSON parsing took: #{elapsed}" }
|
|
336
339
|
partition_data = partition_json["data"]
|
|
337
340
|
|
|
338
341
|
partition_data
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubySnowflake
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
def initialize(details)
|
|
6
|
+
@details = details
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def message
|
|
10
|
+
@details.to_s
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class BadResponseError < Error ; end
|
|
15
|
+
class ConnectionError < Error ; end
|
|
16
|
+
class ConnectionStarvedError < Error ; end
|
|
17
|
+
class MissingConfig < Error ; end
|
|
18
|
+
class RetryableBadResponseError < Error ; end
|
|
19
|
+
class RequestError < Error ; end
|
|
20
|
+
class QueryTimeoutError < Error ; end
|
|
21
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "connection_pool"
|
|
4
|
+
|
|
5
|
+
require_relative "errors"
|
|
6
|
+
|
|
7
|
+
module RubySnowflake
|
|
8
|
+
module PartitionFuture
|
|
9
|
+
# Waits for a partition fetch. RubySnowflake errors pass through, pool timeouts become
|
|
10
|
+
# ConnectionStarvedError and anything else a RubySnowflake::Error, with the original as cause.
|
|
11
|
+
# Only the fetch's own error is mapped, never an interrupt raised in the waiting thread.
|
|
12
|
+
def self.value!(future)
|
|
13
|
+
future.wait
|
|
14
|
+
return future.value unless future.rejected?
|
|
15
|
+
|
|
16
|
+
reason = future.reason
|
|
17
|
+
case reason
|
|
18
|
+
when RubySnowflake::Error
|
|
19
|
+
raise reason
|
|
20
|
+
when ConnectionPool::TimeoutError
|
|
21
|
+
raise ConnectionStarvedError.new(
|
|
22
|
+
"A partition request timed out. This is usually due to using the client in " \
|
|
23
|
+
"multiple threads. The client uses a connection thread pool and if too many " \
|
|
24
|
+
"requests are all done in threads at the same time, threads can get starved " \
|
|
25
|
+
"of access to connections. The solution for this is to either increase the " \
|
|
26
|
+
"max_connections parameter on the client or create a new client instance " \
|
|
27
|
+
"with its own connection pool to snowflake per thread. Rejection reason: #{reason.message}"
|
|
28
|
+
), cause: reason
|
|
29
|
+
else
|
|
30
|
+
raise Error.new("Fetching a result partition failed: #{reason.class}: #{reason.message}"), cause: reason
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
private_constant :PartitionFuture
|
|
35
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "concurrent"
|
|
4
4
|
|
|
5
|
+
require_relative "partition_future"
|
|
5
6
|
require_relative "result"
|
|
6
7
|
|
|
7
8
|
module RubySnowflake
|
|
@@ -16,30 +17,36 @@ module RubySnowflake
|
|
|
16
17
|
|
|
17
18
|
thread_pool = Concurrent::FixedThreadPool.new 1
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
data[next_index]
|
|
23
|
-
|
|
20
|
+
begin
|
|
21
|
+
data.each_with_index do |_partition, index|
|
|
22
|
+
next_index = [index+1, data.size-1].min
|
|
23
|
+
if data[next_index].nil? # prefetch
|
|
24
|
+
data[next_index] = Concurrent::Future.execute(executor: thread_pool) do
|
|
25
|
+
@retreive_proc.call(next_index)
|
|
26
|
+
end
|
|
24
27
|
end
|
|
25
|
-
end
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
if data[index].is_a? Concurrent::Future
|
|
30
|
+
data[index] = PartitionFuture.value!(data[index]) # wait for it to finish and propagate errors
|
|
31
|
+
end
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
data[index].each do |row|
|
|
34
|
+
yield wrap_row(row)
|
|
35
|
+
end
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
# After iterating over the current partition, clear the data to release memory
|
|
38
|
+
data[index].clear
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
# Reassign to a symbol so:
|
|
41
|
+
# - When looking at the list of partitions in `data` it is easier to detect
|
|
42
|
+
# - Will raise an exception if `data.each` is attempted to be called again
|
|
43
|
+
# - It won't trigger prefetch detection as `next_index`
|
|
44
|
+
data[index] = :finished
|
|
45
|
+
end
|
|
46
|
+
ensure
|
|
47
|
+
# No wait: an early exit shouldn't block on a prefetch it won't read. The worker finishes
|
|
48
|
+
# it and exits; killing it could return a connection mid-response.
|
|
49
|
+
thread_pool.shutdown
|
|
43
50
|
end
|
|
44
51
|
end
|
|
45
52
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rb_snowflake_client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rinsed
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|
|
@@ -138,6 +138,8 @@ files:
|
|
|
138
138
|
- lib/ruby_snowflake/client/single_thread_in_memory_strategy.rb
|
|
139
139
|
- lib/ruby_snowflake/client/streaming_result_strategy.rb
|
|
140
140
|
- lib/ruby_snowflake/client/threaded_in_memory_strategy.rb
|
|
141
|
+
- lib/ruby_snowflake/errors.rb
|
|
142
|
+
- lib/ruby_snowflake/partition_future.rb
|
|
141
143
|
- lib/ruby_snowflake/result.rb
|
|
142
144
|
- lib/ruby_snowflake/row.rb
|
|
143
145
|
- lib/ruby_snowflake/streaming_result.rb
|