pg_graph 0.2.1 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/pg_graph +1 -1
- data/lib/pg_graph/reflector.rb +51 -21
- data/lib/pg_graph/version.rb +1 -1
- data/lib/type/read.rb +19 -11
- data/lib/type/type.rb +5 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40e9eaa99c76e5fe7d06806563b0f6c7bf289acff4e6603abc0fa48e72cec3d1
|
4
|
+
data.tar.gz: a4292fa743ccd275a5dff4766d3b4c1dbd229e778df3bf891279c4c6d2237530
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac5ebfda0d90ac611df0e10203f6a130422b805afcb69fe3b01f8da362ebcc99d5cb84e08db2e206dff3ca55f4490d0e0e781d19cf2e75d7bd4f2527cbffb553
|
7
|
+
data.tar.gz: bcf1584d72b3dea86e89086ce3df10f2690b93dbcc4e18f102938adef736af644b786903ead84edc88e787c77e2636cf3b01b2a33b018ed76eb7b9dac4dc6166
|
data/exe/pg_graph
CHANGED
@@ -21,7 +21,7 @@ SPEC = %(
|
|
21
21
|
you're almost always better off using postgres' own tools
|
22
22
|
(pg_dump/pg_restore/pg_sql), except in the special case when the data
|
23
23
|
contains circular foreign-key constraints and you don't have superuser
|
24
|
-
privileges
|
24
|
+
privileges (say what? FIXME)
|
25
25
|
|
26
26
|
The dump command can also write the type of the database in yaml format. This
|
27
27
|
is the default
|
data/lib/pg_graph/reflector.rb
CHANGED
@@ -14,7 +14,19 @@ module PgGraph
|
|
14
14
|
# included in the model. Can be nil
|
15
15
|
attr_reader :that
|
16
16
|
|
17
|
-
#
|
17
|
+
# True if this reflection applies to multiple references in a table
|
18
|
+
def multi? = multi || multi.nil?
|
19
|
+
|
20
|
+
# If true or nil, the reflection applies to multiple references in a table
|
21
|
+
# to the same referenced table. nil is used to mark the rule to not be
|
22
|
+
# included in the count of references. This is used to exclude explicit
|
23
|
+
# patterns like /^parent_id$/ that can co-exist with a another reference to
|
24
|
+
# the table
|
25
|
+
attr_reader :multi
|
26
|
+
|
27
|
+
# Pluralize the result of #that if true. Default nil. Note that if
|
28
|
+
# pluralize is nil, the Reflector will pluralize unless the column is
|
29
|
+
# unique
|
18
30
|
attr_reader :pluralize
|
19
31
|
|
20
32
|
# RE corresponding to #match. #re always match a full UID
|
@@ -30,10 +42,11 @@ module PgGraph
|
|
30
42
|
attr_reader :default_reflection
|
31
43
|
|
32
44
|
# +this+ and +that+ are template strings, nil, or false
|
33
|
-
def initialize(match, this, that, pluralize =
|
45
|
+
def initialize(match, this, that, multi = nil, pluralize = nil, default_reflection = false)
|
34
46
|
constrain match, Regexp, String
|
35
47
|
constrain this, String, NilClass
|
36
48
|
constrain that, String, FalseClass, NilClass
|
49
|
+
constrain multi, true, false, nil
|
37
50
|
constrain pluralize, TrueClass, FalseClass, NilClass
|
38
51
|
constrain default_reflection, TrueClass, FalseClass, NilClass
|
39
52
|
@match = match.is_a?(Regexp) ? match.source : match
|
@@ -48,12 +61,16 @@ module PgGraph
|
|
48
61
|
(1..4).include?(@components) or raise "Illegal number of name components: #{@match}"
|
49
62
|
@this = this
|
50
63
|
@that = that
|
51
|
-
@
|
64
|
+
@multi = multi
|
65
|
+
@pluralize = pluralize
|
52
66
|
@default_reflection = default_reflection || false
|
53
67
|
end
|
54
68
|
|
55
69
|
def to_yaml
|
56
|
-
{
|
70
|
+
{
|
71
|
+
match: match, this: this, that: that, multi: multi, pluralize: pluralize,
|
72
|
+
default_reflection: default_reflection
|
73
|
+
}
|
57
74
|
end
|
58
75
|
|
59
76
|
def ==(other)
|
@@ -71,7 +88,7 @@ module PgGraph
|
|
71
88
|
}
|
72
89
|
end
|
73
90
|
|
74
|
-
METHODS = %w(match this that pluralize default_reflection).map(&:to_sym)
|
91
|
+
METHODS = %w(match this that multi pluralize default_reflection).map(&:to_sym)
|
75
92
|
end
|
76
93
|
|
77
94
|
class Reflector
|
@@ -123,17 +140,26 @@ module PgGraph
|
|
123
140
|
end
|
124
141
|
|
125
142
|
# Find 'that' field name for the given UID by searching through reflections
|
126
|
-
# for a match. The
|
127
|
-
#
|
128
|
-
# is used in N:M and M:M
|
129
|
-
#
|
130
|
-
|
143
|
+
# for a match. The name is pluralized if the matcher returns true or if the
|
144
|
+
# matcher returns nil and unique is false The :table option can be used to
|
145
|
+
# override the table name in '$$' rules. This is used in N:M and M:M
|
146
|
+
# relations. Returns nil if no match was found or if a matching reflection
|
147
|
+
# has #continue equal to false
|
148
|
+
def that(uid, unique, multi = false, table: nil)
|
131
149
|
constrain uid, String
|
132
|
-
if (name, pluralize = do_match(uid, :that, table: table))
|
133
|
-
pluralize
|
150
|
+
if (name, pluralize = do_match(uid, :that, multi, table: table))
|
151
|
+
if pluralize.nil? && !unique || pluralize
|
152
|
+
PgGraph.inflector.pluralize(name)
|
153
|
+
else
|
154
|
+
name
|
155
|
+
end
|
134
156
|
end
|
135
157
|
end
|
136
158
|
|
159
|
+
def multi?(uid)
|
160
|
+
reflection = reflections.find { |reflection| reflection.re.match(uid) }&.multi == false
|
161
|
+
end
|
162
|
+
|
137
163
|
def to_yaml
|
138
164
|
reflections.map { |reflection| reflection.to_yaml }
|
139
165
|
end
|
@@ -152,7 +178,7 @@ module PgGraph
|
|
152
178
|
table ||= REFLECTIONS_TABLE
|
153
179
|
if conn.schema.exist?(schema) && conn.schema.exist_table?(schema, table)
|
154
180
|
yaml = conn.structs(%(
|
155
|
-
select match, this, that, pluralize, false as "default_reflection"
|
181
|
+
select match, this, that, multi, pluralize, false as "default_reflection"
|
156
182
|
from #{schema}.#{table}
|
157
183
|
order by ordinal
|
158
184
|
)).map(&:to_h)
|
@@ -175,7 +201,7 @@ module PgGraph
|
|
175
201
|
|
176
202
|
conn.exec %(
|
177
203
|
insert into #{schema}.#{table}
|
178
|
-
(match, this, that, pluralize, default_reflection, ordinal)
|
204
|
+
(match, this, that, multi, pluralize, default_reflection, ordinal)
|
179
205
|
values
|
180
206
|
#{values}
|
181
207
|
)
|
@@ -185,13 +211,14 @@ module PgGraph
|
|
185
211
|
@default_reflections ||= begin
|
186
212
|
initializers = [
|
187
213
|
{"match"=>"/parent_id/", "that"=>"child"},
|
188
|
-
{"match"=>"/child_id/", "that"=>"parent"},
|
189
|
-
{"match"=>"/parent_(\\w+)_id/", "that"=>"child_$1"},
|
190
|
-
{"match"=>"/child_(\\w+)_id/", "that"=>"parent_$1"},
|
191
|
-
{"match"=>"kind", "this"=>"kind", "that"=>"$$"},
|
214
|
+
{"match"=>"/child_id/", "that"=>"parent"},
|
215
|
+
{"match"=>"/parent_(\\w+)_id/", "that"=>"child_$1"},
|
216
|
+
{"match"=>"/child_(\\w+)_id/", "that"=>"parent_$1"},
|
217
|
+
{"match"=>"kind", "this"=>"kind", "that"=>"$$"},
|
192
218
|
{"match"=>"/(\\w+)_kind/", "this"=>"$1", "that"=>"$$"},
|
193
219
|
{"match"=>"/(\\w+)_by_id/", "this"=>"$1_by", "that"=>"$1_$$", pluralize: true},
|
194
|
-
{"match"=>"/(\\w+)_id/", "this"=>"$1", "that"=>"$$"},
|
220
|
+
{"match"=>"/(\\w+)_id/", "this"=>"$1", "that"=>"$$", multi: false},
|
221
|
+
{"match"=>"/(\\w+)_id/", "this"=>"$1", "that"=>"$1_of", multi: true, pluralize: false},
|
195
222
|
{"match"=>"/(\\w+)/", "this"=>"$1", "that"=>"$$"}, # Kind fields w/o explicit 'kind'
|
196
223
|
]
|
197
224
|
initializers.map { |initializer|
|
@@ -205,9 +232,11 @@ module PgGraph
|
|
205
232
|
end
|
206
233
|
|
207
234
|
private
|
235
|
+
|
208
236
|
# +kind+ can be :this or :that
|
209
|
-
def do_match(uid, kind, table: nil)
|
237
|
+
def do_match(uid, kind, multi = true, table: nil)
|
210
238
|
reflections.find { |reflection|
|
239
|
+
next if multi == true && reflection.multi == false
|
211
240
|
match_data = reflection.re.match(uid) or next
|
212
241
|
template = reflection.send(kind).dup
|
213
242
|
if template == false
|
@@ -230,7 +259,8 @@ module PgGraph
|
|
230
259
|
match varchar not null,
|
231
260
|
this varchar,
|
232
261
|
that varchar not null,
|
233
|
-
|
262
|
+
multi boolean,
|
263
|
+
pluralize boolean,
|
234
264
|
default_reflection boolean not null,
|
235
265
|
ordinal integer not null -- zero-based
|
236
266
|
)
|
data/lib/pg_graph/version.rb
CHANGED
data/lib/type/read.rb
CHANGED
@@ -68,9 +68,7 @@ module PgGraph::Type
|
|
68
68
|
meta_table.depending_tables.each { |meta_depending_table|
|
69
69
|
depending_table = dot(meta_depending_table.path)
|
70
70
|
table.depending_tables << depending_table
|
71
|
-
# puts "#{table.uid} -> #{depending_table.uid}"
|
72
71
|
}
|
73
|
-
|
74
72
|
}
|
75
73
|
|
76
74
|
# Create postgres columns except kind_columns
|
@@ -83,16 +81,29 @@ module PgGraph::Type
|
|
83
81
|
**column_options(meta_column))
|
84
82
|
}
|
85
83
|
|
86
|
-
|
87
|
-
# Create and collect forward-references. link_fields is a list of [uid, record_column] tuples
|
84
|
+
# link_fields is a list of [uid, record_column] tuples
|
88
85
|
link_fields = []
|
86
|
+
|
87
|
+
# Map from referencing table to referenced table to reference. It is
|
88
|
+
# used to detect when the default reverse map doesn't work because of
|
89
|
+
# name collisions
|
90
|
+
reference_count = {}
|
91
|
+
|
92
|
+
# Create and collect forward-references
|
89
93
|
(link_columns + kind_columns).each { |record_type, meta_column|
|
94
|
+
reference_count[record_type] ||= {}
|
95
|
+
|
90
96
|
meta_column.references.each { |constraint|
|
91
97
|
constraint.referencing_columns.size == 1 or raise Error, "Can't handle multi-column keys (for now)"
|
92
98
|
type = dot(constraint.referenced_table.path).type.record_type
|
93
99
|
this_link_column = constraint.referencing_columns.first.name
|
94
100
|
that_link_column = constraint.referenced_columns.first.name
|
95
101
|
|
102
|
+
# Count references by running the reflector in the link column (this
|
103
|
+
# is expensive because we're doing it again later)
|
104
|
+
(reference_count[record_type] ||= {})[type] ||= 0
|
105
|
+
reference_count[record_type][type] += 1 if reflector.multi?(this_link_column)
|
106
|
+
|
96
107
|
field =
|
97
108
|
if meta_column.kind?
|
98
109
|
name = reflector.this(meta_column.uid) || meta_column.name
|
@@ -119,10 +130,6 @@ module PgGraph::Type
|
|
119
130
|
}
|
120
131
|
}
|
121
132
|
|
122
|
-
# Detect derived tables
|
123
|
-
# link_fields.each { |uid, record_column|
|
124
|
-
# if record_column.this_link_column.primary_key? && that_link_column.primary_key?
|
125
|
-
|
126
133
|
# Create back-reference fields
|
127
134
|
(link_fields).each { |uid, this_column|
|
128
135
|
this_record_type = this_column.record_type
|
@@ -135,7 +142,8 @@ module PgGraph::Type
|
|
135
142
|
this_column.postgres_column == "id" or raise Error, "Primary keys should be named 'id'"
|
136
143
|
name = this_record_type.name
|
137
144
|
else
|
138
|
-
|
145
|
+
multi = reference_count[this_record_type][that_record_type] > 1
|
146
|
+
name = reflector.that(uid, this_column.unique?, multi, table: this_record_type.name)
|
139
147
|
name ||= PgGraph.inflector.pluralize(this_column.table.name) if this_column.kind?
|
140
148
|
end
|
141
149
|
|
@@ -202,8 +210,8 @@ module PgGraph::Type
|
|
202
210
|
mm_column2 = constraint2.referencing_columns.first.name
|
203
211
|
mm_column2_uid = constraint2.referencing_columns.first.uid
|
204
212
|
|
205
|
-
column1_name = reflector.that(mm_column1_uid, false, table: table2.record_type.name)
|
206
|
-
column2_name = reflector.that(mm_column2_uid, false, table: table1.record_type.name)
|
213
|
+
column1_name = reflector.that(mm_column1_uid, false, false, table: table2.record_type.name)
|
214
|
+
column2_name = reflector.that(mm_column2_uid, false, false, table: table1.record_type.name)
|
207
215
|
|
208
216
|
# FIXME: DAGs over an super table creates problems if reflections
|
209
217
|
# doesn't match (eg. role_id/group_id instead of
|
data/lib/type/type.rb
CHANGED
@@ -33,7 +33,10 @@ module PgGraph::Type
|
|
33
33
|
protected
|
34
34
|
# Nodes with nil keys are not attached. This is used in PgCatalogSchema to
|
35
35
|
# avoid being included in the list of schemas
|
36
|
-
def do_attach(key, child)
|
36
|
+
def do_attach(key, child)
|
37
|
+
!key?(key) or raise PgGraph::Error, "Duplicate fields in #{self.uid}: '#{key}'"
|
38
|
+
super if child
|
39
|
+
end
|
37
40
|
|
38
41
|
# Forward list of methods to object. The arguments should be strings or symbols
|
39
42
|
def self.forward_method(object, *methods)
|
@@ -249,6 +252,7 @@ module PgGraph::Type
|
|
249
252
|
end
|
250
253
|
end
|
251
254
|
|
255
|
+
# FIXME Duplicate code
|
252
256
|
def postgres_columns()
|
253
257
|
@postgres_columns ||= begin
|
254
258
|
cols = fields.map { |field|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg_graph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Claus Rasmussen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-11-
|
11
|
+
date: 2022-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: boolean
|
@@ -240,7 +240,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
240
240
|
- !ruby/object:Gem::Version
|
241
241
|
version: '0'
|
242
242
|
requirements: []
|
243
|
-
rubygems_version: 3.3.
|
243
|
+
rubygems_version: 3.3.7
|
244
244
|
signing_key:
|
245
245
|
specification_version: 4
|
246
246
|
summary: Create graph type model of database
|