rb_snowflake_client 1.4.0 → 1.6.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/CHANGELOG.md +16 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +42 -15
- data/README.md +45 -5
- data/lib/ruby_snowflake/client/key_pair_jwt_auth_manager.rb +4 -4
- data/lib/ruby_snowflake/client.rb +48 -26
- data/lib/ruby_snowflake/streaming_result.rb +10 -0
- data/lib/ruby_snowflake/version.rb +1 -1
- 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: 8d4e6f5f28c211ce79c5c049125008efdbddca46ecb670f7bc724a10c11eb5ef
|
|
4
|
+
data.tar.gz: 42378b7321d2ce549b347b5f882d85af33f393a4a34e3032fa22e5bbbaa264e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33be4299bb251eb3be2ddef2301ab6e471c1a312d3a35dab3a2067d757125eec473aecf230bd84544992714e4dc7ab23b4a2129c8a460c5654eadb68de5f0109
|
|
7
|
+
data.tar.gz: 71ed8816683f48ed434fdb44515a3e92d0121d8935cc1e89c41a4accef4431ef5448c2feed0355df66cc04d17a1e88afcc7732a5a56af915d85e8345b84bd49a
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
## [1.6.0] - 2026-04-13
|
|
11
|
+
### Added
|
|
12
|
+
- Support for passing a passphrase when using an encrypted private key for JWT authentication (#166)
|
|
13
|
+
### Security
|
|
14
|
+
- Bumped `activesupport` to patch CVEs (#167)
|
|
15
|
+
|
|
16
|
+
## [1.5.0] - 2025-10-14
|
|
17
|
+
### Added
|
|
18
|
+
- Instrumentation feature added for Active Support users
|
|
19
|
+
- Added `query_timeout` as a per-query parameter, allowing timeout override on individual queries
|
|
20
|
+
### Fixed
|
|
21
|
+
- `query_timeout` now properly sends timeout parameter to Snowflake API for server-side enforcement
|
|
22
|
+
- Streaming mode now releases consumed records, fixing memory leak. Note: if you were iterating over streaming results more than once, this is a breaking change (though that was not its intended usage).
|
|
23
|
+
|
|
8
24
|
## [1.4.0] - 2025-05-01
|
|
9
25
|
### Added
|
|
10
26
|
- Enhanced Row API to implement Enumerable interface
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
rb_snowflake_client (1.
|
|
4
|
+
rb_snowflake_client (1.6.0)
|
|
5
5
|
bigdecimal (>= 3.0)
|
|
6
6
|
concurrent-ruby (>= 1.2)
|
|
7
7
|
connection_pool (>= 2.4)
|
|
@@ -13,42 +13,69 @@ PATH
|
|
|
13
13
|
GEM
|
|
14
14
|
remote: https://rubygems.org/
|
|
15
15
|
specs:
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
activesupport (8.0.5)
|
|
17
|
+
base64
|
|
18
|
+
benchmark (>= 0.3)
|
|
19
|
+
bigdecimal
|
|
20
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
21
|
+
connection_pool (>= 2.2.5)
|
|
22
|
+
drb
|
|
23
|
+
i18n (>= 1.6, < 2)
|
|
24
|
+
logger (>= 1.4.2)
|
|
25
|
+
minitest (>= 5.1)
|
|
26
|
+
securerandom (>= 0.3)
|
|
27
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
28
|
+
uri (>= 0.13.1)
|
|
29
|
+
base64 (0.3.0)
|
|
30
|
+
benchmark (0.5.0)
|
|
31
|
+
bigdecimal (4.1.1)
|
|
18
32
|
coderay (1.1.3)
|
|
19
|
-
concurrent-ruby (1.3.
|
|
20
|
-
connection_pool (
|
|
21
|
-
diff-lcs (1.6.
|
|
33
|
+
concurrent-ruby (1.3.6)
|
|
34
|
+
connection_pool (3.0.2)
|
|
35
|
+
diff-lcs (1.6.2)
|
|
22
36
|
dotenv (3.1.8)
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
drb (2.2.3)
|
|
38
|
+
i18n (1.14.8)
|
|
39
|
+
concurrent-ruby (~> 1.0)
|
|
40
|
+
json (2.15.1)
|
|
41
|
+
jwt (3.1.2)
|
|
25
42
|
base64
|
|
43
|
+
logger (1.7.0)
|
|
26
44
|
method_source (1.1.0)
|
|
45
|
+
minitest (6.0.3)
|
|
46
|
+
drb (~> 2.0)
|
|
47
|
+
prism (~> 1.5)
|
|
27
48
|
parallel (1.27.0)
|
|
49
|
+
prism (1.9.0)
|
|
28
50
|
pry (0.15.2)
|
|
29
51
|
coderay (~> 1.1)
|
|
30
52
|
method_source (~> 1.0)
|
|
31
|
-
rake (13.
|
|
53
|
+
rake (13.3.0)
|
|
32
54
|
retryable (3.0.5)
|
|
33
|
-
rspec (3.13.
|
|
55
|
+
rspec (3.13.1)
|
|
34
56
|
rspec-core (~> 3.13.0)
|
|
35
57
|
rspec-expectations (~> 3.13.0)
|
|
36
58
|
rspec-mocks (~> 3.13.0)
|
|
37
|
-
rspec-core (3.13.
|
|
59
|
+
rspec-core (3.13.5)
|
|
38
60
|
rspec-support (~> 3.13.0)
|
|
39
|
-
rspec-expectations (3.13.
|
|
61
|
+
rspec-expectations (3.13.5)
|
|
40
62
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
41
63
|
rspec-support (~> 3.13.0)
|
|
42
|
-
rspec-mocks (3.13.
|
|
64
|
+
rspec-mocks (3.13.5)
|
|
43
65
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
44
66
|
rspec-support (~> 3.13.0)
|
|
45
|
-
rspec-support (3.13.
|
|
67
|
+
rspec-support (3.13.6)
|
|
68
|
+
securerandom (0.4.1)
|
|
69
|
+
tzinfo (2.0.6)
|
|
70
|
+
concurrent-ruby (~> 1.0)
|
|
71
|
+
uri (1.1.1)
|
|
46
72
|
|
|
47
73
|
PLATFORMS
|
|
48
74
|
arm64-darwin-22
|
|
49
75
|
ruby
|
|
50
76
|
|
|
51
77
|
DEPENDENCIES
|
|
78
|
+
activesupport (~> 8.0.4, >= 8.0.4.1)
|
|
52
79
|
bundler
|
|
53
80
|
parallel
|
|
54
81
|
pry
|
|
@@ -57,4 +84,4 @@ DEPENDENCIES
|
|
|
57
84
|
rspec
|
|
58
85
|
|
|
59
86
|
BUNDLED WITH
|
|
60
|
-
|
|
87
|
+
4.0.3
|
data/README.md
CHANGED
|
@@ -48,6 +48,8 @@ Available ENV variables (see below in the config section for details)
|
|
|
48
48
|
- `SNOWFLAKE_URI`
|
|
49
49
|
- `SNOWFLAKE_PRIVATE_KEY_PATH` or `SNOWFLAKE_PRIVATE_KEY`
|
|
50
50
|
- Use either the key or the path. Key takes precedence if both are provided.
|
|
51
|
+
- `SNOWFLAKE_PRIVATE_KEY_PASSPHRASE`
|
|
52
|
+
- Optional, if you are using an encrypted private key
|
|
51
53
|
- `SNOWFLAKE_ORGANIZATION`
|
|
52
54
|
- Optional, if you leave it off, the library will authenticate with an account name of only SNOWFLAKE_ACCOUNT
|
|
53
55
|
- `SNOWFLAKE_ACCOUNT`
|
|
@@ -132,6 +134,14 @@ Queries by default use the primary role assigned to the account. If there are mu
|
|
|
132
134
|
client.query("SELECT * FROM BIGTABLE", role: "MY_ROLE")
|
|
133
135
|
```
|
|
134
136
|
|
|
137
|
+
## Query timeout
|
|
138
|
+
|
|
139
|
+
You can override the query timeout on a per-query basis. The timeout is specified in seconds and will be enforced by both Snowflake server-side and the client-side polling mechanism.
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
client.query("SELECT * FROM BIGTABLE", query_timeout: 30)
|
|
143
|
+
```
|
|
144
|
+
|
|
135
145
|
## Binding parameters
|
|
136
146
|
|
|
137
147
|
Say we have `BIGTABLE` with a `data` column of a type `VARIANT`.
|
|
@@ -140,16 +150,45 @@ Say we have `BIGTABLE` with a `data` column of a type `VARIANT`.
|
|
|
140
150
|
json_string = '{"valid": "json"}'
|
|
141
151
|
query = "insert into BIGTABLE(data) select parse_json(?)"
|
|
142
152
|
bindings = {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
153
|
+
"1" => {
|
|
154
|
+
"type" => "TEXT",
|
|
155
|
+
"value" => "Other Event"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
148
158
|
client.query(query, bindings: bindings)
|
|
149
159
|
```
|
|
150
160
|
|
|
151
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
|
|
152
162
|
|
|
163
|
+
## Instrumentation
|
|
164
|
+
|
|
165
|
+
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.
|
|
166
|
+
|
|
167
|
+
* `rb_snowflake_client.snowflake_query.finish`: published at query end
|
|
168
|
+
|
|
169
|
+
Events receive a payload with the following properties:
|
|
170
|
+
* `database`: snowflake database
|
|
171
|
+
* `schema`: snowflake schema
|
|
172
|
+
* `warehouse`: snowflake warehouse
|
|
173
|
+
* `query_id`: random UUID for the query
|
|
174
|
+
* `query_name`: argument passed to query/fetch
|
|
175
|
+
* `exception`: present if the query raised an error, see [Notifications documentation](https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html#module-ActiveSupport::Notifications-label-Subscribers) for details
|
|
176
|
+
* `exception_object`: present if the query raised an error, see [Notifications documentation](https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html#module-ActiveSupport::Notifications-label-Subscribers) for details
|
|
177
|
+
|
|
178
|
+
An example integration with [Datadog](https://www.rubydoc.info/gems/datadog) might look like this:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
ActiveSupport::Notifications.subscribe("rb_snowflake_client.snowflake_query.finish") do |name, start, finish, id, payload|
|
|
182
|
+
span = Datadog::Tracing.trace(payload[:query_name] || "snowflake_query",
|
|
183
|
+
resource: "snowflake",
|
|
184
|
+
start_time: start,
|
|
185
|
+
tags: payload,
|
|
186
|
+
type: Datadog::Tracing::Metadata::Ext::AppTypes::TYPE_DB)
|
|
187
|
+
|
|
188
|
+
span.finish(finish)
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
153
192
|
# Configuration Options
|
|
154
193
|
|
|
155
194
|
The client supports the following configuration options, each with their own getter/setter except connection pool options which must be set at construction. Additionally, all except logger can be configured with environment variables (see above, but the pattern is like: "SNOWFLAKE_HTTP_RETRIES". Configuration options can only be set on initialization through `new` or `from_env`.
|
|
@@ -228,6 +267,7 @@ or alternatively, use the client to verify:
|
|
|
228
267
|
client = RubySnowflake::Client.new(
|
|
229
268
|
"https://yourinstance.region.snowflakecomputing.com", # insert your URL here
|
|
230
269
|
File.read("secrets/my_key.pem"), # path to your private key
|
|
270
|
+
"private-key-passphrase", # your private key passphrase, if it has one (defaults to nil)
|
|
231
271
|
"snowflake-organization", # your account name (doesn't match your URL), using nil may be required depending on your snowflake account
|
|
232
272
|
"snowflake-account", # typically your subdomain
|
|
233
273
|
"snowflake-user", # Your snowflake user
|
|
@@ -8,12 +8,13 @@ module RubySnowflake
|
|
|
8
8
|
class Client
|
|
9
9
|
class KeyPairJwtAuthManager
|
|
10
10
|
# requires text of a PEM formatted RSA private key
|
|
11
|
-
def initialize(organization, account, user, private_key, jwt_token_ttl)
|
|
11
|
+
def initialize(organization, account, user, private_key, jwt_token_ttl, private_key_passphrase = nil)
|
|
12
12
|
@organization = organization
|
|
13
13
|
@account = account
|
|
14
14
|
@user = user
|
|
15
15
|
@private_key_pem = private_key
|
|
16
16
|
@jwt_token_ttl = jwt_token_ttl
|
|
17
|
+
@private_key_passphrase = private_key_passphrase
|
|
17
18
|
|
|
18
19
|
# start with an expired value to force creation
|
|
19
20
|
@token_expires_at = Time.now.to_i - 1
|
|
@@ -26,8 +27,7 @@ module RubySnowflake
|
|
|
26
27
|
@token_semaphore.acquire do
|
|
27
28
|
now = Time.now.to_i
|
|
28
29
|
@token_expires_at = now + @jwt_token_ttl
|
|
29
|
-
|
|
30
|
-
private_key = OpenSSL::PKey.read(@private_key_pem)
|
|
30
|
+
private_key = OpenSSL::PKey.read(@private_key_pem, @private_key_passphrase)
|
|
31
31
|
|
|
32
32
|
payload = {
|
|
33
33
|
:iss => "#{account_name}.#{@user.upcase}.#{public_key_fingerprint}",
|
|
@@ -56,7 +56,7 @@ module RubySnowflake
|
|
|
56
56
|
def public_key_fingerprint
|
|
57
57
|
return @public_key_fingerprint unless @public_key_fingerprint.nil?
|
|
58
58
|
|
|
59
|
-
public_key_der = OpenSSL::PKey::RSA.new(@private_key_pem).public_key.to_der
|
|
59
|
+
public_key_der = OpenSSL::PKey::RSA.new(@private_key_pem, @private_key_passphrase).public_key.to_der
|
|
60
60
|
digest = OpenSSL::Digest::SHA256.new.digest(public_key_der)
|
|
61
61
|
fingerprint = Base64.strict_encode64(digest)
|
|
62
62
|
|
|
@@ -12,6 +12,13 @@ require "retryable"
|
|
|
12
12
|
require "securerandom"
|
|
13
13
|
require "uri"
|
|
14
14
|
|
|
15
|
+
begin
|
|
16
|
+
require "active_support"
|
|
17
|
+
require "active_support/notifications"
|
|
18
|
+
rescue LoadError
|
|
19
|
+
# This isn't required
|
|
20
|
+
end
|
|
21
|
+
|
|
15
22
|
require_relative "client/http_connection_wrapper"
|
|
16
23
|
require_relative "client/key_pair_jwt_auth_manager"
|
|
17
24
|
require_relative "client/single_thread_in_memory_strategy"
|
|
@@ -90,6 +97,7 @@ module RubySnowflake
|
|
|
90
97
|
new(
|
|
91
98
|
ENV.fetch("SNOWFLAKE_URI"),
|
|
92
99
|
private_key,
|
|
100
|
+
ENV["SNOWFLAKE_PRIVATE_KEY_PASSPHRASE"],
|
|
93
101
|
ENV.fetch("SNOWFLAKE_ORGANIZATION"),
|
|
94
102
|
ENV.fetch("SNOWFLAKE_ACCOUNT"),
|
|
95
103
|
ENV.fetch("SNOWFLAKE_USER"),
|
|
@@ -109,7 +117,7 @@ module RubySnowflake
|
|
|
109
117
|
end
|
|
110
118
|
|
|
111
119
|
def initialize(
|
|
112
|
-
uri, private_key, organization, account, user, default_warehouse, default_database,
|
|
120
|
+
uri, private_key, private_key_passphrase = nil, organization, account, user, default_warehouse, default_database,
|
|
113
121
|
default_role: nil,
|
|
114
122
|
logger: DEFAULT_LOGGER,
|
|
115
123
|
log_level: DEFAULT_LOG_LEVEL,
|
|
@@ -123,7 +131,7 @@ module RubySnowflake
|
|
|
123
131
|
)
|
|
124
132
|
@base_uri = uri
|
|
125
133
|
@key_pair_jwt_auth_manager =
|
|
126
|
-
KeyPairJwtAuthManager.new(organization, account, user, private_key, jwt_token_ttl)
|
|
134
|
+
KeyPairJwtAuthManager.new(organization, account, user, private_key, jwt_token_ttl, private_key_passphrase)
|
|
127
135
|
@default_warehouse = default_warehouse
|
|
128
136
|
@default_database = default_database
|
|
129
137
|
@default_role = default_role
|
|
@@ -143,31 +151,35 @@ module RubySnowflake
|
|
|
143
151
|
@_enable_polling_queries = false
|
|
144
152
|
end
|
|
145
153
|
|
|
146
|
-
def query(query, warehouse: nil, streaming: false, database: nil, schema: nil, bindings: nil, role: nil)
|
|
154
|
+
def query(query, warehouse: nil, streaming: false, database: nil, schema: nil, bindings: nil, role: nil, query_name: nil, query_timeout: nil)
|
|
147
155
|
warehouse ||= @default_warehouse
|
|
148
156
|
database ||= @default_database
|
|
149
157
|
role ||= @default_role
|
|
158
|
+
query_timeout ||= @query_timeout
|
|
150
159
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
160
|
+
with_instrumentation({ database:, schema:, warehouse:, query_name: }) do
|
|
161
|
+
query_start_time = Time.now.to_i
|
|
162
|
+
response = nil
|
|
163
|
+
connection_pool.with do |connection|
|
|
164
|
+
request_body = {
|
|
165
|
+
"warehouse" => warehouse&.upcase,
|
|
166
|
+
"schema" => schema&.upcase,
|
|
167
|
+
"database" => database&.upcase,
|
|
168
|
+
"statement" => query,
|
|
169
|
+
"bindings" => bindings,
|
|
170
|
+
"role" => role,
|
|
171
|
+
"timeout" => query_timeout
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
response = request_with_auth_and_headers(
|
|
175
|
+
connection,
|
|
176
|
+
Net::HTTP::Post,
|
|
177
|
+
"/api/v2/statements?requestId=#{SecureRandom.uuid}&async=#{@_enable_polling_queries}",
|
|
178
|
+
request_body.to_json
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
retrieve_result_set(query_start_time, query, response, streaming, query_timeout)
|
|
169
182
|
end
|
|
170
|
-
retrieve_result_set(query_start_time, query, response, streaming)
|
|
171
183
|
end
|
|
172
184
|
|
|
173
185
|
alias fetch query
|
|
@@ -251,7 +263,7 @@ module RubySnowflake
|
|
|
251
263
|
end
|
|
252
264
|
end
|
|
253
265
|
|
|
254
|
-
def poll_for_completion_or_timeout(query_start_time, query, statement_handle)
|
|
266
|
+
def poll_for_completion_or_timeout(query_start_time, query, statement_handle, query_timeout)
|
|
255
267
|
first_data_json_body = nil
|
|
256
268
|
|
|
257
269
|
connection_pool.with do |connection|
|
|
@@ -259,7 +271,7 @@ module RubySnowflake
|
|
|
259
271
|
sleep POLLING_INTERVAL
|
|
260
272
|
|
|
261
273
|
elapsed_time = Time.now.to_i - query_start_time
|
|
262
|
-
if elapsed_time >
|
|
274
|
+
if elapsed_time > query_timeout
|
|
263
275
|
cancelled = attempt_to_cancel_and_silence_errors(connection, statement_handle)
|
|
264
276
|
raise QueryTimeoutError.new("Query timed out. Query cancelled? #{cancelled}; Duration: #{elapsed_time}; Query: '#{query}'")
|
|
265
277
|
end
|
|
@@ -287,12 +299,12 @@ module RubySnowflake
|
|
|
287
299
|
false
|
|
288
300
|
end
|
|
289
301
|
|
|
290
|
-
def retrieve_result_set(query_start_time, query, response, streaming)
|
|
302
|
+
def retrieve_result_set(query_start_time, query, response, streaming, query_timeout)
|
|
291
303
|
json_body = JSON.parse(response.body, JSON_PARSE_OPTIONS)
|
|
292
304
|
statement_handle = json_body["statementHandle"]
|
|
293
305
|
|
|
294
306
|
if response.code == POLLING_RESPONSE_CODE
|
|
295
|
-
result_response = poll_for_completion_or_timeout(query_start_time, query, statement_handle)
|
|
307
|
+
result_response = poll_for_completion_or_timeout(query_start_time, query, statement_handle, query_timeout)
|
|
296
308
|
json_body = JSON.parse(result_response.body, JSON_PARSE_OPTIONS)
|
|
297
309
|
end
|
|
298
310
|
|
|
@@ -329,5 +341,15 @@ module RubySnowflake
|
|
|
329
341
|
def number_of_threads_to_use(partition_count)
|
|
330
342
|
[[1, (partition_count / @thread_scale_factor.to_f).ceil].max, @max_threads_per_query].min
|
|
331
343
|
end
|
|
344
|
+
|
|
345
|
+
def with_instrumentation(tags, &block)
|
|
346
|
+
return block.call unless defined?(::ActiveSupport) && ::ActiveSupport
|
|
347
|
+
|
|
348
|
+
::ActiveSupport::Notifications.instrument(
|
|
349
|
+
"rb_snowflake_client.snowflake_query.finish",
|
|
350
|
+
tags.merge(query_id: SecureRandom.uuid)) do
|
|
351
|
+
block.call
|
|
352
|
+
end
|
|
353
|
+
end
|
|
332
354
|
end
|
|
333
355
|
end
|
|
@@ -27,9 +27,19 @@ module RubySnowflake
|
|
|
27
27
|
if data[index].is_a? Concurrent::Future
|
|
28
28
|
data[index] = data[index].value # wait for it to finish
|
|
29
29
|
end
|
|
30
|
+
|
|
30
31
|
data[index].each do |row|
|
|
31
32
|
yield wrap_row(row)
|
|
32
33
|
end
|
|
34
|
+
|
|
35
|
+
# After iterating over the current partition, clear the data to release memory
|
|
36
|
+
data[index].clear
|
|
37
|
+
|
|
38
|
+
# Reassign to a symbol so:
|
|
39
|
+
# - When looking at the list of partitions in `data` it is easier to detect
|
|
40
|
+
# - Will raise an exception if `data.each` is attempted to be called again
|
|
41
|
+
# - It won't trigger prefetch detection as `next_index`
|
|
42
|
+
data[index] = :finished
|
|
33
43
|
end
|
|
34
44
|
end
|
|
35
45
|
|
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.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rinsed
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-04-13 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|