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 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