pg_graph 0.2.1 → 0.3.1
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/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
|