tina4-ultipa 0.1.0 → 0.2.0
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 +4 -4
- data/lib/tina4_ultipa/client.rb +48 -56
- data/lib/tina4_ultipa/codec.rb +2 -0
- data/lib/tina4_ultipa/grpc.rb +226 -0
- data/lib/tina4_ultipa/pb.rb +426 -0
- data/lib/tina4_ultipa/version.rb +1 -1
- data/tina4-ultipa.gemspec +5 -2
- metadata +7 -21
- data/lib/tina4_ultipa/gen/gqldb_pb.rb +0 -23
- data/lib/tina4_ultipa/gen/gqldb_services_pb.rb +0 -36
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: db28f1b774ee4b89511ce92be09703eafcee240f8d365434b65b33aa7b68ac15
|
|
4
|
+
data.tar.gz: 1a35099dd84422f8047c1f3cb5867c1083c6a454635a83019e7f5bc87b71059a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57a742e80ccc65b03794cf907ac37dc3cd5d723fcb18a37c2e25cb1375d57a327a17ab949e54e6de73f612ed9de61c01a240b6bb1f55b9230998a9d39ae43f36
|
|
7
|
+
data.tar.gz: 41b7a417fbe6a8eaceb481a26f7cc556cea14fba09025ff94419a31c36f3e62fe5544ef7d6d752d5142c6937851e9c2784090672139c39d2b58f46584f341190
|
data/lib/tina4_ultipa/client.rb
CHANGED
|
@@ -6,17 +6,16 @@
|
|
|
6
6
|
# then that id rides every call as the gRPC metadata header `session-id`.
|
|
7
7
|
# Query: QueryService.Gql(GqlRequest) -> GqlResponse. This is the whole surface the
|
|
8
8
|
# Tina4 graph adapter needs; node/edge/traverse are built in GQL on top of query.
|
|
9
|
+
#
|
|
10
|
+
# Wire stack (pure Ruby, no C extensions, no google-protobuf):
|
|
11
|
+
# * `http-2` gem drives HTTP/2 framing (HPACK, streams, flow control)
|
|
12
|
+
# * lib/tina4_ultipa/grpc.rb wraps that in gRPC framing (unary_unary only)
|
|
13
|
+
# * lib/tina4_ultipa/pb.rb hand-rolls proto3 for the 8 gqldb messages
|
|
9
14
|
|
|
10
15
|
require "uri"
|
|
11
|
-
require "grpc"
|
|
12
|
-
|
|
13
|
-
# The generated stubs `require 'gqldb_pb'` / `require 'gqldb_services_pb'` by bare
|
|
14
|
-
# name, so put their directory on the load path before requiring them.
|
|
15
|
-
_gen_dir = File.expand_path("gen", __dir__)
|
|
16
|
-
$LOAD_PATH.unshift(_gen_dir) unless $LOAD_PATH.include?(_gen_dir)
|
|
17
|
-
require "gqldb_pb"
|
|
18
|
-
require "gqldb_services_pb"
|
|
19
16
|
|
|
17
|
+
require_relative "pb"
|
|
18
|
+
require_relative "grpc"
|
|
20
19
|
require_relative "codec"
|
|
21
20
|
require_relative "errors"
|
|
22
21
|
|
|
@@ -38,14 +37,9 @@ module Tina4Ultipa
|
|
|
38
37
|
@warnings = resp.warnings.to_a
|
|
39
38
|
@current_graph = resp.current_graph
|
|
40
39
|
d = resp.dml_stats
|
|
41
|
-
@dml_stats =
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
set_nodes: d.set_nodes, set_edges: d.set_edges }
|
|
45
|
-
else
|
|
46
|
-
{ inserted_nodes: 0, inserted_edges: 0, deleted_nodes: 0,
|
|
47
|
-
deleted_edges: 0, set_nodes: 0, set_edges: 0 }
|
|
48
|
-
end
|
|
40
|
+
@dml_stats = { inserted_nodes: d.inserted_nodes, inserted_edges: d.inserted_edges,
|
|
41
|
+
deleted_nodes: d.deleted_nodes, deleted_edges: d.deleted_edges,
|
|
42
|
+
set_nodes: d.set_nodes, set_edges: d.set_edges }
|
|
49
43
|
end
|
|
50
44
|
|
|
51
45
|
# Rows as hashes keyed by column name.
|
|
@@ -85,8 +79,6 @@ module Tina4Ultipa
|
|
|
85
79
|
@connect_timeout = connect_timeout.to_f
|
|
86
80
|
@use_tls = use_tls
|
|
87
81
|
@channel = nil
|
|
88
|
-
@session = nil
|
|
89
|
-
@query = nil
|
|
90
82
|
@session_id = nil
|
|
91
83
|
@server_version = nil
|
|
92
84
|
end
|
|
@@ -112,25 +104,38 @@ module Tina4Ultipa
|
|
|
112
104
|
def connect
|
|
113
105
|
return self unless @session_id.nil?
|
|
114
106
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
107
|
+
@channel = GrpcClient::Channel.new(
|
|
108
|
+
@host, @port, use_tls: @use_tls, connect_timeout: @connect_timeout,
|
|
109
|
+
)
|
|
118
110
|
started = monotonic
|
|
119
|
-
|
|
111
|
+
begin
|
|
112
|
+
@channel.connect
|
|
113
|
+
rescue GrpcClient::ConnectTimeout => e
|
|
114
|
+
elapsed = monotonic - started
|
|
115
|
+
close
|
|
116
|
+
raise ConnectError.new(@host, @port, elapsed, cause: e)
|
|
117
|
+
end
|
|
120
118
|
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
req = Gqldb::LoginRequest.new(
|
|
120
|
+
username: @username || "", password: @password || "",
|
|
121
|
+
default_graph: @graph,
|
|
122
|
+
)
|
|
123
123
|
begin
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
deadline: Time.now + @connect_timeout,
|
|
124
|
+
resp_bytes = @channel.unary(
|
|
125
|
+
"/gqldb.SessionService/Login",
|
|
126
|
+
Gqldb::LoginRequest.encode(req),
|
|
127
|
+
timeout: @connect_timeout,
|
|
129
128
|
)
|
|
130
|
-
rescue
|
|
129
|
+
rescue GrpcClient::RpcError => e
|
|
131
130
|
close
|
|
132
131
|
raise wrap(e, "login failed")
|
|
132
|
+
rescue GrpcClient::ConnectTimeout => e
|
|
133
|
+
elapsed = monotonic - started
|
|
134
|
+
close
|
|
135
|
+
raise ConnectError.new(@host, @port, elapsed, cause: e)
|
|
133
136
|
end
|
|
137
|
+
|
|
138
|
+
resp = Gqldb::LoginResponse.decode(resp_bytes)
|
|
134
139
|
@session_id = resp.session_id
|
|
135
140
|
@server_version = resp.server_version
|
|
136
141
|
self
|
|
@@ -152,11 +157,16 @@ module Tina4Ultipa
|
|
|
152
157
|
timeout: timeout ? timeout.to_i : 0,
|
|
153
158
|
)
|
|
154
159
|
begin
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
resp_bytes = @channel.unary(
|
|
161
|
+
"/gqldb.QueryService/Gql",
|
|
162
|
+
Gqldb::GqlRequest.encode(req),
|
|
163
|
+
metadata: { "session-id" => @session_id.to_s },
|
|
164
|
+
timeout: timeout ? timeout.to_f : nil,
|
|
165
|
+
)
|
|
166
|
+
rescue GrpcClient::RpcError => e
|
|
157
167
|
raise wrap(e, "gql failed")
|
|
158
168
|
end
|
|
159
|
-
Result.new(
|
|
169
|
+
Result.new(Gqldb::GqlResponse.decode(resp_bytes))
|
|
160
170
|
end
|
|
161
171
|
|
|
162
172
|
# Run a writing GQL statement (read_only = false).
|
|
@@ -165,11 +175,12 @@ module Tina4Ultipa
|
|
|
165
175
|
end
|
|
166
176
|
|
|
167
177
|
def close
|
|
168
|
-
@channel
|
|
178
|
+
@channel&.close
|
|
169
179
|
rescue StandardError
|
|
170
180
|
# best effort - the channel is being torn down regardless
|
|
171
181
|
ensure
|
|
172
|
-
@channel =
|
|
182
|
+
@channel = nil
|
|
183
|
+
@session_id = nil
|
|
173
184
|
end
|
|
174
185
|
|
|
175
186
|
private
|
|
@@ -178,28 +189,9 @@ module Tina4Ultipa
|
|
|
178
189
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
179
190
|
end
|
|
180
191
|
|
|
181
|
-
def wait_for_ready(channel, started)
|
|
182
|
-
states = GRPC::Core::ConnectivityStates
|
|
183
|
-
deadline = Time.now + @connect_timeout
|
|
184
|
-
state = channel.connectivity_state(true)
|
|
185
|
-
until state == states::READY
|
|
186
|
-
break if Time.now >= deadline
|
|
187
|
-
|
|
188
|
-
channel.watch_connectivity_state(state, deadline)
|
|
189
|
-
state = channel.connectivity_state(true)
|
|
190
|
-
end
|
|
191
|
-
return if state == states::READY
|
|
192
|
-
|
|
193
|
-
elapsed = monotonic - started
|
|
194
|
-
close
|
|
195
|
-
raise ConnectError.new(@host, @port, elapsed)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
192
|
def wrap(rpc_error, prefix)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
code = rpc_error.class.name.to_s.split("::").last
|
|
202
|
-
Error.new("#{prefix}: #{detail}", code: code, cause: rpc_error)
|
|
193
|
+
code = rpc_error.respond_to?(:status_name) ? rpc_error.status_name : nil
|
|
194
|
+
Error.new("#{prefix}: #{rpc_error.message}", code: code, cause: rpc_error)
|
|
203
195
|
end
|
|
204
196
|
end
|
|
205
197
|
end
|
data/lib/tina4_ultipa/codec.rb
CHANGED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "socket"
|
|
4
|
+
require "openssl"
|
|
5
|
+
require "http/2"
|
|
6
|
+
|
|
7
|
+
# Minimal gRPC-over-HTTP/2 client for this driver's 2 unary RPCs.
|
|
8
|
+
#
|
|
9
|
+
# We ride the pure-Ruby `http-2` gem (no C extensions, no google-protobuf
|
|
10
|
+
# transitive dep) and wrap it in the gRPC framing convention:
|
|
11
|
+
#
|
|
12
|
+
# +--------+------------+---------+
|
|
13
|
+
# | 0x00 | u32 BE len | payload | (compressed flag = 0 = uncompressed)
|
|
14
|
+
# +--------+------------+---------+
|
|
15
|
+
#
|
|
16
|
+
# The response comes back the same way, with the RPC outcome in HTTP/2
|
|
17
|
+
# trailers as `grpc-status: N` and optionally `grpc-message: text`.
|
|
18
|
+
#
|
|
19
|
+
# Only unary_unary is supported - streaming RPCs (server/client/bidi) are not
|
|
20
|
+
# used by gqldb's Login/Gql surface. Adding them is a self-contained extension.
|
|
21
|
+
|
|
22
|
+
module Tina4Ultipa
|
|
23
|
+
module GrpcClient
|
|
24
|
+
# Raised by Channel#unary when the peer returns a non-OK grpc-status trailer.
|
|
25
|
+
class RpcError < StandardError
|
|
26
|
+
attr_reader :status_code, :status_name
|
|
27
|
+
|
|
28
|
+
GRPC_STATUS_NAMES = {
|
|
29
|
+
0 => "OK", 1 => "CANCELLED", 2 => "UNKNOWN", 3 => "INVALID_ARGUMENT",
|
|
30
|
+
4 => "DEADLINE_EXCEEDED", 5 => "NOT_FOUND", 6 => "ALREADY_EXISTS",
|
|
31
|
+
7 => "PERMISSION_DENIED", 8 => "RESOURCE_EXHAUSTED", 9 => "FAILED_PRECONDITION",
|
|
32
|
+
10 => "ABORTED", 11 => "OUT_OF_RANGE", 12 => "UNIMPLEMENTED",
|
|
33
|
+
13 => "INTERNAL", 14 => "UNAVAILABLE", 15 => "DATA_LOSS",
|
|
34
|
+
16 => "UNAUTHENTICATED",
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
def initialize(status_code, message)
|
|
38
|
+
@status_code = status_code
|
|
39
|
+
@status_name = GRPC_STATUS_NAMES[status_code] || "STATUS_#{status_code}"
|
|
40
|
+
super("gRPC #{@status_name}: #{message}")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Raised when the socket connect / TLS handshake times out. Callers translate
|
|
45
|
+
# this into their own ConnectError with the elapsed time.
|
|
46
|
+
class ConnectTimeout < StandardError; end
|
|
47
|
+
|
|
48
|
+
# A single HTTP/2 connection to a gqldb host. Not thread-safe; use one
|
|
49
|
+
# Channel per Client instance.
|
|
50
|
+
class Channel
|
|
51
|
+
READ_CHUNK = 16_384
|
|
52
|
+
|
|
53
|
+
def initialize(host, port, use_tls: false, connect_timeout: 10.0)
|
|
54
|
+
@host = host
|
|
55
|
+
@port = port.to_i
|
|
56
|
+
@use_tls = use_tls
|
|
57
|
+
@connect_timeout = connect_timeout.to_f
|
|
58
|
+
@socket = nil
|
|
59
|
+
@client = nil
|
|
60
|
+
@closed = false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def connect
|
|
64
|
+
return self if @socket
|
|
65
|
+
|
|
66
|
+
deadline = monotonic + @connect_timeout
|
|
67
|
+
|
|
68
|
+
# Plain TCP connect with a wall-clock deadline. Socket.tcp handles
|
|
69
|
+
# IPv4/IPv6 fallback; a bare TCPSocket does not.
|
|
70
|
+
begin
|
|
71
|
+
@socket = Socket.tcp(@host, @port, connect_timeout: @connect_timeout)
|
|
72
|
+
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH,
|
|
73
|
+
Errno::ENETUNREACH, SocketError => e
|
|
74
|
+
raise ConnectTimeout, "tcp: #{e.class}: #{e.message}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if @use_tls
|
|
78
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
|
79
|
+
ctx.alpn_protocols = ["h2"]
|
|
80
|
+
@socket = OpenSSL::SSL::SSLSocket.new(@socket, ctx)
|
|
81
|
+
@socket.sync_close = true
|
|
82
|
+
@socket.hostname = @host
|
|
83
|
+
begin
|
|
84
|
+
@socket.connect
|
|
85
|
+
rescue OpenSSL::SSL::SSLError => e
|
|
86
|
+
raise ConnectTimeout, "tls: #{e.message}"
|
|
87
|
+
end
|
|
88
|
+
if @socket.respond_to?(:alpn_protocol) && @socket.alpn_protocol != "h2"
|
|
89
|
+
raise ConnectTimeout, "alpn: server did not select h2"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# http-2's Client emits :frame events with encoded HTTP/2 frames.
|
|
94
|
+
# Write them straight to the socket. All inbound bytes get fed back
|
|
95
|
+
# via `client << data`, which raises the state-machine events on
|
|
96
|
+
# the stream we open. The client preface + initial SETTINGS fires
|
|
97
|
+
# automatically the first time we emit a frame in `unary`, so there
|
|
98
|
+
# is nothing to do here beyond having a live socket + a Client
|
|
99
|
+
# instance ready to receive callbacks.
|
|
100
|
+
@client = HTTP2::Client.new
|
|
101
|
+
@client.on(:frame) { |bytes| @socket.write(bytes) }
|
|
102
|
+
self
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# unary(method, payload_bytes, metadata:, timeout:) -> response payload bytes
|
|
106
|
+
#
|
|
107
|
+
# Raises RpcError on non-OK grpc-status, ConnectTimeout on connect/read
|
|
108
|
+
# deadline overrun.
|
|
109
|
+
def unary(method, payload, metadata: {}, timeout: nil)
|
|
110
|
+
connect
|
|
111
|
+
deadline = timeout ? monotonic + timeout.to_f : nil
|
|
112
|
+
|
|
113
|
+
headers = {
|
|
114
|
+
":method" => "POST",
|
|
115
|
+
":scheme" => @use_tls ? "https" : "http",
|
|
116
|
+
":path" => method,
|
|
117
|
+
":authority" => "#{@host}:#{@port}",
|
|
118
|
+
"content-type" => "application/grpc+proto",
|
|
119
|
+
"te" => "trailers",
|
|
120
|
+
"user-agent" => "tina4-ultipa-ruby/0.2 http-2",
|
|
121
|
+
}
|
|
122
|
+
metadata.each { |k, v| headers[k.to_s.downcase] = v.to_s }
|
|
123
|
+
|
|
124
|
+
stream = @client.new_stream
|
|
125
|
+
|
|
126
|
+
resp_status = nil
|
|
127
|
+
resp_message = nil
|
|
128
|
+
resp_body = "".b
|
|
129
|
+
stream_closed = false
|
|
130
|
+
rst_reason = nil
|
|
131
|
+
|
|
132
|
+
stream.on(:headers) do |h|
|
|
133
|
+
h.each do |k, v|
|
|
134
|
+
case k
|
|
135
|
+
when "grpc-status" then resp_status = v.to_i
|
|
136
|
+
when "grpc-message" then resp_message = v
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
stream.on(:data) { |d| resp_body << d.b }
|
|
141
|
+
stream.on(:close) { stream_closed = true }
|
|
142
|
+
stream.on(:reset) { |code| rst_reason = code; stream_closed = true }
|
|
143
|
+
|
|
144
|
+
stream.headers(headers, end_stream: false)
|
|
145
|
+
stream.data(frame_encode(payload), end_stream: true)
|
|
146
|
+
|
|
147
|
+
drain_until(deadline) { stream_closed }
|
|
148
|
+
|
|
149
|
+
if rst_reason
|
|
150
|
+
raise RpcError.new(2, "stream reset (#{rst_reason})")
|
|
151
|
+
end
|
|
152
|
+
# No grpc-status trailer at all is a protocol violation (or the server
|
|
153
|
+
# closed the connection); surface as UNKNOWN so the caller sees why.
|
|
154
|
+
if resp_status.nil?
|
|
155
|
+
raise RpcError.new(2, "no grpc-status trailer (server may have closed)")
|
|
156
|
+
end
|
|
157
|
+
unless resp_status.zero?
|
|
158
|
+
raise RpcError.new(resp_status, resp_message || "")
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
frame_decode(resp_body)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def close
|
|
165
|
+
return if @closed
|
|
166
|
+
|
|
167
|
+
@closed = true
|
|
168
|
+
@socket&.close rescue nil
|
|
169
|
+
@socket = nil
|
|
170
|
+
@client = nil
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
private
|
|
174
|
+
|
|
175
|
+
def monotonic
|
|
176
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Drive the read side until the block returns true or the deadline elapses.
|
|
180
|
+
def drain_until(deadline)
|
|
181
|
+
loop do
|
|
182
|
+
return if yield
|
|
183
|
+
|
|
184
|
+
remaining = deadline ? deadline - monotonic : nil
|
|
185
|
+
raise ConnectTimeout, "read deadline" if remaining && remaining <= 0
|
|
186
|
+
|
|
187
|
+
ready = IO.select([@socket], nil, nil, remaining)
|
|
188
|
+
raise ConnectTimeout, "read deadline" if ready.nil?
|
|
189
|
+
|
|
190
|
+
chunk = read_available
|
|
191
|
+
break if chunk.nil? || chunk.empty?
|
|
192
|
+
|
|
193
|
+
@client << chunk
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def read_available
|
|
198
|
+
# SSLSocket buffers ciphertext internally; readpartial returns whatever
|
|
199
|
+
# plaintext is available. On plain TCP, readpartial blocks until any
|
|
200
|
+
# byte arrives (we already selected, so at least one is ready).
|
|
201
|
+
@socket.readpartial(READ_CHUNK)
|
|
202
|
+
rescue EOFError
|
|
203
|
+
nil
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# gRPC frame = 1 byte flag (0 = uncompressed) + u32 BE length + payload.
|
|
207
|
+
def frame_encode(payload)
|
|
208
|
+
"\x00".b + [payload.bytesize].pack("N") + payload.b
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Decode ONE gRPC frame. Unary responses ship exactly one frame; if the
|
|
212
|
+
# server ever streams multiple frames back, extend this to loop.
|
|
213
|
+
def frame_decode(bytes)
|
|
214
|
+
raise RpcError.new(2, "short gRPC frame (#{bytes.bytesize} bytes)") if bytes.bytesize < 5
|
|
215
|
+
|
|
216
|
+
_flag = bytes.getbyte(0)
|
|
217
|
+
len = bytes.byteslice(1, 4).unpack1("N")
|
|
218
|
+
if bytes.bytesize < 5 + len
|
|
219
|
+
raise RpcError.new(2, "truncated gRPC frame (want #{5 + len}, got #{bytes.bytesize})")
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
bytes.byteslice(5, len).b
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Hand-rolled proto3 wire codec for the gqldb messages this driver uses.
|
|
4
|
+
#
|
|
5
|
+
# The `grpc` gem calls MessageClass.encode(msg) and MessageClass.decode(bytes)
|
|
6
|
+
# on whatever class we register with the stub. We supply those two class
|
|
7
|
+
# methods so grpc has no idea protobuf isn't in the loop. No google-protobuf
|
|
8
|
+
# runtime, no descriptor pool, no generated stubs.
|
|
9
|
+
#
|
|
10
|
+
# What we speak:
|
|
11
|
+
# Envelopes: LoginRequest / LoginResponse / GqlRequest / GqlResponse
|
|
12
|
+
# Nested: Parameter / TypedValue / Row / DmlStats
|
|
13
|
+
# Enum: PropertyType (integer constants + symbol lookup for codec parity)
|
|
14
|
+
#
|
|
15
|
+
# Proto3 rules we honour:
|
|
16
|
+
# * default scalar values are NOT written to the wire
|
|
17
|
+
# * unknown fields on read are skipped (server may add fields; we ignore them)
|
|
18
|
+
# * repeated scalar strings/bytes/messages are NOT packed (one tag per element)
|
|
19
|
+
# * repeated numeric scalars would be packed - this schema has none
|
|
20
|
+
#
|
|
21
|
+
# NOT implemented (gqldb.proto does not use them; adding any is a self-contained
|
|
22
|
+
# ~15-line extension):
|
|
23
|
+
# oneof, map<>, Any, packed scalars, groups, edition-2023 features.
|
|
24
|
+
|
|
25
|
+
module Gqldb
|
|
26
|
+
# PropertyType. Integer constants + symbol<->int lookups. Codec speaks symbols,
|
|
27
|
+
# wire speaks integers.
|
|
28
|
+
module PropertyType
|
|
29
|
+
NAMES = {
|
|
30
|
+
0 => :PROPERTY_TYPE_UNSET,
|
|
31
|
+
1 => :PROPERTY_TYPE_INT32,
|
|
32
|
+
2 => :PROPERTY_TYPE_UINT32,
|
|
33
|
+
3 => :PROPERTY_TYPE_INT64,
|
|
34
|
+
4 => :PROPERTY_TYPE_UINT64,
|
|
35
|
+
5 => :PROPERTY_TYPE_FLOAT,
|
|
36
|
+
6 => :PROPERTY_TYPE_DOUBLE,
|
|
37
|
+
7 => :PROPERTY_TYPE_STRING,
|
|
38
|
+
8 => :PROPERTY_TYPE_DATETIME,
|
|
39
|
+
9 => :PROPERTY_TYPE_TIMESTAMP,
|
|
40
|
+
10 => :PROPERTY_TYPE_TEXT,
|
|
41
|
+
11 => :PROPERTY_TYPE_BLOB,
|
|
42
|
+
12 => :PROPERTY_TYPE_POINT,
|
|
43
|
+
13 => :PROPERTY_TYPE_DECIMAL,
|
|
44
|
+
14 => :PROPERTY_TYPE_LIST,
|
|
45
|
+
15 => :PROPERTY_TYPE_SET,
|
|
46
|
+
16 => :PROPERTY_TYPE_MAP,
|
|
47
|
+
17 => :PROPERTY_TYPE_NULL,
|
|
48
|
+
18 => :PROPERTY_TYPE_BOOL,
|
|
49
|
+
19 => :PROPERTY_TYPE_LOCAL_DATETIME,
|
|
50
|
+
20 => :PROPERTY_TYPE_ZONED_DATETIME,
|
|
51
|
+
21 => :PROPERTY_TYPE_DATE,
|
|
52
|
+
22 => :PROPERTY_TYPE_ZONED_TIME,
|
|
53
|
+
23 => :PROPERTY_TYPE_LOCAL_TIME,
|
|
54
|
+
24 => :PROPERTY_TYPE_YEAR_TO_MONTH,
|
|
55
|
+
25 => :PROPERTY_TYPE_DAY_TO_SECOND,
|
|
56
|
+
26 => :PROPERTY_TYPE_RECORD,
|
|
57
|
+
27 => :PROPERTY_TYPE_POINT3D,
|
|
58
|
+
28 => :PROPERTY_TYPE_VECTOR,
|
|
59
|
+
29 => :PROPERTY_TYPE_TABLE,
|
|
60
|
+
30 => :PROPERTY_TYPE_PATH,
|
|
61
|
+
31 => :PROPERTY_TYPE_ERROR,
|
|
62
|
+
32 => :PROPERTY_TYPE_NODE,
|
|
63
|
+
33 => :PROPERTY_TYPE_EDGE,
|
|
64
|
+
}.freeze
|
|
65
|
+
IDS = NAMES.invert.freeze
|
|
66
|
+
|
|
67
|
+
def self.id_of(sym_or_int)
|
|
68
|
+
return sym_or_int if sym_or_int.is_a?(Integer)
|
|
69
|
+
IDS[sym_or_int] || 0
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.name_of(int_or_sym)
|
|
73
|
+
return int_or_sym if int_or_sym.is_a?(Symbol)
|
|
74
|
+
NAMES[int_or_sym] || int_or_sym
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# ---- wire encoders --------------------------------------------------------
|
|
79
|
+
module Wire
|
|
80
|
+
module_function
|
|
81
|
+
|
|
82
|
+
def varint(buf, n)
|
|
83
|
+
# Handle negative numbers as two's-complement uint64.
|
|
84
|
+
n = (1 << 64) + n if n.negative?
|
|
85
|
+
while n >= 0x80
|
|
86
|
+
buf << ((n & 0x7F) | 0x80)
|
|
87
|
+
n >>= 7
|
|
88
|
+
end
|
|
89
|
+
buf << (n & 0x7F)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def tag(buf, field_no, wire_type)
|
|
93
|
+
varint(buf, (field_no << 3) | wire_type)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def write_bytes(buf, field_no, bytes)
|
|
97
|
+
return if bytes.nil? || bytes.empty?
|
|
98
|
+
tag(buf, field_no, 2)
|
|
99
|
+
varint(buf, bytes.bytesize)
|
|
100
|
+
buf << bytes.b
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def write_string(buf, field_no, str)
|
|
104
|
+
return if str.nil? || str.empty?
|
|
105
|
+
write_bytes(buf, field_no, str.dup.force_encoding("BINARY"))
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def write_varint(buf, field_no, n)
|
|
109
|
+
return if n.zero?
|
|
110
|
+
tag(buf, field_no, 0)
|
|
111
|
+
varint(buf, n)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def write_bool(buf, field_no, b)
|
|
115
|
+
return unless b
|
|
116
|
+
tag(buf, field_no, 0)
|
|
117
|
+
varint(buf, 1)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# ---- wire reader ----------------------------------------------------------
|
|
122
|
+
class Reader
|
|
123
|
+
def initialize(bytes)
|
|
124
|
+
@b = bytes.dup.force_encoding("BINARY")
|
|
125
|
+
@p = 0
|
|
126
|
+
@end = @b.bytesize
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def eof?
|
|
130
|
+
@p >= @end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def read_varint
|
|
134
|
+
r = 0
|
|
135
|
+
sh = 0
|
|
136
|
+
loop do
|
|
137
|
+
byte = @b.getbyte(@p)
|
|
138
|
+
@p += 1
|
|
139
|
+
r |= (byte & 0x7F) << sh
|
|
140
|
+
return r if (byte & 0x80).zero?
|
|
141
|
+
sh += 7
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def read_signed_varint
|
|
146
|
+
v = read_varint
|
|
147
|
+
v -= (1 << 64) if v >= (1 << 63)
|
|
148
|
+
v
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def read_len
|
|
152
|
+
n = read_varint
|
|
153
|
+
s = @b.byteslice(@p, n) || ""
|
|
154
|
+
@p += n
|
|
155
|
+
s.dup.force_encoding("BINARY")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def read_string
|
|
159
|
+
read_len.force_encoding("UTF-8")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def skip(wire_type)
|
|
163
|
+
case wire_type
|
|
164
|
+
when 0 then read_varint
|
|
165
|
+
when 2 then @p += read_varint
|
|
166
|
+
when 5 then @p += 4
|
|
167
|
+
when 1 then @p += 8
|
|
168
|
+
else raise ArgumentError, "unknown wire type #{wire_type}"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# ---- messages -------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
class TypedValue
|
|
176
|
+
attr_accessor :type, :data, :is_null
|
|
177
|
+
|
|
178
|
+
def initialize(type: 0, data: "".b, is_null: false)
|
|
179
|
+
@type = PropertyType.id_of(type)
|
|
180
|
+
@data = (data || "".b).dup.force_encoding("BINARY")
|
|
181
|
+
@is_null = is_null
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def self.encode(msg)
|
|
185
|
+
buf = "".b
|
|
186
|
+
Wire.write_varint(buf, 1, msg.type)
|
|
187
|
+
Wire.write_bytes(buf, 2, msg.data)
|
|
188
|
+
Wire.write_bool(buf, 3, msg.is_null)
|
|
189
|
+
buf
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def self.decode(bytes)
|
|
193
|
+
o = new
|
|
194
|
+
r = Reader.new(bytes)
|
|
195
|
+
until r.eof?
|
|
196
|
+
tag = r.read_varint
|
|
197
|
+
fn = tag >> 3
|
|
198
|
+
wt = tag & 7
|
|
199
|
+
if fn == 1 && wt == 0 then o.type = r.read_varint
|
|
200
|
+
elsif fn == 2 && wt == 2 then o.data = r.read_len
|
|
201
|
+
elsif fn == 3 && wt == 0 then o.is_null = !r.read_varint.zero?
|
|
202
|
+
else r.skip(wt)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
o
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
class Parameter
|
|
210
|
+
attr_accessor :name, :value
|
|
211
|
+
|
|
212
|
+
def initialize(name: "", value: TypedValue.new)
|
|
213
|
+
@name = name.to_s
|
|
214
|
+
@value = value
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def self.encode(msg)
|
|
218
|
+
buf = "".b
|
|
219
|
+
Wire.write_string(buf, 1, msg.name)
|
|
220
|
+
inner = TypedValue.encode(msg.value)
|
|
221
|
+
Wire.write_bytes(buf, 2, inner)
|
|
222
|
+
buf
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
class Row
|
|
227
|
+
attr_accessor :values
|
|
228
|
+
|
|
229
|
+
def initialize(values: [])
|
|
230
|
+
@values = values
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def self.decode(bytes)
|
|
234
|
+
o = new
|
|
235
|
+
r = Reader.new(bytes)
|
|
236
|
+
until r.eof?
|
|
237
|
+
tag = r.read_varint
|
|
238
|
+
fn = tag >> 3
|
|
239
|
+
wt = tag & 7
|
|
240
|
+
if fn == 1 && wt == 2
|
|
241
|
+
o.values << TypedValue.decode(r.read_len)
|
|
242
|
+
else
|
|
243
|
+
r.skip(wt)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
o
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
class DmlStats
|
|
251
|
+
attr_accessor :inserted_nodes, :inserted_edges, :deleted_nodes,
|
|
252
|
+
:deleted_edges, :set_nodes, :set_edges
|
|
253
|
+
|
|
254
|
+
def initialize(inserted_nodes: 0, inserted_edges: 0, deleted_nodes: 0,
|
|
255
|
+
deleted_edges: 0, set_nodes: 0, set_edges: 0)
|
|
256
|
+
@inserted_nodes = inserted_nodes
|
|
257
|
+
@inserted_edges = inserted_edges
|
|
258
|
+
@deleted_nodes = deleted_nodes
|
|
259
|
+
@deleted_edges = deleted_edges
|
|
260
|
+
@set_nodes = set_nodes
|
|
261
|
+
@set_edges = set_edges
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def self.decode(bytes)
|
|
265
|
+
o = new
|
|
266
|
+
r = Reader.new(bytes)
|
|
267
|
+
until r.eof?
|
|
268
|
+
tag = r.read_varint
|
|
269
|
+
fn = tag >> 3
|
|
270
|
+
wt = tag & 7
|
|
271
|
+
(r.skip(wt); next) if wt != 0
|
|
272
|
+
v = r.read_signed_varint
|
|
273
|
+
case fn
|
|
274
|
+
when 1 then o.inserted_nodes = v
|
|
275
|
+
when 2 then o.inserted_edges = v
|
|
276
|
+
when 3 then o.deleted_nodes = v
|
|
277
|
+
when 4 then o.deleted_edges = v
|
|
278
|
+
when 5 then o.set_nodes = v
|
|
279
|
+
when 6 then o.set_edges = v
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
o
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
class LoginRequest
|
|
287
|
+
attr_accessor :username, :password, :default_graph
|
|
288
|
+
|
|
289
|
+
def initialize(username: "", password: "", default_graph: "")
|
|
290
|
+
@username = username
|
|
291
|
+
@password = password
|
|
292
|
+
@default_graph = default_graph
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def self.encode(msg)
|
|
296
|
+
buf = "".b
|
|
297
|
+
Wire.write_string(buf, 1, msg.username)
|
|
298
|
+
Wire.write_string(buf, 2, msg.password)
|
|
299
|
+
Wire.write_string(buf, 3, msg.default_graph)
|
|
300
|
+
buf
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
class LoginResponse
|
|
305
|
+
attr_accessor :session_id, :server_version, :roles, :is_cluster,
|
|
306
|
+
:cluster_id, :partition_count, :capabilities,
|
|
307
|
+
:current_graph, :time_cost_ns, :disk_cost_ns, :compute_cost_ns
|
|
308
|
+
|
|
309
|
+
def initialize
|
|
310
|
+
@session_id = 0
|
|
311
|
+
@server_version = ""
|
|
312
|
+
@roles = []
|
|
313
|
+
@is_cluster = false
|
|
314
|
+
@cluster_id = ""
|
|
315
|
+
@partition_count = 0
|
|
316
|
+
@capabilities = []
|
|
317
|
+
@current_graph = ""
|
|
318
|
+
@time_cost_ns = 0
|
|
319
|
+
@disk_cost_ns = 0
|
|
320
|
+
@compute_cost_ns = 0
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def self.decode(bytes)
|
|
324
|
+
o = new
|
|
325
|
+
r = Reader.new(bytes)
|
|
326
|
+
until r.eof?
|
|
327
|
+
tag = r.read_varint
|
|
328
|
+
fn = tag >> 3
|
|
329
|
+
wt = tag & 7
|
|
330
|
+
case [fn, wt]
|
|
331
|
+
when [1, 0] then o.session_id = r.read_varint
|
|
332
|
+
when [2, 2] then o.server_version = r.read_string
|
|
333
|
+
when [3, 2] then o.roles << r.read_string
|
|
334
|
+
when [10, 0] then o.is_cluster = !r.read_varint.zero?
|
|
335
|
+
when [11, 2] then o.cluster_id = r.read_string
|
|
336
|
+
when [12, 0] then o.partition_count = r.read_signed_varint
|
|
337
|
+
when [20, 2] then o.capabilities << r.read_string
|
|
338
|
+
when [21, 2] then o.current_graph = r.read_string
|
|
339
|
+
when [22, 0] then o.time_cost_ns = r.read_signed_varint
|
|
340
|
+
when [23, 0] then o.disk_cost_ns = r.read_signed_varint
|
|
341
|
+
when [24, 0] then o.compute_cost_ns = r.read_signed_varint
|
|
342
|
+
else r.skip(wt)
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
o
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
class GqlRequest
|
|
350
|
+
attr_accessor :gql, :graph_name, :parameters, :session_id,
|
|
351
|
+
:transaction_id, :timeout, :read_only, :max_path_results
|
|
352
|
+
|
|
353
|
+
def initialize(gql: "", graph_name: "", parameters: [], session_id: 0,
|
|
354
|
+
transaction_id: 0, timeout: 0, read_only: false,
|
|
355
|
+
max_path_results: 0)
|
|
356
|
+
@gql = gql
|
|
357
|
+
@graph_name = graph_name
|
|
358
|
+
@parameters = parameters
|
|
359
|
+
@session_id = session_id
|
|
360
|
+
@transaction_id = transaction_id
|
|
361
|
+
@timeout = timeout
|
|
362
|
+
@read_only = read_only
|
|
363
|
+
@max_path_results = max_path_results
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def self.encode(msg)
|
|
367
|
+
buf = "".b
|
|
368
|
+
Wire.write_string(buf, 1, msg.gql)
|
|
369
|
+
Wire.write_string(buf, 2, msg.graph_name)
|
|
370
|
+
msg.parameters.each do |p|
|
|
371
|
+
Wire.write_bytes(buf, 3, Parameter.encode(p))
|
|
372
|
+
end
|
|
373
|
+
Wire.write_varint(buf, 4, msg.session_id)
|
|
374
|
+
Wire.write_varint(buf, 5, msg.transaction_id)
|
|
375
|
+
Wire.write_varint(buf, 6, msg.timeout)
|
|
376
|
+
Wire.write_bool(buf, 7, msg.read_only)
|
|
377
|
+
Wire.write_varint(buf, 8, msg.max_path_results)
|
|
378
|
+
buf
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
class GqlResponse
|
|
383
|
+
attr_accessor :columns, :rows, :row_count, :has_more, :warnings,
|
|
384
|
+
:rows_affected, :current_graph, :time_cost_ns,
|
|
385
|
+
:disk_cost_ns, :compute_cost_ns, :dml_stats
|
|
386
|
+
|
|
387
|
+
def initialize
|
|
388
|
+
@columns = []
|
|
389
|
+
@rows = []
|
|
390
|
+
@row_count = 0
|
|
391
|
+
@has_more = false
|
|
392
|
+
@warnings = []
|
|
393
|
+
@rows_affected = 0
|
|
394
|
+
@current_graph = ""
|
|
395
|
+
@time_cost_ns = 0
|
|
396
|
+
@disk_cost_ns = 0
|
|
397
|
+
@compute_cost_ns = 0
|
|
398
|
+
@dml_stats = DmlStats.new
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def self.decode(bytes)
|
|
402
|
+
o = new
|
|
403
|
+
r = Reader.new(bytes)
|
|
404
|
+
until r.eof?
|
|
405
|
+
tag = r.read_varint
|
|
406
|
+
fn = tag >> 3
|
|
407
|
+
wt = tag & 7
|
|
408
|
+
case [fn, wt]
|
|
409
|
+
when [1, 2] then o.columns << r.read_string
|
|
410
|
+
when [2, 2] then o.rows << Row.decode(r.read_len)
|
|
411
|
+
when [3, 0] then o.row_count = r.read_signed_varint
|
|
412
|
+
when [4, 0] then o.has_more = !r.read_varint.zero?
|
|
413
|
+
when [5, 2] then o.warnings << r.read_string
|
|
414
|
+
when [6, 0] then o.rows_affected = r.read_signed_varint
|
|
415
|
+
when [7, 2] then o.current_graph = r.read_string
|
|
416
|
+
when [8, 0] then o.time_cost_ns = r.read_signed_varint
|
|
417
|
+
when [9, 0] then o.disk_cost_ns = r.read_signed_varint
|
|
418
|
+
when [10, 0] then o.compute_cost_ns = r.read_signed_varint
|
|
419
|
+
when [11, 2] then o.dml_stats = DmlStats.decode(r.read_len)
|
|
420
|
+
else r.skip(wt)
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
o
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
data/lib/tina4_ultipa/version.rb
CHANGED
data/tina4-ultipa.gemspec
CHANGED
|
@@ -28,6 +28,9 @@ Gem::Specification.new do |spec|
|
|
|
28
28
|
]
|
|
29
29
|
spec.require_paths = ["lib"]
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
# Pure-Ruby HTTP/2. Zero C extensions, zero google-protobuf, zero
|
|
32
|
+
# transitive deps. See lib/tina4_ultipa/grpc.rb for the tiny gRPC framing
|
|
33
|
+
# layer on top of it, and lib/tina4_ultipa/pb.rb for the hand-rolled
|
|
34
|
+
# proto3 wire codec for the 8 gqldb messages we consume.
|
|
35
|
+
spec.add_dependency "http-2", ">= 1.2"
|
|
33
36
|
end
|
metadata
CHANGED
|
@@ -1,43 +1,29 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4-ultipa
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Stack
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: http-2
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '1.2'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: grpc
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: '1.60'
|
|
34
|
-
type: :runtime
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '1.60'
|
|
26
|
+
version: '1.2'
|
|
41
27
|
description: A thin, standalone gRPC client for the Ultipa graph database (ultipa-gqldb)
|
|
42
28
|
- the Ultipa driver behind Tina4's graph layer, usable on its own.
|
|
43
29
|
email:
|
|
@@ -52,8 +38,8 @@ files:
|
|
|
52
38
|
- lib/tina4_ultipa/client.rb
|
|
53
39
|
- lib/tina4_ultipa/codec.rb
|
|
54
40
|
- lib/tina4_ultipa/errors.rb
|
|
55
|
-
- lib/tina4_ultipa/
|
|
56
|
-
- lib/tina4_ultipa/
|
|
41
|
+
- lib/tina4_ultipa/grpc.rb
|
|
42
|
+
- lib/tina4_ultipa/pb.rb
|
|
57
43
|
- lib/tina4_ultipa/version.rb
|
|
58
44
|
- proto/gqldb.proto
|
|
59
45
|
- tina4-ultipa.gemspec
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
3
|
-
# source: gqldb.proto
|
|
4
|
-
|
|
5
|
-
require 'google/protobuf'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
descriptor_data = "\n\x0bgqldb.proto\x12\x05gqldb\"N\n\nTypedValue\x12!\n\x04type\x18\x01 \x01(\x0e\x32\x13.gqldb.PropertyType\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\x12\x0f\n\x07is_null\x18\x03 \x01(\x08\";\n\tParameter\x12\x0c\n\x04name\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.gqldb.TypedValue\"(\n\x03Row\x12!\n\x06values\x18\x01 \x03(\x0b\x32\x11.gqldb.TypedValue\"\x8e\x01\n\x08\x44mlStats\x12\x16\n\x0einserted_nodes\x18\x01 \x01(\x03\x12\x16\n\x0einserted_edges\x18\x02 \x01(\x03\x12\x15\n\rdeleted_nodes\x18\x03 \x01(\x03\x12\x15\n\rdeleted_edges\x18\x04 \x01(\x03\x12\x11\n\tset_nodes\x18\x05 \x01(\x03\x12\x11\n\tset_edges\x18\x06 \x01(\x03\"I\n\x0cLoginRequest\x12\x10\n\x08username\x18\x01 \x01(\t\x12\x10\n\x08password\x18\x02 \x01(\t\x12\x15\n\rdefault_graph\x18\x03 \x01(\t\"\xfd\x01\n\rLoginResponse\x12\x12\n\nsession_id\x18\x01 \x01(\x04\x12\x16\n\x0eserver_version\x18\x02 \x01(\t\x12\r\n\x05roles\x18\x03 \x03(\t\x12\x12\n\nis_cluster\x18\n \x01(\x08\x12\x12\n\ncluster_id\x18\x0b \x01(\t\x12\x17\n\x0fpartition_count\x18\x0c \x01(\x05\x12\x14\n\x0c\x63\x61pabilities\x18\x14 \x03(\t\x12\x15\n\rcurrent_graph\x18\x15 \x01(\t\x12\x14\n\x0ctime_cost_ns\x18\x16 \x01(\x03\x12\x14\n\x0c\x64isk_cost_ns\x18\x17 \x01(\x03\x12\x17\n\x0f\x63ompute_cost_ns\x18\x18 \x01(\x03\"\xbd\x01\n\nGqlRequest\x12\x0b\n\x03gql\x18\x01 \x01(\t\x12\x12\n\ngraph_name\x18\x02 \x01(\t\x12$\n\nparameters\x18\x03 \x03(\x0b\x32\x10.gqldb.Parameter\x12\x12\n\nsession_id\x18\x04 \x01(\x04\x12\x16\n\x0etransaction_id\x18\x05 \x01(\x04\x12\x0f\n\x07timeout\x18\x06 \x01(\x05\x12\x11\n\tread_only\x18\x07 \x01(\x08\x12\x18\n\x10max_path_results\x18\x08 \x01(\x03\"\x86\x02\n\x0bGqlResponse\x12\x0f\n\x07\x63olumns\x18\x01 \x03(\t\x12\x18\n\x04rows\x18\x02 \x03(\x0b\x32\n.gqldb.Row\x12\x11\n\trow_count\x18\x03 \x01(\x03\x12\x10\n\x08has_more\x18\x04 \x01(\x08\x12\x10\n\x08warnings\x18\x05 \x03(\t\x12\x15\n\rrows_affected\x18\x06 \x01(\x03\x12\x15\n\rcurrent_graph\x18\x07 \x01(\t\x12\x14\n\x0ctime_cost_ns\x18\x08 \x01(\x03\x12\x14\n\x0c\x64isk_cost_ns\x18\t \x01(\x03\x12\x17\n\x0f\x63ompute_cost_ns\x18\n \x01(\x03\x12\"\n\tdml_stats\x18\x0b \x01(\x0b\x32\x0f.gqldb.DmlStats*\x90\x07\n\x0cPropertyType\x12\x17\n\x13PROPERTY_TYPE_UNSET\x10\x00\x12\x17\n\x13PROPERTY_TYPE_INT32\x10\x01\x12\x18\n\x14PROPERTY_TYPE_UINT32\x10\x02\x12\x17\n\x13PROPERTY_TYPE_INT64\x10\x03\x12\x18\n\x14PROPERTY_TYPE_UINT64\x10\x04\x12\x17\n\x13PROPERTY_TYPE_FLOAT\x10\x05\x12\x18\n\x14PROPERTY_TYPE_DOUBLE\x10\x06\x12\x18\n\x14PROPERTY_TYPE_STRING\x10\x07\x12\x1a\n\x16PROPERTY_TYPE_DATETIME\x10\x08\x12\x1b\n\x17PROPERTY_TYPE_TIMESTAMP\x10\t\x12\x16\n\x12PROPERTY_TYPE_TEXT\x10\n\x12\x16\n\x12PROPERTY_TYPE_BLOB\x10\x0b\x12\x17\n\x13PROPERTY_TYPE_POINT\x10\x0c\x12\x19\n\x15PROPERTY_TYPE_DECIMAL\x10\r\x12\x16\n\x12PROPERTY_TYPE_LIST\x10\x0e\x12\x15\n\x11PROPERTY_TYPE_SET\x10\x0f\x12\x15\n\x11PROPERTY_TYPE_MAP\x10\x10\x12\x16\n\x12PROPERTY_TYPE_NULL\x10\x11\x12\x16\n\x12PROPERTY_TYPE_BOOL\x10\x12\x12 \n\x1cPROPERTY_TYPE_LOCAL_DATETIME\x10\x13\x12 \n\x1cPROPERTY_TYPE_ZONED_DATETIME\x10\x14\x12\x16\n\x12PROPERTY_TYPE_DATE\x10\x15\x12\x1c\n\x18PROPERTY_TYPE_ZONED_TIME\x10\x16\x12\x1c\n\x18PROPERTY_TYPE_LOCAL_TIME\x10\x17\x12\x1f\n\x1bPROPERTY_TYPE_YEAR_TO_MONTH\x10\x18\x12\x1f\n\x1bPROPERTY_TYPE_DAY_TO_SECOND\x10\x19\x12\x18\n\x14PROPERTY_TYPE_RECORD\x10\x1a\x12\x19\n\x15PROPERTY_TYPE_POINT3D\x10\x1b\x12\x18\n\x14PROPERTY_TYPE_VECTOR\x10\x1c\x12\x17\n\x13PROPERTY_TYPE_TABLE\x10\x1d\x12\x16\n\x12PROPERTY_TYPE_PATH\x10\x1e\x12\x17\n\x13PROPERTY_TYPE_ERROR\x10\x1f\x12\x16\n\x12PROPERTY_TYPE_NODE\x10 \x12\x16\n\x12PROPERTY_TYPE_EDGE\x10!2D\n\x0eSessionService\x12\x32\n\x05Login\x12\x13.gqldb.LoginRequest\x1a\x14.gqldb.LoginResponse2<\n\x0cQueryService\x12,\n\x03Gql\x12\x11.gqldb.GqlRequest\x1a\x12.gqldb.GqlResponseb\x06proto3"
|
|
9
|
-
|
|
10
|
-
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
|
-
pool.add_serialized_file(descriptor_data)
|
|
12
|
-
|
|
13
|
-
module Gqldb
|
|
14
|
-
TypedValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gqldb.TypedValue").msgclass
|
|
15
|
-
Parameter = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gqldb.Parameter").msgclass
|
|
16
|
-
Row = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gqldb.Row").msgclass
|
|
17
|
-
DmlStats = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gqldb.DmlStats").msgclass
|
|
18
|
-
LoginRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gqldb.LoginRequest").msgclass
|
|
19
|
-
LoginResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gqldb.LoginResponse").msgclass
|
|
20
|
-
GqlRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gqldb.GqlRequest").msgclass
|
|
21
|
-
GqlResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gqldb.GqlResponse").msgclass
|
|
22
|
-
PropertyType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gqldb.PropertyType").enummodule
|
|
23
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
|
2
|
-
# Source: gqldb.proto for package 'gqldb'
|
|
3
|
-
|
|
4
|
-
require 'grpc'
|
|
5
|
-
require 'gqldb_pb'
|
|
6
|
-
|
|
7
|
-
module Gqldb
|
|
8
|
-
module SessionService
|
|
9
|
-
class Service
|
|
10
|
-
|
|
11
|
-
include ::GRPC::GenericService
|
|
12
|
-
|
|
13
|
-
self.marshal_class_method = :encode
|
|
14
|
-
self.unmarshal_class_method = :decode
|
|
15
|
-
self.service_name = 'gqldb.SessionService'
|
|
16
|
-
|
|
17
|
-
rpc :Login, ::Gqldb::LoginRequest, ::Gqldb::LoginResponse
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
Stub = Service.rpc_stub_class
|
|
21
|
-
end
|
|
22
|
-
module QueryService
|
|
23
|
-
class Service
|
|
24
|
-
|
|
25
|
-
include ::GRPC::GenericService
|
|
26
|
-
|
|
27
|
-
self.marshal_class_method = :encode
|
|
28
|
-
self.unmarshal_class_method = :decode
|
|
29
|
-
self.service_name = 'gqldb.QueryService'
|
|
30
|
-
|
|
31
|
-
rpc :Gql, ::Gqldb::GqlRequest, ::Gqldb::GqlResponse
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
Stub = Service.rpc_stub_class
|
|
35
|
-
end
|
|
36
|
-
end
|