<commit_before>module Neo4j::Server

  # Plugin
  Neo4j::Session.register_db(:server_db) do |*url_opts|
    Neo4j::Server::CypherSession.open(*url_opts)
  end

  class CypherSession < Neo4j::Session
    include Resource
    include Neo4j::Core::CypherTranslator

    alias_method :super_query, :query
    attr_reader :connection

    # @param [Hash] params could be empty or contain basic authentication user and password
    # @return [Faraday]
    # @see https://github.com/lostisland/faraday
    def self.create_connection(params)
      init_params = params[:initialize] and params.delete(:initialize)
      conn = Faraday.new(init_params) do |b|
        b.request :basic_auth, params[:basic_auth][:username], params[:basic_auth][:password] if params[:basic_auth]
        b.request :json
        #b.response :logger
        b.response :json, :content_type => "application/json"
        #b.use Faraday::Response::RaiseError
        b.use Faraday::Adapter::NetHttpPersistent
        # b.adapter  Faraday.default_adapter
      end
      conn.headers = {'Content-Type' => 'application/json', 'User-Agent' => ::Neo4j::Session.user_agent_string}
      conn
    end

    # Opens a session to the database
    # @see Neo4j::Session#open
    #
    # @param [String] endpoint_url - the url to the neo4j server, defaults to 'http://localhost:7474'
    # @param [Hash] params faraday params, see #create_connection or an already created faraday connection
    def self.open(endpoint_url=nil, params = {})
      extract_basic_auth(endpoint_url, params)
      connection = params[:connection] || create_connection(params)
      url = endpoint_url || 'http://localhost:7474'
      response = connection.get(url)
      raise "Server not available on #{url} (response code #{response.status})" unless response.status == 200

      root_data = response.body
      data_url = root_data['data']
      data_url << '/' unless data_url.end_with?('/')

      CypherSession.new(data_url, connection)
    end

    def self.extract_basic_auth(url, params)
      return unless url && URI(url).userinfo
      params[:basic_auth] = {
        username: URI(url).user,
        password: URI(url).password
      }
    end
    private_class_method :extract_basic_auth

    def initialize(data_url, connection)
      @connection = connection
      Neo4j::Session.register(self)
      initialize_resource(data_url)
      Neo4j::Session._notify_listeners(:session_available, self)
    end

    def db_type
      :server_db
    end

    def to_s
      "#{self.class} url: '#{@resource_url}'"
    end

    def inspect
      "#{to_s} version: '#{version}'"
    end

    def version
      resource_data ? resource_data['neo4j_version'] : ''
    end

    def initialize_resource(data_url)
      response = @connection.get(data_url)
      expect_response_code(response,200)
      data_resource = response.body
      raise "No data_resource for #{response.body}" unless data_resource
      # store the resource data
      init_resource_data(data_resource, data_url)
    end

    def close
      super
      Neo4j::Transaction.unregister_current
    end

    def begin_tx
      if Neo4j::Transaction.current
        # Handle nested transaction "placebo transaction"
        Neo4j::Transaction.current.push_nested!
      else
        wrap_resource(self, 'transaction', CypherTransaction, :post, @connection)
      end
      Neo4j::Transaction.current
    end

    def create_node(props = nil, labels = [])
      CypherNode.new self, _query_or_fail(cypher_string(labels, props), true, cypher_prop_list(props))
    end

    def load_node(neo_id)
      cypher_response = _query("MATCH (n) WHERE ID(n) = #{neo_id} RETURN n")
      load_entity(CypherNode, cypher_response)
    end

    def load_relationship(neo_id)
      cypher_response = _query("MATCH (n)-[r]-() WHERE ID(r) = #{neo_id} RETURN r")
      load_entity(CypherRelationship, cypher_response)
    end

    def load_entity(clazz, cypher_response)
      return nil if cypher_response.data.nil? || cypher_response.data[0].nil?
      data  = if cypher_response.is_transaction_response?
                cypher_response.rest_data_with_id
              else
                cypher_response.first_data
              end

      if cypher_response.error?
        cypher_response.raise_error
      elsif cypher_response.error_msg =~ /not found/  # Ugly that the Neo4j API gives us this error message
        return nil
      else
        clazz.new(self, data)
      end
    end

    def create_label(name)
      CypherLabel.new(self, name)
    end

    def uniqueness_constraints(label)
      response = @connection.get("#{@resource_url}schema/constraint/#{label}/uniqueness")
      expect_response_code(response, 200)
      data_resource = response.body

      property_keys = data_resource.map do |row|
        row['property_keys'].map(&:to_sym)
      end

      {
          property_keys: property_keys
      }
    end

    def indexes(label)
      response = @connection.get("#{@resource_url}schema/index/#{label}")
      expect_response_code(response, 200)
      data_resource = response.body

      property_keys = data_resource.map do |row|
        row['property_keys'].map(&:to_sym)
      end

      {
          property_keys: property_keys
      }
    end

    def find_all_nodes(label_name)
      response = _query_or_fail("MATCH (n:`#{label_name}`) RETURN ID(n)")
      search_result_to_enumerable_first_column(response)
    end
<commit_msg>a little DRY and performance tweaks nobody will ever notice<commit_after>module Neo4j::Server

  # Plugin
  Neo4j::Session.register_db(:server_db) do |*url_opts|
    Neo4j::Server::CypherSession.open(*url_opts)
  end

  class CypherSession < Neo4j::Session
    include Resource
    include Neo4j::Core::CypherTranslator

    alias_method :super_query, :query
    attr_reader :connection

    # @param [Hash] params could be empty or contain basic authentication user and password
    # @return [Faraday]
    # @see https://github.com/lostisland/faraday
    def self.create_connection(params)
      init_params = params[:initialize] and params.delete(:initialize)
      conn = Faraday.new(init_params) do |b|
        b.request :basic_auth, params[:basic_auth][:username], params[:basic_auth][:password] if params[:basic_auth]
        b.request :json
        #b.response :logger
        b.response :json, :content_type => "application/json"
        #b.use Faraday::Response::RaiseError
        b.use Faraday::Adapter::NetHttpPersistent
        # b.adapter  Faraday.default_adapter
      end
      conn.headers = {'Content-Type' => 'application/json', 'User-Agent' => ::Neo4j::Session.user_agent_string}
      conn
    end

    # Opens a session to the database
    # @see Neo4j::Session#open
    #
    # @param [String] endpoint_url - the url to the neo4j server, defaults to 'http://localhost:7474'
    # @param [Hash] params faraday params, see #create_connection or an already created faraday connection
    def self.open(endpoint_url=nil, params = {})
      extract_basic_auth(endpoint_url, params)
      connection = params[:connection] || create_connection(params)
      url = endpoint_url || 'http://localhost:7474'
      response = connection.get(url)
      raise "Server not available on #{url} (response code #{response.status})" unless response.status == 200

      root_data = response.body
      data_url = root_data['data']
      data_url << '/' unless data_url.end_with?('/')

      CypherSession.new(data_url, connection)
    end

    def self.extract_basic_auth(url, params)
      return unless url && URI(url).userinfo
      params[:basic_auth] = {
        username: URI(url).user,
        password: URI(url).password
      }
    end
    private_class_method :extract_basic_auth

    def initialize(data_url, connection)
      @connection = connection
      Neo4j::Session.register(self)
      initialize_resource(data_url)
      Neo4j::Session._notify_listeners(:session_available, self)
    end

    def db_type
      :server_db
    end

    def to_s
      "#{self.class} url: '#{@resource_url}'"
    end

    def inspect
      "#{to_s} version: '#{version}'"
    end

    def version
      resource_data ? resource_data['neo4j_version'] : ''
    end

    def initialize_resource(data_url)
      response = @connection.get(data_url)
      expect_response_code(response,200)
      data_resource = response.body
      raise "No data_resource for #{response.body}" unless data_resource
      # store the resource data
      init_resource_data(data_resource, data_url)
    end

    def close
      super
      Neo4j::Transaction.unregister_current
    end

    def begin_tx
      if Neo4j::Transaction.current
        # Handle nested transaction "placebo transaction"
        Neo4j::Transaction.current.push_nested!
      else
        wrap_resource(self, 'transaction', CypherTransaction, :post, @connection)
      end
      Neo4j::Transaction.current
    end

    def create_node(props = nil, labels = [])
      CypherNode.new self, _query_or_fail(cypher_string(labels, props), true, cypher_prop_list(props))
    end

    def load_node(neo_id)
      load_entity(CypherNode, _query("MATCH (n) WHERE ID(n) = #{neo_id} RETURN n"))
    end

    def load_relationship(neo_id)
      load_entity(CypherRelationship, _query("MATCH (n)-[r]-() WHERE ID(r) = #{neo_id} RETURN r"))
    end

    def load_entity(clazz, cypher_response)
      return nil if cypher_response.data.nil? || cypher_response.data[0].nil?
      data  = if cypher_response.is_transaction_response?
                cypher_response.rest_data_with_id
              else
                cypher_response.first_data
              end

      if cypher_response.error?
        cypher_response.raise_error
      elsif cypher_response.error_msg =~ /not found/  # Ugly that the Neo4j API gives us this error message
        return nil
      else
        clazz.new(self, data)
      end
    end

    def create_label(name)
      CypherLabel.new(self, name)
    end

    def uniqueness_constraints(label)
      schema_properties("#{@resource_url}schema/constraint/#{label}/uniqueness")
    end

    def indexes(label)
      schema_properties("#{@resource_url}schema/index/#{label}")
    end

    def schema_properties(query_string)
      response = @connection.get(query_string)
      expect_response_code(response, 200)
      { property_keys: response.body.map { |row| row['property_keys'].map(&:to_sym) } }
    end

    def find_all_nodes(label_name)
      search_result_to_enumerable_first_column(_query_or_fail("MATCH (n:`#{label_name}`) RETURN ID(n)"))
    end

{'fraction_non_alphanumeric': 0.10213529856386999, 'fraction_numerical': 0.006708238851095994, 'mean_word_length': 3.083719135802469, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3609750', 'n_tokens_mistral': 3385, 'n_tokens_neox': 3228, 'n_words': 704}