dbagent 3.5.0 → 3.6.0

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: d63bf0c7e2699b356813cd4d71eb28a812a0432e5dc39f8b3b4097b0e63e0664
4
- data.tar.gz: 19b4815533138bd1a3544366bde4b7ee40daeb963268836c956a1b5abbd38915
3
+ metadata.gz: 487cd25271375faf22bf6f47cc79b839c825cb3a377a16fc330b01ca78954078
4
+ data.tar.gz: dc64a2f4519365470a2e2641c788b6a5ea04b6fbc0d1406f7955e97ba9920218
5
5
  SHA512:
6
- metadata.gz: 19d9883a82ad142391cbf3fdb0c06177ddb3b72495c0ce3d48aaaa178a3bbf7529692a6c8622e17daf70807e848e6dce288026a041e520fb06cf30daccbe088a
7
- data.tar.gz: be304ecb123c8933fa16f606178fb65d3a29d0eedec042dae449a39a092eae371dad06efe78625cdc83a1011eea5c78c7889c904ac6f27c5434a886de9d6a6e1
6
+ metadata.gz: 9185c3155c82d38a668f1ccbaf857ef07f847787f6d4b4dd56620d7b97d5e42cf834658b2fda29577c81fc4f6d30bc4c533cd510d5e9b91c453f42c3b8745106
7
+ data.tar.gz: 58bea5156caa6c070cb5582396d1ead8583485e8055ea067c837a6a7a187771e32f0a7d489eda5ecba99e6bde573387087cbd4dd62dad3648c855709d889ab49
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dbagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-19 00:00:00.000000000 Z
11
+ date: 2023-10-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -189,32 +189,12 @@ email: blambeau@gmail.com
189
189
  executables: []
190
190
  extensions: []
191
191
  extra_rdoc_files: []
192
- files:
193
- - Gemfile
194
- - LICENSE.md
195
- - README.md
196
- - Rakefile
197
- - lib/db_agent.rb
198
- - lib/db_agent/db_handler.rb
199
- - lib/db_agent/db_handler/mssql.rb
200
- - lib/db_agent/db_handler/mysql.rb
201
- - lib/db_agent/db_handler/postgresql.rb
202
- - lib/db_agent/seeder.rb
203
- - lib/db_agent/table_orderer.rb
204
- - lib/db_agent/version.rb
205
- - lib/db_agent/viewpoint.rb
206
- - lib/db_agent/viewpoint/base.rb
207
- - lib/db_agent/viewpoint/delegate.rb
208
- - lib/db_agent/viewpoint/typecheck.rb
209
- - lib/db_agent/webapp.rb
210
- - tasks/db.rake
211
- - tasks/gem.rake
212
- - tasks/test.rake
192
+ files: []
213
193
  homepage: http://github.com/enspirit/dbagent
214
194
  licenses:
215
195
  - MIT
216
196
  metadata: {}
217
- post_install_message:
197
+ post_install_message:
218
198
  rdoc_options: []
219
199
  require_paths:
220
200
  - lib
@@ -229,8 +209,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
229
209
  - !ruby/object:Gem::Version
230
210
  version: '0'
231
211
  requirements: []
232
- rubygems_version: 3.4.6
233
- signing_key:
212
+ rubygems_version: 3.3.26
213
+ signing_key:
234
214
  specification_version: 4
235
215
  summary: A tool to migrate, spy and seed relational databases.
236
216
  test_files: []
data/Gemfile DELETED
@@ -1,2 +0,0 @@
1
- source 'http://rubygems.org'
2
- gemspec
data/LICENSE.md DELETED
@@ -1,22 +0,0 @@
1
- # The MIT Licence
2
-
3
- Copyright (c) 2019 - Enspirit SPRL (Bernard Lambeau)
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md DELETED
@@ -1,122 +0,0 @@
1
- [![Integration](https://github.com/enspirit/dbagent/actions/workflows/integration.yml/badge.svg?branch=master)](https://github.com/enspirit/dbagent/actions/workflows/integration.yml)
2
-
3
- # DbAgent, a ruby tool to migrate, spy and seed relational databases
4
-
5
- DbAgent helps managing a relational database lifecyle through three main tools:
6
-
7
- * Migrations: powered by [Sequel](http://sequel.jeremyevans.net/), migrate as simply as `rake db:migrate`. Supports both superuser and normal user migrations.
8
-
9
- * Spy: using [Shemaspy](http://schemaspy.sourceforge.net/), get your database schema browsable at any moment, through a simple web interface.
10
-
11
- * Seed: maintain, install and flush database content as datasets, organized hierarchically in .json files. Very handy for automated tests, for instance.
12
-
13
- ## Get started using Docker
14
-
15
- DbAgent is expected to be used as its Docker agent, available as `enspirit/dbagent`. Simply mount migrations and data folders, and you're ready to go.
16
-
17
- See the examples folder for details.
18
-
19
- ## Available environment variables
20
-
21
- * `DBAGENT_ROOT_FOLDER` Main folder where data, migrations and viewpoints can be found
22
- * `DBAGENT_LOGLEVEL` Log level to use for dbagent messages (defaults to `WARN`)
23
- * `DBAGENT_LOGSQL` Low Sequel's SQL queries (defaults to `no`)
24
- * `DBAGENT_ADAPTER` Sequel's adapter (defaults to `postgres`)
25
- * `DBAGENT_HOST` Database server host (defaults to `localhost`)
26
- * `DBAGENT_PORT` Database server port (defaults to `5432`)
27
- * `DBAGENT_DB` Database name (defaults to `suppliers-and-parts`)
28
- * `DBAGENT_USER` Database user (defaults to `dbagent`)
29
- * `DBAGENT_PASSWORD` Database password (defaults to `dbagent`)
30
- * `DBAGENT_SOCKET` Database server socket (if host/port is not used)
31
- * `DBAGENT_SUPER_USER` Superuser name (postgres only)
32
- * `DBAGENT_SUPER_DB` Superuser database (postgres only)
33
- * `DBAGENT_SUPER_PASSWORD` Superuser password (postgres only)
34
- * `DBAGENT_WAIT_TIMEOUT_IN_SEC` Timeout in seconds before db:wait_server and db:wait give up
35
- * `DBAGENT_VIEWPOINT` Bmg viewpoint (class name) when using db:flush
36
-
37
- ## Available rake tasks
38
-
39
- The following rake tasks helps you managing the database. They must typically be executed on the docker container.
40
-
41
- ```
42
- rake db:check-seeds # Checks that all seeds can be installed correctly
43
- rake db:create # Creates an fresh new user & database (USE WITH CARE)
44
- rake db:drop # Drops the user & database (USE WITH CARE)
45
- rake db:flush[to] # Flushes the database as a particular data set
46
- rake db:migrate # Runs migrations on the current database
47
- rake db:ping # Pings the database, making sure everything's ready for migration
48
- rake db:rebuild # Rebuilds the database from scratch (USE WITH CARE)
49
- rake db:repl # Opens a database REPL
50
- rake db:seed[from] # Seeds the database with a particular data set
51
- rake db:spy # Dumps the schema documentation into database/schema
52
- rake db:backup # Makes a database backup to the backups folder
53
- rake db:restore[match] # Restore the last matching database backup file from backups folder
54
- rake db:revive # Shortcut for both db:restore and db:migrate
55
- rake db:wait_server # Waits until the postgresql host seems available
56
- rake db:wait # Waits until the postgresql database seems available
57
- rake db:tables # List tables with those with fewer dependencies first
58
- rake db:dependencies[of] # List tables that depend of a given one
59
- ```
60
-
61
- ## Available webservices
62
-
63
- ```
64
- GET /schema/ # Browser the database schema (requires a former `rake db:spy`)
65
- POST /seeds/install?id=... # Install a particular dataset, id is the name of a folder in `data` folder
66
- POST /seeds/flush?id=... # Flushes the current database content as a named dataset
67
- ```
68
-
69
- ## Hacking on dbagent
70
-
71
- ### Installing the library
72
-
73
- ```
74
- bundle install
75
- ```
76
-
77
- ### Preparing your computer
78
-
79
- The tests require a valid PostgreSQL installation with the suppliers-and-parts
80
- database installed. A `dbagent` user would be needed on the PostgreSQL installation
81
- to bootstrap the process.
82
-
83
- ```
84
- sudo su postgres -c 'createuser --createdb dbagent -P'
85
- ```
86
-
87
- DbAgent tries to connect to the suppliers-and-parts with a dbagent/dbagent user/password
88
- pair by default. If you change the database name, user, or password please adapt the
89
- environment variables accordingly in the commands below.
90
-
91
- ### Installing the example database
92
-
93
- ```
94
- DBAGENT_ROOT_FOLDER=examples/suppliers-and-parts bundle exec rake db:create db:migrate db:seed['base']
95
- ```
96
- ### Running test
97
-
98
- To run the test you need to have `Docker` on your computer.
99
-
100
- Run:
101
- ```
102
- make test
103
- ```
104
-
105
- Don't forget to delete created ressources for the tests bun running:
106
- ```
107
- make clean
108
- ```
109
-
110
- ## Contribute
111
-
112
- Please use github issues and pull requests for all questions, bug reports,
113
- and contributions. Don't hesitate to get in touch with us with an early code
114
- spike if you plan to add non trivial features.
115
-
116
- ## Licence
117
-
118
- This software is distributed by Enspirit SRL under a MIT Licence. Please
119
- contact Bernard Lambeau (blambeau@gmail.com) with any question.
120
-
121
- Enspirit (https://enspirit.be) and Klaro App (https://klaro.cards) are both
122
- actively using and contributing to the library.
data/Rakefile DELETED
@@ -1,16 +0,0 @@
1
- require 'path'
2
-
3
- def shell(*cmds)
4
- cmd = cmds.join("\n")
5
- puts cmd
6
- system cmd
7
- end
8
-
9
- #
10
- # Install all tasks found in tasks folder
11
- #
12
- # See .rake files there for complete documentation.
13
- #
14
- Dir["tasks/*.rake"].each do |taskfile|
15
- load taskfile
16
- end
@@ -1,43 +0,0 @@
1
- module DbAgent
2
- class DbHandler
3
- class MSSQL < DbHandler
4
-
5
- def create
6
- raise
7
- end
8
-
9
- def drop
10
- raise
11
- end
12
-
13
- def backup
14
- raise
15
- end
16
-
17
- def repl
18
- raise
19
- end
20
-
21
- def spy
22
- spy_jar = DbAgent._!('vendor').glob('schema*.jar').first
23
- jdbc_jar = DbAgent._!('vendor').glob('mssql*.jar').first
24
- cmd = ""
25
- cmd << %Q{java -jar #{spy_jar}}
26
- cmd << %Q{ -dp #{jdbc_jar} -t mssql05}
27
- cmd << %Q{ -host #{config[:host]}}
28
- cmd << %Q{ -u #{config[:user]}}
29
- cmd << %Q{ -p #{config[:password]}}
30
- cmd << %Q{ -db #{config[:database]}}
31
- cmd << %Q{ -port #{config[:port]}}
32
- cmd << %Q{ -s dbo}
33
- cmd << %Q{ -o #{schema_folder}/spy}
34
- cmd << %Q{ #{ENV['SCHEMA_SPY_ARGS']}} if ENV['SCHEMA_SPY_ARGS']
35
- system(cmd)
36
- system %Q{open #{schema_folder}/spy/index.html}
37
- end
38
-
39
- def restore(t, args)
40
- end
41
- end # MSSQL
42
- end # DbHandler
43
- end # DbAgent
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DbAgent
4
- class DbHandler
5
- class MySQL < DbHandler
6
- def create
7
- raise
8
- end
9
-
10
- def drop
11
- raise
12
- end
13
-
14
- def backup
15
- datetime = Time.now.strftime('%Y%m%dT%H%M%S')
16
- shell mysqldump(config[:database], "> #{backup_folder}/backup-#{datetime}.sql")
17
- end
18
-
19
- def repl
20
- shell mysql(config[:database])
21
- end
22
-
23
- def spy
24
- spy_jar = DbAgent._!('vendor').glob('schema*.jar').first
25
- jdbc_jar = DbAgent._!('vendor').glob('mysql*.jar').first
26
- cmd = ""
27
- cmd << %Q{java -jar #{spy_jar}}
28
- cmd << %Q{ -dp #{jdbc_jar} -t mysql}
29
- cmd << %Q{ -host #{config[:host]}}
30
- cmd << %Q{ -u #{config[:user]}}
31
- cmd << %Q{ -p #{config[:password]}}
32
- cmd << %Q{ -db #{config[:database]}}
33
- cmd << %Q{ -port #{config[:port]}}
34
- cmd << %Q{ -s public}
35
- cmd << %Q{ -o #{schema_folder}/spy}
36
- cmd << %Q{ #{ENV['SCHEMA_SPY_ARGS']}} if ENV['SCHEMA_SPY_ARGS']
37
- system(cmd)
38
- system %Q{open #{schema_folder}/spy/index.html}
39
- end
40
-
41
- def restore(_t, args)
42
- candidates = backup_folder.glob('*.sql').sort
43
- if args[:pattern] && rx = Regexp.new(args[:pattern])
44
- candidates = candidates.select { |f| f.basename.to_s =~ rx }
45
- end
46
- file = candidates.last
47
- shell mysql(config[:database], '<', file.to_s)
48
- end
49
-
50
- private
51
-
52
- def mysql_cmd(cmd, *args)
53
- conf = config
54
- %(#{cmd} -h #{config[:host]} --password=#{config[:password]} -P #{config[:port]} -u #{config[:user]} #{args.join(' ')})
55
- end
56
-
57
- def mysql(*args)
58
- mysql_cmd('mysql', *args)
59
- end
60
-
61
- def mysqldump(*args)
62
- mysql_cmd('mysqldump', *args)
63
- end
64
- end # MySQL
65
- end # DbHandler
66
- end # DbAgent
@@ -1,70 +0,0 @@
1
- module DbAgent
2
- class DbHandler
3
- class PostgreSQL < DbHandler
4
-
5
- def create
6
- shell pg_cmd("createuser","--no-createdb","--no-createrole","--no-superuser","--no-password",config[:user]),
7
- pg_cmd("createdb","--owner=#{config[:user]}", config[:database])
8
- end
9
-
10
- def drop
11
- shell pg_cmd("dropdb", config[:database]),
12
- pg_cmd("dropuser", config[:user])
13
- end
14
-
15
- def backup
16
- datetime = Time.now.strftime("%Y%m%dT%H%M%S")
17
- shell pg_dump("--clean", config[:database], "> #{backup_folder}/backup-#{datetime}.sql")
18
- end
19
-
20
- def repl
21
- shell pg_cmd('psql', config[:database])
22
- end
23
-
24
- def spy
25
- spy_jar = DbAgent._!('vendor').glob('schema*.jar').first
26
- jdbc_jar = DbAgent._!('vendor').glob('postgresql*.jar').first
27
- cmd = ""
28
- cmd << %Q{java -jar #{spy_jar}}
29
- cmd << %Q{ -dp #{jdbc_jar} -t pgsql}
30
- cmd << %Q{ -host #{config[:host]}}
31
- cmd << %Q{ -port #{config[:port]}} if config[:port]
32
- cmd << %Q{ -u #{config[:user]}}
33
- cmd << %Q{ -p #{config[:password]}} if config[:password]
34
- cmd << %Q{ -db #{config[:database]}}
35
- cmd << %Q{ -s public}
36
- cmd << %Q{ -o #{schema_folder}/spy}
37
- cmd << %Q{ #{ENV['SCHEMA_SPY_ARGS']}} if ENV['SCHEMA_SPY_ARGS']
38
- system(cmd)
39
- system %Q{open #{schema_folder}/spy/index.html}
40
- end
41
-
42
- def restore(t, args)
43
- candidates = backup_folder.glob("*.sql").sort
44
- if args[:pattern] && rx = Regexp.new(args[:pattern])
45
- candidates = candidates.select{|f| f.basename.to_s =~ rx }
46
- end
47
- file = candidates.last
48
- shell pg_cmd('psql', config[:database], '<', file.to_s)
49
- end
50
-
51
- private
52
-
53
- def pg_cmd(cmd, *args)
54
- %Q{#{cmd} -h #{config[:host]} -p #{config[:port]} -U #{config[:user]} #{args.join(' ')}}
55
- end
56
-
57
- def psql(*args)
58
- cmd = "psql"
59
- cmd = "PGPASSWORD=#{config[:password]} #{cmd}" if config[:password]
60
- pg_cmd(cmd, *args)
61
- end
62
-
63
- def pg_dump(*args)
64
- cmd = "pg_dump"
65
- cmd = "PGPASSWORD=#{config[:password]} #{cmd}" if config[:password]
66
- pg_cmd(cmd, *args)
67
- end
68
- end # class PostgreSQL
69
- end # module DbHandler
70
- end # module DbAgent
@@ -1,144 +0,0 @@
1
- module DbAgent
2
- class DbHandler
3
-
4
- def initialize(options)
5
- @config = options[:config]
6
- @superconfig = options[:superconfig]
7
- @root_folder = options[:root]
8
- @backup_folder = options[:backup] || options[:root]/'backups'
9
- @schema_folder = options[:schema] || options[:root]/'schema'
10
- @migrations_folder = options[:migrations] || options[:root]/'migrations'
11
- @data_folder = options[:data] || options[:root]/'data'
12
- @viewpoints_folder = options[:viewpoints] || options[:root]/'viewpoints'
13
- require_viewpoints!
14
- end
15
- attr_reader :config, :superconfig
16
- attr_reader :backup_folder, :schema_folder, :migrations_folder
17
- attr_reader :data_folder, :viewpoints_folder
18
-
19
- def ping
20
- puts "Using #{config}"
21
- sequel_db.test_connection
22
- puts "Everything seems fine!"
23
- end
24
-
25
- def create
26
- raise NotImplementedError
27
- end
28
-
29
- def drop
30
- raise NotImplementedError
31
- end
32
-
33
- def backup
34
- raise NotImplementedError
35
- end
36
-
37
- def repl
38
- raise NotImplementedError
39
- end
40
-
41
- def wait_server
42
- require 'net/ping'
43
- raise "No host found" unless config[:host]
44
- check = Net::Ping::External.new(config[:host])
45
- print "Trying to ping `#{config[:host]}`\n"
46
- wait_timeout_in_seconds.downto(0) do |i|
47
- print "."
48
- if check.ping?
49
- print "\nServer found.\n"
50
- break
51
- elsif i == 0
52
- print "\n"
53
- raise "Server not found, I give up."
54
- else
55
- sleep(1)
56
- end
57
- end
58
- end
59
-
60
- def wait
61
- print "Using #{config}\n"
62
- wait_timeout_in_seconds.downto(0) do |i|
63
- print "."
64
- begin
65
- sequel_db.test_connection
66
- print "\nDatabase is there. Great.\n"
67
- break
68
- rescue Sequel::Error
69
- if i==0
70
- print "\n"
71
- raise
72
- end
73
- sleep(1)
74
- end
75
- end
76
- end
77
-
78
- def restore(t, args)
79
- raise NotImplementedError
80
- end
81
-
82
- def migrate(version = nil)
83
- Sequel.extension :migration
84
- if (sf = migrations_folder/'superuser').exists?
85
- Sequel::Migrator.run(sequel_superdb, migrations_folder/'superuser', table: 'superuser_migrations', target: version)
86
- end
87
- Sequel::Migrator.run(sequel_db, migrations_folder, target: version)
88
- end
89
-
90
- def repl
91
- raise NotImplementedError
92
- end
93
-
94
- def spy
95
- raise NotImplementedError
96
- end
97
-
98
- def self.factor(options)
99
- case options[:config][:adapter]
100
- when 'postgres'
101
- PostgreSQL.new(options)
102
- when 'mssql'
103
- MSSQL.new(options)
104
- when /mysql/
105
- MySQL.new(options)
106
- else
107
- PostgreSQL.new(options)
108
- end
109
- end
110
-
111
- def sequel_db
112
- @sequel_db ||= ::Sequel.connect(config)
113
- end
114
-
115
- def sequel_superdb
116
- raise "No superconfig set" if superconfig.nil?
117
- @sequel_superdb ||= ::Sequel.connect(superconfig)
118
- end
119
-
120
- def system(cmd, *args)
121
- puts cmd
122
- ::Kernel.system(cmd, *args)
123
- end
124
-
125
- def require_viewpoints!
126
- f = viewpoints_folder.expand_path
127
- Path.require_tree(f) if f.directory?
128
- end
129
-
130
- private
131
-
132
- def wait_timeout_in_seconds
133
- (ENV['DBAGENT_WAIT_TIMEOUT_IN_SEC'] || '15').to_i
134
- end
135
-
136
- def print(*args)
137
- super.tap{ $stdout.flush }
138
- end
139
-
140
- end # class DbHandler
141
- end # module DbAgent
142
- require_relative 'db_handler/postgresql'
143
- require_relative 'db_handler/mssql'
144
- require_relative 'db_handler/mysql'
@@ -1,167 +0,0 @@
1
- module DbAgent
2
- class Seeder
3
-
4
- def initialize(handler)
5
- @handler = handler
6
- end
7
- attr_reader :handler
8
-
9
- def install(from)
10
- handler.sequel_db.transaction do
11
- before_seeding!
12
-
13
- folder = handler.data_folder/from
14
-
15
- # load files in order
16
- pairs = merged_data(from)
17
- names = pairs.keys.sort{|p1,p2|
18
- pairs[p1].basename <=> pairs[p2].basename
19
- }
20
-
21
- # Truncate tables
22
- names.reverse.each do |name|
23
- LOGGER.info("Emptying table `#{name}`")
24
- handler.sequel_db[name.to_sym].delete
25
- end
26
-
27
- # Fill them
28
- names.each do |name|
29
- LOGGER.info("Filling table `#{name}`")
30
- file = pairs[name]
31
- handler.sequel_db[name.to_sym].multi_insert(file.load)
32
- end
33
-
34
- after_seeding!(folder)
35
- end
36
- end
37
-
38
- def insert_script(from)
39
- folder = handler.data_folder/from
40
-
41
- # load files in order
42
- pairs = merged_data(from)
43
- names = pairs.keys.sort{|p1,p2|
44
- pairs[p1].basename <=> pairs[p2].basename
45
- }
46
-
47
- # Fill them
48
- names.each do |name|
49
- file = pairs[name]
50
- data = file.load
51
- next if data.empty?
52
-
53
- keys = data.first.keys
54
- values = data.map{|t|
55
- keys.map{|k| t[k] }
56
- }
57
- puts handler.sequel_db[name.to_sym].multi_insert_sql(keys, values)
58
- end
59
- end
60
-
61
- def flush_empty(to = "empty")
62
- target = (handler.data_folder/to).rm_rf.mkdir_p
63
- (target/"metadata.json").write <<-JSON.strip
64
- {}
65
- JSON
66
- TableOrderer.new(handler).tsort.each_with_index do |table_name, index|
67
- (target/"#{(index*10).to_s.rjust(5,"0")}-#{table_name}.json").write("[]")
68
- end
69
- end
70
-
71
- def flush(to)
72
- target = (handler.data_folder/to).rm_rf.mkdir_p
73
- source = (handler.data_folder/"empty")
74
- (target/"metadata.json").write <<-JSON.strip
75
- { "inherits": "empty" }
76
- JSON
77
- seed_files(source).each do |f|
78
- flush_seed_file(f, to)
79
- end
80
- end
81
-
82
- def flush_seed_file(f, to)
83
- target = (handler.data_folder/to)
84
- table = file2table(f)
85
- flush_table(table, target, f.basename, true)
86
- end
87
-
88
- def flush_table(table_name, target_folder, file_name, skip_empty)
89
- data = viewpoint.send(table_name.to_sym).to_a
90
- if data.empty? && skip_empty
91
- LOGGER.info("Skipping table `#{table_name}` since empty")
92
- else
93
- LOGGER.info("Flushing table `#{table_name}`")
94
- json = JSON.pretty_generate(data)
95
- (target_folder/file_name).write(json)
96
- end
97
- end
98
-
99
- def each_seed(install = true)
100
- handler.data_folder.glob('**/*') do |file|
101
- next unless file.directory?
102
- next unless (file/"metadata.json").exists?
103
-
104
- base = file.relative_to(handler.data_folder)
105
- begin
106
- Seeder.new(handler).install(base)
107
- puts "#{base} OK"
108
- yield(self, file) if block_given?
109
- rescue => ex
110
- puts "KO on #{file}"
111
- puts ex.message
112
- end if install
113
- end
114
- end
115
-
116
- private
117
-
118
- def before_seeding!
119
- file = handler.data_folder/"before_seeding.sql"
120
- return unless file.exists?
121
-
122
- handler.sequel_db.execute(file.read)
123
- end
124
-
125
- def after_seeding!(folder)
126
- file = folder/"after_seeding.sql"
127
- handler.sequel_db.execute(file.read) if file.exists?
128
- after_seeding!(folder.parent) unless folder == handler.data_folder
129
- end
130
-
131
- def merged_data(from)
132
- folder = handler.data_folder/from
133
- data = {}
134
-
135
- # load metadata and install parent dataset if any
136
- metadata = (folder/"metadata.json").load
137
- if parent = metadata["inherits"]
138
- data = merged_data(parent)
139
- end
140
-
141
- seed_files(folder).each do |f|
142
- data[file2table(f)] = f
143
- end
144
-
145
- data
146
- end
147
-
148
- def seed_files(folder)
149
- folder
150
- .glob("*.json")
151
- .reject{|f| f.basename.to_s =~ /^metadata/ }
152
- end
153
-
154
- def file2table(f)
155
- f.basename.rm_ext.to_s[/^\d+-(.*)/, 1]
156
- end
157
-
158
- def viewpoint
159
- @viewpoint ||= if vp = ENV['DBAGENT_VIEWPOINT']
160
- Kernel.const_get(vp).new(handler.sequel_db)
161
- else
162
- Viewpoint::Base.new(handler.sequel_db)
163
- end
164
- end
165
-
166
- end # class Seeder
167
- end # module DbAgent
@@ -1,94 +0,0 @@
1
- require 'tsort'
2
- module DbAgent
3
- class TableOrderer
4
-
5
- def initialize(handler)
6
- @handler = handler
7
- end
8
- attr_reader :handler
9
-
10
- def db
11
- handler.sequel_db
12
- end
13
-
14
- def tsort
15
- @tsort ||= TSortComputation.new(db).to_a
16
- end
17
-
18
- def graph
19
- @graph ||= TSortComputation.new(db).graph
20
- end
21
-
22
- def dependencies(table)
23
- _dependencies(table, ds = {})
24
- ds
25
- .inject([]){|memo,(_,plus)| (memo + plus).uniq }
26
- .sort{|t1,t2| tsort.index(t1) - tsort.index(t2) }
27
- .reject{|x| x == table }
28
- end
29
-
30
- def _dependencies(table, ds)
31
- return ds if ds.has_key?(table)
32
- ds[table] = graph[table]
33
- ds[table].each do |child|
34
- _dependencies(child, ds)
35
- end
36
- end
37
- private :_dependencies
38
-
39
- class TSortComputation
40
- include TSort
41
-
42
- def initialize(db, except = [])
43
- @db = db
44
- @except = except
45
- end
46
- attr_reader :db, :except
47
-
48
- def graph
49
- g = Hash.new{|h,k| h[k] = [] }
50
- tsort_each_node.each do |table|
51
- tsort_each_child(table) do |child|
52
- g[child] << table
53
- end
54
- end
55
- g
56
- rescue TSort::Cyclic
57
- raise unless killed = to_kill
58
- TSortComputation.new(db, except + [killed]).graph
59
- end
60
-
61
- def to_a
62
- tsort.to_a
63
- rescue TSort::Cyclic
64
- raise unless killed = to_kill
65
- TSortComputation.new(db, except + [killed]).to_a
66
- end
67
-
68
- def tsort_each_node(&bl)
69
- db.tables.each(&bl)
70
- end
71
-
72
- def tsort_each_child(table, &bl)
73
- db.foreign_key_list(table)
74
- .map{|fk| fk[:table] }
75
- .reject{|t|
76
- except.any?{|killed| killed.first == table && killed.last == t }
77
- }
78
- .each(&bl)
79
- end
80
-
81
- private
82
-
83
- def to_kill
84
- each_strongly_connected_component
85
- .select{|scc| scc.size > 1 }
86
- .sort_by{|scc| scc.size }
87
- .first
88
- end
89
-
90
- end # class TSortComputation
91
-
92
- end # class TableOrderer
93
- end # module Dbagent
94
-
@@ -1,6 +0,0 @@
1
- module DbAgent
2
-
3
- # Current version of DbAgent
4
- VERSION = "3.5.0"
5
-
6
- end
@@ -1,18 +0,0 @@
1
- module DbAgent
2
- module Viewpoint
3
- # Factors relations on top of a Sequel database.
4
- class Base
5
-
6
- def initialize(db)
7
- @db = db
8
- end
9
- attr_reader :db
10
-
11
- def method_missing(name, *args, &bl)
12
- return super unless args.empty? and bl.nil?
13
- Bmg.sequel(name, db)
14
- end
15
-
16
- end # class Base
17
- end # module Viewpoint
18
- end # module DbAgent
@@ -1,13 +0,0 @@
1
- module DbAgent
2
- module Viewpoint
3
- # Delegates all relation accesses to `child`.
4
- module Delegate
5
-
6
- def method_missing(name, *args, &bl)
7
- return super unless args.empty? and bl.nil?
8
- child.send(name)
9
- end
10
-
11
- end # module Delegate
12
- end # module Viewpoint
13
- end # module DbAgent
@@ -1,19 +0,0 @@
1
- module DbAgent
2
- module Viewpoint
3
- # Forces typechecking on all child relations.
4
- class TypeCheck
5
-
6
- def initialize(db, child = nil)
7
- @db = db
8
- @child = child || DbAgent::Viewpoint::Base.new(db)
9
- end
10
- attr_reader :db, :child
11
-
12
- def method_missing(name, *args, &bl)
13
- return super unless args.empty? && bl.nil?
14
- child.send(name).with_typecheck
15
- end
16
-
17
- end # class TypeCheck
18
- end # module Viewpoint
19
- end # module DbAgent
@@ -1,3 +0,0 @@
1
- require_relative 'viewpoint/base'
2
- require_relative 'viewpoint/delegate'
3
- require_relative 'viewpoint/typecheck'
@@ -1,35 +0,0 @@
1
- module DbAgent
2
- class Webapp < Sinatra::Base
3
-
4
- set :raise_errors, true
5
- set :show_exceptions, false
6
- set :dump_errors, true
7
- set :db_handler, DbAgent.default_handler
8
-
9
- get '/ping' do
10
- settings.db_handler.sequel_db.test_connection
11
- status 200
12
- "ok"
13
- end
14
-
15
- get %r{/schema/?} do
16
- send_file(settings.db_handler.schema_folder/'spy/index.html')
17
- end
18
-
19
- get '/schema/*' do |url|
20
- send_file(settings.db_handler.schema_folder/'spy'/url)
21
- end
22
-
23
- post '/seeds/install' do
24
- Seeder.new(settings.db_handler).install(request["id"])
25
- "ok"
26
- end
27
-
28
- post '/seeds/flush' do
29
- seed_name = request["id"]
30
- Seeder.new(settings.db_handler).flush(request["id"])
31
- "ok"
32
- end
33
-
34
- end
35
- end
data/lib/db_agent.rb DELETED
@@ -1,78 +0,0 @@
1
- require 'path'
2
- require 'logger'
3
- require 'sequel'
4
- require 'sinatra'
5
- require 'bmg'
6
- require 'bmg/sequel'
7
- module DbAgent
8
- require_relative 'db_agent/version'
9
-
10
- # Simply checks that a path exists of raise an error
11
- def self._!(path)
12
- Path(path).tap do |p|
13
- raise "Missing #{p.basename}." unless p.exists?
14
- end
15
- end
16
-
17
- # Root folder of the project structure
18
- ROOT_FOLDER = if ENV['DBAGENT_ROOT_FOLDER']
19
- _!(ENV['DBAGENT_ROOT_FOLDER'])
20
- else
21
- Path.backfind('.[Gemfile]') or raise("Missing Gemfile")
22
- end
23
-
24
- # Logger instance to use
25
- LOGGER = Logger.new(STDOUT)
26
- LOGGER.level = Logger.const_get(ENV['DBAGENT_LOGLEVEL'] || 'WARN')
27
-
28
- # What database configuration to use for normal access
29
- def self.default_config
30
- cfg = {
31
- adapter: ENV['DBAGENT_ADAPTER'] || 'postgres',
32
- port: ENV['DBAGENT_PORT'] || 5432,
33
- database: ENV['DBAGENT_DB'] || 'suppliers-and-parts',
34
- user: ENV['DBAGENT_USER'] || 'dbagent',
35
- password: ENV['DBAGENT_PASSWORD'] || 'dbagent',
36
- test: false
37
- }
38
-
39
- # Favor a socket approach if specified, otherwise fallback to
40
- # host with default to postgres
41
- if socket = ENV['DBAGENT_SOCKET']
42
- cfg[:socket] = socket
43
- else
44
- cfg[:host] = ENV['DBAGENT_HOST'] || 'localhost'
45
- end
46
-
47
- # Set a logger if explicitly requested
48
- if ENV['DBAGENT_LOGSQL'] == 'yes'
49
- cfg[:loggers] = [LOGGER]
50
- end
51
-
52
- cfg
53
- end
54
-
55
- # What database configuration to use for superuser access
56
- def self.default_superconfig
57
- cfg = default_config
58
- cfg.merge({
59
- user: ENV['DBAGENT_SUPER_USER'] || cfg[:user],
60
- database: ENV['DBAGENT_SUPER_DB'] || cfg[:database],
61
- password: ENV['DBAGENT_SUPER_PASSWORD'] || cfg[:password]
62
- })
63
- end
64
-
65
- def self.default_handler
66
- DbHandler.factor({
67
- config: default_config,
68
- superconfig: default_superconfig,
69
- root: ROOT_FOLDER
70
- })
71
- end
72
-
73
- end # module DbAgent
74
- require 'db_agent/viewpoint'
75
- require 'db_agent/seeder'
76
- require 'db_agent/table_orderer'
77
- require 'db_agent/db_handler'
78
- require 'db_agent/webapp'
data/tasks/db.rake DELETED
@@ -1,114 +0,0 @@
1
- namespace :db do
2
-
3
- task :require do
4
- $:.unshift File.expand_path('../../lib', __FILE__)
5
- require 'db_agent'
6
- include DbAgent
7
- end
8
-
9
- def db_handler
10
- @db_handler ||= DbAgent.default_handler
11
- end
12
-
13
- desc "Pings the database, making sure everything's ready for migration"
14
- task :ping => :require do
15
- db_handler.ping
16
- end
17
-
18
- desc "Drops the user & database (USE WITH CARE)"
19
- task :drop => :require do
20
- db_handler.drop
21
- end
22
-
23
- desc "Creates an fresh new user & database (USE WITH CARE)"
24
- task :create => :require do
25
- db_handler.create
26
- end
27
-
28
- desc "Waits for the database server to ping, up to 15 seconds"
29
- task :wait_server => :require do
30
- db_handler.wait_server
31
- end
32
-
33
- desc "Waits for the database to ping, up to 15 seconds"
34
- task :wait => :require do
35
- db_handler.wait
36
- end
37
-
38
- desc "Dump a database backup"
39
- task :backup => :require do
40
- db_handler.backup
41
- end
42
-
43
- desc "Restore from the last database backup"
44
- task :restore, :pattern do |t,args|
45
- db_handler.restore(t, args)
46
- end
47
- task :restore => :require
48
-
49
- desc "Runs migrations on the current database"
50
- task :migrate, [:version] => :require do |_,args|
51
- version = args[:version].to_i if args[:version]
52
- db_handler.migrate(version)
53
- end
54
-
55
- desc "Opens a database REPL"
56
- task :repl => :require do
57
- db_handler.repl
58
- end
59
-
60
- desc "Dumps the schema documentation into database/schema"
61
- task :spy => :require do
62
- db_handler.spy
63
- end
64
-
65
- desc "Rebuilds the database from scratch (USE WITH CARE)"
66
- task :rebuild => [ :drop, :create, :migrate ]
67
-
68
- desc "Revive the database from the last backup"
69
- task :revive => [ :restore, :migrate ]
70
-
71
-
72
- desc "Checks that all seeds can be installed correctly"
73
- task :"check-seeds" do
74
- Seeder.new(db_handler).each_seed(true)
75
- end
76
- task :"check-seeds" => :require
77
-
78
- desc "Seeds the database with a particular data set"
79
- task :seed, :from do |t,args|
80
- Seeder.new(db_handler).install(args[:from] || 'empty')
81
- end
82
- task :seed => :require
83
-
84
- desc "Prints an INSERT script for a particular data set"
85
- task :insert_script, :from do |t,args|
86
- Seeder.new(db_handler).insert_script(args[:from] || 'empty')
87
- end
88
- task :insert_script => :require
89
-
90
- desc "Flushes the database as a particular data set"
91
- task :flush, :to do |t,args|
92
- Seeder.new(db_handler).flush(args[:to] || Time.now.strftime("%Y%M%d%H%M%S").to_s)
93
- end
94
- task :flush => :require
95
-
96
- desc "Flushes the initial empty files as a data set"
97
- task :flush_empty, :to do |t,args|
98
- Seeder.new(db_handler).flush_empty(args[:to] || Time.now.strftime("%Y%M%d%H%M%S").to_s)
99
- end
100
- task :flush_empty => :require
101
-
102
- desc "Shows what tables depend on a given one"
103
- task :dependencies, :of do |t,args|
104
- puts TableOrderer.new(db_handler).dependencies(args[:of].to_sym).reverse
105
- end
106
- task :dependencies => :require
107
-
108
- desc "Shows all tables in order"
109
- task :tables do |t|
110
- puts TableOrderer.new(db_handler).tsort
111
- end
112
- task :tables => :require
113
-
114
- end
data/tasks/gem.rake DELETED
@@ -1,39 +0,0 @@
1
- require 'rubygems/package_task'
2
-
3
- # Dynamically load the gem spec
4
- gemspec_file = File.expand_path('../../dbagent.gemspec', __FILE__)
5
- gemspec = Kernel.eval(File.read(gemspec_file))
6
-
7
- Gem::PackageTask.new(gemspec) do |t|
8
-
9
- # Name of the package
10
- t.name = gemspec.name
11
-
12
- # Version of the package
13
- t.version = gemspec.version
14
-
15
- # Directory used to store the package files
16
- t.package_dir = "pkg"
17
-
18
- # True if a gzipped tar file (tgz) should be produced
19
- t.need_tar = false
20
-
21
- # True if a gzipped tar file (tar.gz) should be produced
22
- t.need_tar_gz = false
23
-
24
- # True if a bzip2'd tar file (tar.bz2) should be produced
25
- t.need_tar_bz2 = false
26
-
27
- # True if a zip file should be produced (default is false)
28
- t.need_zip = false
29
-
30
- # List of files to be included in the package.
31
- t.package_files = gemspec.files
32
-
33
- # Tar command for gzipped or bzip2ed archives.
34
- t.tar_command = "tar"
35
-
36
- # Zip command for zipped archives.
37
- t.zip_command = "zip"
38
-
39
- end
data/tasks/test.rake DELETED
@@ -1,11 +0,0 @@
1
- namespace :test do
2
-
3
- desc %q{Run all RSpec tests}
4
- task :unit do
5
- require 'rspec'
6
- RSpec::Core::Runner.run(%w[-I. -Ilib -Ispec --pattern=spec/**/test_*.rb --color .])
7
- end
8
-
9
- task :all => :"unit"
10
- end
11
- task :test => :"test:all"