db_schema 0.1.3 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f262094701b7ff8ff410c8854af2676daff44f4f
4
- data.tar.gz: 0ba850facc23b85c344314a7e1e8b567ac762e4a
3
+ metadata.gz: 3851833bd5308ba174d2d70c217b6fc73c7d583b
4
+ data.tar.gz: 95071a258ce6202862e38a64d0d57fee8a0cfea9
5
5
  SHA512:
6
- metadata.gz: 6fb1c3a34728124816899da8c6f67d82d70a38a0f40228cb65fca5597db8db9984fa5490554c64f0484e4160f33da94220fc385cc4b78298c6103aad189b9a6e
7
- data.tar.gz: daeb82268ecfa4202dee52f97d92c8f4e2c6cd01a0af5c24ebb8391f16128836137a8914b8ed752eef3a4d07a478853dc75a1315358e2bc2b51df447e9d451a1
6
+ metadata.gz: 424334e2045a9cf08bdf6fedddbc8710c05f1d101ac2bd22350191e71e8dccc201118bb3511590ff56d67610c80494e6c7aed98d2d2e263b582343f910179567
7
+ data.tar.gz: cac4af5351c520d8610a98c54e57d7c93a884bcc0f0b33e953eedb45291e43e9cfd5fccfef47ee791e3ab07dcde72a546c6c3f0d22959c3acdf8f9b24fe1f1c6
data/README.md CHANGED
@@ -53,7 +53,7 @@ But you would lose it even with manual migrations.
53
53
  Add this line to your application's Gemfile:
54
54
 
55
55
  ``` ruby
56
- gem 'db_schema', '~> 0.1.3'
56
+ gem 'db_schema', '~> 0.2.0'
57
57
  ```
58
58
 
59
59
  And then execute:
@@ -207,7 +207,7 @@ db.table :people do |t|
207
207
  end
208
208
  ```
209
209
 
210
- Passing `null: false` to the field definition makes it `NOT NULL`; passing some value under the `:default` key makes it the default value. A symbol passed as a default is interpreted as a function call so `t.timestamp :created_at, default: :now` defines a field with a default value of `NOW()`; strings, numbers, timestamps etc are evaluated "as is".
210
+ Passing `null: false` to the field definition makes it `NOT NULL`; passing some value under the `:default` key makes it the default value. You can use `String`s as SQL strings, `Fixnum`s as integers, `Float`s as floating point numbers, `true` & `false` as their SQL counterparts, `Date`s as SQL dates and `Time`s as timestamps. A symbol passed as a default is a special case: it is interpreted as an SQL expression so `t.timestamp :created_at, default: :'now()'` defines a field with a default value of `NOW()`.
211
211
 
212
212
  Other attributes are type specific, like `:length` for varchars; the following table lists them all (values in parentheses are default attribute values).
213
213
 
@@ -354,8 +354,6 @@ db.table :users do |t|
354
354
  end
355
355
  ```
356
356
 
357
- Be warned though that you have to specify the condition exactly as PostgreSQL outputs it in `psql` with `\d table_name` command; otherwise your index will be recreated on each DbSchema run. This will be fixed in a later DbSchema version.
358
-
359
357
  If you need an index on expression you can use the same syntax replacing column names with SQL strings containing the expressions:
360
358
 
361
359
  ``` ruby
@@ -367,8 +365,6 @@ end
367
365
 
368
366
  Expression indexes syntax allows specifying an order exactly like in a common index on table fields - just use a hash form like `t.index 'date(created_at)' => :desc`. You can also use an expression in a multiple index.
369
367
 
370
- As with partial index condition (and all other SQL segments in `db_schema`), you must write the expression in a way `psql` outputs it, so instead of `lower(email)` you should use `lower(email::text)` (assuming that `email` is a varchar field).
371
-
372
368
  #### Foreign keys
373
369
 
374
370
  The `#foreign_key` method defines a foreign key. In it's minimal form it takes a referencing field name and referenced table name:
@@ -439,8 +435,6 @@ db.table :users do |t|
439
435
  end
440
436
  ```
441
437
 
442
- As with partial index conditions, for now you have to specify the SQL exactly as `psql` outputs it (otherwise the constraint will be recreated on each run).
443
-
444
438
  ### Enum types
445
439
 
446
440
  PostgreSQL allows developers to create custom enum types; value of enum type is one of a fixed set of values stored in the type definition.
@@ -532,9 +526,7 @@ All configuration options are described in the following table:
532
526
 
533
527
  By default DbSchema logs the changes it applies to your database; you can disable that by setting `log_changes` to false.
534
528
 
535
- DbSchema provides an opt-out post-run schema check; it ensures that there are no remaining differences between your `schema.rb` and the actual database schema. If DbSchema still sees any differences it will keep applying them on each run - usually this is harmless (because it does not really change your schema) but in the case of a partial index with a complex condition or an index on some expression it may rebuild the index which is an expensive operation on a large table. You can set `post_check` to false if you are 100% sure that your persistent changes are not a problem for you but I strongly recommend that you turn it on from time to time just to make sure nothing dangerous appears in these persistent changes.
536
-
537
- The `post_check` option is likely to become off by default when DbSchema becomes more stable and battle-tested, and when the partial index problem will be solved.
529
+ DbSchema provides an opt-out post-run schema check; it ensures that the schema was applied correctly and there are no remaining differences between your `schema.rb` and the actual database schema. The corresponding `post_check` option is likely to become off by default when DbSchema becomes more stable and battle-tested.
538
530
 
539
531
  There is also a dry run mode which does not apply the changes to your database - it just logs the necessary changes (if you leave `log_changes` set to `true`). Post check is also skipped in that case.
540
532
 
@@ -543,12 +535,8 @@ Dry run may be useful while you are building your schema definition for an exist
543
535
  ## Known problems and limitations
544
536
 
545
537
  * primary keys are hardcoded to a single NOT NULL integer field with a postgres sequence attached
546
- * "partial index problem": some conditions of partial indexes and check constraints can cause
547
- a false positive result of checking for differences between `schema.rb` and actual database schema,
548
- resulting in unwanted operations on each run (the worst of them being the recreation of an index
549
- on a large table)
550
538
  * array element type attributes are not supported
551
- * precision in time & datetime types isn't supported
539
+ * precision in all date/time types isn't supported
552
540
  * no support for databases other than PostgreSQL
553
541
  * no support for renaming tables & columns
554
542
 
@@ -13,6 +13,8 @@ if defined?(AwesomePrint)
13
13
 
14
14
  def cast_with_dbschema(object, type)
15
15
  case object
16
+ when ::DbSchema::Definitions::Schema
17
+ :dbschema_schema
16
18
  when ::DbSchema::Definitions::Table
17
19
  :dbschema_table
18
20
  when ::DbSchema::Definitions::Field::Custom
@@ -79,6 +81,15 @@ if defined?(AwesomePrint)
79
81
  end
80
82
 
81
83
  private
84
+ def awesome_dbschema_schema(object)
85
+ data = ["tables: #{object.tables.ai}"]
86
+ data << "enums: #{object.enums.ai}" if object.enums.any?
87
+ data << "extensions: #{object.extensions.ai}" if object.extensions.any?
88
+
89
+ data_string = data.join(', ')
90
+ "#<DbSchema::Definitions::Schema #{data_string}>"
91
+ end
92
+
82
93
  def awesome_dbschema_table(object)
83
94
  data = ["fields: #{object.fields.ai}"]
84
95
  data << "indices: #{object.indices.ai}" if object.indices.any?
@@ -92,7 +103,12 @@ if defined?(AwesomePrint)
92
103
  def awesome_dbschema_field(object)
93
104
  options = object.options.map do |k, v|
94
105
  key = colorize("#{k}:", :symbol)
95
- "#{key} #{v.ai}"
106
+
107
+ if (k == :default) && v.is_a?(Symbol)
108
+ "#{key} #{colorize(v.to_s, :string)}"
109
+ else
110
+ "#{key} #{v.ai}"
111
+ end
96
112
  end.unshift(nil).join(', ')
97
113
 
98
114
  primary_key = if object.primary_key?
@@ -107,7 +123,12 @@ if defined?(AwesomePrint)
107
123
  def awesome_dbschema_custom_field(object)
108
124
  options = object.options.map do |k, v|
109
125
  key = colorize("#{k}:", :symbol)
110
- "#{key} #{v.ai}"
126
+
127
+ if (k == :default) && v.is_a?(Symbol)
128
+ "#{key} #{colorize(v.to_s, :string)}"
129
+ else
130
+ "#{key} #{v.ai}"
131
+ end
111
132
  end.unshift(nil).join(', ')
112
133
 
113
134
  primary_key = if object.primary_key?
@@ -212,7 +233,13 @@ if defined?(AwesomePrint)
212
233
  end
213
234
 
214
235
  def awesome_dbschema_alter_column_default(object)
215
- "#<DbSchema::Changes::AlterColumnDefault #{object.name.ai}, #{object.new_default.ai}>"
236
+ new_default = if object.new_default.is_a?(Symbol)
237
+ colorize(object.new_default.to_s, :string)
238
+ else
239
+ object.new_default.ai
240
+ end
241
+
242
+ "#<DbSchema::Changes::AlterColumnDefault #{object.name.ai}, #{new_default}>"
216
243
  end
217
244
 
218
245
  def awesome_dbschema_create_foreign_key(object)
@@ -5,14 +5,11 @@ module DbSchema
5
5
  module Changes
6
6
  class << self
7
7
  def between(desired_schema, actual_schema)
8
- desired_tables = extract_tables(desired_schema)
9
- actual_tables = extract_tables(actual_schema)
10
-
11
- table_names = [desired_tables, actual_tables].flatten.map(&:name).uniq
8
+ table_names = [desired_schema.tables, actual_schema.tables].flatten.map(&:name).uniq
12
9
 
13
10
  table_changes = table_names.each.with_object([]) do |table_name, changes|
14
- desired = desired_tables.find { |table| table.name == table_name }
15
- actual = actual_tables.find { |table| table.name == table_name }
11
+ desired = desired_schema.tables.find { |table| table.name == table_name }
12
+ actual = actual_schema.tables.find { |table| table.name == table_name }
16
13
 
17
14
  if desired && !actual
18
15
  changes << CreateTable.new(
@@ -51,14 +48,11 @@ module DbSchema
51
48
  end
52
49
  end
53
50
 
54
- desired_enums = extract_enums(desired_schema)
55
- actual_enums = extract_enums(actual_schema)
56
-
57
- enum_names = [desired_enums, actual_enums].flatten.map(&:name).uniq
51
+ enum_names = [desired_schema.enums, actual_schema.enums].flatten.map(&:name).uniq
58
52
 
59
53
  enum_changes = enum_names.each_with_object([]) do |enum_name, changes|
60
- desired = desired_enums.find { |enum| enum.name == enum_name }
61
- actual = actual_enums.find { |enum| enum.name == enum_name }
54
+ desired = desired_schema.enums.find { |enum| enum.name == enum_name }
55
+ actual = actual_schema.enums.find { |enum| enum.name == enum_name }
62
56
 
63
57
  if desired && !actual
64
58
  changes << CreateEnum.new(enum_name, desired.values)
@@ -89,12 +83,9 @@ module DbSchema
89
83
  end
90
84
  end
91
85
 
92
- desired_extensions = extract_extensions(desired_schema)
93
- actual_extensions = extract_extensions(actual_schema)
94
-
95
- extension_changes = (desired_extensions - actual_extensions).map do |extension|
86
+ extension_changes = (desired_schema.extensions - actual_schema.extensions).map do |extension|
96
87
  CreateExtension.new(extension.name)
97
- end + (actual_extensions - desired_extensions).map do |extension|
88
+ end + (actual_schema.extensions - desired_schema.extensions).map do |extension|
98
89
  DropExtension.new(extension.name)
99
90
  end
100
91
 
@@ -226,18 +217,6 @@ module DbSchema
226
217
  end
227
218
  end
228
219
  end
229
-
230
- def extract_tables(schema)
231
- Utils.filter_by_class(schema, Definitions::Table)
232
- end
233
-
234
- def extract_enums(schema)
235
- Utils.filter_by_class(schema, Definitions::Enum)
236
- end
237
-
238
- def extract_extensions(schema)
239
- Utils.filter_by_class(schema, Definitions::Extension)
240
- end
241
220
  end
242
221
 
243
222
  class CreateTable
@@ -9,7 +9,7 @@ module DbSchema
9
9
  @name = name
10
10
  @primary_key = primary_key
11
11
  @null = null
12
- @default = process_default(default)
12
+ @default = default
13
13
  @attributes = attributes
14
14
  end
15
15
 
@@ -21,6 +21,10 @@ module DbSchema
21
21
  !primary_key? && @null
22
22
  end
23
23
 
24
+ def default_is_expression?
25
+ default.is_a?(Symbol)
26
+ end
27
+
24
28
  def options
25
29
  attributes.tap do |options|
26
30
  options[:null] = false unless null?
@@ -48,14 +52,6 @@ module DbSchema
48
52
  self.class.type
49
53
  end
50
54
 
51
- def process_default(default)
52
- if default.is_a?(Symbol)
53
- Sequel.function(default)
54
- else
55
- default
56
- end
57
- end
58
-
59
55
  class << self
60
56
  def register(*types)
61
57
  types.each do |type|
@@ -23,7 +23,7 @@ module DbSchema
23
23
 
24
24
  class Interval < Base
25
25
  register :interval
26
- attributes :fields, :precision
26
+ attributes :fields
27
27
  end
28
28
  end
29
29
  end
@@ -2,6 +2,37 @@ require 'dry/equalizer'
2
2
 
3
3
  module DbSchema
4
4
  module Definitions
5
+ class Schema
6
+ include Dry::Equalizer(:tables, :enums, :extensions)
7
+ attr_reader :tables, :enums, :extensions
8
+ attr_writer :tables
9
+
10
+ def initialize(tables: [], enums: [], extensions: [])
11
+ @tables = tables
12
+ @enums = enums
13
+ @extensions = extensions
14
+ end
15
+ end
16
+
17
+ class Table
18
+ include Dry::Equalizer(:name, :fields, :indices, :checks, :foreign_keys)
19
+ attr_reader :name, :fields, :indices, :checks, :foreign_keys
20
+
21
+ def initialize(name, fields: [], indices: [], checks: [], foreign_keys: [])
22
+ @name = name.to_sym
23
+ @fields = fields
24
+ @indices = indices
25
+ @checks = checks
26
+ @foreign_keys = foreign_keys
27
+ end
28
+
29
+ def has_expressions?
30
+ fields.any?(&:default_is_expression?) ||
31
+ indices.any?(&:has_expressions?) ||
32
+ checks.any?
33
+ end
34
+ end
35
+
5
36
  class Index
6
37
  include Dry::Equalizer(:name, :columns, :unique?, :type, :condition)
7
38
  attr_reader :name, :columns, :type, :condition
@@ -30,6 +61,10 @@ module DbSchema
30
61
  end
31
62
  end
32
63
 
64
+ def has_expressions?
65
+ !condition.nil? || columns.any?(&:expression?)
66
+ end
67
+
33
68
  class Column
34
69
  include Dry::Equalizer(:name, :order, :nulls)
35
70
  attr_reader :name, :order, :nulls
@@ -130,19 +165,6 @@ module DbSchema
130
165
  end
131
166
  end
132
167
 
133
- class Table
134
- include Dry::Equalizer(:name, :fields, :indices, :checks, :foreign_keys)
135
- attr_reader :name, :fields, :indices, :checks, :foreign_keys
136
-
137
- def initialize(name, fields: [], indices: [], checks: [], foreign_keys: [])
138
- @name = name.to_sym
139
- @fields = fields
140
- @indices = indices
141
- @checks = checks
142
- @foreign_keys = foreign_keys
143
- end
144
- end
145
-
146
168
  class Enum
147
169
  include Dry::Equalizer(:name, :values)
148
170
  attr_reader :name, :values
data/lib/db_schema/dsl.rb CHANGED
@@ -4,7 +4,7 @@ module DbSchema
4
4
 
5
5
  def initialize(block)
6
6
  @block = block
7
- @schema = []
7
+ @schema = Definitions::Schema.new
8
8
  end
9
9
 
10
10
  def schema
@@ -16,7 +16,7 @@ module DbSchema
16
16
  def table(name, &block)
17
17
  table_yielder = TableYielder.new(name, block)
18
18
 
19
- @schema << Definitions::Table.new(
19
+ @schema.tables << Definitions::Table.new(
20
20
  name,
21
21
  fields: table_yielder.fields,
22
22
  indices: table_yielder.indices,
@@ -26,11 +26,11 @@ module DbSchema
26
26
  end
27
27
 
28
28
  def enum(name, values)
29
- @schema << Definitions::Enum.new(name.to_sym, values.map(&:to_sym))
29
+ @schema.enums << Definitions::Enum.new(name.to_sym, values.map(&:to_sym))
30
30
  end
31
31
 
32
32
  def extension(name)
33
- @schema << Definitions::Extension.new(name.to_sym)
33
+ @schema.extensions << Definitions::Extension.new(name.to_sym)
34
34
  end
35
35
 
36
36
  class TableYielder
@@ -0,0 +1,89 @@
1
+ require 'digest/md5'
2
+
3
+ module DbSchema
4
+ class Normalizer
5
+ attr_reader :table
6
+
7
+ def initialize(table)
8
+ @table = table
9
+ end
10
+
11
+ def normalized_table
12
+ create_temporary_table!
13
+ read_temporary_table
14
+ ensure
15
+ cleanup!
16
+ end
17
+
18
+ private
19
+ def create_temporary_table!
20
+ operation = Changes::CreateTable.new(
21
+ temporary_table_name,
22
+ fields: table.fields,
23
+ indices: rename_indices(table.indices),
24
+ checks: table.checks
25
+ )
26
+
27
+ Runner.new([operation]).run!
28
+ end
29
+
30
+ def read_temporary_table
31
+ temporary_table = Reader.read_table(temporary_table_name)
32
+
33
+ Definitions::Table.new(
34
+ remove_hash(temporary_table.name),
35
+ fields: temporary_table.fields,
36
+ indices: rename_indices_back(temporary_table.indices),
37
+ checks: temporary_table.checks,
38
+ foreign_keys: table.foreign_keys
39
+ )
40
+ end
41
+
42
+ def cleanup!
43
+ DbSchema.connection.drop_table(temporary_table_name, if_exists: true)
44
+ end
45
+
46
+ def rename_indices(indices)
47
+ indices.map do |index|
48
+ Definitions::Index.new(
49
+ name: append_hash(index.name),
50
+ columns: index.columns,
51
+ unique: index.unique?,
52
+ type: index.type,
53
+ condition: index.condition
54
+ )
55
+ end
56
+ end
57
+
58
+ def rename_indices_back(indices)
59
+ indices.map do |index|
60
+ Definitions::Index.new(
61
+ name: remove_hash(index.name),
62
+ columns: index.columns,
63
+ unique: index.unique?,
64
+ type: index.type,
65
+ condition: index.condition
66
+ )
67
+ end
68
+ end
69
+
70
+ def temporary_table_name
71
+ append_hash(table.name)
72
+ end
73
+
74
+ def append_hash(name)
75
+ "#{name}_#{hash}"
76
+ end
77
+
78
+ def remove_hash(name)
79
+ name.to_s.sub(/_#{Regexp.escape(hash)}$/, '').to_sym
80
+ end
81
+
82
+ def hash
83
+ @hash ||= begin
84
+ names = [table.name] + table.fields.map(&:name) + table.indices.map(&:name) + table.checks.map(&:name)
85
+ Digest::MD5.hexdigest(names.join(','))[0..9]
86
+ end
87
+ end
88
+ end
89
+ end
@@ -5,6 +5,10 @@ module DbSchema
5
5
  adapter.read_schema
6
6
  end
7
7
 
8
+ def read_table(table_name)
9
+ adapter.read_table(table_name)
10
+ end
11
+
8
12
  def adapter
9
13
  adapter_name = DbSchema.configuration.adapter
10
14
  registry.fetch(adapter_name) do |adapter_name|
@@ -19,7 +23,19 @@ module DbSchema
19
23
  end
20
24
 
21
25
  module Postgres
22
- DEFAULT_VALUE = /\A(('(?<string>.*)')|(?<float>\d+\.\d+)|(?<integer>\d+)|(?<boolean>true|false)|((?<function>[A-Za-z_]+)\(\)))/
26
+ DEFAULT_VALUE = /\A(
27
+ ('(?<date>\d{4}-\d{2}-\d{2})'::date)
28
+ |
29
+ ('(?<time>\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}([+-]\d{2})?)'::timestamp)
30
+ |
31
+ ('(?<string>.*)')
32
+ |
33
+ (?<float>\d+\.\d+)
34
+ |
35
+ (?<integer>\d+)
36
+ |
37
+ (?<boolean>true|false)
38
+ )/x
23
39
 
24
40
  COLUMN_NAMES_QUERY = <<-SQL.freeze
25
41
  SELECT c.column_name AS name,
@@ -104,6 +120,10 @@ SELECT extname
104
120
 
105
121
  class << self
106
122
  def read_schema
123
+ tables = DbSchema.connection.tables.map do |table_name|
124
+ read_table(table_name)
125
+ end
126
+
107
127
  enums = DbSchema.connection[ENUMS_QUERY].map do |enum_data|
108
128
  Definitions::Enum.new(enum_data[:name].to_sym, enum_data[:values].map(&:to_sym))
109
129
  end
@@ -112,38 +132,38 @@ SELECT extname
112
132
  Definitions::Extension.new(extension_data[:extname].to_sym)
113
133
  end
114
134
 
115
- tables = DbSchema.connection.tables.map do |table_name|
116
- primary_key_name = DbSchema.connection.primary_key(table_name)
135
+ Definitions::Schema.new(tables: tables, enums: enums, extensions: extensions)
136
+ end
117
137
 
118
- fields = DbSchema.connection[COLUMN_NAMES_QUERY, table_name.to_s].map do |column_data|
119
- build_field(column_data, primary_key: column_data[:name] == primary_key_name)
120
- end
138
+ def read_table(table_name)
139
+ primary_key_name = DbSchema.connection.primary_key(table_name)
121
140
 
122
- indices = indices_data_for(table_name).map do |index_data|
123
- Definitions::Index.new(index_data)
124
- end.sort_by(&:name)
141
+ fields = DbSchema.connection[COLUMN_NAMES_QUERY, table_name.to_s].map do |column_data|
142
+ build_field(column_data, primary_key: column_data[:name] == primary_key_name)
143
+ end
125
144
 
126
- foreign_keys = DbSchema.connection.foreign_key_list(table_name).map do |foreign_key_data|
127
- build_foreign_key(foreign_key_data)
128
- end
145
+ indices = indices_data_for(table_name).map do |index_data|
146
+ Definitions::Index.new(index_data)
147
+ end.sort_by(&:name)
129
148
 
130
- checks = DbSchema.connection[CONSTRAINTS_QUERY, table_name.to_s].map do |check_data|
131
- Definitions::CheckConstraint.new(
132
- name: check_data[:name].to_sym,
133
- condition: check_data[:condition]
134
- )
135
- end
149
+ foreign_keys = DbSchema.connection.foreign_key_list(table_name).map do |foreign_key_data|
150
+ build_foreign_key(foreign_key_data)
151
+ end
136
152
 
137
- Definitions::Table.new(
138
- table_name,
139
- fields: fields,
140
- indices: indices,
141
- checks: checks,
142
- foreign_keys: foreign_keys
153
+ checks = DbSchema.connection[CONSTRAINTS_QUERY, table_name.to_s].map do |check_data|
154
+ Definitions::CheckConstraint.new(
155
+ name: check_data[:name].to_sym,
156
+ condition: check_data[:condition]
143
157
  )
144
158
  end
145
159
 
146
- enums + extensions + tables
160
+ Definitions::Table.new(
161
+ table_name,
162
+ fields: fields,
163
+ indices: indices,
164
+ checks: checks,
165
+ foreign_keys: foreign_keys
166
+ )
147
167
  end
148
168
 
149
169
  def indices_data_for(table_name)
@@ -227,8 +247,12 @@ SELECT extname
227
247
  nullable = (data[:null] != 'NO')
228
248
 
229
249
  unless primary_key || data[:default].nil?
230
- if match = DEFAULT_VALUE.match(data[:default])
231
- default = if match[:string]
250
+ default = if match = DEFAULT_VALUE.match(data[:default])
251
+ if match[:date]
252
+ Date.parse(match[:date])
253
+ elsif match[:time]
254
+ Time.parse(match[:time])
255
+ elsif match[:string]
232
256
  match[:string]
233
257
  elsif match[:integer]
234
258
  match[:integer].to_i
@@ -236,9 +260,9 @@ SELECT extname
236
260
  match[:float].to_f
237
261
  elsif match[:boolean]
238
262
  match[:boolean] == 'true'
239
- elsif match[:function]
240
- match[:function].to_sym
241
263
  end
264
+ else
265
+ data[:default].to_sym
242
266
  end
243
267
  end
244
268
 
@@ -133,7 +133,7 @@ module DbSchema
133
133
  when Changes::DisallowNull
134
134
  set_column_not_null(element.name)
135
135
  when Changes::AlterColumnDefault
136
- set_column_default(element.name, element.new_default)
136
+ set_column_default(element.name, Runner.default_to_sequel(element.new_default))
137
137
  when Changes::CreateIndex
138
138
  add_index(
139
139
  element.columns_to_sequel,
@@ -216,6 +216,20 @@ module DbSchema
216
216
  else
217
217
  options
218
218
  end
219
+
220
+ if mapping.key?(:default)
221
+ mapping.merge(default: default_to_sequel(mapping[:default]))
222
+ else
223
+ mapping
224
+ end
225
+ end
226
+
227
+ def default_to_sequel(default)
228
+ if default.is_a?(Symbol)
229
+ Sequel.lit(default.to_s)
230
+ else
231
+ default
232
+ end
219
233
  end
220
234
  end
221
235
  end
@@ -2,10 +2,7 @@ module DbSchema
2
2
  module Validator
3
3
  class << self
4
4
  def validate(schema)
5
- tables = Utils.filter_by_class(schema, Definitions::Table)
6
- enums = Utils.filter_by_class(schema, Definitions::Enum)
7
-
8
- table_errors = tables.each_with_object([]) do |table, errors|
5
+ table_errors = schema.tables.each_with_object([]) do |table, errors|
9
6
  primary_keys_count = table.fields.select(&:primary_key?).count
10
7
  if primary_keys_count > 1
11
8
  error_message = %(Table "#{table.name}" has #{primary_keys_count} primary keys)
@@ -14,7 +11,7 @@ module DbSchema
14
11
 
15
12
  table.fields.each do |field|
16
13
  if field.is_a?(Definitions::Field::Custom)
17
- unless enums.map(&:name).include?(field.type_name)
14
+ unless schema.enums.map(&:name).include?(field.type_name)
18
15
  error_message = %(Field "#{table.name}.#{field.name}" has unknown type "#{field.type_name}")
19
16
  errors << error_message
20
17
  end
@@ -40,7 +37,7 @@ module DbSchema
40
37
  end
41
38
  end
42
39
 
43
- if referenced_table = schema.find { |table| table.name == fkey.table }
40
+ if referenced_table = schema.tables.find { |table| table.name == fkey.table }
44
41
  if fkey.references_primary_key?
45
42
  unless referenced_table.fields.any?(&:primary_key?)
46
43
  error_message = %(Foreign key "#{fkey.name}" refers to primary key of table "#{fkey.table}" which does not have a primary key)
@@ -63,7 +60,7 @@ module DbSchema
63
60
  end
64
61
  end
65
62
 
66
- enum_errors = enums.each_with_object([]) do |enum, errors|
63
+ enum_errors = schema.enums.each_with_object([]) do |enum, errors|
67
64
  if enum.values.empty?
68
65
  error_message = %(Enum "#{enum.name}" contains no values)
69
66
  errors << error_message
@@ -1,3 +1,3 @@
1
1
  module DbSchema
2
- VERSION = '0.1.3'
2
+ VERSION = '0.2'
3
3
  end
data/lib/db_schema.rb CHANGED
@@ -7,6 +7,7 @@ require 'db_schema/definitions'
7
7
  require 'db_schema/awesome_print'
8
8
  require 'db_schema/dsl'
9
9
  require 'db_schema/validator'
10
+ require 'db_schema/normalizer'
10
11
  require 'db_schema/reader'
11
12
  require 'db_schema/changes'
12
13
  require 'db_schema/runner'
@@ -17,6 +18,7 @@ module DbSchema
17
18
  def describe(&block)
18
19
  desired_schema = DSL.new(block).schema
19
20
  validate(desired_schema)
21
+ normalize(desired_schema)
20
22
 
21
23
  actual_schema = Reader.read_schema
22
24
  changes = Changes.between(desired_schema, actual_schema)
@@ -88,6 +90,18 @@ module DbSchema
88
90
  end
89
91
  end
90
92
 
93
+ def normalize(schema)
94
+ normalized_tables = schema.tables.map do |table|
95
+ if table.has_expressions?
96
+ Normalizer.new(table).normalized_table
97
+ else
98
+ table
99
+ end
100
+ end
101
+
102
+ schema.tables = normalized_tables
103
+ end
104
+
91
105
  def log_changes(changes)
92
106
  return if changes.empty?
93
107
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: db_schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vsevolod Romashov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-10-18 00:00:00.000000000 Z
11
+ date: 2016-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -214,6 +214,7 @@ files:
214
214
  - lib/db_schema/definitions/field/text_search.rb
215
215
  - lib/db_schema/definitions/field/uuid.rb
216
216
  - lib/db_schema/dsl.rb
217
+ - lib/db_schema/normalizer.rb
217
218
  - lib/db_schema/reader.rb
218
219
  - lib/db_schema/runner.rb
219
220
  - lib/db_schema/utils.rb