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 +7 -0
 - data/.gitignore +16 -0
 - data/.rspec +3 -0
 - data/.rubocop.yml +32 -0
 - data/Gemfile +14 -0
 - data/bin/console +11 -0
 - data/bin/setup +8 -0
 - data/lib/spannerlib/connection.rb +91 -0
 - data/lib/spannerlib/darwin-binaries/aarch64-darwin/spannerlib.dylib +0 -0
 - data/lib/spannerlib/darwin-binaries/x86_64-darwin/spannerlib.dylib +0 -0
 - data/lib/spannerlib/exceptions.rb +24 -0
 - data/lib/spannerlib/ffi.rb +290 -0
 - data/lib/spannerlib/linux-binaries/aarch64-linux/spannerlib.so +0 -0
 - data/lib/spannerlib/linux-binaries/x86_64-linux/spannerlib.so +0 -0
 - data/lib/spannerlib/message_handler.rb +56 -0
 - data/lib/spannerlib/pool.rb +63 -0
 - data/lib/spannerlib/rows.rb +67 -0
 - data/lib/spannerlib/ruby/version.rb +7 -0
 - data/lib/spannerlib/ruby.rb +24 -0
 - data/lib/spannerlib/windows-binaries/spannerlib.dll +0 -0
 - data/sig/spannerlib/ruby.rbs +6 -0
 - data/spannerlib-ruby.gemspec +52 -0
 - data/spec/integration/batch_emulator_spec.rb +165 -0
 - data/spec/integration/connection_emulator_spec.rb +151 -0
 - data/spec/integration/pool_emulator_spec.rb +42 -0
 - data/spec/spannerlib/connection_spec.rb +53 -0
 - data/spec/spannerlib/pool_spec.rb +53 -0
 - data/spec/spec_helper.rb +32 -0
 - metadata +128 -0
 
    
        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
    
    
    
        data/.rspec
    ADDED
    
    
    
        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,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
         
     | 
| 
         Binary file 
     | 
| 
         Binary file 
     | 
| 
         @@ -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
         
     | 
| 
         Binary file 
     | 
| 
         Binary file 
     | 
| 
         @@ -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
         
     |