spannerlib-ruby 0.1.0.alpha1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 287861a42225f8adaa1798a29d737b323817c19c005abbbbe0b2d11ee8f2a7f8
4
+ data.tar.gz: 1ff412d54ad8567f3422d621cae5078ddf9cff11cdd0da5abeb40c80990ae549
5
+ SHA512:
6
+ metadata.gz: 2d7fab0308ec1aa0989febf32dcb3096215e2ebc356aeb944fed7a02aa334325c6c88fdbd2f55e939a5ae11f40952d1f3539a2bc06febac94b9efd649b6e784a
7
+ data.tar.gz: 8f131835872c9aacdaba9c7af151f98be51096eae2851369695b83f628b31c9d96af34fdc688fcc5f676f726b37f10d0f799bad5be75b09b2954b5345b5f94a5
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ .rspec_status
11
+ /vendor/bundle
12
+ /shared/
13
+ *.gem
14
+
15
+ .DS_Store
16
+ *.swp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,32 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ SuggestExtensions: false
4
+ Exclude:
5
+ - 'lib/spanner_pb.rb'
6
+ - 'vendor/**/*'
7
+
8
+ plugins:
9
+ - rubocop-rspec
10
+
11
+ Layout/LineLength:
12
+ Max: 150
13
+
14
+ Style/Documentation:
15
+ Enabled: false
16
+
17
+ RSpec/ExampleLength:
18
+ Enabled: false
19
+ RSpec/MultipleExpectations:
20
+ Enabled: false
21
+
22
+ # Add this block to disable the 'let' rule
23
+ RSpec/InstanceVariable:
24
+ Enabled: false
25
+ RSpec/BeforeAfterAll:
26
+ Enabled: false
27
+ RSpec/DescribeClass:
28
+ Exclude:
29
+ - 'spec/integration/**/*'
30
+
31
+ Style/StringLiterals:
32
+ EnforcedStyle: double_quotes
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "rake", "~> 13.0"
8
+
9
+ group :development, :test do
10
+ gem "rake-compiler", "~> 1.0"
11
+ gem "rspec", "~> 3.0"
12
+ gem "rubocop", require: false
13
+ gem "rubocop-rspec", require: false
14
+ end
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "spannerlib/ruby"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require "irb"
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,91 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # frozen_string_literal: true
16
+
17
+ require_relative "ffi"
18
+ require_relative "rows"
19
+
20
+ class Connection
21
+ attr_reader :pool_id, :conn_id
22
+
23
+ def initialize(pool_id, conn_id)
24
+ @pool_id = pool_id
25
+ @conn_id = conn_id
26
+ end
27
+
28
+ # Accepts either an object that responds to `to_proto` or a raw string/bytes
29
+ # containing the serialized mutation proto. We avoid requiring the protobuf
30
+ # definitions at load time so specs that don't need them can run.
31
+ def write_mutations(mutation_group)
32
+ req_bytes = if mutation_group.respond_to?(:to_proto)
33
+ mutation_group.to_proto
34
+ elsif mutation_group.is_a?(String)
35
+ mutation_group
36
+ else
37
+ mutation_group.to_s
38
+ end
39
+ SpannerLib.write_mutations(@pool_id, @conn_id, req_bytes, proto_klass: Google::Cloud::Spanner::V1::CommitResponse)
40
+ end
41
+
42
+ # Begin a read/write transaction on this connection. Accepts TransactionOptions proto or bytes.
43
+ # Returns message bytes (or nil) — higher-level parsing not implemented here.
44
+ def begin_transaction(transaction_options = nil)
45
+ bytes = if transaction_options.respond_to?(:to_proto)
46
+ transaction_options.to_proto
47
+ else
48
+ transaction_options.is_a?(String) ? transaction_options : transaction_options&.to_s
49
+ end
50
+ SpannerLib.begin_transaction(@pool_id, @conn_id, bytes)
51
+ end
52
+
53
+ # Commit the current transaction. Returns CommitResponse bytes or nil.
54
+ def commit
55
+ SpannerLib.commit(@pool_id, @conn_id, proto_klass: Google::Cloud::Spanner::V1::CommitResponse)
56
+ end
57
+
58
+ # Rollback the current transaction.
59
+ def rollback
60
+ SpannerLib.rollback(@pool_id, @conn_id)
61
+ nil
62
+ end
63
+
64
+ # Execute SQL request (expects a request object with to_proto or raw bytes). Returns message bytes (or nil).
65
+ def execute(request)
66
+ bytes = if request.respond_to?(:to_proto)
67
+ request.to_proto
68
+ else
69
+ request.is_a?(String) ? request : request.to_s
70
+ end
71
+ rows_id = SpannerLib.execute(@pool_id, @conn_id, bytes)
72
+ SpannerLib::Rows.new(self, rows_id)
73
+ end
74
+
75
+ # Execute batch DML/DDL request. Returns ExecuteBatchDmlResponse bytes (or nil).
76
+ def execute_batch(request)
77
+ bytes = if request.respond_to?(:to_proto)
78
+ request.to_proto
79
+ else
80
+ request.is_a?(String) ? request : request.to_s
81
+ end
82
+
83
+ SpannerLib.execute_batch(@pool_id, @conn_id, bytes, proto_klass: Google::Cloud::Spanner::V1::ExecuteBatchDmlResponse)
84
+ end
85
+
86
+ # Closes this connection. Any active transaction on the connection is rolled back.
87
+ def close
88
+ SpannerLib.close_connection(@pool_id, @conn_id)
89
+ nil
90
+ end
91
+ end
@@ -0,0 +1,24 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # frozen_string_literal: true
16
+
17
+ class SpannerLibException < StandardError
18
+ attr_reader :status
19
+
20
+ def initialize(msg = nil, status = nil)
21
+ super(msg)
22
+ @status = status
23
+ end
24
+ end
@@ -0,0 +1,290 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # frozen_string_literal: true
16
+
17
+ # rubocop:disable Metrics/ModuleLength
18
+
19
+ require "rubygems"
20
+ require "bundler/setup"
21
+
22
+ require "google/protobuf"
23
+ require "google/rpc/status_pb"
24
+
25
+ require "ffi"
26
+ require_relative "message_handler"
27
+
28
+ module SpannerLib
29
+ extend FFI::Library
30
+
31
+ ENV_OVERRIDE = ENV.fetch("SPANNERLIB_PATH", nil)
32
+
33
+ def self.platform_dir_from_host
34
+ host_os = RbConfig::CONFIG["host_os"]
35
+ host_cpu = RbConfig::CONFIG["host_cpu"]
36
+
37
+ case host_os
38
+ when /darwin/
39
+ host_cpu =~ /arm|aarch64/ ? "aarch64-darwin" : "x86_64-darwin"
40
+ when /linux/
41
+ host_cpu =~ /arm|aarch64/ ? "aarch64-linux" : "x86_64-linux"
42
+ when /mswin|mingw|cygwin/
43
+ "x64-mingw32"
44
+ end
45
+ end
46
+
47
+ # Build list of candidate paths (ordered): env override, platform-specific, any packaged lib, system library
48
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
49
+ def self.library_path
50
+ if ENV_OVERRIDE && !ENV_OVERRIDE.empty?
51
+ return ENV_OVERRIDE if File.file?(ENV_OVERRIDE)
52
+
53
+ warn "SPANNERLIB_PATH set to #{ENV_OVERRIDE} but file not found"
54
+ end
55
+
56
+ lib_dir = File.expand_path(__dir__)
57
+ ext = FFI::Platform::LIBSUFFIX
58
+
59
+ platform = platform_dir_from_host
60
+ if platform
61
+ candidate = File.join(lib_dir, platform, "spannerlib.#{ext}")
62
+ return candidate if File.exist?(candidate)
63
+ end
64
+
65
+ # 3) Any matching packaged binary (first match)
66
+ glob_candidates = Dir.glob(File.join(lib_dir, "*", "spannerlib.#{ext}"))
67
+ return glob_candidates.first unless glob_candidates.empty?
68
+
69
+ # 4) Try loading system-wide library (so users who installed shared lib separately can use it)
70
+ begin
71
+ # Attempt to open system lib name; if succeeds, return bare name so ffi_lib can resolve it
72
+ FFI::DynamicLibrary.open("spannerlib", FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL)
73
+ return "spannerlib"
74
+ rescue LoadError
75
+ # This is intentional. If the system library fails to load,
76
+ # we'll proceed to the final LoadError with all search paths.
77
+ end
78
+
79
+ searched = []
80
+ searched << "ENV SPANNERLIB_PATH=#{ENV_OVERRIDE}" if ENV_OVERRIDE && !ENV_OVERRIDE.empty?
81
+ searched << File.join(lib_dir, platform || "<detected-platform?>", "spannerlib.#{ext}")
82
+ searched << File.join(lib_dir, "*", "spannerlib.#{ext}")
83
+
84
+ raise LoadError, <<~ERR
85
+ Could not locate the spannerlib native library. Tried:
86
+ - #{searched.join("\n - ")}
87
+ If you are using the packaged gem, ensure the gem includes lib/spannerlib/<platform>/spannerlib.#{ext}.
88
+ You can set SPANNERLIB_PATH to the absolute path of the library file, or install a platform-specific native gem.
89
+ ERR
90
+ end
91
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
92
+
93
+ ffi_lib library_path
94
+
95
+ class GoString < FFI::Struct
96
+ layout :p, :pointer,
97
+ :len, :long
98
+ end
99
+
100
+ # GoBytes is the Ruby representation of a Go byte slice
101
+ class GoBytes < FFI::Struct
102
+ layout :p, :pointer,
103
+ :len, :long,
104
+ :cap, :long
105
+ end
106
+
107
+ # Message is the common return type for all native functions.
108
+ class Message < FFI::Struct
109
+ layout :pinner, :long_long,
110
+ :code, :int,
111
+ :objectId, :long_long,
112
+ :length, :int,
113
+ :pointer, :pointer
114
+ end
115
+
116
+ # --- Native Function Signatures ---
117
+ attach_function :CreatePool, [GoString.by_value], Message.by_value
118
+ attach_function :ClosePool, [:int64], Message.by_value
119
+ attach_function :CreateConnection, [:int64], Message.by_value
120
+ attach_function :CloseConnection, %i[int64 int64], Message.by_value
121
+ attach_function :WriteMutations, [:int64, :int64, GoBytes.by_value], Message.by_value
122
+ attach_function :BeginTransaction, [:int64, :int64, GoBytes.by_value], Message.by_value
123
+ attach_function :Commit, %i[int64 int64], Message.by_value
124
+ attach_function :Rollback, %i[int64 int64], Message.by_value
125
+ attach_function :Execute, [:int64, :int64, GoBytes.by_value], Message.by_value
126
+ attach_function :ExecuteBatch, [:int64, :int64, GoBytes.by_value], Message.by_value
127
+ attach_function :Metadata, %i[int64 int64 int64], Message.by_value
128
+ attach_function :Next, %i[int64 int64 int64 int32 int32], Message.by_value
129
+ attach_function :ResultSetStats, %i[int64 int64 int64], Message.by_value
130
+ attach_function :CloseRows, %i[int64 int64 int64], Message.by_value
131
+ attach_function :Release, [:int64], :void
132
+
133
+ # --- Ruby-friendly Wrappers ---
134
+
135
+ def self.create_pool(dsn)
136
+ dsn_str = dsn.to_s.dup
137
+ dsn_ptr = FFI::MemoryPointer.from_string(dsn_str)
138
+
139
+ go_dsn = GoString.new
140
+ go_dsn[:p] = dsn_ptr
141
+ go_dsn[:len] = dsn_str.bytesize
142
+
143
+ message = CreatePool(go_dsn)
144
+ handle_object_id_response(message, "CreatePool")
145
+ end
146
+
147
+ def self.close_pool(pool_id)
148
+ message = ClosePool(pool_id)
149
+ handle_status_response(message, "ClosePool")
150
+ end
151
+
152
+ def self.create_connection(pool_id)
153
+ message = CreateConnection(pool_id)
154
+ handle_object_id_response(message, "CreateConnection")
155
+ end
156
+
157
+ def self.close_connection(pool_id, conn_id)
158
+ message = CloseConnection(pool_id, conn_id)
159
+ handle_status_response(message, "CloseConnection")
160
+ end
161
+
162
+ def self.release(pinner)
163
+ Release(pinner)
164
+ end
165
+
166
+ def self.with_gobytes(bytes)
167
+ bytes ||= ""
168
+ len = bytes.bytesize
169
+ ptr = FFI::MemoryPointer.new(len)
170
+ ptr.write_bytes(bytes, 0, len) if len.positive?
171
+
172
+ go_bytes = GoBytes.new
173
+ go_bytes[:p] = ptr
174
+ go_bytes[:len] = len
175
+ go_bytes[:cap] = len
176
+
177
+ yield(go_bytes)
178
+ end
179
+
180
+ def self.ensure_release(message)
181
+ pinner = message[:pinner]
182
+ begin
183
+ yield
184
+ ensure
185
+ release(pinner) if pinner != 0
186
+ end
187
+ end
188
+
189
+ def self.handle_object_id_response(message, _func_name)
190
+ ensure_release(message) do
191
+ MessageHandler.new(message).object_id
192
+ end
193
+ end
194
+
195
+ def self.handle_status_response(message, _func_name)
196
+ ensure_release(message) do
197
+ MessageHandler.new(message).throw_if_error!
198
+ end
199
+ nil
200
+ end
201
+
202
+ def self.handle_data_response(message, _func_name, options = {})
203
+ proto_klass = options[:proto_klass]
204
+ ensure_release(message) do
205
+ MessageHandler.new(message).data(proto_klass: proto_klass)
206
+ end
207
+ end
208
+
209
+ # rubocop:disable Metrics/MethodLength
210
+ def self.read_error_message(message)
211
+ len = message[:length]
212
+ ptr = message[:pointer]
213
+ if len.positive? && !ptr.null?
214
+ raw_bytes = ptr.read_bytes(len)
215
+ begin
216
+ status_proto = ::Google::Rpc::Status.decode(raw_bytes)
217
+ "Status Proto { code: #{status_proto.code}, message: '#{status_proto.message}' }"
218
+ rescue StandardError => e
219
+ clean_string = raw_bytes.encode("UTF-8", invalid: :replace, undef: :replace, replace: "?").strip
220
+ "Failed to decode Status proto (code #{message[:code]}): #{e.class}: #{e.message} | Raw: #{clean_string}"
221
+ end
222
+ else
223
+ "No error message provided"
224
+ end
225
+ end
226
+ # rubocop:enable Metrics/MethodLength
227
+
228
+ def self.write_mutations(pool_id, conn_id, proto_bytes, options = {})
229
+ proto_klass = options[:proto_klass]
230
+ with_gobytes(proto_bytes) do |gobytes|
231
+ message = WriteMutations(pool_id, conn_id, gobytes)
232
+ handle_data_response(message, "WriteMutations", proto_klass: proto_klass)
233
+ end
234
+ end
235
+
236
+ def self.begin_transaction(pool_id, conn_id, proto_bytes)
237
+ with_gobytes(proto_bytes) do |gobytes|
238
+ message = BeginTransaction(pool_id, conn_id, gobytes)
239
+ handle_data_response(message, "BeginTransaction")
240
+ end
241
+ end
242
+
243
+ def self.commit(pool_id, conn_id, options = {})
244
+ proto_klass = options[:proto_klass]
245
+ message = Commit(pool_id, conn_id)
246
+ handle_data_response(message, "Commit", proto_klass: proto_klass)
247
+ end
248
+
249
+ def self.rollback(pool_id, conn_id)
250
+ message = Rollback(pool_id, conn_id)
251
+ handle_status_response(message, "Rollback")
252
+ end
253
+
254
+ def self.execute(pool_id, conn_id, proto_bytes)
255
+ with_gobytes(proto_bytes) do |gobytes|
256
+ message = Execute(pool_id, conn_id, gobytes)
257
+ handle_object_id_response(message, "Execute")
258
+ end
259
+ end
260
+
261
+ def self.execute_batch(pool_id, conn_id, proto_bytes, options = {})
262
+ proto_klass = options[:proto_klass]
263
+ with_gobytes(proto_bytes) do |gobytes|
264
+ message = ExecuteBatch(pool_id, conn_id, gobytes)
265
+ handle_data_response(message, "ExecuteBatch", proto_klass: proto_klass)
266
+ end
267
+ end
268
+
269
+ def self.metadata(pool_id, conn_id, rows_id)
270
+ message = Metadata(pool_id, conn_id, rows_id)
271
+ handle_data_response(message, "Metadata")
272
+ end
273
+
274
+ def self.next(pool_id, conn_id, rows_id, max_rows, fetch_size)
275
+ message = Next(pool_id, conn_id, rows_id, max_rows, fetch_size)
276
+ handle_data_response(message, "Next")
277
+ end
278
+
279
+ def self.result_set_stats(pool_id, conn_id, rows_id)
280
+ message = ResultSetStats(pool_id, conn_id, rows_id)
281
+ handle_data_response(message, "ResultSetStats")
282
+ end
283
+
284
+ def self.close_rows(pool_id, conn_id, rows_id)
285
+ message = CloseRows(pool_id, conn_id, rows_id)
286
+ handle_status_response(message, "CloseRows")
287
+ end
288
+ end
289
+
290
+ # rubocop:enable Metrics/ModuleLength
@@ -0,0 +1,56 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # frozen_string_literal: true
16
+
17
+ # lib/spannerlib/message_handler.rb
18
+
19
+ require "spannerlib/exceptions"
20
+
21
+ module SpannerLib
22
+ class MessageHandler
23
+ def initialize(message)
24
+ @message = message
25
+ end
26
+
27
+ def object_id
28
+ throw_if_error!
29
+ @message[:objectId]
30
+ end
31
+
32
+ # Returns the data payload from the message.
33
+ # If a proto_klass is provided, it decodes the bytes into a Protobuf object.
34
+ # Otherwise, it returns the raw bytes as a string.
35
+ def data(proto_klass: nil)
36
+ throw_if_error!
37
+
38
+ len = @message[:length]
39
+ ptr = @message[:pointer]
40
+
41
+ return (proto_klass ? proto_klass.new : "") unless len.positive? && !ptr.null?
42
+
43
+ bytes = ptr.read_string(len)
44
+
45
+ proto_klass ? proto_klass.decode(bytes) : bytes
46
+ end
47
+
48
+ def throw_if_error!
49
+ code = @message[:code]
50
+ return if code.zero?
51
+
52
+ error_msg = SpannerLib.read_error_message(@message)
53
+ raise SpannerLibException, "Call failed with code #{code}: #{error_msg}"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # frozen_string_literal: true
16
+
17
+ require_relative "ffi"
18
+ require_relative "connection"
19
+
20
+ class Pool
21
+ attr_reader :id
22
+
23
+ def initialize(id)
24
+ @id = id
25
+ @closed = false
26
+ end
27
+
28
+ # Create a new Pool given a DSN string. Raises SpannerLibException on failure.
29
+ def self.create_pool(dsn)
30
+ begin
31
+ pool_id = SpannerLib.create_pool(dsn)
32
+ rescue StandardError => e
33
+ raise SpannerLibException, e.message
34
+ end
35
+
36
+ raise SpannerLibException, "failed to create pool" if pool_id.nil? || pool_id <= 0
37
+
38
+ Pool.new(pool_id)
39
+ end
40
+
41
+ # Close this pool and free native resources.
42
+ def close
43
+ return if @closed
44
+
45
+ SpannerLib.close_pool(@id)
46
+ @closed = true
47
+ end
48
+
49
+ # Create a new Connection associated with this Pool.
50
+ def create_connection
51
+ raise SpannerLibException, "pool closed" if @closed
52
+
53
+ begin
54
+ conn_id = SpannerLib.create_connection(@id)
55
+ rescue StandardError => e
56
+ raise SpannerLibException, e.message
57
+ end
58
+
59
+ raise SpannerLibException, "failed to create connection" if conn_id.nil? || conn_id <= 0
60
+
61
+ Connection.new(@id, conn_id)
62
+ end
63
+ end