rom-sql 1.3.1 → 1.3.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.
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