orientdb4r 0.2.7 → 0.2.8
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.
- data/changelog.txt +5 -1
- data/fstudy/design_v1.dia +0 -0
- data/fstudy/design_v1.png +0 -0
- data/fstudy/domain_model.dia +0 -0
- data/fstudy/domain_model.png +0 -0
- data/fstudy/flat_class_perf.rb +46 -0
- data/fstudy/sample1_object_diagram.dia +0 -0
- data/fstudy/sample1_object_diagram.png +0 -0
- data/fstudy/study_case.rb +87 -0
- data/fstudy/technical_feasibility.rb +251 -0
- data/lib/orientdb4r/client.rb +15 -2
- data/lib/orientdb4r/node.rb +6 -0
- data/lib/orientdb4r/rest/client.rb +71 -70
- data/lib/orientdb4r/rest/excon_node.rb +80 -0
- data/lib/orientdb4r/rest/node.rb +1 -19
- data/lib/orientdb4r/rest/restclient_node.rb +28 -13
- data/lib/orientdb4r/version.rb +1 -0
- data/lib/orientdb4r.rb +13 -3
- data/test/test_database.rb +11 -4
- data/test/test_ddo.rb +43 -5
- data/test/test_dmo.rb +7 -5
- data/test/test_document_crud.rb +17 -1
- metadata +12 -4
- data/test/graphs.rb +0 -114
data/changelog.txt
CHANGED
@@ -3,6 +3,10 @@
|
|
3
3
|
- 'rest-client' replaced by 'excon' for HTTP communication with Keep-Alive
|
4
4
|
- introduced support for distributed server
|
5
5
|
|
6
|
-
0.
|
6
|
+
0.2.8 /07/07/xx
|
7
|
+
- changed and stabilized exception handling
|
8
|
+
- added feature Client#create_class(:properties)
|
9
|
+
|
10
|
+
0.2.7 07/07/12
|
7
11
|
- changed design to support distributed server
|
8
12
|
- added method Client#class_exists?
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'study_case'
|
2
|
+
|
3
|
+
# This class tests performance on a simple flat class.
|
4
|
+
class FlatClassPerf < FStudy::Case
|
5
|
+
|
6
|
+
# def db; 'perf'; end
|
7
|
+
|
8
|
+
def drop
|
9
|
+
client.drop_class 'User'
|
10
|
+
end
|
11
|
+
def model
|
12
|
+
drop
|
13
|
+
client.create_class 'User' do |c|
|
14
|
+
c.property 'username', :string, :mandatory => true
|
15
|
+
c.property 'name', :string, :mandatory => true
|
16
|
+
c.property 'admin', :boolean
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def del
|
20
|
+
client.command 'DELETE FROM User'
|
21
|
+
end
|
22
|
+
def data
|
23
|
+
1.upto(10) do |i|
|
24
|
+
Orientdb4r::logger.info "...done: #{i}" if 0 == (i % 1000)
|
25
|
+
first_name = dg.word
|
26
|
+
surname = dg.word
|
27
|
+
begin
|
28
|
+
client.create_document({ '@class' => 'User', \
|
29
|
+
:username => "#{first_name}.#{surname}", \
|
30
|
+
:name => "#{first_name.capitalize} #{surname.capitalize}", \
|
31
|
+
:admin => (0 == rand(2)) })
|
32
|
+
rescue Exception => e
|
33
|
+
Orientdb4r::logger.error e
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def count
|
39
|
+
puts client.query 'SELECT count(*) FROM User'
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
c = FlatClassPerf.new
|
45
|
+
c.run
|
46
|
+
puts 'OK'
|
Binary file
|
Binary file
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'orientdb4r'
|
2
|
+
|
3
|
+
module FStudy
|
4
|
+
|
5
|
+
###
|
6
|
+
# This class represents a elaboration infrastructure.
|
7
|
+
class Case
|
8
|
+
|
9
|
+
attr_reader :dg
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@dg = DataGenerator.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def db
|
16
|
+
'temp' # default DB is 'temp'
|
17
|
+
end
|
18
|
+
def host
|
19
|
+
'localhost'
|
20
|
+
end
|
21
|
+
|
22
|
+
def client
|
23
|
+
Orientdb4r.client :host => host
|
24
|
+
end
|
25
|
+
|
26
|
+
def watch method
|
27
|
+
start = Time.now
|
28
|
+
self.send method.to_sym
|
29
|
+
Orientdb4r::logger.info "method '#{method}' performed in #{Time.now - start} [s]"
|
30
|
+
end
|
31
|
+
|
32
|
+
def run
|
33
|
+
threads = []
|
34
|
+
thread_cnt = 1
|
35
|
+
thc = ARGV.select { |arg| arg if arg =~ /-th=\d+/ }
|
36
|
+
thread_cnt = thc[0][4..-1].to_i unless thc.empty?
|
37
|
+
|
38
|
+
if thread_cnt > 1
|
39
|
+
1.upto(thread_cnt) do
|
40
|
+
threads << Thread.new do
|
41
|
+
run_threaded
|
42
|
+
end
|
43
|
+
end
|
44
|
+
else
|
45
|
+
run_threaded
|
46
|
+
end
|
47
|
+
threads.each { |th| th.join }
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def run_threaded
|
54
|
+
Orientdb4r::logger.info "started thread #{Thread.current}"
|
55
|
+
client.connect :database => db, :user => 'admin', :password => 'admin'
|
56
|
+
ARGV.each do |arg|
|
57
|
+
watch arg[2..-1].to_sym if arg =~ /^--\w/
|
58
|
+
end
|
59
|
+
client.disconnect
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
class DataGenerator
|
66
|
+
|
67
|
+
def initialize
|
68
|
+
@words = IO.readlines('/usr/share/dict/words')
|
69
|
+
0.upto(@words.size - 1) do |i|
|
70
|
+
word = @words[i]
|
71
|
+
word.strip!.downcase!
|
72
|
+
idx = word.index("'")
|
73
|
+
word = word[0..(idx - 1)] unless idx.nil?
|
74
|
+
@words[i] = word
|
75
|
+
end
|
76
|
+
@words.uniq!
|
77
|
+
Orientdb4r::logger.info "DataGenerator: #{@words.size} words"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Gets a random word.
|
81
|
+
def word
|
82
|
+
@words[rand(@words.size)]
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'study_case'
|
2
|
+
|
3
|
+
# This class represents model and data for Technical Feasibility Study.
|
4
|
+
# See here for more info:
|
5
|
+
# https://github.com/veny/orientdb4r/wiki/Technical-Feasibility
|
6
|
+
class TechnicalFeasibility < FStudy::Case
|
7
|
+
|
8
|
+
def db; 'perf'; end
|
9
|
+
|
10
|
+
# Dropes the document model.
|
11
|
+
def drop
|
12
|
+
classes_definition.reverse.each do |clazz|
|
13
|
+
client.drop_class clazz[:class]
|
14
|
+
puts "droped class: #{clazz[:class]}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Creates the document model.
|
19
|
+
def model
|
20
|
+
drop
|
21
|
+
classes_definition.each do |clazz|
|
22
|
+
class_name = clazz.delete :class
|
23
|
+
client.create_class(class_name, clazz)
|
24
|
+
puts "created class: #{class_name}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Deletes data.
|
29
|
+
def del
|
30
|
+
classes_definition.each do |clazz|
|
31
|
+
client.command "DELETE FROM #{clazz[:class]}"
|
32
|
+
puts "deleted from class: #{clazz[:class]}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Prepares data.
|
37
|
+
def data
|
38
|
+
communities = insert_communities
|
39
|
+
units = insert_org_units
|
40
|
+
users = insert_users(10000, units, communities)
|
41
|
+
insert_contacts(users, 7) # max pro user => ~ coeficient 4.0 pro user
|
42
|
+
contents = insert_contents(users, communities, 1.0) # coeficient pro user
|
43
|
+
insert_activities(users, communities, contents, 100) # coeficient pro user
|
44
|
+
end
|
45
|
+
|
46
|
+
def count
|
47
|
+
puts client.query 'SELECT count(*) FROM User'
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def insert_communities
|
54
|
+
communities = [
|
55
|
+
{ :name => 'Pianists' },
|
56
|
+
{ :name => 'Violinists' },
|
57
|
+
{ :name => 'Dog fanciers' },
|
58
|
+
{ :name => 'Cat fanciers' },
|
59
|
+
{ :name => 'Soccer fans' },
|
60
|
+
{ :name => 'Ski fans' },
|
61
|
+
{ :name => 'Basket fans' },
|
62
|
+
{ :name => 'Gourmets' },
|
63
|
+
{ :name => 'Scifi reader' },
|
64
|
+
{ :name => 'Comic reader' }
|
65
|
+
]
|
66
|
+
|
67
|
+
start = Time.now
|
68
|
+
communities.each do |c|
|
69
|
+
c['@class'] = 'Community'
|
70
|
+
c[:rid] = client.create_document(c)
|
71
|
+
end
|
72
|
+
puts "Created Communities: #{communities.size}, time = #{Time.now - start} [s]"
|
73
|
+
communities
|
74
|
+
end
|
75
|
+
|
76
|
+
def insert_org_units
|
77
|
+
units = [
|
78
|
+
{ :name => 'BigCompany', :descendants => [ 'Automotive', 'Research', 'Infrastructure' ] },
|
79
|
+
{ :name => 'Automotive', :descendants => [ 'Sales', 'Marketing', 'Design' ] },
|
80
|
+
{ :name => 'Sales' },
|
81
|
+
{ :name => 'Marketing' },
|
82
|
+
{ :name => 'Design' },
|
83
|
+
{ :name => 'Research', :descendants => [ 'Scientist', 'Spies' ] },
|
84
|
+
{ :name => 'Scientist' },
|
85
|
+
{ :name => 'Spies' },
|
86
|
+
{ :name => 'Infrastructure', :descendants => [ 'Accounting', 'HumanResources' ] },
|
87
|
+
{ :name => 'Accounting' },
|
88
|
+
{ :name => 'HumanResources' , :descendants => [ 'Recruitment' ] },
|
89
|
+
{ :name => 'Recruitment' }
|
90
|
+
]
|
91
|
+
|
92
|
+
start = Time.now
|
93
|
+
units.each { |unit| insert_org_unit_helper(unit, units) }
|
94
|
+
puts "Created OrgUnits: #{units.size}, time = #{Time.now - start} [s]"
|
95
|
+
units
|
96
|
+
end
|
97
|
+
def insert_org_unit_helper(unit, all)
|
98
|
+
return if unit.include? :rid
|
99
|
+
|
100
|
+
if unit.include? :descendants
|
101
|
+
# recursion
|
102
|
+
unit[:descendants].each do |desc_name|
|
103
|
+
next_unit = all.select { |u| u if u[:name] == desc_name }[0]
|
104
|
+
insert_org_unit_helper(next_unit, all) unless next_unit.include? :rid
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
cloned = unit.clone
|
109
|
+
cloned['@class'] = 'OrgUnit'
|
110
|
+
cloned.delete :descendants
|
111
|
+
|
112
|
+
if unit.include? :descendants
|
113
|
+
descendants = []
|
114
|
+
unit[:descendants].each do |name|
|
115
|
+
descendants << all.select { |ou| ou if ou[:name] == name }[0][:rid]
|
116
|
+
end
|
117
|
+
cloned[:descendants] = descendants
|
118
|
+
end
|
119
|
+
|
120
|
+
rid = client.create_document(cloned)
|
121
|
+
unit[:rid] = rid
|
122
|
+
end
|
123
|
+
|
124
|
+
def insert_users(count, units, communities)
|
125
|
+
start = Time.now
|
126
|
+
users = []
|
127
|
+
1.upto(count) do
|
128
|
+
firstname = dg.word
|
129
|
+
surname = dg.word
|
130
|
+
username = "#{firstname}.#{surname}"
|
131
|
+
# random distribution of Units (1) & Communities (0..3)
|
132
|
+
unit = units[rand(units.size)]
|
133
|
+
comms = []
|
134
|
+
0.upto(rand(4)) { |i| comms << communities[rand(communities.size)][:rid] if i > 0 }
|
135
|
+
|
136
|
+
user = { '@class' => 'User', \
|
137
|
+
:username => username, \
|
138
|
+
:firstname => firstname.capitalize, \
|
139
|
+
:surname => surname.capitalize, \
|
140
|
+
:unit => unit[:rid], \
|
141
|
+
:communities => comms }
|
142
|
+
rid = client.create_document(user)
|
143
|
+
user[:rid] = rid
|
144
|
+
users << user
|
145
|
+
end
|
146
|
+
puts "Created Users: #{users.size}, time = #{Time.now - start} [s]"
|
147
|
+
users
|
148
|
+
end
|
149
|
+
|
150
|
+
def insert_contacts(users, max_contacts)
|
151
|
+
start = Time.now
|
152
|
+
count = 0
|
153
|
+
types = [:friend, :family, :coworker, :enemy]
|
154
|
+
0.upto(users.size - 1) do |i|
|
155
|
+
a = users[i]
|
156
|
+
0.upto(rand(max_contacts)) do
|
157
|
+
b = users[rand(users.size)]
|
158
|
+
client.create_document({'@class' => 'Contact', :a => a[:rid], :b => b[:rid], :type => rand(types.size)})
|
159
|
+
count += 1
|
160
|
+
end
|
161
|
+
end
|
162
|
+
puts "Created Contacts: #{count}, time = #{Time.now - start} [s]"
|
163
|
+
end
|
164
|
+
|
165
|
+
def insert_contents(users, communities, user_coef = 1.0)
|
166
|
+
start = Time.now
|
167
|
+
contents = []
|
168
|
+
classes = [['Article', 'body'], ['Gallery', 'description'], ['Term', 'topic']]
|
169
|
+
limit = (users.size * user_coef).to_i
|
170
|
+
|
171
|
+
1.upto(limit) do
|
172
|
+
clazz = classes[rand(classes.size)]
|
173
|
+
content = {'@class' => clazz[0], :title => "#{dg.word} #{dg.word}", clazz[1] => dg.word}
|
174
|
+
# random distribution of Users (1) & Communities (0..3)
|
175
|
+
content[:author] = users[rand(users.size)][:rid]
|
176
|
+
comms = []
|
177
|
+
0.upto(rand(4)) { |i| comms << communities[rand(communities.size)][:rid] if i > 0 }
|
178
|
+
content[:communities] = comms
|
179
|
+
|
180
|
+
rid = client.create_document(content)
|
181
|
+
content[:rid] = rid
|
182
|
+
contents << content
|
183
|
+
end
|
184
|
+
puts "Created ContentTypes: #{contents.size}, time = #{Time.now - start} [s]"
|
185
|
+
contents
|
186
|
+
end
|
187
|
+
|
188
|
+
def insert_activities(users, communities, contents, user_coef)
|
189
|
+
start = Time.now
|
190
|
+
types = [:login, :logout, :likeit, :rated, :saw, :commented]
|
191
|
+
limit = users.size * user_coef
|
192
|
+
1.upto(limit) do |i|
|
193
|
+
puts "... #{i}" if 0 == i % 100000
|
194
|
+
type = rand(types.size)
|
195
|
+
activity = { '@class' => 'Activity', :type => type, :stamp => (Time.now.to_i - rand(3600*23*30)) }
|
196
|
+
# random distribution of Users (1) & the same communities as user
|
197
|
+
user = users[rand(users.size)]
|
198
|
+
activity[:source] = user[:rid]
|
199
|
+
activity[:communities] = user[:communities]
|
200
|
+
# content
|
201
|
+
content = (types[type] == :login or types[type] == :logout) ? nil : contents[rand(contents.size)]
|
202
|
+
activity[content['@class'].downcase] = content[:rid] unless content.nil?
|
203
|
+
|
204
|
+
client.create_document(activity)
|
205
|
+
end
|
206
|
+
puts "Created Activities: #{limit}, time = #{Time.now - start} [s]"
|
207
|
+
end
|
208
|
+
|
209
|
+
def classes_definition
|
210
|
+
# TODO rdbms_id
|
211
|
+
[
|
212
|
+
{ :class => 'OrgUnit', :properties => [
|
213
|
+
{ :property => 'name', :type => :string, :mandatory => true },
|
214
|
+
# TODO domain
|
215
|
+
{ :property => 'descendants', :type => :linkset, :linked_class => 'OrgUnit' }]},
|
216
|
+
{ :class => 'Community', :properties => [
|
217
|
+
{ :property => 'name', :type => :string, :mandatory => true }]},
|
218
|
+
{ :class => 'User', :properties => [
|
219
|
+
{ :property => 'username', :type => :string, :mandatory => true },
|
220
|
+
{ :property => 'firstname', :type => :string, :mandatory => true },
|
221
|
+
{ :property => 'surname', :type => :string, :mandatory => true },
|
222
|
+
# TODO roles
|
223
|
+
{ :property => 'unit', :type => :link, :linked_class => 'OrgUnit', :mandatory => true },
|
224
|
+
{ :property => 'communities', :type => :linkset, :linked_class => 'Community' }]},
|
225
|
+
{ :class => 'Contact', :properties => [
|
226
|
+
{ :property => 'type', :type => :integer, :mandatory => true },
|
227
|
+
{ :property => 'a', :type => :link, :linked_class => 'User', :mandatory => true },
|
228
|
+
{ :property => 'b', :type => :link, :linked_class => 'User', :mandatory => true }]},
|
229
|
+
{ :class => 'Content', :properties => [
|
230
|
+
{ :property => 'title', :type => :string, :mandatory => true },
|
231
|
+
{ :property => 'author', :type => :link, :linked_class => 'User', :mandatory => true },
|
232
|
+
{ :property => 'accessible_in', :type => :linkset, :linked_class => 'Community' }]},
|
233
|
+
{ :class => 'Article', :extends => 'Content', :properties => [
|
234
|
+
{ :property => 'body', :type => :string, :mandatory => true }]},
|
235
|
+
{ :class => 'Gallery', :extends => 'Content', :properties => [
|
236
|
+
{ :property => 'description', :type => :string, :mandatory => true }]},
|
237
|
+
{ :class => 'Term', :extends => 'Content', :properties => [
|
238
|
+
{ :property => 'topic', :type => :string, :mandatory => true }]},
|
239
|
+
{ :class => 'Activity', :properties => [
|
240
|
+
{ :property => 'type', :type => :integer, :mandatory => true },
|
241
|
+
{ :property => 'stamp', :type => :integer, :mandatory => true },
|
242
|
+
{ :property => 'source', :type => :link, :linked_class => 'User', :mandatory => true },
|
243
|
+
{ :property => 'communities', :type => :linkset, :linked_class => 'Community' }]}
|
244
|
+
]
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
c = TechnicalFeasibility.new
|
250
|
+
c.run
|
251
|
+
puts 'OK'
|
data/lib/orientdb4r/client.rb
CHANGED
@@ -106,7 +106,7 @@ module Orientdb4r
|
|
106
106
|
# Creates a new class in the schema.
|
107
107
|
def create_class(name, options={})
|
108
108
|
raise ArgumentError, "class name is blank" if blank?(name)
|
109
|
-
opt_pattern = { :extends => :optional , :cluster => :optional, :force => false }
|
109
|
+
opt_pattern = { :extends => :optional , :cluster => :optional, :force => false, :properties => :optional }
|
110
110
|
verify_options(options, opt_pattern)
|
111
111
|
|
112
112
|
sql = "CREATE CLASS #{name}"
|
@@ -117,6 +117,19 @@ module Orientdb4r
|
|
117
117
|
|
118
118
|
command sql
|
119
119
|
|
120
|
+
# properties given?
|
121
|
+
if options.include? :properties
|
122
|
+
props = options[:properties]
|
123
|
+
raise ArgumentError, 'properties have to be an array' unless props.is_a? Array
|
124
|
+
|
125
|
+
props.each do |prop|
|
126
|
+
raise ArgumentError, 'property definition has to be a hash' unless prop.is_a? Hash
|
127
|
+
prop_name = prop.delete :property
|
128
|
+
prop_type = prop.delete :type
|
129
|
+
create_property(name, prop_name, prop_type, prop)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
120
133
|
if block_given?
|
121
134
|
proxy = Orientdb4r::Utils::Proxy.new(self, name)
|
122
135
|
def proxy.property(property, type, options={})
|
@@ -145,7 +158,7 @@ module Orientdb4r
|
|
145
158
|
rslt = true
|
146
159
|
begin
|
147
160
|
get_class name
|
148
|
-
rescue
|
161
|
+
rescue OrientdbError
|
149
162
|
rslt = false
|
150
163
|
end
|
151
164
|
rslt
|
data/lib/orientdb4r/node.rb
CHANGED
@@ -18,6 +18,12 @@ module Orientdb4r
|
|
18
18
|
end
|
19
19
|
|
20
20
|
|
21
|
+
###
|
22
|
+
# Identifies technology backgound of this node implementation.
|
23
|
+
def identification
|
24
|
+
raise NotImplementedError, 'this should be overridden by subclass'
|
25
|
+
end
|
26
|
+
|
21
27
|
###
|
22
28
|
# Cleans up resources used by the node.
|
23
29
|
def cleanup
|