dbagent 3.0.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.
- checksums.yaml +7 -0
- data/Gemfile +2 -0
- data/LICENSE.md +22 -0
- data/README.md +117 -0
- data/Rakefile +16 -0
- data/lib/db_agent.rb +80 -0
- data/lib/db_agent/db_handler.rb +130 -0
- data/lib/db_agent/db_handler/mssql.rb +31 -0
- data/lib/db_agent/db_handler/mysql.rb +54 -0
- data/lib/db_agent/db_handler/postgresql.rb +66 -0
- data/lib/db_agent/seeder.rb +125 -0
- data/lib/db_agent/table_orderer.rb +94 -0
- data/lib/db_agent/viewpoint.rb +3 -0
- data/lib/db_agent/viewpoint/base.rb +18 -0
- data/lib/db_agent/viewpoint/delegate.rb +13 -0
- data/lib/db_agent/viewpoint/typecheck.rb +19 -0
- data/lib/db_agent/webapp.rb +35 -0
- data/tasks/db.rake +108 -0
- data/tasks/gem.rake +39 -0
- data/tasks/test.rake +11 -0
- metadata +215 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 120c9311649f81fdc84a37b73f207105c4ad560008750418f0f1ea1e581d3b3f
|
4
|
+
data.tar.gz: de68e2c53e6119d7df4099e7ae0aafa7beaed5ad1aeb656c2eca90a0e12d9bd0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5f7e584a37a4c74612bf15fd95c0218fd6e908fd85b8e913410eaaa7c1c22a360ad71033aeb71dfb72563e68bb0f237659fc45756f5a6542b440ccd4eb5d7590
|
7
|
+
data.tar.gz: 6636ba62ddecbd1f39c731b36870b4908a0480d4e3d39da6a495947ba66d12802663c4626ae8d1f964652fce4450ad9a45f2b2162924ff62b4253adf6f8edde7
|
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
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
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# DbAgent, a ruby tool to migrate, spy and seed relational databases
|
2
|
+
|
3
|
+
DbAgent helps managing a relational database lifecyle through three main tools:
|
4
|
+
|
5
|
+
* Migrations: powered by [Sequel](http://sequel.jeremyevans.net/), migrate as simply as `rake db:migrate`. Supports both superuser and normal user migrations.
|
6
|
+
|
7
|
+
* Spy: using [Shemaspy](http://schemaspy.sourceforge.net/), get your database schema browsable at any moment, through a simple web interface.
|
8
|
+
|
9
|
+
* Seed: maintain, install and flush database content as datasets, organized hierarchically in .json files. Very handy for automated tests, for instance.
|
10
|
+
|
11
|
+
## Get started using Docker
|
12
|
+
|
13
|
+
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.
|
14
|
+
|
15
|
+
See the examples folder for details.
|
16
|
+
|
17
|
+
## Available environment variables
|
18
|
+
|
19
|
+
* `DBAGENT_ROOT_FOLDER` Main folder where data, migrations and viewpoints can be found
|
20
|
+
* `DBAGENT_LOGLEVEL` Log level to use for dbagent messages (defaults to `WARN`)
|
21
|
+
* `DBAGENT_LOGSQL` Low Sequel's SQL queries (defaults to `no`)
|
22
|
+
* `DBAGENT_ADAPTER` Sequel's adapter (defaults to `postgres`)
|
23
|
+
* `DBAGENT_HOST` Database server host (defaults to `localhost`)
|
24
|
+
* `DBAGENT_PORT` Database server port (defaults to `5432`)
|
25
|
+
* `DBAGENT_DB` Database name (defaults to `suppliers-and-parts`)
|
26
|
+
* `DBAGENT_USER` Database user (defaults to `dbagent`)
|
27
|
+
* `DBAGENT_PASSWORD` Database password (defaults to `dbagent`)
|
28
|
+
* `DBAGENT_SOCKET` Database server socket (if host/port is not used)
|
29
|
+
* `DBAGENT_SUPER_USER` Superuser name (postgres only)
|
30
|
+
* `DBAGENT_SUPER_DB` Superuser database (postgres only)
|
31
|
+
* `DBAGENT_SUPER_PASSWORD` Superuser password (postgres only)
|
32
|
+
* `DBAGENT_VIEWPOINT` Bmg viewpoint (class name) when using db:flush
|
33
|
+
|
34
|
+
## Available rake tasks
|
35
|
+
|
36
|
+
The following rake tasks helps you managing the database. They must typically be executed on the docker container.
|
37
|
+
|
38
|
+
```
|
39
|
+
rake db:check-seeds # Checks that all seeds can be installed correctly
|
40
|
+
rake db:create # Creates an fresh new user & database (USE WITH CARE)
|
41
|
+
rake db:drop # Drops the user & database (USE WITH CARE)
|
42
|
+
rake db:flush[to] # Flushes the database as a particular data set
|
43
|
+
rake db:migrate # Runs migrations on the current database
|
44
|
+
rake db:ping # Pings the database, making sure everything's ready for migration
|
45
|
+
rake db:rebuild # Rebuilds the database from scratch (USE WITH CARE)
|
46
|
+
rake db:repl # Opens a database REPL
|
47
|
+
rake db:seed[from] # Seeds the database with a particular data set
|
48
|
+
rake db:spy # Dumps the schema documentation into database/schema
|
49
|
+
rake db:backup # Makes a database backup to the backups folder
|
50
|
+
rake db:restore[match] # Restore the last matching database backup file from backups folder
|
51
|
+
rake db:revive # Shortcut for both db:restore and db:migrate
|
52
|
+
rake db:tables # List tables with those with fewer dependencies first
|
53
|
+
rake db:dependencies[of] # List tables that depend of a given one
|
54
|
+
```
|
55
|
+
|
56
|
+
## Available webservices
|
57
|
+
|
58
|
+
```
|
59
|
+
GET /schema/ # Browser the database schema (requires a former `rake db:spy`)
|
60
|
+
POST /seeds/install?id=... # Install a particular dataset, id is the name of a folder in `data` folder
|
61
|
+
POST /seeds/flush?id=... # Flushes the current database content as a named dataset
|
62
|
+
```
|
63
|
+
|
64
|
+
## Hacking on dbagent
|
65
|
+
|
66
|
+
### Installing the library
|
67
|
+
|
68
|
+
```
|
69
|
+
bundle install
|
70
|
+
```
|
71
|
+
|
72
|
+
### Preparing your computer
|
73
|
+
|
74
|
+
The tests require a valid PostgreSQL installation with the suppliers-and-parts
|
75
|
+
database installed. A `dbagent` user would be needed on the PostgreSQL installation
|
76
|
+
to bootstrap the process.
|
77
|
+
|
78
|
+
```
|
79
|
+
sudo su postgres -c 'createuser --createdb dbagent -P'
|
80
|
+
```
|
81
|
+
|
82
|
+
DbAgent tries to connect to the suppliers-and-parts with a dbagent/dbagent user/password
|
83
|
+
pair by default. If you change the database name, user, or password please adapt the
|
84
|
+
environment variables accordingly in the commands below.
|
85
|
+
|
86
|
+
### Installing the example database
|
87
|
+
|
88
|
+
```
|
89
|
+
DBAGENT_ROOT_FOLDER=examples/suppliers-and-parts bundle exec rake db:create db:migrate db:seed['base']
|
90
|
+
```
|
91
|
+
### Running test
|
92
|
+
|
93
|
+
To run the test you need to have `Docker` on your computer.
|
94
|
+
|
95
|
+
Run:
|
96
|
+
```
|
97
|
+
make test
|
98
|
+
```
|
99
|
+
|
100
|
+
Don't forget to delete created ressources for the tests bun running:
|
101
|
+
```
|
102
|
+
make clean
|
103
|
+
```
|
104
|
+
|
105
|
+
## Contribute
|
106
|
+
|
107
|
+
Please use github issues and pull requests for all questions, bug reports,
|
108
|
+
and contributions. Don't hesitate to get in touch with us with an early code
|
109
|
+
spike if you plan to add non trivial features.
|
110
|
+
|
111
|
+
## Licence
|
112
|
+
|
113
|
+
This software is distributed by Enspirit SRL under a MIT Licence. Please
|
114
|
+
contact Bernard Lambeau (blambeau@gmail.com) with any question.
|
115
|
+
|
116
|
+
Enspirit (https://enspirit.be) and Klaro App (https://klaro.cards) are both
|
117
|
+
actively using and contributing to the library.
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
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
|
data/lib/db_agent.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'path'
|
2
|
+
require 'logger'
|
3
|
+
require 'sequel'
|
4
|
+
require 'sinatra'
|
5
|
+
require 'bmg'
|
6
|
+
require 'bmg/sequel'
|
7
|
+
module DbAgent
|
8
|
+
|
9
|
+
# Current version of DbAgent
|
10
|
+
VERSION = "3.0.0"
|
11
|
+
|
12
|
+
# Simply checks that a path exists of raise an error
|
13
|
+
def self._!(path)
|
14
|
+
Path(path).tap do |p|
|
15
|
+
raise "Missing #{p.basename}." unless p.exists?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Root folder of the project structure
|
20
|
+
ROOT_FOLDER = if ENV['DBAGENT_ROOT_FOLDER']
|
21
|
+
_!(ENV['DBAGENT_ROOT_FOLDER'])
|
22
|
+
else
|
23
|
+
Path.backfind('.[Gemfile]') or raise("Missing Gemfile")
|
24
|
+
end
|
25
|
+
|
26
|
+
# Logger instance to use
|
27
|
+
LOGGER = Logger.new(STDOUT)
|
28
|
+
LOGGER.level = Logger.const_get(ENV['DBAGENT_LOGLEVEL'] || 'WARN')
|
29
|
+
|
30
|
+
# What database configuration to use for normal access
|
31
|
+
def self.default_config
|
32
|
+
cfg = {
|
33
|
+
adapter: ENV['DBAGENT_ADAPTER'] || 'postgres',
|
34
|
+
port: ENV['DBAGENT_PORT'] || 5432,
|
35
|
+
database: ENV['DBAGENT_DB'] || 'suppliers-and-parts',
|
36
|
+
user: ENV['DBAGENT_USER'] || 'dbagent',
|
37
|
+
password: ENV['DBAGENT_PASSWORD'] || 'dbagent',
|
38
|
+
test: false
|
39
|
+
}
|
40
|
+
|
41
|
+
# Favor a socket approach if specified, otherwise fallback to
|
42
|
+
# host with default to postgres
|
43
|
+
if socket = ENV['DBAGENT_SOCKET']
|
44
|
+
cfg[:socket] = socket
|
45
|
+
else
|
46
|
+
cfg[:host] = ENV['DBAGENT_HOST'] || 'localhost'
|
47
|
+
end
|
48
|
+
|
49
|
+
# Set a logger if explicitly requested
|
50
|
+
if ENV['DBAGENT_LOGSQL'] == 'yes'
|
51
|
+
cfg[:loggers] = [LOGGER]
|
52
|
+
end
|
53
|
+
|
54
|
+
cfg
|
55
|
+
end
|
56
|
+
|
57
|
+
# What database configuration to use for superuser access
|
58
|
+
def self.default_superconfig
|
59
|
+
cfg = default_config
|
60
|
+
cfg.merge({
|
61
|
+
user: ENV['DBAGENT_SUPER_USER'] || cfg[:user],
|
62
|
+
database: ENV['DBAGENT_SUPER_DB'] || cfg[:database],
|
63
|
+
password: ENV['DBAGENT_SUPER_PASSWORD'] || cfg[:password]
|
64
|
+
})
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.default_handler
|
68
|
+
DbHandler.factor({
|
69
|
+
config: default_config,
|
70
|
+
superconfig: default_superconfig,
|
71
|
+
root: ROOT_FOLDER
|
72
|
+
})
|
73
|
+
end
|
74
|
+
|
75
|
+
end # module DbAgent
|
76
|
+
require 'db_agent/viewpoint'
|
77
|
+
require 'db_agent/seeder'
|
78
|
+
require 'db_agent/table_orderer'
|
79
|
+
require 'db_agent/db_handler'
|
80
|
+
require 'db_agent/webapp'
|
@@ -0,0 +1,130 @@
|
|
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
|
+
puts "Trying to ping `#{config[:host]}`"
|
46
|
+
15.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
|
+
15.downto(0) do |i|
|
62
|
+
begin
|
63
|
+
puts "Using #{config}"
|
64
|
+
sequel_db.test_connection
|
65
|
+
puts "Database is there. Great."
|
66
|
+
break
|
67
|
+
rescue Sequel::Error
|
68
|
+
raise if i==0
|
69
|
+
sleep(1)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def restore(t, args)
|
75
|
+
raise NotImplementedError
|
76
|
+
end
|
77
|
+
|
78
|
+
def migrate(version = nil)
|
79
|
+
Sequel.extension :migration
|
80
|
+
if (sf = migrations_folder/'superuser').exists?
|
81
|
+
Sequel::Migrator.run(sequel_superdb, migrations_folder/'superuser', table: 'superuser_migrations', target: version)
|
82
|
+
end
|
83
|
+
Sequel::Migrator.run(sequel_db, migrations_folder, target: version)
|
84
|
+
end
|
85
|
+
|
86
|
+
def repl
|
87
|
+
raise NotImplementedError
|
88
|
+
end
|
89
|
+
|
90
|
+
def spy
|
91
|
+
raise NotImplementedError
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.factor(options)
|
95
|
+
case options[:config][:adapter]
|
96
|
+
when 'postgres'
|
97
|
+
PostgreSQL.new(options)
|
98
|
+
when 'mssql'
|
99
|
+
MSSQL.new(options)
|
100
|
+
when 'mysql'
|
101
|
+
MySQL.new(options)
|
102
|
+
else
|
103
|
+
PostgreSQL.new(options)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def sequel_db
|
108
|
+
@sequel_db ||= ::Sequel.connect(config)
|
109
|
+
end
|
110
|
+
|
111
|
+
def sequel_superdb
|
112
|
+
raise "No superconfig set" if superconfig.nil?
|
113
|
+
@sequel_superdb ||= ::Sequel.connect(superconfig)
|
114
|
+
end
|
115
|
+
|
116
|
+
def system(cmd, *args)
|
117
|
+
puts cmd
|
118
|
+
::Kernel.system(cmd, *args)
|
119
|
+
end
|
120
|
+
|
121
|
+
def require_viewpoints!
|
122
|
+
f = viewpoints_folder.expand_path
|
123
|
+
Path.require_tree(f) if f.directory?
|
124
|
+
end
|
125
|
+
|
126
|
+
end # class DbHandler
|
127
|
+
end # module DbAgent
|
128
|
+
require_relative 'db_handler/postgresql'
|
129
|
+
require_relative 'db_handler/mssql'
|
130
|
+
require_relative 'db_handler/mysql'
|
@@ -0,0 +1,31 @@
|
|
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
|
+
jdbc_jar = (Path.dir.parent/'vendor').glob('mssql*.jar').first
|
23
|
+
system %Q{java -jar vendor/schemaSpy_5.0.0.jar -dp #{jdbc_jar} -t mssql05 -host #{config[:host]} -u #{config[:user]} -p #{config[:password]} -db #{config[:database]} -port #{config[:port]} -s dbo -o #{schema_folder}/spy}
|
24
|
+
system %Q{open #{schema_folder}/spy/index.html}
|
25
|
+
end
|
26
|
+
|
27
|
+
def restore(t, args)
|
28
|
+
end
|
29
|
+
end # MSSQL
|
30
|
+
end # DbHandler
|
31
|
+
end # DbAgent
|
@@ -0,0 +1,54 @@
|
|
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
|
+
jdbc_jar = (Path.dir.parent / 'vendor').glob('mysql*.jar').first
|
25
|
+
system %(java -jar vendor/schemaSpy_5.0.0.jar -dp #{jdbc_jar} -t mysql -host #{config[:host]} -u #{config[:user]} -p #{config[:password]} -db #{config[:database]} -s public -o #{schema_folder}/spy)
|
26
|
+
system %(open #{schema_folder}/spy/index.html)
|
27
|
+
end
|
28
|
+
|
29
|
+
def restore(_t, args)
|
30
|
+
candidates = backup_folder.glob('*.sql').sort
|
31
|
+
if args[:pattern] && rx = Regexp.new(args[:pattern])
|
32
|
+
candidates = candidates.select { |f| f.basename.to_s =~ rx }
|
33
|
+
end
|
34
|
+
file = candidates.last
|
35
|
+
shell mysql(config[:database], '<', file.to_s)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def mysql_cmd(cmd, *args)
|
41
|
+
conf = config
|
42
|
+
%(#{cmd} -h #{config[:host]} --password=#{config[:password]} -P #{config[:port]} -u #{config[:user]} #{args.join(' ')})
|
43
|
+
end
|
44
|
+
|
45
|
+
def mysql(*args)
|
46
|
+
mysql_cmd('mysql', *args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def mysqldump(*args)
|
50
|
+
mysql_cmd('mysqldump', *args)
|
51
|
+
end
|
52
|
+
end # MySQL
|
53
|
+
end # DbHandler
|
54
|
+
end # DbAgent
|
@@ -0,0 +1,66 @@
|
|
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} -dp #{jdbc_jar} -t pgsql}
|
29
|
+
cmd << %Q{ -host #{config[:host]}}
|
30
|
+
cmd << %Q{ -port #{config[:port]}} if config[:port]
|
31
|
+
cmd << %Q{ -u #{config[:user]}}
|
32
|
+
cmd << %Q{ -p #{config[:password]}} if config[:password]
|
33
|
+
cmd << %Q{ -db #{config[:database]} -s public -o #{schema_folder}/spy}
|
34
|
+
system(cmd)
|
35
|
+
system %Q{open #{schema_folder}/spy/index.html}
|
36
|
+
end
|
37
|
+
|
38
|
+
def restore(t, args)
|
39
|
+
candidates = backup_folder.glob("*.sql").sort
|
40
|
+
if args[:pattern] && rx = Regexp.new(args[:pattern])
|
41
|
+
candidates = candidates.select{|f| f.basename.to_s =~ rx }
|
42
|
+
end
|
43
|
+
file = candidates.last
|
44
|
+
shell pg_cmd('psql', config[:database], '<', file.to_s)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def pg_cmd(cmd, *args)
|
50
|
+
%Q{#{cmd} -h #{config[:host]} -p #{config[:port]} -U #{config[:user]} #{args.join(' ')}}
|
51
|
+
end
|
52
|
+
|
53
|
+
def psql(*args)
|
54
|
+
cmd = "psql"
|
55
|
+
cmd = "PGPASSWORD=#{config[:password]} #{cmd}" if config[:password]
|
56
|
+
pg_cmd(cmd, *args)
|
57
|
+
end
|
58
|
+
|
59
|
+
def pg_dump(*args)
|
60
|
+
cmd = "pg_dump"
|
61
|
+
cmd = "PGPASSWORD=#{config[:password]} #{cmd}" if config[:password]
|
62
|
+
pg_cmd(cmd, *args)
|
63
|
+
end
|
64
|
+
end # class PostgreSQL
|
65
|
+
end # module DbHandler
|
66
|
+
end # module DbAgent
|
@@ -0,0 +1,125 @@
|
|
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
|
+
folder = handler.data_folder/from
|
12
|
+
|
13
|
+
# load files in order
|
14
|
+
pairs = merged_data(from)
|
15
|
+
names = pairs.keys.sort{|p1,p2|
|
16
|
+
pairs[p1].basename <=> pairs[p2].basename
|
17
|
+
}
|
18
|
+
|
19
|
+
# Truncate tables then fill them
|
20
|
+
names.reverse.each do |name|
|
21
|
+
LOGGER.info("Emptying table `#{name}`")
|
22
|
+
handler.sequel_db[name.to_sym].delete
|
23
|
+
end
|
24
|
+
names.each do |name|
|
25
|
+
LOGGER.info("Filling table `#{name}`")
|
26
|
+
file = pairs[name]
|
27
|
+
handler.sequel_db[name.to_sym].multi_insert(file.load)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def flush_empty(to = "empty")
|
33
|
+
target = (handler.data_folder/to).rm_rf.mkdir_p
|
34
|
+
(target/"metadata.json").write <<-JSON.strip
|
35
|
+
{}
|
36
|
+
JSON
|
37
|
+
TableOrderer.new(handler).tsort.each_with_index do |table_name, index|
|
38
|
+
(target/"#{(index*10).to_s.rjust(5,"0")}-#{table_name}.json").write("[]")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def flush(to)
|
43
|
+
target = (handler.data_folder/to).rm_rf.mkdir_p
|
44
|
+
source = (handler.data_folder/"empty")
|
45
|
+
(target/"metadata.json").write <<-JSON.strip
|
46
|
+
{ "inherits": "empty" }
|
47
|
+
JSON
|
48
|
+
seed_files(source).each do |f|
|
49
|
+
flush_seed_file(f, to)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def flush_seed_file(f, to)
|
54
|
+
target = (handler.data_folder/to)
|
55
|
+
table = file2table(f)
|
56
|
+
flush_table(table, target, f.basename, true)
|
57
|
+
end
|
58
|
+
|
59
|
+
def flush_table(table_name, target_folder, file_name, skip_empty)
|
60
|
+
data = viewpoint.send(table_name.to_sym).to_a
|
61
|
+
if data.empty? && skip_empty
|
62
|
+
LOGGER.info("Skipping table `#{table_name}` since empty")
|
63
|
+
else
|
64
|
+
LOGGER.info("Flushing table `#{table_name}`")
|
65
|
+
json = JSON.pretty_generate(data)
|
66
|
+
(target_folder/file_name).write(json)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def each_seed(install = true)
|
71
|
+
handler.data_folder.glob('**/*') do |file|
|
72
|
+
next unless file.directory?
|
73
|
+
next unless (file/"metadata.json").exists?
|
74
|
+
|
75
|
+
base = file.relative_to(handler.data_folder)
|
76
|
+
begin
|
77
|
+
Seeder.new(handler).install(base)
|
78
|
+
puts "#{base} OK"
|
79
|
+
yield(self, file) if block_given?
|
80
|
+
rescue => ex
|
81
|
+
puts "KO on #{file}"
|
82
|
+
puts ex.message
|
83
|
+
end if install
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def merged_data(from)
|
90
|
+
folder = handler.data_folder/from
|
91
|
+
data = {}
|
92
|
+
|
93
|
+
# load metadata and install parent dataset if any
|
94
|
+
metadata = (folder/"metadata.json").load
|
95
|
+
if parent = metadata["inherits"]
|
96
|
+
data = merged_data(parent)
|
97
|
+
end
|
98
|
+
|
99
|
+
seed_files(folder).each do |f|
|
100
|
+
data[file2table(f)] = f
|
101
|
+
end
|
102
|
+
|
103
|
+
data
|
104
|
+
end
|
105
|
+
|
106
|
+
def seed_files(folder)
|
107
|
+
folder
|
108
|
+
.glob("*.json")
|
109
|
+
.reject{|f| f.basename.to_s =~ /^metadata/ }
|
110
|
+
end
|
111
|
+
|
112
|
+
def file2table(f)
|
113
|
+
f.basename.rm_ext.to_s[/^\d+-(.*)/, 1]
|
114
|
+
end
|
115
|
+
|
116
|
+
def viewpoint
|
117
|
+
@viewpoint ||= if vp = ENV['DBAGENT_VIEWPOINT']
|
118
|
+
Kernel.const_get(vp).new(handler.sequel_db)
|
119
|
+
else
|
120
|
+
Viewpoint::Base.new(handler.sequel_db)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end # class Seeder
|
125
|
+
end # module DbAgent
|
@@ -0,0 +1,94 @@
|
|
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
|
+
|
@@ -0,0 +1,18 @@
|
|
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
|
@@ -0,0 +1,13 @@
|
|
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
|
@@ -0,0 +1,19 @@
|
|
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
|
@@ -0,0 +1,35 @@
|
|
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.sequel_db).install(request["id"])
|
25
|
+
"ok"
|
26
|
+
end
|
27
|
+
|
28
|
+
post '/seeds/flush' do
|
29
|
+
seed_name = request["id"]
|
30
|
+
Seeder.new(settings.db_handler.sequel_db).flush(request["id"])
|
31
|
+
"ok"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/tasks/db.rake
ADDED
@@ -0,0 +1,108 @@
|
|
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 "Flushes the database as a particular data set"
|
85
|
+
task :flush, :to do |t,args|
|
86
|
+
Seeder.new(db_handler).flush(args[:to] || Time.now.strftime("%Y%M%d%H%M%S").to_s)
|
87
|
+
end
|
88
|
+
task :flush => :require
|
89
|
+
|
90
|
+
desc "Flushes the initial empty files as a data set"
|
91
|
+
task :flush_empty, :to do |t,args|
|
92
|
+
Seeder.new(db_handler).flush_empty(args[:to] || Time.now.strftime("%Y%M%d%H%M%S").to_s)
|
93
|
+
end
|
94
|
+
task :flush_empty => :require
|
95
|
+
|
96
|
+
desc "Shows what tables depend on a given one"
|
97
|
+
task :dependencies, :of do |t,args|
|
98
|
+
puts TableOrderer.new(db_handler).dependencies(args[:of].to_sym).reverse
|
99
|
+
end
|
100
|
+
task :dependencies => :require
|
101
|
+
|
102
|
+
desc "Shows all tables in order"
|
103
|
+
task :tables do |t|
|
104
|
+
puts TableOrderer.new(db_handler).tsort
|
105
|
+
end
|
106
|
+
task :tables => :require
|
107
|
+
|
108
|
+
end
|
data/tasks/gem.rake
ADDED
@@ -0,0 +1,39 @@
|
|
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
ADDED
metadata
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dbagent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 3.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bernard Lambeau
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-08-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sequel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '13'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '13'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: path
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sinatra
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bmg
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.18'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.18'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: net-ping
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: predicate
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '2'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '2'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: bundler
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '2'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '2'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rspec
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '3'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '3'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rack-test
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '1'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '1'
|
167
|
+
description: A tool to migrate, spy and seed relational databases
|
168
|
+
email: blambeau@gmail.com
|
169
|
+
executables: []
|
170
|
+
extensions: []
|
171
|
+
extra_rdoc_files: []
|
172
|
+
files:
|
173
|
+
- Gemfile
|
174
|
+
- LICENSE.md
|
175
|
+
- README.md
|
176
|
+
- Rakefile
|
177
|
+
- lib/db_agent.rb
|
178
|
+
- lib/db_agent/db_handler.rb
|
179
|
+
- lib/db_agent/db_handler/mssql.rb
|
180
|
+
- lib/db_agent/db_handler/mysql.rb
|
181
|
+
- lib/db_agent/db_handler/postgresql.rb
|
182
|
+
- lib/db_agent/seeder.rb
|
183
|
+
- lib/db_agent/table_orderer.rb
|
184
|
+
- lib/db_agent/viewpoint.rb
|
185
|
+
- lib/db_agent/viewpoint/base.rb
|
186
|
+
- lib/db_agent/viewpoint/delegate.rb
|
187
|
+
- lib/db_agent/viewpoint/typecheck.rb
|
188
|
+
- lib/db_agent/webapp.rb
|
189
|
+
- tasks/db.rake
|
190
|
+
- tasks/gem.rake
|
191
|
+
- tasks/test.rake
|
192
|
+
homepage: http://github.com/enspirit/dbagent
|
193
|
+
licenses:
|
194
|
+
- MIT
|
195
|
+
metadata: {}
|
196
|
+
post_install_message:
|
197
|
+
rdoc_options: []
|
198
|
+
require_paths:
|
199
|
+
- lib
|
200
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
201
|
+
requirements:
|
202
|
+
- - ">="
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
version: '0'
|
205
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
206
|
+
requirements:
|
207
|
+
- - ">="
|
208
|
+
- !ruby/object:Gem::Version
|
209
|
+
version: '0'
|
210
|
+
requirements: []
|
211
|
+
rubygems_version: 3.0.8
|
212
|
+
signing_key:
|
213
|
+
specification_version: 4
|
214
|
+
summary: A tool to migrate, spy and seed relational databases.
|
215
|
+
test_files: []
|