orientdb_client 0.0.1
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 +22 -0
- data/.rspec +2 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +95 -0
- data/Rakefile +30 -0
- data/lib/orientdb_client.rb +378 -0
- data/lib/orientdb_client/class_configurator.rb +10 -0
- data/lib/orientdb_client/errors.rb +35 -0
- data/lib/orientdb_client/http_adapters.rb +25 -0
- data/lib/orientdb_client/http_adapters/curb_adapter.rb +35 -0
- data/lib/orientdb_client/http_adapters/typhoeus_adapter.rb +53 -0
- data/lib/orientdb_client/test.rb +9 -0
- data/lib/orientdb_client/version.rb +3 -0
- data/orientdb_client.gemspec +31 -0
- data/spec/curb_adapter_spec.rb +8 -0
- data/spec/orientdb_client_spec.rb +703 -0
- data/spec/spec_helper.rb +111 -0
- data/spec/support/shared_examples_for_http_adapter.rb +67 -0
- data/spec/typhoeus_adapter_spec.rb +8 -0
- metadata +196 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
module OrientdbClient
|
2
|
+
class OrientdbError < StandardError
|
3
|
+
attr_reader :http_code, :response_body
|
4
|
+
|
5
|
+
def initialize(message = nil, http_code = nil, response_body = nil)
|
6
|
+
super(message)
|
7
|
+
@http_code = http_code
|
8
|
+
@response_body = response_body
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ConnectionError < OrientdbError; end
|
13
|
+
|
14
|
+
# ServerError: server has rejected your command/query because
|
15
|
+
# processing it would violate some invariant
|
16
|
+
class ServerError < OrientdbError; end
|
17
|
+
class TransactionException < ServerError; end
|
18
|
+
class DistributedTransactionException < TransactionException; end
|
19
|
+
class MVCCError < ServerError; end
|
20
|
+
|
21
|
+
# ClientError: you did something wrong
|
22
|
+
class ClientError < OrientdbError; end
|
23
|
+
class UnauthorizedError < ClientError; end
|
24
|
+
class IllegalArgumentException < ClientError; end
|
25
|
+
class CommandExecutionException < ClientError; end
|
26
|
+
|
27
|
+
# ConflictError: you tried to create something that already exists
|
28
|
+
class ConflictError < ClientError; end
|
29
|
+
class DuplicateRecordError < ConflictError; end
|
30
|
+
class DistributedDuplicateRecordError < DuplicateRecordError; end
|
31
|
+
|
32
|
+
class NotFoundError < OrientdbError; end
|
33
|
+
|
34
|
+
class NegativeArraySizeException < OrientdbError; end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module OrientdbClient
|
2
|
+
module HttpAdapters
|
3
|
+
SESSION_COOKIE_NAME = 'OSESSIONID'
|
4
|
+
|
5
|
+
class Base
|
6
|
+
attr_accessor :username, :password
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@username = nil
|
10
|
+
@password = nil
|
11
|
+
@session_id = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def reset_credentials
|
15
|
+
@username = nil
|
16
|
+
@password = nil
|
17
|
+
@session_id = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def request
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'curb'
|
2
|
+
|
3
|
+
module OrientdbClient
|
4
|
+
module HttpAdapters
|
5
|
+
class CurbAdapter < Base
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
@curl = Curl::Easy.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def request(method, url, options = {})
|
13
|
+
req = prepare_request(method, url, options)
|
14
|
+
run_request(req, method)
|
15
|
+
req
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def prepare_request(method, url, options)
|
21
|
+
username = options[:username] || @username
|
22
|
+
password = options[:password] || @password
|
23
|
+
@curl.url = url
|
24
|
+
@curl.http_auth_types = :basic
|
25
|
+
@curl.username = username
|
26
|
+
@curl.password = password
|
27
|
+
@curl
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_request(request, method)
|
31
|
+
request.public_send(method)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'typhoeus'
|
2
|
+
|
3
|
+
module OrientdbClient
|
4
|
+
module HttpAdapters
|
5
|
+
class TyphoeusAdapter < Base
|
6
|
+
|
7
|
+
def request(method, url, options = {})
|
8
|
+
req = prepare_request(method, url, options)
|
9
|
+
run_request(req)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def prepare_request(method, url, options)
|
15
|
+
options = {
|
16
|
+
userpwd: authentication_string(options),
|
17
|
+
method: method
|
18
|
+
}.merge(options)
|
19
|
+
Typhoeus::Request.new(url, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def run_request(request)
|
23
|
+
request.run
|
24
|
+
response = request.response
|
25
|
+
if cookies = response.headers['Set-Cookie']
|
26
|
+
@session_id = extract_session_id(cookies)
|
27
|
+
end
|
28
|
+
# TODO hacky, replace with response adpater object probably
|
29
|
+
def response.content_type
|
30
|
+
headers['Content-Type']
|
31
|
+
end
|
32
|
+
response
|
33
|
+
end
|
34
|
+
|
35
|
+
def authentication_string(options)
|
36
|
+
username = options[:username] || @username
|
37
|
+
password = options[:password] || @password
|
38
|
+
"#{username}:#{password}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def extract_session_id(cookies)
|
42
|
+
r = Regexp.new("#{SESSION_COOKIE_NAME}=([^\s;]+)")
|
43
|
+
if cookies.is_a?(Array)
|
44
|
+
return cookies.detect { |cookie| cookie.match(r) != nil }.
|
45
|
+
match(r)[1]
|
46
|
+
else
|
47
|
+
return cookies.match(r)[1]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module OrientdbClient
|
2
|
+
module Test
|
3
|
+
OrientdbClient::Test::DatabaseName = ENV['ORIENTDB_TEST_DATABASENAME'] || 'orientdb_client_rb_test'
|
4
|
+
OrientdbClient::Test::Username = ENV['ORIENTDB_TEST_USERNAME'] || 'test'
|
5
|
+
OrientdbClient::Test::Password = ENV['ORIENTDB_TEST_PASSWORD'] || 'test'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'orientdb_client/http_adapters/curb_adapter'
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'orientdb_client/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "orientdb_client"
|
8
|
+
spec.version = OrientdbClient::VERSION
|
9
|
+
spec.authors = ["Luke Rodgers"]
|
10
|
+
spec.email = ["lukeasrodgers@gmail.com"]
|
11
|
+
spec.summary = %q{Orientdb ruby client}
|
12
|
+
spec.description = %q{Orientdb ruby client aiming to be simple, fast, and provide good integration with Orientdb error messages.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "typhoeus", "~> 0.6"
|
22
|
+
spec.add_dependency "oj", "~> 2.0"
|
23
|
+
spec.add_dependency "rainbow", "> 1.99"
|
24
|
+
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "pry"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.3"
|
28
|
+
spec.add_development_dependency "rspec-mocks", "~> 3.3"
|
29
|
+
spec.add_development_dependency "webmock", '~> 1.22'
|
30
|
+
spec.add_development_dependency "byebug"
|
31
|
+
end
|
@@ -0,0 +1,703 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe OrientdbClient do
|
4
|
+
let(:client) do
|
5
|
+
c = OrientdbClient.client
|
6
|
+
c.logger.level = Logger::ERROR
|
7
|
+
c
|
8
|
+
end
|
9
|
+
let(:username) { OrientdbClient::Test::Username }
|
10
|
+
let(:valid_username) { OrientdbClient::Test::Username }
|
11
|
+
let(:password) { OrientdbClient::Test::Password }
|
12
|
+
let(:valid_password) { OrientdbClient::Test::Password }
|
13
|
+
let(:db) { OrientdbClient::Test::DatabaseName }
|
14
|
+
let(:temp_db_name) { "#{OrientdbClient::Test::DatabaseName}_temp" }
|
15
|
+
|
16
|
+
after(:each) do
|
17
|
+
if client.connected?
|
18
|
+
client.disconnect
|
19
|
+
end
|
20
|
+
if client.database_exists?(temp_db_name)
|
21
|
+
client.delete_database(temp_db_name, username: valid_username, password: valid_password)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'integration specs', type: :integration do
|
26
|
+
describe '#connect' do
|
27
|
+
subject { client.connect(username: username, password: password, db: db) }
|
28
|
+
|
29
|
+
after(:each) do
|
30
|
+
if client.connected?
|
31
|
+
client.disconnect
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'with valid credentials' do
|
36
|
+
let(:username) { OrientdbClient::Test::Username }
|
37
|
+
let(:password) { OrientdbClient::Test::Password }
|
38
|
+
let(:db) { OrientdbClient::Test::DatabaseName }
|
39
|
+
|
40
|
+
it 'connects to the database' do
|
41
|
+
subject
|
42
|
+
expect(client.connected?).to be true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'with invalid credentials' do
|
47
|
+
let(:username) { 'foo' }
|
48
|
+
let(:password) { 'bar' }
|
49
|
+
let(:db) { OrientdbClient::Test::DatabaseName }
|
50
|
+
|
51
|
+
it 'fails to connect' do
|
52
|
+
begin
|
53
|
+
subject
|
54
|
+
rescue
|
55
|
+
ensure
|
56
|
+
expect(client.connected?).to be false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'raises an UnauthorizedError' do
|
61
|
+
expect { subject }.to raise_exception(OrientdbClient::UnauthorizedError)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#create_database' do
|
67
|
+
before(:each) do
|
68
|
+
if client.database_exists?(temp_db_name)
|
69
|
+
client.delete_database(temp_db_name, username: username, password: password)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'without existing connection' do
|
74
|
+
context 'with valid authentication options' do
|
75
|
+
it 'creates the database' do
|
76
|
+
client.create_database(temp_db_name, 'plocal', 'document', username: username, password: password)
|
77
|
+
expect(client.database_exists?(temp_db_name)).to be true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'with invalid authentication options' do
|
82
|
+
it 'raises UnauthorizedError' do
|
83
|
+
expect do
|
84
|
+
client.create_database(temp_db_name, 'plocal', 'document', username: 'foo', password: 'bar')
|
85
|
+
end.to raise_exception(OrientdbClient::UnauthorizedError)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'with existing connection' do
|
91
|
+
before do
|
92
|
+
client.connect(username: username, password: password, db: db)
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with valid database parameters' do
|
96
|
+
it 'creates a database' do
|
97
|
+
client.create_database(temp_db_name, 'plocal', 'document')
|
98
|
+
expect(client.database_exists?(temp_db_name)).to be true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'with existing database' do
|
103
|
+
it 'creates a database' do
|
104
|
+
client.create_database(temp_db_name, 'plocal', 'document')
|
105
|
+
expect(client.database_exists?(temp_db_name)).to be true
|
106
|
+
expect do
|
107
|
+
client.create_database(temp_db_name, 'plocal', 'document')
|
108
|
+
end.to raise_exception(OrientdbClient::ConflictError)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'with invalid storage type' do
|
113
|
+
it 'raises a ClientError' do
|
114
|
+
expect do
|
115
|
+
client.create_database(temp_db_name, 'foo', 'document')
|
116
|
+
end.to raise_exception(OrientdbClient::ClientError, /OCommandExecutionException/)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'with invalid database type' do
|
121
|
+
it 'raises a ClientError' do
|
122
|
+
expect do
|
123
|
+
client.create_database(temp_db_name, 'memory', 'dog')
|
124
|
+
end.to raise_exception(ArgumentError)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#delete_database' do
|
131
|
+
before(:each) do
|
132
|
+
if !client.database_exists?(temp_db_name)
|
133
|
+
client.create_database(temp_db_name, 'plocal', 'document', username: valid_username, password: valid_password)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'without existing connection' do
|
138
|
+
context 'with valid authentication options' do
|
139
|
+
it 'deletes the database' do
|
140
|
+
client.delete_database(temp_db_name, username: username, password: password)
|
141
|
+
expect(client.database_exists?(temp_db_name)).to be false
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'with invalid authentication options' do
|
146
|
+
it 'raises UnauthorizedError' do
|
147
|
+
expect do
|
148
|
+
client.delete_database(temp_db_name, username: 'foo', password: 'bar')
|
149
|
+
end.to raise_exception(OrientdbClient::UnauthorizedError)
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'does not delete the database' do
|
153
|
+
begin
|
154
|
+
client.delete_database(temp_db_name, username: 'foo', password: 'bar')
|
155
|
+
rescue
|
156
|
+
ensure
|
157
|
+
expect(client.database_exists?(temp_db_name)).to be true
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'with existing connection' do
|
164
|
+
before do
|
165
|
+
client.connect(username: username, password: password, db: db)
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'with valid database parameters' do
|
169
|
+
it 'deletes the database' do
|
170
|
+
client.delete_database(temp_db_name)
|
171
|
+
expect(client.database_exists?(temp_db_name)).to be false
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'with no matching database' do
|
176
|
+
it 'raises a ClientError' do
|
177
|
+
expect do
|
178
|
+
client.delete_database(temp_db_name + 'baz')
|
179
|
+
end.to raise_exception(OrientdbClient::ClientError, /OConfigurationException/)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe '#query' do
|
186
|
+
context 'when connected' do
|
187
|
+
before(:each) do
|
188
|
+
client.connect(username: username, password: password, db: db)
|
189
|
+
end
|
190
|
+
|
191
|
+
context 'with valid query' do
|
192
|
+
it 'returns result' do
|
193
|
+
r = client.query('select * from OUser')
|
194
|
+
expect(r).to be
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'with invalid query' do
|
199
|
+
it 'raises ClientError' do
|
200
|
+
expect { client.query('select * crumb') }.to raise_exception(OrientdbClient::ClientError, /OCommandSQLParsingException/)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context 'with non-idempotent query' do
|
205
|
+
it 'raises ClientError' do
|
206
|
+
expect { client.query('create class User') }.to raise_exception(OrientdbClient::ClientError, /OCommandExecutionException/)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context 'when not connected' do
|
212
|
+
it 'raises UnauthorizedError' do
|
213
|
+
expect { client.query('select * from OUser') }.to raise_exception(OrientdbClient::UnauthorizedError)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe '#command' do
|
219
|
+
context 'when connected' do
|
220
|
+
before(:each) do
|
221
|
+
client.connect(username: username, password: password, db: db)
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'with valid command' do
|
225
|
+
it 'returns result' do
|
226
|
+
r = client.command('select * from OUser')
|
227
|
+
expect(r).to be
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
context 'with invalid query' do
|
232
|
+
it 'returns result' do
|
233
|
+
expect { client.query('select * crumb') }.to raise_exception(OrientdbClient::ClientError, /OCommandSQLParsingException/)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
context 'when not connected' do
|
239
|
+
it 'raises UnauthorizedError' do
|
240
|
+
expect { client.command('select * from OUser') }.to raise_exception(OrientdbClient::UnauthorizedError)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
describe '#get_class' do
|
246
|
+
context 'when connected' do
|
247
|
+
before(:each) do
|
248
|
+
client.connect(username: username, password: password, db: db)
|
249
|
+
end
|
250
|
+
|
251
|
+
context 'with matching class' do
|
252
|
+
it 'returns result' do
|
253
|
+
r = client.get_class('OUser')
|
254
|
+
expect(r).to be
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context 'with class that does not exist' do
|
259
|
+
it 'raises NotFoundError' do
|
260
|
+
expect { client.get_class('foobar') }.to raise_exception(OrientdbClient::NotFoundError)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
context 'when not connected' do
|
266
|
+
it 'raises UnauthorizedError' do
|
267
|
+
expect { client.get_class('OUser') }.to raise_exception(OrientdbClient::UnauthorizedError)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe '#list_databases' do
|
273
|
+
context 'when connected' do
|
274
|
+
before(:each) do
|
275
|
+
client.connect(username: username, password: password, db: db)
|
276
|
+
end
|
277
|
+
|
278
|
+
context 'with a database' do
|
279
|
+
before(:each) do
|
280
|
+
unless client.database_exists?(temp_db_name)
|
281
|
+
client.create_database(temp_db_name, 'plocal', 'document', username: valid_username, password: valid_password)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'returns an array including the database name' do
|
286
|
+
expect(client.list_databases).to include(temp_db_name)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
context 'when not connected' do
|
292
|
+
it 'lists databases anyways' do
|
293
|
+
expect { client.list_databases }.not_to raise_exception
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
describe '#create_property' do
|
299
|
+
let(:class_name) { 'Member' }
|
300
|
+
|
301
|
+
context 'when connected' do
|
302
|
+
before(:each) do
|
303
|
+
client.connect(username: username, password: password, db: db)
|
304
|
+
end
|
305
|
+
|
306
|
+
context 'when class exists' do
|
307
|
+
before do
|
308
|
+
if (client.has_class?(class_name))
|
309
|
+
client.drop_class(class_name)
|
310
|
+
end
|
311
|
+
client.create_class(class_name)
|
312
|
+
end
|
313
|
+
|
314
|
+
after do
|
315
|
+
if (client.has_class?(class_name))
|
316
|
+
client.drop_class(class_name)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'can add string property to the class' do
|
321
|
+
client.create_property(class_name, 'member_name', 'string')
|
322
|
+
expect(client.get_class(class_name)['properties']).to include(hash_including({
|
323
|
+
'name' => 'member_name',
|
324
|
+
'type' => 'STRING',
|
325
|
+
'mandatory' => false,
|
326
|
+
'readonly' => false,
|
327
|
+
'notNull' => false,
|
328
|
+
'min' => nil,
|
329
|
+
'max' => nil,
|
330
|
+
'collate' => 'default'
|
331
|
+
}))
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'accepts options for the new property' do
|
335
|
+
client.create_property(class_name, 'member_name', 'string', notnull: true, mandatory: true, min: 4, max: 10)
|
336
|
+
expect(client.get_class(class_name)['properties']).to include(hash_including({
|
337
|
+
'name' => 'member_name',
|
338
|
+
'type' => 'STRING',
|
339
|
+
'mandatory' => true,
|
340
|
+
'readonly' => false,
|
341
|
+
'notNull' => true,
|
342
|
+
'min' => '4',
|
343
|
+
'max' => '10',
|
344
|
+
'collate' => 'default'
|
345
|
+
}))
|
346
|
+
end
|
347
|
+
|
348
|
+
context 'when property already exists' do
|
349
|
+
before do
|
350
|
+
client.create_property(class_name, 'member_name', 'string')
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'raises exception' do
|
354
|
+
expect do
|
355
|
+
client.create_property(class_name, 'member_name', 'string')
|
356
|
+
end.to raise_exception(OrientdbClient::CommandExecutionException, /OCommandExecutionException/)
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
context 'when class does not exist' do
|
362
|
+
it 'raises exception' do
|
363
|
+
expect do
|
364
|
+
client.create_property(class_name, 'member_name', 'string')
|
365
|
+
end.to raise_exception(OrientdbClient::CommandExecutionException, /OCommandExecutionException/)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
context 'when not connected' do
|
371
|
+
it 'raises UnauthorizedError' do
|
372
|
+
expect do
|
373
|
+
client.create_property(class_name, 'member_name', 'string')
|
374
|
+
end.to raise_exception(OrientdbClient::UnauthorizedError)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
describe '#alter_property' do
|
380
|
+
let(:class_name) { 'Member' }
|
381
|
+
|
382
|
+
context 'when connected' do
|
383
|
+
before(:each) do
|
384
|
+
client.connect(username: username, password: password, db: db)
|
385
|
+
end
|
386
|
+
|
387
|
+
context 'when class and property exist' do
|
388
|
+
before do
|
389
|
+
if (client.has_class?(class_name))
|
390
|
+
client.drop_class(class_name)
|
391
|
+
end
|
392
|
+
client.create_class(class_name) do |c|
|
393
|
+
c.property('member_name', 'string')
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
after do
|
398
|
+
if (client.has_class?(class_name))
|
399
|
+
client.drop_class(class_name)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
it 'can change string to be notnull' do
|
404
|
+
client.alter_property(class_name, 'member_name', 'notnull', true)
|
405
|
+
expect(client.get_class(class_name)['properties']).to include(hash_including({
|
406
|
+
'name' => 'member_name',
|
407
|
+
'type' => 'STRING',
|
408
|
+
'mandatory' => false,
|
409
|
+
'readonly' => false,
|
410
|
+
'notNull' => true,
|
411
|
+
'min' => nil,
|
412
|
+
'max' => nil,
|
413
|
+
'collate' => 'default'
|
414
|
+
}))
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
context 'when class does not exist' do
|
419
|
+
before do
|
420
|
+
if (client.has_class?(class_name))
|
421
|
+
client.drop_class(class_name)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
it 'raises exception' do
|
426
|
+
expect do
|
427
|
+
client.create_property(class_name, 'member_name', 'string')
|
428
|
+
end.to raise_exception(OrientdbClient::CommandExecutionException, /OCommandExecutionException/)
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
context 'when class exists but property does not' do
|
433
|
+
before do
|
434
|
+
if (client.has_class?(class_name))
|
435
|
+
client.drop_class(class_name)
|
436
|
+
end
|
437
|
+
client.create_class(class_name)
|
438
|
+
end
|
439
|
+
|
440
|
+
it 'raises exception' do
|
441
|
+
expect do
|
442
|
+
client.alter_property(class_name, 'member_name', 'notnull', true)
|
443
|
+
end.to raise_exception(OrientdbClient::CommandExecutionException, /OCommandExecutionException/)
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
context 'when not connected' do
|
449
|
+
it 'raises UnauthorizedError' do
|
450
|
+
expect do
|
451
|
+
client.create_property(class_name, 'member_name', 'string')
|
452
|
+
end.to raise_exception(OrientdbClient::UnauthorizedError)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
describe '#create_class' do
|
458
|
+
let(:class_name) { 'Member' }
|
459
|
+
|
460
|
+
context 'when connected' do
|
461
|
+
before(:each) do
|
462
|
+
client.connect(username: username, password: password, db: db)
|
463
|
+
|
464
|
+
if (client.has_class?(class_name))
|
465
|
+
client.drop_class(class_name)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
it 'creates the class' do
|
470
|
+
expect(client.create_class(class_name)).to be
|
471
|
+
expect(client.get_class(class_name)['name']).to eq(class_name)
|
472
|
+
end
|
473
|
+
|
474
|
+
it 'allows creation of classes that extend Vertex' do
|
475
|
+
client.create_class(class_name, extends: 'V')
|
476
|
+
expect(client.get_class(class_name)['superClass']).to eq('V')
|
477
|
+
end
|
478
|
+
|
479
|
+
it 'allows creation of abstract classes that extend Edge' do
|
480
|
+
client.create_class(class_name, extends: 'E', abstract: true)
|
481
|
+
expect(client.get_class(class_name)['superClass']).to eq('E')
|
482
|
+
expect(client.get_class(class_name)['abstract']).to be true
|
483
|
+
end
|
484
|
+
|
485
|
+
it 'raises exception on creation of classes that extend nothing' do
|
486
|
+
expect do
|
487
|
+
client.create_class(class_name, extends: 'VJk')
|
488
|
+
end.to raise_exception(OrientdbClient::ClientError, /OCommandSQLParsingException/)
|
489
|
+
end
|
490
|
+
|
491
|
+
describe 'with block' do
|
492
|
+
it 'creates properties on the class' do
|
493
|
+
client.create_class(class_name, extends: 'V') do |c|
|
494
|
+
c.property('member_name', 'string', notnull: true)
|
495
|
+
end
|
496
|
+
expect(client.get_class(class_name)['properties']).to include(hash_including({
|
497
|
+
'name' => 'member_name',
|
498
|
+
'type' => 'STRING',
|
499
|
+
'mandatory' => false,
|
500
|
+
'readonly' => false,
|
501
|
+
'notNull' => true,
|
502
|
+
'min' => nil,
|
503
|
+
'max' => nil,
|
504
|
+
'collate' => 'default'
|
505
|
+
}))
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
context 'with existing class of that name' do
|
510
|
+
it 'raises a ClientError' do
|
511
|
+
client.create_class(class_name)
|
512
|
+
expect do
|
513
|
+
client.create_class(class_name)
|
514
|
+
end.to raise_exception(OrientdbClient::ClientError, /OSchemaException/)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
|
520
|
+
context 'when not connected' do
|
521
|
+
it 'raises UnauthorizedError' do
|
522
|
+
expect do
|
523
|
+
client.create_class(class_name)
|
524
|
+
end.to raise_exception(OrientdbClient::UnauthorizedError)
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
describe '#drop_class' do
|
530
|
+
let(:class_name) { 'Member' }
|
531
|
+
|
532
|
+
context 'when connected' do
|
533
|
+
before(:each) do
|
534
|
+
client.connect(username: username, password: password, db: db)
|
535
|
+
end
|
536
|
+
|
537
|
+
context 'with class' do
|
538
|
+
before(:each) do
|
539
|
+
unless client.has_class?(class_name)
|
540
|
+
client.create_class(class_name)
|
541
|
+
end
|
542
|
+
end
|
543
|
+
|
544
|
+
it 'deletes the class' do
|
545
|
+
client.drop_class(class_name)
|
546
|
+
expect(client.has_class?(class_name)).to be false
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
context 'without class' do
|
551
|
+
before(:each) do
|
552
|
+
if client.has_class?(class_name)
|
553
|
+
client.drop_class(class_name)
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
557
|
+
it 'returns nil' do
|
558
|
+
expect(client.drop_class(class_name)).to be_nil
|
559
|
+
end
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
context 'without connection' do
|
564
|
+
it 'raises UnauthorizedError' do
|
565
|
+
expect { client.drop_class(class_name) }.to raise_exception(OrientdbClient::UnauthorizedError)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
describe '#get_database' do
|
571
|
+
context 'when connected' do
|
572
|
+
before(:each) do
|
573
|
+
client.connect(username: username, password: password, db: db)
|
574
|
+
end
|
575
|
+
|
576
|
+
context 'with db' do
|
577
|
+
it 'returns the database' do
|
578
|
+
expect(client.get_database(db)).to be
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
context 'without db' do
|
583
|
+
it 'raises NotFoundError' do
|
584
|
+
expect { client.get_database('foo') }.to raise_exception(OrientdbClient::NotFoundError, /not authorized/)
|
585
|
+
end
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
context 'without connection' do
|
590
|
+
it 'raises UnauthorizedError' do
|
591
|
+
expect { client.get_database(db) }.to raise_exception(OrientdbClient::UnauthorizedError)
|
592
|
+
end
|
593
|
+
|
594
|
+
context 'with option auth data' do
|
595
|
+
it 'returns the database' do
|
596
|
+
expect(client.get_database(db, {username: username, password: password})).to be
|
597
|
+
end
|
598
|
+
end
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
describe 'duplicate edge creation' do
|
603
|
+
before do
|
604
|
+
client.connect(username: username, password: password, db: db)
|
605
|
+
if client.has_class?('Person')
|
606
|
+
client.command('delete vertex Person')
|
607
|
+
client.drop_class('Person')
|
608
|
+
end
|
609
|
+
if client.has_class?('Friend')
|
610
|
+
client.drop_class('Friend')
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
after do
|
615
|
+
client.command('delete vertex Person')
|
616
|
+
client.drop_class('Person')
|
617
|
+
client.drop_class('Friend')
|
618
|
+
end
|
619
|
+
|
620
|
+
it 'raises DuplicateRecordError' do
|
621
|
+
client.create_class('Person', extends: 'V')
|
622
|
+
client.create_class('Friend', extends: 'E')
|
623
|
+
client.command('create property Friend.out link Person')
|
624
|
+
client.command('create property Friend.in link Person')
|
625
|
+
client.command('create index FollowIdx on Friend (out,in) unique')
|
626
|
+
client.command('create property Person.age integer')
|
627
|
+
jim = client.command('insert into Person CONTENT ' + Oj.dump({'name' => 'jim'}))
|
628
|
+
bob = client.command('insert into Person CONTENT ' + Oj.dump({'name' => 'bob'}))
|
629
|
+
jim_rid = jim['result'][0]['@rid']
|
630
|
+
bob_rid = bob['result'][0]['@rid']
|
631
|
+
client.command("create edge Friend from #{jim_rid} to #{bob_rid}")
|
632
|
+
expect do
|
633
|
+
client.command("create edge Friend from #{jim_rid} to #{bob_rid}")
|
634
|
+
end.to raise_exception(OrientdbClient::DuplicateRecordError, /found duplicated key/)
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
end
|
639
|
+
|
640
|
+
# These specs will sometimes fail, not too much we can do about that, depends
|
641
|
+
# on timing/threading in ruby and odb
|
642
|
+
describe 'mvcc handling', type: :integration do
|
643
|
+
let(:client) do
|
644
|
+
c = OrientdbClient.client
|
645
|
+
c.logger.level = Logger::ERROR
|
646
|
+
c
|
647
|
+
end
|
648
|
+
before do
|
649
|
+
client.connect(username: username, password: password, db: db)
|
650
|
+
if client.has_class?('Person')
|
651
|
+
client.command('delete vertex Person')
|
652
|
+
client.drop_class('Person')
|
653
|
+
end
|
654
|
+
if client.has_class?('Friend')
|
655
|
+
client.drop_class('Friend')
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
after do
|
660
|
+
client.command('delete vertex Person')
|
661
|
+
client.drop_class('Person')
|
662
|
+
client.drop_class('Friend')
|
663
|
+
end
|
664
|
+
|
665
|
+
it 'handles mvcc conflicts' do
|
666
|
+
client.create_class('Person', extends: 'V')
|
667
|
+
client.create_class('Friend', extends: 'E')
|
668
|
+
client.command('create property Friend.out link Person')
|
669
|
+
client.command('create property Friend.in link Person')
|
670
|
+
client.command('create index FollowIdx on Friend (out,in) unique')
|
671
|
+
client.command('create property Person.age integer')
|
672
|
+
jim = client.command('insert into Person CONTENT ' + Oj.dump({'name' => 'jim'}))
|
673
|
+
bob = client.command('insert into Person CONTENT ' + Oj.dump({'name' => 'bob'}))
|
674
|
+
jim_rid = jim['result'][0]['@rid']
|
675
|
+
bob_rid = bob['result'][0]['@rid']
|
676
|
+
thrs = []
|
677
|
+
expect do
|
678
|
+
thrs << Thread.new do
|
679
|
+
100.times do
|
680
|
+
client.command("create edge Friend from #{jim_rid} to #{bob_rid}")
|
681
|
+
client.command("delete edge Friend from #{jim_rid} to #{bob_rid}")
|
682
|
+
end
|
683
|
+
end
|
684
|
+
thrs << Thread.new do
|
685
|
+
100.times do |i|
|
686
|
+
client.command("update #{jim_rid} set age=#{i}")
|
687
|
+
client.command("update #{bob_rid} set age=#{i}")
|
688
|
+
end
|
689
|
+
end
|
690
|
+
thrs.each { |t| t.join }
|
691
|
+
end.to raise_exception(OrientdbClient::MVCCError, /OConcurrentModificationException/)
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
describe 'initialization' do
|
696
|
+
context 'with non-default adapter' do
|
697
|
+
it 'initializes specified adapter' do
|
698
|
+
client = OrientdbClient.client(adapter: 'CurbAdapter')
|
699
|
+
expect(client.http_client).to be_an_instance_of(OrientdbClient::HttpAdapters::CurbAdapter)
|
700
|
+
end
|
701
|
+
end
|
702
|
+
end
|
703
|
+
end
|