nose 0.1.0pre

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/lib/nose/backend/cassandra.rb +390 -0
  3. data/lib/nose/backend/file.rb +185 -0
  4. data/lib/nose/backend/mongo.rb +242 -0
  5. data/lib/nose/backend.rb +557 -0
  6. data/lib/nose/cost/cassandra.rb +33 -0
  7. data/lib/nose/cost/entity_count.rb +27 -0
  8. data/lib/nose/cost/field_size.rb +31 -0
  9. data/lib/nose/cost/request_count.rb +32 -0
  10. data/lib/nose/cost.rb +68 -0
  11. data/lib/nose/debug.rb +45 -0
  12. data/lib/nose/enumerator.rb +199 -0
  13. data/lib/nose/indexes.rb +239 -0
  14. data/lib/nose/loader/csv.rb +99 -0
  15. data/lib/nose/loader/mysql.rb +199 -0
  16. data/lib/nose/loader/random.rb +48 -0
  17. data/lib/nose/loader/sql.rb +105 -0
  18. data/lib/nose/loader.rb +38 -0
  19. data/lib/nose/model/entity.rb +136 -0
  20. data/lib/nose/model/fields.rb +293 -0
  21. data/lib/nose/model.rb +113 -0
  22. data/lib/nose/parser.rb +202 -0
  23. data/lib/nose/plans/execution_plan.rb +282 -0
  24. data/lib/nose/plans/filter.rb +99 -0
  25. data/lib/nose/plans/index_lookup.rb +302 -0
  26. data/lib/nose/plans/limit.rb +42 -0
  27. data/lib/nose/plans/query_planner.rb +361 -0
  28. data/lib/nose/plans/sort.rb +49 -0
  29. data/lib/nose/plans/update.rb +60 -0
  30. data/lib/nose/plans/update_planner.rb +270 -0
  31. data/lib/nose/plans.rb +135 -0
  32. data/lib/nose/proxy/mysql.rb +275 -0
  33. data/lib/nose/proxy.rb +102 -0
  34. data/lib/nose/query_graph.rb +481 -0
  35. data/lib/nose/random/barbasi_albert.rb +48 -0
  36. data/lib/nose/random/watts_strogatz.rb +50 -0
  37. data/lib/nose/random.rb +391 -0
  38. data/lib/nose/schema.rb +89 -0
  39. data/lib/nose/search/constraints.rb +143 -0
  40. data/lib/nose/search/problem.rb +328 -0
  41. data/lib/nose/search/results.rb +200 -0
  42. data/lib/nose/search.rb +266 -0
  43. data/lib/nose/serialize.rb +747 -0
  44. data/lib/nose/statements/connection.rb +160 -0
  45. data/lib/nose/statements/delete.rb +83 -0
  46. data/lib/nose/statements/insert.rb +146 -0
  47. data/lib/nose/statements/query.rb +161 -0
  48. data/lib/nose/statements/update.rb +101 -0
  49. data/lib/nose/statements.rb +645 -0
  50. data/lib/nose/timing.rb +79 -0
  51. data/lib/nose/util.rb +305 -0
  52. data/lib/nose/workload.rb +244 -0
  53. data/lib/nose.rb +37 -0
  54. data/templates/workload.erb +42 -0
  55. metadata +700 -0
data/lib/nose/proxy.rb ADDED
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ # Query processing proxies to transparently execute queries against a backend
5
+ module Proxy
6
+ # A proxy server to interpret our query language and implement query plans
7
+ class ProxyBase
8
+ attr_reader :logger
9
+ def initialize(config, result, backend)
10
+ @logger = Logging.logger['nose::proxy']
11
+
12
+ @result = result
13
+ @backend = backend
14
+ @config = config
15
+
16
+ @continue = true
17
+ end
18
+
19
+ # Start the proxy server
20
+ # @return [void]
21
+ def start
22
+ @logger.info "Starting server on port #{@config[:port]}"
23
+
24
+ server_socket = TCPServer.new('127.0.0.1', @config[:port])
25
+ server_socket.listen(100)
26
+
27
+ @read_sockets = [server_socket]
28
+ @write_sockets = []
29
+ loop do
30
+ break unless @continue && select_connection(server_socket)
31
+ end
32
+ end
33
+
34
+ # @abstract Subclasses should process a new connection
35
+ # on the given socket
36
+ # :nocov:
37
+ # @return [void]
38
+ def handle_connection(_socket)
39
+ fail NotImplementedError
40
+ end
41
+ # :nocov:
42
+
43
+ # @abstract Subclasses should dispose of state associated with the socket
44
+ # :nocov:
45
+ # @return [void]
46
+ def remove_connection(_socket)
47
+ fail NotImplementedError
48
+ end
49
+ # :nocov:
50
+
51
+ # Stop accepting connections
52
+ # @return [void]
53
+ def stop
54
+ @continue = false
55
+ end
56
+
57
+ private
58
+
59
+ # Select sockets which are available to be processed
60
+ # @return [void]
61
+ def select_connection(server_socket)
62
+ read, write, error = IO.select @read_sockets, @write_sockets,
63
+ @read_sockets + @write_sockets, 5
64
+ return true if read.nil?
65
+
66
+ # Check if we have a new incoming connection
67
+ if read.include? server_socket
68
+ accept_connection server_socket
69
+ read.delete server_socket
70
+ elsif error.include? server_socket
71
+ @logger.error 'Server socket died'
72
+ return false
73
+ end
74
+
75
+ # Remove all sockets which have errors
76
+ error.each { |socket| remove_connection socket }
77
+ @read_sockets -= error
78
+ @write_sockets -= error
79
+
80
+ # Handle connections on each available socket
81
+ process_connections read + write
82
+ end
83
+
84
+ # Accept the new connection
85
+ # @return [void]
86
+ def accept_connection(server_socket)
87
+ client_socket, = server_socket.accept
88
+ @read_sockets << client_socket
89
+ @write_sockets << client_socket
90
+ end
91
+
92
+ # Process all pending connections
93
+ # @return [void]
94
+ def process_connections(sockets)
95
+ sockets.each do |socket|
96
+ @write_sockets.delete socket
97
+ @read_sockets.delete socket unless handle_connection socket
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,481 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module NoSE
6
+ # Representations for connected entities which are referenced in a {Query}
7
+ module QueryGraph
8
+ # A single node in a query graph
9
+ class Node
10
+ attr_reader :entity
11
+
12
+ def initialize(entity)
13
+ @entity = entity
14
+ end
15
+
16
+ # :nocov:
17
+ def inspect
18
+ @entity.name
19
+ end
20
+ # :nocov:
21
+
22
+ # Two nodes are equal if they represent the same entity
23
+ def ==(other)
24
+ other.is_a?(Node) && @entity == other.entity
25
+ end
26
+ alias eql? ==
27
+
28
+ def hash
29
+ @entity.name.hash
30
+ end
31
+ end
32
+
33
+ # An edge between two {Node}s in a {Graph}
34
+ class Edge
35
+ attr_reader :from, :to, :key
36
+
37
+ def initialize(from, to, key)
38
+ @from = from
39
+ @to = to
40
+ @key = key
41
+ end
42
+
43
+ # :nocov:
44
+ def inspect
45
+ @key.inspect
46
+ end
47
+ # :nocov:
48
+
49
+ # Edges are equal if the canonical parameters used to construct
50
+ # them are the same
51
+ def ==(other)
52
+ return false unless other.is_a? Edge
53
+ canonical_params == other.canonical_params
54
+ end
55
+ alias eql? ==
56
+
57
+ def hash
58
+ canonical_params.hash
59
+ end
60
+
61
+ # Produce the parameters to initialize the canonical version of this edge
62
+ # (this accounts for different directionality of edges)
63
+ # @return [Array]
64
+ def canonical_params
65
+ if @from.entity.name > @to.entity.name
66
+ [@from.entity.name, @to.entity.name, @key.name]
67
+ else
68
+ [@to.entity.name, @from.entity.name, @key.reverse.name]
69
+ end
70
+ end
71
+ end
72
+
73
+ # A graph identifying entities and relationships involved in a query
74
+ class Graph
75
+ attr_reader :nodes, :entities
76
+
77
+ def initialize(nodes = [], *edges)
78
+ @nodes = Set.new
79
+ @entities = Set.new
80
+ nodes.each { |n| add_node n }
81
+ @edges = {}
82
+
83
+ edges.each { |edge| add_edge(*edge) }
84
+ end
85
+
86
+ # :nocov:
87
+ def inspect
88
+ "Graph(nodes: #{@nodes.map(&:inspect).join(', ')}, " \
89
+ "edges: #{@edges.inspect})"
90
+ end
91
+ # :nocov:
92
+
93
+ # Graphs are equal if they have the same nodes and edges
94
+ def ==(other)
95
+ return false unless other.is_a? Graph
96
+ @nodes == other.nodes && unique_edges == other.unique_edges
97
+ end
98
+ alias eql? ==
99
+
100
+ def hash
101
+ [@nodes, unique_edges.map(&:canonical_params).sort!].hash
102
+ end
103
+
104
+ # The total number of nodes in the graph
105
+ # @return [Integer]
106
+ def size
107
+ @nodes.size
108
+ end
109
+
110
+ # Check if the graph is empty
111
+ # @return [Boolean]
112
+ def empty?
113
+ @nodes.empty?
114
+ end
115
+
116
+ # Duplicate graphs ensuring that edges are correctly copied
117
+ # @return [Graph]
118
+ def dup
119
+ new_graph = super
120
+
121
+ new_graph.instance_variable_set :@nodes, @nodes.dup
122
+ new_edges = Hash[@edges.map do |k, v|
123
+ [k, v.dup]
124
+ end]
125
+ new_graph.instance_variable_set :@edges, new_edges
126
+ new_graph.instance_variable_set :@unique_edges, nil
127
+
128
+ new_graph
129
+ end
130
+
131
+ # Produce an array of entities in the desired join order
132
+ # @return [Array<Entity>]
133
+ def join_order(eq_fields)
134
+ return [@nodes.first.entity] if @nodes.size == 1
135
+
136
+ # Start with a leaf entity which has an equality predicate
137
+ # and the lowest overall count of all such entities
138
+ entities = @entities.dup
139
+ leaf_entities = entities.select { |e| leaf_entity?(e) }
140
+ join_order = [leaf_entities.select do |entity|
141
+ eq_fields.map(&:parent).include?(entity)
142
+ end.min_by(&:count)].compact
143
+ join_order = [leaf_entities.min_by(&:count)] if join_order.empty?
144
+ entities.delete join_order.first
145
+
146
+ # Keep going until we have joined all entities
147
+ until entities.empty?
148
+ # Try to continue from the last entity
149
+ next_entities = edges_for_entity(join_order.last).map do |edge|
150
+ edge.to.entity
151
+ end.to_set
152
+
153
+ # Otherwise look for a new branch from the existing entities
154
+ if (next_entities & entities).empty?
155
+ next_entities = join_order.reduce(Set.new) do |edges, entity|
156
+ edges.union(edges_for_entity(entity))
157
+ end.map { |edge| edge.to.entity }.to_set
158
+ end
159
+
160
+ # Pick the entity with the smallest count, remove it, and keep going
161
+ next_entity = (next_entities & entities).min_by(&:count)
162
+ join_order << next_entity
163
+ entities.delete next_entity
164
+ end
165
+
166
+ join_order
167
+ end
168
+
169
+ # Find the node corresponding to a given entity in the graph
170
+ # @return [Node]
171
+ def entity_node(entity)
172
+ @nodes.find { |n| n.entity == entity }
173
+ end
174
+
175
+ # Check if the graph includes the given entity
176
+ # @return [Boolean]
177
+ def include_entity?(entity)
178
+ !entity_node(entity).nil?
179
+ end
180
+
181
+ # Check if this entity is a leaf in the graph (at most one edge)
182
+ # @return [Boolean]
183
+ def leaf_entity?(entity)
184
+ node = entity_node(entity)
185
+ return false if node.nil?
186
+ !@edges.key?(node) || @edges[node].size <= 1
187
+ end
188
+
189
+ # Produce a path in the graph between two nodes
190
+ # @return [KeyPath]
191
+ def path_between(node1, node2)
192
+ node1 = find_entity_node node1
193
+ node2 = find_entity_node node2
194
+ keys = path_between_visit [node1.entity.id_field], [node1], node2
195
+
196
+ KeyPath.new keys
197
+ end
198
+
199
+ # Find the node which corresponds to a given entity
200
+ def find_entity_node(entity)
201
+ @nodes.find { |n| n.entity == entity }
202
+ end
203
+
204
+ # Add a new node to the graph
205
+ # @return [Node] the new node which was added
206
+ def add_node(node)
207
+ if node.is_a? Entity
208
+ existing = find_entity_node node
209
+ if existing.nil?
210
+ node = Node.new(node)
211
+ @nodes.add node
212
+ @entities.add node.entity
213
+ else
214
+ node = existing
215
+ end
216
+ elsif !@nodes.include? node
217
+ @nodes.add node
218
+ @entities.add node.entity
219
+ end
220
+
221
+ node
222
+ end
223
+
224
+ # Add a new edge betwene two nodes in the graph
225
+ # @return [void]
226
+ def add_edge(node1, node2, key)
227
+ node1 = add_node node1
228
+ node2 = add_node node2
229
+
230
+ @edges[node1] = Set.new unless @edges.key? node1
231
+ @edges[node1].add Edge.new(node1, node2, key)
232
+ @edges[node2] = Set.new unless @edges.key? node2
233
+ @edges[node2].add Edge.new(node2, node1, key.reverse)
234
+
235
+ @unique_edges = nil
236
+ end
237
+
238
+ # Prune nodes not reachable from a given starting node
239
+ # @return [void]
240
+ def prune(start)
241
+ to_visit = [start]
242
+ reachable = Set.new([start])
243
+
244
+ # Determine which nodes are reachable
245
+ until to_visit.empty?
246
+ discovered = Set.new
247
+ to_visit.each do |node|
248
+ next unless @edges.key? node
249
+ discovered += @edges[node].map(&:to).to_set
250
+ end
251
+ to_visit = discovered - reachable
252
+ reachable += discovered
253
+ end
254
+
255
+ remove_nodes @nodes - reachable
256
+ end
257
+
258
+ # Remove nodes (or entities) from the graph
259
+ def remove_nodes(nodes)
260
+ # Find all the nodes to be removed if needed
261
+ nodes.map! { |n| n.is_a?(Node) ? n : find_entity_node(n) }
262
+
263
+ # Remove any nodes and edges which are not reachable
264
+ @edges.reject! { |node| nodes.include? node }
265
+ @edges.each do |_, edges|
266
+ edges.reject! do |edge|
267
+ nodes.include?(edge.to) || nodes.include?(edge.from)
268
+ end
269
+ end
270
+ @edges.reject! { |_, edges| edges.empty? }
271
+ @nodes -= nodes.to_set
272
+ @entities -= nodes.map(&:entity)
273
+
274
+ @unique_edges = nil
275
+ end
276
+
277
+ # Construct a list of all unique edges in the graph
278
+ # @reutrn [Array<Edge>]
279
+ def unique_edges
280
+ # We memoize this calculation so check if it has already been computed
281
+ return @unique_edges unless @unique_edges.nil?
282
+
283
+ all_edges = @edges.values.reduce(&:union).to_a
284
+ all_edges.uniq!(&:canonical_params)
285
+
286
+ @unique_edges = all_edges.to_set
287
+ end
288
+
289
+ # Produce an enumerator which yields all subgraphs of this graph
290
+ # @return [Set<Graph>]
291
+ def subgraphs(recursive = true)
292
+ # We have no subgraphs if there is only one node
293
+ return [self] if @nodes.size == 1
294
+
295
+ all_edges = unique_edges
296
+ all_subgraphs = Set.new([self])
297
+ all_edges.each do |remove_edge|
298
+ # Construct new graphs from either side of the cut edge
299
+ graph1 = Graph.new [remove_edge.from]
300
+ graph2 = Graph.new [remove_edge.to]
301
+ all_edges.each do |edge|
302
+ next if edge == remove_edge
303
+
304
+ graph1.add_edge edge.from, edge.to, edge.key
305
+ graph2.add_edge edge.from, edge.to, edge.key
306
+ end
307
+
308
+ # Prune the graphs before yielding them and their subgraphs
309
+ graph1.prune remove_edge.from
310
+ all_subgraphs.add graph1
311
+ all_subgraphs += graph1.subgraphs if recursive
312
+
313
+ graph2.prune remove_edge.to
314
+ all_subgraphs.add graph2
315
+ all_subgraphs += graph2.subgraphs if recursive
316
+ end
317
+
318
+ all_subgraphs.to_set
319
+ end
320
+
321
+ # Construct a graph from a KeyPath
322
+ # @return [Graph]
323
+ def self.from_path(path)
324
+ return Graph.new if path.empty?
325
+
326
+ path = path.entries if path.is_a?(KeyPath)
327
+ graph = Graph.new
328
+ prev_node = graph.add_node path.first.parent
329
+ path[1..-1].each do |key|
330
+ next_node = graph.add_node key.entity
331
+ graph.add_edge prev_node, next_node, key
332
+ prev_node = next_node
333
+ end
334
+
335
+ graph
336
+ end
337
+
338
+ # Convert this graph into a path if possible
339
+ # @return [KeyPath]
340
+ # @raise [InvalidPathException]
341
+ def to_path(start_entity)
342
+ return KeyPath.new if @nodes.empty?
343
+
344
+ start = @nodes.find { |n| n.entity == start_entity }
345
+
346
+ fail InvalidPathException, 'Need start for path conversion' \
347
+ if start.nil?
348
+ keys = [start.entity.id_field]
349
+ entities = Set.new [start.entity]
350
+
351
+ edges = edges_for_entity start.entity
352
+ until edges.empty?
353
+ new_entities = edges.map { |e| e.to.entity }.to_set.delete_if do |n|
354
+ entities.include?(n)
355
+ end
356
+ break if new_entities.empty?
357
+ fail InvalidPathException, 'Graph cannot be converted to path' \
358
+ if new_entities.size > 1
359
+ edge = edges.find { |e| !entities.include? e.to.entity }
360
+ keys << edge.key
361
+ entities.add edge.to.entity
362
+ edges = edges_for_entity edge.to.entity
363
+ end
364
+
365
+ KeyPath.new keys
366
+ end
367
+
368
+ # Produce a path through the graph of maximum length
369
+ # @return [KeyPath]
370
+ def longest_path
371
+ return KeyPath.new [@nodes.first.entity.id_field] if @nodes.size == 1
372
+
373
+ longest_path = []
374
+ @nodes.each do |node|
375
+ next unless leaf_entity?(node.entity)
376
+
377
+ longest_path = longest_path_visit node, Set.new([node]), [],
378
+ longest_path
379
+ end
380
+
381
+ KeyPath.new [longest_path.first.from.entity.id_field] +
382
+ longest_path.map(&:key)
383
+ end
384
+
385
+ # Output an image of the query graph
386
+ # @return [void]
387
+ def output(format, filename)
388
+ graph = GraphViz.new :G, type: :graph
389
+ nodes = Hash[@nodes.map do |node|
390
+ [node, graph.add_nodes(node.entity.name)]
391
+ end]
392
+
393
+ @edges.each do |_, edges|
394
+ edges.each do |edge|
395
+ graph.add_edges nodes[edge.from], nodes[edge.to],
396
+ label: edge.key.name
397
+ end
398
+ end
399
+
400
+ graph.output(**{ format => filename })
401
+ end
402
+
403
+ # Split this graph into multiple graphs at the given
404
+ # entity, optionally removing the corresponding node
405
+ # return [Array<Graph>]
406
+ def split(entity, keep = false)
407
+ # Simple case with one node
408
+ return keep ? [dup] : [] if size == 1
409
+
410
+ # Find the node corresponding to the entity to remove
411
+ remove_node = @nodes.find { |n| n.entity == entity }
412
+
413
+ # For each edge from this entity, build a new graph with
414
+ # the entity removed and explore the different paths
415
+ @edges[remove_node].map do |edge|
416
+ new_graph = dup
417
+ remove_nodes = (@edges[remove_node] - [edge]).map(&:to)
418
+ remove_nodes << remove_node unless keep
419
+ new_graph.remove_nodes remove_nodes
420
+ new_graph.prune edge.to
421
+
422
+ new_graph
423
+ end
424
+ end
425
+
426
+ # Produce the keys for all edges leaving the given entity
427
+ # @return [Array<Fields::ForeignKeyField>]
428
+ def keys_from_entity(entity)
429
+ edges_for_entity(entity).map(&:key)
430
+ end
431
+
432
+ private
433
+
434
+ # Helper to find the longest path in the graph starting at a given
435
+ # node and not exploring nodes which have been visited
436
+ # We keep track of the visited edges and update the longest path
437
+ # @return [Array<Edge>]
438
+ def longest_path_visit(node, visited_nodes, edges, longest_path)
439
+ # Find new edges we may want to traverse
440
+ new_edges = @edges[node].reject { |e| visited_nodes.include? e.to }
441
+
442
+ # If we reached the end of a path, see if we found a longer one
443
+ if new_edges.empty?
444
+ return edges.length > longest_path.length ? edges : longest_path
445
+ end
446
+
447
+ new_edges.each do |edge|
448
+ longest_path = longest_path_visit edge.to,
449
+ visited_nodes.dup.add(edge.to),
450
+ edges.dup << edge, longest_path
451
+ end
452
+
453
+ longest_path
454
+ end
455
+
456
+ # Return all the edges starting at a given entity
457
+ # @return [Array<Edge>]
458
+ def edges_for_entity(entity)
459
+ pair = @edges.find { |n, _| n.entity == entity }
460
+ pair.nil? ? Set.new : pair.last
461
+ end
462
+
463
+ # Helper for path_between to recursively visit nodes in the graph
464
+ # @return [void]
465
+ def path_between_visit(keys, nodes, end_node)
466
+ return keys if nodes.last == end_node
467
+ return nil unless @edges.key? nodes.last
468
+
469
+ @edges[nodes.last].lazy.map do |edge|
470
+ next if nodes.include? edge.to
471
+ path_between_visit keys.dup << edge.key,
472
+ nodes.dup << edge.to, end_node
473
+ end.reject(&:nil?).first
474
+ end
475
+ end
476
+
477
+ # Thrown when trying to convert a graph which is not a path
478
+ class InvalidPathException < StandardError
479
+ end
480
+ end
481
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ module Random
5
+ # Generates a random graph using the Barbasi-Albert model
6
+ class BarbasiAlbertNetwork < Network
7
+ def initialize(params = {})
8
+ super params
9
+
10
+ # We assume for now that m0 = m = 2
11
+
12
+ create_entities
13
+ add_foreign_keys
14
+ end
15
+
16
+ private
17
+
18
+ # Create all the entities in the graph and their connections
19
+ # @return [void]
20
+ def create_entities
21
+ # Add the initial one or two entities
22
+ @entities = [create_entity(0)]
23
+ return if @nodes_nb == 1
24
+
25
+ @entities << create_entity(1)
26
+ add_link 0, 1
27
+ return if @nodes_nb == 2
28
+
29
+ @entities << create_entity(2)
30
+ add_link 2, 0
31
+ add_link 2, 1
32
+ return if @nodes_nb == 3
33
+
34
+ # Add and connect more entities as needed
35
+ 3.upto(@nodes_nb - 1).each do |node|
36
+ @entities << create_entity(node)
37
+ pick = Pickup.new(0.upto(node - 1).to_a,
38
+ key_func: ->(n) { n },
39
+ weight_func: ->(n) { @neighbours[n].size },
40
+ uniq: true)
41
+ pick.pick(2).each do |node2|
42
+ add_link node, node2
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NoSE
4
+ module Random
5
+ # Generates a random graph using the Watts-Strogatz model
6
+ class WattsStrogatzNetwork < Network
7
+ def initialize(params = {})
8
+ super params
9
+
10
+ @beta = params.fetch :beta, 0.5
11
+ @node_degree = params.fetch :node_degree, 2
12
+ @nodes = 0..(@nodes_nb - 1)
13
+
14
+ @entities = @nodes.map do |node|
15
+ create_entity node
16
+ end
17
+
18
+ build_initial_links
19
+ rewire_links
20
+ add_foreign_keys
21
+ end
22
+
23
+ private
24
+
25
+ # Set up the initial links between all nodes
26
+ # @return [void]
27
+ def build_initial_links
28
+ @nodes.each do |node|
29
+ (@node_degree / 2).times do |i|
30
+ add_link node, (node + i + 1) % @nodes_nb
31
+ end
32
+ end
33
+ end
34
+
35
+ # Rewire all links between nodes
36
+ # @return [void]
37
+ def rewire_links
38
+ (@node_degree / 2).times do |i|
39
+ @nodes.each do |node|
40
+ next unless rand < @beta
41
+
42
+ neighbour = (node + i + 1) % @nodes_nb
43
+ remove_link node, neighbour
44
+ add_link node, new_neighbour(node, neighbour)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end