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.
@@ -0,0 +1,67 @@
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
+ module SpannerLib
18
+ class Rows
19
+ include Enumerable
20
+
21
+ attr_reader :id, :connection
22
+
23
+ def initialize(connection, rows_id)
24
+ @connection = connection
25
+ @id = rows_id
26
+ @closed = false
27
+ end
28
+
29
+ def each
30
+ return enum_for(:each) unless block_given?
31
+
32
+ while (row = self.next)
33
+ yield row
34
+ end
35
+ ensure
36
+ close
37
+ end
38
+
39
+ def next
40
+ return nil if @closed
41
+
42
+ row_data = SpannerLib.next(connection.pool_id, connection.conn_id, id, 1, 0)
43
+
44
+ if row_data.nil? || row_data.empty? || (row_data.respond_to?(:values) && row_data.values.empty?)
45
+ close
46
+ return nil
47
+ end
48
+
49
+ row_data
50
+ end
51
+
52
+ def metadata
53
+ SpannerLib.metadata(connection.pool_id, connection.conn_id, id)
54
+ end
55
+
56
+ def result_set_stats
57
+ SpannerLib.result_set_stats(connection.pool_id, connection.conn_id, id)
58
+ end
59
+
60
+ def close
61
+ return if @closed
62
+
63
+ SpannerLib.close_rows(connection.pool_id, connection.conn_id, id)
64
+ @closed = true
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spannerlib
4
+ module Ruby
5
+ VERSION = "0.1.0.alpha1"
6
+ end
7
+ 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
+ require_relative "ruby/version"
18
+
19
+ module Spannerlib
20
+ module Ruby
21
+ class Error < StandardError; end
22
+ # Your code goes here...
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ module Spannerlib
2
+ module Ruby
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/spannerlib/ruby/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "spannerlib-ruby"
7
+ spec.version = Spannerlib::Ruby::VERSION
8
+ spec.authors = ["Google LLC"]
9
+ spec.email = ["cloud-spanner-developers@googlegroups.com"]
10
+
11
+ spec.summary = "Ruby wrapper for the Spanner native library"
12
+ spec.description = "Lightweight Ruby FFI bindings for the Spanner native library produced from the Go implementation."
13
+ spec.homepage = "https://github.com/googleapis/go-sql-spanner/tree/main/spannerlib/wrappers/spannerlib-ruby"
14
+ spec.license = "Apache 2.0 License"
15
+ spec.required_ruby_version = ">= 3.1.0"
16
+
17
+ spec.metadata["rubygems_mfa_required"] = "true"
18
+
19
+ # Include both git-tracked files (for local development) and any built native libraries
20
+ # that exist on disk (for CI). We do this so:
21
+ # - During local development, spec.files is driven by `git ls-files` (keeps the gem manifest clean).
22
+ # - In CI we build native shared libraries into lib/spannerlib/<platform>/ and those files are
23
+ # not checked into git; we therefore also glob lib/spannerlib/** to pick up the CI-built binaries
24
+ # so the gem produced in CI actually contains the native libraries.
25
+ # - We explicitly filter out common build artifacts and non-distributable files to avoid accidentally
26
+ # packaging object files, headers, or temporary files.
27
+ # This allows us to publish a single multi-platform gem that contains prebuilt shared libraries
28
+
29
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
30
+ files = []
31
+ # prefer git-tracked files when available (local dev), but also pick up built files present on disk (CI)
32
+ files += `git ls-files -z`.split("\x0") if system("git rev-parse --is-inside-work-tree > /dev/null 2>&1")
33
+
34
+ # include any built native libs (CI places them under lib/spannerlib/)
35
+ files += Dir.glob("lib/spannerlib/**/*").select { |f| File.file?(f) }
36
+
37
+ # dedupe and reject unwanted entries
38
+ files.map! { |f| f.sub(%r{\A\./}, "") }.uniq!
39
+ files.reject do |f|
40
+ f.match(%r{^(pkg|Gemfile\.lock|.*\.gem|Rakefile|spec/|.*\.o|.*\.h)$})
41
+ end
42
+ end
43
+
44
+ spec.bindir = "exe"
45
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
46
+ spec.require_paths = ["lib"]
47
+
48
+ spec.add_dependency "ffi"
49
+ spec.add_dependency "google-cloud-spanner-v1", "~> 1.7"
50
+ spec.add_dependency "google-protobuf", "~> 3.19"
51
+ spec.add_dependency "grpc", "~> 1.60"
52
+ end
@@ -0,0 +1,165 @@
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 "spec_helper"
18
+ require "google/cloud/spanner/v1"
19
+
20
+ RSpec.describe "Batch API test", :integration do
21
+ before(:all) do
22
+ @emulator_host = ENV.fetch("SPANNER_EMULATOR_HOST", nil)
23
+ skip "SPANNER_EMULATOR_HOST not set" unless @emulator_host && !@emulator_host.empty?
24
+
25
+ require "spannerlib/pool"
26
+ @dsn = "projects/your-project-id/instances/test-instance/databases/test-database?autoConfigEmulator=true"
27
+
28
+ @pool = Pool.create_pool(@dsn)
29
+ conn = @pool.create_connection
30
+ ddl_batch_req = Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest.new(
31
+ statements: [
32
+ Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest::Statement.new(sql: "DROP TABLE IF EXISTS test_table"),
33
+ Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest::Statement.new(
34
+ sql: "CREATE TABLE test_table (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY(id)"
35
+ )
36
+ ]
37
+ )
38
+ conn.execute_batch(ddl_batch_req)
39
+ conn.close
40
+ end
41
+
42
+ after(:all) do
43
+ @pool&.close
44
+ end
45
+
46
+ before do
47
+ @conn = @pool.create_connection
48
+ delete_req = Google::Cloud::Spanner::V1::BatchWriteRequest::MutationGroup.new(
49
+ mutations: [
50
+ Google::Cloud::Spanner::V1::Mutation.new(
51
+ delete: Google::Cloud::Spanner::V1::Mutation::Delete.new(
52
+ table: "test_table",
53
+ key_set: Google::Cloud::Spanner::V1::KeySet.new(all: true)
54
+ )
55
+ )
56
+ ]
57
+ )
58
+ @conn.write_mutations(delete_req)
59
+ end
60
+
61
+ after do
62
+ @conn&.close
63
+ end
64
+
65
+ it "tests a batch DML request" do
66
+ dml_batch_req = Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest.new(
67
+ statements: [
68
+ Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest::Statement.new(
69
+ sql: "INSERT INTO test_table (id, name) VALUES (1, 'name1')"
70
+ ),
71
+ Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest::Statement.new(
72
+ sql: "INSERT INTO test_table (id, name) VALUES (2, 'name2')"
73
+ ),
74
+ Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest::Statement.new(
75
+ sql: "UPDATE test_table SET name='name3' WHERE id=1"
76
+ )
77
+ ]
78
+ )
79
+ resp = @conn.execute_batch(dml_batch_req)
80
+ expect(resp.result_sets.length).to eq 3
81
+
82
+ select_req = Google::Cloud::Spanner::V1::ExecuteSqlRequest.new(
83
+ sql: "SELECT id, name FROM test_table ORDER BY id"
84
+ )
85
+ rows = @conn.execute(select_req)
86
+ all_rows = rows.map { |row_bytes| Google::Protobuf::ListValue.decode(row_bytes) }
87
+
88
+ expect(all_rows.length).to eq 2
89
+ expect(all_rows[0].values[0].string_value).to eq "1"
90
+ expect(all_rows[0].values[1].string_value).to eq "name3"
91
+ expect(all_rows[1].values[0].string_value).to eq "2"
92
+ expect(all_rows[1].values[1].string_value).to eq "name2"
93
+ end
94
+
95
+ it "tests a batch DDL request" do
96
+ ddl_batch_req = Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest.new(
97
+ statements: [
98
+ Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest::Statement.new(
99
+ sql: "DROP TABLE IF EXISTS test_table"
100
+ ),
101
+ Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest::Statement.new(
102
+ sql: "CREATE TABLE test_table (key INT64 NOT NULL, data STRING(MAX)) PRIMARY KEY(key)"
103
+ )
104
+ ]
105
+ )
106
+
107
+ expect { @conn.execute_batch(ddl_batch_req) }.not_to raise_error
108
+
109
+ insert_req = Google::Cloud::Spanner::V1::BatchWriteRequest::MutationGroup.new(
110
+ mutations: [
111
+ Google::Cloud::Spanner::V1::Mutation.new(
112
+ insert: Google::Cloud::Spanner::V1::Mutation::Write.new(
113
+ table: "test_table",
114
+ columns: %w[key data],
115
+ values: [
116
+ Google::Protobuf::ListValue.new(values: [
117
+ Google::Protobuf::Value.new(string_value: "101"),
118
+ Google::Protobuf::Value.new(string_value: "VerificationData")
119
+ ])
120
+ ]
121
+ )
122
+ )
123
+ ]
124
+ )
125
+ expect { @conn.write_mutations(insert_req) }.not_to raise_error
126
+ end
127
+
128
+ it "queries data using parameters" do
129
+ insert_req = Google::Cloud::Spanner::V1::BatchWriteRequest::MutationGroup.new(
130
+ mutations: [
131
+ Google::Cloud::Spanner::V1::Mutation.new(
132
+ insert: Google::Cloud::Spanner::V1::Mutation::Write.new(
133
+ table: "test_table",
134
+ columns: %w[key data],
135
+ values: [
136
+ Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "1"),
137
+ Google::Protobuf::Value.new(string_value: "Alice")]),
138
+ Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "2"),
139
+ Google::Protobuf::Value.new(string_value: "Bob")])
140
+ ]
141
+ )
142
+ )
143
+ ]
144
+ )
145
+ @conn.write_mutations(insert_req)
146
+
147
+ # Execute the parameterized query.
148
+ select_req = Google::Cloud::Spanner::V1::ExecuteSqlRequest.new(
149
+ sql: "SELECT key, data FROM test_table WHERE data = @dataParam",
150
+ params: Google::Protobuf::Struct.new(
151
+ fields: {
152
+ "dataParam" => Google::Protobuf::Value.new(string_value: "Alice")
153
+ }
154
+ ),
155
+ param_types: {
156
+ "dataParam" => Google::Cloud::Spanner::V1::Type.new(code: Google::Cloud::Spanner::V1::TypeCode::STRING)
157
+ }
158
+ )
159
+ rows = @conn.execute(select_req)
160
+ all_rows = rows.map { |row_bytes| Google::Protobuf::ListValue.decode(row_bytes) }
161
+
162
+ expect(all_rows.length).to eq(1)
163
+ expect(all_rows[0].values[1].string_value).to eq("Alice")
164
+ end
165
+ end
@@ -0,0 +1,151 @@
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 "spec_helper"
18
+ require "google/cloud/spanner/v1"
19
+
20
+ RSpec.describe "Connection APIs against Spanner emulator", :integration do
21
+ before(:all) do
22
+ @emulator_host = ENV.fetch("SPANNER_EMULATOR_HOST", nil)
23
+ skip "SPANNER_EMULATOR_HOST not set" unless @emulator_host && !@emulator_host.empty?
24
+
25
+ require "spannerlib/pool"
26
+ @dsn = "projects/your-project-id/instances/test-instance/databases/test-database?autoConfigEmulator=true"
27
+
28
+ pool = Pool.create_pool(@dsn)
29
+ conn = pool.create_connection
30
+ ddl_batch_req = Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest.new(
31
+ statements: [
32
+ Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest::Statement.new(sql: "DROP TABLE IF EXISTS test_table"),
33
+ Google::Cloud::Spanner::V1::ExecuteBatchDmlRequest::Statement.new(
34
+ sql: "CREATE TABLE test_table (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY(id)"
35
+ )
36
+ ]
37
+ )
38
+ conn.execute_batch(ddl_batch_req)
39
+ conn.close
40
+ pool.close
41
+ end
42
+
43
+ before do
44
+ @pool = Pool.create_pool(@dsn)
45
+ @conn = @pool.create_connection
46
+ delete_req = Google::Cloud::Spanner::V1::BatchWriteRequest::MutationGroup.new(
47
+ mutations: [
48
+ Google::Cloud::Spanner::V1::Mutation.new(
49
+ delete: Google::Cloud::Spanner::V1::Mutation::Delete.new(
50
+ table: "test_table",
51
+ key_set: Google::Cloud::Spanner::V1::KeySet.new(all: true)
52
+ )
53
+ )
54
+ ]
55
+ )
56
+ @conn.write_mutations(delete_req)
57
+ end
58
+
59
+ after do
60
+ @conn.close
61
+ @pool.close
62
+ end
63
+
64
+ it "creates a connection pool" do
65
+ expect(@pool.id).to be > 0
66
+ expect(@conn.conn_id).to be > 0
67
+ end
68
+
69
+ it "creates two connections from the same pool" do
70
+ conn2 = @pool.create_connection
71
+ expect(@conn.conn_id).not_to eq(conn2.conn_id)
72
+ expect(conn2.conn_id).to be > 0
73
+ conn2.close
74
+ end
75
+
76
+ it "writes and reads data in a read-write transaction" do
77
+ @conn.begin_transaction
78
+ insert_data_req = Google::Cloud::Spanner::V1::BatchWriteRequest::MutationGroup.new(
79
+ mutations: [
80
+ Google::Cloud::Spanner::V1::Mutation.new(
81
+ insert: Google::Cloud::Spanner::V1::Mutation::Write.new(
82
+ table: "test_table",
83
+ columns: %w[id name],
84
+ values: [
85
+ Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "1"),
86
+ Google::Protobuf::Value.new(string_value: "Alice")]),
87
+ Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "2"),
88
+ Google::Protobuf::Value.new(string_value: "Bob")])
89
+ ]
90
+ )
91
+ )
92
+ ]
93
+ )
94
+ @conn.write_mutations(insert_data_req)
95
+ @conn.commit
96
+
97
+ select_req = Google::Cloud::Spanner::V1::ExecuteSqlRequest.new(sql: "SELECT id, name FROM test_table ORDER BY id")
98
+ rows = @conn.execute(select_req)
99
+
100
+ all_rows = rows.map { |row_bytes| Google::Protobuf::ListValue.decode(row_bytes) }
101
+
102
+ expect(all_rows.length).to eq(2)
103
+ expect(all_rows[0].values[1].string_value).to eq("Alice")
104
+ end
105
+
106
+ it "writes and reads data without an explicit transaction (autocommit)" do
107
+ insert_data_req = Google::Cloud::Spanner::V1::BatchWriteRequest::MutationGroup.new(
108
+ mutations: [
109
+ Google::Cloud::Spanner::V1::Mutation.new(
110
+ insert: Google::Cloud::Spanner::V1::Mutation::Write.new(
111
+ table: "test_table",
112
+ columns: %w[id name],
113
+ values: [
114
+ Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "3"),
115
+ Google::Protobuf::Value.new(string_value: "Charlie")]),
116
+ Google::Protobuf::ListValue.new(values: [Google::Protobuf::Value.new(string_value: "4"),
117
+ Google::Protobuf::Value.new(string_value: "David")])
118
+ ]
119
+ )
120
+ )
121
+ ]
122
+ )
123
+ @conn.write_mutations(insert_data_req)
124
+
125
+ select_req = Google::Cloud::Spanner::V1::ExecuteSqlRequest.new(sql: "SELECT id, name FROM test_table ORDER BY id")
126
+ rows = @conn.execute(select_req)
127
+
128
+ all_rows = rows.map { |row_bytes| Google::Protobuf::ListValue.decode(row_bytes) }
129
+
130
+ expect(all_rows.length).to eq(2)
131
+ expect(all_rows[0].values[1].string_value).to eq("Charlie")
132
+ end
133
+
134
+ it "raises an error when writing in a read-only transaction" do
135
+ transaction_options = Google::Cloud::Spanner::V1::TransactionOptions.new(
136
+ read_only: Google::Cloud::Spanner::V1::TransactionOptions::ReadOnly.new(strong: true)
137
+ )
138
+ @conn.begin_transaction(transaction_options)
139
+
140
+ insert_data_req = Google::Cloud::Spanner::V1::BatchWriteRequest::MutationGroup.new(
141
+ mutations: [
142
+ Google::Cloud::Spanner::V1::Mutation.new(
143
+ insert: Google::Cloud::Spanner::V1::Mutation::Write.new(table: "test_table")
144
+ )
145
+ ]
146
+ )
147
+ expect { @conn.write_mutations(insert_data_req) }.to raise_error(SpannerLibException, /read-only transactions cannot write/)
148
+
149
+ @conn.rollback
150
+ end
151
+ end
@@ -0,0 +1,42 @@
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 "spec_helper"
18
+
19
+ RSpec.describe "Connection APIs against Spanner emulator", :integration do
20
+ before(:all) do
21
+ @emulator_host = ENV.fetch("SPANNER_EMULATOR_HOST", nil)
22
+ skip "SPANNER_EMULATOR_HOST not set; skipping emulator integration tests" unless @emulator_host && !@emulator_host.empty?
23
+
24
+ begin
25
+ require "spannerlib/pool"
26
+ rescue LoadError, StandardError => e
27
+ skip "Could not load native spanner library; skipping emulator integration tests: #{e.class}: #{e.message}"
28
+ end
29
+ @dsn = "projects/your-project-id/instances/test-instance/databases/test-database?autoConfigEmulator=true"
30
+ end
31
+
32
+ it "creates a pool and a connection against the emulator" do
33
+ pool = Pool.create_pool(@dsn)
34
+ expect(pool.id).to be > 0
35
+
36
+ conn = pool.create_connection
37
+ expect(conn).to respond_to(:pool_id)
38
+ expect(conn).to respond_to(:conn_id)
39
+
40
+ expect { pool.close }.not_to raise_error
41
+ end
42
+ end
@@ -0,0 +1,53 @@
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 gove
13
+
14
+ # frozen_string_literal: true
15
+
16
+ require "spec_helper"
17
+ require "spannerlib/pool"
18
+ require "spannerlib/ffi"
19
+ require "spannerlib/exceptions"
20
+
21
+ RSpec.describe Connection do
22
+ let(:dsn) { "localhost:1234/projects/p/instances/i/databases/d?usePlainText=true" }
23
+ let(:pool) { Pool.create_pool(dsn) }
24
+
25
+ before do
26
+ allow(SpannerLib).to receive(:create_pool).and_return(1)
27
+ end
28
+
29
+ describe "creation" do
30
+ it "is created by a Pool" do
31
+ allow(SpannerLib).to receive(:create_connection).with(1).and_return(2)
32
+
33
+ # The object under test is the one returned by `pool.create_connection`
34
+ conn = pool.create_connection
35
+
36
+ expect(conn).to be_a(described_class)
37
+ expect(conn.conn_id).to eq(2)
38
+ expect(conn.pool_id).to eq(1)
39
+ end
40
+
41
+ it "raises a SpannerLibException when the FFI call fails" do
42
+ allow(SpannerLib).to receive(:create_connection).with(1).and_raise(StandardError.new("boom"))
43
+
44
+ expect { pool.create_connection }.to raise_error(SpannerLibException)
45
+ end
46
+
47
+ it "raises when the FFI call returns a non-positive id" do
48
+ allow(SpannerLib).to receive(:create_connection).with(1).and_return(0)
49
+
50
+ expect { pool.create_connection }.to raise_error(SpannerLibException)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,53 @@
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 "spec_helper"
18
+ require "spannerlib/pool"
19
+ require "spannerlib/ffi"
20
+ require "spannerlib/exceptions"
21
+
22
+ RSpec.describe Pool do
23
+ let(:dsn) { "localhost:1234/projects/p/instances/i/databases/d?usePlainText=true" }
24
+
25
+ describe ".create_pool" do
26
+ it "creates a pool and returns an object with id > 0" do
27
+ allow(SpannerLib).to receive(:create_pool).with(dsn).and_return(42)
28
+
29
+ pool = described_class.create_pool(dsn)
30
+
31
+ expect(pool).to be_a(described_class)
32
+ expect(pool.id).to be > 0
33
+ end
34
+
35
+ it "raises a SpannerLibException when create_session/create_pool fails" do
36
+ allow(SpannerLib).to receive(:create_pool).with(dsn).and_raise(StandardError.new("Not allowed"))
37
+
38
+ expect { described_class.create_pool(dsn) }.to raise_error(SpannerLibException)
39
+ end
40
+
41
+ it "raises when create_pool returns nil" do
42
+ allow(SpannerLib).to receive(:create_pool).with(dsn).and_return(nil)
43
+
44
+ expect { described_class.create_pool(dsn) }.to raise_error(SpannerLibException)
45
+ end
46
+
47
+ it "raises when create_pool returns a non-positive id" do
48
+ allow(SpannerLib).to receive(:create_pool).with(dsn).and_return(0)
49
+
50
+ expect { described_class.create_pool(dsn) }.to raise_error(SpannerLibException)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,32 @@
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 "rubygems"
18
+ require "bundler/setup"
19
+
20
+ require "spannerlib/ruby"
21
+
22
+ RSpec.configure do |config|
23
+ # Enable flags like --only-failures and --next-failure
24
+ config.example_status_persistence_file_path = ".rspec_status"
25
+
26
+ # Disable RSpec exposing methods globally on `Module` and `main`
27
+ config.disable_monkey_patching!
28
+
29
+ config.expect_with :rspec do |c|
30
+ c.syntax = :expect
31
+ end
32
+ end