Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions .github/workflows/integration-tests-on-emulator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ on:
push:
branches: [ main ]
pull_request:

name: Integration tests on emulator

jobs:
test:
# This is the original job, renamed for clarity
test-go:
name: Go Integration Tests
runs-on: ubuntu-latest
services:
emulator:
Expand All @@ -18,11 +22,37 @@ jobs:
with:
go-version: 1.25.x
- name: Checkout code
uses: actions/checkout@v5
- name: Run integration tests on emulator
uses: actions/checkout@v4
- name: Run Go integration tests on emulator
run: go test -race
env:
JOB_TYPE: test
SPANNER_EMULATOR_HOST: localhost:9010
SPANNER_TEST_PROJECT: emulator-test-project
SPANNER_TEST_INSTANCE: test-instance

test-ruby:
name: Ruby Integration Tests
runs-on: ubuntu-latest
services:
emulator:
image: gcr.io/cloud-spanner-emulator/emulator:latest
ports:
- 9010:9010
- 9020:9020
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3.1'
working-directory: spannerlib/wrappers/spannerlib-ruby
bundler-cache: true
- name: Compile and Run Ruby Integration Tests
working-directory: spannerlib/wrappers/spannerlib-ruby
env:
SPANNER_EMULATOR_HOST: localhost:9010
run: |
bundle exec rake compile
bundle exec rspec spec/integration/
7 changes: 7 additions & 0 deletions spannerlib/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
spannerlib.h
spannerlib.so
grpc_server
vendor/bundle
shared/
*.gem
.DS_Store
*.swp
ext/
Gemfile.lock
16 changes: 16 additions & 0 deletions spannerlib/wrappers/spannerlib-ruby/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/

.rspec_status
/vendor/bundle
/shared/
*.gem

.DS_Store
*.swp
3 changes: 3 additions & 0 deletions spannerlib/wrappers/spannerlib-ruby/.rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--format documentation
--color
--require spec_helper
32 changes: 32 additions & 0 deletions spannerlib/wrappers/spannerlib-ruby/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
AllCops:
NewCops: enable
SuggestExtensions: false
Exclude:
- 'lib/spanner_pb.rb'
- 'vendor/**/*'

plugins:
- rubocop-rspec

Layout/LineLength:
Max: 150

Style/Documentation:
Enabled: false

RSpec/ExampleLength:
Enabled: false
RSpec/MultipleExpectations:
Enabled: false

# Add this block to disable the 'let' rule
RSpec/InstanceVariable:
Enabled: false
RSpec/BeforeAfterAll:
Enabled: false
RSpec/DescribeClass:
Exclude:
- 'spec/integration/**/*'

Style/StringLiterals:
EnforcedStyle: double_quotes
14 changes: 14 additions & 0 deletions spannerlib/wrappers/spannerlib-ruby/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

source "https://rubygems.org"

gemspec

gem "rake", "~> 13.0"

group :development, :test do
gem "rake-compiler", "~> 1.0"
gem "rspec", "~> 3.0"
gem "rubocop", require: false
gem "rubocop-rspec", require: false
end
45 changes: 45 additions & 0 deletions spannerlib/wrappers/spannerlib-ruby/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# frozen_string_literal: true

require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "rubocop/rake_task"
require "rbconfig"

RSpec::Core::RakeTask.new(:spec)

RuboCop::RakeTask.new

task :compile do
go_source_dir = File.expand_path("../../shared", __dir__)
target_dir = File.expand_path("lib/spannerlib/#{RbConfig::CONFIG['arch']}", __dir__)
output_file = File.join(target_dir, "spannerlib.#{RbConfig::CONFIG['SOEXT']}")

mkdir_p target_dir

command = [
"go", "build",
"-buildmode=c-shared",
"-o", output_file,
go_source_dir
].join(" ")

puts command
sh command
end

task default: %i[compile spec rubocop]

11 changes: 11 additions & 0 deletions spannerlib/wrappers/spannerlib-ruby/bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require "bundler/setup"
require "spannerlib/ruby"

# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.

require "irb"
IRB.start(__FILE__)
8 changes: 8 additions & 0 deletions spannerlib/wrappers/spannerlib-ruby/bin/setup
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx

bundle install

# Do any other automated setup that you need to do here
106 changes: 106 additions & 0 deletions spannerlib/wrappers/spannerlib-ruby/lib/spannerlib/connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# frozen_string_literal: true

require_relative "ffi"

class Connection
attr_reader :pool_id, :conn_id

def initialize(pool_id, conn_id)
@pool_id = pool_id
@conn_id = conn_id
end

# Accepts either an object that responds to `to_proto` or a raw string/bytes
# containing the serialized mutation proto. We avoid requiring the protobuf
# definitions at load time so specs that don't need them can run.
def write_mutations(mutation_group)
req_bytes = if mutation_group.respond_to?(:to_proto)
mutation_group.to_proto
elsif mutation_group.is_a?(String)
mutation_group
else
mutation_group.to_s
end

SpannerLib.write_mutations(@pool_id, @conn_id, req_bytes)
end

# Begin a read/write transaction on this connection. Accepts TransactionOptions proto or bytes.
# Returns message bytes (or nil) — higher-level parsing not implemented here.
def begin_transaction(transaction_options = nil)
bytes = if transaction_options.respond_to?(:to_proto)
transaction_options.to_proto
else
transaction_options.is_a?(String) ? transaction_options : transaction_options&.to_s
end
SpannerLib.begin_transaction(@pool_id, @conn_id, bytes)
end

# Commit the current transaction. Returns CommitResponse bytes or nil.
def commit
SpannerLib.commit(@pool_id, @conn_id)
end

# Rollback the current transaction.
def rollback
SpannerLib.rollback(@pool_id, @conn_id)
nil
end

# Execute SQL request (expects a request object with to_proto or raw bytes). Returns message bytes (or nil).
def execute(request)
bytes = if request.respond_to?(:to_proto)
request.to_proto
else
request.is_a?(String) ? request : request.to_s
end
SpannerLib.execute(@pool_id, @conn_id, bytes)
end

# Execute batch DML/DDL request. Returns ExecuteBatchDmlResponse bytes (or nil).
def execute_batch(request)
bytes = if request.respond_to?(:to_proto)
request.to_proto
else
request.is_a?(String) ? request : request.to_s
end
SpannerLib.execute_batch(@pool_id, @conn_id, bytes)
end

# Rows helpers — return raw message bytes (caller should parse them).
def metadata(rows_id)
SpannerLib.metadata(@pool_id, @conn_id, rows_id)
end

def next_rows(rows_id, num_rows, encoding = 0)
SpannerLib.next(@pool_id, @conn_id, rows_id, num_rows, encoding)
end

def result_set_stats(rows_id)
SpannerLib.result_set_stats(@pool_id, @conn_id, rows_id)
end

def close_rows(rows_id)
SpannerLib.close_rows(@pool_id, @conn_id, rows_id)
end

# Closes this connection. Any active transaction on the connection is rolled back.
def close
SpannerLib.close_connection(@pool_id, @conn_id)
nil
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# frozen_string_literal: true

class SpannerLibException < StandardError
attr_reader :status

def initialize(msg = nil, status = nil)
super(msg)
@status = status
end
end
Loading
Loading