flockdb 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/LICENSE +13 -0
- data/README.md +60 -0
- data/Rakefile +34 -0
- data/VERSION +1 -0
- data/flockdb.gemspec +82 -0
- data/lib/flock.rb +40 -0
- data/lib/flock/client.rb +124 -0
- data/lib/flock/mixins/sizeable.rb +17 -0
- data/lib/flock/mock_service.rb +178 -0
- data/lib/flock/operation.rb +116 -0
- data/lib/flock/operations/complex_operation.rb +15 -0
- data/lib/flock/operations/execute_operation.rb +14 -0
- data/lib/flock/operations/execute_operations.rb +35 -0
- data/lib/flock/operations/query_term.rb +25 -0
- data/lib/flock/operations/select_operation.rb +51 -0
- data/lib/flock/operations/simple_operation.rb +16 -0
- data/lib/flock/service.rb +16 -0
- data/lib/flock/thrift/edges.rb +1445 -0
- data/lib/flock/thrift/edges_types.rb +210 -0
- data/lib/flock/thrift/flock_constants.rb +12 -0
- data/lib/flock/thrift/flock_types.rb +168 -0
- data/lib/flock/thrift/shards.rb +1598 -0
- data/lib/flockdb.rb +1 -0
- data/spec/flock_spec.rb +102 -0
- data/spec/mock_service_spec.rb +27 -0
- data/spec/spec.opts +7 -0
- data/spec/spec_helper.rb +19 -0
- metadata +145 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/*
|
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
FlockDB Client is Copyright (C) 2010 Twitter, Inc.
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
4
|
+
use this work except in compliance with the License. You may obtain a copy of
|
5
|
+
the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
11
|
+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
12
|
+
License for the specific language governing permissions and limitations under
|
13
|
+
the License.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
FlockDB ruby client
|
2
|
+
|
3
|
+
FlockDB is a distributed graph database capable of storing millions of
|
4
|
+
nodes, billions of edges, and handling tens of thousands of operations
|
5
|
+
per second. Interact with it in ruby with this client.
|
6
|
+
|
7
|
+
|
8
|
+
INSTALLATION
|
9
|
+
------------
|
10
|
+
|
11
|
+
gem install flockdb
|
12
|
+
|
13
|
+
FlockDB depends on thrift and thrift_client, which will install as
|
14
|
+
part of the gem's dependencies. Note that thrift has a native C
|
15
|
+
extension which it will need to compile.
|
16
|
+
|
17
|
+
|
18
|
+
USAGE
|
19
|
+
-----
|
20
|
+
|
21
|
+
Instantiate a client:
|
22
|
+
|
23
|
+
flockdb = Flock.new 'localhost:7915, :graphs => { :follows => 1, :blocks => 2 }
|
24
|
+
|
25
|
+
Flock.new is a convenience shortcut for Flock::Client.new. Flock.new
|
26
|
+
expects a list (or a single) servers as the first argument, and an
|
27
|
+
options hash. Flock::Client.new takes the same options as ThriftClient
|
28
|
+
for configuring the raw thrift client, as well as :graphs for mapping
|
29
|
+
ruby symbols to the corresponding graph ids. See [thrift_client's
|
30
|
+
documentation](http://github.com/fauna/thrift_client) for information
|
31
|
+
on the other options it takes.
|
32
|
+
|
33
|
+
Edge manipulation:
|
34
|
+
|
35
|
+
flockdb.add(1, :follows, 2)
|
36
|
+
flockdb.remove(1, :blocks, 2)
|
37
|
+
|
38
|
+
Perform a query:
|
39
|
+
|
40
|
+
flockdb.select(1, :follows, nil).to_a
|
41
|
+
|
42
|
+
The client supports a rich query api to build up complex set operations:
|
43
|
+
|
44
|
+
flockdb.select(nil, :follows, 1).difference(flockdb.select(1, :follows, nil).intersect(2, :follows, nil)).to_a
|
45
|
+
|
46
|
+
|
47
|
+
CONTRIBUTORS
|
48
|
+
------------
|
49
|
+
|
50
|
+
Nick Kallen
|
51
|
+
Matt Freels
|
52
|
+
Rael Dornfest
|
53
|
+
|
54
|
+
|
55
|
+
LICENSE
|
56
|
+
-------
|
57
|
+
|
58
|
+
Copyright (C) 2010 Twitter, Inc.
|
59
|
+
|
60
|
+
This work is licensed under the Apache License, Version 2.0. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
ROOT_DIR = File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'rubygems' rescue nil
|
4
|
+
require 'rake'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
desc "Run all specs in spec directory."
|
10
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
11
|
+
t.spec_opts = ['--options', "\"#{ROOT_DIR}/spec/spec.opts\""]
|
12
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
13
|
+
end
|
14
|
+
|
15
|
+
# gemification with jeweler
|
16
|
+
begin
|
17
|
+
require 'jeweler'
|
18
|
+
Jeweler::Tasks.new do |gemspec|
|
19
|
+
gemspec.name = "flockdb"
|
20
|
+
gemspec.summary = "Ruby Flock client"
|
21
|
+
gemspec.description = "Get your flock on in Ruby."
|
22
|
+
gemspec.email = "freels@twitter.com"
|
23
|
+
gemspec.homepage = "http://twitter.com"
|
24
|
+
gemspec.authors = ["Matt Freels", "Rael Dornfest", "Nick Kallen"]
|
25
|
+
gemspec.add_dependency 'thrift', '>= 0.2.0'
|
26
|
+
gemspec.add_dependency 'thrift_client', '>= 0.4.1'
|
27
|
+
|
28
|
+
# development
|
29
|
+
gemspec.add_development_dependency 'rspec'
|
30
|
+
gemspec.add_development_dependency 'rr'
|
31
|
+
end
|
32
|
+
rescue LoadError
|
33
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
34
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
data/flockdb.gemspec
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{flockdb}
|
8
|
+
s.version = "0.3.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Matt Freels", "Rael Dornfest", "Nick Kallen"]
|
12
|
+
s.date = %q{2010-04-19}
|
13
|
+
s.description = %q{Get your flock on in Ruby.}
|
14
|
+
s.email = %q{freels@twitter.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.md",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"flockdb.gemspec",
|
26
|
+
"lib/flock.rb",
|
27
|
+
"lib/flock/client.rb",
|
28
|
+
"lib/flock/mixins/sizeable.rb",
|
29
|
+
"lib/flock/mock_service.rb",
|
30
|
+
"lib/flock/operation.rb",
|
31
|
+
"lib/flock/operations/complex_operation.rb",
|
32
|
+
"lib/flock/operations/execute_operation.rb",
|
33
|
+
"lib/flock/operations/execute_operations.rb",
|
34
|
+
"lib/flock/operations/query_term.rb",
|
35
|
+
"lib/flock/operations/select_operation.rb",
|
36
|
+
"lib/flock/operations/simple_operation.rb",
|
37
|
+
"lib/flock/service.rb",
|
38
|
+
"lib/flock/thrift/edges.rb",
|
39
|
+
"lib/flock/thrift/edges_types.rb",
|
40
|
+
"lib/flock/thrift/flock_constants.rb",
|
41
|
+
"lib/flock/thrift/flock_types.rb",
|
42
|
+
"lib/flock/thrift/shards.rb",
|
43
|
+
"lib/flockdb.rb",
|
44
|
+
"spec/flock_spec.rb",
|
45
|
+
"spec/mock_service_spec.rb",
|
46
|
+
"spec/spec.opts",
|
47
|
+
"spec/spec_helper.rb"
|
48
|
+
]
|
49
|
+
s.homepage = %q{http://twitter.com}
|
50
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
51
|
+
s.require_paths = ["lib"]
|
52
|
+
s.rubygems_version = %q{1.3.6}
|
53
|
+
s.summary = %q{Ruby Flock client}
|
54
|
+
s.test_files = [
|
55
|
+
"spec/flock_spec.rb",
|
56
|
+
"spec/mock_service_spec.rb",
|
57
|
+
"spec/spec_helper.rb"
|
58
|
+
]
|
59
|
+
|
60
|
+
if s.respond_to? :specification_version then
|
61
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
65
|
+
s.add_runtime_dependency(%q<thrift>, [">= 0.2.0"])
|
66
|
+
s.add_runtime_dependency(%q<thrift_client>, [">= 0.4.1"])
|
67
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
68
|
+
s.add_development_dependency(%q<rr>, [">= 0"])
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<thrift>, [">= 0.2.0"])
|
71
|
+
s.add_dependency(%q<thrift_client>, [">= 0.4.1"])
|
72
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
73
|
+
s.add_dependency(%q<rr>, [">= 0"])
|
74
|
+
end
|
75
|
+
else
|
76
|
+
s.add_dependency(%q<thrift>, [">= 0.2.0"])
|
77
|
+
s.add_dependency(%q<thrift_client>, [">= 0.4.1"])
|
78
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
79
|
+
s.add_dependency(%q<rr>, [">= 0"])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
data/lib/flock.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# external dependencies
|
2
|
+
require 'thrift'
|
3
|
+
require 'thrift_client'
|
4
|
+
|
5
|
+
require 'flock/mixins/sizeable'
|
6
|
+
|
7
|
+
# thrift sources. load order is important.
|
8
|
+
require 'flock/thrift/flock_types'
|
9
|
+
require 'flock/thrift/flock_constants'
|
10
|
+
require 'flock/thrift/shards'
|
11
|
+
require 'flock/thrift/edges_types'
|
12
|
+
require 'flock/thrift/edges'
|
13
|
+
|
14
|
+
require 'flock/operation'
|
15
|
+
require 'flock/operations/query_term'
|
16
|
+
require 'flock/operations/select_operation'
|
17
|
+
require 'flock/operations/complex_operation'
|
18
|
+
require 'flock/operations/simple_operation'
|
19
|
+
require 'flock/operations/execute_operation'
|
20
|
+
require 'flock/operations/execute_operations'
|
21
|
+
require 'flock/service'
|
22
|
+
require 'flock/client'
|
23
|
+
|
24
|
+
module Flock
|
25
|
+
autoload :MockService, 'flock/mock_service'
|
26
|
+
|
27
|
+
class UnknownGraphError < FlockException
|
28
|
+
def initialize(graph)
|
29
|
+
super("Unable to look up id for graph #{graph.inspect}. Register graphs with Flock.graphs = <graph list>.")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
attr_accessor :default_service_class, :graphs
|
35
|
+
|
36
|
+
def new(*args)
|
37
|
+
Flock::Client.new(*args)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/flock/client.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
class Flock::Client
|
2
|
+
|
3
|
+
attr_accessor :graphs
|
4
|
+
attr_reader :service
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def service_class; Flock.default_service_class ||= Flock::Service end
|
8
|
+
end
|
9
|
+
|
10
|
+
# takes arguments a list of servers and an options hash to pass to the default service_class,
|
11
|
+
# or a service itself
|
12
|
+
def initialize(servers = nil, options = {})
|
13
|
+
if graphs = (options.delete(:graphs) || Flock.graphs)
|
14
|
+
@graphs = graphs
|
15
|
+
end
|
16
|
+
|
17
|
+
@service =
|
18
|
+
if servers.nil? or servers.is_a? Array or servers.is_a? String
|
19
|
+
self.class.service_class.new(servers, options)
|
20
|
+
else
|
21
|
+
servers
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# local results cache
|
27
|
+
|
28
|
+
def cache_locally
|
29
|
+
@cache = {}
|
30
|
+
yield
|
31
|
+
ensure
|
32
|
+
@cache = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def _cache(op, query)
|
36
|
+
return yield unless @cache
|
37
|
+
@cache[[op, query]] ||= yield
|
38
|
+
end
|
39
|
+
|
40
|
+
def _cache_clear
|
41
|
+
@cache = {} if @cache
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# queries
|
46
|
+
|
47
|
+
def select(*query)
|
48
|
+
Flock::SimpleOperation.new(self, _query_args(query))
|
49
|
+
end
|
50
|
+
|
51
|
+
def contains(*query)
|
52
|
+
_cache :contains, query do
|
53
|
+
select(*query).any?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def size(*query)
|
58
|
+
_cache :size, query do
|
59
|
+
select(*query).size
|
60
|
+
end
|
61
|
+
end
|
62
|
+
alias count size
|
63
|
+
|
64
|
+
|
65
|
+
# edge manipulation
|
66
|
+
|
67
|
+
def update(method, source_id, graph, destination_id, priority = Flock::Priority::High)
|
68
|
+
_cache_clear
|
69
|
+
ops = current_transaction || Flock::ExecuteOperations.new(@service, priority)
|
70
|
+
ops.send(method, *_query_args([source_id, graph, destination_id]))
|
71
|
+
ops.apply unless in_transaction?
|
72
|
+
end
|
73
|
+
|
74
|
+
[:add, :remove, :archive, :unarchive].each do |method|
|
75
|
+
class_eval "def #{method}(*args); update(#{method.inspect}, *args) end", __FILE__, __LINE__
|
76
|
+
end
|
77
|
+
|
78
|
+
def transaction(priority = Flock::Priority::High, &block)
|
79
|
+
new_transaction = !in_transaction?
|
80
|
+
|
81
|
+
ops =
|
82
|
+
if new_transaction
|
83
|
+
Thread.current[:edge_transaction] = Flock::ExecuteOperations.new(@service, priority)
|
84
|
+
else
|
85
|
+
current_transaction
|
86
|
+
end
|
87
|
+
|
88
|
+
result = yield self if block.arity == 1
|
89
|
+
ops.apply if new_transaction
|
90
|
+
result
|
91
|
+
|
92
|
+
ensure
|
93
|
+
Thread.current[:edge_transaction] = nil if new_transaction
|
94
|
+
end
|
95
|
+
|
96
|
+
def current_transaction
|
97
|
+
Thread.current[:edge_transaction]
|
98
|
+
end
|
99
|
+
|
100
|
+
def in_transaction?
|
101
|
+
!!Thread.current[:edge_transaction]
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
# graph name lookup utility methods
|
106
|
+
|
107
|
+
def _lookup_graph(key)
|
108
|
+
if @graphs.nil? or key.is_a? Integer
|
109
|
+
key
|
110
|
+
else
|
111
|
+
@graphs[key] or raise UnknownGraphError.new(key)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def _query_args(args)
|
116
|
+
source, graph, destination = *((args.length == 1) ? args.first : args)
|
117
|
+
[_node_arg(source), _lookup_graph(graph), _node_arg(destination)]
|
118
|
+
end
|
119
|
+
|
120
|
+
def _node_arg(node)
|
121
|
+
return node.map {|n| n.to_i if n } if node.respond_to? :map
|
122
|
+
node.to_i if node
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module Flock
|
2
|
+
module MockService
|
3
|
+
extend self
|
4
|
+
|
5
|
+
attr_accessor :timeout, :fixtures
|
6
|
+
|
7
|
+
def clear
|
8
|
+
@sources = @destinations = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def load(fixtures = nil)
|
12
|
+
fixtures ||= self.fixtures or raise "No flock fixtures specified. either pass fixtures to load, or set Flock::MockService.fixtures."
|
13
|
+
|
14
|
+
clear
|
15
|
+
|
16
|
+
fixtures.each do |fixture|
|
17
|
+
file, graph_id, source, destination = fixture.values_at(:file, :graph, :source, :destination)
|
18
|
+
|
19
|
+
fixtures_data = YAML::load(ERB.new(File.open(file, 'r').read).result(binding)).sort
|
20
|
+
fixtures_data.each do |key, value|
|
21
|
+
sources[[value[destination], graph_id]] << value[source]
|
22
|
+
destinations[[value[source], graph_id]] << value[destination]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"Flock::MockService: " + @sources.inspect + " - " + @destinations.inspect
|
29
|
+
end
|
30
|
+
|
31
|
+
def execute(operations)
|
32
|
+
operations = operations.operations
|
33
|
+
operations.each do |operation|
|
34
|
+
term = operation.term
|
35
|
+
backward_data_source, forward_data_source = term.is_forward ? [sources, destinations] : [destinations, sources]
|
36
|
+
backward_archived_data_source, forward_archived_data_source = term.is_forward ? [archived_sources, archived_destinations] : [archived_destinations, archived_sources]
|
37
|
+
source_id, graph_id = term.source_id, term.graph_id
|
38
|
+
destination_ids = term.destination_ids && term.destination_ids.unpack("Q*")
|
39
|
+
case operation.operation_type
|
40
|
+
when Edges::ExecuteOperationType::Add
|
41
|
+
if destination_ids.nil?
|
42
|
+
backward_archived_data_source.delete([source_id, graph_id]).to_a.each do |n|
|
43
|
+
(forward_data_source[[n, graph_id]] << source_id).uniq!
|
44
|
+
(backward_data_source[[source_id, graph_id]] << n).uniq!
|
45
|
+
forward_archived_data_source[[n, graph_id]].delete(source_id)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
destination_ids.each do |destination_id|
|
49
|
+
backward_data_source[[destination_id, graph_id]] << source_id
|
50
|
+
forward_data_source[[source_id, graph_id]] << destination_id
|
51
|
+
end
|
52
|
+
end
|
53
|
+
when Edges::ExecuteOperationType::Remove
|
54
|
+
destination_ids.each do |destination_id|
|
55
|
+
backward_data_source[[destination_id, graph_id]].delete(source_id)
|
56
|
+
forward_data_source[[source_id, graph_id]].delete(destination_id)
|
57
|
+
end
|
58
|
+
when Edges::ExecuteOperationType::Archive
|
59
|
+
if destination_ids.nil?
|
60
|
+
backward_data_source.delete([source_id, graph_id]).to_a.each do |n|
|
61
|
+
forward_archived_data_source[[n, graph_id]] << source_id
|
62
|
+
backward_archived_data_source[[source_id, graph_id]] << n
|
63
|
+
forward_data_source[[n, graph_id]].delete(source_id)
|
64
|
+
end
|
65
|
+
else
|
66
|
+
raise "not yet implemented"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def select(select_operations, page)
|
73
|
+
iterate(select_query(select_operations), page)
|
74
|
+
end
|
75
|
+
|
76
|
+
def count(select_operations)
|
77
|
+
select_query(select_operations).size
|
78
|
+
end
|
79
|
+
|
80
|
+
# FIXME
|
81
|
+
def counts_of_sources_for(destination_ids, graph_id)
|
82
|
+
destination_ids.unpack("Q*").map do |destination_id|
|
83
|
+
(sources[[destination_id, graph_id]] || []).size
|
84
|
+
end.pack("I*")
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def select_query(select_operations)
|
89
|
+
stack = []
|
90
|
+
select_operations.each do |select_operation|
|
91
|
+
case select_operation.operation_type
|
92
|
+
when Edges::SelectOperationType::SimpleQuery
|
93
|
+
query_term = select_operation.term
|
94
|
+
data_source = query_term.is_forward ? destinations : sources
|
95
|
+
data = data_source[[query_term.source_id, query_term.graph_id]]
|
96
|
+
if query_term.destination_ids
|
97
|
+
data &= query_term.destination_ids.unpack("Q*")
|
98
|
+
end
|
99
|
+
stack.push(data)
|
100
|
+
when Edges::SelectOperationType::Intersection
|
101
|
+
stack.push(stack.pop & stack.pop)
|
102
|
+
when Edges::SelectOperationType::Union
|
103
|
+
stack.push(stack.pop | stack.pop)
|
104
|
+
when Edges::SelectOperationType::Difference
|
105
|
+
operand2 = stack.pop
|
106
|
+
operand1 = stack.pop
|
107
|
+
stack.push(operand1 - operand2)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
stack.pop
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def iterate(data, page)
|
116
|
+
return empty_result if page.cursor == Flock::CursorEnd
|
117
|
+
|
118
|
+
start = if page.cursor < Flock::CursorStart
|
119
|
+
[-page.cursor - page.count, 0].max
|
120
|
+
else
|
121
|
+
page.cursor == Flock::CursorStart ? 0 : page.cursor
|
122
|
+
end
|
123
|
+
rv = data.slice(start, page.count)
|
124
|
+
next_cursor = (start + page.count >= data.size) ? Flock::CursorEnd : start + page.count
|
125
|
+
prev_cursor = (start <= 0) ? Flock::CursorEnd : -start
|
126
|
+
|
127
|
+
result = Flock::Results.new
|
128
|
+
result.ids = Array(rv).pack("Q*")
|
129
|
+
result.next_cursor = next_cursor
|
130
|
+
result.prev_cursor = prev_cursor
|
131
|
+
result
|
132
|
+
end
|
133
|
+
|
134
|
+
def empty_result
|
135
|
+
@empty_result ||= begin
|
136
|
+
empty_result = Flock::Results.new
|
137
|
+
empty_result.ids = ""
|
138
|
+
empty_result.next_cursor = Flock::CursorEnd
|
139
|
+
empty_result.prev_cursor = Flock::CursorEnd
|
140
|
+
empty_result
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def sources
|
145
|
+
@sources ||= Hash.new do |h, k|
|
146
|
+
h[k] = []
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def destinations
|
151
|
+
@destinations ||= Hash.new do |h, k|
|
152
|
+
h[k] = []
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def archived_sources
|
157
|
+
@archived_sources ||= Hash.new do |h, k|
|
158
|
+
h[k] = []
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def archived_destinations
|
163
|
+
@archived_destinations ||= Hash.new do |h, k|
|
164
|
+
h[k] = []
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# deprecated
|
169
|
+
public
|
170
|
+
def offset_sources_for(destination_id, graph_id, offset, count)
|
171
|
+
(sources[[destination_id, graph_id]].slice(offset, count) || []).pack("Q*")
|
172
|
+
end
|
173
|
+
|
174
|
+
def offset_destinations_for(source_id, graph_id, offset, count)
|
175
|
+
(destinations[[source_id, graph_id]].slice(offset, count) || []).pack("Q*")
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|