graph2relational 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/graph2relational +13 -0
- data/lib/graph2relational.rb +22 -0
- data/lib/graph2relational/neo4j-connection.rb +26 -0
- data/lib/graph2relational/neo4j-database.rb +81 -0
- data/lib/graph2relational/rdbms-column.rb +35 -0
- data/lib/graph2relational/rdbms-connection.rb +38 -0
- data/lib/graph2relational/rdbms-database.rb +146 -0
- data/lib/graph2relational/rdbms-join-table.rb +43 -0
- data/lib/graph2relational/rdbms-table.rb +36 -0
- data/lib/graph2relational/util-commandline-parser.rb +57 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ffd5eda800a15b74f1843d5e5e32c9063d8b2646
|
4
|
+
data.tar.gz: a10dc8539499cb64bbcbd88355bfa2a064ce5970
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aa6904f22f311b822dad0cfa1575b155cf1b3e03b58a1316eea3f026e4f69a7fc34984f242044c8ed2ebbf75dee188181a82c0f03d97dbb973a18d6cfb2bb6ea
|
7
|
+
data.tar.gz: 656a7e830a82e14f0fece65483f6efe32b9c82ba8af15f225839c04037623784ba6e9a09b3455fdbd5a97ec98365116837aab9d1c63366c609ebed3acc1c2fb1
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'graph2relational'
|
3
|
+
|
4
|
+
# parse command line input
|
5
|
+
options = G2R::Util::CommandLineParser.new.parse
|
6
|
+
|
7
|
+
# create database connections
|
8
|
+
neo4j = G2R::Neo4J::Database.new(options[:input])
|
9
|
+
rdbms = G2R::RDBMS::Database.new(options[:output], options)
|
10
|
+
|
11
|
+
# convert
|
12
|
+
rdbms.source = neo4j
|
13
|
+
rdbms.convert()
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# external dependencies
|
2
|
+
require 'neography'
|
3
|
+
require 'sequel'
|
4
|
+
require 'optparse'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
#
|
8
|
+
$:.unshift File.dirname(__FILE__)
|
9
|
+
|
10
|
+
# Utils
|
11
|
+
require 'graph2relational/util-commandline-parser'
|
12
|
+
|
13
|
+
# Neo4J require
|
14
|
+
require 'graph2relational/neo4j-database'
|
15
|
+
require 'graph2relational/neo4j-connection'
|
16
|
+
|
17
|
+
# RDBMS require
|
18
|
+
require 'graph2relational/rdbms-database'
|
19
|
+
require 'graph2relational/rdbms-connection'
|
20
|
+
require 'graph2relational/rdbms-table'
|
21
|
+
require 'graph2relational/rdbms-join-table'
|
22
|
+
require 'graph2relational/rdbms-column'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module G2R
|
2
|
+
module Neo4J
|
3
|
+
class Connection
|
4
|
+
def initialize(options)
|
5
|
+
@conn = Neography::Rest.new(options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def query_data(cypher)
|
9
|
+
query(cypher)['data']
|
10
|
+
end
|
11
|
+
|
12
|
+
def query_columns(cypher)
|
13
|
+
columns = Set.new
|
14
|
+
query(cypher)['data'].each do |row|
|
15
|
+
columns += row[0]['data'].keys
|
16
|
+
end
|
17
|
+
|
18
|
+
columns.to_a
|
19
|
+
end
|
20
|
+
|
21
|
+
def query(cypher)
|
22
|
+
@conn.execute_query(cypher)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module G2R
|
2
|
+
module Neo4J
|
3
|
+
|
4
|
+
# The Neo4J database that will have its data converted. It is the source of the conversion.
|
5
|
+
class Database
|
6
|
+
|
7
|
+
def initialize(connection_options)
|
8
|
+
# services
|
9
|
+
@conn = Connection.new(connection_options)
|
10
|
+
|
11
|
+
# data
|
12
|
+
@labels = nil
|
13
|
+
@label_attributes = {}
|
14
|
+
@label_relationships = {}
|
15
|
+
@relationships = nil
|
16
|
+
@relationship_attributes = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
# ==========================================================================
|
20
|
+
# NODES
|
21
|
+
# ==========================================================================
|
22
|
+
def labels
|
23
|
+
# lazy load labels
|
24
|
+
if @labels.nil?
|
25
|
+
rows = @conn.query_data("MATCH (n) UNWIND labels(n) AS label RETURN DISTINCT label ORDER BY label")
|
26
|
+
@labels = rows.map {|row| row[0]}
|
27
|
+
end
|
28
|
+
|
29
|
+
@labels
|
30
|
+
end
|
31
|
+
|
32
|
+
def label_attributes(label)
|
33
|
+
if not @label_attributes.key? label
|
34
|
+
@label_attributes[label] = @conn.query_columns("MATCH (n:#{label}) RETURN n LIMIT 1000")
|
35
|
+
end
|
36
|
+
|
37
|
+
@label_attributes[label]
|
38
|
+
end
|
39
|
+
|
40
|
+
def label_relationships(label)
|
41
|
+
if not @label_relationships.key? label
|
42
|
+
@label_relationships[label] = @conn.query_data("MATCH (n:#{label})-[r]->(m) UNWIND labels(m) AS label WITH label, type(r) as relationship RETURN DISTINCT relationship, label")
|
43
|
+
end
|
44
|
+
|
45
|
+
@label_relationships[label]
|
46
|
+
end
|
47
|
+
|
48
|
+
def label_data(label)
|
49
|
+
return_clause = label_attributes(label).map {|column| "n.#{column}"}.join(", ")
|
50
|
+
@conn.query_data("MATCH (n:#{label}) RETURN id(n), #{return_clause}")
|
51
|
+
end
|
52
|
+
|
53
|
+
# ==========================================================================
|
54
|
+
# RELATIONSIIPS
|
55
|
+
# ==========================================================================
|
56
|
+
def relationships
|
57
|
+
# lazy load relationships
|
58
|
+
if @relationships.nil?
|
59
|
+
rows = @conn.query_data("MATCH (n)-[r]-(m) RETURN DISTINCT type(r) AS relationship ORDER BY relationship")
|
60
|
+
@relationships = rows.map{|row| row[0]}
|
61
|
+
end
|
62
|
+
|
63
|
+
@relationships
|
64
|
+
end
|
65
|
+
|
66
|
+
def relationship_attributes(source, type, target)
|
67
|
+
key = "#{source}_#{type}_#{target}"
|
68
|
+
if not @relationship_attributes.has_key? key
|
69
|
+
@relationship_attributes[key] = @conn.query_columns("MATCH (n:#{source})-[r:#{type}]->(m:#{target}) RETURN r LIMIT 1000")
|
70
|
+
end
|
71
|
+
|
72
|
+
@relationship_attributes[key]
|
73
|
+
end
|
74
|
+
|
75
|
+
def relationship_data(source, type, target)
|
76
|
+
return_clause = relationship_attributes(source, type, target).map {|column| "r.#{column}"}.join(", ")
|
77
|
+
@conn.query_data("MATCH (n:#{source})-[r:#{type}]->(m:#{target}) RETURN id(n), id(m), #{return_clause}")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module G2R
|
2
|
+
module RDBMS
|
3
|
+
class Column
|
4
|
+
attr_accessor :name
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name.downcase.to_sym
|
8
|
+
@primary_key = false
|
9
|
+
@foreign_key = false
|
10
|
+
end
|
11
|
+
|
12
|
+
# Define the column as a primary key
|
13
|
+
def primary_key
|
14
|
+
@primary_key = true
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Define the column as a foreign key
|
19
|
+
def foreign_key
|
20
|
+
@foreign_key = true
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
# Checks if the column is a primary key
|
25
|
+
def primary_key?
|
26
|
+
@primary_key
|
27
|
+
end
|
28
|
+
|
29
|
+
# Check if the column is a foreign key
|
30
|
+
def foreign_key?
|
31
|
+
@foreign_key
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module G2R
|
2
|
+
module RDBMS
|
3
|
+
class Connection
|
4
|
+
|
5
|
+
def initialize(options)
|
6
|
+
# sequel
|
7
|
+
@db = Sequel.connect(options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_table(table)
|
11
|
+
@db.create_table! table.name do
|
12
|
+
|
13
|
+
table.columns.each do |column|
|
14
|
+
# primary key
|
15
|
+
if column.primary_key?
|
16
|
+
primary_key column.name
|
17
|
+
# foreign key
|
18
|
+
elsif column.foreign_key?
|
19
|
+
Integer column.name
|
20
|
+
# common column
|
21
|
+
else
|
22
|
+
String column.name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def insert_data(table)
|
29
|
+
@db.transaction do
|
30
|
+
table.data.each do |row|
|
31
|
+
@db[table.name.to_sym].insert(row)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module G2R
|
2
|
+
module RDBMS
|
3
|
+
|
4
|
+
# The target RDBMS database that will have the schema and data generated for. It is the target of the conversion.
|
5
|
+
class Database
|
6
|
+
|
7
|
+
# ==========================================================================
|
8
|
+
# INITIALIZATION
|
9
|
+
# ==========================================================================
|
10
|
+
def initialize(connection_options, app_options = {})
|
11
|
+
# services
|
12
|
+
@options = app_options
|
13
|
+
@conn = Connection.new(connection_options)
|
14
|
+
|
15
|
+
# data
|
16
|
+
reset_data()
|
17
|
+
end
|
18
|
+
|
19
|
+
# ==========================================================================
|
20
|
+
# CONVERSION
|
21
|
+
# ==========================================================================
|
22
|
+
def source=(source)
|
23
|
+
@source = source
|
24
|
+
end
|
25
|
+
|
26
|
+
def source
|
27
|
+
@source
|
28
|
+
end
|
29
|
+
|
30
|
+
# Export the schema creation and data import scripts to the specified location
|
31
|
+
def convert
|
32
|
+
# prepare
|
33
|
+
reset_data()
|
34
|
+
|
35
|
+
# create tables
|
36
|
+
puts "Creating base tables"
|
37
|
+
base_tables.each do |table|
|
38
|
+
@conn.create_table(table)
|
39
|
+
end
|
40
|
+
|
41
|
+
puts "Creating relationship tables"
|
42
|
+
relationship_tables.each do |table|
|
43
|
+
@conn.create_table(table)
|
44
|
+
end
|
45
|
+
|
46
|
+
# insert data
|
47
|
+
puts "Inserting base tables data"
|
48
|
+
base_tables.each do |table|
|
49
|
+
@conn.insert_data(table)
|
50
|
+
end
|
51
|
+
|
52
|
+
puts "Inserting relationship tables data"
|
53
|
+
relationship_tables.each do |table|
|
54
|
+
@conn.insert_data(table)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Reset the saved data to perform a new conversion
|
59
|
+
def reset_data
|
60
|
+
@base_tables = nil
|
61
|
+
@relationship_tables = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
# ==========================================================================
|
65
|
+
# TABLES
|
66
|
+
# ==========================================================================
|
67
|
+
# Get all the base tables that will be generated. They are based on the Neo4J labels.
|
68
|
+
# Columns are based on the Neo4J node attributes.
|
69
|
+
def base_tables
|
70
|
+
# lazy initialization
|
71
|
+
if @base_tables.nil?
|
72
|
+
@base_tables = @source.labels.map do |label|
|
73
|
+
|
74
|
+
# if excluding label, do not transform into base table
|
75
|
+
next if exclude? label
|
76
|
+
|
77
|
+
# generate table
|
78
|
+
table = Table.new(label)
|
79
|
+
|
80
|
+
# generate primary key
|
81
|
+
table.add_columns(Column.new(:id).primary_key)
|
82
|
+
|
83
|
+
# generate columns
|
84
|
+
columns = @source.label_attributes(label).map {|attribute| Column.new(attribute)}
|
85
|
+
table.add_columns(columns)
|
86
|
+
|
87
|
+
# generate data
|
88
|
+
label_data = @source.label_data(label)
|
89
|
+
table.add_data(label_data)
|
90
|
+
|
91
|
+
# generated table
|
92
|
+
table
|
93
|
+
end.compact
|
94
|
+
end
|
95
|
+
|
96
|
+
@base_tables.compact
|
97
|
+
end
|
98
|
+
|
99
|
+
# Get all the relationships tables that will be generated. They are based in the found Neo4J relationships.
|
100
|
+
# Columss are based on the Neo4J relationship attributes.
|
101
|
+
def relationship_tables
|
102
|
+
# lazy initialization
|
103
|
+
if @relationship_tables.nil?
|
104
|
+
@relationship_tables = @source.labels.flat_map do |label|
|
105
|
+
|
106
|
+
# if excluding label, do not transform into relationship table
|
107
|
+
next if exclude? label
|
108
|
+
|
109
|
+
@source.label_relationships(label).map do |relationship|
|
110
|
+
relationship, target_label = relationship
|
111
|
+
|
112
|
+
# if excluding label, do not transform into relationship table
|
113
|
+
next if exclude? target_label
|
114
|
+
|
115
|
+
# gerarate table
|
116
|
+
table_name = "#{label}_#{relationship}_#{target_label}"
|
117
|
+
table = JoinTable.new(table_name)
|
118
|
+
|
119
|
+
# generate keys
|
120
|
+
table.source = label
|
121
|
+
table.target = target_label
|
122
|
+
|
123
|
+
# generate columns
|
124
|
+
columns = @source.relationship_attributes(label, relationship, target_label).map {|attribute| Column.new(attribute)}
|
125
|
+
table.add_columns(columns)
|
126
|
+
|
127
|
+
# generate data
|
128
|
+
relationship_data = @source.relationship_data(label, relationship, target_label)
|
129
|
+
table.add_data(relationship_data)
|
130
|
+
|
131
|
+
# generated table
|
132
|
+
table
|
133
|
+
end
|
134
|
+
end.compact
|
135
|
+
end
|
136
|
+
|
137
|
+
@relationship_tables
|
138
|
+
end
|
139
|
+
|
140
|
+
# Check if a label should be excluded and not be transformed into a table
|
141
|
+
def exclude?(label)
|
142
|
+
@options.has_key? :exclude and @options[:exclude].include? label
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module G2R
|
2
|
+
module RDBMS
|
3
|
+
|
4
|
+
class JoinTable < Table
|
5
|
+
|
6
|
+
# Additionally return the source and target column ids plus all defined columns
|
7
|
+
def columns
|
8
|
+
[source_column, target_column] + super
|
9
|
+
end
|
10
|
+
|
11
|
+
def source_column
|
12
|
+
Column.new(source + "_id").foreign_key
|
13
|
+
end
|
14
|
+
|
15
|
+
def target_column
|
16
|
+
if source != target
|
17
|
+
Column.new(target + "_id").foreign_key
|
18
|
+
else
|
19
|
+
Column.new(target + "_id_2").foreign_key
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# ==========================================================================
|
24
|
+
# ACESSORS
|
25
|
+
# ==========================================================================
|
26
|
+
def source=(source)
|
27
|
+
@source = source
|
28
|
+
end
|
29
|
+
|
30
|
+
def source
|
31
|
+
@source
|
32
|
+
end
|
33
|
+
|
34
|
+
def target=(target)
|
35
|
+
@target = target
|
36
|
+
end
|
37
|
+
|
38
|
+
def target
|
39
|
+
@target
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module G2R
|
2
|
+
module RDBMS
|
3
|
+
class Table
|
4
|
+
|
5
|
+
def initialize(name)
|
6
|
+
@name = name.downcase.to_sym
|
7
|
+
@columns = []
|
8
|
+
@data = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_columns(columns)
|
16
|
+
if columns.class == Array
|
17
|
+
@columns += columns
|
18
|
+
else
|
19
|
+
@columns << columns
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def columns
|
24
|
+
@columns
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_data(data)
|
28
|
+
@data += data
|
29
|
+
end
|
30
|
+
|
31
|
+
def data
|
32
|
+
@data
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module G2R
|
2
|
+
module Util
|
3
|
+
|
4
|
+
class CommandLineParser
|
5
|
+
|
6
|
+
def initialize()
|
7
|
+
@valid = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse()
|
11
|
+
# default values
|
12
|
+
options = {:input => nil, :output => nil, :exclude => []}
|
13
|
+
|
14
|
+
# display help if no args
|
15
|
+
ARGV << '-h' if ARGV.empty?
|
16
|
+
|
17
|
+
# do parsing
|
18
|
+
OptionParser.new do |opts|
|
19
|
+
opts.banner = "Usage: graph2relational -i <neo4j_connection_url> -o <rdbms_connection_url> [optional_arguments]"
|
20
|
+
|
21
|
+
# input file
|
22
|
+
opts.on("-i CONNECTION", "--input CONNECTION", "Neo4J connection URI from where the schema and data will be geberated") do |input_connection|
|
23
|
+
options[:input] = input_connection
|
24
|
+
end
|
25
|
+
|
26
|
+
# output dir
|
27
|
+
opts.on("-o CONNECTION", "--output CONNECTION", "RDBMS connection URI to where the schema and data will be persisted") do |output_connection|
|
28
|
+
options[:output] = output_connection
|
29
|
+
end
|
30
|
+
|
31
|
+
# exclusion
|
32
|
+
opts.on("-x EXCLUSION", "--exclude EXCLUSION", "Neo4J labels (separated by comma) to exclude from the conversion") do |exclude|
|
33
|
+
options[:exclude] = exclude.split(",").map{|exclude| exclude.strip}
|
34
|
+
end
|
35
|
+
|
36
|
+
# help
|
37
|
+
opts.on_tail("-h", "--help", "Help message") do
|
38
|
+
puts opts
|
39
|
+
exit
|
40
|
+
end
|
41
|
+
end.parse!
|
42
|
+
|
43
|
+
# check mandatory args
|
44
|
+
if options[:input].nil?
|
45
|
+
puts "Missing parameter: specify Neo4J connection URI using -i <connection> parameter"
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
if options[:output].nil?
|
49
|
+
puts "Missing parameter: specify RDBMS connection URI using -o <connection> parameter"
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
|
53
|
+
return options
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graph2relational
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Renato Dinhani Conceição
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: neography
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sequel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.32'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.32'
|
41
|
+
description: Converts a Neo4J graph database to a supported RDBMS database automatically
|
42
|
+
generating tables from its labels, relationships and attributes
|
43
|
+
email: renatodinhani@gmail.com
|
44
|
+
executables:
|
45
|
+
- graph2relational
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- bin/graph2relational
|
50
|
+
- lib/graph2relational.rb
|
51
|
+
- lib/graph2relational/neo4j-connection.rb
|
52
|
+
- lib/graph2relational/neo4j-database.rb
|
53
|
+
- lib/graph2relational/rdbms-column.rb
|
54
|
+
- lib/graph2relational/rdbms-connection.rb
|
55
|
+
- lib/graph2relational/rdbms-database.rb
|
56
|
+
- lib/graph2relational/rdbms-join-table.rb
|
57
|
+
- lib/graph2relational/rdbms-table.rb
|
58
|
+
- lib/graph2relational/util-commandline-parser.rb
|
59
|
+
homepage: https://github.com/renatodinhani/graph2relational
|
60
|
+
licenses:
|
61
|
+
- MIT
|
62
|
+
metadata: {}
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 2.4.5.1
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: Converts a Neo4J graph database to a RDBMS database
|
83
|
+
test_files: []
|