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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43d525c39dae62bc1c13b2708ddfaeb1081dba754f5ec698f8611239c2231b18
4
- data.tar.gz: cdbdd1b7a98e5c986bd933dd85f631eaf5d11c9668deedb2d047886cfdc01e20
3
+ metadata.gz: 40e9eaa99c76e5fe7d06806563b0f6c7bf289acff4e6603abc0fa48e72cec3d1
4
+ data.tar.gz: a4292fa743ccd275a5dff4766d3b4c1dbd229e778df3bf891279c4c6d2237530
5
5
  SHA512:
6
- metadata.gz: c4a94970c70daa369e5802c7e4b22a80aad249a41f91f6b3da308d372eb53567d065abdf68d390a7156721a55fc31e29622e701f7ee89b854d5284569a10ba9a
7
- data.tar.gz: 3243ed336473a1409d107147f16e1bd6c634cbad5fbfe81dd35537f12c41c4ec9a49119fb10f9f0b06b8c0b9eec7f0f6b61b7b01410ec150f492f79684bd4272
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
@@ -14,7 +14,19 @@ module PgGraph
14
14
  # included in the model. Can be nil
15
15
  attr_reader :that
16
16
 
17
- # Don't pluralize the result of #that if false. Default false
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 = false, default_reflection = false)
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
- @pluralize = pluralize || pluralize.nil?
64
+ @multi = multi
65
+ @pluralize = pluralize
52
66
  @default_reflection = default_reflection || false
53
67
  end
54
68
 
55
69
  def to_yaml
56
- { match: match, this: this, that: that, pluralize: pluralize, default_reflection: default_reflection }
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 field name is pluralized unless +unique+ is true. The
127
- # :table option can be used to override the table name in '$$' rules. This
128
- # is used in N:M and M:M relations. Returns nil if no match was found or if
129
- # a matching reflection has #continue equal to false
130
- def that(uid, unique, table: nil)
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 != false && unique ? name : PgGraph.inflector.pluralize(name)
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
- pluralize boolean not null,
262
+ multi boolean,
263
+ pluralize boolean,
234
264
  default_reflection boolean not null,
235
265
  ordinal integer not null -- zero-based
236
266
  )
@@ -1,3 +1,3 @@
1
1
  module PgGraph
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.1"
3
3
  end
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
- name = reflector.that(uid, this_column.unique?, table: this_record_type.name)
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) super if child end
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.2.1
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-03 00:00:00.000000000 Z
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.18
243
+ rubygems_version: 3.3.7
244
244
  signing_key:
245
245
  specification_version: 4
246
246
  summary: Create graph type model of database