rom-sql 1.3.1 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/lib/rom/sql/association.rb +1 -1
  4. data/lib/rom/sql/association/many_to_one.rb +1 -1
  5. data/lib/rom/sql/association/name.rb +1 -1
  6. data/lib/rom/sql/association/one_to_many.rb +1 -1
  7. data/lib/rom/sql/attribute.rb +23 -9
  8. data/lib/rom/sql/dsl.rb +13 -0
  9. data/lib/rom/sql/extensions/postgres/commands.rb +4 -4
  10. data/lib/rom/sql/extensions/postgres/types.rb +262 -110
  11. data/lib/rom/sql/function.rb +2 -2
  12. data/lib/rom/sql/projection_dsl.rb +1 -12
  13. data/lib/rom/sql/relation.rb +1 -1
  14. data/lib/rom/sql/relation/reading.rb +8 -4
  15. data/lib/rom/sql/restriction_dsl.rb +7 -1
  16. data/lib/rom/sql/types.rb +2 -0
  17. data/lib/rom/sql/version.rb +1 -1
  18. data/spec/extensions/postgres/attribute_spec.rb +78 -0
  19. data/spec/integration/association/many_to_many/custom_fks_spec.rb +8 -3
  20. data/spec/integration/association/many_to_many/from_view_spec.rb +9 -3
  21. data/spec/integration/association/many_to_many_spec.rb +8 -2
  22. data/spec/integration/association/many_to_one/custom_fks_spec.rb +8 -4
  23. data/spec/integration/association/many_to_one/from_view_spec.rb +10 -4
  24. data/spec/integration/association/many_to_one/self_ref_spec.rb +4 -2
  25. data/spec/integration/association/many_to_one_spec.rb +8 -4
  26. data/spec/integration/association/one_to_many/custom_fks_spec.rb +5 -2
  27. data/spec/integration/association/one_to_many/from_view_spec.rb +5 -2
  28. data/spec/integration/association/one_to_many/self_ref_spec.rb +4 -2
  29. data/spec/integration/association/one_to_many_spec.rb +1 -1
  30. data/spec/integration/commands/upsert_spec.rb +2 -2
  31. data/spec/integration/plugins/auto_wrap_spec.rb +1 -1
  32. data/spec/integration/sequel_api_spec.rb +3 -2
  33. data/spec/unit/function_spec.rb +1 -1
  34. data/spec/unit/order_dsl_spec.rb +4 -4
  35. data/spec/unit/projection_dsl_spec.rb +8 -0
  36. data/spec/unit/relation/dataset_spec.rb +3 -3
  37. data/spec/unit/relation/project_spec.rb +1 -1
  38. data/spec/unit/relation/qualified_columns_spec.rb +3 -2
  39. data/spec/unit/relation/where_spec.rb +20 -0
  40. data/spec/unit/restriction_dsl_spec.rb +2 -2
  41. metadata +2 -2
@@ -6,9 +6,9 @@ module ROM
6
6
  class Function < ROM::Schema::Attribute
7
7
  def sql_literal(ds)
8
8
  if name
9
- func.as(name).sql_literal(ds)
9
+ ds.literal(func.as(name))
10
10
  else
11
- func.sql_literal(ds)
11
+ ds.literal(func)
12
12
  end
13
13
  end
14
14
 
@@ -7,7 +7,7 @@ module ROM
7
7
  class ProjectionDSL < DSL
8
8
  # @api public
9
9
  def `(value)
10
- expr = ::Sequel::VIRTUAL_ROW.instance_eval { `#{value}` }
10
+ expr = ::Sequel.lit(value)
11
11
  ::ROM::SQL::Attribute.new(type(:string)).meta(sql_expr: expr)
12
12
  end
13
13
 
@@ -18,17 +18,6 @@ module ROM
18
18
 
19
19
  private
20
20
 
21
- # @api private
22
- def type(identifier)
23
- type_name = ::Dry::Core::Inflector.classify(identifier)
24
- types.const_get(type_name) if types.const_defined?(type_name)
25
- end
26
-
27
- # @api private
28
- def types
29
- ::ROM::SQL::Types
30
- end
31
-
32
21
  # @api private
33
22
  def method_missing(meth, *args, &block)
34
23
  if schema.key?(meth)
@@ -54,7 +54,7 @@ module ROM
54
54
 
55
55
  if db.table_exists?(table)
56
56
  if schema
57
- select(*schema.map(&:to_sym)).order(*schema.project(*schema.primary_key_names).qualified.map(&:to_sym))
57
+ select(*schema).order(*schema.project(*schema.primary_key_names).qualified)
58
58
  else
59
59
  select(*columns).order(*klass.primary_key_columns(db, table))
60
60
  end
@@ -105,7 +105,7 @@ module ROM
105
105
  #
106
106
  # @api public
107
107
  def qualified_columns
108
- schema.qualified.map(&:to_sym)
108
+ schema.qualified.map(&:to_sql_name)
109
109
  end
110
110
 
111
111
  # Map tuples from the relation
@@ -341,8 +341,10 @@ module ROM
341
341
  where(*args).where(self.class.schema.restriction(&block))
342
342
  elsif args.size == 1 && args[0].is_a?(Hash)
343
343
  new(dataset.where(coerce_conditions(args[0])))
344
- else
344
+ elsif !args.empty?
345
345
  new(dataset.where(*args))
346
+ else
347
+ self
346
348
  end
347
349
  end
348
350
 
@@ -395,9 +397,9 @@ module ROM
395
397
  # @api public
396
398
  def having(*args, &block)
397
399
  if block
398
- new(dataset.having(*args).having(self.class.schema.restriction(&block)))
400
+ new(dataset.having(*args, *self.class.schema.restriction(&block)))
399
401
  else
400
- new(dataset.__send__(__method__, *args, &block))
402
+ new(dataset.__send__(__method__, *args))
401
403
  end
402
404
  end
403
405
 
@@ -835,6 +837,8 @@ module ROM
835
837
  else
836
838
  new(dataset.__send__(type, other.to_sym, join_cond, opts, &block))
837
839
  end
840
+ elsif other.is_a?(Sequel::SQL::AliasedExpression)
841
+ new(dataset.__send__(type, other, join_cond, opts, &block))
838
842
  elsif other.respond_to?(:name) && other.name.is_a?(Relation::Name)
839
843
  associations[other.name.dataset].join(__registry__, type, self, other)
840
844
  else
@@ -16,7 +16,13 @@ module ROM
16
16
  if schema.key?(meth)
17
17
  schema[meth]
18
18
  else
19
- ::Sequel::VIRTUAL_ROW.__send__(meth, *args, &block)
19
+ type = type(meth)
20
+
21
+ if type
22
+ ::ROM::SQL::Function.new(type)
23
+ else
24
+ ::Sequel::VIRTUAL_ROW.__send__(meth, *args, &block)
25
+ end
20
26
  end
21
27
  end
22
28
  end
data/lib/rom/sql/types.rb CHANGED
@@ -16,6 +16,8 @@ module ROM
16
16
  Serial = Int.meta(primary_key: true)
17
17
 
18
18
  Blob = Constructor(Sequel::SQL::Blob, &Sequel::SQL::Blob.method(:new))
19
+
20
+ Void = Nil
19
21
  end
20
22
  end
21
23
  end
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module SQL
3
- VERSION = '1.3.1'.freeze
3
+ VERSION = '1.3.2'.freeze
4
4
  end
5
5
  end
@@ -125,4 +125,82 @@ RSpec.describe 'ROM::SQL::Attribute', :postgres do
125
125
  { result: jsonb_hash['age' => 25] }])
126
126
  end
127
127
  end
128
+
129
+ describe 'using array types' do
130
+ before do
131
+ conn.create_table :pg_people do
132
+ primary_key :id
133
+ String :name
134
+ column :emails, 'text[]'
135
+ column :bigids, 'bigint[]'
136
+ end
137
+
138
+ conf.commands(:people) do
139
+ define(:create)
140
+ define(:update)
141
+ end
142
+
143
+ create_person.(name: 'John Doe', emails: %w(john@doe.com john@example.com), bigids: [84])
144
+ create_person.(name: 'Jade Doe', emails: %w(jade@hotmail.com), bigids: [42])
145
+ end
146
+
147
+ it 'filters by email inclusion' do
148
+ expect(people.select(:name).where { emails.contain(['john@doe.com']) }.one).
149
+ to eql(name: 'John Doe')
150
+ end
151
+
152
+ it 'coerces values so that PG does not complain' do
153
+ expect(people.select(:name).where { bigids.contain([84]) }.one).
154
+ to eql(name: 'John Doe')
155
+ end
156
+
157
+ it 'fetches element by index' do
158
+ expect(people.select { [name, emails.get(2).as(:second_email)] }.to_a).
159
+ to eql([{ name: 'John Doe', second_email: 'john@example.com' },
160
+ { name: 'Jade Doe', second_email: nil }])
161
+ end
162
+
163
+ it 'restricts with ANY' do
164
+ expect(people.select(:name).where { bigids.any(84)}.one).
165
+ to eql(name: 'John Doe')
166
+ end
167
+
168
+ it 'restricts by <@' do
169
+ expect(people.select(:name).where { bigids.contained_by((30..50).to_a) }.one).
170
+ to eql(name: 'Jade Doe')
171
+ end
172
+
173
+ it 'returns array length' do
174
+ expect(people.select { [name, emails.length.as(:size)] }.to_a).
175
+ to eql([{ name: 'John Doe', size: 2 }, { name: 'Jade Doe', size: 1 }])
176
+ end
177
+
178
+ it 'restrict by overlapping with other array' do
179
+ expect(people.select(:name).where { emails.overlaps(%w(jade@hotmail.com)) }.one).
180
+ to eql(name: 'Jade Doe')
181
+
182
+ expect(people.select(:name).where { bigids.overlaps([42]) }.one).
183
+ to eql(name: 'Jade Doe')
184
+ end
185
+
186
+ it 'removes element by value' do
187
+ expect(people.select { emails.remove_value('john@example.com').as(:emails) }.to_a).
188
+ to eq([{ emails: %w(john@doe.com) }, { emails: %w(jade@hotmail.com) }])
189
+
190
+ pending "doesn't have auto-casting yet"
191
+ expect(people.select(:name).where { bigids.remove_value(100).contains([42]) }.one).
192
+ to eql(name: 'Jade Doe')
193
+ end
194
+
195
+ it 'joins values' do
196
+ expect(people.select { emails.join(',').as(:emails) }.to_a).
197
+ to eql([{ emails: 'john@doe.com,john@example.com' },
198
+ { emails: 'jade@hotmail.com' }])
199
+ end
200
+
201
+ it 'concatenates arrays' do
202
+ expect(people.select { (emails + %w(foo@bar.com)).as(:emails) }.where { name.is('Jade Doe') }.one).
203
+ to eq(emails: %w(jade@hotmail.com foo@bar.com))
204
+ end
205
+ end
128
206
  end
@@ -9,6 +9,9 @@ RSpec.describe ROM::SQL::Association::ManyToMany, '#call' do
9
9
  relations[:users].associations[:puzzles]
10
10
  end
11
11
 
12
+ let(:puzzles) { relations[:puzzles] }
13
+ let(:puzzle_solvers) { relations[:puzzle_solvers] }
14
+
12
15
  with_adapters do
13
16
  before do
14
17
  conn.create_table(:puzzles) do
@@ -52,10 +55,12 @@ RSpec.describe ROM::SQL::Association::ManyToMany, '#call' do
52
55
  end
53
56
 
54
57
  it 'prepares joined relations using custom FK' do
55
- relation = assoc.call(relations).order(:puzzles__text, :puzzle_solvers__solver_id)
58
+ relation = assoc.call(relations).order(puzzles[:text].qualified, puzzle_solvers[:solver_id].qualified)
56
59
 
57
- expect(relation.schema.map(&:to_sym)).
58
- to eql(%i[puzzles__id puzzles__text puzzle_solvers__solver_id])
60
+ expect(relation.schema.map(&:to_sql_name)).
61
+ to eql([Sequel.qualify(:puzzles, :id),
62
+ Sequel.qualify(:puzzles, :text),
63
+ Sequel.qualify(:puzzle_solvers, :solver_id)])
59
64
 
60
65
  expect(relation.to_a).
61
66
  to eql([
@@ -9,6 +9,9 @@ RSpec.describe ROM::SQL::Association::ManyToMany, '#call' do
9
9
  relations[:users].associations[:solved_puzzles]
10
10
  end
11
11
 
12
+ let(:puzzles) { relations[:puzzles] }
13
+ let(:puzzle_solvers) { relations[:puzzle_solvers] }
14
+
12
15
  with_adapters do
13
16
  before do
14
17
  conn.create_table(:puzzles) do
@@ -67,10 +70,13 @@ RSpec.describe ROM::SQL::Association::ManyToMany, '#call' do
67
70
  end
68
71
 
69
72
  it 'prepares joined relations using custom FK' do
70
- relation = assoc.call(relations).order(:puzzles__text, :puzzle_solvers__user_id)
73
+ relation = assoc.call(relations).order(puzzles[:text].qualified, puzzle_solvers[:user_id].qualified)
71
74
 
72
- expect(relation.schema.map(&:to_sym)).
73
- to eql(%i[puzzles__id puzzles__text puzzles__solved puzzle_solvers__user_id])
75
+ expect(relation.schema.map(&:to_sql_name)).
76
+ to eql([Sequel.qualify(:puzzles, :id),
77
+ Sequel.qualify(:puzzles, :text),
78
+ Sequel.qualify(:puzzles, :solved),
79
+ Sequel.qualify(:puzzle_solvers, :user_id)])
74
80
 
75
81
  expect(relation.to_a).
76
82
  to eql([
@@ -46,7 +46,10 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
46
46
  it 'prepares joined relations' do
47
47
  relation = assoc.call(container.relations)
48
48
 
49
- expect(relation.schema.map(&:to_sym)).to eql(%i[tags__id tags__name task_tags__task_id])
49
+ expect(relation.schema.map(&:to_sql_name)).
50
+ to eql([Sequel.qualify(:tags, :id),
51
+ Sequel.qualify(:tags, :name),
52
+ Sequel.qualify(:task_tags, :task_id)])
50
53
  expect(relation.to_a).to eql([id: 1, name: 'important', task_id: 1])
51
54
  end
52
55
  end
@@ -59,7 +62,10 @@ RSpec.describe ROM::SQL::Association::ManyToMany do
59
62
  it 'prepares joined relations through other association' do
60
63
  relation = assoc.call(container.relations)
61
64
 
62
- expect(relation.schema.map(&:to_sym)).to eql(%i[tags__id tags__name tasks__user_id])
65
+ expect(relation.schema.map(&:to_sql_name)).
66
+ to eql([Sequel.qualify(:tags, :id),
67
+ Sequel.qualify(:tags, :name),
68
+ Sequel.qualify(:tasks, :user_id)])
63
69
  expect(relation.to_a).to eql([id: 1, name: 'important', user_id: 2])
64
70
  end
65
71
  end
@@ -42,15 +42,19 @@ RSpec.describe ROM::SQL::Association::ManyToOne, '#call' do
42
42
  it 'prepares joined relations using correct FKs based on association aliases' do
43
43
  relation = assoc_from.call(relations)
44
44
 
45
- expect(relation.schema.map(&:to_sym)).
46
- to eql(%i(destinations__id destinations__name flights__id___flight_id))
45
+ expect(relation.schema.map(&:to_sql_name)).
46
+ to eql([Sequel.qualify(:destinations, :id),
47
+ Sequel.qualify(:destinations, :name),
48
+ Sequel.qualify(:flights, :id).as(:flight_id)])
47
49
 
48
50
  expect(relation.first).to eql(id: 1, name: 'FROM', flight_id: 1)
49
51
 
50
52
  relation = assoc_to.call(relations)
51
53
 
52
- expect(relation.schema.map(&:to_sym)).
53
- to eql(%i(destinations__id destinations__name flights__id___flight_id))
54
+ expect(relation.schema.map(&:to_sql_name)).
55
+ to eql([Sequel.qualify(:destinations, :id),
56
+ Sequel.qualify(:destinations, :name),
57
+ Sequel.qualify(:flights, :id).as(:flight_id)])
54
58
 
55
59
  expect(relation.first).to eql(id: 2, name: 'TO', flight_id: 1)
56
60
  end
@@ -60,16 +60,22 @@ RSpec.describe ROM::SQL::Association::ManyToOne, '#call' do
60
60
  it 'prepares joined relations using custom view in target relation' do
61
61
  relation = assoc_inter.call(relations)
62
62
 
63
- expect(relation.schema.map(&:to_sym)).
64
- to eql(%i(destinations__id destinations__name destinations__intermediate flights__id___flight_id))
63
+ expect(relation.schema.map(&:to_sql_name)).
64
+ to eql([Sequel.qualify(:destinations, :id),
65
+ Sequel.qualify(:destinations, :name),
66
+ Sequel.qualify(:destinations, :intermediate),
67
+ Sequel.qualify(:flights, :id).as(:flight_id)])
65
68
 
66
69
  expect(relation.first).to eql(id: 2, intermediate: db_true, name: 'Intermediate', flight_id: 1)
67
70
  expect(relation.count).to be(1)
68
71
 
69
72
  relation = assoc_final.call(relations)
70
73
 
71
- expect(relation.schema.map(&:to_sym)).
72
- to eql(%i(destinations__id destinations__name destinations__intermediate flights__id___flight_id))
74
+ expect(relation.schema.map(&:to_sql_name)).
75
+ to eql([Sequel.qualify(:destinations, :id),
76
+ Sequel.qualify(:destinations, :name),
77
+ Sequel.qualify(:destinations, :intermediate),
78
+ Sequel.qualify(:flights, :id).as(:flight_id)])
73
79
 
74
80
  expect(relation.first).to eql(id: 1, intermediate: db_false, name: 'Final', flight_id: 2)
75
81
  expect(relation.count).to be(1)
@@ -37,8 +37,10 @@ RSpec.describe ROM::SQL::Association::OneToMany, '#call' do
37
37
  it 'prepares joined relations using custom FK for a self-ref association' do
38
38
  relation = assoc.call(relations)
39
39
 
40
- expect(relation.schema.map(&:to_sym)).
41
- to eql(%i[categories__id categories__parent_id categories__name])
40
+ expect(relation.schema.map(&:to_sql_name)).
41
+ to eql([Sequel.qualify(:categories, :id),
42
+ Sequel.qualify(:categories, :parent_id),
43
+ Sequel.qualify(:categories, :name)])
42
44
 
43
45
  expect(relation.to_a).
44
46
  to eql([
@@ -41,8 +41,10 @@ RSpec.describe ROM::SQL::Association::ManyToOne, helpers: true do
41
41
  it 'prepares joined relations' do
42
42
  relation = assoc.call(container.relations)
43
43
 
44
- expect(relation.schema.map(&:to_sym))
45
- .to eql(%i[users__id users__name tasks__id___task_id])
44
+ expect(relation.schema.map(&:to_sql_name)).
45
+ to eql([Sequel.qualify(:users, :id),
46
+ Sequel.qualify(:users, :name),
47
+ Sequel.qualify(:tasks, :id).as(:task_id)])
46
48
 
47
49
  expect(relation.where(user_id: 1).one).to eql(id: 1, task_id: 2, name: 'Jane')
48
50
 
@@ -109,8 +111,10 @@ RSpec.describe ROM::SQL::Association::ManyToOne, helpers: true do
109
111
  it 'prepares joined relations' do
110
112
  relation = assoc.call(container.relations)
111
113
 
112
- expect(relation.schema.map(&:to_sym))
113
- .to eql(%i[users__id users__name posts__post_id])
114
+ expect(relation.schema.map(&:to_sql_name)).
115
+ to eql([Sequel.qualify(:users, :id),
116
+ Sequel.qualify(:users, :name),
117
+ Sequel.qualify(:posts, :post_id)])
114
118
 
115
119
  expect(relation.order(:id).to_a).to eql([
116
120
  { id: 1, name: 'Jane', post_id: 2 },
@@ -40,8 +40,11 @@ RSpec.describe ROM::SQL::Association::OneToMany, '#call' do
40
40
  it 'prepares joined relations using custom FK' do
41
41
  relation = assoc.call(relations)
42
42
 
43
- expect(relation.schema.map(&:to_sym)).
44
- to eql(%i[puzzles__id puzzles__author_id puzzles__solver_id puzzles__text])
43
+ expect(relation.schema.map(&:to_sql_name)).
44
+ to eql([Sequel.qualify(:puzzles, :id),
45
+ Sequel.qualify(:puzzles, :author_id),
46
+ Sequel.qualify(:puzzles, :solver_id),
47
+ Sequel.qualify(:puzzles, :text)])
45
48
 
46
49
  expect(relation.first).to eql(id: 2, author_id: 2, solver_id: 1, text: 'P2')
47
50
  end
@@ -44,8 +44,11 @@ RSpec.describe ROM::SQL::Association::OneToMany, '#call' do
44
44
  it 'prepares joined relations using custom view' do
45
45
  relation = assoc.call(relations)
46
46
 
47
- expect(relation.schema.map(&:to_sym)).
48
- to eql(%i[puzzles__id puzzles__user_id puzzles__text puzzles__solved])
47
+ expect(relation.schema.map(&:to_sql_name)).
48
+ to eql([Sequel.qualify(:puzzles, :id),
49
+ Sequel.qualify(:puzzles, :user_id),
50
+ Sequel.qualify(:puzzles, :text),
51
+ Sequel.qualify(:puzzles, :solved)])
49
52
 
50
53
  expect(relation.count).to be(1)
51
54
  expect(relation.first).to eql(id: 2, user_id: 2, solved: db_true, text: 'P2')
@@ -38,8 +38,10 @@ RSpec.describe ROM::SQL::Association::OneToMany, '#call' do
38
38
  it 'prepares joined relations using custom FK for a self-ref association' do
39
39
  relation = assoc.call(relations)
40
40
 
41
- expect(relation.schema.map(&:to_sym)).
42
- to eql(%i[categories__id categories__parent_id categories__name])
41
+ expect(relation.schema.map(&:to_sql_name)).
42
+ to eql([Sequel.qualify(:categories, :id),
43
+ Sequel.qualify(:categories, :parent_id),
44
+ Sequel.qualify(:categories, :name)])
43
45
 
44
46
  expect(relation.to_a).
45
47
  to eql([
@@ -26,7 +26,7 @@ RSpec.describe ROM::SQL::Association::OneToMany do
26
26
 
27
27
  expect(relation.schema.map(&:name)).to eql(%i[id user_id title])
28
28
 
29
- expect(relation.order(:tasks__id).to_a).to eql([
29
+ expect(relation.order(tasks[:id].qualified).to_a).to eql([
30
30
  { id: 1, user_id: 2, title: "Joe's task" },
31
31
  { id: 2, user_id: 1, title: "Jane's task" }
32
32
  ])
@@ -56,7 +56,7 @@ RSpec.describe 'Commands / Postgres / Upsert', :postgres, seeds: false do
56
56
  let(:command_config) do
57
57
  -> do
58
58
  constraint :tasks_title_key
59
- update_statement user_id: :excluded__user_id
59
+ update_statement user_id: Sequel.qualify(:excluded, :user_id)
60
60
  end
61
61
  end
62
62
 
@@ -70,7 +70,7 @@ RSpec.describe 'Commands / Postgres / Upsert', :postgres, seeds: false do
70
70
  -> do
71
71
  conflict_target :title
72
72
  update_statement user_id: nil
73
- update_where tasks__id: 2
73
+ update_where Sequel.qualify(:tasks, :id) => 2
74
74
  end
75
75
  end
76
76