Class: Tina4::Drivers::BoltGraphDriver

Inherits:
GraphAdapter show all
Defined in:
lib/tina4/drivers/bolt_graph_driver.rb

Overview

The Bolt graph adapter — Neo4j + Memgraph behind the portable surface.

Instance Method Summary collapse

Methods inherited from GraphAdapter

#get_error

Constructor Details

#initialize(graph_url, username: "", password: "") ⇒ BoltGraphDriver

Returns a new instance of BoltGraphDriver.



431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 431

def initialize(graph_url, username: "", password: "")
  @url = graph_url
  @last_error = nil
  timeout = Tina4.resolve_graph_connect_timeout
  user = graph_url.username
  user = username unless user && !user.empty?
  pwd = graph_url.password
  pwd = password unless pwd && !pwd.empty?
  user = "neo4j" if (user.nil? || user.empty?) && graph_url.engine == "bolt" && !password.to_s.empty?
  @conn = BoltConnection.new(
    host: graph_url.host, port: graph_url.port,
    user: user, password: pwd,
    connect_timeout: timeout, use_tls: graph_url.use_tls
  )
rescue BoltConnection::BoltConnectError => e
  @last_error = e.message
  raise Tina4::GraphConnectTimeout,
        "Graph connect to #{graph_url.host}:#{graph_url.port} timed out or was refused " \
        "(TINA4_GRAPH_CONNECT_TIMEOUT). Raise TINA4_GRAPH_CONNECT_TIMEOUT if the server is " \
        "simply slow, or set it to 0 to wait indefinitely. Cause: #{e.message}"
end

Instance Method Details

#add_edge(from_id, to_id, type, properties = nil) ⇒ Object



474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 474

def add_edge(from_id, to_id, type, properties = nil)
  cypher = "MATCH (a), (b) WHERE id(a) = $from_id AND id(b) = $to_id " \
           "CREATE (a)-[e:`#{type}` $props]->(b) " \
           "RETURN id(e) AS id, type(e) AS type, id(a) AS f, id(b) AS t, " \
           "properties(e) AS props"
  rows = run(cypher, { "from_id" => from_id, "to_id" => to_id, "props" => properties || {} })
  return nil if rows.empty?

  row = rows[0]
  Tina4::GraphEdge.new(
    id: row["id"], type: row["type"], from: row["f"], to: row["t"],
    properties: row["props"] || {}
  )
end

#add_node(label, properties = nil) ⇒ Object

-- portable node/edge/traverse core (Cypher) -----------------------



467
468
469
470
471
472
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 467

def add_node(label, properties = nil)
  cypher = "CREATE (n:`#{label}` $props) " \
           "RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props"
  rows = run(cypher, { "props" => properties || {} })
  node_from_row(rows[0])
end

#closeObject



537
538
539
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 537

def close
  @conn&.close
end

#delete_node(node_id) ⇒ Object



503
504
505
506
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 503

def delete_node(node_id)
  run("MATCH (n) WHERE id(n) = $id DETACH DELETE n", { "id" => node_id })
  true
end

#execute(text, params = nil) ⇒ Object



461
462
463
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 461

def execute(text, params = nil)
  query(text, params)
end

#get_node(node_id) ⇒ Object



489
490
491
492
493
494
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 489

def get_node(node_id)
  cypher = "MATCH (n) WHERE id(n) = $id " \
           "RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props"
  rows = run(cypher, { "id" => node_id })
  node_from_row(rows[0])
end

#neighbors(node_id, direction: "both", edge_type: nil, limit: 100) ⇒ Object



508
509
510
511
512
513
514
515
516
517
518
519
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 508

def neighbors(node_id, direction: "both", edge_type: nil, limit: 100)
  edge = edge_type ? ":`#{edge_type}`" : ""
  pattern = case direction
            when "out" then "(n)-[#{edge}]->(m)"
            when "in" then "(n)<-[#{edge}]-(m)"
            else "(n)-[#{edge}]-(m)"
            end
  cypher = "MATCH #{pattern} WHERE id(n) = $id " \
           "RETURN DISTINCT id(m) AS id, labels(m) AS labels, properties(m) AS props " \
           "LIMIT #{limit.to_i}"
  run(cypher, { "id" => node_id }).map { |row| node_from_row(row) }
end

#query(text, params = nil) ⇒ Object

-- raw pass-through (native Cypher) --------------------------------



455
456
457
458
459
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 455

def query(text, params = nil)
  rows = run(text, params)
  columns = rows.empty? ? [] : rows[0].keys
  Tina4::GraphResult.new(records: rows, columns: columns)
end

#traverse(start_id, depth: 1, direction: "both", edge_type: nil, limit: 1000) ⇒ Object



521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 521

def traverse(start_id, depth: 1, direction: "both", edge_type: nil, limit: 1000)
  # Cypher variable-length path `[*1..N]` — Neo4j AND Memgraph (the OPPOSITE
  # of Ultipa's GQL quantifier `{1,N}`).
  edge = edge_type ? ":`#{edge_type}`" : ""
  n = depth.to_i
  arrow = case direction
          when "out" then "-[#{edge}*1..#{n}]->"
          when "in" then "<-[#{edge}*1..#{n}]-"
          else "-[#{edge}*1..#{n}]-"
          end
  cypher = "MATCH (n)#{arrow}(m) WHERE id(n) = $start " \
           "RETURN DISTINCT id(m) AS id, labels(m) AS labels, properties(m) AS props " \
           "LIMIT #{limit.to_i}"
  run(cypher, { "start" => start_id }).map { |row| node_from_row(row) }
end

#update_node(node_id, properties) ⇒ Object



496
497
498
499
500
501
# File 'lib/tina4/drivers/bolt_graph_driver.rb', line 496

def update_node(node_id, properties)
  cypher = "MATCH (n) WHERE id(n) = $id SET n += $props " \
           "RETURN id(n) AS id, labels(n) AS labels, properties(n) AS props"
  rows = run(cypher, { "id" => node_id, "props" => properties || {} })
  node_from_row(rows[0])
end