rom-sql 2.0.0.beta1 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -1
  3. data/lib/rom/plugins/relation/sql/auto_restrictions.rb +19 -6
  4. data/lib/rom/sql/attribute.rb +9 -0
  5. data/lib/rom/sql/commands/create.rb +0 -2
  6. data/lib/rom/sql/commands/delete.rb +0 -2
  7. data/lib/rom/sql/commands/update.rb +0 -2
  8. data/lib/rom/sql/extensions/postgres/attributes_inferrer.rb +1 -1
  9. data/lib/rom/sql/index.rb +16 -0
  10. data/lib/rom/sql/migration/inline_runner.rb +12 -4
  11. data/lib/rom/sql/migration/schema_diff.rb +23 -3
  12. data/lib/rom/sql/plugin/associates.rb +3 -3
  13. data/lib/rom/sql/plugin/timestamps.rb +2 -2
  14. data/lib/rom/sql/relation.rb +7 -0
  15. data/lib/rom/sql/relation/reading.rb +1 -1
  16. data/lib/rom/sql/schema.rb +2 -1
  17. data/lib/rom/sql/schema/dsl.rb +26 -0
  18. data/lib/rom/sql/schema/index_dsl.rb +50 -0
  19. data/lib/rom/sql/schema/inferrer.rb +11 -5
  20. data/lib/rom/sql/version.rb +1 -1
  21. data/spec/integration/auto_migrations/indexes_spec.rb +147 -3
  22. data/spec/integration/{graph_spec.rb → combine_with_spec.rb} +1 -1
  23. data/spec/integration/commands/create_spec.rb +37 -62
  24. data/spec/integration/commands/delete_spec.rb +6 -6
  25. data/spec/integration/commands/update_spec.rb +6 -8
  26. data/spec/integration/plugins/associates/many_to_many_spec.rb +2 -2
  27. data/spec/integration/plugins/associates_spec.rb +7 -7
  28. data/spec/integration/plugins/auto_restrictions_spec.rb +31 -0
  29. data/spec/integration/relation_schema_spec.rb +68 -0
  30. data/spec/integration/schema/inferrer_spec.rb +31 -6
  31. data/spec/support/helpers.rb +1 -1
  32. data/spec/unit/plugin/timestamp_spec.rb +2 -2
  33. data/spec/unit/types_spec.rb +1 -1
  34. metadata +6 -5
  35. data/lib/rom/sql/commands/transaction.rb +0 -33
@@ -24,7 +24,7 @@ RSpec.describe 'Commands / Delete' do
24
24
  describe '#transaction' do
25
25
  it 'deletes in normal way if no error raised' do
26
26
  expect {
27
- delete_user.transaction do
27
+ users.transaction do
28
28
  delete_user.by_name('Jade').call
29
29
  end
30
30
  }.to change { users.count }.by(-1)
@@ -32,9 +32,9 @@ RSpec.describe 'Commands / Delete' do
32
32
 
33
33
  it 'deletes nothing if error was raised' do
34
34
  expect {
35
- delete_user.transaction do
35
+ users.transaction do |t|
36
36
  delete_user.by_name('Jade').call
37
- raise ROM::SQL::Rollback
37
+ t.rollback!
38
38
  end
39
39
  }.to_not change { users.count }
40
40
  end
@@ -42,9 +42,9 @@ RSpec.describe 'Commands / Delete' do
42
42
 
43
43
  describe '#call' do
44
44
  it 'deletes all tuples in a restricted relation' do
45
- result = user_commands.try { delete_user.by_name('Jade').call }
45
+ result = delete_user.by_name('Jade').call
46
46
 
47
- expect(result.value).to eql(id: 3, name: 'Jade')
47
+ expect(result).to eql(id: 3, name: 'Jade')
48
48
  end
49
49
 
50
50
  it 're-raises database error' do
@@ -55,7 +55,7 @@ RSpec.describe 'Commands / Delete' do
55
55
  )
56
56
 
57
57
  expect {
58
- user_commands.try { command.call }
58
+ command.call
59
59
  }.to raise_error(ROM::SQL::DatabaseError, /totally wrong/)
60
60
  end
61
61
  end
@@ -41,17 +41,17 @@ RSpec.describe 'Commands / Update', seeds: false do
41
41
 
42
42
  context '#transaction' do
43
43
  it 'update record if there was no errors' do
44
- result = update_user.transaction do
44
+ result = users.transaction do
45
45
  update_user.by_id(piotr[:id]).call(peter)
46
46
  end
47
47
 
48
- expect(result.value).to eq([{ id: 1, name: 'Peter' }])
48
+ expect(result).to eq([{ id: 1, name: 'Peter' }])
49
49
  end
50
50
 
51
51
  it 'updates nothing if error was raised' do
52
- update_user.transaction do
52
+ users.transaction do |t|
53
53
  update_user.by_id(piotr[:id]).call(peter)
54
- raise ROM::SQL::Rollback
54
+ t.rollback!
55
55
  end
56
56
 
57
57
  expect(users.first[:name]).to eql('Piotr')
@@ -60,11 +60,9 @@ RSpec.describe 'Commands / Update', seeds: false do
60
60
 
61
61
  describe '#call' do
62
62
  it 'updates relation tuples' do
63
- result = user_commands.try do
64
- update_user.by_id(piotr[:id]).call(peter)
65
- end
63
+ result = update_user.by_id(piotr[:id]).call(peter)
66
64
 
67
- expect(result.value.to_a).to match_array([{ id: 1, name: 'Peter' }])
65
+ expect(result.to_a).to match_array([{ id: 1, name: 'Peter' }])
68
66
  end
69
67
 
70
68
  it 're-raises database errors' do |example|
@@ -53,8 +53,8 @@ RSpec.describe 'Plugins / :associates / with many-to-many', :sqlite, seeds: fals
53
53
  end
54
54
 
55
55
  it 'associates a child with many parents' do
56
- add_tags = create_tag.with([{ name: 'red' }, { name: 'blue' }])
57
- add_task = create_task.with(user_id: jane[:id], title: "Jade's task")
56
+ add_tags = create_tag.curry([{ name: 'red' }, { name: 'blue' }])
57
+ add_task = create_task.curry(user_id: jane[:id], title: "Jade's task")
58
58
 
59
59
  command = add_tags >> add_task
60
60
 
@@ -68,8 +68,8 @@ RSpec.describe 'Plugins / :associates', seeds: false do
68
68
 
69
69
  shared_context 'automatic FK setting' do
70
70
  it 'sets foreign key prior execution for many tuples' do
71
- create_user = users[:create].with(name: 'Jade')
72
- create_task = tasks[:create_many].with([{ title: 'Task one' }, { title: 'Task two' }])
71
+ create_user = users[:create].curry(name: 'Jade')
72
+ create_task = tasks[:create_many].curry([{ title: 'Task one' }, { title: 'Task two' }])
73
73
 
74
74
  command = create_user >> create_task
75
75
 
@@ -82,8 +82,8 @@ RSpec.describe 'Plugins / :associates', seeds: false do
82
82
  end
83
83
 
84
84
  it 'sets foreign key prior execution for one tuple' do
85
- create_user = users[:create].with(name: 'Jade')
86
- create_task = tasks[:create_one].with(title: 'Task one')
85
+ create_user = users[:create].curry(name: 'Jade')
86
+ create_task = tasks[:create_one].curry(title: 'Task one')
87
87
 
88
88
  command = create_user >> create_task
89
89
 
@@ -161,9 +161,9 @@ RSpec.describe 'Plugins / :associates', seeds: false do
161
161
  end
162
162
 
163
163
  it 'sets FKs for the join table' do
164
- create_user = users[:create].with(name: 'Jade')
165
- create_task = tasks[:create].with(title: "Jade's task")
166
- create_tags = tags[:create].with([{ name: 'red' }, { name: 'blue' }])
164
+ create_user = users[:create].curry(name: 'Jade')
165
+ create_task = tasks[:create].curry(title: "Jade's task")
166
+ create_tags = tags[:create].curry([{ name: 'red' }, { name: 'blue' }])
167
167
 
168
168
  command = create_user >> create_task >> create_tags
169
169
 
@@ -31,6 +31,10 @@ RSpec.describe 'Plugins / :auto_restrictions', seeds: true do
31
31
  attribute :id, ROM::SQL::Types::Serial
32
32
  attribute :user_id, ROM::SQL::Types::Int
33
33
  attribute :title, ROM::SQL::Types::String.meta(index: true)
34
+
35
+ indexes do
36
+ index :user_id, :title
37
+ end
34
38
  end
35
39
 
36
40
  use :auto_restrictions
@@ -38,6 +42,33 @@ RSpec.describe 'Plugins / :auto_restrictions', seeds: true do
38
42
  end
39
43
 
40
44
  include_context 'auto-generated restriction view'
45
+
46
+ it 'generates restrictrions by a composite index' do
47
+ expect(tasks.by_user_id_and_title(1, "Jane's task").first).to eql(id: 2, user_id: 1, title: "Jane's task")
48
+ end
49
+ end
50
+
51
+ if metadata[:postgres]
52
+ # An auto-generated restriction should include the prediate from the index definition
53
+ # but it seems to be too much from my POV, better leave it to the user
54
+ # Note that this can be enabled later
55
+ it 'skips partial indexes' do
56
+ conf.relation(:tasks) do
57
+ schema do
58
+ attribute :id, ROM::SQL::Types::Serial
59
+ attribute :user_id, ROM::SQL::Types::Int
60
+ attribute :title, ROM::SQL::Types::String
61
+
62
+ indexes do
63
+ index :title, predicate: 'title is not null'
64
+ end
65
+ end
66
+
67
+ use :auto_restrictions
68
+ end
69
+
70
+ expect(tasks).not_to respond_to(:by_title)
71
+ end
41
72
  end
42
73
  end
43
74
  end
@@ -199,5 +199,73 @@ RSpec.describe 'Inferring schema from database' do
199
199
  expect(tag_associations[:published_posts].definition).to eql(assoc)
200
200
  end
201
201
  end
202
+
203
+ context 'defining indexes', :helpers do |ctx|
204
+ it 'allows defining indexes' do
205
+ class Test::Tags < ROM::Relation[:sql]
206
+ schema(:tags) do
207
+ attribute :id, Types::Serial
208
+ attribute :name, Types::String
209
+ attribute :created_at, Types::Time
210
+ attribute :updated_at, Types::Time
211
+
212
+ indexes do
213
+ index :name
214
+ index :created_at, :name
215
+ index :updated_at, name: :recently_idx
216
+ index :created_at, name: :unique_date, unique: true
217
+ end
218
+ end
219
+ end
220
+
221
+ conf.register_relation(Test::Tags)
222
+ schema = container.relations[:tags].schema
223
+
224
+ expect(schema.indexes.to_a).
225
+ to contain_exactly(
226
+ ROM::SQL::Index.new([define_attribute(:name, :String, source: schema.name)]),
227
+ ROM::SQL::Index.new(
228
+ [define_attribute(:created_at, :Time, source: schema.name),
229
+ define_attribute(:name, :String, source: schema.name)]
230
+ ),
231
+ ROM::SQL::Index.new(
232
+ [define_attribute(:updated_at, :Time, source: schema.name)],
233
+ name: :recently_idx
234
+ ),
235
+ ROM::SQL::Index.new(
236
+ [define_attribute(:created_at, :Time, source: schema.name)],
237
+ name: :unique_date,
238
+ unique: true
239
+ )
240
+ )
241
+ end
242
+
243
+ if metadata[:postgres]
244
+ it 'can provide index type' do
245
+ class Test::Tags < ROM::Relation[:sql]
246
+ schema(:tags) do
247
+ attribute :id, Types::Serial
248
+ attribute :name, Types::String
249
+
250
+ indexes do
251
+ index :name, type: :gist
252
+ end
253
+ end
254
+ end
255
+
256
+ conf.register_relation(Test::Tags)
257
+ schema = container.relations[:tags].schema
258
+ index = schema.indexes.first
259
+
260
+ expect(index).to eql(
261
+ ROM::SQL::Index.new(
262
+ [define_attribute(:name, :String, source: schema.name)],
263
+ type: :gist)
264
+ )
265
+
266
+ expect(index.type).to eql(:gist)
267
+ end
268
+ end
269
+ end
202
270
  end
203
271
  end
@@ -12,6 +12,10 @@ RSpec.describe 'Schema inference for common datatypes', seeds: false do
12
12
  Time.mktime(time.year, time.month, time.day, time.hour, time.min, time.sec, usec)
13
13
  end
14
14
 
15
+ def index_by_name(indexes, name)
16
+ indexes.find { |idx| idx.name == name }
17
+ end
18
+
15
19
  with_adapters do |adapter|
16
20
  describe 'inferring attributes' do
17
21
  before do
@@ -335,7 +339,10 @@ RSpec.describe 'Schema inference for common datatypes', seeds: false do
335
339
  end
336
340
 
337
341
  describe 'inferring indices', oracle: false do
338
- before do |ex|
342
+ let(:dataset) { :test_inferrence }
343
+ let(:source) { ROM::Relation::Name[dataset] }
344
+
345
+ it 'infers types with indices' do
339
346
  conn.create_table :test_inferrence do
340
347
  primary_key :id
341
348
  Integer :foo
@@ -348,17 +355,35 @@ RSpec.describe 'Schema inference for common datatypes', seeds: false do
348
355
  index :baz, name: :baz2_idx
349
356
 
350
357
  index %i(bar baz), name: :composite_idx
358
+ index %i(foo bar), name: :unique_idx, unique: true
351
359
  end
352
360
 
353
361
  conf.relation(:test_inferrence) { schema(infer: true) }
362
+
363
+ expect(schema.indexes.map(&:name)).
364
+ to match_array(%i(foo_idx bar_idx baz1_idx baz2_idx composite_idx unique_idx))
365
+
366
+ unique_idx = index_by_name(schema.indexes, :unique_idx)
367
+
368
+ expect(unique_idx).to be_unique
354
369
  end
355
370
 
356
- let(:dataset) { :test_inferrence }
357
- let(:source) { ROM::Relation::Name[dataset] }
371
+ if metadata[:postgres]
372
+ it 'infers cutsom index types' do
373
+ pending 'Sequel not returning index type'
374
+ conn.create_table :test_inferrence do
375
+ primary_key :id
376
+ Integer :foo
377
+ index :foo, name: :foo_idx, type: :gist
378
+ end
358
379
 
359
- it 'infers types with indices' do
360
- expect(schema.indexes.map(&:name)).
361
- to match_array(%i(foo_idx bar_idx baz1_idx baz2_idx composite_idx))
380
+ conf.relation(:test_inferrence) { schema(infer: true) }
381
+
382
+ index = schema.indexes.first
383
+
384
+ expect(index.name).to eql(:foo_idx)
385
+ expect(index.type).to eql(:gist)
386
+ end
362
387
  end
363
388
  end
364
389
  end
@@ -12,7 +12,7 @@ module Helpers
12
12
  )
13
13
  end
14
14
 
15
- def define_type(name, id, **opts)
15
+ def define_attribute(name, id, **opts)
16
16
  ROM::SQL::Attribute.new(ROM::Types.const_get(id).meta(name: name, **opts))
17
17
  end
18
18
 
@@ -97,8 +97,8 @@ RSpec.describe 'Plugin / Timestamp' do
97
97
  end
98
98
 
99
99
  it "works with chained commands" do
100
- create_user = container.commands[:users].create.with(name: "John Doe")
101
- create_note = container.commands[:notes].create_with_user.with(text: "new note")
100
+ create_user = container.commands[:users].create.curry(name: "John Doe")
101
+ create_note = container.commands[:notes].create_with_user.curry(text: "new note")
102
102
 
103
103
  time = DateTime.now
104
104
  command = create_user >> create_note
@@ -12,7 +12,7 @@ RSpec.describe ROM::SQL::Types, :postgres do
12
12
  end
13
13
 
14
14
  describe '#sql_literal', helpers: true do
15
- subject(:base) { define_type(:age, :Int, source: ROM::Relation::Name.new(:users)) }
15
+ subject(:base) { define_attribute(:age, :Int, source: ROM::Relation::Name.new(:users)) }
16
16
 
17
17
  include_context 'database setup'
18
18
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta1
4
+ version: 2.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-30 00:00:00.000000000 Z
11
+ date: 2017-07-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -158,7 +158,6 @@ files:
158
158
  - lib/rom/sql/commands/create.rb
159
159
  - lib/rom/sql/commands/delete.rb
160
160
  - lib/rom/sql/commands/error_wrapper.rb
161
- - lib/rom/sql/commands/transaction.rb
162
161
  - lib/rom/sql/commands/update.rb
163
162
  - lib/rom/sql/dsl.rb
164
163
  - lib/rom/sql/error.rb
@@ -199,6 +198,8 @@ files:
199
198
  - lib/rom/sql/schema.rb
200
199
  - lib/rom/sql/schema/associations_dsl.rb
201
200
  - lib/rom/sql/schema/attributes_inferrer.rb
201
+ - lib/rom/sql/schema/dsl.rb
202
+ - lib/rom/sql/schema/index_dsl.rb
202
203
  - lib/rom/sql/schema/inferrer.rb
203
204
  - lib/rom/sql/spec/support.rb
204
205
  - lib/rom/sql/tasks/migration_tasks.rake
@@ -231,12 +232,12 @@ files:
231
232
  - spec/integration/auto_migrations/indexes_spec.rb
232
233
  - spec/integration/auto_migrations/managing_columns_spec.rb
233
234
  - spec/integration/auto_migrations/postgres/column_types_spec.rb
235
+ - spec/integration/combine_with_spec.rb
234
236
  - spec/integration/commands/create_spec.rb
235
237
  - spec/integration/commands/delete_spec.rb
236
238
  - spec/integration/commands/update_spec.rb
237
239
  - spec/integration/commands/upsert_spec.rb
238
240
  - spec/integration/gateway_spec.rb
239
- - spec/integration/graph_spec.rb
240
241
  - spec/integration/migration_spec.rb
241
242
  - spec/integration/plugins/associates/many_to_many_spec.rb
242
243
  - spec/integration/plugins/associates_spec.rb
@@ -368,12 +369,12 @@ test_files:
368
369
  - spec/integration/auto_migrations/indexes_spec.rb
369
370
  - spec/integration/auto_migrations/managing_columns_spec.rb
370
371
  - spec/integration/auto_migrations/postgres/column_types_spec.rb
372
+ - spec/integration/combine_with_spec.rb
371
373
  - spec/integration/commands/create_spec.rb
372
374
  - spec/integration/commands/delete_spec.rb
373
375
  - spec/integration/commands/update_spec.rb
374
376
  - spec/integration/commands/upsert_spec.rb
375
377
  - spec/integration/gateway_spec.rb
376
- - spec/integration/graph_spec.rb
377
378
  - spec/integration/migration_spec.rb
378
379
  - spec/integration/plugins/associates/many_to_many_spec.rb
379
380
  - spec/integration/plugins/associates_spec.rb
@@ -1,33 +0,0 @@
1
- require 'rom/commands/result'
2
-
3
- module ROM
4
- module SQL
5
- module Commands
6
- # Adds transaction interface to commands
7
- #
8
- # @api private
9
- module Transaction
10
- ROM::SQL::Rollback = Class.new(Sequel::Rollback)
11
-
12
- # Start a transaction
13
- #
14
- # @param [Hash] options The options hash supported by Sequel
15
- #
16
- # @return [ROM::Commands::Result::Success,ROM::Commands::Result::Failure]
17
- #
18
- # @api public
19
- def transaction(options = {}, &block)
20
- result = relation.dataset.db.transaction(options, &block)
21
-
22
- if result
23
- ROM::Commands::Result::Success.new(result)
24
- else
25
- ROM::Commands::Result::Failure.new(result)
26
- end
27
- rescue ROM::CommandError => e
28
- ROM::Commands::Result::Failure.new(e)
29
- end
30
- end
31
- end
32
- end
33
- end