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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +32 -0
  4. data/Gemfile +4 -1
  5. data/lib/rom/plugins/relation/sql/auto_wrap.rb +1 -3
  6. data/lib/rom/sql/association.rb +33 -14
  7. data/lib/rom/sql/association/many_to_many.rb +17 -10
  8. data/lib/rom/sql/association/many_to_one.rb +29 -13
  9. data/lib/rom/sql/association/name.rb +12 -4
  10. data/lib/rom/sql/association/one_to_many.rb +21 -10
  11. data/lib/rom/sql/commands/create.rb +0 -1
  12. data/lib/rom/sql/commands/update.rb +1 -49
  13. data/lib/rom/sql/dsl.rb +29 -0
  14. data/lib/rom/sql/expression.rb +26 -0
  15. data/lib/rom/sql/function.rb +23 -0
  16. data/lib/rom/sql/gateway.rb +24 -9
  17. data/lib/rom/sql/migration.rb +6 -7
  18. data/lib/rom/sql/migration/migrator.rb +7 -8
  19. data/lib/rom/sql/order_dsl.rb +20 -0
  20. data/lib/rom/sql/plugin/associates.rb +58 -45
  21. data/lib/rom/sql/plugin/pagination.rb +8 -11
  22. data/lib/rom/sql/plugins.rb +0 -2
  23. data/lib/rom/sql/projection_dsl.rb +41 -0
  24. data/lib/rom/sql/qualified_attribute.rb +2 -2
  25. data/lib/rom/sql/relation.rb +35 -67
  26. data/lib/rom/sql/relation/reading.rb +77 -25
  27. data/lib/rom/sql/restriction_dsl.rb +24 -0
  28. data/lib/rom/sql/schema.rb +73 -7
  29. data/lib/rom/sql/schema/associations_dsl.rb +4 -3
  30. data/lib/rom/sql/schema/dsl.rb +5 -2
  31. data/lib/rom/sql/schema/inferrer.rb +21 -11
  32. data/lib/rom/sql/transaction.rb +19 -0
  33. data/lib/rom/sql/type.rb +76 -0
  34. data/lib/rom/sql/version.rb +1 -1
  35. data/rom-sql.gemspec +3 -4
  36. data/spec/extensions/postgres/inferrer_spec.rb +19 -9
  37. data/spec/integration/association/many_to_many/custom_fks_spec.rb +73 -0
  38. data/spec/integration/association/many_to_many/from_view_spec.rb +81 -0
  39. data/spec/integration/association/many_to_many_spec.rb +2 -2
  40. data/spec/integration/association/many_to_one/custom_fks_spec.rb +59 -0
  41. data/spec/integration/association/many_to_one/from_view_spec.rb +74 -0
  42. data/spec/integration/association/many_to_one/self_ref_spec.rb +51 -0
  43. data/spec/integration/association/many_to_one_spec.rb +4 -2
  44. data/spec/integration/association/one_to_many/custom_fks_spec.rb +48 -0
  45. data/spec/integration/association/one_to_many/from_view_spec.rb +57 -0
  46. data/spec/integration/association/one_to_many/self_ref_spec.rb +52 -0
  47. data/spec/integration/association/one_to_many_spec.rb +1 -1
  48. data/spec/integration/association/one_to_one_spec.rb +1 -1
  49. data/spec/integration/association/one_to_one_through_spec.rb +2 -2
  50. data/spec/integration/commands/create_spec.rb +11 -27
  51. data/spec/integration/commands/update_spec.rb +54 -109
  52. data/spec/integration/gateway_spec.rb +31 -17
  53. data/spec/integration/plugins/associates_spec.rb +27 -0
  54. data/spec/integration/plugins/auto_wrap_spec.rb +8 -8
  55. data/spec/integration/schema/call_spec.rb +24 -0
  56. data/spec/integration/schema/prefix_spec.rb +18 -0
  57. data/spec/integration/schema/qualified_spec.rb +18 -0
  58. data/spec/integration/schema/rename_spec.rb +23 -0
  59. data/spec/integration/schema/view_spec.rb +29 -0
  60. data/spec/integration/schema_inference_spec.rb +31 -14
  61. data/spec/spec_helper.rb +2 -2
  62. data/spec/support/helpers.rb +7 -0
  63. data/spec/unit/gateway_spec.rb +5 -4
  64. data/spec/unit/projection_dsl_spec.rb +54 -0
  65. data/spec/unit/relation/dataset_spec.rb +3 -3
  66. data/spec/unit/relation/distinct_spec.rb +8 -7
  67. data/spec/unit/relation/exclude_spec.rb +2 -4
  68. data/spec/unit/relation/having_spec.rb +6 -4
  69. data/spec/unit/relation/inner_join_spec.rb +47 -2
  70. data/spec/unit/relation/invert_spec.rb +2 -3
  71. data/spec/unit/relation/left_join_spec.rb +44 -3
  72. data/spec/unit/relation/order_spec.rb +40 -0
  73. data/spec/unit/relation/prefix_spec.rb +2 -0
  74. data/spec/unit/relation/project_spec.rb +3 -1
  75. data/spec/unit/relation/qualified_columns_spec.rb +2 -0
  76. data/spec/unit/relation/rename_spec.rb +2 -0
  77. data/spec/unit/relation/right_join_spec.rb +59 -0
  78. data/spec/unit/relation/select_append_spec.rb +21 -0
  79. data/spec/unit/relation/select_spec.rb +41 -0
  80. data/spec/unit/relation/where_spec.rb +28 -0
  81. data/spec/unit/restriction_dsl_spec.rb +34 -0
  82. metadata +62 -40
  83. data/lib/rom/plugins/relation/sql/base_view.rb +0 -31
  84. data/lib/rom/sql/header.rb +0 -61
  85. data/lib/rom/sql/plugin/assoc_macros.rb +0 -133
  86. data/lib/rom/sql/plugin/assoc_macros/class_interface.rb +0 -128
  87. data/spec/integration/read_spec.rb +0 -111
  88. data/spec/unit/association_errors_spec.rb +0 -19
  89. data/spec/unit/plugin/assoc_macros/combined_associations_spec.rb +0 -73
  90. data/spec/unit/plugin/assoc_macros/many_to_many_spec.rb +0 -53
  91. data/spec/unit/plugin/assoc_macros/many_to_one_spec.rb +0 -61
  92. data/spec/unit/plugin/assoc_macros/one_to_many_spec.rb +0 -78
  93. data/spec/unit/plugin/base_view_spec.rb +0 -18
@@ -1,4 +1,4 @@
1
- require 'rom/support/cache'
1
+ require 'dry/core/cache'
2
2
 
3
3
  module ROM
4
4
  module SQL
@@ -10,7 +10,7 @@ module ROM
10
10
  class QualifiedAttribute
11
11
  include Dry::Equalizer(:dataset, :attribute)
12
12
 
13
- extend Cache
13
+ extend Dry::Core::Cache
14
14
 
15
15
  # Dataset (table) name
16
16
  #
@@ -1,19 +1,14 @@
1
- require 'rom/sql/header'
2
1
  require 'rom/sql/types'
3
-
4
2
  require 'rom/sql/schema'
5
3
 
6
4
  require 'rom/sql/relation/reading'
7
5
  require 'rom/sql/relation/writing'
8
6
 
9
- require 'rom/plugins/relation/view'
10
7
  require 'rom/plugins/relation/key_inference'
11
- require 'rom/plugins/relation/sql/base_view'
12
8
  require 'rom/plugins/relation/sql/auto_combine'
13
9
  require 'rom/plugins/relation/sql/auto_wrap'
14
10
 
15
- require 'rom/support/deprecations'
16
- require 'rom/support/constants'
11
+ require 'dry/core/deprecations'
17
12
 
18
13
  module ROM
19
14
  module SQL
@@ -26,8 +21,6 @@ module ROM
26
21
  adapter :sql
27
22
 
28
23
  use :key_inference
29
- use :view
30
- use :base_view
31
24
  use :auto_combine
32
25
  use :auto_wrap
33
26
 
@@ -42,31 +35,45 @@ module ROM
42
35
 
43
36
  klass.class_eval do
44
37
  schema_dsl SQL::Schema::DSL
45
- schema_inferrer -> (dataset, gateway) do
38
+
39
+ schema_inferrer -> (name, gateway) do
46
40
  inferrer_for_db = ROM::SQL::Schema::Inferrer.get(gateway.connection.database_type.to_sym)
47
- inferrer_for_db.new.call(dataset, gateway)
41
+ begin
42
+ inferrer_for_db.new.call(name, gateway)
43
+ rescue Sequel::Error => e
44
+ ROM::Schema::DEFAULT_INFERRER.()
45
+ end
48
46
  end
49
47
 
50
48
  dataset do
49
+ # TODO: feels strange to do it here - we need a new hook for this during finalization
50
+ klass.define_default_views!
51
+ schema = klass.schema
52
+
51
53
  table = opts[:from].first
52
54
 
53
55
  if db.table_exists?(table)
54
- pk_header = klass.primary_key_header(db, table)
55
- col_names = klass.schema ? klass.schema.attributes.keys : columns
56
- select(*col_names).order(*pk_header.qualified)
56
+ if schema
57
+ select(*schema.map(&:to_sym)).order(*schema.project(*schema.primary_key_names).qualified.map(&:to_sym))
58
+ else
59
+ select(*columns).order(*klass.primary_key_columns(db, table))
60
+ end
57
61
  else
58
62
  self
59
63
  end
60
64
  end
65
+ end
66
+ end
61
67
 
62
- # @!method by_pk(pk)
63
- # Return a relation restricted by its primary key
64
- # @param [Object] pk The primary key value
65
- # @return [SQL::Relation]
66
- # @api public
67
- view(:by_pk, attributes[:base]) do |pk|
68
- where(primary_key => pk)
69
- end
68
+ # @api private
69
+ def self.define_default_views!
70
+ # @!method by_pk(pk)
71
+ # Return a relation restricted by its primary key
72
+ # @param [Object] pk The primary key value
73
+ # @return [SQL::Relation]
74
+ # @api public
75
+ view(:by_pk, schema.map(&:name)) do |pk|
76
+ where(primary_key => pk)
70
77
  end
71
78
  end
72
79
 
@@ -76,16 +83,9 @@ module ROM
76
83
  end
77
84
 
78
85
  # @api private
79
- def self.primary_key_header(db, table)
80
- names =
81
- if schema
82
- schema.primary_key_names
83
- elsif db.respond_to?(:primary_key)
84
- Array(db.primary_key(table))
85
- else
86
- [:id]
87
- end
88
- Header.new(names, table)
86
+ def self.primary_key_columns(db, table)
87
+ names = db.respond_to?(:primary_key) ? Array(db.primary_key(table)) : [:id]
88
+ names.map { |col| :"#{table}__#{col}" }
89
89
  end
90
90
 
91
91
  # Set primary key
@@ -94,8 +94,10 @@ module ROM
94
94
  #
95
95
  # @api public
96
96
  def self.primary_key(value)
97
- Deprecations.announce(
98
- :primary_key, "use schema definition to configure primary key"
97
+ Dry::Core::Deprecations.announce(
98
+ :primary_key,
99
+ "use schema definition to configure primary key",
100
+ tag: :rom
99
101
  )
100
102
  option :primary_key, reader: true, default: value
101
103
  end
@@ -104,26 +106,6 @@ module ROM
104
106
  rel.schema? ? rel.schema.primary_key_name : :id
105
107
  }
106
108
 
107
- # Return table name from relation's sql statement
108
- #
109
- # This value is used by `header` for prefixing column names
110
- #
111
- # @return [Symbol]
112
- #
113
- # @api private
114
- def table
115
- @table ||= dataset.opts[:from].first
116
- end
117
-
118
- # Return a header for this relation
119
- #
120
- # @return [Header]
121
- #
122
- # @api private
123
- def header
124
- @header ||= Header.new(selected_columns, table)
125
- end
126
-
127
109
  # Return raw column names
128
110
  #
129
111
  # @return [Array<Symbol>]
@@ -132,20 +114,6 @@ module ROM
132
114
  def columns
133
115
  @columns ||= dataset.columns
134
116
  end
135
-
136
- protected
137
-
138
- # Return a list of columns from *the sql select* statement or default to
139
- # dataset columns
140
- #
141
- # This is used to construct relation's header
142
- #
143
- # @return [Array<Symbol>]
144
- #
145
- # @api private
146
- def selected_columns
147
- @selected_columns ||= dataset.opts.fetch(:select, columns)
148
- end
149
117
  end
150
118
  end
151
119
  end
@@ -1,3 +1,5 @@
1
+ require 'dry/core/inflector'
2
+
1
3
  module ROM
2
4
  module SQL
3
5
  class Relation < ROM::Relation
@@ -72,8 +74,8 @@ module ROM
72
74
  # @return [Relation]
73
75
  #
74
76
  # @api public
75
- def prefix(name = Inflector.singularize(table))
76
- rename(header.prefix(name).to_h)
77
+ def prefix(name = Dry::Core::Inflector.singularize(schema.name.dataset))
78
+ schema.prefix(name).(self)
77
79
  end
78
80
 
79
81
  # Qualifies all columns in a relation
@@ -87,7 +89,7 @@ module ROM
87
89
  #
88
90
  # @api public
89
91
  def qualified
90
- select(*qualified_columns)
92
+ schema.qualified.(self)
91
93
  end
92
94
 
93
95
  # Return a list of qualified column names
@@ -102,7 +104,7 @@ module ROM
102
104
  #
103
105
  # @api public
104
106
  def qualified_columns
105
- header.qualified.to_a
107
+ schema.qualified.map(&:to_sym)
106
108
  end
107
109
 
108
110
  # Map tuples from the relation
@@ -152,7 +154,7 @@ module ROM
152
154
  #
153
155
  # @api public
154
156
  def project(*names)
155
- select(*header.project(*names))
157
+ schema.project(*names).(self)
156
158
  end
157
159
 
158
160
  # Rename columns in a relation
@@ -169,7 +171,7 @@ module ROM
169
171
  #
170
172
  # @api public
171
173
  def rename(options)
172
- select(*header.rename(options))
174
+ schema.rename(options).(self)
173
175
  end
174
176
 
175
177
  # Select specific columns for select clause
@@ -182,7 +184,7 @@ module ROM
182
184
  #
183
185
  # @api public
184
186
  def select(*args, &block)
185
- __new__(dataset.__send__(__method__, *args, &block))
187
+ schema.project(*args, &block).(self)
186
188
  end
187
189
 
188
190
  # Append specific columns to select clause
@@ -197,7 +199,7 @@ module ROM
197
199
  #
198
200
  # @api public
199
201
  def select_append(*args, &block)
200
- __new__(dataset.__send__(__method__, *args, &block))
202
+ schema.merge(self.class.schema.project(*args, &block)).(self)
201
203
  end
202
204
 
203
205
  # Returns a copy of the relation with a SQL DISTINCT clause.
@@ -211,7 +213,7 @@ module ROM
211
213
  #
212
214
  # @api public
213
215
  def distinct(*args, &block)
214
- __new__(dataset.__send__(__method__, *args, &block))
216
+ new(dataset.__send__(__method__, *args, &block))
215
217
  end
216
218
 
217
219
  # Returns a result of SQL SUM clause.
@@ -288,7 +290,11 @@ module ROM
288
290
  #
289
291
  # @api public
290
292
  def where(*args, &block)
291
- __new__(dataset.__send__(__method__, *args, &block))
293
+ if block
294
+ new(dataset.where(*args).where(self.class.schema.restriction(&block)))
295
+ else
296
+ new(dataset.__send__(__method__, *args))
297
+ end
292
298
  end
293
299
 
294
300
  # Restrict a relation to not match criteria
@@ -302,7 +308,7 @@ module ROM
302
308
  #
303
309
  # @api public
304
310
  def exclude(*args, &block)
305
- __new__(dataset.__send__(__method__, *args, &block))
311
+ new(dataset.__send__(__method__, *args, &block))
306
312
  end
307
313
 
308
314
  # Restrict a relation to match grouping criteria
@@ -320,7 +326,11 @@ module ROM
320
326
  #
321
327
  # @api public
322
328
  def having(*args, &block)
323
- __new__(dataset.__send__(__method__, *args, &block))
329
+ if block
330
+ new(dataset.having(*args).having(self.class.schema.restriction(&block)))
331
+ else
332
+ new(dataset.__send__(__method__, *args, &block))
333
+ end
324
334
  end
325
335
 
326
336
  # Inverts the current WHERE and HAVING clauses. If there is neither a
@@ -336,13 +346,14 @@ module ROM
336
346
  #
337
347
  # @api public
338
348
  def invert
339
- __new__(dataset.invert)
349
+ new(dataset.invert)
340
350
  end
341
351
 
342
352
  # Set order for the relation
343
353
  #
344
354
  # @example
345
355
  # users.order(:name)
356
+ # users.order { [name.desc, id.qualified.desc]}
346
357
  #
347
358
  # @param [Array<Symbol>] *args A list with column names
348
359
  #
@@ -350,7 +361,11 @@ module ROM
350
361
  #
351
362
  # @api public
352
363
  def order(*args, &block)
353
- __new__(dataset.__send__(__method__, *args, &block))
364
+ if block
365
+ new(dataset.order(*args, *self.class.schema.order(&block)))
366
+ else
367
+ new(dataset.__send__(__method__, *args, &block))
368
+ end
354
369
  end
355
370
 
356
371
  # Reverse the order of the relation
@@ -362,7 +377,7 @@ module ROM
362
377
  #
363
378
  # @api public
364
379
  def reverse(*args, &block)
365
- __new__(dataset.__send__(__method__, *args, &block))
380
+ new(dataset.__send__(__method__, *args, &block))
366
381
  end
367
382
 
368
383
  # Limit a relation to a specific number of tuples
@@ -379,7 +394,7 @@ module ROM
379
394
  #
380
395
  # @api public
381
396
  def limit(*args, &block)
382
- __new__(dataset.__send__(__method__, *args, &block))
397
+ new(dataset.__send__(__method__, *args, &block))
383
398
  end
384
399
 
385
400
  # Set offset for the relation
@@ -393,7 +408,7 @@ module ROM
393
408
  #
394
409
  # @api public
395
410
  def offset(*args, &block)
396
- __new__(dataset.__send__(__method__, *args, &block))
411
+ new(dataset.__send__(__method__, *args, &block))
397
412
  end
398
413
 
399
414
  # Join with another relation using INNER JOIN
@@ -407,9 +422,10 @@ module ROM
407
422
  # @return [Relation]
408
423
  #
409
424
  # @api public
410
- def inner_join(*args, &block)
411
- __new__(dataset.__send__(__method__, *args, &block))
425
+ def join(*args, &block)
426
+ __join__(__method__, *args, &block)
412
427
  end
428
+ alias_method :inner_join, :join
413
429
 
414
430
  # Join other relation using LEFT OUTER JOIN
415
431
  #
@@ -423,7 +439,23 @@ module ROM
423
439
  #
424
440
  # @api public
425
441
  def left_join(*args, &block)
426
- __new__(dataset.__send__(__method__, *args, &block))
442
+ __join__(__method__, *args, &block)
443
+ end
444
+
445
+ # Join other relation using RIGHT JOIN
446
+ #
447
+ # @example
448
+ # users.right_join(:tasks, id: :user_id)
449
+ # users.right_join(tasks)
450
+ #
451
+ # @param [Symbol] relation name
452
+ # @param [Hash] join keys
453
+ #
454
+ # @return [Relation]
455
+ #
456
+ # @api public
457
+ def right_join(*args, &block)
458
+ __join__(__method__, *args, &block)
427
459
  end
428
460
 
429
461
  # Group by specific columns
@@ -437,7 +469,7 @@ module ROM
437
469
  #
438
470
  # @api public
439
471
  def group(*args, &block)
440
- __new__(dataset.__send__(__method__, *args, &block))
472
+ new(dataset.__send__(__method__, *args, &block))
441
473
  end
442
474
 
443
475
  # Group by specific columns and count by group
@@ -452,7 +484,7 @@ module ROM
452
484
  #
453
485
  # @api public
454
486
  def group_and_count(*args, &block)
455
- __new__(dataset.__send__(__method__, *args, &block))
487
+ new(dataset.__send__(__method__, *args, &block))
456
488
  end
457
489
 
458
490
  # Select and group by specific columns
@@ -467,7 +499,8 @@ module ROM
467
499
  #
468
500
  # @api public
469
501
  def select_group(*args, &block)
470
- __new__(dataset.__send__(__method__, *args, &block))
502
+ new_schema = schema.project(*args, &block)
503
+ new_schema.(self).group(*new_schema)
471
504
  end
472
505
 
473
506
  # Adds a UNION clause for relation dataset using second relation dataset
@@ -487,7 +520,7 @@ module ROM
487
520
  #
488
521
  # @api public
489
522
  def union(relation, options = EMPTY_HASH, &block)
490
- __new__(dataset.__send__(__method__, relation.dataset, options, &block))
523
+ new(dataset.__send__(__method__, relation.dataset, options, &block))
491
524
  end
492
525
 
493
526
  # Return if a restricted relation has 0 tuples
@@ -519,7 +552,26 @@ module ROM
519
552
  #
520
553
  # @api public
521
554
  def read(sql)
522
- __new__(dataset.db[sql])
555
+ new(dataset.db[sql])
556
+ end
557
+
558
+ private
559
+
560
+ # @api private
561
+ def __join__(type, other, opts = EMPTY_HASH, &block)
562
+ case other
563
+ when Symbol, Association::Name
564
+ new(dataset.__send__(type, other.to_sym, opts, &block))
565
+ when Relation
566
+ __send__(type, other.name.dataset, join_keys(other))
567
+ else
568
+ raise ArgumentError, "+other+ must be either a symbol or a relation, #{other.class} given"
569
+ end
570
+ end
571
+
572
+ # @api private
573
+ def join_keys(other)
574
+ other.associations[name].join_keys(__registry__)
523
575
  end
524
576
  end
525
577
  end
@@ -0,0 +1,24 @@
1
+ require 'rom/sql/dsl'
2
+ require 'rom/sql/expression'
3
+
4
+ module ROM
5
+ module SQL
6
+ class RestrictionDSL < DSL
7
+ # @api private
8
+ def call(&block)
9
+ instance_exec(&block)
10
+ end
11
+
12
+ private
13
+
14
+ # @api private
15
+ def method_missing(meth, *args, &block)
16
+ if schema.key?(meth)
17
+ ::ROM::SQL::Expression.new(schema[meth])
18
+ else
19
+ ::Sequel::VIRTUAL_ROW.__send__(meth, *args, &block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end