graph2relational 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/graph2relational.rb +4 -0
- data/lib/graph2relational/neo4j-connection.rb +9 -4
- data/lib/graph2relational/neo4j-database.rb +2 -2
- data/lib/graph2relational/rdbms-column.rb +23 -7
- data/lib/graph2relational/rdbms-connection.rb +5 -4
- data/lib/graph2relational/rdbms-database.rb +32 -15
- data/lib/graph2relational/rdbms-join-table.rb +20 -16
- data/lib/graph2relational/rdbms-table.rb +20 -5
- data/lib/graph2relational/rdbms.rb +16 -0
- data/lib/graph2relational/util-commandline-parser.rb +20 -3
- metadata +31 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4707cb31fae3fc71e5cfa92a2a98378c1637347d
|
4
|
+
data.tar.gz: 3ea233af867720ecc8a4e11a6213da93b8ec82e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1451c83f93957bd2d5a14534cfdc3974f9c61ed4890688c6f653200cb2df5591fc9c0ef320193b57ca0453aed467cdaba0c8714d0c4a7d378fbd93b9df36b6c3
|
7
|
+
data.tar.gz: 101e107513aa533ff66bf4f130406883de8962016f106c054864ed5cb655fb660f24242bef7057bc4d9cf689d8d9a601402809c1f08d2b5ed202c597a0448c0c
|
data/lib/graph2relational.rb
CHANGED
@@ -15,8 +15,12 @@ require 'graph2relational/neo4j-database'
|
|
15
15
|
require 'graph2relational/neo4j-connection'
|
16
16
|
|
17
17
|
# RDBMS require
|
18
|
+
require 'graph2relational/rdbms'
|
18
19
|
require 'graph2relational/rdbms-database'
|
19
20
|
require 'graph2relational/rdbms-connection'
|
20
21
|
require 'graph2relational/rdbms-table'
|
21
22
|
require 'graph2relational/rdbms-join-table'
|
22
23
|
require 'graph2relational/rdbms-column'
|
24
|
+
|
25
|
+
# Sequel extensions
|
26
|
+
Sequel.extension :inflector
|
@@ -5,10 +5,6 @@ module G2R
|
|
5
5
|
@conn = Neography::Rest.new(options)
|
6
6
|
end
|
7
7
|
|
8
|
-
def query_data(cypher)
|
9
|
-
query(cypher)['data']
|
10
|
-
end
|
11
|
-
|
12
8
|
def query_columns(cypher)
|
13
9
|
columns = Set.new
|
14
10
|
query(cypher)['data'].each do |row|
|
@@ -18,6 +14,15 @@ module G2R
|
|
18
14
|
columns.to_a
|
19
15
|
end
|
20
16
|
|
17
|
+
def query_data(cypher)
|
18
|
+
query(cypher)['data']
|
19
|
+
end
|
20
|
+
|
21
|
+
def query_hash(cypher)
|
22
|
+
results = query(cypher)
|
23
|
+
results["data"].map {|row| Hash[*results["columns"].zip(row).flatten] }
|
24
|
+
end
|
25
|
+
|
21
26
|
def query(cypher)
|
22
27
|
@conn.execute_query(cypher)
|
23
28
|
end
|
@@ -46,8 +46,8 @@ module G2R
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def label_data(label)
|
49
|
-
return_clause = label_attributes(label).map {|column| "n.#{column}"}.join(", ")
|
50
|
-
@conn.
|
49
|
+
return_clause = label_attributes(label).map {|column| "n.#{column} as #{column}"}.join(", ")
|
50
|
+
@conn.query_hash("MATCH (n:#{label}) RETURN id(n) as id, #{return_clause}")
|
51
51
|
end
|
52
52
|
|
53
53
|
# ==========================================================================
|
@@ -4,32 +4,48 @@ module G2R
|
|
4
4
|
attr_accessor :name
|
5
5
|
|
6
6
|
def initialize(name)
|
7
|
-
@name = name
|
7
|
+
@name = RDBMS.transform_name(name)
|
8
8
|
@primary_key = false
|
9
9
|
@foreign_key = false
|
10
10
|
end
|
11
11
|
|
12
|
+
def ==(other_column)
|
13
|
+
@name == other_column.name
|
14
|
+
end
|
15
|
+
|
16
|
+
# ========================================================================
|
17
|
+
# PRIMARY KEY
|
18
|
+
# ========================================================================
|
12
19
|
# Define the column as a primary key
|
13
20
|
def primary_key
|
14
21
|
@primary_key = true
|
15
22
|
self
|
16
23
|
end
|
17
24
|
|
18
|
-
# Define the column as a foreign key
|
19
|
-
def foreign_key
|
20
|
-
@foreign_key = true
|
21
|
-
self
|
22
|
-
end
|
23
|
-
|
24
25
|
# Checks if the column is a primary key
|
25
26
|
def primary_key?
|
26
27
|
@primary_key
|
27
28
|
end
|
28
29
|
|
30
|
+
# ========================================================================
|
31
|
+
# FOREIGN KEY
|
32
|
+
# ========================================================================
|
33
|
+
# Define the column as a foreign key
|
34
|
+
def foreign_key(target_table)
|
35
|
+
@foreign_key = true
|
36
|
+
@target_table = RDBMS.transform_name(target_table)
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
29
40
|
# Check if the column is a foreign key
|
30
41
|
def foreign_key?
|
31
42
|
@foreign_key
|
32
43
|
end
|
44
|
+
|
45
|
+
# Table the foreign key is targeting
|
46
|
+
def target_table
|
47
|
+
@target_table
|
48
|
+
end
|
33
49
|
end
|
34
50
|
end
|
35
51
|
end
|
@@ -8,18 +8,19 @@ module G2R
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def create_table(table)
|
11
|
-
@db.create_table! table.name do
|
11
|
+
@db.create_table! table.name.to_sym do
|
12
12
|
|
13
13
|
table.columns.each do |column|
|
14
14
|
# primary key
|
15
15
|
if column.primary_key?
|
16
|
-
primary_key column.name
|
16
|
+
primary_key column.name.to_sym
|
17
17
|
# foreign key
|
18
18
|
elsif column.foreign_key?
|
19
|
-
|
19
|
+
foreign_key column.name.to_sym, column.target_table.to_sym
|
20
|
+
index column.name.to_sym
|
20
21
|
# common column
|
21
22
|
else
|
22
|
-
String column.name
|
23
|
+
String column.name.to_sym
|
23
24
|
end
|
24
25
|
end
|
25
26
|
end
|
@@ -33,12 +33,12 @@ module G2R
|
|
33
33
|
reset_data()
|
34
34
|
|
35
35
|
# create tables
|
36
|
-
puts "Creating base tables"
|
36
|
+
puts "Creating base tables schema"
|
37
37
|
base_tables.each do |table|
|
38
38
|
@conn.create_table(table)
|
39
39
|
end
|
40
40
|
|
41
|
-
puts "Creating relationship tables"
|
41
|
+
puts "Creating relationship tables schema"
|
42
42
|
relationship_tables.each do |table|
|
43
43
|
@conn.create_table(table)
|
44
44
|
end
|
@@ -72,18 +72,24 @@ module G2R
|
|
72
72
|
@base_tables = @source.labels.map do |label|
|
73
73
|
|
74
74
|
# if excluding label, do not transform into base table
|
75
|
-
next if
|
75
|
+
next if exclude_label? label
|
76
76
|
|
77
77
|
# generate table
|
78
78
|
table = Table.new(label)
|
79
79
|
|
80
80
|
# generate primary key
|
81
|
-
table.add_columns(Column.new(
|
81
|
+
table.add_columns(Column.new('id').primary_key)
|
82
82
|
|
83
|
-
# generate columns
|
83
|
+
# generate schema columns
|
84
84
|
columns = @source.label_attributes(label).map {|attribute| Column.new(attribute)}
|
85
85
|
table.add_columns(columns)
|
86
86
|
|
87
|
+
# add forced columns
|
88
|
+
if @options.has_key? :additional_base_columns
|
89
|
+
forced_columns = @options[:additional_base_columns].map {|name| Column.new(name)}
|
90
|
+
table.add_columns(forced_columns)
|
91
|
+
end
|
92
|
+
|
87
93
|
# generate data
|
88
94
|
label_data = @source.label_data(label)
|
89
95
|
table.add_data(label_data)
|
@@ -104,26 +110,28 @@ module G2R
|
|
104
110
|
@relationship_tables = @source.labels.flat_map do |label|
|
105
111
|
|
106
112
|
# if excluding label, do not transform into relationship table
|
107
|
-
next if
|
113
|
+
next if exclude_label? label
|
108
114
|
|
109
115
|
@source.label_relationships(label).map do |relationship|
|
110
116
|
relationship, target_label = relationship
|
111
117
|
|
112
118
|
# if excluding label, do not transform into relationship table
|
113
|
-
next if
|
119
|
+
next if exclude_relationship? relationship
|
120
|
+
next if exclude_label? target_label
|
114
121
|
|
115
122
|
# gerarate table
|
116
|
-
|
117
|
-
table = JoinTable.new(table_name)
|
118
|
-
|
119
|
-
# generate keys
|
120
|
-
table.source = label
|
121
|
-
table.target = target_label
|
123
|
+
table = JoinTable.new(label, relationship, target_label)
|
122
124
|
|
123
125
|
# generate columns
|
124
126
|
columns = @source.relationship_attributes(label, relationship, target_label).map {|attribute| Column.new(attribute)}
|
125
127
|
table.add_columns(columns)
|
126
128
|
|
129
|
+
# add forced columns
|
130
|
+
if @options.has_key? :additional_relationship_columns
|
131
|
+
forced_columns = @options[:additional_relationship_columns].map {|name| Column.new(name)}
|
132
|
+
table.add_columns(forced_columns)
|
133
|
+
end
|
134
|
+
|
127
135
|
# generate data
|
128
136
|
relationship_data = @source.relationship_data(label, relationship, target_label)
|
129
137
|
table.add_data(relationship_data)
|
@@ -137,9 +145,18 @@ module G2R
|
|
137
145
|
@relationship_tables
|
138
146
|
end
|
139
147
|
|
148
|
+
# ========================================================================
|
149
|
+
# CHECKS
|
150
|
+
# ========================================================================
|
151
|
+
|
140
152
|
# Check if a label should be excluded and not be transformed into a table
|
141
|
-
def
|
142
|
-
@options.has_key? :
|
153
|
+
def exclude_label?(label)
|
154
|
+
@options.has_key? :exclude_labels and @options[:exclude_labels].include? label
|
155
|
+
end
|
156
|
+
|
157
|
+
# Check if a relationship should be excluded and not be transformed into a relationship table
|
158
|
+
def exclude_relationship?(relationship)
|
159
|
+
@options.has_key? :exclude_relationships and @options[:exclude_relationships].include? relationship
|
143
160
|
end
|
144
161
|
end
|
145
162
|
end
|
@@ -3,40 +3,44 @@ module G2R
|
|
3
3
|
|
4
4
|
class JoinTable < Table
|
5
5
|
|
6
|
+
def initialize(source_table, relationship, target_table)
|
7
|
+
# parent initialization
|
8
|
+
super("")
|
9
|
+
|
10
|
+
# additiional initialization
|
11
|
+
table_name = "#{source_table}_#{relationship}_#{target_table}"
|
12
|
+
@name = RDBMS.transform_name(table_name)
|
13
|
+
|
14
|
+
@source_table = RDBMS.transform_name(source_table)
|
15
|
+
@target_table = RDBMS.transform_name(target_table)
|
16
|
+
end
|
17
|
+
|
6
18
|
# Additionally return the source and target column ids plus all defined columns
|
7
19
|
def columns
|
8
20
|
[source_column, target_column] + super
|
9
21
|
end
|
10
22
|
|
11
23
|
def source_column
|
12
|
-
Column.new(
|
24
|
+
Column.new(source_table + "_id").foreign_key(source_table)
|
13
25
|
end
|
14
26
|
|
15
27
|
def target_column
|
16
|
-
if
|
17
|
-
Column.new(
|
28
|
+
if source_table != target_table
|
29
|
+
Column.new(target_table + "_id").foreign_key(target_table)
|
18
30
|
else
|
19
|
-
Column.new(
|
31
|
+
Column.new(target_table + "_id_2").foreign_key(target_table)
|
20
32
|
end
|
21
33
|
end
|
22
34
|
|
23
35
|
# ==========================================================================
|
24
36
|
# ACESSORS
|
25
37
|
# ==========================================================================
|
26
|
-
def
|
27
|
-
@
|
28
|
-
end
|
29
|
-
|
30
|
-
def source
|
31
|
-
@source
|
32
|
-
end
|
33
|
-
|
34
|
-
def target=(target)
|
35
|
-
@target = target
|
38
|
+
def source_table
|
39
|
+
@source_table
|
36
40
|
end
|
37
41
|
|
38
|
-
def
|
39
|
-
@
|
42
|
+
def target_table
|
43
|
+
@target_table
|
40
44
|
end
|
41
45
|
end
|
42
46
|
end
|
@@ -3,21 +3,36 @@ module G2R
|
|
3
3
|
class Table
|
4
4
|
|
5
5
|
def initialize(name)
|
6
|
-
@name = name
|
6
|
+
@name = RDBMS.transform_name(name)
|
7
7
|
@columns = []
|
8
8
|
@data = []
|
9
9
|
end
|
10
10
|
|
11
|
+
def ==(other_table)
|
12
|
+
@name == other_table.name
|
13
|
+
end
|
14
|
+
|
11
15
|
def name
|
12
16
|
@name
|
13
17
|
end
|
14
18
|
|
15
19
|
def add_columns(columns)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@columns << columns
|
20
|
+
# transform string into array
|
21
|
+
if columns.class != Array
|
22
|
+
columns = [columns]
|
20
23
|
end
|
24
|
+
|
25
|
+
# filter out columns that already exists
|
26
|
+
columns = columns.map do |column|
|
27
|
+
if @columns.include? column
|
28
|
+
nil
|
29
|
+
else
|
30
|
+
column
|
31
|
+
end
|
32
|
+
end.compact
|
33
|
+
|
34
|
+
# append columns
|
35
|
+
@columns += columns
|
21
36
|
end
|
22
37
|
|
23
38
|
def columns
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module G2R
|
2
|
+
module RDBMS
|
3
|
+
|
4
|
+
def self.transform_name(name)
|
5
|
+
name = name.to_s if name.class == Symbol
|
6
|
+
|
7
|
+
# sequel underscore (handles CamelCase)
|
8
|
+
name = name.underscore
|
9
|
+
|
10
|
+
# space to underscore
|
11
|
+
name = name.gsub(/\s/, '_')
|
12
|
+
|
13
|
+
name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -9,7 +9,11 @@ module G2R
|
|
9
9
|
|
10
10
|
def parse()
|
11
11
|
# default values
|
12
|
-
options = {
|
12
|
+
options = {
|
13
|
+
:input => nil, :output => nil,
|
14
|
+
:additional_base_columns => [], :additional_relationship_columns => [],
|
15
|
+
:exclude_labels => [], :exclude_relationships => []
|
16
|
+
}
|
13
17
|
|
14
18
|
# display help if no args
|
15
19
|
ARGV << '-h' if ARGV.empty?
|
@@ -28,9 +32,22 @@ module G2R
|
|
28
32
|
options[:output] = output_connection
|
29
33
|
end
|
30
34
|
|
35
|
+
# base columns to always be created
|
36
|
+
opts.on("--additional-base-columns COLUMNS", "Base table columns (separated by comma) that should always be created, even if they are not found in Neo4J schema") do |columns|
|
37
|
+
options[:additional_base_columns] = columns.split(",").map{|column| column.strip}
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on("--additional-relationships-columns COLUMNS", "Relationship table columns (separated by comma) that should always be created, even if they are not found in Neo4J schema") do |columns|
|
41
|
+
options[:additional_relationship_columns] = columns.split(",").map{|column| column.strip}
|
42
|
+
end
|
43
|
+
|
31
44
|
# exclusion
|
32
|
-
opts.on("
|
33
|
-
options[:
|
45
|
+
opts.on("--exclude-labels EXCLUSION", "Neo4J labels (separated by comma) to exclude from the conversion") do |exclude|
|
46
|
+
options[:exclude_labels] = exclude.split(",").map{|exclude| exclude.strip}
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on("--exclude-relationships EXCLUSION", "Neo4J relationships (separated by comma) to exclude from the conversion") do |exclude|
|
50
|
+
options[:exclude_relationships] = exclude.split(",").map{|exclude| exclude.strip}
|
34
51
|
end
|
35
52
|
|
36
53
|
# help
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graph2relational
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Renato Dinhani Conceição
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-03-
|
11
|
+
date: 2016-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: neography
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '4.32'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
description: Converts a Neo4J graph database to a supported RDBMS database automatically
|
42
70
|
generating tables from its labels, relationships and attributes
|
43
71
|
email: renatodinhani@gmail.com
|
@@ -55,6 +83,7 @@ files:
|
|
55
83
|
- lib/graph2relational/rdbms-database.rb
|
56
84
|
- lib/graph2relational/rdbms-join-table.rb
|
57
85
|
- lib/graph2relational/rdbms-table.rb
|
86
|
+
- lib/graph2relational/rdbms.rb
|
58
87
|
- lib/graph2relational/util-commandline-parser.rb
|
59
88
|
homepage: https://github.com/renatodinhani/graph2relational
|
60
89
|
licenses:
|