├── spec ├── pacer_spec.rb ├── pacer │ ├── support │ │ └── array_list_spec.rb │ ├── blueprints │ │ ├── dex_spec.rb │ │ ├── tg_spec.rb │ │ ├── orient_spec.rb │ │ ├── neo4j_spec.rb │ │ └── neo4j2_spec.rb │ ├── side_effect │ │ ├── is_unique_spec.rb │ │ └── as_var_spec.rb │ ├── filter │ │ ├── uniq_filter_spec.rb │ │ ├── collection_filter_spec.rb │ │ ├── random_filter_spec.rb │ │ ├── object_filter_spec.rb │ │ ├── empty_filter_spec.rb │ │ └── property_filter │ │ │ └── edge_filters_spec.rb │ ├── route │ │ └── mixin │ │ │ ├── bulk_operations_spec.rb │ │ │ └── route_operations_spec.rb │ ├── transform │ │ ├── process_spec.rb │ │ ├── map_spec.rb │ │ └── path_tree_spec.rb │ ├── core │ │ └── graph │ │ │ ├── element_route_spec.rb │ │ │ └── edges_route_spec.rb │ ├── graph │ │ ├── yaml_encoder_spec.rb │ │ └── simple_encoder_spec.rb │ └── utils │ │ └── tsort_spec.rb ├── support │ ├── contexts.rb │ ├── matchers.rb │ └── use_transactions.rb └── tackle │ ├── simple_mixin.rb │ └── tinkerpop_graph_mixins.rb ├── .rspec ├── autotest └── discover.rb ├── .document ├── CONTRIBUTORS ├── Jarfile ├── lib └── pacer │ ├── support │ ├── nil_class.rb │ ├── hash.rb │ ├── lock_jar_disabler.rb │ ├── lock_jar.rb │ ├── native_exception.rb │ ├── array_list.rb │ ├── proc.rb │ └── awesome_print.rb │ ├── core │ ├── string_route.rb │ ├── side_effect.rb │ ├── graph.rb │ ├── hash_route.rb │ └── graph │ │ ├── graph_route.rb │ │ ├── graph_index_route.rb │ │ └── mixed_route.rb │ ├── transform │ ├── flat_map.rb │ ├── gather.rb │ ├── identity.rb │ ├── wrapped_path.rb │ ├── scatter.rb │ ├── stream_uniq.rb │ ├── stream_sort.rb │ ├── parallel.rb │ ├── make_pairs.rb │ ├── process.rb │ ├── has_count_cap.rb │ ├── lookup_ids.rb │ ├── payload.rb │ ├── path.rb │ ├── join.rb │ ├── count_section.rb │ ├── cap.rb │ ├── map.rb │ ├── gather_section.rb │ └── path_tree.rb │ ├── utils.rb │ ├── pipe │ ├── unwrapping_pipe.rb │ ├── type_filter_pipe.rb │ ├── process_pipe.rb │ ├── naked_pipe.rb │ ├── block_filter_pipe.rb │ ├── enumerable_pipe.rb │ ├── stream_uniq_pipe.rb │ ├── ruby_pipe.rb │ ├── multi_pipe.rb │ ├── property_comparison_pipe.rb │ ├── cross_product_transform_pipe.rb │ ├── wrapping_pipe.rb │ ├── unary_transform_pipe.rb │ ├── path_wrapping_pipe.rb │ ├── simple_visitor_pipe.rb │ ├── visitor_pipe.rb │ ├── loop_pipe.rb │ └── stream_sort_pipe.rb │ ├── blueprints │ ├── tg.rb │ ├── payload_elements.rb │ └── group_vertex.rb │ ├── filter │ ├── random_filter.rb │ ├── uniq_filter.rb │ ├── index_filter.rb │ ├── block_filter.rb │ ├── uniq_section.rb │ ├── limit_section_filter.rb │ ├── where_filter.rb │ ├── future_filter.rb │ ├── empty_filter.rb │ ├── range_filter.rb │ ├── property_filter │ │ └── edge_filters.rb │ ├── property_filter.rb │ └── object_filter.rb │ ├── version.rb │ ├── side_effect │ ├── counted.rb │ ├── visitor.rb │ ├── group_count.rb │ ├── is_unique.rb │ ├── aggregate.rb │ └── as_var.rb │ ├── graph │ ├── hash_index.rb │ ├── simple_encoder.rb │ ├── graph_ml.rb │ └── yaml_encoder.rb │ ├── exceptions.rb │ ├── visitors │ ├── visits_section.rb │ └── section.rb │ ├── wrappers │ ├── index_wrapper.rb │ ├── wrapper_selector.rb │ ├── path_wrapping_pipe_function.rb │ └── wrapping_pipe_function.rb │ ├── function_resolver.rb │ ├── route │ └── mixin │ │ └── bulk_operations.rb │ ├── utils │ ├── tsort.rb │ └── trie.rb │ └── pipes.rb ├── .autotest ├── ext ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── xnlogic │ │ │ └── pacer │ │ │ └── pipes │ │ │ ├── NeverPipe.java │ │ │ ├── CollectionFilterPipe.java │ │ │ ├── LabelPrefixPipe.java │ │ │ ├── EdgesPipe.java │ │ │ ├── VerticesPipe.java │ │ │ ├── IsEmptyPipe.java │ │ │ ├── LabelCollectionFilterPipe.java │ │ │ ├── IsUniquePipe.java │ │ │ ├── BlackboxPipeline.java │ │ │ ├── IdCollectionFilterPipe.java │ │ │ └── ExpandablePipe.java │ └── test │ │ └── java │ │ └── com │ │ └── xnlogic │ │ └── pacer │ │ └── pipes │ │ ├── NeverPipeTest.java │ │ ├── BlackboxPipelineTest.java │ │ ├── CollectionFilterPipeTest.java │ │ ├── ExpandablePipeTest.java │ │ ├── IsUniquePipeTest.java │ │ ├── LabelPrefixPipeTest.java │ │ ├── IdCollectionFilterPipeTest.java │ │ └── VerticesPipeTest.java ├── .project ├── .gitignore ├── .classpath └── pom.xml ├── samples ├── profile.rb └── grateful_dead.rb ├── bin ├── rake ├── rcov ├── yard ├── rspec ├── yardoc ├── autospec └── autotest ├── .gitignore ├── Gemfile ├── pacer.gemspec ├── Gemfile-custom.sample ├── ideas.rb ├── Rakefile ├── LICENSE.txt └── blog └── detach_benchmarks.txt /spec/pacer_spec.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format d 2 | -------------------------------------------------------------------------------- /spec/pacer/support/array_list_spec.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /autotest/discover.rb: -------------------------------------------------------------------------------- 1 | Autotest.add_discovery { "rspec2" } 2 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Maintainer: 2 | Darrick Wiebe 3 | 4 | Contributors: 5 | * 6 | -------------------------------------------------------------------------------- /Jarfile: -------------------------------------------------------------------------------- 1 | jar "com.tinkerpop.blueprints:blueprints-core:2.6.0" 2 | jar "com.tinkerpop:pipes:2.6.0" 3 | -------------------------------------------------------------------------------- /lib/pacer/support/nil_class.rb: -------------------------------------------------------------------------------- 1 | class NilClass 2 | def as(*args) 3 | nil 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/pacer/blueprints/dex_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.dex do 4 | # no special cases? 5 | end 6 | -------------------------------------------------------------------------------- /spec/pacer/blueprints/tg_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.tg do 4 | # no special cases for TinkerGraph? 5 | end 6 | -------------------------------------------------------------------------------- /spec/pacer/blueprints/orient_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.orient do 4 | # no special cases for Orient? 5 | end 6 | -------------------------------------------------------------------------------- /lib/pacer/core/string_route.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core 3 | module StringRoute 4 | def lengths 5 | map(element_type: :integer) { |s| s.length } 6 | end 7 | end 8 | end 9 | end 10 | 11 | -------------------------------------------------------------------------------- /lib/pacer/transform/flat_map.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def flat_map(opts = {}, &block) 5 | map(&block).scatter(opts) 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/pacer/transform/gather.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core 3 | module Route 4 | def gather(into = nil, &block) 5 | aggregate(into, &block).cap(element_type: :array) 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/pacer/transform/identity.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def identity 5 | chain_route route_name: 'identity', pipe_class: com.tinkerpop.pipes.IdentityPipe 6 | end 7 | end 8 | end 9 | end 10 | 11 | -------------------------------------------------------------------------------- /lib/pacer/utils.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Utils 3 | autoload :YFilesExport, 'pacer/utils/y_files' 4 | autoload :GraphAnalysis, 'pacer/utils/graph_analysis' 5 | autoload :TSort, 'pacer/utils/tsort' 6 | autoload :Trie, 'pacer/utils/trie' 7 | end 8 | end 9 | 10 | -------------------------------------------------------------------------------- /lib/pacer/core/side_effect.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core 3 | module SideEffect 4 | # Get the side effect produced by the most recently created pipe. 5 | # @return [Object] 6 | def side_effect 7 | @pipe.getSideEffect 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/pacer/side_effect/is_unique_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe '#unique?' do 4 | it 'should be true if unique' do 5 | [1, 2, 3].to_route.unique?.should be_true 6 | end 7 | 8 | it 'should be true if unique' do 9 | [1, 2, 3, 1, 4].to_route.unique?.should be_false 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/pacer/support/hash.rb: -------------------------------------------------------------------------------- 1 | class Hash 2 | def to_hash_map 3 | hm = java.util.HashMap.new 4 | if block_given? 5 | each do |key, value| 6 | hm.put(*yield(key, value)) 7 | end 8 | else 9 | each do |key, value| 10 | hm.put key, value 11 | end 12 | end 13 | hm 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /.autotest: -------------------------------------------------------------------------------- 1 | Autotest.add_hook(:initialize) {|at| 2 | at.add_exception %r{\.git} # ignore Version Control System 3 | at.add_exception %r{/\.} # ignore any file that starts with a . (dot) 4 | at.add_exception %r{^\./tmp} # ignore temp files, lest autotest will run again, and again... 5 | at.add_exception %r{^\./(coverage|data|samples|pkg|\.yardoc)} 6 | nil 7 | } 8 | -------------------------------------------------------------------------------- /spec/support/contexts.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Core 3 | module SharedExampleGroup 4 | def contexts(ctxts, &block) 5 | ctxts.each do |name, setup_proc| 6 | context(*[*name]) do 7 | instance_eval &setup_proc 8 | instance_eval &block 9 | end 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/pacer/pipe/unwrapping_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Pipes 3 | class UnwrappingPipe < RubyPipe 4 | def getSideEffect 5 | starts.getSideEffect 6 | end 7 | 8 | def processNextStart 9 | starts.next.element 10 | end 11 | 12 | def getCurrentPath 13 | starts.getCurrentPath 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /ext/src/main/java/com/xnlogic/pacer/pipes/NeverPipe.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import com.tinkerpop.pipes.AbstractPipe; 4 | import com.tinkerpop.pipes.util.FastNoSuchElementException; 5 | 6 | public class NeverPipe extends AbstractPipe { 7 | protected Object processNextStart() { 8 | throw FastNoSuchElementException.instance(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/pacer/pipe/type_filter_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class TypeFilterPipe < RubyPipe 3 | def initialize(type) 4 | super() 5 | @type = type 6 | end 7 | 8 | def processNextStart() 9 | while @starts.hasNext 10 | s = @starts.next 11 | return s if s.is_a? @type 12 | end 13 | raise EmptyPipe.instance 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/tackle/simple_mixin.rb: -------------------------------------------------------------------------------- 1 | module Tackle 2 | module SimpleMixin 3 | module Route 4 | def route_mixin_method 5 | true 6 | end 7 | end 8 | 9 | module Vertex 10 | def vertex_mixin_method 11 | true 12 | end 13 | end 14 | 15 | module Edge 16 | def edge_mixin_method 17 | true 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/pacer/core/graph.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core 3 | module Graph 4 | end 5 | end 6 | end 7 | 8 | require 'pacer/core/graph/graph_route' 9 | require 'pacer/core/graph/graph_index_route' 10 | require 'pacer/core/graph/element_route' 11 | require 'pacer/core/graph/edges_route' 12 | require 'pacer/core/graph/vertices_route' 13 | require 'pacer/core/graph/mixed_route' 14 | require 'pacer/core/graph/path_route' 15 | -------------------------------------------------------------------------------- /lib/pacer/blueprints/tg.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | import com.tinkerpop.blueprints.impls.tg.TinkerGraph 3 | 4 | # Create a new TinkerGraph. If path is given, use Tinkergraph in 5 | # its standard simple persistant mode. 6 | def self.tg(path = nil) 7 | if path 8 | PacerGraph.new SimpleEncoder, TinkerGraph.new(path) 9 | else 10 | PacerGraph.new SimpleEncoder, TinkerGraph.new 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /samples/profile.rb: -------------------------------------------------------------------------------- 1 | require 'lib/pacer' 2 | 3 | require 'jruby/profiler' 4 | 5 | g = Pacer.tg 'samples/grateful-dead.xml' 6 | g.v.out.in.count 7 | 8 | profile_data = JRuby::Profiler.profile do 9 | 100.times do 10 | g.v(type: 'song').out(type: 'artist', name: nil).out_e.in_v.count 11 | end 12 | end 13 | 14 | profile_printer = JRuby::Profiler::GraphProfilePrinter.new(profile_data) 15 | profile_printer.printProfile(STDOUT) 16 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env jruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rake' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rake', 'rake') 17 | -------------------------------------------------------------------------------- /bin/rcov: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env jruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rcov' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rcov', 'rcov') 17 | -------------------------------------------------------------------------------- /bin/yard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env jruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'yard' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('yard', 'yard') 17 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env jruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'rspec' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rspec-core', 'rspec') 17 | -------------------------------------------------------------------------------- /bin/yardoc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env jruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'yardoc' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('yard', 'yardoc') 17 | -------------------------------------------------------------------------------- /bin/autospec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env jruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'autospec' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('rspec-core', 'autospec') 17 | -------------------------------------------------------------------------------- /lib/pacer/filter/random_filter.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core 3 | module Route 4 | # Return elements based on a bias:1 chance. 5 | # 6 | # If given an integer (n) > 0, bias is calcualated at 1 / n. 7 | def random(bias = 0.5) 8 | bias = 1 / bias.to_f if bias.is_a? Fixnum and bias > 0 9 | chain_route :pipe_class => Pacer::Pipes::RandomFilterPipe, :pipe_args => bias 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/pacer/support/lock_jar_disabler.rb: -------------------------------------------------------------------------------- 1 | # Disable LockJar so that other libraries can't bring in random other jars. 2 | # TODO: add LockJar.disable! or something like that. 3 | # 4 | # NOTE: this file should only be loaded by #register_bundled_jarfiles after loading is complete. 5 | module LockJar 6 | class << self 7 | alias orig_lock lock 8 | alias orig_load load 9 | end 10 | def self.lock(*a); end 11 | def self.load(*a); end 12 | end 13 | 14 | -------------------------------------------------------------------------------- /bin/autotest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env jruby 2 | # 3 | # This file was generated by Bundler. 4 | # 5 | # The application 'autotest' is installed as part of a gem, and 6 | # this file is here to facilitate running it. 7 | # 8 | 9 | require 'pathname' 10 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", 11 | Pathname.new(__FILE__).realpath) 12 | 13 | require 'rubygems' 14 | require 'bundler/setup' 15 | 16 | load Gem.bin_path('autotest-standalone', 'autotest') 17 | -------------------------------------------------------------------------------- /lib/pacer/support/lock_jar.rb: -------------------------------------------------------------------------------- 1 | module LockJar 2 | # If Bundler constant is defined, assume we are running in a bundled environment and 3 | # register all Jarfiles 4 | def self.register_bundled_jarfiles 5 | if defined? Bundler 6 | Gem::Specification. 7 | map { |s| File.join(s.full_gem_path, 'Jarfile') }. 8 | select { |f| File.exists? f }. 9 | each { |f| LockJar.register_jarfile f } 10 | true 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/pacer/version.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | VERSION = "2.0.24" 3 | # Clients may optionally define the following constants in the Pacer namespace: 4 | # - LOAD_JARS : set to false to manage jar loading in the client. Be sure to load the jars defined in JARFILES. 5 | # - LOCKJAR_LOCK_OPTS : set some options to be passed to LockJar.lock (ie. :lockfile, :download_artifacts, :local_repo) 6 | unless const_defined? :START_TIME 7 | START_TIME = Time.now 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rcov generated 2 | coverage 3 | 4 | # rdoc generated 5 | rdoc 6 | 7 | # yard generated 8 | doc 9 | .yardoc 10 | 11 | # bundler 12 | .bundle 13 | Gemfile.lock 14 | 15 | # jeweler generated 16 | pkg 17 | 18 | # mac custom attributes files 19 | .DS_Store 20 | 21 | # Project specific 22 | *.graph 23 | *.db 24 | *.graphml 25 | pkg 26 | tmp 27 | lib/*.jar 28 | jdex.log 29 | bin 30 | .jitcache 31 | coverage/* 32 | dex.log 33 | vendor/ 34 | private/ 35 | Gemfile-custom 36 | Jarfile.lock 37 | Jarfile.*.lock 38 | -------------------------------------------------------------------------------- /lib/pacer/pipe/process_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class ProcessPipe < AbstractPipe 3 | field_reader :starts 4 | attr_reader :is_element, :extensions, :back, :block, :graph 5 | 6 | def initialize(back, block) 7 | super() 8 | @block = Pacer::Wrappers::WrappingPipeFunction.new back, block 9 | end 10 | 11 | def processNextStart 12 | while true 13 | obj = starts.next 14 | block.call(obj) 15 | return obj 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pacer/support/native_exception.rb: -------------------------------------------------------------------------------- 1 | class NativeException 2 | def unravel 3 | e = self 4 | while e and e.respond_to? :cause 5 | puts '--------------------' 6 | puts e.class.to_s 7 | puts e.message 8 | pp e.backtrace.to_a 9 | e = e.cause 10 | end 11 | puts '======================' 12 | end 13 | 14 | def root_cause 15 | rc = e = self 16 | while e and e.respond_to? :cause 17 | rc = e 18 | e = e.cause 19 | end 20 | rc 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pacer/pipe/naked_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Pipes 3 | class NakedPipe < RubyPipe 4 | def getCurrentPath 5 | starts.getCurrentPath 6 | end 7 | 8 | def processNextStart 9 | e = starts.next 10 | e = e.element if e.respond_to? :element 11 | if e.respond_to? :raw_vertex 12 | e.raw_vertex 13 | elsif e.respond_to? :raw_edge 14 | e.raw_edge 15 | else 16 | e 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Core 3 | module Matchers 4 | def fail 5 | raise_error(::RSpec::Expectations::ExpectationNotMetError) 6 | end 7 | 8 | def fail_with(message) 9 | raise_error(::RSpec::Expectations::ExpectationNotMetError, message) 10 | end 11 | end 12 | end 13 | end 14 | 15 | RSpec::Matchers.define :be_one_of do |*list| 16 | match do |item| 17 | list.include? item or list.select { |i| i.is_a? Regexp }.any? { |r| item =~ r } 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/pacer/filter/uniq_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.tg(:read_only) do 4 | use_pacer_graphml_data(:read_only) 5 | 6 | describe 'UniqFilter' do 7 | describe '#uniq' do 8 | it 'should be a route' do 9 | graph.v.uniq.should be_a(Pacer::Route) 10 | end 11 | 12 | it 'results should be unique' do 13 | graph.e.in_v.group_count(:name).values.sort.last.should > 1 14 | graph.e.in_v.uniq.group_count(:name).values.sort.last.should == 1 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/pacer/support/array_list.rb: -------------------------------------------------------------------------------- 1 | java.util.ArrayList 2 | class Java::JavaUtil::ArrayList 3 | def inspect 4 | to_a.inspect 5 | end 6 | end 7 | 8 | java.util.LinkedList 9 | class Java::JavaUtil::LinkedList 10 | def inspect 11 | to_a.inspect 12 | end 13 | end 14 | 15 | require 'set' 16 | java.util.HashSet 17 | class Java::JavaUtil::HashSet 18 | def inspect 19 | to_set.inspect.sub(/Set/, 'HashSet') 20 | end 21 | end 22 | 23 | java.util.HashMap 24 | class Java::JavaUtil::HashMap 25 | def to_hash_map 26 | self 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/pacer/transform/wrapped_path.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core 3 | module Graph 4 | module PathRoute 5 | def wrapped(*exts) 6 | chain_route transform: :wrap_path, element_type: :path 7 | end 8 | end 9 | end 10 | end 11 | 12 | module Transform 13 | module WrapPath 14 | protected 15 | 16 | def attach_pipe(end_pipe) 17 | pipe = Pacer::Pipes::PathWrappingPipe.new(graph) 18 | pipe.setStarts end_pipe if end_pipe 19 | pipe 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # A sample Gemfile 2 | source "http://rubygems.org" 3 | 4 | gemspec 5 | 6 | group :development do 7 | gem 'rspec', '~> 2.10.0' 8 | gem 'rr', '~> 1.0' 9 | gem 'simplecov' 10 | gem 'rake' 11 | gem 'autotest-standalone' 12 | gem 'autotest-growl' 13 | gem 'awesome_print', '0.4.0' 14 | gem 'coveralls', require: false 15 | end 16 | 17 | 18 | # Gemfile-custom is .gitignored, but eval'd here so you can add 19 | # whatever dev tools you like to use to your local environment. 20 | eval File.read('Gemfile-custom') if File.exist?('Gemfile-custom') 21 | -------------------------------------------------------------------------------- /lib/pacer/transform/scatter.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core 3 | module Route 4 | def scatter(args = {}) 5 | chain_route({transform: :scatter, element_type: :object}.merge(args)) 6 | end 7 | end 8 | end 9 | 10 | module Transform 11 | module Scatter 12 | import com.tinkerpop.pipes.transform.ScatterPipe 13 | 14 | protected 15 | 16 | def attach_pipe(end_pipe) 17 | pipe = ScatterPipe.new 18 | pipe.setStarts end_pipe if end_pipe 19 | pipe 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/pacer/transform/stream_uniq.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes::RouteOperations 3 | # Deprecated: use uniq_section instead. 4 | def stream_uniq(buffer = 1000) 5 | chain_route :transform => :stream_uniq, :buffer => buffer 6 | end 7 | end 8 | 9 | module Transform 10 | module StreamUniq 11 | attr_accessor :buffer 12 | 13 | protected 14 | 15 | def attach_pipe(end_pipe) 16 | pipe = Pacer::Pipes::StreamUniqPipe.new buffer 17 | pipe.setStarts end_pipe if end_pipe 18 | pipe 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /ext/src/test/java/com/xnlogic/pacer/pipes/NeverPipeTest.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import java.util.ArrayList; 4 | import java.util.NoSuchElementException; 5 | 6 | import org.junit.Test; 7 | 8 | public class NeverPipeTest { 9 | @Test(expected=NoSuchElementException.class) 10 | public void ensureExceptionTest() { 11 | NeverPipe neverPipe = new NeverPipe(); 12 | ArrayList starts = new ArrayList(); 13 | starts.add(new Object()); 14 | neverPipe.setStarts(starts.iterator()); 15 | neverPipe.next(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /spec/pacer/filter/collection_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Pacer::Filter::CollectionFilter do 4 | context 'on an array of objects' do 5 | let(:route) { [1,2,3].to_route } 6 | 7 | it 'should filter out an element' do 8 | route.except(2).to_a.should == [1,3] 9 | end 10 | 11 | it 'should filter out an array of elements' do 12 | route.except([2,3]).to_a.should == [1] 13 | end 14 | 15 | it 'should filter out the elements from another route' do 16 | route.except([2,3].to_route).to_a.should == [1] 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/pacer/pipe/block_filter_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class BlockFilterPipe < AbstractPipe 3 | field_reader :starts 4 | 5 | attr_reader :block, :invert 6 | 7 | def initialize(back, block, invert = false) 8 | super() 9 | @invert = invert 10 | @block = Pacer::Wrappers::WrappingPipeFunction.new back, block 11 | end 12 | 13 | def processNextStart() 14 | while raw_element = starts.next 15 | ok = block.call raw_element 16 | ok = !ok if invert 17 | return raw_element if ok 18 | end 19 | raise EmptyPipe.instance 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/pacer/transform/stream_sort.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes::RouteOperations 3 | # Deprecated: use sort_section instead. 4 | def stream_sort(buffer = 1000, silo = 100) 5 | chain_route :transform => :stream_sort, :buffer => buffer, :silo => silo 6 | end 7 | end 8 | 9 | module Transform 10 | module StreamSort 11 | attr_accessor :buffer, :silo 12 | 13 | protected 14 | 15 | def attach_pipe(end_pipe) 16 | pipe = Pacer::Pipes::StreamSortPipe.new buffer, silo 17 | pipe.setStarts end_pipe if end_pipe 18 | pipe 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /ext/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | pacer 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /spec/pacer/filter/random_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.tg(:read_only) do 4 | use_pacer_graphml_data(:read_only) 5 | 6 | describe 'RandomFilter' do 7 | describe '#random' do 8 | it { Set[*graph.v.random(1)].should == Set[*graph.v] } 9 | it { graph.v.random(0).to_a.should == [] } 10 | it 'should usually have some number of elements more than 1 and less than all' do 11 | range = 1..(graph.v.count - 1) 12 | best2of3 = (0...3).map { graph.v.random(0.5).count } 13 | best2of3.select { |num| range.include? num }.length.should >= 2 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pacer/filter/uniq_filter.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | import com.tinkerpop.pipes.filter.DuplicateFilterPipe 5 | import com.tinkerpop.pipes.filter.CyclicPathFilterPipe 6 | 7 | # Do not return duplicate elements. 8 | def uniq 9 | chain_route :pipe_class => DuplicateFilterPipe, :route_name => 'uniq' 10 | end 11 | 12 | # Filter out any element where its path would contain the same element twice. 13 | def unique_path 14 | chain_route :pipe_class => CyclicPathFilterPipe, :route_name => 'unique_path' 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/pacer/pipe/enumerable_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class EnumerablePipe < RubyPipe 3 | def initialize(enumerable) 4 | super() 5 | case enumerable 6 | when Enumerator 7 | starts = enumerable 8 | when Pacer::Wrappers::ElementWrapper 9 | starts = [enumerable.element].to_enum 10 | when Enumerable 11 | starts = enumerable.to_enum 12 | else 13 | starts = [enumerable].to_enum 14 | end 15 | set_starts starts 16 | end 17 | 18 | def processNextStart() 19 | @starts.next 20 | rescue StopIteration 21 | raise EmptyPipe.instance 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/pacer/side_effect/counted.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def counted 5 | chain_route :side_effect => :counted 6 | end 7 | 8 | def count 9 | counted.count 10 | end 11 | alias run! count 12 | end 13 | end 14 | 15 | 16 | module SideEffect 17 | module Counted 18 | def count 19 | cap.first 20 | end 21 | 22 | protected 23 | 24 | def attach_pipe(end_pipe) 25 | @pipe = com.tinkerpop.pipes.sideeffect.CountPipe.new 26 | @pipe.setStarts(end_pipe) if end_pipe 27 | @pipe 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/pacer/transform/parallel.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def parallel(opts = {}, &block) 5 | threads = opts.fetch(:threads, 2) 6 | branched = (0..threads).reduce(channel_cap buffer: opts.fetch(:in_buffer, threads)) do |r, n| 7 | r.branch do |x| 8 | b = block.call x.channel_reader 9 | b.channel_cap 10 | end 11 | end 12 | branched.merge_exhaustive.gather.channel_fan_in(buffer: opts.fetch(:out_buffer, threads), 13 | based_on: block.call(self)) 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/pacer/graph/hash_index.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Graph 2 | class HashIndex 3 | attr_reader :name, :type 4 | 5 | def initialize(element_type, name) 6 | @type = type 7 | @name = name 8 | @data = Hash.new do |h, k| 9 | h[k] = Hash.new { |h, k| h[k] = Set[] } 10 | end 11 | end 12 | 13 | def get(key, value) 14 | Pacer::Pipes::EnumerablePipe.new data[key][value] 15 | end 16 | 17 | def put(key, value, element) 18 | data[key][value] << element 19 | end 20 | 21 | def count(key, value) 22 | data[key][value].count 23 | end 24 | 25 | private 26 | 27 | attr_reader :data 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/pacer/pipe/stream_uniq_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class StreamUniqPipe < RubyPipe 3 | def initialize(buffer = 1000) 4 | super() 5 | @list = java.util.LinkedList.new 6 | @buffer = buffer 7 | end 8 | 9 | protected 10 | 11 | def processNextStart 12 | while true 13 | obj = @starts.next 14 | duplicate = @list.removeLastOccurrence(obj) 15 | @list.addLast obj 16 | if not duplicate 17 | if @buffer == 0 18 | @list.removeFirst 19 | else 20 | @buffer -= 1 21 | end 22 | return obj 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /ext/src/main/java/com/xnlogic/pacer/pipes/CollectionFilterPipe.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import com.tinkerpop.blueprints.Contains; 4 | import com.tinkerpop.pipes.util.structures.AsMap; 5 | 6 | import java.util.Collection; 7 | 8 | public class CollectionFilterPipe extends com.tinkerpop.pipes.filter.CollectionFilterPipe { 9 | 10 | public CollectionFilterPipe(final Collection storedCollection, final Contains contains) { 11 | super(storedCollection, contains); 12 | } 13 | 14 | public CollectionFilterPipe(final Contains contains, final AsMap asMap, final String... namedSteps) { 15 | super(contains, asMap, namedSteps); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /lib/pacer/graph/simple_encoder.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | class SimpleEncoder 3 | JBoolean = java.lang.Boolean 4 | JDate = java.util.Date 5 | JFalse = false.to_java 6 | 7 | def self.encode_property(value) 8 | if value.is_a? String 9 | value = value.strip 10 | value unless value == '' 11 | elsif false == value 12 | JFalse 13 | else 14 | value 15 | end 16 | end 17 | 18 | def self.decode_property(value) 19 | if value.is_a? JBoolean and value == JFalse 20 | false 21 | elsif value.is_a? JDate 22 | Time.at value.getTime() / 1000.0 23 | else 24 | value 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/pacer/pipe/ruby_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class RubyPipe < AbstractPipe 3 | field_reader :pathEnabled 4 | 5 | attr_reader :starts 6 | 7 | def setStarts(starts) 8 | @starts = starts 9 | end 10 | alias set_starts setStarts 11 | 12 | def reset 13 | super 14 | starts.reset if starts.respond_to? :reset 15 | end 16 | 17 | def enablePath(b) 18 | super 19 | starts.enablePath b if starts.respond_to? :enablePath 20 | end 21 | 22 | protected 23 | 24 | def getPathToHere 25 | if starts.respond_to? :getCurrentPath 26 | starts.getCurrentPath 27 | else 28 | java.util.ArrayList.new 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/pacer/support/proc.rb: -------------------------------------------------------------------------------- 1 | class Proc 2 | def to_route(opts = {}) 3 | based_on = opts[:based_on] 4 | if opts[:unwrap] or based_on and based_on.extensions and based_on.graph 5 | source = proc { self.call.map { |e| e.element } } 6 | else 7 | source = self 8 | end 9 | if based_on 10 | Pacer::RouteBuilder.current.chain(source, :element_type => :mixed, :graph => based_on.graph, :extensions => based_on.extensions, :info => based_on.info) 11 | else 12 | graph = opts[:graph] if opts[:graph] 13 | Pacer::RouteBuilder.current.chain(source, :element_type => (opts[:element_type] || :object), :graph => graph, :extensions => opts[:extensions], :info => opts[:info]) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /ext/src/main/java/com/xnlogic/pacer/pipes/LabelPrefixPipe.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import com.tinkerpop.blueprints.Edge; 4 | import com.tinkerpop.pipes.AbstractPipe; 5 | import java.util.regex.Pattern; 6 | 7 | public class LabelPrefixPipe extends AbstractPipe { 8 | private Pattern pattern; 9 | 10 | public LabelPrefixPipe(final String pattern) { 11 | super(); 12 | this.pattern = Pattern.compile("^" + pattern); 13 | } 14 | 15 | protected Edge processNextStart() { 16 | while (true) { 17 | Edge e = this.starts.next(); 18 | if (this.pattern.matcher(e.getLabel()).matches()) { 19 | return e; 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/pacer/core/hash_route.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core 3 | module HashRoute 4 | def lengths 5 | map(element_type: :integer) { |h| h.length } 6 | end 7 | 8 | def keys 9 | map(element_type: :array) { |h| h.keys } 10 | end 11 | 12 | def values 13 | map(element_type: :array) { |h| h.values } 14 | end 15 | 16 | def pairs 17 | map(element_type: :array) { |h| h.to_a } 18 | end 19 | 20 | def [](k) 21 | map { |h| h[k] } 22 | end 23 | 24 | def set(k, v) 25 | process { |h| h[k] = v } 26 | end 27 | 28 | def fetch(k, *d, &block) 29 | map { |h| h.fetch(k, *d, &block) } 30 | end 31 | end 32 | end 33 | end 34 | 35 | -------------------------------------------------------------------------------- /ext/.gitignore: -------------------------------------------------------------------------------- 1 | Created by https://www.gitignore.io 2 | 3 | ### Maven ### 4 | target/ 5 | pom.xml.tag 6 | pom.xml.releaseBackup 7 | pom.xml.versionsBackup 8 | pom.xml.next 9 | release.properties 10 | 11 | ### TODO: Move this IDE-specific declarations out of here ### 12 | ### Eclipse ### 13 | *.pydevproject 14 | .metadata 15 | .gradle 16 | bin/ 17 | tmp/ 18 | *.tmp 19 | *.bak 20 | *.swp 21 | *~.nib 22 | local.properties 23 | .settings/ 24 | .loadpath 25 | 26 | # External tool builders 27 | .externalToolBuilders/ 28 | 29 | # Locally stored "Eclipse launch configurations" 30 | *.launch 31 | 32 | # CDT-specific 33 | .cproject 34 | 35 | # PDT-specific 36 | .buildpath 37 | 38 | # sbteclipse plugin 39 | .target 40 | 41 | # TeXlipse plugin 42 | .texlipse 43 | 44 | 45 | -------------------------------------------------------------------------------- /lib/pacer/filter/index_filter.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Filter 3 | module IndexFilter 4 | attr_accessor :index, :key, :value 5 | 6 | def count 7 | if @index and @key and @value 8 | if @index.respond_to? :count 9 | @index.count(@key, graph.encode_property(@value)) 10 | else 11 | super 12 | end 13 | else 14 | super 15 | end 16 | end 17 | 18 | protected 19 | 20 | def source_iterator 21 | src = index.get(key, graph.encode_property(value)) || java.util.ArrayList.new 22 | src.iterator 23 | end 24 | 25 | def inspect_string 26 | "#{ inspect_class_name }(#{ key }: #{value.inspect})" 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /ext/src/main/java/com/xnlogic/pacer/pipes/EdgesPipe.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import java.util.Iterator; 4 | 5 | import com.tinkerpop.blueprints.Edge; 6 | import com.tinkerpop.blueprints.Graph; 7 | import com.tinkerpop.pipes.AbstractPipe; 8 | 9 | public class EdgesPipe extends AbstractPipe { 10 | private Iterator iter; 11 | private Graph starts; 12 | 13 | public void setStarts(Iterator starts) { 14 | // TODO: Error checking? 15 | this.starts = (Graph)starts.next(); 16 | this.iter = this.starts.getEdges().iterator(); 17 | } 18 | 19 | protected Edge processNextStart() { 20 | return this.iter.next(); 21 | } 22 | 23 | public void reset() { 24 | this.iter = this.starts.getEdges().iterator(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ext/src/main/java/com/xnlogic/pacer/pipes/VerticesPipe.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import java.util.Iterator; 4 | 5 | import com.tinkerpop.blueprints.Graph; 6 | import com.tinkerpop.blueprints.Vertex; 7 | import com.tinkerpop.pipes.AbstractPipe; 8 | 9 | public class VerticesPipe extends AbstractPipe { 10 | private Iterator iter; 11 | private Graph starts; 12 | 13 | public void setStarts(Iterator starts) { 14 | // TODO: Error checking? 15 | this.starts = (Graph)starts.next(); 16 | this.iter = this.starts.getVertices().iterator(); 17 | } 18 | 19 | protected Vertex processNextStart() { 20 | return this.iter.next(); 21 | } 22 | 23 | public void reset() { 24 | this.iter = this.starts.getVertices().iterator(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/pacer/filter/block_filter.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def select(&block) 5 | chain_route :filter => :block, :block => block, :route_name => 'Select' 6 | end 7 | 8 | def reject(&block) 9 | chain_route :filter => :block, :block => block, :invert => true, :route_name => 'Reject' 10 | end 11 | end 12 | end 13 | 14 | module Filter 15 | module BlockFilter 16 | attr_accessor :block, :invert 17 | 18 | def ==(other) 19 | super and invert == other.invert and block == other.block 20 | end 21 | 22 | protected 23 | 24 | def attach_pipe(end_pipe) 25 | pipe = Pacer::Pipes::BlockFilterPipe.new(self, block, invert) 26 | pipe.set_starts end_pipe if end_pipe 27 | pipe 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/pacer/exceptions.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | class Error < StandardError; end 3 | class UserError < Error; end 4 | class ElementNotFound < UserError; end 5 | class ElementExists < UserError; end 6 | 7 | class LogicError < Error; end 8 | class ClientError < LogicError; end 9 | class TransactionError < ClientError; end 10 | class TransactionConcludedError < TransactionError; end 11 | class NestedTransactionError < TransactionError; end 12 | class NestedTransactionRollback < TransactionError; end 13 | class NestedMockTransactionRollback < NestedTransactionRollback; end 14 | class MockTransactionRollback < TransactionError; end 15 | class UnsupportedOperation < ClientError; end 16 | 17 | class InternalError < LogicError; end 18 | class TransientError < Error; end 19 | end 20 | 21 | -------------------------------------------------------------------------------- /lib/pacer/pipe/multi_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class MultiPipe < RubyPipe 3 | import com.tinkerpop.pipes.util.iterators.MultiIterator 4 | import com.tinkerpop.pipes.Pipe 5 | 6 | attr_reader :pipes 7 | 8 | def initialize(enums) 9 | super() 10 | @pipes = enums.map do |e| 11 | if e.is_a? Pipe 12 | e 13 | else 14 | e.to_iterable 15 | end 16 | end 17 | setStarts MultiIterator.new(*pipes) 18 | end 19 | 20 | def +(enum) 21 | MultiPipe.new(pipes + [enum]) 22 | end 23 | 24 | def -(enum) 25 | MultiPipe.new(pipes - [enum]) 26 | end 27 | 28 | def getCurrentPath 29 | starts.getCurrentPath 30 | end 31 | 32 | def processNextStart 33 | starts.next 34 | end 35 | 36 | def inspect 37 | "#" 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /pacer.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "pacer/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "pacer" 7 | s.version = Pacer::VERSION 8 | s.platform = 'java' 9 | s.authors = ["Darrick Wiebe"] 10 | s.email = "dw@xnlogic.com" 11 | s.homepage = "http://github.com/pangloss/pacer" 12 | s.license = "MIT" 13 | s.summary = %Q{A very efficient and easy to use graph traversal engine.} 14 | s.description = %Q{Pacer defines composeable routes through a graph and then traverses them very quickly.} 15 | 16 | s.files = `git ls-files`.split("\n") + ['lib/pacer-ext.jar'] 17 | s.test_files = `git ls-files -- spec/*`.split("\n") 18 | s.require_paths = ['lib'] 19 | s.add_dependency "lock_jar", "~> 0.12.0" 20 | s.add_development_dependency 'xn_gem_release_tasks' 21 | s.add_development_dependency "rake-compiler", "~> 0.9.2" 22 | end 23 | 24 | -------------------------------------------------------------------------------- /lib/pacer/transform/make_pairs.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def make_pairs(other = nil, &block) 5 | if block 6 | # This would be cool if it could create a pair based on 7 | fail 'not implemented yet' 8 | elsif other 9 | if other.is_a? Route and [element_type, other.element_type].all? { |t| [:vertex, :edge].include? t } 10 | et = :path 11 | else 12 | et = :object 13 | end 14 | other = other.to_a 15 | if other.empty? 16 | empty(self) 17 | else 18 | flat_map(element_type: et, route_name: 'make_pairs') do |el| 19 | other.map { |o| [el, o] } 20 | end 21 | end 22 | else 23 | fail Pacer::ClientError, 'No source for pairs given to make_pairs' 24 | end 25 | end 26 | end 27 | end 28 | end 29 | 30 | -------------------------------------------------------------------------------- /spec/pacer/filter/object_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.tg(:read_only) do 4 | use_pacer_graphml_data(:read_only) 5 | 6 | describe Pacer::Filter::ObjectFilter do 7 | it '#is' do 8 | [1, 2, 3, 2, 3].to_route.is(2).to_a.should == [2, 2] 9 | end 10 | 11 | it '#is_not' do 12 | [1, 2, 3, 2, 3].to_route.is_not(2).to_a.should == [1, 3, 3] 13 | end 14 | 15 | it '#compact' do 16 | [1, nil, 2, 3].to_route.compact.to_a.should == [1, 2, 3] 17 | end 18 | 19 | describe '#vertex filter' do 20 | 21 | let (:filter_node) { graph.vertex 3 } 22 | 23 | it "#is_not" do 24 | all_nodes = graph.v.to_a.select { |n| n.getId != filter_node.getId } 25 | graph.v.is_not(filter_node).to_a.should == all_nodes 26 | end 27 | 28 | it "#is" do 29 | graph.v.is(filter_node).to_a.should == [filter_node] 30 | end 31 | 32 | end 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /lib/pacer/side_effect/visitor.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def visitor(visitor) 5 | chain_route side_effect: :visitor, visitor: visitor 6 | end 7 | end 8 | end 9 | 10 | module SideEffect 11 | module Visitor 12 | attr_reader :visitor 13 | 14 | def visitor=(v) 15 | @visitor = v 16 | @visitor = @visitor.on_route(self) if @visitor.respond_to? :on_route 17 | end 18 | 19 | def element_type 20 | if @visitor.respond_to? :element_type 21 | @visitor.element_type 22 | else 23 | super 24 | end 25 | end 26 | 27 | protected 28 | 29 | def attach_pipe(end_pipe) 30 | pipe = @visitor.attach_pipe(end_pipe) if @visitor.respond_to? :attach_pipe 31 | pipe ||= Pacer::Pipes::VisitorPipe.new(visitor) 32 | pipe.setStarts end_pipe if end_pipe 33 | pipe 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/support/use_transactions.rb: -------------------------------------------------------------------------------- 1 | module RSpec 2 | module Core 3 | class Example 4 | module ProcsyTransactions 5 | def use_transactions? 6 | find_metadata(metadata, :transactions) != false 7 | end 8 | 9 | def use_read_transaction? 10 | find_metadata(metadata, :read_transaction) != false 11 | end 12 | 13 | def find_metadata(hash, key) 14 | return unless hash.is_a? Hash 15 | if hash.key? key 16 | hash[key] 17 | elsif hash.key? :example_group 18 | find_metadata(hash[:example_group], key) 19 | end 20 | end 21 | end 22 | 23 | if not defined? Procsy or Procsy.class == Module 24 | # RSpec version >= '2.5.0' 25 | module Procsy 26 | include ProcsyTransactions 27 | end 28 | else 29 | class Procsy 30 | include ProcsyTransactions 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /ext/src/main/java/com/xnlogic/pacer/pipes/IsEmptyPipe.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import java.util.NoSuchElementException; 4 | 5 | import com.tinkerpop.pipes.AbstractPipe; 6 | import com.tinkerpop.pipes.util.FastNoSuchElementException; 7 | 8 | public class IsEmptyPipe extends AbstractPipe { 9 | private boolean raise; 10 | 11 | public IsEmptyPipe() { 12 | super(); 13 | this.raise = false; 14 | } 15 | 16 | protected Boolean processNextStart() throws NoSuchElementException{ 17 | if (this.raise) { 18 | throw FastNoSuchElementException.instance(); 19 | } 20 | 21 | try { 22 | this.starts.next(); 23 | this.raise = true; 24 | } catch (NoSuchElementException nsee) { 25 | return true; 26 | } 27 | 28 | throw FastNoSuchElementException.instance(); 29 | } 30 | 31 | public void reset() { 32 | this.raise = false; 33 | super.reset(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/pacer/pipe/property_comparison_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class PropertyComparisonFilterPipe < RubyPipe 3 | def initialize(left, right, filter) 4 | super() 5 | @filter = filter 6 | @left = left.to_s 7 | @right = right.to_s 8 | end 9 | 10 | protected 11 | 12 | def processNextStart 13 | while true 14 | obj = @starts.next 15 | l = obj.getProperty(@left) 16 | r = obj.getProperty(@right) 17 | case @filter 18 | when Compare::EQUAL 19 | return obj if l == r 20 | when Compare::NOT_EQUAL 21 | return obj if l != r 22 | when Compare::GREATER_THAN 23 | return obj if l and r and l > r 24 | when Compare::LESS_THAN 25 | return obj if l and r and l < r 26 | when Compare::GREATER_THAN_EQUAL 27 | return obj if l and r and l >= r 28 | when Compare::LESS_THAN_EQUAL 29 | return obj if l and r and l <= r 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/pacer/side_effect/group_count.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes::RouteOperations 3 | def fast_group_count(hash_map = nil) 4 | chain_route :side_effect => :group_count, :hash_map => hash_map 5 | end 6 | end 7 | 8 | module SideEffect 9 | module GroupCount 10 | def hash_map=(hash_map) 11 | @hash_map = hash_map 12 | end 13 | 14 | def at_least(n) 15 | @min = n 16 | self 17 | end 18 | 19 | def to_h 20 | h = {} 21 | min = @min || 0 22 | cap.first.each do |k,v| 23 | h[k] = v if v >= min 24 | end 25 | h 26 | end 27 | 28 | protected 29 | 30 | def attach_pipe(end_pipe) 31 | if @hash_map 32 | @pipe = com.tinkerpop.pipes.sideeffect.GroupCountPipe.new @hash_map 33 | else 34 | @pipe = com.tinkerpop.pipes.sideeffect.GroupCountPipe.new 35 | end 36 | @pipe.set_starts(end_pipe) if end_pipe 37 | @pipe 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ext/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/pacer/transform/process.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def process(opts = {}, &block) 5 | chain_route({:transform => :process, :block => block}.merge(opts)) 6 | end 7 | end 8 | end 9 | 10 | module Transform 11 | module Process 12 | attr_accessor :block 13 | 14 | def help(section = nil) 15 | case section 16 | when nil 17 | puts < { 11 | private Set labels; 12 | 13 | public LabelCollectionFilterPipe(final Collection labels) { 14 | if (labels instanceof Set) { 15 | this.labels = (Set)labels; 16 | } else { 17 | this.labels = new HashSet(); 18 | if(labels != null){ 19 | this.labels.addAll(labels); 20 | } 21 | } 22 | } 23 | 24 | protected Edge processNextStart() { 25 | while (true) { 26 | Edge edge = this.starts.next(); 27 | if (edge != null && this.labels.contains(edge.getLabel())) { 28 | return edge; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/pacer/transform/has_count_cap.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def has_count_route(opts = {}) 5 | chain_route({ :transform => :has_count_cap, element_type: :object }.merge(opts)) 6 | end 7 | 8 | def has_count?(opts = {}) 9 | has_count_route(opts).first 10 | end 11 | end 12 | end 13 | 14 | module Transform 15 | module HasCountCap 16 | import com.tinkerpop.pipes.transform.HasCountPipe 17 | 18 | attr_accessor :min, :max 19 | 20 | protected 21 | 22 | def attach_pipe(end_pipe) 23 | pipe = HasCountPipe.new(min || -1, max || -1) 24 | pipe.setStarts end_pipe if end_pipe 25 | pipe 26 | end 27 | 28 | def inspect_string 29 | if min and max 30 | "HasCount(#{ min }..#{max})" 31 | elsif min 32 | "HasCount(>= #{ min })" 33 | elsif max 34 | "HasCount(<= #{ max })" 35 | else 36 | "HasCount(...any...)" 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /Gemfile-custom.sample: -------------------------------------------------------------------------------- 1 | group :development do 2 | # pacer-* gems are required for testing pacer. 3 | # If you have the gem repos cloned locally, we'll use them. 4 | # 5 | [ 'pacer-titan', 'pacer-orient', 'pacer-dex' ].each do |lib| 6 | if File.directory? "../#{lib}" 7 | gem lib, :path => "../#{lib}" 8 | end 9 | end 10 | 11 | # Neo4j versions are mutually incompatible 12 | # To test Pacer against Neo4j 1.x when the neo2 gem is present, use: 13 | # 14 | # neo=1 bundle 15 | # rspec 16 | # 17 | # To switch back, just use: 18 | # 19 | # bundle 20 | # rspec 21 | # 22 | 23 | neo_test_ver = ENV.fetch('neo') do 24 | if File.directory?("../pacer-neo4j2") 25 | '2' 26 | elsif File.directory?("../pacer-neo4j") 27 | '1' 28 | else 29 | '0' 30 | end 31 | end 32 | if neo_test_ver == '1' 33 | gem 'pacer-neo4j', :path => "../pacer-neo4j" 34 | elsif neo_test_ver == '2' 35 | gem 'pacer-neo4j2', :path => "../pacer-neo4j2" 36 | end 37 | 38 | if File.directory? "../mcfly" 39 | gem 'pacer-mcfly', :path => "../mcfly" 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/pacer/route/mixin/bulk_operations_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.all do 4 | use_pacer_graphml_data 5 | 6 | describe RouteOperations, :transactions => false do 7 | before do 8 | graph.transaction do 9 | setup_data 10 | end 11 | end 12 | 13 | describe '#bulk_job', :transactions => false do 14 | context 'commit every 2nd record, updating all vertices' do 15 | context 'with wrapped elements' do 16 | it 'should update all records' do 17 | graph.v(Tackle::SimpleMixin).bulk_job(2) do |v| 18 | v[:updated] = 'yes' 19 | end 20 | graph.read_transaction do 21 | graph.v(:updated => 'yes').count.should == 7 22 | end 23 | end 24 | end 25 | 26 | it 'should update all records' do 27 | graph.v.bulk_job(2) do |v| 28 | v[:updated] = 'yup' 29 | end 30 | graph.read_transaction do 31 | graph.v(:updated => 'yup').count.should == 7 32 | end 33 | end 34 | end 35 | end 36 | end 37 | end 38 | 39 | -------------------------------------------------------------------------------- /spec/pacer/transform/process_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for '#process' do 4 | describe 'simple element route' do 5 | subject do 6 | c = 0 7 | source.process { c += 1 } 8 | end 9 | its(:first) { should == source.first } 10 | its(:element_type) { should == :vertex } 11 | end 12 | 13 | describe 'with extensions' do 14 | let(:extended) { source.add_extensions([Tackle::SimpleMixin]) } 15 | let(:exts) { Set[] } 16 | 17 | subject { extended.process { |v| exts << v.extensions } } 18 | 19 | its('first.extensions') { should == [Tackle::SimpleMixin] } 20 | 21 | it 'should have the right extensions in the block' do 22 | subject.first 23 | exts.first.should == [Tackle::SimpleMixin] 24 | end 25 | end 26 | end 27 | 28 | Run.tg :read_only do 29 | use_pacer_graphml_data :read_only 30 | 31 | context 'on route' do 32 | it_uses '#process' do 33 | let(:source) { graph.v } 34 | end 35 | end 36 | 37 | context 'on element' do 38 | it_uses '#process' do 39 | let(:source) { pangloss } 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/pacer/visitors/visits_section.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Visitors 3 | # This module is mixed in to the route that actually refers to this section. 4 | module VisitsSection 5 | attr_reader :section, :section_route, :visitor_num 6 | 7 | def section=(section) 8 | if section.is_a? Symbol 9 | @section = section 10 | @section_route = @back.get_section_route(section) 11 | elsif section.is_a? Pacer::Route and section.respond_to? :section_name 12 | @section = section.section_name 13 | @section_route = section 14 | else 15 | raise ArgumentError, "Unknown section #{ section }. Provide either a name or a route created with the #section methed." 16 | end 17 | @visitor_num = section_route.will_visit! 18 | section_route 19 | end 20 | 21 | protected 22 | 23 | def section_visitor 24 | section_route.section_visitor!(visitor_num) if visitor_num 25 | end 26 | 27 | def section_visitor_target 28 | section_route.visitor_target if section_route 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/tackle/tinkerpop_graph_mixins.rb: -------------------------------------------------------------------------------- 1 | module TP 2 | module Person 3 | def self.route_conditions(graph) 4 | { :type => 'person' } 5 | end 6 | 7 | module Route 8 | def projects 9 | out_e.in_v(Project) 10 | end 11 | end 12 | end 13 | 14 | 15 | class Project 16 | def self.route_conditions(graph) 17 | { :type => 'project' } 18 | end 19 | 20 | module Route 21 | def people 22 | in_e.out_v(Person) 23 | end 24 | end 25 | end 26 | 27 | class Pangloss 28 | def self.route(base) 29 | base.v(:name => 'pangloss') 30 | end 31 | end 32 | 33 | module Coder 34 | module Route 35 | def projects 36 | out(Project, Software) 37 | end 38 | end 39 | end 40 | 41 | module Software 42 | module Route 43 | def coders 44 | self.in(Coder) 45 | end 46 | end 47 | end 48 | 49 | module Wrote 50 | def self.route_conditions(graph) 51 | { label: 'wrote' } 52 | end 53 | 54 | module Edge 55 | def writer 56 | out_vertex 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/pacer/filter/empty_filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.tg(:read_only) do 4 | use_pacer_graphml_data :read_only 5 | 6 | describe Pacer::Filter::EmptyFilter do 7 | let(:origin) { graph.v(Tackle::SimpleMixin) } 8 | subject { Pacer::Route.empty(origin) } 9 | 10 | its(:graph) { should == graph } 11 | its(:element_type) { should == graph.element_type(:vertex) } 12 | its(:extensions) { should == [Tackle::SimpleMixin] } 13 | its(:build_pipeline) { should be_nil } 14 | 15 | context 'with route built on it' do 16 | subject { Pacer::Route.empty(origin).filter(name: 'joe') } 17 | its(:graph) { should == graph } 18 | its(:element_type) { should == graph.element_type(:vertex) } 19 | its(:extensions) { should == [Tackle::SimpleMixin] } 20 | its(:inspect) { should == '# V-Property(name=="joe")>' } 21 | it 'should create a pipeline with only the pipe added to it' do 22 | start_pipe, end_pipe = subject.send :build_pipeline 23 | start_pipe.should == end_pipe 24 | start_pipe.should be_a Java::ComTinkerpopPipesFilter::PropertyFilterPipe 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /ideas.rb: -------------------------------------------------------------------------------- 1 | g.v.parallel(threads: 8, in_buffer: 4, out_buffer: 10) { |v| v.out.out.out.out.out } 2 | 3 | # - eagerly consume (1) input and push into a channel 4 | # - ChannelCapPipe 5 | # - create a cap pipe that does this. The pipe's output is the channel 6 | # - source data may be slow. Should probably not use a go block 7 | # - 1 thread in a loop 8 | # - Control the construction of parallel pipes. Default 2 threads, make 9 | # it configurable. 10 | # - standard copy split pipe can push the channel to subchannels 11 | # - each parallel route pulls from the channel. 12 | # - in a go block (waits will not block go thread pool) 13 | # - ChannelReaderPipe 14 | # - PathChannelReaderPipe 15 | # - parallel routes are unmodified 16 | # - cap each route - eagerly consume input and push into a channel 17 | # - ChannelCapPipe again 18 | # - 19 | # - like ExhaustMergePipe + GatherPipe 20 | # - use alts to read from any of the channels 21 | # - ChannelAltsReaderPipe 22 | 23 | 24 | 25 | # CCP 26 | # CSP (parallelism is 1 thread per pipe being split into) 27 | # CRP -> Work ... -> CCP 28 | # CRP -> Work ... -> CCP 29 | # ... 30 | # EMP 31 | # GP 32 | # CARP 33 | -------------------------------------------------------------------------------- /spec/pacer/side_effect/as_var_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.all(:read_only) do 4 | use_pacer_graphml_data(:read_only) 5 | 6 | describe '#as_var' do 7 | it 'should set the variable to the correct node' do 8 | vars = Set[] 9 | route = graph.v.as_var(:a_vertex) 10 | route.in_e(:wrote) { |edge| vars << route.vars[:a_vertex] }.count 11 | vars.should == Set[*graph.e.e(:wrote).in_v] 12 | end 13 | 14 | it 'should not break path generation (simple)' do 15 | who = nil 16 | r1 = graph.v.as_var(:who) 17 | r = r1.in_e(:wrote).out_v.v { |v| 18 | who = r1.vars[:who] 19 | }.paths 20 | r.each do |path| 21 | path.to_a[0].should == who 22 | path.length.should == 3 23 | end 24 | end 25 | 26 | it 'should not break path generation' do 27 | who_wrote_what = nil 28 | r1 = graph.v.as_var(:who) 29 | r = r1.in_e(:wrote).as_var(:wrote).out_v.as_var(:what).v { |v| 30 | who_wrote_what = [r1.vars[:who], r1.vars[:wrote], r1.vars[:what]] 31 | }.paths 32 | r.each do |path| 33 | path.to_a.should == who_wrote_what 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/pacer/pipe/cross_product_transform_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class CrossProductTransformPipe < UnaryTransformPipe 3 | attr_reader :branch_b 4 | 5 | def initialize(method, branch_a, branch_b) 6 | super(method, branch_a) 7 | if branch_b.is_a? Pipe 8 | @branch_b = branch_b 9 | @b = nil 10 | else 11 | @b = branch_b 12 | end 13 | end 14 | 15 | def processNextStart 16 | next_pair 17 | a.send method, b rescue nil if a.respond_to? method 18 | end 19 | 20 | protected 21 | 22 | attr_accessor :b 23 | 24 | def next_pair 25 | if branch_b 26 | begin 27 | self.b = branch_b.next 28 | rescue Pacer::EmptyPipe, java.util.NoSuchElementException 29 | next_a 30 | branch_b.setStarts SingleIterator.new(element) 31 | retry 32 | rescue NativeException => e 33 | if @first 34 | next_a 35 | branch_b.setStarts SingleIterator.new(element) 36 | retry 37 | else 38 | raise e 39 | end 40 | end 41 | else 42 | next_a 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | Bundler::GemHelper.install_tasks 4 | 5 | require 'rspec/core/rake_task' 6 | 7 | RSpec::Core::RakeTask.new(:spec) do |spec| 8 | spec.rspec_path = 'bin/rspec' 9 | spec.pattern = FileList['spec/**/*_spec.rb'] 10 | end 11 | 12 | task :mvn_tests do 13 | cd 'ext' do 14 | sh 'mvn test' 15 | end 16 | end 17 | 18 | task :default => :spec 19 | task :spec => :compile 20 | 21 | task :check_18_mode do 22 | if RUBY_VERSION !~ /1\.8/ 23 | warn 'Releasing gems in 1.9 mode does not work as of JRuby 1.6.5' 24 | raise 'Nooooooo!' 25 | end 26 | end 27 | 28 | require 'xn_gem_release_tasks' 29 | XNGemReleaseTasks.setup Pacer, 'lib/pacer/version.rb' 30 | 31 | task :build => :compile 32 | 33 | require 'rake/javaextensiontask' 34 | Rake::JavaExtensionTask.new('pacer-ext') do |ext| 35 | require 'lock_jar' 36 | LockJar.lock 37 | locked_jars = LockJar.load 38 | 39 | ext.name = 'pacer-ext' 40 | ext.ext_dir = 'ext/src/main/java' 41 | ext.lib_dir = 'lib' 42 | ext.source_version = '1.7' 43 | ext.target_version = '1.7' 44 | ext.classpath = locked_jars.map {|x| File.expand_path x}.join ':' 45 | end 46 | 47 | task :compile => :mvn_tests 48 | -------------------------------------------------------------------------------- /lib/pacer/side_effect/is_unique.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | # This method adds the IsUniquePipe to the pipeline and whenever the 5 | # pipeline is built, yields the pipe to the block given here. 6 | # 7 | # See #unique? below for example usage. 8 | def is_unique(&block) 9 | chain_route side_effect: :is_unique, on_build_pipe: block 10 | end 11 | 12 | # This method builds a pipe and ataches the IsUniquePipe to the end then 13 | # iterates the pipeline until it finds a unique element or hits the end. 14 | def unique? 15 | check = nil 16 | is_unique { |pipe| check = pipe }.each do 17 | return false unless check.isUnique 18 | end 19 | true 20 | end 21 | end 22 | end 23 | 24 | 25 | module SideEffect 26 | module IsUnique 27 | attr_accessor :on_build_pipe 28 | 29 | protected 30 | 31 | def attach_pipe(end_pipe) 32 | checked = Pacer::Pipes::IsUniquePipe.new 33 | checked.setStarts end_pipe if end_pipe 34 | on_build_pipe.call checked if on_build_pipe 35 | checked 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /ext/src/test/java/com/xnlogic/pacer/pipes/BlackboxPipelineTest.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import static org.junit.Assert.*; 4 | import org.junit.Test; 5 | import com.tinkerpop.pipes.Pipe; 6 | import com.tinkerpop.pipes.IdentityPipe; 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import com.xnlogic.pacer.pipes.BlackboxPipeline; 10 | 11 | public class BlackboxPipelineTest { 12 | @Test 13 | public void resetTest() { 14 | List data = Arrays.asList("Pacer", "Pipes", "Test"); 15 | Pipe pipe1 = new IdentityPipe(); 16 | Pipe pipe2 = new IdentityPipe(); 17 | BlackboxPipeline blackboxPipeline = new BlackboxPipeline(pipe1, pipe2); 18 | 19 | blackboxPipeline.setStarts(data); 20 | pipe2.setStarts(data); 21 | 22 | int count = 0; 23 | 24 | while (blackboxPipeline.hasNext()) { 25 | assertEquals(blackboxPipeline.next(), data.get(count)); 26 | blackboxPipeline.reset(); 27 | count++; 28 | } 29 | 30 | assertEquals(count, data.size()); 31 | assertFalse(blackboxPipeline.hasNext()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/pacer/pipe/wrapping_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Pipes 3 | class WrappingPipe < RubyPipe 4 | attr_reader :graph, :element_type, :extensions, :wrapper 5 | 6 | def initialize(graph, element_type = nil, extensions = []) 7 | super() 8 | if graph.is_a? Array 9 | @graph, @wrapper = graph 10 | else 11 | @graph = graph 12 | @element_type = element_type 13 | @extensions = extensions || [] 14 | @wrapper = Pacer::Wrappers::WrapperSelector.build graph, element_type, @extensions 15 | end 16 | end 17 | 18 | def instance(pipe, g) 19 | g ||= graph 20 | p = WrappingPipe.new [g, wrapper] 21 | p.setStarts pipe 22 | p 23 | end 24 | 25 | def getSideEffect 26 | starts.getSideEffect 27 | end 28 | 29 | def getCurrentPath 30 | starts.getCurrentPath 31 | end 32 | 33 | def wrapper=(w) 34 | if extensions.any? and w.respond_to? :add_extensions 35 | @wrapper = w.add_extensions extensions 36 | else 37 | @wrapper = w 38 | end 39 | end 40 | 41 | def processNextStart 42 | wrapper.new graph, starts.next 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /ext/src/main/java/com/xnlogic/pacer/pipes/IsUniquePipe.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import java.util.LinkedHashSet; 4 | import java.util.Set; 5 | 6 | import com.tinkerpop.pipes.AbstractPipe; 7 | 8 | public class IsUniquePipe extends AbstractPipe { 9 | private boolean unique; 10 | private Set historySet; 11 | 12 | public IsUniquePipe() { 13 | super(); 14 | this.prepareState(); 15 | } 16 | 17 | protected T processNextStart() { 18 | T value = this.starts.next(); 19 | if (this.unique) 20 | this.checkUniqueness(value); 21 | return value; 22 | } 23 | 24 | public void reset() { 25 | super.reset(); 26 | this.prepareState(); 27 | } 28 | 29 | public boolean isUnique() { 30 | return this.unique; 31 | } 32 | 33 | public boolean getSideEffect() { 34 | return this.unique; 35 | } 36 | 37 | protected void checkUniqueness(T value) { 38 | if (!this.historySet.add(value)) { 39 | this.unique = false; 40 | this.historySet = null; 41 | } 42 | } 43 | 44 | protected void prepareState() { 45 | this.historySet = new LinkedHashSet(); 46 | this.unique = true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/pacer/transform/lookup_ids.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | # TODO: this should only apply to ID routes...do we want to be that granular with route types? 4 | module RouteOperations 5 | # args is (optional) extensions followed by an (optional) options hash 6 | def lookup_ids(*args) 7 | if args.last.is_a? Hash 8 | opts = args.pop 9 | else 10 | opts = {} 11 | end 12 | chain_route({transform: :lookup_ids, element_type: :vertex, extensions: args, wrapper: nil}.merge(opts)) 13 | end 14 | end 15 | end 16 | 17 | module Transform 18 | module LookupIds 19 | import com.tinkerpop.pipes.transform.IdVertexPipe 20 | import com.tinkerpop.pipes.transform.IdEdgePipe 21 | 22 | def attach_pipe(end_pipe) 23 | fail ClientError, 'Can not look up elements without the graph' unless graph 24 | if element_type == :vertex 25 | pipe = IdVertexPipe.new graph.blueprints_graph 26 | elsif element_type == :vertex 27 | pipe = IdEdgePipe.new graph.blueprints_graph 28 | else 29 | fail ClientError, 'Can not look up elements without the element_type' 30 | end 31 | pipe.setStarts end_pipe if end_pipe 32 | pipe 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/pacer/core/graph/element_route_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for Pacer::Core::Graph::ElementRoute do 4 | describe '#properties' do 5 | subject { r.properties } 6 | its(:count) { should == r.count } 7 | its(:element_type) { should == :hash } 8 | specify 'should all be hashes' do 9 | props = subject.each 10 | elements = r.each 11 | elements.zip(props.to_a).each do |e, p| 12 | e.properties.should == p 13 | end 14 | end 15 | end 16 | 17 | context 'with extensions' do 18 | let(:route) { r.add_extensions([Tackle::SimpleMixin]) } 19 | describe '#each without a block' do 20 | subject { route.each } 21 | specify 'elements should be wrapped' do 22 | subject.first.extensions.should include(Tackle::SimpleMixin) 23 | end 24 | end 25 | end 26 | end 27 | 28 | Run.all(:read_only) do 29 | use_pacer_graphml_data(:read_only) 30 | 31 | context Pacer::Core::Graph::EdgesRoute, '2' do 32 | it_uses Pacer::Core::Graph::ElementRoute do 33 | def r 34 | graph.e 35 | end 36 | end 37 | end 38 | 39 | context Pacer::Core::Graph::VerticesRoute, '2' do 40 | it_uses Pacer::Core::Graph::ElementRoute do 41 | def r 42 | graph.v 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/pacer/blueprints/payload_elements.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Payload 3 | class Element 4 | include com.tinkerpop.blueprints.Element 5 | extend Forwardable 6 | 7 | def initialize(element, payload = nil) 8 | @element = element 9 | self.payload = payload 10 | end 11 | 12 | def inspect 13 | "#" 14 | end 15 | 16 | attr_reader :element 17 | attr_accessor :payload 18 | end 19 | 20 | class Edge < Element 21 | include com.tinkerpop.blueprints.Edge 22 | 23 | def_delegators :@element, 24 | # Object 25 | :equals, :toString, :hashCode, 26 | # Element 27 | :getId, :getPropertyKeys, :getProperty, :setProperty, :removeProperty, :getRawElement, 28 | # Edge 29 | :getLabel, :getVertex, :getRawEdge 30 | end 31 | 32 | class Vertex < Element 33 | include com.tinkerpop.blueprints.Vertex 34 | 35 | def_delegators :@element, 36 | # Object 37 | :equals, :toString, :hashCode, 38 | # Element 39 | :getId, :getPropertyKeys, :getProperty, :setProperty, :removeProperty, :getRawElement, 40 | # Vertex 41 | :getEdges, :getVertices, :query, :getRawVertex 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/pacer/support/awesome_print.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'awesome_print' 3 | rescue LoadError 4 | end 5 | 6 | # Fix the way ap indents hashes: 7 | 8 | # for awesome_print <= 0.4.0 9 | class AwesomePrint 10 | # for awesome_print >= 1.0.0 11 | #class AwesomePrint::Formatter 12 | private 13 | 14 | # Format a hash. If @options[:indent] if negative left align hash keys. 15 | #------------------------------------------------------------------------------ 16 | def awesome_hash(h) 17 | return "{}" if h == {} 18 | 19 | keys = @options[:sorted_hash_keys] ? h.keys.sort { |a, b| a.to_s <=> b.to_s } : h.keys 20 | data = keys.map do |key| 21 | plain_single_line do 22 | #[ @inspector.awesome(key), h[key] ] 23 | [ awesome(key), h[key] ] 24 | end 25 | end 26 | 27 | data = data.map do |key, value| 28 | if @options[:multiline] 29 | formatted_key = indent + key 30 | else 31 | formatted_key = key 32 | end 33 | indented do 34 | #formatted_key << colorize(" => ", :hash) << @inspector.awesome(value) 35 | formatted_key << colorize(" => ", :hash) << awesome(value) 36 | end 37 | end 38 | if @options[:multiline] 39 | "{\n" << data.join(",\n") << "\n#{outdent}}" 40 | else 41 | "{ #{data.join(', ')} }" 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/pacer/transform/payload.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Core::Graph::ElementRoute 2 | def payload(&block) 3 | chain_route transform: :payload, block: block 4 | end 5 | end 6 | 7 | module Pacer::Transform 8 | module Payload 9 | attr_accessor :block 10 | 11 | protected 12 | 13 | def attach_pipe(end_pipe) 14 | pipe = PayloadPipe.new(self, block) 15 | pipe.setStarts end_pipe if end_pipe 16 | pipe 17 | end 18 | 19 | class PayloadPipe < Pacer::Pipes::RubyPipe 20 | field_reader :currentEnd 21 | 22 | attr_reader :block, :wrapper 23 | 24 | def initialize(route, block) 25 | super() 26 | if route.element_type == :edge 27 | @wrapper = Pacer::Payload::Edge 28 | elsif route.element_type == :vertex 29 | @wrapper = Pacer::Payload::Vertex 30 | else 31 | fail Pacer::ClientError, 'Can not use PayloadPipe on non-element data' 32 | end 33 | block ||= proc { |el| nil } 34 | @block = Pacer::Wrappers::WrappingPipeFunction.new route, block 35 | end 36 | 37 | def processNextStart 38 | el = starts.next 39 | @wrapper.new el, block.call(el) 40 | end 41 | 42 | def getPathToHere 43 | path = super 44 | i = path.size - 1 45 | path.remove path.size - 1 if i >= 0 46 | path.add currentEnd 47 | path 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/pacer/graph/graph_ml.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | # Methods to be mixed into Blueprints Graph objects from any 3 | # implementation. 4 | # 5 | # Adds more convenient/rubyish methods and adds support for extensions 6 | # to some methods where needed. 7 | class GraphML 8 | # Import the data in a GraphML file. 9 | # 10 | # Will fail if the data already exsts in the current graph. 11 | # 12 | # @param [String] path 13 | def self.import(graph, path) 14 | path = File.expand_path path 15 | begin 16 | stream = java.net.URL.new(path).open_stream 17 | rescue java.net.MalformedURLException 18 | stream = java.io.FileInputStream.new path 19 | end 20 | graph.transaction(nesting: true) do 21 | graph.send :creating_elements do 22 | com.tinkerpop.blueprints.util.io.graphml.GraphMLReader.input_graph graph.blueprints_graph, stream 23 | end 24 | end 25 | true 26 | ensure 27 | stream.close if stream 28 | end 29 | 30 | # Export the graph to GraphML 31 | # 32 | # @param [String] path will be replaced if it exists 33 | def self.export(graph, path) 34 | path = File.expand_path path 35 | stream = java.io.FileOutputStream.new path 36 | com.tinkerpop.blueprints.util.io.graphml.GraphMLWriter.outputGraph graph.blueprints_graph, stream 37 | ensure 38 | stream.close if stream 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/pacer/wrappers/index_wrapper.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Wrappers 2 | class IndexWrapper 3 | attr_reader :index, :graph, :element_type 4 | 5 | def initialize(graph, index, element_type) 6 | @index = index 7 | @graph = graph 8 | @element_type = element_type 9 | end 10 | 11 | def name 12 | index.index_name 13 | end 14 | 15 | def wrapper 16 | WrapperSelector.build graph, element_type 17 | end 18 | 19 | def first(key, value, extensions = nil) 20 | e = index.get(key, value).first 21 | if e 22 | e = wrapper.new graph, e 23 | e = e.add_extensions extensions if extensions 24 | end 25 | e 26 | end 27 | 28 | def all(key, value, extensions = nil) 29 | iter = index.get(key, value) 30 | if graph or extensions 31 | pipe = Pacer::Pipes::WrappingPipe.new graph, element_type, extensions 32 | pipe.setStarts iter.iterator 33 | pipe 34 | else 35 | iter 36 | end 37 | end 38 | 39 | def put(key, value, element) 40 | if element.is_a? ElementWrapper 41 | element = element.element 42 | end 43 | 44 | index.put key.to_s, value, element 45 | end 46 | 47 | def remove(key, value, element) 48 | if element.is_a? ElementWrapper 49 | element = element.element 50 | end 51 | 52 | index.remove key.to_s, value, element 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/pacer/pipe/unary_transform_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class UnaryTransformPipe < RubyPipe 3 | import com.tinkerpop.pipes.Pipe 4 | import com.tinkerpop.pipes.util.iterators.SingleIterator 5 | 6 | attr_reader :branch_a, :method 7 | 8 | def initialize(method, branch_a) 9 | super() 10 | if branch_a.is_a? Pipe 11 | @branch_a = branch_a 12 | @a = nil 13 | else 14 | @a = branch_a 15 | end 16 | @first = true 17 | @method = method 18 | @element = nil 19 | end 20 | 21 | def processNextStart 22 | next_a 23 | a.send method rescue nil if a.respond_to? method 24 | end 25 | 26 | protected 27 | 28 | attr_accessor :element, :a 29 | 30 | def next_element 31 | @first = false 32 | self.element = starts.next 33 | end 34 | 35 | def next_a 36 | if branch_a 37 | begin 38 | self.a = branch_a.next 39 | rescue EmptyPipe 40 | next_element 41 | branch_a.setStarts SingleIterator.new(element) if branch_a 42 | retry 43 | rescue NativeException => e 44 | if @first 45 | next_element 46 | branch_a.setStarts SingleIterator.new(element) if branch_a 47 | retry 48 | else 49 | raise e 50 | end 51 | end 52 | else 53 | next_element 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/pacer/pipe/path_wrapping_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Pipes 3 | class PathWrappingPipe < RubyPipe 4 | attr_reader :graph 5 | attr_accessor :vertex_wrapper, :edge_wrapper, :other_wrapper 6 | 7 | def initialize(graph, vertex_extensions = [], edge_extensions = []) 8 | super() 9 | if graph.is_a? Array 10 | @graph, @vertex_wrapper, @edge_wrapper = graph 11 | else 12 | @graph = graph 13 | @vertex_wrapper = Pacer::Wrappers::WrapperSelector.build graph, :vertex, vertex_extensions || Set[] 14 | @edge_wrapper = Pacer::Wrappers::WrapperSelector.build graph, :edge, edge_extensions || Set[] 15 | end 16 | end 17 | 18 | def instance(pipe, g) 19 | g ||= graph 20 | p = PathWrappingPipe.new [g, vertex_wrapper, edge_wrapper] 21 | p.setStarts pipe 22 | p 23 | end 24 | 25 | def getCurrentPath 26 | starts.getCurrentPath 27 | end 28 | 29 | def processNextStart 30 | path = starts.next 31 | path.collect do |item| 32 | if item.is_a? Pacer::Vertex 33 | vertex_wrapper.new graph, item 34 | elsif item.is_a? Pacer::Edge 35 | edge_wrapper.new graph, item 36 | elsif other_wrapper 37 | other_wrapper.new graph, item 38 | else 39 | item 40 | end 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/pacer/filter/uniq_section.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def uniq_in_section(section = nil) 5 | chain_route filter: Pacer::Filter::UniqueSectionFilter, section: section 6 | end 7 | end 8 | end 9 | 10 | module Filter 11 | module UniqueSectionFilter 12 | # VisitsSection module provides: 13 | # section= 14 | # section_visitor 15 | include Pacer::Visitors::VisitsSection 16 | 17 | def attach_pipe(end_pipe) 18 | pipe = UniqueSectionPipe.new(self, section_visitor) 19 | pipe.setStarts end_pipe if end_pipe 20 | pipe 21 | end 22 | 23 | class UniqueSectionPipe < Pacer::Pipes::RubyPipe 24 | attr_reader :seen, :section, :route 25 | 26 | 27 | def initialize(route, section) 28 | super() 29 | @seen = Set[] 30 | @section = section 31 | @route = route 32 | section.visitor = self if section 33 | end 34 | 35 | def processNextStart 36 | while true 37 | element = starts.next 38 | unless seen.include? element 39 | seen << element 40 | return element 41 | end 42 | end 43 | end 44 | 45 | def after_element 46 | seen.clear 47 | end 48 | 49 | def reset 50 | seen.clear 51 | end 52 | end 53 | end 54 | end 55 | end 56 | 57 | -------------------------------------------------------------------------------- /lib/pacer/side_effect/aggregate.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes::RouteOperations 3 | def aggregate(into = nil, &block) 4 | aggregate = ::Pacer::SideEffect::Aggregate 5 | r = self 6 | r = section(into, aggregate::ElementSet) if into.is_a? Symbol 7 | into = block if block 8 | r.chain_route :side_effect => aggregate, :into => into 9 | end 10 | end 11 | 12 | module SideEffect 13 | module Aggregate 14 | import com.tinkerpop.pipes.sideeffect.AggregatePipe 15 | import java.util.HashSet 16 | 17 | include Pacer::Visitors::VisitsSection 18 | 19 | attr_reader :into 20 | 21 | def into=(name) 22 | @into = name 23 | self.section = name if name.is_a? Symbol 24 | end 25 | 26 | protected 27 | 28 | def attach_pipe(end_pipe) 29 | case into 30 | when Symbol 31 | hs = vars[into] = HashSet.new 32 | pipe = AggregatePipe.new hs 33 | when Proc 34 | pipe = AggregatePipe.new into.call(self) 35 | when nil 36 | pipe = AggregatePipe.new HashSet.new 37 | else 38 | pipe = AggregatePipe.new into 39 | end 40 | pipe.setStarts end_pipe if end_pipe 41 | pipe 42 | end 43 | 44 | class ElementSet < HashSet 45 | def on_element(element) 46 | add element 47 | end 48 | 49 | def reset 50 | clear 51 | end 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/pacer/filter/limit_section_filter.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def limit_section(section = nil, max) 5 | chain_route filter: Pacer::Filter::LimitSectionFilter, section_max: max, section: section 6 | end 7 | end 8 | end 9 | 10 | module Filter 11 | module LimitSectionFilter 12 | # VisitsSection module provides: 13 | # section= 14 | # section_visitor 15 | include Pacer::Visitors::VisitsSection 16 | 17 | attr_accessor :section_max 18 | 19 | def attach_pipe(end_pipe) 20 | pipe = LimitSectionPipe.new(self, section_visitor, section_max) 21 | pipe.setStarts end_pipe if end_pipe 22 | pipe 23 | end 24 | 25 | class LimitSectionPipe < Pacer::Pipes::RubyPipe 26 | attr_reader :max, :section, :route 27 | attr_accessor :hits 28 | 29 | 30 | def initialize(route, section, max) 31 | super() 32 | @hits = 0 33 | @max = max 34 | @section = section 35 | @route = route 36 | section.visitor = self if section 37 | end 38 | 39 | def processNextStart 40 | while hits == max 41 | starts.next 42 | end 43 | self.hits += 1 44 | starts.next 45 | end 46 | 47 | def after_element 48 | self.hits = 0 49 | end 50 | 51 | def reset 52 | self.hits = 0 53 | end 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/pacer/pipe/simple_visitor_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Pipes 3 | class SimpleVisitorPipe < Pacer::Pipes::RubyPipe 4 | attr_reader :visitor, :in_section, :wrapper, :graph 5 | 6 | attr_accessor :use_on_raw_element, :use_on_element, :use_after_element, :use_visitor_reset 7 | 8 | def initialize(wrapper, graph) 9 | super() 10 | @in_section = false 11 | @wrapper = wrapper 12 | @graph = graph 13 | end 14 | 15 | def visitor=(visitor) 16 | @visitor = visitor 17 | @use_on_raw_element = visitor.respond_to? :on_raw_element 18 | @use_on_element = visitor.respond_to? :on_element 19 | @use_after_element = visitor.respond_to? :after_element 20 | @use_visitor_reset = visitor.respond_to? :visitor_reset 21 | end 22 | 23 | def processNextStart 24 | visitor.after_element if use_after_element and in_section 25 | current = starts.next 26 | @in_section = true unless in_section 27 | visitor.on_raw_element current if use_on_raw_element 28 | if use_on_element 29 | wrapped = wrapper.new graph, current 30 | visitor.on_element(wrapped) 31 | end 32 | return current 33 | rescue EmptyPipe, java.util.NoSuchElementException 34 | @in_section = false 35 | raise EmptyPipe.instance 36 | end 37 | 38 | def reset 39 | visitor.visitor_reset if use_visitor_reset 40 | @in_section = false 41 | super 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /ext/src/main/java/com/xnlogic/pacer/pipes/BlackboxPipeline.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import java.util.Iterator; 4 | import java.util.List; 5 | 6 | import com.tinkerpop.pipes.Pipe; 7 | 8 | public class BlackboxPipeline implements Pipe { 9 | private Pipe startPipe; 10 | private Pipe endPipe; 11 | 12 | public BlackboxPipeline(Pipe startPipe, Pipe endPipe) { 13 | this.startPipe = startPipe; 14 | this.endPipe = endPipe; 15 | } 16 | 17 | public void setStarts(final Iterator pipe) { 18 | this.startPipe.setStarts(pipe); 19 | } 20 | 21 | public void setStarts(final Iterable pipe) { 22 | this.setStarts(pipe.iterator()); 23 | } 24 | 25 | public E next() { 26 | return this.endPipe.next(); 27 | } 28 | 29 | public boolean hasNext() { 30 | return this.endPipe.hasNext(); 31 | } 32 | 33 | public void reset() { 34 | this.endPipe.reset(); 35 | } 36 | 37 | public void enablePath(boolean enable) { 38 | this.endPipe.enablePath(enable); 39 | } 40 | 41 | public List getCurrentPath() { 42 | return this.endPipe.getCurrentPath(); 43 | } 44 | 45 | public Iterator iterator() { 46 | return this.endPipe.iterator(); 47 | } 48 | 49 | public void remove() { 50 | throw new UnsupportedOperationException(); 51 | } 52 | 53 | public String toString() { 54 | return "[" + this.startPipe.toString() + "..." + this.endPipe.toString() + "]"; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, TinkerPop [http://tinkerpop.com] 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the TinkerPop nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL TINKERPOP BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /lib/pacer/core/graph/graph_route.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Core::Graph 2 | 3 | # This module adds route methods to the basic graph classes returned from the 4 | # blueprints library. 5 | module GraphRoute 6 | # Returns a new route to all graph vertices. Standard filter options. 7 | def v(*filters, &block) 8 | filters = Pacer::Route.filters(self, filters) 9 | route = chain_route :element_type => :vertex, 10 | :pipe_class => Pacer::Pipes::VerticesPipe, 11 | :route_name => 'GraphV' 12 | Pacer::Route.property_filter(route, filters, block, true) 13 | end 14 | 15 | # Returns a new route to all graph edges. Standard filter options. 16 | def e(*filters, &block) 17 | filters = Pacer::Route.edge_filters(self, filters) 18 | route = chain_route :element_type => :edge, 19 | :pipe_class => Pacer::Pipes::EdgesPipe, 20 | :route_name => 'GraphE' 21 | Pacer::Route.property_filter(route, filters, block, true) 22 | end 23 | 24 | def filter(*args) 25 | raise 'Not implemented' 26 | end 27 | 28 | # Specialization of result simply returns self. 29 | def result 30 | self 31 | end 32 | 33 | # The graph itself is as root as you can get. 34 | def root? 35 | true 36 | end 37 | 38 | def graph 39 | # This must be defined here to overwrite the #graph method in Route. 40 | self 41 | end 42 | 43 | def ==(other) 44 | equal?(other) 45 | end 46 | 47 | # Don't try to inspect the graph data when inspecting. 48 | def hide_elements 49 | true 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/pacer/filter/where_filter.rb: -------------------------------------------------------------------------------- 1 | require 'pacer/filter/where_filter/node_visitor' 2 | require 'jruby' # for JRuby.parse 3 | 4 | module Pacer 5 | module Routes 6 | module RouteOperations 7 | def where(str, values = {}) 8 | if str and not str.blank? 9 | chain_route :filter => :where, :where_statement => str.to_s, :values => values 10 | else 11 | self 12 | end 13 | end 14 | 15 | def unless(str, values = {}) 16 | if str and str !~ /\A\s*\Z/ 17 | where "not (#{str})", values 18 | else 19 | self 20 | end 21 | end 22 | end 23 | end 24 | 25 | module Filter 26 | module WhereFilter 27 | attr_reader :where_statement 28 | attr_accessor :values 29 | 30 | def where_statement=(str) 31 | @where_statement = str 32 | @parsed = @intermediate = nil 33 | end 34 | 35 | def parsed 36 | @parsed ||= JRuby.parse @where_statement 37 | end 38 | 39 | def intermediate 40 | encoded_values = {} 41 | if values 42 | values.each { |k, v| encoded_values[k.to_sym] = graph.encode_property(v) } 43 | end 44 | @intermediate ||= parsed.accept(NodeVisitor.new(self, encoded_values)) 45 | end 46 | 47 | def build! 48 | intermediate.build 49 | end 50 | 51 | protected 52 | 53 | def attach_pipe(end_pipe) 54 | pipe = build! 55 | pipe.setStarts end_pipe if end_pipe 56 | pipe 57 | end 58 | 59 | def inspect_string 60 | "where(#@where_statement)" 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/pacer/function_resolver.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module FunctionResolver 3 | class << self 4 | def clear_cache 5 | @lookup_path = nil 6 | end 7 | 8 | def function(args) 9 | lookup_path.each do |key, map, extension| 10 | if value = args[key] 11 | function = map.fetch(value, value.is_a?(Module) && value) 12 | return [function, extension] if function 13 | end 14 | end 15 | [] 16 | end 17 | 18 | def lookup_path 19 | @lookup_path ||= [ 20 | [:filter, filter_map, nil], 21 | [:transform, transform_map, nil], 22 | [:side_effect, side_effect_map, Pacer::Core::SideEffect], 23 | [:visitor, visitor_map] 24 | ] 25 | end 26 | 27 | def filter_map 28 | Hash[Pacer::Filter.constants.map { |name| [symbolize_module_name(name), Pacer::Filter.const_get(name)] }] 29 | end 30 | 31 | def side_effect_map 32 | Hash[Pacer::SideEffect.constants.map { |name| [symbolize_module_name(name), Pacer::SideEffect.const_get(name)] }] 33 | end 34 | 35 | def visitor_map 36 | Hash[Pacer::Visitors.constants.map { |name| [symbolize_module_name(name), Pacer::Visitors.const_get(name)] }] 37 | end 38 | 39 | def transform_map 40 | Hash[Pacer::Transform.constants.map { |name| [symbolize_module_name(name), Pacer::Transform.const_get(name)] }] 41 | end 42 | 43 | def symbolize_module_name(name) 44 | name.to_s.sub(/(Filter|SideEffect|Transform)$/, '').gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase.to_sym 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /samples/grateful_dead.rb: -------------------------------------------------------------------------------- 1 | module GD 2 | module Artist 3 | def self.route_conditions(graph) 4 | { type: 'artist' } 5 | end 6 | 7 | module Route 8 | def written 9 | in_e(:written_by).out_v(Song) 10 | end 11 | 12 | def sung 13 | in_e(:sung_by).out_v(Song) 14 | end 15 | 16 | def songwriters 17 | lookahead { |v| v.in_e(:written_by) } 18 | end 19 | 20 | def singers 21 | lookahead { |v| v.in_e(:sung_by) } 22 | end 23 | end 24 | end 25 | 26 | module Song 27 | def self.route_conditions(graph) 28 | { type: 'song' } 29 | end 30 | 31 | module Route 32 | def writer(*args) 33 | out_e(:written_by).in_v(Artist).filter(*args) 34 | end 35 | 36 | def sung(*args) 37 | out_e(:sung_by).in_v(Artist).filter(*args) 38 | end 39 | 40 | def next_song(arg = nil, args = nil) 41 | if arg.is_a? Fixnum 42 | out_e(:followed_by, :weight => arg).in_v(Song).filter(args) 43 | else 44 | out_e(:followed_by).in_v(Song).filter(arg) 45 | end 46 | end 47 | 48 | def prev_song(arg = nil, args = nil) 49 | if arg.is_a? Fixnum 50 | in_e(:followed_by, :weight => arg).out_v(Song).filter(args) 51 | else 52 | in_e(:followed_by).out_v(Song).filter(arg) 53 | end 54 | end 55 | 56 | def collaborations 57 | # The [1] is a range filter. It will only succeed if there are 58 | # at least 2 results (remember, range filter is 0-indexed) 59 | lookahead { |s| s.writer.uniq[1] } 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/pacer/core/graph/edges_route_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.all(:read_only) do 4 | use_pacer_graphml_data(:read_only) 5 | 6 | context Pacer::Core::Graph::EdgesRoute do 7 | subject { graph.e } 8 | it { should be_a Pacer::Core::Graph::EdgesRoute } 9 | its(:count) { should == 14 } 10 | 11 | describe '#out_v' do 12 | subject { graph.e.out_v } 13 | it { should be_a Pacer::Core::Graph::VerticesRoute } 14 | its(:count) { should == 14 } 15 | end 16 | 17 | describe '#in_v' do 18 | subject { graph.e.in_v } 19 | it { should be_a Pacer::Core::Graph::VerticesRoute } 20 | its(:count) { should == 14 } 21 | end 22 | 23 | describe '#both_v' do 24 | subject { graph.e.both_v } 25 | it { should be_a Pacer::Core::Graph::VerticesRoute } 26 | its(:count) { should == 28 } 27 | end 28 | 29 | describe '#e' do 30 | subject { graph.e.e } 31 | it { should be_a Pacer::Core::Graph::EdgesRoute } 32 | its(:count) { should == 14 } 33 | end 34 | 35 | describe '#labels' do 36 | subject { graph.e.labels } 37 | it { should be_a Pacer::Core::Route } 38 | its(:element_type) { should == :object } 39 | its(:count) { should == 14 } 40 | end 41 | 42 | describe '#to_h' do 43 | subject { graph.e.to_h } 44 | let(:okram) { graph.v(:name => 'okram').first } 45 | 46 | it { should be_a Hash } 47 | specify 'okram key has all outward related vertices in an array as the value' do 48 | subject[okram].sort_by { |v| v.element_id }.should == okram.out_e.in_v.to_a.sort_by { |v| v.element_id } 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/pacer/transform/map_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | shared_examples_for '#map' do 4 | describe 'simple element route' do 5 | subject do 6 | c = 0 7 | source.map { c += 1 } 8 | end 9 | its(:first) { should == 1 } 10 | its(:element_type) { should == :object } 11 | end 12 | 13 | describe 'with extensions' do 14 | let(:extended) { source.add_extensions([Tackle::SimpleMixin]) } 15 | 16 | subject { extended.map { |v| v.extensions } } 17 | 18 | its(:first) { should == [Tackle::SimpleMixin] } 19 | its(:element_type) { should == :object } 20 | 21 | context 'with vertex result type' do 22 | subject { extended.map(element_type: :vertex) { |v| v } } 23 | its(:element_type) { should == :vertex } 24 | its(:extensions) { should == [] } 25 | end 26 | 27 | context 'with extended vertex result type' do 28 | let(:exts) { [] } 29 | subject { extended.map(element_type: :vertex, extensions: TP::Person) { |v| exts << v.extensions; v } } 30 | its(:element_type) { should == :vertex } 31 | its(:extensions) { should == [TP::Person] } 32 | it 'should use the source - not the result - extension in the block' do 33 | v = subject.first 34 | v.extensions.should == [TP::Person] 35 | exts.first.should == [Tackle::SimpleMixin] 36 | end 37 | end 38 | end 39 | end 40 | 41 | Run.tg :read_only do 42 | use_pacer_graphml_data :read_only 43 | 44 | context 'on route' do 45 | it_uses '#map' do 46 | let(:source) { graph.v } 47 | end 48 | end 49 | 50 | context 'on element' do 51 | it_uses '#map' do 52 | let(:source) { pangloss } 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/pacer/blueprints/group_vertex.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | # Created without property support, though it could easily be added if it is ever needed. 3 | class GroupVertex 4 | import com.tinkerpop.blueprints.util.VerticesFromEdgesIterable 5 | IN = com.tinkerpop.blueprints.Direction::IN 6 | OUT = com.tinkerpop.blueprints.Direction::OUT 7 | BOTH = com.tinkerpop.blueprints.Direction::BOTH 8 | 9 | attr_reader :components, :key 10 | attr_reader :paths, :wrapper, :graph 11 | 12 | # Initialize it with an empty set to force uniqueness. Non-unique by default. 13 | def initialize(key, graph, wrapper, components = nil) 14 | @key = key 15 | @wrapper = wrapper 16 | if components 17 | @components = components 18 | else 19 | @components = [] 20 | end 21 | end 22 | 23 | def add_component(vertex) 24 | components << vertex 25 | end 26 | 27 | include com.tinkerpop.blueprints.Element 28 | 29 | def getId 30 | "#{ key }:#{ components.count }" 31 | end 32 | 33 | def getPropertyKeys 34 | Set[] 35 | end 36 | 37 | def getProperty(key) 38 | case key 39 | when 'components' then components.map { |c| wrapper.new graph, c } 40 | when 'key' then key 41 | when 'count' then components.count 42 | end 43 | end 44 | 45 | include com.tinkerpop.blueprints.Vertex 46 | 47 | def getRawVertex 48 | self 49 | end 50 | 51 | def getVertices(direction, *labels) 52 | VerticesFromEdgesIterable.new self, direction, *labels 53 | end 54 | 55 | def getEdges(direction, *labels) 56 | Pacer::Pipes::MultiPipe.new components.map { |v| v.getEdges(direction, *labels) } 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/pacer/visitors/section.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def section(section_name = nil, visitor_target = nil) 5 | chain_route visitor: :section, section_name: section_name, visitor_target: visitor_target 6 | end 7 | 8 | # see #as_var for the old as implementation 9 | def as(section_name = nil) 10 | section section_name 11 | end 12 | end 13 | end 14 | 15 | module Visitors 16 | module Section 17 | attr_accessor :section_name, :visitor_target 18 | 19 | def will_visit! 20 | @visitor_count = visitor_count + 1 21 | visitor_count - 1 22 | end 23 | 24 | def section_visitor!(visitor_num) 25 | vpipes = Thread.current["visitors_#{object_id}"] 26 | vpipe = vpipes[visitor_num] 27 | vpipes[visitor_num] = nil 28 | vpipe 29 | end 30 | 31 | protected 32 | 33 | def visitor_count 34 | @visitor_count = 0 unless defined? @visitor_count 35 | @visitor_count 36 | end 37 | 38 | attr_reader :section_visitors 39 | 40 | def attach_pipe(end_pipe) 41 | # With detached pipes, pipe construction happens in 42 | # multiple threads, multiple times. 43 | pipe = end_pipe 44 | vpipes = (1..visitor_count).map do 45 | pipe = Pacer::Pipes::SimpleVisitorPipe.new Pacer::Wrappers::WrapperSelector.build(graph, element_type, extensions), graph 46 | pipe.setStarts end_pipe if end_pipe 47 | end_pipe = pipe 48 | end 49 | Thread.current["visitors_#{object_id}"] = vpipes 50 | pipe 51 | end 52 | 53 | def inspect_class_name 54 | "#{super}(#{section_name.inspect})" 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/pacer/wrappers/wrapper_selector.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Wrappers 2 | class WrapperSelector 3 | import com.tinkerpop.blueprints.Vertex 4 | import com.tinkerpop.blueprints.Edge 5 | 6 | def self.build(graph, element_type = nil, extensions = []) 7 | if graph 8 | if element_type == :vertex 9 | graph.base_vertex_wrapper.wrapper_for extensions 10 | elsif element_type == :edge 11 | graph.base_edge_wrapper.wrapper_for extensions 12 | else 13 | new extensions 14 | end 15 | else 16 | if element_type == :vertex 17 | Pacer::Wrappers::VertexWrapper.wrapper_for extensions 18 | elsif element_type == :edge 19 | Pacer::Wrappers::EdgeWrapper.wrapper_for extensions 20 | else 21 | new extensions 22 | end 23 | end 24 | end 25 | 26 | attr_reader :extensions 27 | attr_accessor :vertex_wrapper, :edge_wrapper 28 | 29 | def initialize(extensions = []) 30 | @extensions = extensions 31 | end 32 | 33 | def wrapper(graph, element) 34 | if graph 35 | if element.is_a? Vertex 36 | self.vertex_wrapper ||= graph.base_vertex_wrapper.wrapper_for extensions 37 | elsif element.is_a? Edge 38 | self.edge_wrapper ||= graph.base_edge_wrapper.wrapper_for extensions 39 | end 40 | else 41 | if element.is_a? Vertex 42 | self.vertex_wrapper ||= Pacer::Wrappers::VertexWrapper.wrapper_for extensions 43 | elsif element.is_a? Edge 44 | self.edge_wrapper ||= Pacer::Wrappers::EdgeWrapper.wrapper_for extensions 45 | end 46 | end 47 | end 48 | 49 | def new(graph, element) 50 | w = wrapper(graph, element) 51 | if w 52 | w.new graph, element 53 | else 54 | element 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/pacer/transform/path.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core 3 | module Route 4 | def paths(*exts) 5 | route = chain_route :transform => :path, :element_type => :path 6 | if exts.any? 7 | exts = exts.map { |e| Array.wrap(e) if e } 8 | route.map(element_type: :path) do |path| 9 | path.zip(exts).map { |element, ext| ext ? element.add_extensions(ext) : element } 10 | end 11 | else 12 | route 13 | end 14 | end 15 | end 16 | end 17 | 18 | module Transform 19 | module Path 20 | import com.tinkerpop.pipes.transform.PathPipe 21 | 22 | def help(section = nil) 23 | case section 24 | when nil 25 | puts < # Obj-Map -> Path-Path> 31 | p.to_a #=> [[1,1], [2,4], [3,6]] 32 | 33 | This is especially useful for graph traversals. 34 | 35 | g.v.out_e.in_v.out_e.in_v.paths.first #=> [#, 36 | # #, 37 | # #, 38 | # #, 39 | # #] 40 | 41 | See: 42 | :paths - for more details and general information about paths. 43 | :arrays - for general operations that work on arrays or paths. 44 | 45 | HELP 46 | else 47 | super 48 | end 49 | description 50 | end 51 | 52 | protected 53 | 54 | def attach_pipe(end_pipe) 55 | pipe = PathPipe.new 56 | pipe.setStarts end_pipe if end_pipe 57 | pipe 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/pacer/route/mixin/bulk_operations.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Routes 2 | 3 | # Additional iteration methods that allow for rapid data 4 | # manipulation in transactional graphs. Bulk operations automatically 5 | # manage transactions in larger batches rather than on every 6 | # element created or removed or every property set. 7 | module BulkOperations 8 | # Like bulk_job that also returns an array of results 9 | def bulk_map(size = nil, target_graph = nil) 10 | result = [] 11 | bulk_job(size, target_graph) do |e| 12 | result << yield(e) 13 | end 14 | result 15 | end 16 | 17 | # Iterates over each element in the route, controlling 18 | # transactions so that they are only committed once every 19 | # +size+ records. 20 | def bulk_job(size = nil, target_graph = nil, pre_commit = nil) 21 | target_graph ||= graph 22 | graph.read_transaction do 23 | if target_graph and not target_graph.in_bulk_job? 24 | begin 25 | target_graph.in_bulk_job = true 26 | size ||= target_graph.bulk_job_size 27 | counter = 0 28 | print "Bulk job ->" if Pacer.verbose? 29 | each_slice(size) do |slice| 30 | target_graph.transaction(nesting: true) do 31 | print " #{counter}" if Pacer.verbose? 32 | counter += size 33 | slice.each do |element| 34 | yield element 35 | end 36 | pre_commit.call if pre_commit 37 | end 38 | end 39 | ensure 40 | puts '!' if Pacer.verbose? 41 | target_graph.in_bulk_job = false 42 | end 43 | elsif target_graph 44 | each do |element| 45 | yield element 46 | end 47 | else 48 | raise 'No graph in route for bulk job' 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/pacer/transform/join.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def join(&block) 5 | chain_route transform: Pacer::Transform::Join, block: block 6 | end 7 | 8 | def unjoin 9 | map { |g| g[:components] }.scatter extensions: extensions, graph: graph, element_type: element_type 10 | end 11 | end 12 | end 13 | 14 | module Transform 15 | module Join 16 | attr_reader :key_block, :unique 17 | 18 | def block=(block) 19 | @key_block = block 20 | end 21 | 22 | def uniq 23 | @unique = true 24 | self 25 | end 26 | 27 | protected 28 | 29 | def attach_pipe(end_pipe) 30 | pipe = JoinPipe.new(self, key_block, unique) 31 | pipe.setStarts end_pipe if end_pipe 32 | pipe 33 | end 34 | 35 | class JoinPipe < Pacer::Pipes::RubyPipe 36 | attr_reader :block, :groups, :unique 37 | attr_accessor:to_emit 38 | 39 | def initialize(back, key_block, unique) 40 | super() 41 | @unique = unique 42 | @block = Pacer::Wrappers::WrappingPipeFunction.new back, key_block 43 | end 44 | 45 | def processNextStart 46 | unless to_emit 47 | coll = unique ? Set : Array 48 | groups = Hash.new { |h, k| h[k] = Pacer::GroupVertex.new k, block.graph, block.wrapper, coll.new } 49 | while starts.hasNext 50 | el = starts.next 51 | groups[block.call(el)].add_component el 52 | end 53 | self.to_emit = groups.values 54 | end 55 | if to_emit.empty? 56 | raise Pacer::Pipes::EmptyPipe.instance 57 | else 58 | to_emit.shift 59 | end 60 | end 61 | 62 | def reset 63 | super 64 | self.loaded = false 65 | self.to_emit = nil 66 | end 67 | end 68 | end 69 | end 70 | end 71 | 72 | -------------------------------------------------------------------------------- /ext/src/main/java/com/xnlogic/pacer/pipes/IdCollectionFilterPipe.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import java.util.Collection; 4 | import java.util.HashSet; 5 | import java.util.Set; 6 | 7 | import com.tinkerpop.blueprints.Contains; 8 | import com.tinkerpop.blueprints.Element; 9 | import com.tinkerpop.pipes.AbstractPipe; 10 | 11 | public class IdCollectionFilterPipe extends AbstractPipe { 12 | private Set ids; 13 | private boolean containsIn; 14 | 15 | // TODO: Consider making this a derived exception. Also, this constructor is the reverse of the Ruby one. Is this ok? 16 | // Also also, is an Object array the way to go? Can we nail down the type further? 17 | @SuppressWarnings("unchecked") 18 | public IdCollectionFilterPipe(final Collection ids, final Contains comparison) throws RuntimeException { 19 | super(); 20 | if (ids instanceof Set) { 21 | this.ids = (Set)ids; 22 | } else if (ids == null) { 23 | this.ids = new HashSet(); 24 | } else { 25 | this.ids = new HashSet(); 26 | this.ids.addAll(ids); 27 | } 28 | if (comparison == Contains.IN) 29 | this.containsIn = true; 30 | else if (comparison == Contains.NOT_IN) 31 | this.containsIn = false; 32 | else 33 | throw new RuntimeException("Unknown comparison type for ID collection filter"); 34 | } 35 | 36 | protected E processNextStart() { 37 | if (this.containsIn) { 38 | while (true) { 39 | E e = this.starts.next(); 40 | if (e != null && this.ids.contains(e.getId())) 41 | return e; 42 | } 43 | } else { 44 | while (true) { 45 | E e = this.starts.next(); 46 | if (e != null && !this.ids.contains(e.getId())) 47 | return e; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ext/src/test/java/com/xnlogic/pacer/pipes/CollectionFilterPipeTest.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import static org.junit.Assert.*; 4 | import org.junit.Test; 5 | import com.tinkerpop.blueprints.Contains; 6 | import java.util.Collection; 7 | import java.util.Arrays; 8 | import java.util.ArrayList; 9 | import com.xnlogic.pacer.pipes.CollectionFilterPipe; 10 | 11 | public class CollectionFilterPipeTest { 12 | @Test 13 | public void filterInTest() { 14 | Collection collection = Arrays.asList("Pacer", "Pipes", "XNLogic"); 15 | Collection starts = Arrays.asList("Pacer", "XNLogic"); 16 | Collection result = new ArrayList(); 17 | CollectionFilterPipe collectionFilterPipe = new CollectionFilterPipe(collection, Contains.IN); 18 | 19 | collectionFilterPipe.setStarts(starts); 20 | 21 | while (collectionFilterPipe.hasNext()) { 22 | result.add(collectionFilterPipe.next()); 23 | } 24 | 25 | assertEquals(2, result.size()); 26 | assertTrue(result.contains("Pacer")); 27 | assertTrue(result.contains("XNLogic")); 28 | assertFalse(result.contains("Pipes")); 29 | } 30 | 31 | @Test 32 | public void filterNotInTest() { 33 | Collection collection = Arrays.asList("Pacer", "Pipes", "XNLogic"); 34 | Collection starts = Arrays.asList("Pacer", "Java"); 35 | Collection result = new ArrayList(); 36 | CollectionFilterPipe collectionFilterPipe = new CollectionFilterPipe(collection, Contains.NOT_IN); 37 | 38 | collectionFilterPipe.setStarts(starts); 39 | 40 | while (collectionFilterPipe.hasNext()) { 41 | result.add(collectionFilterPipe.next()); 42 | } 43 | 44 | assertEquals(1, result.size()); 45 | assertFalse(result.contains("Pacer")); 46 | assertTrue(result.contains("Java")); 47 | } 48 | 49 | // TODO: Test other constructor version. 50 | } 51 | -------------------------------------------------------------------------------- /lib/pacer/side_effect/as_var.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes::RouteOperations 3 | # Store the current intermediate element in the route's vars hash by the 4 | # given name so that it is accessible subsequently in the processing of the 5 | # route. 6 | # 7 | # Deprecated. 8 | def as_var(name) 9 | as = ::Pacer::SideEffect::AsVar 10 | section(name, as::SingleElementSet).chain_route :side_effect => as, :variable_name => name 11 | end 12 | end 13 | 14 | module SideEffect 15 | module AsVar 16 | class AsPipe < Pacer::Pipes::RubyPipe 17 | attr_accessor :vars 18 | 19 | def initialize(pipe, vars, variable_name) 20 | super() 21 | setStarts pipe if pipe 22 | @vars = vars 23 | @variable_name = variable_name 24 | end 25 | 26 | def getCurrentPath 27 | starts.getCurrentPath 28 | end 29 | 30 | protected 31 | 32 | def processNextStart 33 | @vars[@variable_name] = starts.next 34 | end 35 | end 36 | 37 | 38 | import java.util.HashSet 39 | 40 | attr_accessor :variable_name 41 | 42 | protected 43 | 44 | def attach_pipe(pipe) 45 | if element_type == :vertex or element_type == :edge or element_type == :mixed 46 | wrapped = Pacer::Pipes::WrappingPipe.new graph, element_type, extensions 47 | wrapped.setStarts pipe if pipe 48 | as_pipe = AsPipe.new(wrapped, vars, variable_name) 49 | unwrapped = Pacer::Pipes::UnwrappingPipe.new 50 | unwrapped.setStarts as_pipe 51 | unwrapped 52 | else 53 | AsPipe.new(pipe, vars, variable_name) 54 | end 55 | end 56 | 57 | def inspect_class_name 58 | @variable_name.inspect 59 | end 60 | 61 | class SingleElementSet < HashSet 62 | def on_element(element) 63 | clear 64 | add element 65 | end 66 | 67 | def reset 68 | clear 69 | end 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/pacer/wrappers/path_wrapping_pipe_function.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Wrappers 3 | class PathWrappingPipeFunction 4 | include com.tinkerpop.pipes.PipeFunction 5 | 6 | attr_reader :block, :graph, :wrapper 7 | 8 | def initialize(back, block) 9 | @block = block 10 | if back 11 | @graph = back.graph 12 | end 13 | @wrapper = WrapperSelector.build graph 14 | end 15 | 16 | def arity 17 | block.arity 18 | end 19 | 20 | def compute(path) 21 | if path.first.is_a? Pacer::Wrappers::ElementWrapper 22 | block.call path 23 | else 24 | p = path.map do |element| 25 | wrapper.new graph, element 26 | end 27 | block.call p 28 | end 29 | end 30 | 31 | alias call compute 32 | 33 | def call_with_args(element, *args) 34 | if path.first.is_a? Pacer::Wrappers::ElementWrapper 35 | block.call path, *args 36 | else 37 | p = path.map do |element| 38 | wrapper.new graph, element 39 | end 40 | block.call p, *args 41 | end 42 | end 43 | end 44 | 45 | class PathUnwrappingPipeFunction 46 | include com.tinkerpop.pipes.PipeFunction 47 | 48 | attr_reader :block 49 | 50 | def initialize(block) 51 | @block = block 52 | end 53 | 54 | def arity 55 | block.arity 56 | end 57 | 58 | def compute(path) 59 | unwrap block.call path 60 | end 61 | 62 | alias call compute 63 | 64 | def call_with_args(path, *args) 65 | unwrap block.call path, *args 66 | end 67 | 68 | def unwrap(p) 69 | if p.is_a? Array 70 | p.map do |e| 71 | if e.is_a? ElementWrapper 72 | e.element 73 | else 74 | e 75 | end 76 | end 77 | elsif p.is_a? ElementWrapper 78 | p.element 79 | else 80 | p 81 | end 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/pacer/filter/future_filter.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def lookahead(opts = {}, &block) 5 | chain_route({ :filter => :future, :block => block }.merge(opts)) 6 | end 7 | 8 | def neg_lookahead(opts = {}, &block) 9 | chain_route({ :filter => :future, :neg_block => block }.merge(opts)) 10 | end 11 | end 12 | end 13 | 14 | module Filter 15 | module FutureFilter 16 | import com.tinkerpop.pipes.filter.FutureFilterPipe 17 | 18 | attr_accessor :min, :max 19 | 20 | def block=(block) 21 | @future_filter = [block, false] 22 | end 23 | 24 | def neg_block=(block) 25 | @future_filter = [block, true] 26 | end 27 | 28 | protected 29 | 30 | def after_initialize 31 | @future_filter = nil unless defined? @future_filter 32 | @route = nil unless defined? @route 33 | super 34 | end 35 | 36 | def attach_pipe(end_pipe) 37 | pipe = FutureFilterPipe.new(lookahead_pipe) 38 | pipe.setStarts(end_pipe) if end_pipe 39 | pipe 40 | end 41 | 42 | def lookahead_route 43 | if @future_filter 44 | block, negate = @future_filter 45 | @future_filter = nil 46 | route = block.call(Pacer::Route.empty(self)) 47 | route = route.back while route.remove_from_lookahead 48 | route = route.lookahead_replacement.call(route) if route.lookahead_replacement 49 | if min or max 50 | route = route.has_count_route(:min => min, :max => max).is(true) 51 | end 52 | if negate 53 | route = route.chain_route(pipe_class: Pacer::Pipes::IsEmptyPipe, :route_name => 'negate') 54 | end 55 | @route = route 56 | elsif @route 57 | @route 58 | end 59 | end 60 | 61 | def lookahead_pipe 62 | Pacer::Route.pipeline(lookahead_route) 63 | end 64 | 65 | def inspect_string 66 | "#{ inspect_class_name }(#{ lookahead_route.inspect })" 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /ext/src/test/java/com/xnlogic/pacer/pipes/ExpandablePipeTest.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import static org.junit.Assert.assertNull; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.ArrayList; 7 | 8 | import org.junit.Test; 9 | 10 | public class ExpandablePipeTest { 11 | 12 | @Test 13 | public void queueWithElementsTest() { 14 | ExpandablePipe expandablePipe = new ExpandablePipe(); 15 | 16 | ArrayList input = new ArrayList(); 17 | input.add("X"); 18 | 19 | expandablePipe.setStarts(input.iterator()); 20 | 21 | expandablePipe.add("a", 1, new ArrayList()); 22 | expandablePipe.add("b", 2, new ArrayList()); 23 | expandablePipe.add("c", 3, new ArrayList()); 24 | 25 | Object result = expandablePipe.next(); 26 | assertTrue(result.equals("a")); 27 | assertTrue(expandablePipe.getMetadata().equals(1)); 28 | 29 | result = expandablePipe.next(); 30 | assertTrue(result.equals("b")); 31 | assertTrue(expandablePipe.getMetadata().equals(2)); 32 | 33 | result = expandablePipe.next(); 34 | assertTrue(result.equals("c")); 35 | assertTrue(expandablePipe.getMetadata().equals(3)); 36 | 37 | result = expandablePipe.next(); 38 | assertTrue(result.equals("X")); 39 | assertNull(expandablePipe.getMetadata()); 40 | } 41 | 42 | @Test 43 | public void emptyQueueTest() { 44 | // TODO: fix this test 45 | 46 | //ExpandablePipe expandablePipe = new ExpandablePipe(); 47 | 48 | //IdentityPipe pipe1 = new IdentityPipe(); 49 | //DeadPipe pipe2 = new DeadPipe(); 50 | 51 | //pipe1.enablePath(true); 52 | 53 | //pipe1.setStarts(pipe2); 54 | //expandablePipe.setStarts(pipe1); 55 | // 56 | //Pipe p = expandablePipe.next(); 57 | //assertTrue(pipe2.equals(p)); 58 | //assertNull(expandablePipe.getMetadata()); 59 | } 60 | 61 | // TODO: Test getPathToHere() 62 | 63 | } 64 | -------------------------------------------------------------------------------- /lib/pacer/wrappers/wrapping_pipe_function.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Wrappers 3 | class WrappingPipeFunction 4 | include com.tinkerpop.pipes.PipeFunction 5 | 6 | attr_reader :block, :graph, :wrapper, :extensions 7 | 8 | def initialize(back, block) 9 | @block = block 10 | if back 11 | @graph = back.graph 12 | @extensions = back.extensions 13 | element_type = back.element_type 14 | end 15 | @wrapper = WrapperSelector.build graph, element_type, extensions 16 | end 17 | 18 | def arity 19 | block.arity 20 | end 21 | 22 | def compute(element) 23 | e = wrapper.new graph, element if element 24 | block.call e 25 | end 26 | 27 | alias call compute 28 | 29 | def call_with_args(element, *args) 30 | e = wrapper.new graph, element if element 31 | block.call e, *args 32 | end 33 | 34 | def wrap_path(path) 35 | path.collect do |item| 36 | if item.is_a? Pacer::Vertex 37 | wrapped = Pacer::Wrappers::VertexWrapper.new graph, item 38 | wrapped 39 | elsif item.is_a? Pacer::Edge 40 | wrapped = Pacer::Wrappers::EdgeWrapper.new graph, item 41 | wrapped 42 | else 43 | item 44 | end 45 | end 46 | end 47 | end 48 | 49 | class UnwrappingPipeFunction 50 | include com.tinkerpop.pipes.PipeFunction 51 | 52 | attr_reader :block 53 | 54 | def initialize(block) 55 | @block = block 56 | end 57 | 58 | def arity 59 | block.arity 60 | end 61 | 62 | def compute(element) 63 | e = block.call element 64 | if e.is_a? ElementWrapper 65 | e.element 66 | else 67 | e 68 | end 69 | end 70 | 71 | alias call compute 72 | 73 | def call_with_args(element, *args) 74 | e = block.call element, *args 75 | if e.is_a? ElementWrapper 76 | e.element 77 | else 78 | e 79 | end 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/pacer/pipe/visitor_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Pipes 3 | class VisitorPipe < Pacer::Pipes::RubyPipe 4 | attr_reader :visitor, :queue, :in_section 5 | 6 | attr_accessor :use_on_element, :use_replace_element, 7 | :use_after_element, :use_reset, :use_hasNext, :use_next 8 | 9 | def initialize(visitor = nil) 10 | super() 11 | self.visitor = visitor if visitor 12 | @queue = [] 13 | @in_section = false 14 | end 15 | 16 | def visitor=(visitor) 17 | if visitor.respond_to? :on_pipe 18 | @visitor = visitor.on_pipe(self) 19 | else 20 | @visitor = visitor 21 | end 22 | @use_hasNext = visitor.respond_to? :hasNext 23 | @use_next = visitor.respond_to? :next 24 | @use_on_element = visitor.respond_to? :on_element 25 | @use_replace_element = visitor.respond_to? :replace_element 26 | @use_after_element = visitor.respond_to? :after_element 27 | @use_reset = visitor.respond_to? :reset 28 | end 29 | 30 | def processNextStart 31 | while true 32 | visitor.after_element if use_after_element and in_section 33 | if use_next and (not use_hasNext or visitor.hasNext) 34 | return visitor.next 35 | elsif queue.any? 36 | return queue.shift 37 | else 38 | current = starts.next 39 | @in_section = true unless in_section 40 | visitor.on_element(current) if use_on_element 41 | if use_replace_element 42 | visitor.replace_element(current) do |e| 43 | queue << e 44 | end 45 | else 46 | return current 47 | end 48 | end 49 | end 50 | rescue EmptyPipe, java.util.NoSuchElementException 51 | @in_section = false 52 | raise EmptyPipe.instance 53 | end 54 | 55 | def reset 56 | visitor.reset if use_reset 57 | @in_section = false 58 | @queue = [] 59 | super 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /ext/src/test/java/com/xnlogic/pacer/pipes/IsUniquePipeTest.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import static org.junit.Assert.*; 4 | import org.junit.Test; 5 | import java.util.Collection; 6 | import java.util.Arrays; 7 | import com.xnlogic.pacer.pipes.IsUniquePipe; 8 | 9 | public class IsUniquePipeTest { 10 | 11 | @Test 12 | public void allUniqueTest() { 13 | Collection collection = Arrays.asList("These", "are", "all", "unique"); 14 | IsUniquePipe isUniquePipe = new IsUniquePipe(); 15 | 16 | isUniquePipe.setStarts(collection); 17 | 18 | String s = isUniquePipe.next(); 19 | assertTrue(s.equals("These")); 20 | assertTrue(isUniquePipe.isUnique()); 21 | 22 | s = isUniquePipe.next(); 23 | assertTrue(s.equals("are")); 24 | assertTrue(isUniquePipe.isUnique()); 25 | 26 | s = isUniquePipe.next(); 27 | assertTrue(s.equals("all")); 28 | assertTrue(isUniquePipe.isUnique()); 29 | 30 | s = isUniquePipe.next(); 31 | assertTrue(s.equals("unique")); 32 | assertTrue(isUniquePipe.isUnique()); 33 | } 34 | 35 | @Test 36 | public void notAllUniqueTest() { 37 | Collection collection = Arrays.asList("Not", "all", "all", "all", "unique"); 38 | IsUniquePipe isUniquePipe = new IsUniquePipe(); 39 | 40 | isUniquePipe.setStarts(collection); 41 | 42 | String s = isUniquePipe.next(); 43 | assertTrue(s.equals("Not")); 44 | assertTrue(isUniquePipe.isUnique()); 45 | 46 | s = isUniquePipe.next(); 47 | assertTrue(s.equals("all")); 48 | assertTrue(isUniquePipe.isUnique()); 49 | 50 | s = isUniquePipe.next(); 51 | assertTrue(s.equals("all")); 52 | assertFalse(isUniquePipe.isUnique()); 53 | 54 | s = isUniquePipe.next(); 55 | assertTrue(s.equals("all")); 56 | assertFalse(isUniquePipe.isUnique()); 57 | 58 | s = isUniquePipe.next(); 59 | assertTrue(s.equals("unique")); 60 | assertFalse(isUniquePipe.isUnique()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ext/src/test/java/com/xnlogic/pacer/pipes/LabelPrefixPipeTest.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import java.util.Arrays; 7 | import java.util.Collection; 8 | import java.util.NoSuchElementException; 9 | 10 | import org.junit.After; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | import com.tinkerpop.blueprints.Edge; 15 | import com.tinkerpop.blueprints.Vertex; 16 | import com.tinkerpop.blueprints.impls.tg.TinkerGraph; 17 | 18 | public class LabelPrefixPipeTest { 19 | private TinkerGraph graph; 20 | private Collection edges; 21 | 22 | @Before 23 | public void setup() throws Exception { 24 | this.graph = new TinkerGraph(); 25 | } 26 | 27 | @After 28 | public void teardown() throws Exception { 29 | this.graph.shutdown(); 30 | this.graph = null; 31 | } 32 | 33 | private void createEdges() { 34 | Vertex v1 = this.graph.addVertex(1); 35 | Vertex v2 = this.graph.addVertex(2); 36 | Vertex v3 = this.graph.addVertex(3); 37 | Vertex v4 = this.graph.addVertex(4); 38 | 39 | Edge e1 = this.graph.addEdge("E1", v1, v2, "edge1"); 40 | Edge e2 = this.graph.addEdge("E2", v2, v1, "edge2"); 41 | Edge e3 = this.graph.addEdge("E3", v2, v3, "edge3"); 42 | Edge e4 = this.graph.addEdge("E4", v3, v4, "edge4"); 43 | 44 | this.edges = Arrays.asList(e1, e2, e3, e4); 45 | } 46 | 47 | @Test 48 | public void hasLabelPrefixesTest() { 49 | this.createEdges(); 50 | LabelPrefixPipe labelPrefixPipe = new LabelPrefixPipe("edge[2-3]"); 51 | 52 | labelPrefixPipe.setStarts(this.edges); 53 | Edge e = labelPrefixPipe.next(); 54 | assertEquals("E2", e.getId()); 55 | 56 | e = labelPrefixPipe.next(); 57 | assertEquals("E3", e.getId()); 58 | 59 | boolean hasEx = false; 60 | 61 | try { 62 | e = labelPrefixPipe.next(); 63 | } catch (NoSuchElementException nsee) { 64 | hasEx = true; 65 | } 66 | 67 | assertTrue(hasEx); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/pacer/utils/tsort.rb: -------------------------------------------------------------------------------- 1 | require 'tsort' 2 | 3 | module Pacer 4 | module Utils 5 | # Include this module in your traversal to use ruby's built-in TSort 6 | # utility to sort your vertices according to the direction of the 7 | # edges that connect them. 8 | module TSort 9 | module Route 10 | include ::TSort 11 | 12 | attr_accessor :tsort_anon_mod 13 | 14 | # NOTE: this is a great example of dynamically injecting a custom method 15 | # into a route. 16 | def dependencies(&block) 17 | anon_mod = Module.new 18 | class << anon_mod 19 | def inspect 20 | '' 21 | end 22 | end 23 | anon_mod.const_set :Route, TSort::Route 24 | anon_mod.const_set :Vertex, Module.new 25 | anon_mod::Vertex.const_set :DependenciesBlock, block 26 | anon_mod::Vertex.instance_eval do 27 | def self.included(target) 28 | target.const_set :DependenciesBlock, self::DependenciesBlock 29 | end 30 | end 31 | route = v.add_extensions [TSort, anon_mod] 32 | route.tsort_anon_mod = anon_mod 33 | route 34 | end 35 | 36 | def tsort_each_node 37 | v.each do |vertex| 38 | yield vertex 39 | end 40 | end 41 | 42 | def tsort_each_child(node) 43 | node.tsort_dependencies(tsort_anon_mod).each do |vertex| 44 | yield vertex 45 | end 46 | end 47 | 48 | def tsort 49 | super.to_route(:graph => graph, 50 | :element_type => :vertex, 51 | :extensions => (extensions - [TSort, tsort_anon_mod])) 52 | end 53 | end 54 | 55 | module Vertex 56 | def tsort_each_node 57 | yield self 58 | end 59 | 60 | def tsort_dependencies(tsort_anon_mod = nil) 61 | if self.class.const_defined? :DependenciesBlock 62 | self.class::DependenciesBlock.call(self).add_extensions([TSort, tsort_anon_mod]) 63 | else 64 | self.in(extensions) 65 | end 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/pacer/transform/count_section.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def count_section(section = nil, &block) 5 | chain_route transform: Pacer::Transform::CountSection, key_block: block, section: section, 6 | element_type: :array 7 | end 8 | end 9 | end 10 | 11 | 12 | module Transform 13 | module CountSection 14 | # VisitsSection module provides: 15 | # section= 16 | # section_visitor 17 | # section_route 18 | include Pacer::Visitors::VisitsSection 19 | 20 | attr_accessor :key_block 21 | 22 | protected 23 | 24 | def attach_pipe(end_pipe) 25 | block = key_block || proc { |x| x } 26 | pf = Pacer::Wrappers::WrappingPipeFunction.new section_route, block 27 | pipe = CountSectionPipe.new(section_visitor, pf) 28 | pipe.setStarts end_pipe if end_pipe 29 | pipe 30 | end 31 | 32 | class CountSectionPipe < Pacer::Pipes::RubyPipe 33 | attr_reader :pf, :to_emit, :section, :count, :section_key 34 | 35 | def initialize(section, pipe_function) 36 | super() 37 | @count = 0 38 | @section = section 39 | if section 40 | section.visitor = self 41 | else 42 | on_element nil 43 | end 44 | @pf = pipe_function 45 | end 46 | 47 | def processNextStart 48 | until to_emit 49 | starts.next 50 | @count += 1 51 | end 52 | raise EmptyPipe.instance if to_emit.empty? 53 | emit = to_emit 54 | @to_emit = nil 55 | emit 56 | rescue EmptyPipe, java.util.NoSuchElementException 57 | if count == 0 58 | raise EmptyPipe.instance 59 | else 60 | after_element 61 | to_emit 62 | end 63 | end 64 | 65 | def getPathToHere 66 | section.getCurrentPath 67 | end 68 | 69 | def on_element(element) 70 | @section_key = pf.compute element 71 | end 72 | 73 | def after_element 74 | @to_emit = [section_key, count] 75 | @count = 0 76 | end 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/pacer/graph/yaml_encoder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Pacer::YamlEncoder do 4 | let(:original) do 5 | { :string => ' bob ', 6 | :symbol => :abba, 7 | :empty => '', 8 | :integer => 121, 9 | :float => 100.001, 10 | :time => Time.utc(1999, 11, 9, 9, 9, 1), 11 | :object => { :a => 1, 1 => :a }, 12 | :set => Set[1, 2, 3], 13 | :nested_array => [1, 'a', [2]], 14 | :ok_string => 'string value' 15 | } 16 | end 17 | 18 | describe '#encode_property' do 19 | subject do 20 | pairs = original.map do |name, value| 21 | [name, Pacer::YamlEncoder.encode_property(value)] 22 | end 23 | Hash[pairs] 24 | end 25 | 26 | it { should_not equal(original) } 27 | 28 | specify 'string should be stripped' do 29 | subject[:string].should == 'bob' 30 | end 31 | 32 | specify 'empty string becomes nil' do 33 | subject[:empty].should be_nil 34 | end 35 | 36 | specify 'numbers should be javafied' do 37 | subject[:integer].should == 121.to_java(:long) 38 | subject[:float].should == 100.001 39 | end 40 | 41 | specify 'dates are custom to enable range queries' do 42 | subject[:time].should =~ /^ utcT \d/ 43 | end 44 | 45 | specify 'everything else should be yaml' do 46 | subject[:nested_array].should == ' ' + YAML.dump(original[:nested_array]) 47 | end 48 | end 49 | 50 | describe '#decode_property' do 51 | it 'should round-trip cleanly' do 52 | # remove values that get cleaned up when encoded 53 | original.delete :string 54 | original.delete :empty 55 | 56 | original.values.each do |value| 57 | encoded = Pacer::YamlEncoder.encode_property(value) 58 | decoded = Pacer::YamlEncoder.decode_property(encoded) 59 | decoded.should == value 60 | end 61 | end 62 | 63 | it 'should strip strings' do 64 | encoded = Pacer::YamlEncoder.encode_property(' a b c ') 65 | decoded = Pacer::YamlEncoder.decode_property(encoded) 66 | decoded.should == 'a b c' 67 | end 68 | 69 | it 'empty strings -> nil' do 70 | encoded = Pacer::YamlEncoder.encode_property(' ') 71 | decoded = Pacer::YamlEncoder.decode_property(encoded) 72 | decoded.should be_nil 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/pacer/filter/property_filter/edge_filters_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # edge filters are also tested in filters_spec 4 | module Pacer::Filter::PropertyFilter 5 | Run.tg do 6 | describe EdgeFilters do 7 | subject { filters } 8 | 9 | context 'symbol label' do 10 | let(:filters) { Pacer::Route.edge_filters graph, [:label] } 11 | 12 | its(:any?) { should be_true } 13 | its(:labels) { should == ['label'] } 14 | its(:extensions_only?) { should be_false } 15 | its(:extensions) { should be_empty } 16 | its(:route_modules) { should be_empty } 17 | its(:wrapper) { should be_nil } 18 | its(:blocks) { should be_empty } 19 | its(:properties) { should be_empty } 20 | end 21 | 22 | context 'symbol labels' do 23 | let(:filters) { Pacer::Route.edge_filters graph, [:label, :label2] } 24 | 25 | its(:any?) { should be_true } 26 | its(:labels) { should == ['label', 'label2'] } 27 | end 28 | 29 | context 'labels arrays' do 30 | let(:filters) { Pacer::Route.edge_filters graph, ["label", [:label2]] } 31 | 32 | its(:any?) { should be_true } 33 | its(:labels) { should == ['label', 'label2'] } 34 | end 35 | 36 | context 'labels and properties' do 37 | let(:filters) { Pacer::Route.edge_filters graph, [:label, { prop: 'value' }] } 38 | 39 | its(:any?) { should be_true } 40 | its(:labels) { should == ['label'] } 41 | its(:properties) { should == [['prop', 'value']] } 42 | end 43 | 44 | context 'labels and extension' do 45 | let(:filters) { Pacer::Route.edge_filters graph, [:label, TP::Person] } 46 | 47 | its(:any?) { should be_true } 48 | its(:labels) { should == ['label'] } 49 | its(:extensions) { should == [TP::Person] } 50 | its(:properties) { should == [ %w[ type person ] ] } 51 | end 52 | 53 | context 'labels and simple extension' do 54 | let(:filters) { Pacer::Route.edge_filters graph, [:label, Tackle::SimpleMixin] } 55 | 56 | its(:any?) { should be_true } 57 | its(:labels) { should == ['label'] } 58 | its(:extensions) { should == [Tackle::SimpleMixin] } 59 | its(:properties) { should be_empty } 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/pacer/graph/simple_encoder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.tg do 4 | describe Pacer::SimpleEncoder do 5 | use_simple_graph_data 6 | 7 | let(:original) do 8 | { :string => ' bob ', 9 | :symbol => :abba, 10 | :empty => '', 11 | :integer => 121, 12 | :float => 100.001, 13 | :time => Time.utc(1999, 11, 9, 9, 9, 1), 14 | :object => { :a => 1, 1 => :a }, 15 | :set => Set[1, 2, 3], 16 | :nested_array => [1, 'a', [2]], 17 | :ok_string => 'string value' 18 | } 19 | end 20 | 21 | describe '#encode_property' do 22 | subject do 23 | pairs = original.map do |name, value| 24 | [name, Pacer::SimpleEncoder.encode_property(value)] 25 | end 26 | Hash[pairs] 27 | end 28 | 29 | it { should_not equal(original) } 30 | 31 | specify 'string should be stripped' do 32 | subject[:string].should == 'bob' 33 | end 34 | 35 | specify 'empty string becomes nil' do 36 | subject[:empty].should be_nil 37 | end 38 | 39 | it 'should not change anything else' do 40 | # remove values that get cleaned up when encoded 41 | original.delete :string 42 | original.delete :empty 43 | 44 | original.values.each do |value| 45 | encoded = Pacer::SimpleEncoder.encode_property(value) 46 | encoded.should == value 47 | end 48 | end 49 | 50 | end 51 | 52 | describe '#decode_property' do 53 | it 'should round-trip cleanly' do 54 | # remove values that get cleaned up when encoded 55 | original.delete :string 56 | original.delete :empty 57 | 58 | original.values.each do |value| 59 | encoded = Pacer::SimpleEncoder.encode_property(value) 60 | decoded = Pacer::SimpleEncoder.decode_property(encoded) 61 | decoded.should == value 62 | end 63 | end 64 | 65 | it 'should strip strings' do 66 | encoded = Pacer::SimpleEncoder.encode_property(' a b c ') 67 | decoded = Pacer::SimpleEncoder.decode_property(encoded) 68 | decoded.should == 'a b c' 69 | end 70 | 71 | it 'empty strings -> nil' do 72 | encoded = Pacer::SimpleEncoder.encode_property(' ') 73 | decoded = Pacer::SimpleEncoder.decode_property(encoded) 74 | decoded.should be_nil 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/pacer/filter/empty_filter.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | class Route 3 | class << self 4 | # This method is useful for creating sideline routes that branch 5 | # off of the current route. 6 | # 7 | # It creates a new route without any source based on the type, 8 | # filters, function and extensions of the given route. The main 9 | # thing about the returned route is that the pipeline that is 10 | # built from it will not include any of the pipes that make up 11 | # the route it's based on. 12 | # 13 | # @param [Route] back the route the new route is based on. 14 | # @return [Route] 15 | def empty(back) 16 | back.chain_route :filter => :empty 17 | end 18 | 19 | def block_branch(back, block, branch_start = nil) 20 | if block.arity == 0 21 | route = block.call rescue nil 22 | else 23 | unless branch_start 24 | if back.is_a? Pacer::Graph 25 | branch_start = back 26 | else 27 | branch_start = Pacer::Route.empty(back) 28 | end 29 | end 30 | route = block.call(branch_start) rescue nil 31 | end 32 | if route == branch_start 33 | identity_branch(back).route 34 | elsif route.is_a? Pacer::Route 35 | route.route 36 | else 37 | empty(back).map(&block).route 38 | end 39 | end 40 | 41 | def identity_branch(back) 42 | Pacer::Route.empty(back).chain_route(:pipe_class => Pacer::Pipes::IdentityPipe, 43 | :route_name => '@').route 44 | end 45 | 46 | end 47 | end 48 | 49 | 50 | module Filter 51 | module EmptyFilter 52 | protected 53 | 54 | def after_initialize 55 | @empty_back = @back 56 | @back = @pacer_source = nil 57 | super 58 | end 59 | 60 | def build_pipeline 61 | nil 62 | end 63 | 64 | def inspect_class_name 65 | s = case element_type 66 | when :vertex 67 | 'V' 68 | when :edge 69 | 'E' 70 | when :object 71 | 'Obj' 72 | when :mixed 73 | 'Elem' 74 | else 75 | element_type.to_s.capitalize 76 | end 77 | s = "#{s} #{ @info }" if @info 78 | s 79 | end 80 | end 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /spec/pacer/blueprints/neo4j_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module NeoSpec 4 | class Person < Pacer::Wrappers::VertexWrapper 5 | def self.route_conditions(graph) 6 | { type: 'person' } 7 | end 8 | end 9 | 10 | class FastPerson < Pacer::Wrappers::VertexWrapper 11 | def self.lookup(graph) 12 | { type: 'person' } 13 | end 14 | end 15 | 16 | class Frog < Pacer::Wrappers::VertexWrapper 17 | def self.route_conditions(graph) 18 | { frog: 'yes' } 19 | end 20 | end 21 | 22 | Run.neo4j :read_only do 23 | use_pacer_graphml_data :read_only 24 | 25 | describe '#vertex' do 26 | it 'should not raise an exception for invalid key type' do 27 | graph.vertex('bad id').should be_nil 28 | end 29 | end 30 | 31 | describe '#edge' do 32 | it 'should not raise an exception for invalid key type' do 33 | graph.edge('bad id').should be_nil 34 | end 35 | end 36 | 37 | describe 'indexed' do 38 | before do 39 | # TODO FIXME: why do the presence of these key indices break lots of 40 | # subsequent tests if they are on graph rather than graph2? 41 | graph2.create_key_index :type, :vertex 42 | graph2.create_key_index :name, :vertex 43 | end 44 | 45 | describe Person do 46 | subject { graph2.v(Person) } 47 | 48 | # sanity checks 49 | it { should be_a Pacer::Filter::LuceneFilter } 50 | its(:query) { should == 'type:"person"' } 51 | # This doesn't work because neo indices are out of sync before the transaction finalizes 52 | #its(:count) { should == 2 } 53 | 54 | its(:wrapper) { should == Person } 55 | end 56 | 57 | describe FastPerson do 58 | subject { graph2.v(FastPerson) } 59 | 60 | # sanity checks 61 | it { should be_a Pacer::Filter::LuceneFilter } 62 | its(:query) { should == 'type:"person"' } 63 | # This doesn't work because neo indices are out of sync before the transaction finalizes 64 | #its(:count) { should == 2 } 65 | 66 | its(:wrapper) { should == FastPerson } 67 | end 68 | 69 | describe Frog do 70 | subject { graph2.v(Frog) } 71 | 72 | # sanity checks 73 | it { should_not be_a Pacer::Filter::LuceneFilter } 74 | its(:count) { should == 0 } 75 | 76 | its(:wrapper) { should == Frog } 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/pacer/blueprints/neo4j2_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | module Neo2Spec 4 | class Person < Pacer::Wrappers::VertexWrapper 5 | def self.route_conditions(graph) 6 | { type: 'person' } 7 | end 8 | end 9 | 10 | class FastPerson < Pacer::Wrappers::VertexWrapper 11 | def self.route_conditions(graph) 12 | { type: 'person' } 13 | end 14 | end 15 | 16 | class Frog < Pacer::Wrappers::VertexWrapper 17 | def self.route_conditions(graph) 18 | { frog: 'yes' } 19 | end 20 | end 21 | 22 | Run.neo4j2 :read_only do 23 | use_pacer_graphml_data :read_only 24 | 25 | describe '#vertex' do 26 | it 'should not raise an exception for invalid key type' do 27 | graph.vertex('bad id').should be_nil 28 | end 29 | end 30 | 31 | describe '#edge' do 32 | it 'should not raise an exception for invalid key type' do 33 | graph.edge('bad id').should be_nil 34 | end 35 | end 36 | 37 | describe 'indexed' do 38 | before do 39 | # TODO FIXME: why do the presence of these key indices break lots of 40 | # subsequent tests if they are on graph rather than graph2? 41 | graph2.create_key_index :type, :vertex 42 | graph2.create_key_index :name, :vertex 43 | end 44 | 45 | describe Person do 46 | subject { graph2.v(Person) } 47 | 48 | # sanity checks 49 | it { should be_a Pacer::Filter::LuceneFilter } 50 | its(:query) { should == 'type:"person"' } 51 | # This doesn't work because neo indices are out of sync before the transaction finalizes 52 | #its(:count) { should == 2 } 53 | 54 | its(:wrapper) { should == Person } 55 | end 56 | 57 | describe FastPerson do 58 | subject { graph2.v(FastPerson) } 59 | 60 | # sanity checks 61 | it { should be_a Pacer::Filter::LuceneFilter } 62 | its(:query) { should == 'type:"person"' } 63 | # This doesn't work because neo indices are out of sync before the transaction finalizes 64 | #its(:count) { should == 2 } 65 | 66 | its(:wrapper) { should == FastPerson } 67 | end 68 | 69 | describe Frog do 70 | subject { graph2.v(Frog) } 71 | 72 | # sanity checks 73 | it { should_not be_a Pacer::Filter::LuceneFilter } 74 | its(:count) { should == 0 } 75 | 76 | its(:wrapper) { should == Frog } 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/pacer/core/graph/graph_index_route.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Core::Graph 2 | 3 | # This module adds indexed route methods to the basic graph classes returned from the 4 | # blueprints library. 5 | module GraphIndexRoute 6 | # If never_scan is true, raise an exception if a graph route does not 7 | # start with an indexed property. Large databases could spend hours 8 | # scanning! 9 | attr_accessor :never_scan 10 | attr_accessor :choose_best_index 11 | attr_accessor :search_manual_indices 12 | 13 | # Returns a new route to all graph vertices. Standard filter options. 14 | def v(*args, &block) 15 | filters = Pacer::Route.filters(self, args) 16 | if features.supportsKeyIndices or (search_manual_indices and features.supportsIndices) 17 | route = indexed_route(:vertex, filters, block) 18 | end 19 | if route 20 | route 21 | elsif never_scan 22 | fail Pacer::ClientError, "No indexed properties found among: #{ filters.property_keys.join ', ' }" 23 | else 24 | super(filters, &block) 25 | end 26 | end 27 | 28 | # Returns a new route to all graph edges. Standard filter options. 29 | def e(*args, &block) 30 | filters = Pacer::Route.edge_filters(self, args) 31 | if features.supportsKeyIndices or (search_manual_indices and features.supportsIndices) 32 | route = indexed_route(:edge, filters, block) 33 | end 34 | if route 35 | route 36 | elsif never_scan 37 | fail Pacer::ClientError, "No indexed properties found among: #{ filters.property_keys.join ', ' }" 38 | else 39 | super(filters, &block) 40 | end 41 | end 42 | 43 | private 44 | 45 | def indexed_route(element_type, filters, block) 46 | filters.graph = self 47 | filters.use_lookup! 48 | filters.indices = graph.indices 49 | filters.choose_best_index = choose_best_index != false 50 | filters.search_manual_indices = search_manual_indices 51 | idx, key, value = filters.best_index(element_type) 52 | if idx and key 53 | route = chain_route :back => self, :element_type => element_type, :filter => :index, :index => idx, :key => key, :value => value 54 | Pacer::Route.property_filter(route, filters, block) 55 | elsif filters.route_modules.any? 56 | mod = filters.route_modules.shift 57 | Pacer::Route.property_filter(mod.route(self), filters, block) 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/pacer/transform/cap.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core::SideEffect 3 | def cap(opts = {}) 4 | back.chain_route({:transform => :cap, :with => self, :element_type => :object}.merge(opts)) 5 | end 6 | end 7 | 8 | module Transform 9 | module Cap 10 | import com.tinkerpop.pipes.transform.SideEffectCapPipe 11 | 12 | def help(section = nil) 13 | case section 14 | when nil 15 | puts < # Obj-Cap(V-Counted)> 25 | r.to_a #=> [123] 26 | 27 | In this example, #counted is a side effect pipe. Side effect pipes can 28 | be used on their own but their value is not reliable until the full 29 | pipeline has been processed: 30 | 31 | pipe = g.v.counted.pipe 32 | pipe.getSideEffect #=> 0 33 | pipe.next #=> # 34 | pipe.getSideEffect #=> 1 35 | 36 | HELP 37 | else 38 | super 39 | end 40 | description 41 | end 42 | 43 | def with=(route) 44 | @side_effect = route 45 | end 46 | 47 | protected 48 | 49 | def pipe_source 50 | s, e = super 51 | if not s and not e 52 | s = e = Pacer::Pipes::IdentityPipe.new 53 | end 54 | [s, e] 55 | end 56 | 57 | def side_effect_pipe(end_pipe) 58 | old_back = @side_effect.back 59 | begin 60 | empty = Pacer::Route.empty self 61 | @side_effect.back = empty 62 | _, side_effect_pipe = @side_effect.send :build_pipeline 63 | side_effect_pipe.setStarts end_pipe if end_pipe 64 | side_effect_pipe 65 | ensure 66 | @side_effect.back = old_back 67 | end 68 | end 69 | 70 | def attach_pipe(end_pipe) 71 | pipe = SideEffectCapPipe.new side_effect_pipe(end_pipe) 72 | pipe.setStarts end_pipe if end_pipe 73 | pipe 74 | end 75 | 76 | def inspect_string 77 | "#{ inspect_class_name }(#{ @side_effect.send(:inspect_string) })" 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/pacer/pipe/loop_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | class LoopPipe < RubyPipe 3 | import java.util.ArrayList 4 | BlueprintsGraph = com.tinkerpop.blueprints.Graph 5 | 6 | def initialize(graph, looping_pipe, control_block) 7 | super() 8 | @graph = graph 9 | @control_block = control_block 10 | @wrapper = Pacer::Wrappers::WrapperSelector.build graph 11 | 12 | @expando = ExpandablePipe.new 13 | looping_pipe.setStarts(@expando) 14 | if control_block.arity < 0 or control_block.arity > 2 15 | @yield_paths = true 16 | looping_pipe.enablePath true 17 | end 18 | @looping_pipe = looping_pipe 19 | end 20 | 21 | def next 22 | super 23 | ensure 24 | @path = next_path 25 | end 26 | 27 | def setStarts(starts) 28 | super 29 | enablePath true if yield_paths 30 | end 31 | 32 | def enablePath(b) 33 | super 34 | looping_pipe.enablePath true if b and not yield_paths 35 | end 36 | 37 | protected 38 | 39 | attr_reader :wrapper, :control_block, :expando, :looping_pipe, :graph, :yield_paths 40 | attr_accessor :next_path 41 | 42 | def processNextStart 43 | while true 44 | if looping_pipe.hasNext 45 | element = looping_pipe.next 46 | depth = (expando.metadata || 0) + 1 47 | self.next_path = looping_pipe.getCurrentPath if pathEnabled 48 | else 49 | element = starts.next 50 | self.next_path = starts.getCurrentPath if pathEnabled 51 | depth = 0 52 | end 53 | wrapped = wrapper.new(graph, element) 54 | if pathEnabled 55 | path = next_path.map do |e| 56 | wrapper.new graph, e 57 | end 58 | control = control_block.call wrapped, depth, path 59 | else 60 | control = control_block.call wrapped, depth 61 | end 62 | case control 63 | when :loop 64 | expando.add element, depth, next_path 65 | when :emit 66 | return element 67 | when :emit_and_loop, :loop_and_emit 68 | expando.add element, depth, next_path 69 | return element 70 | when false, nil, :discard 71 | else 72 | expando.add element, depth, next_path 73 | return element 74 | end 75 | end 76 | end 77 | 78 | def getPathToHere 79 | path = ArrayList.new 80 | if @path 81 | @path.each do |e| 82 | path.add e 83 | end 84 | end 85 | path 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/pacer/route/mixin/route_operations_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | # New tests: 4 | Run.tg(:read_only) do 5 | use_pacer_graphml_data(:read_only) 6 | 7 | describe RouteOperations do 8 | describe '#most_frequent' do 9 | context '()' do 10 | subject { graph.v[:type].most_frequent } 11 | it { should == 'project' } 12 | end 13 | 14 | context '(0)' do 15 | subject { graph.v[:type].most_frequent(0) } 16 | it { should == 'project' } 17 | end 18 | 19 | context '(1)' do 20 | subject { graph.v[:type].most_frequent(1) } 21 | it { should == 'person' } 22 | end 23 | 24 | context '(0..1)' do 25 | subject { graph.v[:type].most_frequent(0..1) } 26 | it { should be_a Pacer::Core::Route } 27 | its(:element_type) { should == :object } 28 | its(:to_a) { should == ['project', 'person'] } 29 | end 30 | 31 | context '(0, true)' do 32 | subject { graph.v[:type].most_frequent(0, true) } 33 | it { should == ['project', 4] } 34 | end 35 | 36 | context '(1, true)' do 37 | subject { graph.v[:type].most_frequent(1, true) } 38 | it { should == ['person', 2] } 39 | end 40 | 41 | context '(0..1, true)' do 42 | subject { graph.v[:type].most_frequent(0..1, true) } 43 | it { should == [['project', 4], ['person', 2]] } 44 | end 45 | end 46 | end 47 | end 48 | 49 | Run.tg do 50 | use_pacer_graphml_data 51 | 52 | describe RouteOperations do 53 | before do 54 | setup_data 55 | end 56 | 57 | describe '#build_index' do 58 | context "('new_index', 'k') { count += 1 }", :transactions => true do 59 | it 'should build the index with raw elements' do 60 | count = 0 61 | index = graph.v.build_index('new_index', 'k', 'name') 62 | index.should_not be_nil 63 | index.all('k', 'pangloss').count.should == 1 64 | end 65 | 66 | it 'should build the index with wrapped elements' do 67 | count = 0 68 | index = graph.v(TP::Person).build_index('new_index', 'k', 'name') 69 | index.should_not be_nil 70 | index.all('k', 'pangloss').count.should == 1 71 | end 72 | 73 | it 'should do nothing if there are no elements' do 74 | count = 0 75 | index = graph.v.limit(0).build_index('new_index', 'k', 'name') 76 | index.should be_nil 77 | end 78 | 79 | after do 80 | graph.drop_index 'new_index' 81 | end 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/pacer/utils/trie.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Utils 2 | module Trie 3 | class << self 4 | def trie(graph, name) 5 | t = graph.v(self, :name => name).first 6 | if t 7 | t 8 | else 9 | graph.create_vertex self, :type => 'Trie', :name => name 10 | end 11 | end 12 | 13 | def route_conditions(graph) 14 | { :type => 'Trie' } 15 | end 16 | end 17 | 18 | module Vertex 19 | def find_word(word) 20 | find word.scan(/./) 21 | end 22 | 23 | def find(array) 24 | found = find_partial(array) 25 | if found.length == array.length 26 | found.last.in_vertex.add_extensions [Trie] 27 | end 28 | end 29 | 30 | def find_partial(array) 31 | return [] if array.empty? 32 | strings = array.map &:to_s 33 | max_depth = array.length - 1 34 | found = [] 35 | v.out_e(strings.first).loop { |e| e.in_v.out_e }.while do |e, depth| 36 | if e.label == "trie:#{strings[depth]}" and depth <= max_depth 37 | found << e 38 | :loop 39 | end 40 | end.to_a 41 | found 42 | end 43 | 44 | def add_word(word) 45 | add word.scan(/./) 46 | end 47 | 48 | def add(array) 49 | found = find_partial(array) 50 | if found.length == array.length 51 | result = found.last.in_vertex 52 | else 53 | found[array.length] ||= nil 54 | result = array.zip(found).inject(self) do |vertex, (part, edge)| 55 | if edge 56 | edge.in_vertex 57 | else 58 | new_vertex = graph.create_vertex :type => 'Trie' 59 | graph.create_edge nil, vertex, new_vertex, "trie:#{part.to_s}" 60 | new_vertex 61 | end 62 | end 63 | end 64 | result[:end] = true 65 | result.add_extensions [Trie] 66 | end 67 | 68 | def path 69 | result = [] 70 | v.in_e.chain_route(:pipe_class => Pacer::Pipes::LabelPrefixPipe, :pipe_args => 'trie:').loop do |e| 71 | e.out_v.in_e.chain_route(:pipe_class => Pacer::Pipes::LabelPrefixPipe, :pipe_args => 'trie:') 72 | end.while do |e, d| 73 | if e.out_vertex[:type] == 'Trie' 74 | :emit 75 | else 76 | :loop 77 | end 78 | end.paths.to_a.first.reverse.to_route(:element_type => :mixed, :graph => graph) 79 | end 80 | 81 | def array 82 | path.e.labels.map { |label| label[/^trie:(.*)$/, 1] }.to_a 83 | end 84 | 85 | def word 86 | array.join 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /blog/detach_benchmarks.txt: -------------------------------------------------------------------------------- 1 | sp = g.v.detach { |v| v.out_e.in_v(name: 'Joe').element_ids } 2 | spp = sp.call 3 | Benchmark.bmbm { |x| 4 | x.report('a') { 50000.times { g.v.detach { |v| v.out_e.in_v(name: 'Joe').element_ids } } } 5 | x.report('b') { 50000.times { sp.call } } 6 | x.report('c') { 50000.times { sp.call.call(v) } } 7 | x.report('d') { 50000.times { spp.call(v) } } 8 | x.report('e') { 50000.times { v.out_e.in_v(name: 'Joe').element_ids.to_a } } 9 | x.report('f') { 50000.times { v.out_edges.map { |e| e.in_vertex }.select { |z| z[:name] == 'Joe' }.map { |z| z.element_id }.first } } 10 | } 11 | 12 | Rehearsal ------------------------------------- 13 | a 7.080000 0.070000 7.150000 ( 6.729000) 14 | b 0.890000 0.010000 0.900000 ( 0.668000) 15 | c 3.530000 0.080000 3.610000 ( 1.760000) 16 | d 0.080000 0.000000 0.080000 ( 0.083000) 17 | e 7.500000 0.020000 7.520000 ( 6.873000) 18 | f 15.830000 0.030000 15.860000 ( 15.594000) 19 | --------------------------- total: 35.120000sec 20 | 21 | user system total real 22 | a 6.660000 0.010000 6.670000 ( 6.449000) 23 | b 0.660000 0.000000 0.660000 ( 0.587000) 24 | c 3.840000 0.040000 3.880000 ( 1.883000) 25 | d 0.090000 0.000000 0.090000 ( 0.083000) 26 | e 7.450000 0.030000 7.480000 ( 6.951000) 27 | f 14.720000 0.020000 14.740000 ( 14.519000) 28 | 29 | 30 | 31 | sp = g.v.detach { |v| v.out_e.in_v(name: 'Joe') } 32 | spp = sp.call g 33 | Benchmark.bmbm { |x| 34 | x.report('a') { 50000.times { g.v.detach { |v| v.out_e.in_v(name: 'Joe') } } } 35 | x.report('b') { 50000.times { sp.call } } 36 | x.report('c') { 50000.times { sp.call.call(v) } } 37 | x.report('d') { 50000.times { spp.call(v) } } 38 | x.report('e') { 50000.times { v.out_e.in_v(name: 'Joe').to_a } } 39 | x.report('f') { 50000.times { v.out_edges.map { |e| e.in_vertex }.select { |z| z[:name] == 'Joe' }.first } } 40 | } 41 | Rehearsal ------------------------------------- 42 | a 6.500000 0.060000 6.560000 ( 6.427000) 43 | b 0.920000 0.020000 0.940000 ( 0.636000) 44 | c 2.790000 0.070000 2.860000 ( 2.444000) 45 | d 1.190000 0.010000 1.200000 ( 1.089000) 46 | e 8.160000 0.020000 8.180000 ( 7.638000) 47 | f 15.970000 0.030000 16.000000 ( 15.868000) 48 | --------------------------- total: 35.740000sec 49 | 50 | user system total real 51 | a 6.250000 0.010000 6.260000 ( 6.079000) 52 | b 1.710000 0.030000 1.740000 ( 0.931000) 53 | c 2.770000 0.010000 2.780000 ( 2.301000) 54 | d 1.170000 0.010000 1.180000 ( 1.032000) 55 | e 7.950000 0.010000 7.960000 ( 7.528000) 56 | f 14.820000 0.030000 14.850000 ( 14.777000) 57 | -------------------------------------------------------------------------------- /lib/pacer/pipe/stream_sort_pipe.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Pipes 2 | # has 3 states: 3 | # - starts in rebalancing mode to build up a working set which stores 4 | # elements in 4 silos 5 | # - if the first silo becomes empty, it goes into clearing mode 6 | # - in clearing mode, it will empty all elements out of the first non-empty silo 7 | # - it then goes back into rebalancing mode 8 | # 9 | # Number of silos is 4 by default, a number I pulled out of my ass without any testing. 10 | class StreamSortPipe < RubyPipe 11 | def initialize(queue_length = 100, silo_size = 10) 12 | super() 13 | @queue_length = queue_length 14 | @rebalancing = [] 15 | @rebal_length = 0 16 | @first_silo = [] 17 | @second_silo = [] 18 | @third_silo = [] 19 | @clearing = [] 20 | @silo_size = silo_size 21 | end 22 | 23 | def setSiloSize(n) 24 | @silo_size = n 25 | end 26 | 27 | def processNextStart 28 | if @clearing.any? 29 | return @clearing.shift 30 | end 31 | while @rebal_length < @queue_length and @starts.hasNext 32 | @rebalancing << @starts.next 33 | @rebal_length += 1 34 | end 35 | if @rebalancing.any? 36 | if @starts.hasNext 37 | @rebalancing.sort! 38 | @first_silo = @rebalancing.slice(0, @silo_size) 39 | @second_silo = @rebalancing.slice(@silo_size, @silo_size * 2) || [] 40 | @third_silo = @rebalancing.slice(@silo_size * 2..-1) || [] 41 | @rebalancing = [] 42 | else 43 | @clearing = @rebalancing.sort 44 | @rebalancing = [] 45 | return processNextStart 46 | end 47 | end 48 | if @starts.hasNext 49 | if @first_silo.any? 50 | element = @starts.next 51 | begin 52 | if element < @first_silo.last 53 | @first_silo << element 54 | @first_silo.sort! 55 | elsif @second_silo.none? or element < @second_silo.last 56 | @second_silo.unshift element 57 | else 58 | @third_silo << element 59 | end 60 | rescue => e 61 | @first_silo.unshift element 62 | end 63 | return @first_silo.shift 64 | else 65 | @clearing = @second_silo 66 | @clearing.sort! 67 | @rebalancing = @third_silo 68 | @rebal_length = @rebalancing.length 69 | return @rebalancing.shift 70 | end 71 | else 72 | @clearing = @first_silo + @second_silo.sort! + @third_silo.sort! 73 | return processNextStart if @clearing.any? 74 | end 75 | raise EmptyPipe.instance 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/pacer/filter/range_filter.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def range(from, to) 5 | args = { :filter => :range } 6 | args[:begin] = from if from 7 | args[:end] = to if to 8 | chain_route args 9 | end 10 | 11 | def limit(max) 12 | chain_route :filter => :range, :limit => max 13 | end 14 | alias take limit 15 | 16 | def offset(amount) 17 | chain_route :filter => :range, :offset => amount 18 | end 19 | alias drop offset 20 | 21 | def at(pos) 22 | chain_route :filter => :range, :index => pos 23 | end 24 | end 25 | end 26 | 27 | module Filter 28 | module RangeFilter 29 | def limit(n = nil) 30 | @limit = n 31 | if range.begin == -1 32 | @range = range.begin...n 33 | else 34 | @range = range.begin...(range.begin + n) 35 | end 36 | self 37 | end 38 | 39 | def limit=(n) 40 | limit n 41 | n 42 | end 43 | 44 | def offset(n = nil) 45 | s = n 46 | s += 1 if range.begin == -1 47 | if range.end == -1 48 | @range = (range.begin + s)..-1 49 | elsif range.exclude_end? 50 | @range = (range.begin + s)...(range.end + n) 51 | else 52 | @range = (range.begin + s)..(range.end + n) 53 | end 54 | self 55 | end 56 | alias drop offset 57 | 58 | def offset=(n) 59 | offset n 60 | n 61 | end 62 | 63 | def range=(range) 64 | @range = range 65 | end 66 | 67 | def begin=(n) 68 | @range = n..range.end 69 | end 70 | 71 | def end=(n) 72 | @range = range.begin..n 73 | end 74 | 75 | def index=(index) 76 | @range = index..index 77 | end 78 | 79 | def range 80 | @range ||= -1..-1 81 | end 82 | 83 | protected 84 | 85 | def attach_pipe(end_pipe) 86 | from = @range.begin 87 | to = @range.end 88 | if @range.exclude_end? 89 | if to == 0 90 | pipe = Pacer::Pipes::NeverPipe.new 91 | pipe.set_starts end_pipe if end_pipe 92 | return pipe 93 | elsif to > 0 94 | to -= 1 95 | end 96 | end 97 | pipe = Pacer::Pipes::RangeFilterPipe.new from, to 98 | pipe.set_starts end_pipe if end_pipe 99 | pipe 100 | end 101 | 102 | def inspect_string 103 | "#{ inspect_class_name }(#{ range.inspect })" 104 | end 105 | end 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /ext/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | pacer 4 | pacer 5 | jar 6 | 0.1.0-SNAPSHOT 7 | pacer 8 | FIXME: write description 9 | http://example.com/FIXME 10 | 11 | 12 | Eclipse Public License 13 | http://www.eclipse.org/legal/epl-v10.html 14 | 15 | 16 | 17 | src/test/java 18 | 19 | 20 | org.codehaus.mojo 21 | build-helper-maven-plugin 22 | 1.7 23 | 24 | 25 | add-source 26 | generate-sources 27 | 28 | add-source 29 | 30 | 31 | 32 | dev 33 | src/java 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-surefire-plugin 42 | 2.18 43 | 44 | 45 | 46 | 47 | 48 | central 49 | https://repo1.maven.org/maven2/ 50 | 51 | false 52 | 53 | 54 | true 55 | 56 | 57 | 58 | 59 | 60 | com.tinkerpop.blueprints 61 | blueprints-core 62 | 2.6.0 63 | 64 | 65 | com.tinkerpop 66 | pipes 67 | 2.6.0 68 | 69 | 70 | junit 71 | junit 72 | 4.13.1 73 | 74 | 75 | 76 | UTF-8 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /lib/pacer/graph/yaml_encoder.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | module Pacer 4 | # This encoder was originally part of pacer-neo4j. It uses native data where 5 | # Neo4j could and for everything else it uses (slow (but easy)) 6 | # human-readable YAML encoding. 7 | class YamlEncoder 8 | def self.encode_property(value) 9 | case value 10 | when nil 11 | nil 12 | when String 13 | value = value.strip 14 | value = nil if value == '' 15 | value 16 | when Numeric 17 | if value.is_a? Bignum 18 | dump value 19 | else 20 | value.to_java 21 | end 22 | when true, false 23 | value.to_java 24 | when DateTime 25 | # rfc3339 drops the millisecond 26 | value.new_offset(0).strftime ' utcT %Y-%m-%d %H:%M:%S.%L' 27 | when Time 28 | if value.utc? 29 | value.getutc.strftime ' utcT %Y-%m-%d %H:%M:%S.%L' 30 | else 31 | value.strftime ' time %Y-%m-%d %H:%M:%S.%L %z' 32 | end 33 | when Date 34 | value.strftime ' date %Y-%m-%d' 35 | when Array 36 | if value.length == 0 37 | value_type = Fixnum 38 | else 39 | value_type = value.first.class 40 | value_type = TrueClass if value_type == FalseClass 41 | value.each do |v| 42 | if value_type != v.class or (value == true or value == false and value_type == TrueClass) 43 | value_type = nil 44 | break 45 | end 46 | end 47 | end 48 | case value_type 49 | when Fixnum 50 | value.to_java :long 51 | when Float 52 | value.to_java :double 53 | when TrueClass 54 | value.to_java :boolean 55 | when String 56 | value.to_java :string 57 | else 58 | dump value 59 | end 60 | else 61 | dump value 62 | end 63 | end 64 | 65 | def self.decode_property(value) 66 | if value.is_a? String and value[0, 1] == ' ' 67 | marker = value[1, 4] 68 | if marker == 'utcT' 69 | # FIXME: we lose the milliseconds here... 70 | DateTime.parse(value[6..-1]).to_time.utc 71 | elsif marker == 'time' 72 | DateTime.parse(value[6..-1]).to_time 73 | elsif marker == 'date' 74 | Date.parse(value[6..-1]) 75 | else 76 | YAML.load(value[1..-1]) 77 | end 78 | elsif value.is_a? ArrayJavaProxy 79 | value.to_a 80 | else 81 | value 82 | end 83 | rescue Psych::SyntaxError 84 | value 85 | end 86 | 87 | private 88 | 89 | def self.dump(value) 90 | " #{ YAML.dump value }" 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/pacer/transform/map.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def map(opts = {}, &block) 5 | chain_route({:transform => :map, :block => block, :element_type => :object, :extensions => []}.merge(opts)) 6 | end 7 | end 8 | end 9 | 10 | module Transform 11 | module Map 12 | attr_accessor :block 13 | 14 | def help(section = nil) 15 | case section 16 | when nil 17 | puts < # Obj-Map> 24 | 25 | mapped.to_a #=> [2,3,4] 26 | mapped.limit(1).to_a #=> [2] 27 | 28 | Note that the block will be called *twice* in the above example where limit(1) 29 | is applied to the route after the map is defined. Routes do some pre-processing 30 | and you can not assume that a function executed within a route will be executed 31 | the expected number of times without carefully testing your logic. 32 | 33 | Further, note that routes may be re-executed multiple times: 34 | 35 | [mapped.to_a, mapped.to_a] #=> [[2,3,4], [2,3,4]] 36 | 37 | The element_type option is frequently useful. The following looks up elements 38 | by ID in the graph and produces a fully-fledged vertices route: 39 | 40 | route = [1,2,3].to_route 41 | mapped = route.map(graph: g, element_type: :vertex) { |n| g.vertex(n) } 42 | 43 | mapped.out_e #=> # V-Map -> outE> 44 | mapped.in_e #=> # V-Map -> inE> 45 | 46 | If you want to map over a route immediately without adding a map step to it, 47 | use the synonym for #map built-in to Ruby: #collect 48 | 49 | [1,2,3].to_route.collect { |n| n + 1 } #=> [2,3,4] 50 | 51 | HELP 52 | else 53 | super 54 | end 55 | description 56 | end 57 | 58 | protected 59 | 60 | def attach_pipe(end_pipe) 61 | # Must wrap based on parent pipe because the element in the block has 62 | # not yet been affected by any of this block's transforms. 63 | if back and back.element_type == :path 64 | pf = Pacer::Wrappers::PathWrappingPipeFunction.new back, block 65 | else 66 | pf = Pacer::Wrappers::WrappingPipeFunction.new back || pacer_source, block 67 | pf = Pacer::Wrappers::UnwrappingPipeFunction.new pf 68 | end 69 | pipe = com.tinkerpop.pipes.transform.TransformFunctionPipe.new pf 70 | pipe.setStarts end_pipe if end_pipe 71 | pipe 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/pacer/utils/tsort_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Pacer::Utils::TSort do 4 | let(:graph) { Pacer.tg } 5 | 6 | context 'single vertex' do 7 | before do 8 | graph.create_vertex 9 | end 10 | let(:node) { graph.v(Pacer::Utils::TSort).first } 11 | 12 | describe 'tsort()' do 13 | subject { node.tsort.to_a } 14 | it { should == [node] } 15 | end 16 | end 17 | 18 | context 'breakfast example' do 19 | let(:breakfast) { graph.create_vertex 'breakfast' } 20 | let(:serve) { graph.create_vertex 'serve' } 21 | let(:cook) { graph.create_vertex 'cook' } 22 | let(:eggs) { graph.create_vertex 'buy eggs' } 23 | let(:bacon) { graph.create_vertex 'buy bacon' } 24 | 25 | before do 26 | [eggs, bacon].to_route(:graph => graph, :element_type => :vertex).add_edges_to :requires, cook 27 | cook.add_edges_to :requires, serve 28 | serve.add_edges_to :requires, breakfast 29 | end 30 | 31 | context 'whole meal' do 32 | subject do 33 | graph.v(Pacer::Utils::TSort).tsort.to_a 34 | end 35 | 36 | it { should == [ bacon, eggs, cook, serve, breakfast ] } 37 | 38 | it 'should be different from the default order' do 39 | should_not == graph.v.to_a 40 | end 41 | end 42 | 43 | context 'from one vertex' do 44 | subject do 45 | graph.v(Pacer::Utils::TSort).only(cook).tsort.to_a 46 | end 47 | 48 | it { should == [ bacon, eggs, cook ] } 49 | end 50 | end 51 | 52 | context 'circular' do 53 | let(:a) { graph.create_vertex 'a' } 54 | let(:b) { graph.create_vertex 'b' } 55 | let(:c) { graph.create_vertex 'c' } 56 | 57 | before do 58 | a.add_edges_to :needs, b 59 | b.add_edges_to :needs, c 60 | c.add_edges_to :needs, a 61 | end 62 | 63 | it 'should raise a TSort::Cyclic error' do 64 | proc { 65 | graph.v(Pacer::Utils::TSort).tsort 66 | }.should raise_error TSort::Cyclic 67 | end 68 | 69 | it 'can not TSort a subset of a cyclical graph' do 70 | proc { 71 | graph.v(Pacer::Utils::TSort).only([a,b]).tsort.should == [a,b] 72 | }.should raise_error TSort::Cyclic 73 | end 74 | 75 | it 'can be worked around with subgraph' do 76 | vertices = graph.v.only([a,b]).result 77 | edges = graph.e.lookahead(:min => 2) { |e| e.both_v.only(vertices) }.result 78 | subgraph = (vertices.to_a + edges.to_a).to_route(:graph => graph, :element_type => :mixed).subgraph 79 | subgraph.v(Pacer::Utils::TSort).tsort.element_ids.to_a.should == ['a', 'b'] 80 | end 81 | 82 | it 'can be sorted with a custom dependencies block' do 83 | graph.v(Pacer::Utils::TSort).dependencies { |v| v.in.except(c) }.tsort.to_a.should == 84 | [a, b, c] 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/pacer/pipes.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | # Import the Pipes and related objects that we'll be using. 3 | module Pipes 4 | # TODO: move pipe imports to the modules that actually use them. 5 | import com.tinkerpop.pipes.AbstractPipe 6 | import com.tinkerpop.pipes.IdentityPipe 7 | import com.tinkerpop.pipes.util.Pipeline 8 | import com.tinkerpop.pipes.util.PipeHelper 9 | 10 | import com.tinkerpop.pipes.filter.RandomFilterPipe 11 | import com.tinkerpop.pipes.filter.RangeFilterPipe 12 | import com.tinkerpop.pipes.filter.FilterPipe 13 | 14 | import com.tinkerpop.pipes.transform.IdPipe 15 | import com.tinkerpop.pipes.transform.PropertyPipe 16 | import com.tinkerpop.pipes.transform.PropertyMapPipe 17 | 18 | import com.xnlogic.pacer.pipes.BlackboxPipeline 19 | import com.xnlogic.pacer.pipes.EdgesPipe 20 | import com.xnlogic.pacer.pipes.CollectionFilterPipe 21 | import com.xnlogic.pacer.pipes.ExpandablePipe 22 | SimpleExpandablePipe = com.tinkerpop.pipes.util.ExpandablePipe 23 | import com.xnlogic.pacer.pipes.IdCollectionFilterPipe 24 | import com.xnlogic.pacer.pipes.IsEmptyPipe 25 | import com.xnlogic.pacer.pipes.LabelCollectionFilterPipe 26 | import com.xnlogic.pacer.pipes.IsUniquePipe 27 | import com.xnlogic.pacer.pipes.VerticesPipe 28 | import com.xnlogic.pacer.pipes.LabelPrefixPipe 29 | import com.xnlogic.pacer.pipes.NeverPipe 30 | import com.tinkerpop.pipes.util.iterators.EmptyIterator 31 | import com.tinkerpop.pipes.transform.GatherPipe 32 | 33 | IN = com.tinkerpop.blueprints.Direction::IN 34 | OUT = com.tinkerpop.blueprints.Direction::OUT 35 | BOTH = com.tinkerpop.blueprints.Direction::BOTH 36 | 37 | import com.tinkerpop.blueprints.Compare 38 | EQUAL = Compare::EQUAL 39 | NOT_EQUAL = Compare::NOT_EQUAL 40 | #GREATER_THAN, LESS_THAN, GREATER_THAN_EQUAL, LESS_THAN_EQUAL 41 | end 42 | 43 | import java.util.Iterator 44 | 45 | EmptyPipe = com.tinkerpop.pipes.util.FastNoSuchElementException 46 | Pipes::EmptyPipe = EmptyPipe 47 | end 48 | 49 | require 'pacer/pipe/ruby_pipe' 50 | 51 | require 'pacer/pipe/multi_pipe' 52 | require 'pacer/pipe/block_filter_pipe' 53 | require 'pacer/pipe/enumerable_pipe' 54 | require 'pacer/pipe/loop_pipe' 55 | require 'pacer/pipe/process_pipe' 56 | require 'pacer/pipe/visitor_pipe' 57 | require 'pacer/pipe/simple_visitor_pipe' 58 | require 'pacer/pipe/stream_sort_pipe' 59 | require 'pacer/pipe/stream_uniq_pipe' 60 | require 'pacer/pipe/type_filter_pipe' 61 | 62 | require 'pacer/pipe/property_comparison_pipe' 63 | 64 | require 'pacer/pipe/unary_transform_pipe' 65 | require 'pacer/pipe/cross_product_transform_pipe' 66 | 67 | require 'pacer/pipe/wrapping_pipe' 68 | require 'pacer/pipe/path_wrapping_pipe' 69 | require 'pacer/pipe/unwrapping_pipe' 70 | require 'pacer/pipe/naked_pipe' 71 | -------------------------------------------------------------------------------- /lib/pacer/core/graph/mixed_route.rb: -------------------------------------------------------------------------------- 1 | module Pacer::Core::Graph 2 | 3 | # Basic methods for routes that may contain both vertices and edges. That can 4 | # happen as the result of a branched route, for example. 5 | module MixedRoute 6 | # Pass through only vertices. 7 | def v(*args, &block) 8 | route = chain_route :element_type => :vertex, 9 | :pipe_class => Pacer::Pipes::TypeFilterPipe, 10 | :pipe_args => Pacer::Vertex, 11 | :wrapper => wrapper, 12 | :extensions => extensions 13 | Pacer::Route.property_filter(route, args, block, true) 14 | end 15 | 16 | # Pass through only edges. 17 | def e(*args, &block) 18 | route = chain_route :element_type => :edge, 19 | :pipe_class => Pacer::Pipes::TypeFilterPipe, 20 | :pipe_args => Pacer::Edge, 21 | :wrapper => wrapper, 22 | :extensions => extensions 23 | Pacer::Route.property_filter(route, args, block, true) 24 | end 25 | 26 | def filter(*args, &block) 27 | mixed(*args, &block) 28 | end 29 | 30 | def mixed(*args, &block) 31 | route = chain_route :pipe_class => Pacer::Pipes::IdentityPipe 32 | Pacer::Route.property_filter(route, args, block, true) 33 | end 34 | 35 | # Out edges from matching vertices. 36 | def out_e(*args, &block) 37 | v.out_e(*args, &block) 38 | end 39 | 40 | # In edges from matching vertices. 41 | def in_e(*args, &block) 42 | v.in_e(*args, &block) 43 | end 44 | 45 | # All edges from matching vertices. 46 | def both_e(*args, &block) 47 | v.both_e(*args, &block) 48 | end 49 | 50 | # Out vertices from matching edges. 51 | def out_v(*args, &block) 52 | e.out_v(*args, &block) 53 | end 54 | 55 | # In vertices from matching edges. 56 | def in_v(*args, &block) 57 | e.in_v(*args, &block) 58 | end 59 | 60 | # All vertices from matching edges. 61 | def both_v(*args, &block) 62 | e.both_v(*args, &block) 63 | end 64 | 65 | # Return an iterator of or yield all labels on matching edges. 66 | def labels(&block) 67 | e.labels(&block) 68 | end 69 | 70 | def element_type 71 | :mixed 72 | end 73 | 74 | # Calculate and save result. 75 | def result(name = nil) 76 | ids = collect do |element| 77 | if element.is_a? Pacer::Vertex 78 | [:vertex, element.element_id] 79 | else 80 | [:edge, element.element_id] 81 | end 82 | end 83 | args = { 84 | :graph => graph, 85 | :element_type => :mixed, 86 | :extensions => extensions, 87 | :info => [name, info].compact.join(':') 88 | } 89 | ids.to_route(:info => "#{ ids.count } ids").map(args) do |method, id| 90 | graph.send(method, id) 91 | end 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /ext/src/test/java/com/xnlogic/pacer/pipes/IdCollectionFilterPipeTest.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import static org.junit.Assert.*; 4 | import org.junit.Test; 5 | import org.junit.Before; 6 | import org.junit.After; 7 | import com.tinkerpop.blueprints.Contains; 8 | import com.tinkerpop.blueprints.Vertex; 9 | import com.tinkerpop.blueprints.impls.tg.TinkerGraph; 10 | import java.util.Arrays; 11 | import java.util.NoSuchElementException; 12 | import com.xnlogic.pacer.pipes.IdCollectionFilterPipe; 13 | 14 | public class IdCollectionFilterPipeTest { 15 | private TinkerGraph graph = null; 16 | 17 | @Before 18 | public void setup() throws Exception { 19 | this.graph = new TinkerGraph(); 20 | } 21 | 22 | @After 23 | public void teardown() throws Exception { 24 | this.graph.shutdown(); 25 | this.graph = null; 26 | } 27 | 28 | @Test 29 | public void containsInTest() { 30 | IdCollectionFilterPipe idCollectionFilterPipe = 31 | new IdCollectionFilterPipe(Arrays.asList("1", "2", "3", "4"), Contains.IN); 32 | 33 | Vertex v1 = this.graph.addVertex("1"); 34 | Vertex v2 = this.graph.addVertex("2"); 35 | Vertex v3 = this.graph.addVertex("5"); 36 | 37 | idCollectionFilterPipe.setStarts(Arrays.asList(v1, v2, v3)); 38 | 39 | Vertex v = idCollectionFilterPipe.next(); 40 | assertTrue(v.getId().equals("1")); 41 | 42 | v = idCollectionFilterPipe.next(); 43 | assertTrue(v.getId().equals("2")); 44 | 45 | boolean hasEx = false; 46 | 47 | try { 48 | v = idCollectionFilterPipe.next(); 49 | } catch (NoSuchElementException nsee) { 50 | hasEx = true; 51 | } 52 | 53 | assertTrue(hasEx); 54 | } 55 | 56 | @Test 57 | public void containsNotInTest() { 58 | IdCollectionFilterPipe idCollectionFilterPipe = 59 | new IdCollectionFilterPipe(Arrays.asList("1", "2", "3", "4"), Contains.NOT_IN); 60 | 61 | Vertex v1 = this.graph.addVertex("7"); 62 | Vertex v2 = this.graph.addVertex("8"); 63 | Vertex v3 = this.graph.addVertex("1"); 64 | 65 | idCollectionFilterPipe.setStarts(Arrays.asList(v1, v2, v3)); 66 | 67 | Vertex v = idCollectionFilterPipe.next(); 68 | assertTrue(v.getId().equals("7")); 69 | 70 | v = idCollectionFilterPipe.next(); 71 | assertTrue(v.getId().equals("8")); 72 | 73 | boolean hasEx = false; 74 | 75 | try { 76 | v = idCollectionFilterPipe.next(); 77 | } catch (NoSuchElementException nsee) { 78 | hasEx = true; 79 | } 80 | 81 | assertTrue(hasEx); 82 | } 83 | 84 | // TODO: Lookup "Contains" and see if there are more than just the two values in the enum. 85 | } 86 | -------------------------------------------------------------------------------- /lib/pacer/filter/property_filter/edge_filters.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Filter 3 | module PropertyFilter 4 | class EdgeFilters < Filters 5 | attr_accessor :labels 6 | 7 | protected 8 | 9 | attr_accessor :non_ext_labels 10 | 11 | public 12 | 13 | def initialize(graph, filters) 14 | @best_index = nil 15 | self.labels = [] 16 | self.non_ext_labels = [] 17 | super 18 | end 19 | 20 | def add_filter(filter, extension) 21 | case filter 22 | when String, Symbol 23 | reset_properties 24 | self.non_ext_labels << filter 25 | self.labels << filter.to_s 26 | else 27 | super 28 | end 29 | end 30 | 31 | def build_pipeline(route, start_pipe = nil, pipe = nil) 32 | pipe ||= start_pipe 33 | if labels.any? 34 | label_pipe = Pacer::Pipes::LabelCollectionFilterPipe.new labels.map(&:to_s) 35 | label_pipe.set_starts pipe if pipe 36 | Pacer.debug_pipes << { :name => labels.inspect, :start => pipe, :end => block_pipe } if Pacer.debug_pipes 37 | pipe = label_pipe 38 | start_pipe ||= pipe 39 | end 40 | super(route, start_pipe, pipe) 41 | end 42 | 43 | def any? 44 | labels.any? or super 45 | end 46 | 47 | def extensions_only? 48 | labels.empty? and super 49 | end 50 | 51 | def to_s 52 | if labels.any? 53 | [labels.map { |l| l.to_sym.inspect }.join(', '), super].reject { |s| s == '' }.join ', ' 54 | else 55 | super 56 | end 57 | end 58 | 59 | def best_index(route) 60 | index, key, value = find_best_index(route) 61 | if key == 'label' 62 | labels.delete value 63 | end 64 | super 65 | end 66 | 67 | protected 68 | 69 | def reset_properties 70 | @encoded_properties = nil 71 | if @best_index 72 | # put removed index label back... 73 | i, k, v = @best_index 74 | labels << v if k == 'label' 75 | end 76 | super 77 | end 78 | 79 | def find_best_index(route) 80 | super do |avail, index_options| 81 | labels.each do |label| 82 | if idxs = avail["key:label"] 83 | if choose_best_index 84 | idxs.each do |idx| 85 | index_options << [idx.count('label', label), idx, k, v] 86 | end 87 | else 88 | return @best_index = [idxs.first, 'label', label] 89 | end 90 | end 91 | end 92 | end 93 | end 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /spec/pacer/transform/path_tree_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | Run.tg :read_only do 4 | use_pacer_graphml_data :read_only 5 | 6 | describe Pacer::Transform::PathTree do 7 | let(:first) do 8 | graph.v(type: 'person').payload do |p| 9 | { pos: 'first', 10 | type: p[:type], 11 | name: p[:name] } 12 | end.lookahead { |v| v.out_e.in_v(type: 'project') } 13 | end 14 | let(:second) do 15 | first.out_e.in_v(type: 'project').payload do |prj| 16 | { pos: 'second', 17 | type: 'project', 18 | name: prj[:name] } 19 | end 20 | end 21 | let(:paths) { second.paths } 22 | let(:payloads) { paths.payloads } 23 | let(:compacted) { payloads.compacted } 24 | 25 | describe 'basic paths' do 26 | subject { paths } 27 | 28 | its(:count) { should == 4 } 29 | 30 | it 'should have 3 elements' do 31 | paths.each { |x| x.length.should == 3 } 32 | end 33 | end 34 | 35 | describe 'tree of paths' do 36 | subject { paths.tree } 37 | 38 | its(:count) { should == first.count } 39 | its(:first) { should be_a Array } 40 | 41 | it 'should be a tree' do 42 | (pangloss, wrote, pacer), (a2, e2, b2), (_, e3, b3), (_, e4, b4) = paths.to_a 43 | subject.to_a.should == [ 44 | [pangloss, [wrote, [pacer]]], 45 | [a2, [e2, [b2]], 46 | [e3, [b3]], 47 | [e4, [b4]]] 48 | ] 49 | end 50 | end 51 | 52 | describe 'tree of payloads' do 53 | subject { payloads.tree } 54 | 55 | its(:count) { should == first.count } 56 | its(:first) { should be_a Array } 57 | 58 | it 'should be a tree' do 59 | (pangloss, wrote, pacer), (a2, _, b2), (_, _, b3), (_, _, b4) = payloads.to_a 60 | subject.to_a.should == [ 61 | [pangloss, [wrote, [pacer]]], 62 | [a2, [nil, [b2], 63 | [b3], 64 | [b4]]] 65 | ] 66 | end 67 | end 68 | 69 | describe 'tree of compacted payloads' do 70 | subject { compacted.tree } 71 | 72 | its(:count) { should == first.count } 73 | its(:first) { should be_a Array } 74 | 75 | it 'should be a tree' do 76 | (pangloss, pacer), (a2, b2), (_, b3), (_, b4) = compacted.to_a 77 | subject.to_a.should == [ 78 | [pangloss, [pacer]], 79 | [a2, [b2], 80 | [b3], 81 | [b4]] 82 | ] 83 | end 84 | end 85 | 86 | describe 'tree of compacted payloads by type' do 87 | subject { compacted.tree { |a, b| a[:type] == b[:type] } } 88 | 89 | it 'should be a tree, taking the first match' do 90 | pangloss, pacer = compacted.first 91 | subject.to_a.should == [ 92 | [pangloss, [pacer]] 93 | ] 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/pacer/filter/property_filter.rb: -------------------------------------------------------------------------------- 1 | require 'pacer/filter/property_filter/filters' 2 | require 'pacer/filter/property_filter/edge_filters' 3 | 4 | module Pacer 5 | class Route 6 | class << self 7 | def filters(graph, filters) 8 | if filters? filters 9 | filters 10 | elsif filters? filters.first 11 | filters.first 12 | else 13 | Pacer::Filter::PropertyFilter::Filters.new(graph, filters) 14 | end 15 | end 16 | 17 | def edge_filters(graph, filters) 18 | if filters? filters 19 | filters 20 | elsif filters? filters.first 21 | filters.first 22 | else 23 | Pacer::Filter::PropertyFilter::EdgeFilters.new(graph, filters) 24 | end 25 | end 26 | 27 | def filters?(filters) 28 | filters.is_a? Pacer::Filter::PropertyFilter::Filters 29 | end 30 | 31 | def property_filter(base, args, block, strict = false) 32 | filters = Pacer::Route.edge_filters(base.graph, args) 33 | filters.use_lookup! if strict 34 | filters.blocks = [block] if block 35 | args = chain_args(filters) 36 | if filters.extensions_only? and base.is_a? Route 37 | base.chain_route(args) 38 | elsif filters.any? 39 | base.chain_route(args.merge!(filter: :property, filters: filters)) 40 | elsif base.is_a? Pacer::Wrappers::ElementWrapper 41 | base.chain_route({}) 42 | else 43 | base 44 | end 45 | end 46 | 47 | def chain_args(filters) 48 | if filters.wrapper or (filters.extensions and not filters.extensions.empty?) 49 | { extensions: filters.extensions, wrapper: filters.wrapper } 50 | else 51 | {} 52 | end 53 | end 54 | end 55 | end 56 | 57 | module Filter 58 | module PropertyFilter 59 | #import com.tinkerpop.pipes.filter.LabelCollectionFilterPipe 60 | import com.tinkerpop.pipes.filter.PropertyFilterPipe 61 | 62 | def filters=(f) 63 | if f.is_a? Filters 64 | @filters = f 65 | else 66 | @filters = EdgeFilters.new(graph, f) 67 | end 68 | end 69 | 70 | # Return an array of filter options for the current route. 71 | def filters 72 | @filters ||= EdgeFilters.new(graph, nil) 73 | end 74 | 75 | def block=(block) 76 | if block 77 | filters.blocks = [block] 78 | else 79 | filters.blocks = [] 80 | end 81 | end 82 | 83 | def block 84 | filters.blocks.first 85 | end 86 | 87 | protected 88 | 89 | def build_pipeline 90 | filters.build_pipeline(self, *pipe_source) 91 | end 92 | 93 | def inspect_string 94 | if filters.any? 95 | "#{inspect_class_name}(#{filters})" 96 | else 97 | inspect_class_name 98 | end 99 | end 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/pacer/filter/object_filter.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def is(value) 5 | if value.is_a? Symbol 6 | chain_route filter: Pacer::Filter::SectionFilter, section: value 7 | else 8 | chain_route filter: Pacer::Filter::ObjectFilter, value: value 9 | end 10 | end 11 | 12 | def is_not(value) 13 | if value.is_a? Symbol 14 | chain_route filter: Pacer::Filter::SectionFilter, section: value, negate: true 15 | else 16 | chain_route filter: Pacer::Filter::ObjectFilter, value: value, negate: true 17 | end 18 | end 19 | 20 | def compact 21 | is_not nil 22 | end 23 | end 24 | end 25 | 26 | module Filter 27 | module ObjectFilter 28 | import com.tinkerpop.pipes.filter.ObjectFilterPipe 29 | 30 | attr_accessor :value, :negate 31 | 32 | protected 33 | 34 | def attach_pipe(end_pipe) 35 | obj = if value.respond_to?(:element) then value.element else value end 36 | pipe = ObjectFilterPipe.new(obj, negate ? Pacer::Pipes::NOT_EQUAL : Pacer::Pipes::EQUAL) 37 | pipe.set_starts end_pipe if end_pipe 38 | pipe 39 | end 40 | 41 | def inspect_string 42 | if negate 43 | "is_not(#{ value.inspect })" 44 | else 45 | "is(#{ value.inspect })" 46 | end 47 | end 48 | end 49 | 50 | module SectionFilter 51 | # VisitsSection module provides: 52 | # section= 53 | # section_visitor 54 | include Pacer::Visitors::VisitsSection 55 | 56 | attr_accessor :negate 57 | 58 | def attach_pipe(end_pipe) 59 | pipe = FilterSectionPipe.new(section_visitor, negate) 60 | pipe.setStarts end_pipe if end_pipe 61 | pipe 62 | end 63 | 64 | def inspect_class_name 65 | if negate 66 | "is_not(#{section.inspect})" 67 | else 68 | "is(#{section.inspect})" 69 | end 70 | end 71 | 72 | 73 | class FilterSectionPipe < Pacer::Pipes::RubyPipe 74 | attr_reader :section, :negate 75 | attr_accessor :other 76 | 77 | def initialize(section, negate) 78 | super() 79 | @section = section 80 | @negate = negate 81 | section.visitor = self if section 82 | end 83 | 84 | def processNextStart 85 | value = starts.next 86 | if negate 87 | while value == other 88 | value = starts.next 89 | end 90 | else 91 | while value != other 92 | value = starts.next 93 | end 94 | end 95 | value 96 | end 97 | 98 | def on_raw_element(x) 99 | self.other = x 100 | end 101 | 102 | def reset 103 | self.other = nil 104 | super 105 | end 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/pacer/transform/gather_section.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Routes 3 | module RouteOperations 4 | def gather_section(section = nil, opts = {}) 5 | wrapper = Pacer::Wrappers::WrapperSelector.build graph, element_type, extensions 6 | chain_route opts.merge(element_type: :array, transform: :gather_section, 7 | section: section, build_wrapper: wrapper) 8 | end 9 | end 10 | end 11 | 12 | module Transform 13 | module GatherSection 14 | # VisitsSection module provides: 15 | # section= 16 | # section_visitor 17 | include Pacer::Visitors::VisitsSection 18 | 19 | attr_accessor :build_wrapper 20 | 21 | def to_id_hash 22 | id_pairs = paths.pairs(-2, -1).map(element_type: :array) do |(k, v)| 23 | [k.element_id, v.map { |e| e.element_id }] 24 | end 25 | Hash[*id_pairs.flatten] 26 | end 27 | 28 | def to_hash 29 | Hash[*paths.pairs(-2, -1).flatten] 30 | end 31 | 32 | protected 33 | 34 | def attach_pipe(end_pipe) 35 | # TODO: wrap gathered vertices 36 | pipe = GatherSectionPipe.new(section_visitor, graph, build_wrapper) 37 | pipe.setStarts end_pipe if end_pipe 38 | pipe 39 | end 40 | 41 | class GatherSectionPipe < Pacer::Pipes::RubyPipe 42 | attr_reader :to_emit, :collecting, :collecting_path, :section, :graph, :wrapper 43 | attr_reader :getPathToHere 44 | 45 | def initialize(visitor_pipe, graph, wrapper) 46 | super() 47 | @collecting = [] 48 | @graph = graph 49 | @wrapper = wrapper 50 | @visitor_pipe = visitor_pipe 51 | if visitor_pipe 52 | visitor_pipe.visitor = self 53 | end 54 | end 55 | 56 | def processNextStart 57 | if pathEnabled 58 | while !to_emit 59 | e = wrapper.new(graph, starts.next) 60 | collecting << e 61 | @collecting_path = @visitor_pipe.getCurrentPath 62 | end 63 | else 64 | while !to_emit 65 | e = wrapper.new(graph, starts.next) 66 | collecting << e 67 | end 68 | end 69 | if !to_emit 70 | raise EmptyPipe.instance 71 | else 72 | emit 73 | end 74 | rescue EmptyPipe, java.util.NoSuchElementException 75 | if collecting 76 | @collecting = nil 77 | emit 78 | else 79 | raise EmptyPipe.instance 80 | end 81 | end 82 | 83 | def emit 84 | e = to_emit 85 | @to_emit = nil 86 | e 87 | end 88 | 89 | def after_element 90 | unless collecting.empty? 91 | @to_emit = collecting 92 | @getPathToHere = collecting_path 93 | @collecting = [] 94 | end 95 | end 96 | end 97 | end 98 | end 99 | end 100 | 101 | -------------------------------------------------------------------------------- /ext/src/test/java/com/xnlogic/pacer/pipes/VerticesPipeTest.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import static org.junit.Assert.*; 4 | import org.junit.Test; 5 | import org.junit.Before; 6 | import org.junit.After; 7 | import com.tinkerpop.blueprints.impls.tg.TinkerGraph; 8 | import com.tinkerpop.blueprints.Graph; 9 | import com.tinkerpop.blueprints.Vertex; 10 | import java.util.Collection; 11 | import java.util.Arrays; 12 | import java.util.ArrayList; 13 | import com.xnlogic.pacer.pipes.VerticesPipe; 14 | 15 | public class VerticesPipeTest { 16 | private TinkerGraph graph = null; 17 | private Collection graphs; 18 | private Collection verticesThatCount; 19 | 20 | @Before 21 | public void setup() throws Exception { 22 | this.graph = new TinkerGraph(); 23 | this.graphs = Arrays.asList((Graph)this.graph); 24 | } 25 | 26 | private void createVertices() { 27 | Vertex v1 = this.graph.addVertex(1); 28 | Vertex v2 = this.graph.addVertex(2); 29 | Vertex v3 = this.graph.addVertex(3); 30 | 31 | this.verticesThatCount = Arrays.asList(v1, v2, v3); 32 | } 33 | 34 | @After 35 | public void teardown() throws Exception { 36 | this.graph.shutdown(); 37 | this.graph = null; 38 | } 39 | 40 | @Test 41 | public void getVerticesFromGraphTest() { 42 | this.createVertices(); 43 | 44 | VerticesPipe verticesPipe = new VerticesPipe(); 45 | verticesPipe.setStarts(this.graphs); 46 | 47 | Collection vertices = new ArrayList(); 48 | 49 | while (verticesPipe.hasNext()) { 50 | vertices.add(verticesPipe.next()); 51 | } 52 | 53 | assertEquals(3, vertices.size()); 54 | assertTrue(vertices.containsAll(this.verticesThatCount)); 55 | } 56 | 57 | @Test 58 | public void getVerticesFromGraphAfterResetTest() { 59 | this.createVertices(); 60 | 61 | VerticesPipe verticesPipe = new VerticesPipe(); 62 | verticesPipe.setStarts(this.graphs); 63 | 64 | Collection vertices = new ArrayList(); 65 | 66 | while (verticesPipe.hasNext()) { 67 | vertices.add(verticesPipe.next()); 68 | } 69 | 70 | assertEquals(3, vertices.size()); 71 | assertTrue(vertices.containsAll(this.verticesThatCount)); 72 | 73 | verticesPipe.reset(); 74 | vertices.clear(); 75 | 76 | while (verticesPipe.hasNext()) { 77 | vertices.add(verticesPipe.next()); 78 | } 79 | 80 | assertEquals(3, vertices.size()); 81 | assertTrue(vertices.containsAll(this.verticesThatCount)); 82 | } 83 | 84 | @Test 85 | public void getNoVerticesFromGraphTest() { 86 | VerticesPipe verticesPipe = new VerticesPipe(); 87 | verticesPipe.setStarts(this.graphs); 88 | 89 | Collection vertices = new ArrayList(); 90 | 91 | while (verticesPipe.hasNext()) { 92 | vertices.add(verticesPipe.next()); 93 | } 94 | 95 | assertEquals(0, vertices.size()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/pacer/transform/path_tree.rb: -------------------------------------------------------------------------------- 1 | module Pacer 2 | module Core 3 | module Graph 4 | module PathRoute 5 | # Transform raw paths to a tree: 6 | # [a b c] 7 | # [a b d] 8 | # [a e f] 9 | # [a e g] 10 | # -- becomes -- 11 | # [a [b [c] 12 | # [d]] 13 | # [e [f] 14 | # [g]]] 15 | 16 | # The default comparator block is { |prev, current| prev == current } 17 | def tree(&block) 18 | wrapped.chain_route transform: :path_tree, element_type: :array, compare: block 19 | end 20 | end 21 | end 22 | end 23 | 24 | module Transform 25 | module PathTree 26 | attr_accessor :compare 27 | 28 | protected 29 | 30 | def attach_pipe(end_pipe) 31 | pipe = PathTreePipe.new compare 32 | pipe.setStarts end_pipe if end_pipe 33 | pipe 34 | end 35 | 36 | class PathTreePipe < Pacer::Pipes::RubyPipe 37 | def initialize(compare = nil) 38 | super() 39 | self.building_path = nil 40 | self.prev_path = nil 41 | self.compare = compare || proc { |a, b| a == b } 42 | end 43 | 44 | # NOTE: doesn't handle variable length paths yet... 45 | def processNextStart() 46 | while true 47 | path = starts.next 48 | if building_path 49 | if compare.call path.first, building_path.first 50 | add_path path 51 | else 52 | return next_path(path) 53 | end 54 | else 55 | next_path(path) 56 | end 57 | end 58 | rescue Pacer::EmptyPipe, java.util.NoSuchElementException 59 | if building_path 60 | r = building_path 61 | self.building_path = nil 62 | r 63 | else 64 | raise EmptyPipe.instance 65 | end 66 | end 67 | 68 | private 69 | 70 | attr_accessor :building_path, :prev_path, :compare 71 | 72 | def make(path) 73 | path.reverse.inject(nil) do |inner, e| 74 | if inner 75 | [e, inner] 76 | else 77 | [e] 78 | end 79 | end 80 | end 81 | 82 | def add_path(path) 83 | working = building_path 84 | (1..path.length - 1).each do |pos| 85 | current = path[pos] 86 | prev = prev_path[pos] 87 | if compare.call current, prev 88 | working = working.last 89 | else 90 | if pos < path.length 91 | working << make(path[pos..-1]) 92 | else 93 | working << [current] 94 | end 95 | break 96 | end 97 | end 98 | self.prev_path = path 99 | end 100 | 101 | def next_path(path) 102 | finished = building_path 103 | self.building_path = make path 104 | self.prev_path = path 105 | finished 106 | end 107 | end 108 | end 109 | end 110 | end 111 | 112 | -------------------------------------------------------------------------------- /ext/src/main/java/com/xnlogic/pacer/pipes/ExpandablePipe.java: -------------------------------------------------------------------------------- 1 | package com.xnlogic.pacer.pipes; 2 | 3 | import com.tinkerpop.pipes.AbstractPipe; 4 | import com.tinkerpop.pipes.Pipe; 5 | 6 | import java.util.Iterator; 7 | import java.util.NoSuchElementException; 8 | import java.util.Queue; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.ArrayList; 12 | 13 | public class ExpandablePipe extends AbstractPipe { 14 | private Queue queue; 15 | private Object metadata; 16 | private Object nextMetadata; 17 | 18 | private List path; 19 | private List nextPath; 20 | 21 | public ExpandablePipe() { 22 | this.queue = new LinkedList(); 23 | 24 | // By default, initialize this pipe with an empty iterator 25 | setStarts(new Iterator() { 26 | public boolean hasNext() {return false;} 27 | public T next() {throw new NoSuchElementException();} 28 | public void remove() {} 29 | }); 30 | } 31 | 32 | public void add(T element, Object metadata, List path) { 33 | this.queue.add(new EPTriple(element, metadata, path)); 34 | } 35 | 36 | public void add(T element, Object metadata) { 37 | this.add(element, metadata, null); 38 | } 39 | 40 | public void add(T element) { 41 | this.add(element, null, null); 42 | } 43 | 44 | public Object getMetadata() { 45 | return this.metadata; 46 | } 47 | 48 | public T next() { 49 | T toReturn = null; 50 | 51 | try { 52 | toReturn = super.next(); 53 | } finally { 54 | this.path = this.nextPath; 55 | this.metadata = this.nextMetadata; 56 | } 57 | 58 | return toReturn; 59 | } 60 | 61 | @SuppressWarnings("unchecked") 62 | protected T processNextStart() { 63 | if (this.queue.isEmpty()) { 64 | this.nextMetadata = null; 65 | T r = this.starts.next(); 66 | 67 | if (this.pathEnabled && this.starts instanceof Pipe) { 68 | this.nextPath = ((Pipe)this.starts).getCurrentPath(); 69 | } else { 70 | this.nextPath = new ArrayList(); 71 | } 72 | return r; 73 | } else { 74 | EPTriple triple = this.queue.remove(); 75 | this.nextMetadata = triple.metadata; 76 | this.nextPath = triple.path; 77 | return triple.element; 78 | } 79 | } 80 | 81 | public List getPathToHere() { 82 | List path = new ArrayList(); 83 | 84 | if (this.path != null) { 85 | for (Object p : this.path) { 86 | path.add(p); 87 | } 88 | } 89 | 90 | return path; 91 | } 92 | 93 | private class EPTriple { 94 | public T element; 95 | public Object metadata; 96 | public List path; 97 | 98 | public EPTriple(T element, Object metadata, List path) { 99 | this.element = element; 100 | this.metadata = metadata; 101 | this.path = path; 102 | } 103 | } 104 | } 105 | --------------------------------------------------------------------------------