Module: SpannerLib

Extended by:
FFI::Library
Defined in:
lib/spannerlib/rows.rb,
lib/spannerlib/ffi.rb,
lib/spannerlib/message_handler.rb

Overview

frozen_string_literal: true

Defined Under Namespace

Classes: GoBytes, GoString, Message, MessageHandler, Rows

Constant Summary collapse

ENV_OVERRIDE =
ENV.fetch("SPANNERLIB_PATH", nil)

Class Method Summary collapse

Class Method Details

.begin_transaction(pool_id, conn_id, proto_bytes) ⇒ Object



236
237
238
239
240
241
# File 'lib/spannerlib/ffi.rb', line 236

def self.begin_transaction(pool_id, conn_id, proto_bytes)
  with_gobytes(proto_bytes) do |gobytes|
    message = BeginTransaction(pool_id, conn_id, gobytes)
    handle_data_response(message, "BeginTransaction")
  end
end

.close_connection(pool_id, conn_id) ⇒ Object



157
158
159
160
# File 'lib/spannerlib/ffi.rb', line 157

def self.close_connection(pool_id, conn_id)
  message = CloseConnection(pool_id, conn_id)
  handle_status_response(message, "CloseConnection")
end

.close_pool(pool_id) ⇒ Object



147
148
149
150
# File 'lib/spannerlib/ffi.rb', line 147

def self.close_pool(pool_id)
  message = ClosePool(pool_id)
  handle_status_response(message, "ClosePool")
end

.close_rows(pool_id, conn_id, rows_id) ⇒ Object



284
285
286
287
# File 'lib/spannerlib/ffi.rb', line 284

def self.close_rows(pool_id, conn_id, rows_id)
  message = CloseRows(pool_id, conn_id, rows_id)
  handle_status_response(message, "CloseRows")
end

.commit(pool_id, conn_id, options = {}) ⇒ Object



243
244
245
246
247
# File 'lib/spannerlib/ffi.rb', line 243

def self.commit(pool_id, conn_id, options = {})
  proto_klass = options[:proto_klass]
  message = Commit(pool_id, conn_id)
  handle_data_response(message, "Commit", proto_klass: proto_klass)
end

.create_connection(pool_id) ⇒ Object



152
153
154
155
# File 'lib/spannerlib/ffi.rb', line 152

def self.create_connection(pool_id)
  message = CreateConnection(pool_id)
  handle_object_id_response(message, "CreateConnection")
end

.create_pool(dsn) ⇒ Object

— Ruby-friendly Wrappers —



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/spannerlib/ffi.rb', line 135

def self.create_pool(dsn)
  dsn_str = dsn.to_s.dup
  dsn_ptr = FFI::MemoryPointer.from_string(dsn_str)

  go_dsn = GoString.new
  go_dsn[:p] = dsn_ptr
  go_dsn[:len] = dsn_str.bytesize

  message = CreatePool(go_dsn)
  handle_object_id_response(message, "CreatePool")
end

.ensure_release(message) ⇒ Object



180
181
182
183
184
185
186
187
# File 'lib/spannerlib/ffi.rb', line 180

def self.ensure_release(message)
  pinner = message[:pinner]
  begin
    yield
  ensure
    release(pinner) if pinner != 0
  end
end

.execute(pool_id, conn_id, proto_bytes) ⇒ Object



254
255
256
257
258
259
# File 'lib/spannerlib/ffi.rb', line 254

def self.execute(pool_id, conn_id, proto_bytes)
  with_gobytes(proto_bytes) do |gobytes|
    message = Execute(pool_id, conn_id, gobytes)
    handle_object_id_response(message, "Execute")
  end
end

.execute_batch(pool_id, conn_id, proto_bytes, options = {}) ⇒ Object



261
262
263
264
265
266
267
# File 'lib/spannerlib/ffi.rb', line 261

def self.execute_batch(pool_id, conn_id, proto_bytes, options = {})
  proto_klass = options[:proto_klass]
  with_gobytes(proto_bytes) do |gobytes|
    message = ExecuteBatch(pool_id, conn_id, gobytes)
    handle_data_response(message, "ExecuteBatch", proto_klass: proto_klass)
  end
end

.handle_data_response(message, _func_name, options = {}) ⇒ Object



202
203
204
205
206
207
# File 'lib/spannerlib/ffi.rb', line 202

def self.handle_data_response(message, _func_name, options = {})
  proto_klass = options[:proto_klass]
  ensure_release(message) do
    MessageHandler.new(message).data(proto_klass: proto_klass)
  end
end

.handle_object_id_response(message, _func_name) ⇒ Object



189
190
191
192
193
# File 'lib/spannerlib/ffi.rb', line 189

def self.handle_object_id_response(message, _func_name)
  ensure_release(message) do
    MessageHandler.new(message).object_id
  end
end

.handle_status_response(message, _func_name) ⇒ Object



195
196
197
198
199
200
# File 'lib/spannerlib/ffi.rb', line 195

def self.handle_status_response(message, _func_name)
  ensure_release(message) do
    MessageHandler.new(message).throw_if_error!
  end
  nil
end

.library_pathObject

Build list of candidate paths (ordered): env override, platform-specific, any packaged lib, system library rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity

Raises:

  • (LoadError)


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/spannerlib/ffi.rb', line 49

def self.library_path
  if ENV_OVERRIDE && !ENV_OVERRIDE.empty?
    return ENV_OVERRIDE if File.file?(ENV_OVERRIDE)

    warn "SPANNERLIB_PATH set to #{ENV_OVERRIDE} but file not found"
  end

  lib_dir = File.expand_path(__dir__)
  ext = FFI::Platform::LIBSUFFIX

  platform = platform_dir_from_host
  if platform
    candidate = File.join(lib_dir, platform, "spannerlib.#{ext}")
    return candidate if File.exist?(candidate)
  end

  # 3) Any matching packaged binary (first match)
  glob_candidates = Dir.glob(File.join(lib_dir, "*", "spannerlib.#{ext}"))
  return glob_candidates.first unless glob_candidates.empty?

  # 4) Try loading system-wide library (so users who installed shared lib separately can use it)
  begin
    # Attempt to open system lib name; if succeeds, return bare name so ffi_lib can resolve it
    FFI::DynamicLibrary.open("spannerlib", FFI::DynamicLibrary::RTLD_LAZY | FFI::DynamicLibrary::RTLD_GLOBAL)
    return "spannerlib"
  rescue LoadError
    # This is intentional. If the system library fails to load,
    # we'll proceed to the final LoadError with all search paths.
  end

  searched = []
  searched << "ENV SPANNERLIB_PATH=#{ENV_OVERRIDE}" if ENV_OVERRIDE && !ENV_OVERRIDE.empty?
  searched << File.join(lib_dir, platform || "<detected-platform?>", "spannerlib.#{ext}")
  searched << File.join(lib_dir, "*", "spannerlib.#{ext}")

  raise LoadError, <<~ERR
    Could not locate the spannerlib native library. Tried:
      - #{searched.join("\n  - ")}
    If you are using the packaged gem, ensure the gem includes lib/spannerlib/<platform>/spannerlib.#{ext}.
    You can set SPANNERLIB_PATH to the absolute path of the library file, or install a platform-specific native gem.
  ERR
end

.metadata(pool_id, conn_id, rows_id) ⇒ Object



269
270
271
272
# File 'lib/spannerlib/ffi.rb', line 269

def self.(pool_id, conn_id, rows_id)
  message = Metadata(pool_id, conn_id, rows_id)
  handle_data_response(message, "Metadata")
end

.next(pool_id, conn_id, rows_id, max_rows, fetch_size) ⇒ Object



274
275
276
277
# File 'lib/spannerlib/ffi.rb', line 274

def self.next(pool_id, conn_id, rows_id, max_rows, fetch_size)
  message = Next(pool_id, conn_id, rows_id, max_rows, fetch_size)
  handle_data_response(message, "Next")
end

.platform_dir_from_hostObject



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/spannerlib/ffi.rb', line 33

def self.platform_dir_from_host
  host_os  = RbConfig::CONFIG["host_os"]
  host_cpu = RbConfig::CONFIG["host_cpu"]

  case host_os
  when /darwin/
    host_cpu =~ /arm|aarch64/ ? "aarch64-darwin" : "x86_64-darwin"
  when /linux/
    host_cpu =~ /arm|aarch64/ ? "aarch64-linux" : "x86_64-linux"
  when /mswin|mingw|cygwin/
    "x64-mingw32"
  end
end

.read_error_message(message) ⇒ Object

rubocop:disable Metrics/MethodLength



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/spannerlib/ffi.rb', line 210

def self.read_error_message(message)
  len = message[:length]
  ptr = message[:pointer]
  if len.positive? && !ptr.null?
    raw_bytes = ptr.read_bytes(len)
    begin
      status_proto = ::Google::Rpc::Status.decode(raw_bytes)
      "Status Proto { code: #{status_proto.code}, message: '#{status_proto.message}' }"
    rescue StandardError => e
      clean_string = raw_bytes.encode("UTF-8", invalid: :replace, undef: :replace, replace: "?").strip
      "Failed to decode Status proto (code #{message[:code]}): #{e.class}: #{e.message} | Raw: #{clean_string}"
    end
  else
    "No error message provided"
  end
end

.release(pinner) ⇒ Object



162
163
164
# File 'lib/spannerlib/ffi.rb', line 162

def self.release(pinner)
  Release(pinner)
end

.result_set_stats(pool_id, conn_id, rows_id) ⇒ Object



279
280
281
282
# File 'lib/spannerlib/ffi.rb', line 279

def self.result_set_stats(pool_id, conn_id, rows_id)
  message = ResultSetStats(pool_id, conn_id, rows_id)
  handle_data_response(message, "ResultSetStats")
end

.rollback(pool_id, conn_id) ⇒ Object



249
250
251
252
# File 'lib/spannerlib/ffi.rb', line 249

def self.rollback(pool_id, conn_id)
  message = Rollback(pool_id, conn_id)
  handle_status_response(message, "Rollback")
end

.with_gobytes(bytes) {|go_bytes| ... } ⇒ Object

Yields:

  • (go_bytes)


166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/spannerlib/ffi.rb', line 166

def self.with_gobytes(bytes)
  bytes ||= ""
  len = bytes.bytesize
  ptr = FFI::MemoryPointer.new(len)
  ptr.write_bytes(bytes, 0, len) if len.positive?

  go_bytes = GoBytes.new
  go_bytes[:p] = ptr
  go_bytes[:len] = len
  go_bytes[:cap] = len

  yield(go_bytes)
end

.write_mutations(pool_id, conn_id, proto_bytes, options = {}) ⇒ Object

rubocop:enable Metrics/MethodLength



228
229
230
231
232
233
234
# File 'lib/spannerlib/ffi.rb', line 228

def self.write_mutations(pool_id, conn_id, proto_bytes, options = {})
  proto_klass = options[:proto_klass]
  with_gobytes(proto_bytes) do |gobytes|
    message = WriteMutations(pool_id, conn_id, gobytes)
    handle_data_response(message, "WriteMutations", proto_klass: proto_klass)
  end
end