dbagile 0.0.1 → 0.0.2
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.
- 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
|