rom-sql 0.9.1 → 1.0.0.beta1
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 +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +32 -0
- data/Gemfile +4 -1
- data/lib/rom/plugins/relation/sql/auto_wrap.rb +1 -3
- data/lib/rom/sql/association.rb +33 -14
- data/lib/rom/sql/association/many_to_many.rb +17 -10
- data/lib/rom/sql/association/many_to_one.rb +29 -13
- data/lib/rom/sql/association/name.rb +12 -4
- data/lib/rom/sql/association/one_to_many.rb +21 -10
- data/lib/rom/sql/commands/create.rb +0 -1
- data/lib/rom/sql/commands/update.rb +1 -49
- data/lib/rom/sql/dsl.rb +29 -0
- data/lib/rom/sql/expression.rb +26 -0
- data/lib/rom/sql/function.rb +23 -0
- data/lib/rom/sql/gateway.rb +24 -9
- data/lib/rom/sql/migration.rb +6 -7
- data/lib/rom/sql/migration/migrator.rb +7 -8
- data/lib/rom/sql/order_dsl.rb +20 -0
- data/lib/rom/sql/plugin/associates.rb +58 -45
- data/lib/rom/sql/plugin/pagination.rb +8 -11
- data/lib/rom/sql/plugins.rb +0 -2
- data/lib/rom/sql/projection_dsl.rb +41 -0
- data/lib/rom/sql/qualified_attribute.rb +2 -2
- data/lib/rom/sql/relation.rb +35 -67
- data/lib/rom/sql/relation/reading.rb +77 -25
- data/lib/rom/sql/restriction_dsl.rb +24 -0
- data/lib/rom/sql/schema.rb +73 -7
- data/lib/rom/sql/schema/associations_dsl.rb +4 -3
- data/lib/rom/sql/schema/dsl.rb +5 -2
- data/lib/rom/sql/schema/inferrer.rb +21 -11
- data/lib/rom/sql/transaction.rb +19 -0
- data/lib/rom/sql/type.rb +76 -0
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +3 -4
- data/spec/extensions/postgres/inferrer_spec.rb +19 -9
- data/spec/integration/association/many_to_many/custom_fks_spec.rb +73 -0
- data/spec/integration/association/many_to_many/from_view_spec.rb +81 -0
- data/spec/integration/association/many_to_many_spec.rb +2 -2
- data/spec/integration/association/many_to_one/custom_fks_spec.rb +59 -0
- data/spec/integration/association/many_to_one/from_view_spec.rb +74 -0
- data/spec/integration/association/many_to_one/self_ref_spec.rb +51 -0
- data/spec/integration/association/many_to_one_spec.rb +4 -2
- data/spec/integration/association/one_to_many/custom_fks_spec.rb +48 -0
- data/spec/integration/association/one_to_many/from_view_spec.rb +57 -0
- data/spec/integration/association/one_to_many/self_ref_spec.rb +52 -0
- data/spec/integration/association/one_to_many_spec.rb +1 -1
- data/spec/integration/association/one_to_one_spec.rb +1 -1
- data/spec/integration/association/one_to_one_through_spec.rb +2 -2
- data/spec/integration/commands/create_spec.rb +11 -27
- data/spec/integration/commands/update_spec.rb +54 -109
- data/spec/integration/gateway_spec.rb +31 -17
- data/spec/integration/plugins/associates_spec.rb +27 -0
- data/spec/integration/plugins/auto_wrap_spec.rb +8 -8
- data/spec/integration/schema/call_spec.rb +24 -0
- data/spec/integration/schema/prefix_spec.rb +18 -0
- data/spec/integration/schema/qualified_spec.rb +18 -0
- data/spec/integration/schema/rename_spec.rb +23 -0
- data/spec/integration/schema/view_spec.rb +29 -0
- data/spec/integration/schema_inference_spec.rb +31 -14
- data/spec/spec_helper.rb +2 -2
- data/spec/support/helpers.rb +7 -0
- data/spec/unit/gateway_spec.rb +5 -4
- data/spec/unit/projection_dsl_spec.rb +54 -0
- data/spec/unit/relation/dataset_spec.rb +3 -3
- data/spec/unit/relation/distinct_spec.rb +8 -7
- data/spec/unit/relation/exclude_spec.rb +2 -4
- data/spec/unit/relation/having_spec.rb +6 -4
- data/spec/unit/relation/inner_join_spec.rb +47 -2
- data/spec/unit/relation/invert_spec.rb +2 -3
- data/spec/unit/relation/left_join_spec.rb +44 -3
- data/spec/unit/relation/order_spec.rb +40 -0
- data/spec/unit/relation/prefix_spec.rb +2 -0
- data/spec/unit/relation/project_spec.rb +3 -1
- data/spec/unit/relation/qualified_columns_spec.rb +2 -0
- data/spec/unit/relation/rename_spec.rb +2 -0
- data/spec/unit/relation/right_join_spec.rb +59 -0
- data/spec/unit/relation/select_append_spec.rb +21 -0
- data/spec/unit/relation/select_spec.rb +41 -0
- data/spec/unit/relation/where_spec.rb +28 -0
- data/spec/unit/restriction_dsl_spec.rb +34 -0
- metadata +62 -40
- data/lib/rom/plugins/relation/sql/base_view.rb +0 -31
- data/lib/rom/sql/header.rb +0 -61
- data/lib/rom/sql/plugin/assoc_macros.rb +0 -133
- data/lib/rom/sql/plugin/assoc_macros/class_interface.rb +0 -128
- data/spec/integration/read_spec.rb +0 -111
- data/spec/unit/association_errors_spec.rb +0 -19
- data/spec/unit/plugin/assoc_macros/combined_associations_spec.rb +0 -73
- data/spec/unit/plugin/assoc_macros/many_to_many_spec.rb +0 -53
- data/spec/unit/plugin/assoc_macros/many_to_one_spec.rb +0 -61
- data/spec/unit/plugin/assoc_macros/one_to_many_spec.rb +0 -78
- data/spec/unit/plugin/base_view_spec.rb +0 -18
@@ -27,7 +27,7 @@ RSpec.describe ROM::SQL::Association::OneToMany do
|
|
27
27
|
it 'prepares joined relations' do
|
28
28
|
relation = assoc.call(container.relations)
|
29
29
|
|
30
|
-
expect(relation.
|
30
|
+
expect(relation.schema.map(&:name)).to eql(%i[id user_id title])
|
31
31
|
|
32
32
|
expect(relation.order(:tasks__id).to_a).to eql([
|
33
33
|
{ id: 1, user_id: 2, title: "Joe's task" },
|
@@ -28,7 +28,7 @@ RSpec.describe ROM::SQL::Association::OneToOne do
|
|
28
28
|
it 'prepares joined relations' do |example|
|
29
29
|
relation = assoc.call(container.relations)
|
30
30
|
|
31
|
-
expect(relation.
|
31
|
+
expect(relation.schema.map(&:name)).to eql(%i[id user_id number balance])
|
32
32
|
|
33
33
|
# TODO: this if caluse should be removed when (and if) https://github.com/xerial/sqlite-jdbc/issues/112
|
34
34
|
# will be resolved. See https://github.com/rom-rb/rom-sql/issues/49 for details
|
@@ -57,7 +57,7 @@ RSpec.describe ROM::SQL::Association::OneToOneThrough do
|
|
57
57
|
it 'prepares joined relations' do
|
58
58
|
relation = assoc.call(container.relations)
|
59
59
|
|
60
|
-
expect(relation.
|
60
|
+
expect(relation.schema.map(&:name)).to eql(%i[id account_id pan user_id])
|
61
61
|
expect(relation.to_a).to eql([id: 1, account_id: 1, pan: '*6789', user_id: 1])
|
62
62
|
end
|
63
63
|
end
|
@@ -74,7 +74,7 @@ RSpec.describe ROM::SQL::Association::OneToOneThrough do
|
|
74
74
|
it 'prepares joined relations through other association' do
|
75
75
|
relation = assoc.call(container.relations)
|
76
76
|
|
77
|
-
expect(relation.
|
77
|
+
expect(relation.schema.map(&:name)).to eql(%i[id card_id service user_id])
|
78
78
|
expect(relation.to_a).to eql([id: 1, card_id: 1, service: 'aws', user_id: 1])
|
79
79
|
end
|
80
80
|
end
|
@@ -27,10 +27,6 @@ RSpec.describe 'Commands / Create', :postgres do
|
|
27
27
|
define(:create) do
|
28
28
|
input Test::Params
|
29
29
|
|
30
|
-
validator -> tuple {
|
31
|
-
raise ROM::CommandError, 'name cannot be empty' if tuple[:name] == ''
|
32
|
-
}
|
33
|
-
|
34
30
|
result :one
|
35
31
|
end
|
36
32
|
|
@@ -80,18 +76,13 @@ RSpec.describe 'Commands / Create', :postgres do
|
|
80
76
|
|
81
77
|
it 'creates nothing if command error was raised' do
|
82
78
|
expect {
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
}
|
91
|
-
|
92
|
-
expect(result.value).to be(nil)
|
93
|
-
expect(result.error.message).to eql('name cannot be empty')
|
94
|
-
expect(passed).to be(false)
|
79
|
+
begin
|
80
|
+
users.create.transaction {
|
81
|
+
users.create.call(name: 'Jane')
|
82
|
+
users.create.call(name: nil)
|
83
|
+
}
|
84
|
+
rescue ROM::SQL::Error
|
85
|
+
end
|
95
86
|
}.to_not change { container.relations.users.count }
|
96
87
|
end
|
97
88
|
|
@@ -209,7 +200,8 @@ RSpec.describe 'Commands / Create', :postgres do
|
|
209
200
|
}.to raise_error(ROM::SQL::UniqueConstraintError)
|
210
201
|
end
|
211
202
|
|
212
|
-
it 're-raises fk constraint violation error' do
|
203
|
+
it 're-raises fk constraint violation error' do |ex|
|
204
|
+
pending 'Waits for https://github.com/jeremyevans/sequel/pull/1283' if jruby? && sqlite?(ex)
|
213
205
|
expect {
|
214
206
|
tasks.try {
|
215
207
|
tasks.create.call(user_id: 918_273_645)
|
@@ -219,16 +211,8 @@ RSpec.describe 'Commands / Create', :postgres do
|
|
219
211
|
|
220
212
|
it 're-raises database errors' do
|
221
213
|
expect {
|
222
|
-
|
223
|
-
|
224
|
-
}.to raise_error(ROM::SQL::DatabaseError)
|
225
|
-
end
|
226
|
-
|
227
|
-
it 'supports [] syntax instead of call' do
|
228
|
-
expect {
|
229
|
-
Test::Params.attribute :bogus_field, Types::Int
|
230
|
-
users.try { users.create[name: 'some name', bogus_field: 23] }
|
231
|
-
}.to raise_error(ROM::SQL::DatabaseError)
|
214
|
+
users.try { users.create.call(name: nil) }
|
215
|
+
}.to raise_error(ROM::SQL::NotNullConstraintError)
|
232
216
|
end
|
233
217
|
|
234
218
|
describe '#execute' do
|
@@ -12,138 +12,83 @@ RSpec.describe 'Commands / Update' do
|
|
12
12
|
let(:peter) { { name: 'Peter' } }
|
13
13
|
|
14
14
|
with_adapters do
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
before do
|
16
|
+
Test::User = Class.new(Dry::Struct) {
|
17
|
+
attribute :id, Types::Strict::Int
|
18
|
+
attribute :name, Types::Strict::String
|
19
|
+
}
|
20
|
+
|
21
|
+
conf.relation(:users) do
|
22
|
+
def by_id(id)
|
23
|
+
where(id: id).limit(1)
|
22
24
|
end
|
23
|
-
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
define(:update) do
|
28
|
-
result :one
|
29
|
-
end
|
26
|
+
def by_name(name)
|
27
|
+
where(name: name)
|
30
28
|
end
|
31
|
-
|
32
|
-
expect(update.input[foo: 'bar', id: 1, name: 'Jane']).to eql(
|
33
|
-
id: 1, name: 'Jane'
|
34
|
-
)
|
35
29
|
end
|
36
|
-
end
|
37
30
|
|
38
|
-
|
39
|
-
|
40
|
-
Test::User = Class.new(Dry::Struct) {
|
41
|
-
attribute :id, Types::Strict::Int
|
42
|
-
attribute :name, Types::Strict::String
|
43
|
-
}
|
44
|
-
|
45
|
-
conf.relation(:users) do
|
46
|
-
def by_id(id)
|
47
|
-
where(id: id).limit(1)
|
48
|
-
end
|
49
|
-
|
50
|
-
def by_name(name)
|
51
|
-
where(name: name)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
conf.commands(:users) do
|
56
|
-
define(:update)
|
57
|
-
end
|
58
|
-
|
59
|
-
conf.mappers do
|
60
|
-
register :users, entity: -> tuples { tuples.map { |tuple| Test::User.new(tuple) } }
|
61
|
-
end
|
62
|
-
|
63
|
-
relation.insert(name: 'Piotr')
|
64
|
-
relation.insert(name: 'Jane')
|
31
|
+
conf.commands(:users) do
|
32
|
+
define(:update)
|
65
33
|
end
|
66
34
|
|
67
|
-
|
68
|
-
|
69
|
-
foo: 'bar', id: 1, name: 'Jane'
|
70
|
-
)
|
35
|
+
conf.mappers do
|
36
|
+
register :users, entity: -> tuples { tuples.map { |tuple| Test::User.new(tuple) } }
|
71
37
|
end
|
72
38
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
users.update.by_id(piotr[:id]).call(peter)
|
77
|
-
end
|
39
|
+
relation.insert(name: 'Piotr')
|
40
|
+
relation.insert(name: 'Jane')
|
41
|
+
end
|
78
42
|
|
79
|
-
|
43
|
+
context '#transaction' do
|
44
|
+
it 'update record if there was no errors' do
|
45
|
+
result = users.update.transaction do
|
46
|
+
users.update.by_id(piotr[:id]).call(peter)
|
80
47
|
end
|
81
48
|
|
82
|
-
|
83
|
-
users.update.transaction do
|
84
|
-
users.update.by_id(piotr[:id]).call(peter)
|
85
|
-
raise ROM::SQL::Rollback
|
86
|
-
end
|
87
|
-
|
88
|
-
expect(relation.first[:name]).to eql('Piotr')
|
89
|
-
end
|
49
|
+
expect(result.value).to eq([{ id: 1, name: 'Peter' }])
|
90
50
|
end
|
91
51
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
97
|
-
|
98
|
-
expect(result.value.to_a).to match_array([{ id: 1, name: 'Peter' }])
|
52
|
+
it 'updates nothing if error was raised' do
|
53
|
+
users.update.transaction do
|
54
|
+
users.update.by_id(piotr[:id]).call(peter)
|
55
|
+
raise ROM::SQL::Rollback
|
99
56
|
end
|
100
57
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
58
|
+
expect(relation.first[:name]).to eql('Piotr')
|
59
|
+
end
|
60
|
+
end
|
105
61
|
|
106
|
-
|
62
|
+
describe '#call' do
|
63
|
+
it 'updates relation tuples' do
|
64
|
+
result = users.try do
|
65
|
+
users.update.by_id(piotr[:id]).call(peter)
|
107
66
|
end
|
108
67
|
|
109
|
-
|
110
|
-
|
111
|
-
command = users.update.by_id(piotr[:id]).change(piotr)
|
112
|
-
|
113
|
-
expect(command.relation).not_to receive(:update)
|
68
|
+
expect(result.value.to_a).to match_array([{ id: 1, name: 'Peter' }])
|
69
|
+
end
|
114
70
|
|
115
|
-
|
116
|
-
|
71
|
+
it 're-raises database errors' do |example|
|
72
|
+
pending 'why is it failing on travis?' if ENV['TRAVIS'] && mysql?(example) && !jruby?
|
117
73
|
|
118
|
-
|
119
|
-
|
74
|
+
expect {
|
75
|
+
users.update.by_id(piotr[:id]).call(name: nil)
|
76
|
+
}.to raise_error(ROM::SQL::NotNullConstraintError, /name/)
|
77
|
+
end
|
120
78
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
79
|
+
it 'materializes single result' do
|
80
|
+
result = users.update.by_name('Piotr').call(name: 'Pete')
|
81
|
+
expect(result).to eq([
|
82
|
+
{ id: 1, name: 'Pete' }
|
83
|
+
])
|
84
|
+
end
|
126
85
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
])
|
134
|
-
end
|
135
|
-
end
|
136
|
-
|
137
|
-
context 'with multiple records' do
|
138
|
-
it 'materializes the results' do
|
139
|
-
result = users.update.by_name(%w(Piotr Jane)).execute(name: 'Josie')
|
140
|
-
expect(result).to eq([
|
141
|
-
{ id: 1, name: 'Josie' },
|
142
|
-
{ id: 2, name: 'Josie' }
|
143
|
-
])
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
86
|
+
it 'materializes multiple results' do
|
87
|
+
result = users.update.by_name(%w(Piotr Jane)).call(name: 'Josie')
|
88
|
+
expect(result).to eq([
|
89
|
+
{ id: 1, name: 'Josie' },
|
90
|
+
{ id: 2, name: 'Josie' }
|
91
|
+
])
|
147
92
|
end
|
148
93
|
end
|
149
94
|
end
|
@@ -62,28 +62,42 @@ RSpec.describe ROM::SQL::Gateway, :postgres, skip_tables: true do
|
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
end
|
65
|
+
describe 'transactions' do
|
66
|
+
before do
|
67
|
+
conn.drop_table?(:names)
|
68
|
+
|
69
|
+
conn.create_table(:names) do
|
70
|
+
String :name
|
72
71
|
end
|
73
|
-
expect { ROM.container(conf) }.not_to raise_error
|
74
72
|
end
|
75
73
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
74
|
+
let(:gw) { container.gateways[:default] }
|
75
|
+
let(:names) { gw.dataset(:names) }
|
76
|
+
|
77
|
+
it 'can run the code inside a transaction' do
|
78
|
+
names.insert name: 'Jade'
|
79
|
+
|
80
|
+
gw.transaction do |t|
|
81
|
+
names.insert name: 'John'
|
82
|
+
|
83
|
+
t.rollback!
|
84
|
+
names.insert name: 'Jack'
|
83
85
|
end
|
84
86
|
|
85
|
-
expect
|
86
|
-
|
87
|
+
expect(names.to_a).to eql([name: 'Jade'])
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'sets isolation level to read commited' do
|
91
|
+
gw = container.gateways[:default]
|
92
|
+
names = gw.dataset(:names)
|
93
|
+
|
94
|
+
gw.transaction do |t|
|
95
|
+
names.insert name: 'John'
|
96
|
+
concurrent_names = nil
|
97
|
+
Thread.new { concurrent_names = names.to_a }.join
|
98
|
+
|
99
|
+
expect(concurrent_names).to eql([])
|
100
|
+
end
|
87
101
|
end
|
88
102
|
end
|
89
103
|
end
|
@@ -13,6 +13,33 @@ RSpec.describe 'Plugins / :associates' do
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
describe '#with_association' do
|
17
|
+
let(:user) do
|
18
|
+
users[:create].call(name: 'Jane')
|
19
|
+
end
|
20
|
+
|
21
|
+
let(:task) do
|
22
|
+
{ title: 'Task one' }
|
23
|
+
end
|
24
|
+
|
25
|
+
before do
|
26
|
+
conf.commands(:users) do
|
27
|
+
define(:create) { result :one }
|
28
|
+
end
|
29
|
+
|
30
|
+
conf.commands(:tasks) do
|
31
|
+
define(:create) { result :one }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns a command prepared for the given association' do
|
36
|
+
command = tasks[:create].with_association(:user, key: %i[user_id id])
|
37
|
+
|
38
|
+
expect(command.call(task, user)).
|
39
|
+
to eql(id: 1, title: 'Task one', user_id: user[:id])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
16
43
|
shared_context 'automatic FK setting' do
|
17
44
|
it 'sets foreign key prior execution for many tuples' do
|
18
45
|
create_user = users[:create].with(name: 'Jade')
|
@@ -17,26 +17,26 @@ RSpec.describe 'Plugins / :auto_wrap' do
|
|
17
17
|
end
|
18
18
|
|
19
19
|
context 'when parent relation is registered under dataset name' do
|
20
|
-
subject(:tasks) {
|
20
|
+
subject(:tasks) { relations[:tasks] }
|
21
21
|
|
22
|
-
let(:users) {
|
22
|
+
let(:users) { relations[:users] }
|
23
23
|
|
24
24
|
before do
|
25
|
-
conf.relation(:tasks)
|
26
|
-
conf.relation(:users)
|
25
|
+
conf.relation(:tasks) { schema(infer: true) }
|
26
|
+
conf.relation(:users) { schema(infer: true) }
|
27
27
|
end
|
28
28
|
|
29
29
|
include_context 'joined tuple'
|
30
30
|
end
|
31
31
|
|
32
32
|
context 'when parent relation is registered under a custom name' do
|
33
|
-
subject(:tasks) {
|
33
|
+
subject(:tasks) { relations[:tasks] }
|
34
34
|
|
35
|
-
let(:users) {
|
35
|
+
let(:users) { relations[:authors] }
|
36
36
|
|
37
37
|
before do
|
38
|
-
conf.relation(:tasks)
|
39
|
-
conf.relation(:authors) {
|
38
|
+
conf.relation(:tasks) { schema(infer: true) }
|
39
|
+
conf.relation(:authors) { schema(:users, infer: true) }
|
40
40
|
end
|
41
41
|
|
42
42
|
include_context 'joined tuple'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe ROM::SQL::Schema, '#call' do
|
4
|
+
include_context 'database setup'
|
5
|
+
|
6
|
+
with_adapters :postgres do
|
7
|
+
before do
|
8
|
+
conf.relation(:users) do
|
9
|
+
schema(infer: true)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:schema) { relations[:users].schema }
|
14
|
+
|
15
|
+
it 'auto-projects a relation' do
|
16
|
+
expect(schema.(relations[:users]).dataset.sql).to eql('SELECT "id", "name" FROM "users" ORDER BY "users"."id"')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'maintains schema' do
|
20
|
+
projected = relations[:users].schema.project(:name)
|
21
|
+
expect(projected.(relations[:users]).schema).to be(projected)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|