rom-sql 0.9.1 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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