dbagile 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/dbagile.rb +2 -2
- data/lib/dbagile/adapter/sequel/data/transaction_driven.rb +1 -0
- data/lib/dbagile/adapter/sequel/schema/concrete_script.rb +18 -0
- data/lib/dbagile/adapter/sequel/schema/table_driven.rb +10 -1
- data/lib/dbagile/adapter/sequel/sequel_tracer.rb +1 -0
- data/lib/dbagile/command/bulk/commons.rb +7 -2
- data/lib/dbagile/command/bulk/export.rb +4 -1
- data/lib/dbagile/command/bulk/import.rb +32 -2
- data/lib/dbagile/command/schema/check.rb +2 -1
- data/lib/dbagile/command/schema/commons.rb +16 -1
- data/lib/dbagile/command/schema/dump.rb +85 -4
- data/lib/dbagile/command/schema/sql_script.rb +1 -0
- data/lib/dbagile/contract/data/dataset.rb +9 -0
- data/lib/dbagile/core/schema/builder.rb +8 -4
- data/lib/dbagile/core/schema/builder/concept_factory.rb +5 -0
- data/lib/dbagile/core/schema/composite.rb +1 -1
- data/lib/dbagile/core/schema/computations/merge.rb +1 -1
- data/lib/dbagile/core/schema/errors.rb +1 -3
- data/lib/dbagile/core/schema/logical.rb +1 -0
- data/lib/dbagile/core/schema/logical/attribute.rb +11 -2
- data/lib/dbagile/core/schema/logical/relview.rb +42 -0
- data/lib/dbagile/core/schema/migrate/abstract_script.rb +2 -0
- data/lib/dbagile/core/schema/migrate/create_view.rb +15 -0
- data/lib/dbagile/core/schema/migrate/drop_view.rb +15 -0
- data/lib/dbagile/core/schema/migrate/operation.rb +8 -6
- data/lib/dbagile/core/schema/migrate/stager.rb +12 -3
- data/lib/dbagile/core/schema/part.rb +0 -49
- data/lib/dbagile/core/schema/schema_object.rb +54 -0
- data/lib/dbagile/environment/repository.rb +2 -2
- data/lib/dbagile/errors.rb +3 -0
- data/lib/dbagile/io.rb +7 -3
- data/lib/dbagile/io/html.rb +48 -0
- data/lib/dbagile/restful.rb +1 -1
- data/lib/dbagile/restful/middleware/one_database.rb +1 -0
- data/lib/dbagile/restful/middleware/post.rb +16 -1
- data/lib/dbagile/tools/tuple.rb +7 -0
- data/test/assumptions/sequel/connect.spec +12 -9
- data/test/assumptions/sequel/test.db +0 -0
- data/test/commands.spec +4 -4
- data/test/commands/bulk/import.spec +26 -3
- data/test/commands/schema/check.spec +20 -13
- data/test/commands/schema/dump.spec +23 -0
- data/test/fixtures/basics/dbagile.idx +7 -7
- data/test/fixtures/basics/robust.db +0 -0
- data/test/fixtures/basics/test.db +0 -0
- data/test/fixtures/empty/dbagile.idx +1 -1
- data/test/restful/get/html_format.ex +12 -0
- data/test/run_all_suite.rb +1 -1
- data/test/spec_helper.rb +0 -2
- data/test/support/be_a_valid_json_string.rb +1 -1
- data/test/support/be_a_valid_yaml_string.rb +1 -1
- data/test/unit/core/schema/fixtures/views.yaml +5 -0
- data/test/unit/core/schema/part_keys.spec +12 -0
- data/test/unit/core/schema/yaml_load.spec +5 -0
- metadata +414 -244
data/lib/dbagile.rb
CHANGED
@@ -2,7 +2,7 @@ require 'dbagile/loader'
|
|
2
2
|
module DbAgile
|
3
3
|
|
4
4
|
# Version of the DbAgile interface
|
5
|
-
VERSION = "0.0.
|
5
|
+
VERSION = "0.0.2".freeze
|
6
6
|
|
7
7
|
# Domains recognized as valid domains inside a SQL database
|
8
8
|
RECOGNIZED_DOMAINS = [
|
@@ -52,7 +52,7 @@ module DbAgile
|
|
52
52
|
DbAgile::Environment.default
|
53
53
|
end
|
54
54
|
module_function :default_environment
|
55
|
-
|
55
|
+
|
56
56
|
end # module DbAgile
|
57
57
|
require 'dbagile/robustness'
|
58
58
|
require 'dbagile/errors'
|
@@ -10,6 +10,24 @@ module DbAgile
|
|
10
10
|
buffer
|
11
11
|
end
|
12
12
|
|
13
|
+
def create_view(conn, op, buffer)
|
14
|
+
tname = conn.send(:quote_schema_table, op.table_name)
|
15
|
+
buffer << "CREATE VIEW #{tname} AS #{op.relview.definition};" << "\n"
|
16
|
+
staged!(op)
|
17
|
+
rescue Sequel::Error => ex
|
18
|
+
buffer << "-- UNSUPPORTED: #{op.to_sql92}" << "\n"
|
19
|
+
unsupported!(op)
|
20
|
+
end
|
21
|
+
|
22
|
+
def drop_view(conn, op, buffer)
|
23
|
+
tname = conn.send(:quote_schema_table, op.table_name)
|
24
|
+
buffer << "DROP VIEW #{tname};" << "\n"
|
25
|
+
staged!(op)
|
26
|
+
rescue Sequel::Error => ex
|
27
|
+
buffer << "-- UNSUPPORTED: #{op.to_sql92}" << "\n"
|
28
|
+
unsupported!(op)
|
29
|
+
end
|
30
|
+
|
13
31
|
def create_table(conn, op, buffer)
|
14
32
|
gen = Sequel::Schema::Generator.new(conn)
|
15
33
|
build_sequel_expand_generator(conn, op, gen, "")
|
@@ -43,7 +43,16 @@ module DbAgile
|
|
43
43
|
|
44
44
|
# @see DbAgile::Contract::Schema::TableDriven#keys
|
45
45
|
def keys(table_name)
|
46
|
-
|
46
|
+
# take the indexes
|
47
|
+
indexes = db.indexes(table_name).values
|
48
|
+
indexes = indexes.select{|i| i[:unique] == true}.collect{|i| i[:columns]}.sort{|a1, a2| a1.size <=> a2.size}
|
49
|
+
|
50
|
+
# take single keys as well
|
51
|
+
key = db.schema(table_name).select{|pair|
|
52
|
+
pair[1][:primary_key]
|
53
|
+
}.collect{|pair| pair[0]}
|
54
|
+
|
55
|
+
key.empty? ? indexes : (indexes + [ key ])
|
47
56
|
end
|
48
57
|
|
49
58
|
end # module TableDriven
|
@@ -59,8 +59,8 @@ module DbAgile
|
|
59
59
|
|
60
60
|
# Adds the format options for output
|
61
61
|
def add_output_format_options(opt)
|
62
|
-
opt.on('--format=X', [:csv, :text, :json, :yaml, :ruby, :xml],
|
63
|
-
"Export dataset in (csv, text, json, yaml, ruby, xml)") do |value|
|
62
|
+
opt.on('--format=X', [:csv, :text, :json, :yaml, :ruby, :xml, :html],
|
63
|
+
"Export dataset in (csv, text, json, yaml, ruby, xml, html)") do |value|
|
64
64
|
self.format = value
|
65
65
|
end
|
66
66
|
opt.on("--csv", "Export dataset in csv (default)"){ self.format = :csv }
|
@@ -68,6 +68,7 @@ module DbAgile
|
|
68
68
|
opt.on("--json", "Export dataset in json"){ self.format = :json }
|
69
69
|
opt.on("--yaml", "Export dataset in yaml"){ self.format = :yaml }
|
70
70
|
opt.on("--xml", "Export dataset in xml"){ self.format = :xml }
|
71
|
+
opt.on("--html", "Export dataset in html"){ self.format = :html }
|
71
72
|
opt.on("--ruby", "Export dataset as ruby code"){ self.format = :ruby }
|
72
73
|
end
|
73
74
|
|
@@ -123,6 +124,10 @@ module DbAgile
|
|
123
124
|
io_options[:text][:append_with] = '...'
|
124
125
|
end
|
125
126
|
end
|
127
|
+
|
128
|
+
# Adds output HTML options
|
129
|
+
def add_html_output_options(opt)
|
130
|
+
end
|
126
131
|
|
127
132
|
end # module Commons
|
128
133
|
end # module Bulk
|
@@ -39,6 +39,10 @@ module DbAgile
|
|
39
39
|
opt.separator "\nTEXT options:"
|
40
40
|
add_text_output_options(opt)
|
41
41
|
|
42
|
+
# HTML output options
|
43
|
+
opt.separator "\nHTML options:"
|
44
|
+
add_html_output_options(opt)
|
45
|
+
|
42
46
|
# JSON output options
|
43
47
|
opt.separator "\nJSON options:"
|
44
48
|
add_json_output_options(opt)
|
@@ -85,7 +89,6 @@ module DbAgile
|
|
85
89
|
# Export it
|
86
90
|
with_io{|io|
|
87
91
|
method = "to_#{self.format}".to_sym
|
88
|
-
io = environment.output_buffer
|
89
92
|
options = io_options[self.format]
|
90
93
|
options[:type_system] = self.type_system if self.type_system
|
91
94
|
ds.send(method, io, options)
|
@@ -23,6 +23,9 @@ module DbAgile
|
|
23
23
|
# Truncate table?
|
24
24
|
attr_accessor :truncate_table
|
25
25
|
|
26
|
+
# Update tuple when key already exists?
|
27
|
+
attr_accessor :update_on_existing
|
28
|
+
|
26
29
|
# Contribute to options
|
27
30
|
def add_options(opt)
|
28
31
|
# Main output options
|
@@ -48,9 +51,13 @@ module DbAgile
|
|
48
51
|
opt.on('--trace-sql', "Trace SQL statements on STDOUT (but executes them)") do |value|
|
49
52
|
self.conn_options[:trace_sql] = true
|
50
53
|
end
|
54
|
+
opt.on("--update", "-u", "Update tuple instead of insert when key already exists") do |value|
|
55
|
+
self.update_on_existing = true
|
56
|
+
end
|
51
57
|
opt.on('--dry-run', "Trace SQL statements on STDOUT only, do nothing on the database") do |value|
|
52
58
|
self.conn_options[:trace_sql] = true
|
53
59
|
self.conn_options[:trace_only] = true
|
60
|
+
self.conn_options[:trace_buffer] = environment.output_buffer
|
54
61
|
end
|
55
62
|
|
56
63
|
opt.separator "\nRecognized format options:"
|
@@ -106,7 +113,7 @@ module DbAgile
|
|
106
113
|
# Executes the command
|
107
114
|
def execute_command
|
108
115
|
with_current_connection(conn_options) do |connection|
|
109
|
-
|
116
|
+
|
110
117
|
# Make the job now
|
111
118
|
connection.transaction do |t|
|
112
119
|
first = true
|
@@ -119,10 +126,33 @@ module DbAgile
|
|
119
126
|
end
|
120
127
|
end
|
121
128
|
|
129
|
+
def find_key(t, tuple)
|
130
|
+
t.keys(self.dataset).find{|key|
|
131
|
+
key.all?{|k| tuple.has_key?(k)}
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
122
135
|
# Makes the insert job
|
123
136
|
def make_the_job(t, tuple, first = true)
|
124
137
|
handle_schema_modification(t, tuple) if first
|
125
|
-
|
138
|
+
if self.update_on_existing
|
139
|
+
|
140
|
+
# find a candidate key
|
141
|
+
key = find_key(t, tuple)
|
142
|
+
if key.nil?
|
143
|
+
raise DbAgile::CandidateKeyNotFoundError, "Unable to find a candidate key for UPDATE"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Make insert or update according to key
|
147
|
+
key_value = tuple_project(tuple, key)
|
148
|
+
if t.exists?(self.dataset, key_value)
|
149
|
+
t.update(self.dataset, tuple, key_value)
|
150
|
+
else
|
151
|
+
t.insert(self.dataset, tuple)
|
152
|
+
end
|
153
|
+
else
|
154
|
+
t.insert(self.dataset, tuple)
|
155
|
+
end
|
126
156
|
end
|
127
157
|
|
128
158
|
# Handles the schema modifications
|
@@ -4,7 +4,7 @@ module DbAgile
|
|
4
4
|
#
|
5
5
|
# Check a database schema
|
6
6
|
#
|
7
|
-
# Usage: dba #{command_name} [SCHEMA.yaml|announced|effective|physical]
|
7
|
+
# Usage: dba #{command_name} [options] [SCHEMA.yaml|announced|effective|physical]
|
8
8
|
#
|
9
9
|
# This command informs you about bad smells and good practices with relational
|
10
10
|
# schemas (i.e. forgetting to create keys, not providing unique constraint
|
@@ -33,6 +33,7 @@ module DbAgile
|
|
33
33
|
# Contribute to options
|
34
34
|
def add_options(opt)
|
35
35
|
self.check_schemas = false
|
36
|
+
add_stdin_options(opt)
|
36
37
|
end
|
37
38
|
|
38
39
|
# Executes the command
|
@@ -6,6 +6,9 @@ module DbAgile
|
|
6
6
|
# Checks schema(s) first?
|
7
7
|
attr_accessor :check_schemas
|
8
8
|
|
9
|
+
# Take schema on standard input?
|
10
|
+
attr_accessor :on_stdin
|
11
|
+
|
9
12
|
# Schema arguments
|
10
13
|
attr_accessor :schema_arguments
|
11
14
|
|
@@ -17,6 +20,13 @@ module DbAgile
|
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
23
|
+
# Adds --stdin option
|
24
|
+
def add_stdin_options(opt)
|
25
|
+
opt.on('--stdin', "Take schema on the standard command input") do
|
26
|
+
self.on_stdin = true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
20
30
|
# Normalizes a schema argument
|
21
31
|
def normalize_schema_argument(argument)
|
22
32
|
if File.exists?(argument) and File.file?(argument)
|
@@ -31,7 +41,7 @@ module DbAgile
|
|
31
41
|
@schema_arguments = case kind_of_schema_arguments
|
32
42
|
when :single
|
33
43
|
if arguments.empty?
|
34
|
-
[ :announced ]
|
44
|
+
on_stdin ? [ :stdin ] : [ :announced ]
|
35
45
|
elsif arguments.size == 1
|
36
46
|
arguments.collect{|arg| normalize_schema_argument(arg) }
|
37
47
|
else
|
@@ -64,6 +74,11 @@ module DbAgile
|
|
64
74
|
# Loads a given schema from a schema argument
|
65
75
|
def load_schema(schema_argument, check = self.check_schemas)
|
66
76
|
schema = case schema_argument
|
77
|
+
when :stdin
|
78
|
+
r = environment.input_buffer.read
|
79
|
+
s = DbAgile::Core::Schema::yaml_load(r)
|
80
|
+
s.schema_identifier = "--stdin"
|
81
|
+
s
|
67
82
|
when Symbol
|
68
83
|
with_current_database do |database|
|
69
84
|
case schema_argument
|
@@ -4,13 +4,32 @@ module DbAgile
|
|
4
4
|
#
|
5
5
|
# Dump a schema of the current database
|
6
6
|
#
|
7
|
-
# Usage: dba #{command_name} [announced|effective|physical]
|
7
|
+
# Usage: dba #{command_name} [options] [announced|effective|physical]
|
8
8
|
#
|
9
9
|
# This command dumps the schema of the current database on the output console.
|
10
10
|
# Without argument, the announced schema is implicit. This command uses the
|
11
11
|
# fallback chain (announced -> effective -> physical) and has no side-effect
|
12
12
|
# on the database itself (read-only).
|
13
13
|
#
|
14
|
+
# Schema filtering is possible via the --include and --exclude options, which
|
15
|
+
# allow including/excluding specific schema object kinds. When used conjointly,
|
16
|
+
# the semantics is '--include AND NOT(--exclude)'. The different object kinds
|
17
|
+
# are:
|
18
|
+
# * logical (all logical objects, see indented list below)
|
19
|
+
# * table (base tables)
|
20
|
+
# * view (derived tables, aka views)
|
21
|
+
# * constraint (all constraints, see indented list below)
|
22
|
+
# * candidate_key (all candidate keys, primary or not)
|
23
|
+
# * primary_key (all primary keys)
|
24
|
+
# * foreign_key (all foreign keys)
|
25
|
+
# * physical (all physical objects, see indented list below)
|
26
|
+
# * index (all physical indexes)
|
27
|
+
#
|
28
|
+
# A typical usage of schema filtering is to generate a script that drops all
|
29
|
+
# constraints at once by chaining this command with sql-script:
|
30
|
+
#
|
31
|
+
# dba schema:dump --include=constraint | dba schema:sql-script --stdin drop
|
32
|
+
#
|
14
33
|
# NOTE: Schema-checking is on by default, which may lead to checking errors,
|
15
34
|
# typically when reverse engineering poorly designed databases. Doing so
|
16
35
|
# immediately informs you about a potential problem.
|
@@ -21,22 +40,84 @@ module DbAgile
|
|
21
40
|
include Schema::Commons
|
22
41
|
Command::build_me(self, __FILE__)
|
23
42
|
|
43
|
+
QUERIES = {
|
44
|
+
:logical => [:logical?],
|
45
|
+
:table => [:attribute?, :constraint?],
|
46
|
+
:view => [:relview?],
|
47
|
+
:constraint => [:constraint?],
|
48
|
+
:candidate_key => [:candidate_key],
|
49
|
+
:primary_key => [:primary_key],
|
50
|
+
:foreign_key => [:foreign_key],
|
51
|
+
:physical => [:physical?],
|
52
|
+
:index => [:index?]
|
53
|
+
}
|
54
|
+
|
55
|
+
# Returns :single
|
56
|
+
def kind_of_schema_arguments
|
57
|
+
:single
|
58
|
+
end
|
59
|
+
|
60
|
+
# Include and exclude options
|
61
|
+
attr_accessor :include_kind, :exclude_kind
|
62
|
+
|
63
|
+
# Marks a kind a begin included
|
64
|
+
def include_kind!(kind)
|
65
|
+
if queries = QUERIES[kind]
|
66
|
+
self.include_kind ||= []
|
67
|
+
self.include_kind += queries
|
68
|
+
else
|
69
|
+
raise DbAgile::Error, "Unrecognized object kind #{kind}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Marks a kind a begin excluded
|
74
|
+
def exclude_kind!(kind)
|
75
|
+
if queries = QUERIES[kind]
|
76
|
+
self.exclude_kind ||= []
|
77
|
+
self.exclude_kind += queries
|
78
|
+
else
|
79
|
+
raise DbAgile::Error, "Unrecognized object kind #{kind}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
24
83
|
# Contribute to options
|
25
84
|
def add_options(opt)
|
26
85
|
opt.separator nil
|
27
86
|
opt.separator "Options:"
|
28
87
|
add_check_options(opt)
|
88
|
+
add_stdin_options(opt)
|
89
|
+
opt.on('--include=x,y,z', Array,
|
90
|
+
"Include object kinds (logical, physical, candidate_key, ...)") do |values|
|
91
|
+
values.each{|value| include_kind!(value.to_sym)}
|
92
|
+
end
|
93
|
+
opt.on('--exclude=x,y,z', Array,
|
94
|
+
"Exclude object kinds (logical, physical, candidate_key, ...)") do |values|
|
95
|
+
values.each{|value| exclude_kind!(value.to_sym)}
|
96
|
+
end
|
29
97
|
end
|
30
98
|
|
31
|
-
# Returns
|
32
|
-
def
|
33
|
-
|
99
|
+
# Returns the kind of an object
|
100
|
+
def include?(obj)
|
101
|
+
if include_kind && !include_kind.any?{|kind| obj.send(kind)}
|
102
|
+
return false
|
103
|
+
end
|
104
|
+
if exclude_kind && exclude_kind.any?{|kind| obj.send(kind)}
|
105
|
+
return false
|
106
|
+
end
|
107
|
+
true
|
108
|
+
end
|
109
|
+
|
110
|
+
# Filters the schema according to --select and --reject
|
111
|
+
# options.
|
112
|
+
def filter_shema(schema)
|
113
|
+
schema.filter{|obj| include?(obj)}
|
34
114
|
end
|
35
115
|
|
36
116
|
# Executes the command
|
37
117
|
def execute_command
|
38
118
|
with_schema do |schema|
|
39
119
|
say("# Schema of #{schema.schema_identifier.inspect}", :magenta)
|
120
|
+
schema = filter_shema(schema)
|
40
121
|
flush(schema.to_yaml)
|
41
122
|
schema
|
42
123
|
end
|
@@ -45,6 +45,15 @@ module DbAgile
|
|
45
45
|
DbAgile::IO::XML::to_xml(self, self.columns, buffer, options)
|
46
46
|
end
|
47
47
|
|
48
|
+
#
|
49
|
+
# Outputs this dataset as a HTML string
|
50
|
+
#
|
51
|
+
# @return [...] the buffer itself
|
52
|
+
#
|
53
|
+
def to_html(buffer = "", options = {})
|
54
|
+
DbAgile::IO::HTML::to_html(self, self.columns, buffer, options)
|
55
|
+
end
|
56
|
+
|
48
57
|
#
|
49
58
|
# Outputs this dataset as a Ruby Array of hashes string
|
50
59
|
#
|
@@ -117,10 +117,14 @@ module DbAgile
|
|
117
117
|
|
118
118
|
# Starts a relvar section
|
119
119
|
def relvar(name, hash = nil, &block)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
120
|
+
if String === hash
|
121
|
+
(_peek(:logical)[name] ||= build_relview(name, hash))
|
122
|
+
else
|
123
|
+
block = lambda{ _natural(hash) } unless block
|
124
|
+
name = coerce_relvar_name(name)
|
125
|
+
relvar = (_peek(:logical)[name] ||= build_relvar(name))
|
126
|
+
_push(:relvar, relvar, &block)
|
127
|
+
end
|
124
128
|
rescue SByC::TypeSystem::CoercionError => ex
|
125
129
|
invalid!("Invalid relvar definition (#{name}): #{ex.message}")
|
126
130
|
end
|
@@ -14,7 +14,7 @@ module DbAgile
|
|
14
14
|
end
|
15
15
|
|
16
16
|
# Computes key differences
|
17
|
-
left_keys, right_keys = left.part_keys
|
17
|
+
left_keys, right_keys = left.part_keys, right.part_keys
|
18
18
|
left_only = left_keys - right_keys
|
19
19
|
right_only = right_keys - left_keys
|
20
20
|
commons = left_keys & right_keys
|