meta_db 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1e0499ce2a211cd8e953ffd5ce447913f59d7f40d4db6c56b3515d1f0f810d74
4
+ data.tar.gz: cca551c4c6e6537d267ab140f789977cca5ca2a8ccac12d976527780071e3e02
5
+ SHA512:
6
+ metadata.gz: 2d8670680900ef650d36a453904f13e635c996e1634ccbf0d9cf197355652eb84f74cb07405f0a62e632a5ee70f5425381886edfddc1eb8d32c42a5065804ee7
7
+ data.tar.gz: 452f2f681469529b9deb7f7e70ca91cc492c9d7a87d2c0bf22d6c7fd36f7e2e74a1635f6b7a15b80312d793851142fadc5ce9289c62b362e12812b539b07b7db
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ # Ignore Gemfile.lock. See https://stackoverflow.com/questions/4151495/should-gemfile-lock-be-included-in-gitignore
14
+ /Gemfile.lock
15
+
16
+ # Ignore generated yaml and marshall files
17
+ /*.yaml
18
+ /*.marshal
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1 @@
1
+ ruby-2.6.6
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.1
7
+ before_install: gem install bundler -v 1.16.5
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in meta_db.gemspec
6
+ gemspec
@@ -0,0 +1,37 @@
1
+ # MetaDb
2
+
3
+ Models the meta data (schema) of a database. The model implements parts of the
4
+ information schema and extends it with postgresql specific features and
5
+ features for practical use.
6
+
7
+ This is work in progress. Use at your own peril
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'meta_db'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install meta_db
24
+
25
+ ## Usage
26
+
27
+ TODO: Write usage instructions here
28
+
29
+ ## Development
30
+
31
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
32
+
33
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/clrgit/meta_db.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/TODO ADDED
@@ -0,0 +1,12 @@
1
+ o Tests
2
+ o Handle trigger constraints
3
+ o Handle exclusion constraints
4
+ o Handle array types better
5
+
6
+ + Use information_schema
7
+ + Implement Schema
8
+ + Default values
9
+ + Include schemas in [] and lookup on MetaDb::Db
10
+
11
+ - Rename to PgMeta
12
+ - Implement RDBMS
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "meta_db"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,182 @@
1
+ require "meta_db/version"
2
+
3
+ require "indented_io"
4
+
5
+ require 'pg'
6
+ require 'yaml'
7
+
8
+ require 'meta_db/connection.rb'
9
+ require 'meta_db/db_object.rb'
10
+
11
+ module MetaDb
12
+ # TODO
13
+ def self.load_yaml(file)
14
+ YAML.load_file(file)
15
+ end
16
+
17
+ def self.save_yaml(db, file, pretty: true)
18
+ File.open(file, 'w') do |f|
19
+ arg = pretty ? { :indentation => 3 } : {}
20
+ f.write(YAML.dump(db, arg))
21
+ end
22
+ end
23
+
24
+ def self.load_marshal(file)
25
+ File.open(file) { |f| Marshal.load(f) }
26
+ end
27
+
28
+ def self.save_marshal(db, file)
29
+ File.open(file, 'w') { |f| Marshal.dump(db, f) }
30
+ end
31
+
32
+ # The options argument is a hash as PG::Connection options. :host, :port,
33
+ # :dbname, :user, and :password are some of the most used
34
+ def self.load_pg_conn(options)
35
+ PostgresConnection.open(options) { |conn| load_conn(conn) }
36
+ end
37
+
38
+ private
39
+ # Connect to the given database and return a MetaDb::Db object. If host is
40
+ # nil, a socket is used to connect to the database
41
+ #
42
+ # The connection object should support #name and #exec
43
+ def self.load_conn(conn)
44
+ # TODO: Start transaction
45
+
46
+ # Build database
47
+ r = conn.select %(
48
+ select usename::varchar
49
+ from pg_database d
50
+ join pg_user u on u.usesysid = d.datdba
51
+ where d.datname = '#{conn.database}'
52
+ )
53
+ r.size == 1 or raise "Internal error"
54
+ db = MetaDb::Database.new(conn.database, r.each_array.first)
55
+
56
+ # Build schemas
57
+ conn.select(%(
58
+ select schema_name::varchar as name,
59
+ schema_owner::varchar as owner
60
+ from information_schema.schemata
61
+ where schema_name !~ '^pg_' AND schema_name <> 'information_schema'
62
+ )).each_hash { |row|
63
+ row[:database] = db
64
+ MetaDb::Schema.init(row)
65
+ }
66
+
67
+ # Build tables
68
+ conn.select(%(
69
+ select table_schema::varchar as schema,
70
+ table_name::varchar as name,
71
+ table_type::varchar as type,
72
+ is_insertable_into = 'YES' as "insertable?",
73
+ is_typed = 'YES' as "typed?"
74
+ from information_schema.tables
75
+ where table_schema !~ '^pg_' AND table_schema <> 'information_schema'
76
+ )).each_hash { |row|
77
+ row[:schema] = db.dot row[:schema]
78
+ klass = (row[:type] == 'VIEW' ? MetaDb::View : MetaDb::Table)
79
+ klass.init(row)
80
+ }
81
+
82
+ # Build columns
83
+ conn.select(%(
84
+ select table_schema::varchar || '.' || table_name as table,
85
+ ordinal_position as ordinal,
86
+ column_name::varchar as name,
87
+ data_type::varchar as type,
88
+ column_default as default,
89
+ is_identity = 'YES' as "identity?",
90
+ is_generated = 'YES' as "generated?",
91
+ is_nullable = 'YES' as "nullable?",
92
+ is_updatable = 'YES' as "updatable?"
93
+ from information_schema.columns
94
+ where table_schema !~ '^pg_' AND table_schema <> 'information_schema'
95
+ )).each_hash { |row|
96
+ row[:table] = db.dot row[:table]
97
+ MetaDb::Column.init(row)
98
+ }
99
+
100
+ # Build simple constraints
101
+ conn.select(%(
102
+ select c.constraint_type::varchar,
103
+ c.table_schema || '.' || c.table_name as table,
104
+ c.constraint_name::varchar as name,
105
+ cc.check_clause as expression,
106
+ (
107
+ select array_agg(column_name::varchar)
108
+ from information_schema.constraint_column_usage ccu
109
+ where ccu.table_schema = c.table_schema
110
+ and ccu.table_name = c.table_name
111
+ and ccu.constraint_schema = c.constraint_schema
112
+ and ccu.constraint_name = c.constraint_name
113
+ ) as columns
114
+ from information_schema.table_constraints c
115
+ left join information_schema.check_constraints cc
116
+ on cc.constraint_schema = c.table_schema and
117
+ cc.constraint_name = c.constraint_name
118
+ where c.table_schema !~ '^pg_' AND c.table_schema <> 'information_schema'
119
+ and c.constraint_type in ('PRIMARY KEY', 'UNIQUE', 'CHECK')
120
+ )).each_hash { |row|
121
+ row[:table] = db.dot row[:table]
122
+ row[:columns] = lookup_columns(row[:table], row[:columns] || [])
123
+ case row[:constraint_type]
124
+ when "PRIMARY KEY"; MetaDb::PrimaryKeyConstraint.init(row)
125
+ when "UNIQUE"; MetaDb::UniqueConstraint.init(row)
126
+ when "CHECK"; MetaDb::CheckConstraint.init(row)
127
+ else
128
+ raise "Oops"
129
+ end
130
+ }
131
+
132
+ # Build referential constraints
133
+ #
134
+ # Referential constraints has to be initialized after unique constraints
135
+ #
136
+ # The GROUP BY is necessary because we re-assign constraints from schema to
137
+ # table. This requires joining key_column_usage again to get the name of
138
+ # the referenced table and that yields a row for each column in the unique
139
+ # key (TODO: Can this be omitted?)
140
+ #
141
+ conn.select(%(
142
+ select rc.constraint_schema::varchar as schema,
143
+ rc.constraint_name::varchar as name,
144
+ cu_refing.table_schema || '.' || cu_refing.table_name as "referencing_table",
145
+ (
146
+ select array_agg(column_name::varchar order by ordinal_position)
147
+ from information_schema.key_column_usage kcu
148
+ where kcu.constraint_name = rc.constraint_name
149
+ ) as "referencing_columns",
150
+ cu_refed.table_schema || '.' || cu_refed.table_name || '.' || cu_refed.constraint_name
151
+ as "referenced_constraint"
152
+ from information_schema.referential_constraints rc
153
+ join information_schema.key_column_usage cu_refing on
154
+ cu_refing.constraint_schema = rc.constraint_schema
155
+ and cu_refing.constraint_name = rc.constraint_name
156
+ join information_schema.key_column_usage cu_refed on
157
+ cu_refed.constraint_schema = rc.unique_constraint_schema
158
+ and cu_refed.constraint_name = rc.unique_constraint_name
159
+ where cu_refing.table_schema !~ '^pg_' AND cu_refing.table_schema <> 'information_schema'
160
+ group by
161
+ rc.constraint_schema,
162
+ rc.constraint_name,
163
+ cu_refing.table_schema,
164
+ cu_refing.table_name,
165
+ cu_refed.table_schema,
166
+ cu_refed.table_name,
167
+ cu_refed.constraint_name
168
+ )).each_hash { |row|
169
+ row[:schema] = db.dot row[:schema]
170
+ row[:referencing_table] = db.dot row[:referencing_table]
171
+ row[:referencing_columns] = lookup_columns(row[:referencing_table], row[:referencing_columns])
172
+ row[:referenced_constraint] = db.dot row[:referenced_constraint]
173
+ MetaDb::ReferentialConstraint.init(row)
174
+ }
175
+ db
176
+ end
177
+
178
+ def self.lookup_columns(table, column_names)
179
+ column_names.map { |n| table.children[n] }
180
+ end
181
+ end
182
+
@@ -0,0 +1,97 @@
1
+
2
+ module MetaDb
3
+ class Connection
4
+ # In derived classes, no initialization may take place after the call to
5
+ # super because otherwise #initialize would call the user-supplied block
6
+ # with a partial initialized object
7
+ def initialize(options)
8
+ @conn = self.class.connect(options)
9
+ end
10
+
11
+ def self.open(options, &block)
12
+ if block_given?
13
+ begin
14
+ conn = self.new(options)
15
+ return yield(conn)
16
+ ensure
17
+ conn&.close
18
+ end
19
+ else
20
+ self.new(options)
21
+ end
22
+ end
23
+
24
+ def host() raise end
25
+ def port() raise end
26
+ def database() raise end
27
+ def user() raise end
28
+ def password() raise end
29
+
30
+ # Escapes s as a SQL string value
31
+ def escape(s) raise end
32
+
33
+ def execute(sql) raise end
34
+ def select(sql, &block) raise end
35
+
36
+ def close() raise end
37
+ def closed?() raise end
38
+
39
+ private
40
+ def self.connect(options) raise end
41
+ end
42
+
43
+ class Result
44
+ def initialize(result) @result = result end
45
+ def size() raise end
46
+ def each_hash() raise end
47
+ def each_array() raise end
48
+ def to_a() each_array end
49
+ end
50
+
51
+ class PostgresResult < Result
52
+ def size()
53
+ @result.ntuples
54
+ end
55
+
56
+ def each_hash(&block)
57
+ if block_given?
58
+ each_hash.each(&block)
59
+ else
60
+ @result.map { |row| row.map { |field, value| [field.to_sym, value] }.to_h }
61
+ end
62
+ end
63
+
64
+ def each_array(&block)
65
+ if block_given?
66
+ each_array.each(&block)
67
+ else
68
+ @result.each_row
69
+ end
70
+ end
71
+
72
+ def to_a() @result.values end
73
+ end
74
+
75
+ class PostgresConnection < Connection
76
+ def host() @conn.host end
77
+ def port() @conn.port end
78
+ def database() @conn.db end
79
+ def user() @conn.user end
80
+ def password() @conn.pass end
81
+
82
+ def escape(s) @conn.escape_string(s) end
83
+
84
+ def execute(sql) @conn.exec(sql) end
85
+ def select(sql) PostgresResult.new(@conn.exec(sql)) end
86
+
87
+ def close() @conn.finish end
88
+ def closed?() @conn.finished? end
89
+
90
+ private
91
+ def self.connect(options)
92
+ conn = PG::Connection.new(options)
93
+ conn.type_map_for_results = PG::BasicTypeMapForResults.new conn
94
+ conn
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,352 @@
1
+
2
+ require 'meta_db/dump.rb'
3
+
4
+ module MetaDb
5
+ class DbObject
6
+ # Used to multi-assign values and for dump
7
+ #
8
+ # TODO: rename db_attr or 'column', and create read/writer methods
9
+ def self.attrs(*attrs)
10
+ attrs = Array(attrs)
11
+ if attrs.empty?
12
+ @attrs
13
+ else
14
+ @attrs = attrs
15
+ end
16
+ end
17
+
18
+ def self.init(row)
19
+ h = []
20
+ self.attrs.each { |a| h << row[a] if row.key?(a) }
21
+ self.new(*h)
22
+ end
23
+
24
+ attrs :parent, :name, :children
25
+
26
+ # Name of object. Unique within contianing object. Not nil
27
+ attr_reader :name
28
+
29
+ # Parent DbObject. Non-nil except for the top-level MetaDb::Database object
30
+ attr_reader :parent
31
+
32
+ # Hash from name to contained objects
33
+ attr_reader :children
34
+
35
+ def initialize(parent, name)
36
+ @name, @parent = name, parent
37
+ @children = {}
38
+ parent&.send(:attach, self)
39
+ end
40
+
41
+ # Unique dot-separated list of names leading from the top-level MetaDb::Database object to
42
+ # self. Omits the first path-element if strip is true
43
+ def path(strip: false)
44
+ r = []
45
+ node = self
46
+ while node
47
+ r << node.name
48
+ node = node.parent
49
+ end
50
+ r.pop if strip
51
+ r.reverse.compact.join(".")
52
+ end
53
+
54
+ # Return child object with the given name
55
+ def [](name) @children[name] end
56
+
57
+ # Recursively lookup object by dot-separated list of names. If strip is true, the first element
58
+ # will be stripped from the path
59
+ def dot(path, strip: false)
60
+ elems = path.split(".")
61
+ elems.shift if strip
62
+ elems.inject(self) { |a,e| a[e] } or raise "Can't lookup '#{path}' in #{self.path}"
63
+ end
64
+
65
+ # Compare two objects by identity
66
+ def <=>(r) path <=> r.path end
67
+
68
+ # Mostly for debug
69
+ def inspect
70
+ string = StringIO.new
71
+ stdout = $stdout
72
+ begin
73
+ $stdout = string
74
+ dump
75
+ ensure
76
+ $stdout = stdout
77
+ end
78
+ string.string
79
+ end
80
+
81
+ protected
82
+ def constrain_children(klass)
83
+ children.values.select { |v| v.is_a?(klass) }
84
+ end
85
+
86
+ private
87
+ def attach(child)
88
+ !@children.key?(child.name) or raise "Duplicate child key: #{child.name.inspect}"
89
+ child.instance_variable_set(:@parent, self)
90
+ @children[child.name] = child
91
+ end
92
+
93
+ def detach(child)
94
+ @children.key?(child.name) or raise "Non-existing child key: #{child.name.inspect}"
95
+ child.instance_variable_set(:@parent, nil)
96
+ @children.delete(child.name)
97
+ end
98
+ end
99
+
100
+ class Database < DbObject
101
+ attrs :name, :owner, :schemas
102
+
103
+ # List of schemas
104
+ def schemas()
105
+ @schemas ||= children.values
106
+ end
107
+
108
+ # Owner of the database
109
+ attr_reader :owner
110
+
111
+ def initialize(name, owner)
112
+ super(nil, name)
113
+ @owner = owner
114
+ end
115
+ end
116
+
117
+ class Schema < DbObject
118
+ attrs :database, :name, :owner, :tables, :views, :functions, :procedures
119
+
120
+ # Database of the schema
121
+ alias_method :database, :parent
122
+
123
+ # Owner of the schema
124
+ attr_reader :owner
125
+
126
+ # List of tables (and views)
127
+ def tables() @tables ||= constrain_children(MetaDb::Table) end
128
+
129
+ # List of views
130
+ def views() @views ||= constrain_children(MetaDb::View) end
131
+
132
+ # List of functions
133
+ def functions() @functions ||= constrain_children(MetaDb::Function) end
134
+
135
+ # List of procedures
136
+ def procedures() @procedures ||= constrain_children(MetaDb::Procedure) end
137
+
138
+ def initialize(database, name, owner)
139
+ super(database, name)
140
+ @owner = owner
141
+ end
142
+ end
143
+
144
+ class Table < DbObject
145
+ attrs \
146
+ :schema, :name, :type, :table?, :view?, :insertable?, :typed?, :columns,
147
+ :primary_key_columns, :constraints, :referential_constraints, :triggers
148
+
149
+ # Schema of the table. Redefines #parent
150
+ alias_method :schema, :parent
151
+
152
+ # Type of table. Either 'BASE TABLE' or 'VIEW'
153
+ attr_reader :type
154
+
155
+ # True iff table is a real table and not a view
156
+ def table?() true end
157
+
158
+ # True iff table is a view
159
+ def view?() !table? end
160
+
161
+ # True if the table/view is insertable
162
+ def insertable?() @is_insertable end
163
+
164
+ # True if the table/view is typed
165
+ def typed?() @is_typed end
166
+
167
+ # List of columns. Columns are sorted by ordinal
168
+ def columns() @columns ||= constrain_children(MetaDb::Column).sort end
169
+
170
+ # The primary key column. nil if the table has multiple primary key columns
171
+ def primary_key_column
172
+ return @primary_key_column if @primary_key_column != :undefined
173
+ if primary_key_columns.size == 1
174
+ @primary_key_column = primary_key_columns.first
175
+ else
176
+ @primary_key_column = nil
177
+ end
178
+ end
179
+
180
+ # List of primary key columns
181
+ #
182
+ # Note: Assigned by PrimaryKeyConstraint#initialize
183
+ attr_reader :primary_key_columns
184
+
185
+ # List of constraints
186
+ def constraints()
187
+ @constraints ||= constrain_children(MetaDb::Constraint)
188
+ end
189
+
190
+ # List of referential constraints
191
+ def referential_constraints()
192
+ @referential_constraints ||= constrain_children(MetaDb::ReferentialConstraint)
193
+ end
194
+
195
+ # List of triggers
196
+ def triggers() @triggers ||= constrain_children(MetaDb::Trigger) end
197
+
198
+ def initialize(schema, name, type, is_insertable, is_typed)
199
+ super(schema, name)
200
+ @type, @is_insertable, @is_typed = type, is_insertable, is_typed
201
+ @primary_key_column = :undefined
202
+ @primary_key_columns = []
203
+ end
204
+
205
+ end
206
+
207
+ class View < Table
208
+ def table?() false end
209
+ end
210
+
211
+ class Column < DbObject
212
+ attrs \
213
+ :table, :ordinal, :name, :type, :default, :identity?, :generated?,
214
+ :nullable?, :updatable?, :primary_key?
215
+
216
+ # Table of the column
217
+ alias_method :table, :parent
218
+
219
+ # Ordinal number of the column
220
+ attr_reader :ordinal
221
+
222
+ # Type of the column
223
+ attr_reader :type
224
+
225
+ # Default value
226
+ attr_reader :default
227
+
228
+ # True if column is an identity column
229
+ def identity?() @is_identity end
230
+
231
+ # True if column is auto generated
232
+ def generated?() @is_generated end
233
+
234
+ # True if column is nullable
235
+ def nullable?() @is_nullable end
236
+
237
+ # True if column is updatable
238
+ def updatable?() @is_updatable end
239
+
240
+ # True if column is the single primary key of the table. Always false for tables
241
+ # with multiple primary keys
242
+ def primary_key?() table.table? && self == table.primary_key_column end
243
+
244
+ # True is column is (part of) the primary key of the table
245
+ def primary_key_column?() table.table? && table.primary_key_columns.include?(self) end
246
+
247
+ def initialize(table, ordinal, name, type, default, is_identity, is_generated, is_nullable, is_updatable)
248
+ super(table, name)
249
+ @type, @ordinal, @default, @is_identity, @is_generated, @is_nullable, @is_updatable =
250
+ type, ordinal, default, is_identity, is_generated, is_nullable, is_updatable
251
+ end
252
+
253
+ # Compare columns by table and ordinal
254
+ def <=>(other)
255
+ if other.is_a?(Column) && table == other.table
256
+ ordinal <=> other.ordinal
257
+ else
258
+ super
259
+ end
260
+ end
261
+ end
262
+
263
+ class Constraint < DbObject
264
+ attrs :table, :name, :columns
265
+
266
+ # Table of the constraint
267
+ alias_method :table, :parent
268
+
269
+ # List of columns in the constraint. Empty for CheckConstraint objects
270
+ attr_reader :columns
271
+
272
+ # Constraint kind. Either :primary_key, :foreign_key, :unique, or :check
273
+ def kind() CONSTRAINT_KINDS[self.class] end
274
+
275
+ def initialize(table, name, columns)
276
+ super(table, name)
277
+ @columns = columns
278
+ end
279
+ end
280
+
281
+ class PrimaryKeyConstraint < Constraint
282
+ attrs :table, :name, :columns
283
+
284
+ def initialize(table, name, columns)
285
+ super
286
+ columns.each { |c| c.table.primary_key_columns << c }
287
+ end
288
+ end
289
+
290
+ class UniqueConstraint < Constraint
291
+ attrs :table, :name, :columns
292
+ end
293
+
294
+ # Note that #columns is always empty for check constraints
295
+ class CheckConstraint < Constraint
296
+ attrs :table, :name, :expression
297
+
298
+ # SQL check expression
299
+ attr_reader :expression
300
+
301
+ # Half-baked SQL-to-ruby expression transpiler
302
+ def ruby_expression # Very simple
303
+ @ruby ||= sql.sub(/\((.*)\)/, "\\1").gsub(/\((\w+) IS NOT NULL\)/, "!\\1.nil?").gsub(/ OR /, " || ")
304
+ end
305
+
306
+ def initialize(table, name, expression)
307
+ super(table, name, [])
308
+ @expression = expression
309
+ end
310
+ end
311
+
312
+ class ReferentialConstraint < Constraint
313
+ attrs :referencing_table, :name, :referencing_columns, :referenced_constraint
314
+
315
+ # The referencing tabla
316
+ alias_method :referencing_table, :table
317
+
318
+ # The referencing columns. Can't be empty
319
+ alias_method :referencing_columns, :columns
320
+
321
+ # The referenced constraint
322
+ attr_reader :referenced_constraint
323
+
324
+ # The referenced table
325
+ def referenced_table() referenced_constraint.table end
326
+
327
+ # The referenced columns
328
+ def referenced_columns() referenced_constraints.columns end
329
+
330
+ def initialize(referencing_table, name, referencing_columns, referenced_constraint)
331
+ super(referencing_table, name, referencing_columns)
332
+ @referenced_constraint = referenced_constraint
333
+ end
334
+ end
335
+
336
+ class Function < DbObject
337
+ end
338
+
339
+ class Procedure < DbObject
340
+ end
341
+
342
+ class Trigger < DbObject
343
+ end
344
+
345
+ CONSTRAINT_KINDS = {
346
+ PrimaryKeyConstraint => :primary_key,
347
+ ReferentialConstraint => :foreign_key,
348
+ UniqueConstraint => :unique,
349
+ CheckConstraint => :check
350
+ }
351
+ end
352
+
@@ -0,0 +1,31 @@
1
+
2
+ require 'indented_io'
3
+
4
+ module MetaDb
5
+ class DbObject
6
+ def dump
7
+ puts self.class.to_s
8
+ dump_attrs
9
+ end
10
+
11
+ def dump_attrs(*attrs)
12
+ attrs = Array(attrs)
13
+ attrs = self.class.attrs if attrs.empty?
14
+ indent {
15
+ for attr in Array(attrs)
16
+ value = self.send(attr)
17
+ case value
18
+ when Array
19
+ puts "#{attr}:"
20
+ indent { value.each { |v| v.dump } }
21
+ when DbObject
22
+ puts "#{attr}: #{value.name} (#{value.class})"
23
+ else
24
+ puts "#{attr}: #{self.send(attr).inspect}"
25
+ end
26
+ end
27
+ }
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,3 @@
1
+ module MetaDb
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,40 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "meta_db/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "meta_db"
8
+ spec.version = MetaDb::VERSION
9
+ spec.authors = ["Claus Rasmussen"]
10
+ spec.email = ["claus.l.rasmussen@gmail.com"]
11
+
12
+ spec.summary = %q{Meta model of database}
13
+ spec.description = %q{Reads in the information schema of a database and models it as ruby objects}
14
+ spec.homepage = "http://www.nowhere.com/meta_db"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
20
+ else
21
+ raise "RubyGems 2.0 or newer is required to protect against " \
22
+ "public gem pushes."
23
+ end
24
+
25
+ # Specify which files should be added to the gem when it is released.
26
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
28
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ spec.add_development_dependency "bundler", "~> 1.16"
35
+ spec.add_development_dependency "rake", "~> 10.0"
36
+ spec.add_development_dependency "rspec", "~> 3.0"
37
+
38
+ spec.add_dependency "indented_io"
39
+ spec.add_dependency "pg"
40
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: meta_db
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Claus Rasmussen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-07-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: indented_io
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pg
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Reads in the information schema of a database and models it as ruby objects
84
+ email:
85
+ - claus.l.rasmussen@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".ruby-version"
93
+ - ".travis.yml"
94
+ - Gemfile
95
+ - README.md
96
+ - Rakefile
97
+ - TODO
98
+ - bin/console
99
+ - bin/setup
100
+ - lib/meta_db.rb
101
+ - lib/meta_db/connection.rb
102
+ - lib/meta_db/db_object.rb
103
+ - lib/meta_db/dump.rb
104
+ - lib/meta_db/version.rb
105
+ - meta_db.gemspec
106
+ homepage: http://www.nowhere.com/meta_db
107
+ licenses: []
108
+ metadata:
109
+ allowed_push_host: https://rubygems.org
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubygems_version: 3.0.8
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Meta model of database
129
+ test_files: []