dbagile 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/lib/dbagile.rb +2 -2
  2. data/lib/dbagile/adapter/sequel/data/transaction_driven.rb +1 -0
  3. data/lib/dbagile/adapter/sequel/schema/concrete_script.rb +18 -0
  4. data/lib/dbagile/adapter/sequel/schema/table_driven.rb +10 -1
  5. data/lib/dbagile/adapter/sequel/sequel_tracer.rb +1 -0
  6. data/lib/dbagile/command/bulk/commons.rb +7 -2
  7. data/lib/dbagile/command/bulk/export.rb +4 -1
  8. data/lib/dbagile/command/bulk/import.rb +32 -2
  9. data/lib/dbagile/command/schema/check.rb +2 -1
  10. data/lib/dbagile/command/schema/commons.rb +16 -1
  11. data/lib/dbagile/command/schema/dump.rb +85 -4
  12. data/lib/dbagile/command/schema/sql_script.rb +1 -0
  13. data/lib/dbagile/contract/data/dataset.rb +9 -0
  14. data/lib/dbagile/core/schema/builder.rb +8 -4
  15. data/lib/dbagile/core/schema/builder/concept_factory.rb +5 -0
  16. data/lib/dbagile/core/schema/composite.rb +1 -1
  17. data/lib/dbagile/core/schema/computations/merge.rb +1 -1
  18. data/lib/dbagile/core/schema/errors.rb +1 -3
  19. data/lib/dbagile/core/schema/logical.rb +1 -0
  20. data/lib/dbagile/core/schema/logical/attribute.rb +11 -2
  21. data/lib/dbagile/core/schema/logical/relview.rb +42 -0
  22. data/lib/dbagile/core/schema/migrate/abstract_script.rb +2 -0
  23. data/lib/dbagile/core/schema/migrate/create_view.rb +15 -0
  24. data/lib/dbagile/core/schema/migrate/drop_view.rb +15 -0
  25. data/lib/dbagile/core/schema/migrate/operation.rb +8 -6
  26. data/lib/dbagile/core/schema/migrate/stager.rb +12 -3
  27. data/lib/dbagile/core/schema/part.rb +0 -49
  28. data/lib/dbagile/core/schema/schema_object.rb +54 -0
  29. data/lib/dbagile/environment/repository.rb +2 -2
  30. data/lib/dbagile/errors.rb +3 -0
  31. data/lib/dbagile/io.rb +7 -3
  32. data/lib/dbagile/io/html.rb +48 -0
  33. data/lib/dbagile/restful.rb +1 -1
  34. data/lib/dbagile/restful/middleware/one_database.rb +1 -0
  35. data/lib/dbagile/restful/middleware/post.rb +16 -1
  36. data/lib/dbagile/tools/tuple.rb +7 -0
  37. data/test/assumptions/sequel/connect.spec +12 -9
  38. data/test/assumptions/sequel/test.db +0 -0
  39. data/test/commands.spec +4 -4
  40. data/test/commands/bulk/import.spec +26 -3
  41. data/test/commands/schema/check.spec +20 -13
  42. data/test/commands/schema/dump.spec +23 -0
  43. data/test/fixtures/basics/dbagile.idx +7 -7
  44. data/test/fixtures/basics/robust.db +0 -0
  45. data/test/fixtures/basics/test.db +0 -0
  46. data/test/fixtures/empty/dbagile.idx +1 -1
  47. data/test/restful/get/html_format.ex +12 -0
  48. data/test/run_all_suite.rb +1 -1
  49. data/test/spec_helper.rb +0 -2
  50. data/test/support/be_a_valid_json_string.rb +1 -1
  51. data/test/support/be_a_valid_yaml_string.rb +1 -1
  52. data/test/unit/core/schema/fixtures/views.yaml +5 -0
  53. data/test/unit/core/schema/part_keys.spec +12 -0
  54. data/test/unit/core/schema/yaml_load.spec +5 -0
  55. metadata +414 -244
@@ -2,7 +2,7 @@ require 'dbagile/loader'
2
2
  module DbAgile
3
3
 
4
4
  # Version of the DbAgile interface
5
- VERSION = "0.0.1".freeze
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'
@@ -16,6 +16,7 @@ module DbAgile
16
16
  else
17
17
  db[table_name].where(proj).update(update)
18
18
  end
19
+ update
19
20
  end
20
21
 
21
22
  # @see DbAgile::Contract::Data::TransactionDriven#delete
@@ -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
- db.indexes(table_name).values.select{|i| i[:unique] == true}.collect{|i| i[:columns]}.sort{|a1, a2| a1.size <=> a2.size}
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
@@ -1,3 +1,4 @@
1
+ require 'logger'
1
2
  module DbAgile
2
3
  class SequelAdapter
3
4
  class SequelTracer
@@ -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
- t.insert(self.dataset, tuple)
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 :single
32
- def kind_of_schema_arguments
33
- :single
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
@@ -49,6 +49,7 @@ module DbAgile
49
49
  opt.separator nil
50
50
  opt.separator "Options:"
51
51
  add_check_options(opt)
52
+ add_stdin_options(opt)
52
53
  end
53
54
 
54
55
  # Returns :single
@@ -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
- block = lambda{ _natural(hash) } unless block
121
- name = coerce_relvar_name(name)
122
- relvar = (_peek(:logical)[name] ||= build_relvar(name))
123
- _push(:relvar, relvar, &block)
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
@@ -18,6 +18,11 @@ module DbAgile
18
18
  def build_relvar(name)
19
19
  Schema::Logical::Relvar.new(name)
20
20
  end
21
+
22
+ # Builds a relvar
23
+ def build_relview(name, defn)
24
+ Schema::Logical::Relview.new(name, defn)
25
+ end
21
26
 
22
27
  # Builds a heading
23
28
  def build_heading
@@ -140,7 +140,7 @@ module DbAgile
140
140
  end
141
141
 
142
142
  # @see DbAgile::Core::SchemaObject
143
- def part_keys(sort = false)
143
+ def part_keys(sort = (RUBY_VERSION < "1.9"))
144
144
  if sort
145
145
  @composite_parts.keys.sort{|k1,k2| k1.to_s <=> k2.to_s}
146
146
  else
@@ -14,7 +14,7 @@ module DbAgile
14
14
  end
15
15
 
16
16
  # Computes key differences
17
- left_keys, right_keys = left.part_keys(true), right.part_keys(true)
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