rom-sql 2.0.0.beta3 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3c40812f8e5e3a90dbb8a24608845b06a69f2e7c
4
- data.tar.gz: 0675140e980842b9bfe70aae46826b7e1c4ccb4c
3
+ metadata.gz: 6220c48972476b2eb922c0520c59eab29768cd3d
4
+ data.tar.gz: 4cc6885f74372fd464ffe85600350c6c64ba29ba
5
5
  SHA512:
6
- metadata.gz: 872afeaa13eb89ce9bb8606b5dc88f599d01169201464b66f746784baef1dd297fb2cfdf4f1da5f3772e54427d8a19168bd6266444b969b5cf5dbac4f781efec
7
- data.tar.gz: b344cad962d4db308295b01c37ec7efdbcfd41d30c7aeeb51cae997188238fa73cc4273c07b12a088c283fbc25035d1afdc63b1bd7734711b70344b2cefef72c
6
+ metadata.gz: 44814022262e8f88ae6e7def5049f3e18790c71bab8fcd5f0c1bc1a06934ebac57715f511ac0929ad276055d185ab229e29e3312c8c38f4269d2eaf9273d22d7
7
+ data.tar.gz: 4e37a732f6291c6e37349262dccda67842441c3c34ead4076c9ca8a6ca8ed7d54d3c5bc6da92e76793bf2ad4714102a9406a6b6f2314d93860e872d565031618
data/CHANGELOG.md CHANGED
@@ -66,6 +66,13 @@
66
66
  employees.select { [dep_no, salary, int::avg(salary).over(partition: dep_no, order: id).as(:avg_salary)] }
67
67
  ```
68
68
 
69
+ * Function result can be negated, also `ROM::SQL::Function#not` was added (flash-gordon)
70
+
71
+ ```ruby
72
+ users.where { !lower(name).is('John') }
73
+ users.where { lower(name).not('John') }
74
+ ```
75
+
69
76
 
70
77
  ### Changed
71
78
 
@@ -73,6 +80,7 @@
73
80
  * [BREAKING] `Associates` command plugin requires associations now (solnic)
74
81
  * [BREAKING] `Command#transaction` is gone in favor of `Relation#transaction` (solnic)
75
82
  * [BREAKING] `PG::JSONArray`, `PG::JSONBArray`, `PG::JSONHash`, and `PG::JSONBHash` types were dropped, use `PG::JSON` and `PG::JSONB` instead (flash-gordon)
83
+ * [BREAKING] The `pg_hstore` extension now doesn't get loaded automatically, use the `:extension` option to load it on config initialization (flash-gordon)
76
84
  * `ManyToOne` no longer uses a join (solnic)
77
85
  * `AutoCombine` and `AutoWrap` plugins were removed as this functionality is provided by core API (solnic)
78
86
  * Foreign keys are indexed by default (flash-gordon)
@@ -84,6 +92,7 @@
84
92
  * Self-ref associations work correctly with custom FKs (solnic)
85
93
  * Aliased associations with custom FKs work correctly (solnic)
86
94
  * Defining a custom dataset block no longer prevents default views like `by_pk` to be defined (solnic)
95
+ * `Relation#group` uses canonical schema now (solnic)
87
96
 
88
97
  [Compare v1.3.3...master](https://github.com/rom-rb/rom-sql/compare/v1.3.3...master)
89
98
 
@@ -36,6 +36,7 @@ module ROM
36
36
  methods.each { |meth| relation.auto_curry(meth) }
37
37
  end
38
38
 
39
+ # @api private
39
40
  def self.restriction_methods(schema)
40
41
  mod = Module.new
41
42
 
@@ -3,6 +3,8 @@ module ROM
3
3
  module Relation
4
4
  module SQL
5
5
  module Postgres
6
+ # PG-specific extensions which adds `Relation#explain` method
7
+ #
6
8
  # @api public
7
9
  module Explain
8
10
  # Show the execution plan
@@ -77,7 +77,7 @@ module ROM
77
77
  # Whenever you join two schemas, the right schema's attribute
78
78
  # will be marked as joined using this method
79
79
  #
80
- # @return [SQL::Attribute]
80
+ # @return [SQL::Attribute] Original attribute marked as joined
81
81
  #
82
82
  # @api public
83
83
  def joined
@@ -168,6 +168,13 @@ module ROM
168
168
  self =~ other
169
169
  end
170
170
 
171
+ # Return a new attribute with an equality expression
172
+ #
173
+ # @example
174
+ # users.where { email =~ 1 }
175
+ #
176
+ # @return [Attribute]
177
+ #
171
178
  # @api public
172
179
  def =~(other)
173
180
  meta(sql_expr: sql_expr =~ binary_operation_arg(other))
@@ -214,7 +221,7 @@ module ROM
214
221
  # users.where { id.in(1, 2, 3) }
215
222
  # users.where(users[:id].in(1, 2, 3))
216
223
  #
217
- # @param [Array<Object>] *args A range or a list of values for an inclusion check
224
+ # @param [Array<Object>] args A range or a list of values for an inclusion check
218
225
  #
219
226
  # @api public
220
227
  def in(*args)
@@ -260,7 +267,7 @@ module ROM
260
267
 
261
268
  # Sequel calls this method to coerce an attribute into SQL string
262
269
  #
263
- # @param [Sequel::Dataset]
270
+ # @param [Sequel::Dataset] ds
264
271
  #
265
272
  # @api private
266
273
  def sql_literal(ds)
@@ -7,6 +7,10 @@ module ROM
7
7
  module ErrorWrapper
8
8
  # Handle Sequel errors and re-raise ROM-specific errors
9
9
  #
10
+ # @return [Hash, Array<Hash>]
11
+ #
12
+ # @raise SQL::Error
13
+ #
10
14
  # @api public
11
15
  def call(*args)
12
16
  super
data/lib/rom/sql/dsl.rb CHANGED
@@ -21,6 +21,17 @@ module ROM
21
21
  end
22
22
  end
23
23
 
24
+ # Return a string literal that will be used directly in an ORDER clause
25
+ #
26
+ # @param [String] value
27
+ #
28
+ # @return [Sequel::LiteralString]
29
+ #
30
+ # @api public
31
+ def `(value)
32
+ ::Sequel.lit(value)
33
+ end
34
+
24
35
  # @api private
25
36
  def respond_to_missing?(name, include_private = false)
26
37
  super || schema.key?(name)
@@ -98,7 +98,7 @@ module ROM
98
98
  # # Translates to an `array_to_string` call
99
99
  # #
100
100
  # # @param [Object] delimiter
101
- # # @param [Object] null
101
+ # # @param [Object] null_repr
102
102
  # #
103
103
  # # @return [SQL::Attribute<Types::String>]
104
104
  # #
@@ -4,6 +4,7 @@ module ROM
4
4
  class TypeBuilder < Schema::TypeBuilder
5
5
  NO_TYPE = EMPTY_STRING
6
6
 
7
+ # @api private
7
8
  def map_type(_, db_type, **_kw)
8
9
  if db_type.eql?(NO_TYPE)
9
10
  ROM::SQL::Types::SQLite::Any
@@ -2,6 +2,8 @@ require 'rom/attribute'
2
2
 
3
3
  module ROM
4
4
  module SQL
5
+ # Specialized attribute type for defining SQL functions
6
+ #
5
7
  # @api public
6
8
  class Function < ROM::Attribute
7
9
  class << self
@@ -49,6 +51,8 @@ module ROM
49
51
  meta[:alias] || super
50
52
  end
51
53
 
54
+ # @see Attribute#qualified
55
+ #
52
56
  # @api private
53
57
  def qualified(table_alias = nil)
54
58
  meta(
@@ -56,9 +60,20 @@ module ROM
56
60
  )
57
61
  end
58
62
 
59
- # @api private
63
+ # @see ROM::SQL::Attribute#is
64
+ #
65
+ # @api public
60
66
  def is(other)
61
- ::Sequel::SQL::BooleanExpression.new(:'=', func, other)
67
+ ::ROM::SQL::Attribute[::ROM::SQL::Types::Bool].meta(
68
+ sql_expr: ::Sequel::SQL::BooleanExpression.new(:'=', func, other)
69
+ )
70
+ end
71
+
72
+ # @see ROM::SQL::Attribute#not
73
+ #
74
+ # @api public
75
+ def not(other)
76
+ !is(other)
62
77
  end
63
78
 
64
79
  # Add an OVER clause making a window function call
@@ -21,7 +21,7 @@ module ROM
21
21
  adapter :sql
22
22
 
23
23
  CONNECTION_EXTENSIONS = {
24
- postgres: %i(pg_array pg_json pg_enum pg_hstore)
24
+ postgres: %i(pg_array pg_json pg_enum)
25
25
  }.freeze
26
26
 
27
27
  # @!attribute [r] logger
@@ -47,8 +47,9 @@ module ROM
47
47
  # end
48
48
  # end
49
49
  #
50
- # @param [ROM::Container] container The container instance used for accessing gateways
51
- # @param [Symbol] gateway The gateway name, :default by default
50
+ # @overload migration(container, gateway)
51
+ # @param [ROM::Container] container The container instance used for accessing gateways
52
+ # @param [Symbol] gateway The gateway name, :default by default
52
53
  #
53
54
  # @api public
54
55
  def migration(*args, &block)
@@ -10,6 +10,7 @@ module ROM
10
10
  class AssociateOptions
11
11
  attr_reader :name, :assoc, :opts
12
12
 
13
+ # @api private
13
14
  def initialize(name, relation, opts)
14
15
  @name = name
15
16
  @assoc = relation.associations[name]
@@ -42,6 +43,7 @@ module ROM
42
43
  super
43
44
  end
44
45
 
46
+ # @api public
45
47
  module ClassMethods
46
48
  # @see ROM::Command::ClassInterface.build
47
49
  #
@@ -134,6 +136,12 @@ module ROM
134
136
  result_type == :one ? output_tuples[0] : output_tuples
135
137
  end
136
138
 
139
+ # Return a new command with the provided association
140
+ #
141
+ # @param [Symbol, Relation::Name] name The name of the association
142
+ #
143
+ # @return [Command]
144
+ #
137
145
  # @api public
138
146
  def with_association(name, opts = EMPTY_HASH)
139
147
  self.class.build(
@@ -3,34 +3,76 @@ require 'rom/initializer'
3
3
  module ROM
4
4
  module SQL
5
5
  module Plugin
6
+ # Pagination plugin for Relations
7
+ #
8
+ # @api public
6
9
  module Pagination
10
+ # Pager object provides the underlying pagination API for relations
11
+ #
12
+ # @api public
7
13
  class Pager
8
14
  extend Initializer
9
15
  include Dry::Equalizer(:dataset, :options)
10
16
 
17
+ # @!attribute [r] dataset
18
+ # @return [Sequel::Dataset] Relation's dataset
11
19
  param :dataset
12
20
 
21
+ # @!attribute [r] current_page
22
+ # @return [Integer] Current page number
13
23
  option :current_page, default: -> { 1 }
24
+
25
+ # @!attribute [r] per_page
26
+ # @return [Integer] Current per-page number
14
27
  option :per_page
15
28
 
29
+ # Return next page number
30
+ #
31
+ # @example
32
+ # users.page(2).pager.next_page
33
+ # # => 3
34
+ #
35
+ # @return [Integer]
36
+ #
37
+ # @api public
16
38
  def next_page
17
39
  num = current_page + 1
18
40
  num if total_pages >= num
19
41
  end
20
42
 
43
+ # Return previous page number
44
+ #
45
+ # @example
46
+ # users.page(2).pager.prev_page
47
+ # # => 1
48
+ #
49
+ # @return [Integer]
50
+ #
51
+ # @api public
21
52
  def prev_page
22
53
  num = current_page - 1
23
54
  num if num > 0
24
55
  end
25
56
 
57
+ # Return total number of tuples
58
+ #
59
+ # @return [Integer]
60
+ #
61
+ # @api public
26
62
  def total
27
63
  dataset.unlimited.count
28
64
  end
29
65
 
66
+ # Return total number of pages
67
+ #
68
+ # @return [Integer]
69
+ #
70
+ # @api public
30
71
  def total_pages
31
72
  (total / per_page.to_f).ceil
32
73
  end
33
74
 
75
+ # @api private
34
76
  def at(dataset, current_page, per_page = self.per_page)
35
77
  current_page = current_page.to_i
36
78
  per_page = per_page.to_i
@@ -44,6 +86,7 @@ module ROM
44
86
  alias_method :limit_value, :per_page
45
87
  end
46
88
 
89
+ # @api private
47
90
  def self.included(klass)
48
91
  super
49
92
 
@@ -59,9 +102,8 @@ module ROM
59
102
  # Paginate a relation
60
103
  #
61
104
  # @example
62
- # rom.relations[:users].class.per_page(10)
63
- # rom.relations[:users].page(1)
64
- # rom.relations[:users].pager # => info about pagination
105
+ # users.page(1)
106
+ # users.pager # => info about pagination
65
107
  #
66
108
  # @return [Relation]
67
109
  #
@@ -74,7 +116,9 @@ module ROM
74
116
  # Set limit for pagination
75
117
  #
76
118
  # @example
77
- # rom.relations[:users].page(2).per_page(10)
119
+ # users.per_page(10).page(2)
120
+ #
121
+ # @return [Relation]
78
122
  #
79
123
  # @api public
80
124
  def per_page(num)
@@ -38,11 +38,11 @@ module ROM
38
38
  # result = create_user.call
39
39
  # result[:created_at] #=> Time.now.utc
40
40
  #
41
- # @param [Symbol] name of the attribute to set
41
+ # @param [Array<Symbol>] names A list of attribute names
42
42
  #
43
43
  # @api public
44
- def timestamps(*args)
45
- timestamp_columns timestamp_columns.merge(args)
44
+ def timestamps(*names)
45
+ timestamp_columns timestamp_columns.merge(names)
46
46
 
47
47
  include InstanceMethods
48
48
  end
@@ -62,11 +62,11 @@ module ROM
62
62
  # result = create_user.call
63
63
  # result[:created_at] #=> Date.today
64
64
  #
65
- # @param [Symbol] name of the attribute to set
65
+ # @param [Array<Symbol>] names A list of attribute names
66
66
  #
67
67
  # @api public
68
- def datestamps(*args)
69
- datestamp_columns datestamp_columns.merge(args)
68
+ def datestamps(*names)
69
+ datestamp_columns datestamp_columns.merge(names)
70
70
 
71
71
  include InstanceMethods
72
72
  end
@@ -3,8 +3,20 @@ require 'rom/sql/function'
3
3
 
4
4
  module ROM
5
5
  module SQL
6
- # @api private
6
+ # Projection DSL used in reading API (`select`, `select_append` etc.)
7
+ #
8
+ # @api public
7
9
  class ProjectionDSL < DSL
10
+ # Return a string literal that will be directly used in an SQL statement or query
11
+ #
12
+ # @example
13
+ # users.select { `FOO`.as(:foo) }.first
14
+ # # => { :foo => "FOO" }
15
+ #
16
+ # @param [String] value A string object
17
+ #
18
+ # @return [Attribute] An SQL attribute with a string literal expression
19
+ #
8
20
  # @api public
9
21
  def `(value)
10
22
  expr = ::Sequel.lit(value)
@@ -61,7 +61,7 @@ module ROM
61
61
  # @api public
62
62
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
63
63
  def by_pk(#{schema.primary_key.map(&:name).join(', ')})
64
- where(#{schema.primary_key.map { |attr| "self.class.schema[:#{attr.name}] => #{attr.name}" }.join(', ')})
64
+ where(#{schema.primary_key.map { |attr| "schema.canonical[:#{attr.name}] => #{attr.name}" }.join(', ')})
65
65
  end
66
66
  RUBY
67
67
  else
@@ -80,7 +80,7 @@ module ROM
80
80
  "Missing primary key for :\#{schema.name}"
81
81
  )
82
82
  end
83
- where(self.class.schema[self.class.schema.primary_key_name].qualified => pk)
83
+ where(schema.canonical[schema.canonical.primary_key_name].qualified => pk)
84
84
  end
85
85
  RUBY
86
86
  end
@@ -116,6 +116,26 @@ module ROM
116
116
  associations[name].()
117
117
  end
118
118
 
119
+ # Open a database transaction
120
+ #
121
+ # @param [Hash] opts
122
+ # @option opts [Boolean] :auto_savepoint Automatically use a savepoint for Database#transaction calls inside this transaction block.
123
+ # @option opts [Symbol] :isolation The transaction isolation level to use for this transaction, should be :uncommitted, :committed, :repeatable, or :serializable, used if given and the database/adapter supports customizable transaction isolation levels.
124
+ # @option opts [Integer] :num_retries The number of times to retry if the :retry_on option is used. The default is 5 times. Can be set to nil to retry indefinitely, but that is not recommended.
125
+ # @option opts [Proc] :before_retry Proc to execute before rertrying if the :retry_on option is used. Called with two arguments: the number of retry attempts (counting the current one) and the error the last attempt failed with.
126
+ # @option opts [String] :prepare A string to use as the transaction identifier for a prepared transaction (two-phase commit), if the database/adapter supports prepared transactions.
127
+ # @option opts [Class] :retry_on An exception class or array of exception classes for which to automatically retry the transaction. Can only be set if not inside an existing transaction. Note that this should not be used unless the entire transaction block is idempotent, as otherwise it can cause non-idempotent behavior to execute multiple times.
128
+ # @option opts [Symbol] :rollback Can the set to :reraise to reraise any Sequel::Rollback exceptions raised, or :always to always rollback even if no exceptions occur (useful for testing).
129
+ # @option opts [Symbol] :server The server to use for the transaction. Set to :default, :read_only, or whatever symbol you used in the connect string when naming your servers.
130
+ # @option opts [Boolean] :savepoint Whether to create a new savepoint for this transaction, only respected if the database/adapter supports savepoints. By default Sequel will reuse an existing transaction, so if you want to use a savepoint you must use this option. If the surrounding transaction uses :auto_savepoint, you can set this to false to not use a savepoint. If the value given for this option is :only, it will only create a savepoint if it is inside a transacation.
131
+ # @option opts [Boolean] :deferrable **PG 9.1+ only** If present, set to DEFERRABLE if true or NOT DEFERRABLE if false.
132
+ # @option opts [Boolean] :read_only **PG only** If present, set to READ ONLY if true or READ WRITE if false.
133
+ # @option opts [Symbol] :synchronous **PG only** if non-nil, set synchronous_commit appropriately. Valid values true, :on, false, :off, :local (9.1+), and :remote_write (9.2+).
134
+ #
135
+ # @yield [t] Transaction
136
+ #
137
+ # @return [Mixed]
138
+ #
119
139
  # @api public
120
140
  def transaction(opts = EMPTY_HASH, &block)
121
141
  Transaction.new(dataset.db).run(opts, &block)
@@ -241,7 +241,7 @@ module ROM
241
241
  #
242
242
  # @api public
243
243
  def select_append(*args, &block)
244
- schema.merge(self.class.schema.project(*args, &block)).(self)
244
+ schema.merge(schema.canonical.project(*args, &block)).(self)
245
245
  end
246
246
 
247
247
  # Returns a copy of the relation with a SQL DISTINCT clause.
@@ -273,7 +273,7 @@ module ROM
273
273
  # @example
274
274
  # users.sum(:age)
275
275
  #
276
- # @param [Array<Symbol>] *args A list with column names
276
+ # @param [Array<Symbol>] args A list with column names
277
277
  #
278
278
  # @return [Integer]
279
279
  #
@@ -287,7 +287,7 @@ module ROM
287
287
  # @example
288
288
  # users.min(:age)
289
289
  #
290
- # @param [Array<Symbol>] *args A list with column names
290
+ # @param [Array<Symbol>] args A list with column names
291
291
  #
292
292
  # @return Number
293
293
  #
@@ -301,7 +301,7 @@ module ROM
301
301
  # @example
302
302
  # users.max(:age)
303
303
  #
304
- # @param [Array<Symbol>] *args A list with column names
304
+ # @param [Array<Symbol>] args A list with column names
305
305
  #
306
306
  # @return Number
307
307
  #
@@ -315,7 +315,7 @@ module ROM
315
315
  # @example
316
316
  # users.avg(:age)
317
317
  #
318
- # @param [Array<Symbol>] *args A list with column names
318
+ # @param [Array<Symbol>] args A list with column names
319
319
  #
320
320
  # @return Number
321
321
  #
@@ -354,7 +354,7 @@ module ROM
354
354
  # @api public
355
355
  def where(*args, &block)
356
356
  if block
357
- where(*args).where(self.class.schema.restriction(&block))
357
+ where(*args).where(schema.canonical.restriction(&block))
358
358
  elsif args.size == 1 && args[0].is_a?(Hash)
359
359
  new(dataset.where(coerce_conditions(args[0])))
360
360
  elsif !args.empty?
@@ -369,7 +369,7 @@ module ROM
369
369
  # @example
370
370
  # users.exclude(name: 'Jane')
371
371
  #
372
- # @param [Hash] *args A hash with conditions for exclusion
372
+ # @param [Hash] args A hash with conditions for exclusion
373
373
  #
374
374
  # @return [Relation]
375
375
  #
@@ -413,7 +413,7 @@ module ROM
413
413
  # @api public
414
414
  def having(*args, &block)
415
415
  if block
416
- new(dataset.having(*args, *self.class.schema.restriction(&block)))
416
+ new(dataset.having(*args, *schema.canonical.restriction(&block)))
417
417
  else
418
418
  new(dataset.__send__(__method__, *args))
419
419
  end
@@ -468,7 +468,7 @@ module ROM
468
468
  # @api public
469
469
  def order(*args, &block)
470
470
  if block
471
- new(dataset.order(*args, *self.class.schema.order(&block)))
471
+ new(dataset.order(*args, *schema.canonical.order(&block)))
472
472
  else
473
473
  new(dataset.__send__(__method__, *args, &block))
474
474
  end
@@ -678,10 +678,10 @@ module ROM
678
678
  if args.size > 0
679
679
  group(*args).group_append(&block)
680
680
  else
681
- new(dataset.__send__(__method__, *schema.group(&block)))
681
+ new(dataset.__send__(__method__, *schema.canonical.group(&block)))
682
682
  end
683
683
  else
684
- new(dataset.__send__(__method__, *schema.project(*args).canonical))
684
+ new(dataset.__send__(__method__, *schema.canonical.project(*args)))
685
685
  end
686
686
  end
687
687
 
@@ -717,7 +717,7 @@ module ROM
717
717
  if args.size > 0
718
718
  group_append(*args).group_append(&block)
719
719
  else
720
- new(dataset.group_append(*schema.group(&block)))
720
+ new(dataset.group_append(*schema.canonical.group(&block)))
721
721
  end
722
722
  else
723
723
  new(dataset.group_append(*args))
@@ -730,7 +730,7 @@ module ROM
730
730
  # tasks.group_and_count(:user_id)
731
731
  # # => [{ user_id: 1, count: 2 }, { user_id: 2, count: 3 }]
732
732
  #
733
- # @param [Array<Symbol>] *args A list of column names
733
+ # @param [Array<Symbol>] args A list of column names
734
734
  #
735
735
  # @return [Relation]
736
736
  #
@@ -745,7 +745,7 @@ module ROM
745
745
  # tasks.select_group(:user_id)
746
746
  # # => [{ user_id: 1 }, { user_id: 2 }]
747
747
  #
748
- # @param [Array<Symbol>] *args A list of column names
748
+ # @param [Array<Symbol>] args A list of column names
749
749
  #
750
750
  # @return [Relation]
751
751
  #
@@ -949,8 +949,8 @@ module ROM
949
949
  # @api private
950
950
  def coerce_conditions(conditions)
951
951
  conditions.each_with_object({}) { |(k, v), h|
952
- if k.is_a?(Symbol) && self.class.schema.key?(k)
953
- type = self.class.schema[k]
952
+ if k.is_a?(Symbol) && schema.canonical.key?(k)
953
+ type = schema.canonical[k]
954
954
  h[k] = v.is_a?(Array) ? v.map { |e| type[e] } : type[v]
955
955
  else
956
956
  h[k] = v
@@ -11,6 +11,8 @@ module ROM
11
11
  # users.upsert({ name: 'Jane', email: 'jane@foo.com' },
12
12
  # { target: :email, update: { name: :excluded__name } }
13
13
  #
14
+ # @return [Integer] Number of affected rows
15
+ #
14
16
  # @api public
15
17
  def upsert(*args, &block)
16
18
  if args.size > 1 && args[-1].is_a?(Hash)
@@ -28,9 +30,9 @@ module ROM
28
30
  # @example
29
31
  # users.insert(name: 'Jane')
30
32
  #
31
- # @param [Hash] tuple
33
+ # @param [Hash] args
32
34
  #
33
- # @return [Relation]
35
+ # @return [Hash] Inserted tuple
34
36
  #
35
37
  # @api public
36
38
  def insert(*args, &block)
@@ -42,9 +44,9 @@ module ROM
42
44
  # @example
43
45
  # users.multi_insert([{name: 'Jane'}, {name: 'Jack'}])
44
46
  #
45
- # @param [Array] tuples
47
+ # @param [Array<Hash>] args
46
48
  #
47
- # @return [Relation]
49
+ # @return [Array<String>] A list of executed SQL statements
48
50
  #
49
51
  # @api public
50
52
  def multi_insert(*args, &block)
@@ -57,7 +59,7 @@ module ROM
57
59
  # users.update(name: 'Jane')
58
60
  # users.where(name: 'Jane').update(name: 'Jane Doe')
59
61
  #
60
- # @return [Relation]
62
+ # @return [Integer] Number of updated rows
61
63
  #
62
64
  # @api public
63
65
  def update(*args, &block)
@@ -71,7 +73,7 @@ module ROM
71
73
  # users.where(name: 'Jane').delete # delete tuples
72
74
  # from restricted relation
73
75
  #
74
- # @return [Relation]
76
+ # @return [Integer] Number of deleted tuples
75
77
  #
76
78
  # @api public
77
79
  def delete(*args, &block)
@@ -79,6 +81,7 @@ module ROM
79
81
  end
80
82
 
81
83
  # Insert tuples from other relation
84
+ #
82
85
  # NOTE: The method implicitly uses a transaction
83
86
  #
84
87
  # @example
@@ -89,7 +92,7 @@ module ROM
89
92
  # the INSERT ... SELECT statement will
90
93
  # be used for importing the data
91
94
  #
92
- # @params [SQL::Relation] other_sql_relation
95
+ # @param [SQL::Relation] other_sql_relation
93
96
  #
94
97
  # @option [Integer] :slice
95
98
  # Split loading into batches of provided size,
@@ -100,10 +103,12 @@ module ROM
100
103
  # Import data from another relation. The source
101
104
  # relation will be materialized before loading
102
105
  #
103
- # @params [Relation] other
106
+ # @param [Relation] other
104
107
  #
105
108
  # @option [Integer] :slice
106
109
  #
110
+ # @return [Integer] Number of imported tuples
111
+ #
107
112
  # @api public
108
113
  def import(other, options = EMPTY_HASH)
109
114
  if other.gateway.eql?(gateway)
@@ -14,7 +14,7 @@ module ROM
14
14
  # @example
15
15
  # users.where { exists(users.where(name: 'John')) }
16
16
  def exists(relation)
17
- relation.dataset.exists
17
+ ::ROM::SQL::Attribute[Types::Bool].meta(sql_expr: relation.dataset.exists)
18
18
  end
19
19
 
20
20
  private
@@ -11,6 +11,9 @@ require 'rom/sql/schema/inferrer'
11
11
 
12
12
  module ROM
13
13
  module SQL
14
+ # Specialized schema for SQL databases
15
+ #
16
+ # @api public
14
17
  class Schema < ROM::Schema
15
18
  # @!attribute [r] indexes
16
19
  # @return [Array<Index>] Array with schema indexes
@@ -20,16 +23,34 @@ module ROM
20
23
  # @return [Array<ForeignKey>] Array with foreign keys
21
24
  option :foreign_keys, default: -> { EMPTY_SET }
22
25
 
26
+ # Open restriction DSL for defining query conditions using schema attributes
27
+ #
28
+ # @see Relation#where
29
+ #
30
+ # @return [Mixed] Result of the block call
31
+ #
23
32
  # @api public
24
33
  def restriction(&block)
25
34
  RestrictionDSL.new(self).call(&block)
26
35
  end
27
36
 
37
+ # Open Order DSL for setting ORDER clause in queries
38
+ #
39
+ # @see Relation#order
40
+ #
41
+ # @return [Mixed] Result of the block call
42
+ #
28
43
  # @api public
29
44
  def order(&block)
30
45
  OrderDSL.new(self).call(&block)
31
46
  end
32
47
 
48
+ # Open Group DSL for setting GROUP BY clause in queries
49
+ #
50
+ # @see Relation#group
51
+ #
52
+ # @return [Mixed] Result of the block call
53
+ #
33
54
  # @api public
34
55
  def group(&block)
35
56
  GroupDSL.new(self).call(&block)
@@ -44,15 +65,13 @@ module ROM
44
65
  new(map { |attr| attr.qualified(table_alias) })
45
66
  end
46
67
 
47
- # Return a new schema with attributes restored to canonical form
68
+ # Project a schema
48
69
  #
49
- # @return [Schema]
70
+ # @see ROM::Schema#project
71
+ # @see Relation#select
72
+ #
73
+ # @return [Schema] A new schema with projected attributes
50
74
  #
51
- # @api public
52
- def canonical
53
- new(map(&:canonical))
54
- end
55
-
56
75
  # @api public
57
76
  def project(*names, &block)
58
77
  if block
@@ -62,21 +81,39 @@ module ROM
62
81
  end
63
82
  end
64
83
 
84
+ # Project schema so that it only contains primary key
85
+ #
86
+ # @return [Schema]
87
+ #
65
88
  # @api private
66
89
  def project_pk
67
90
  project(*primary_key_names)
68
91
  end
69
92
 
93
+ # Project schema so that it only contains renamed foreign key
94
+ #
95
+ # @return [Schema]
96
+ #
70
97
  # @api private
71
98
  def project_fk(mapping)
72
99
  new(rename(mapping).map(&:foreign_key))
73
100
  end
74
101
 
102
+ # Join with another schema
103
+ #
104
+ # @param [Schema] other The other schema to join with
105
+ #
106
+ # @return [Schema]
107
+ #
75
108
  # @api public
76
109
  def join(other)
77
110
  merge(other.joined)
78
111
  end
79
112
 
113
+ # Return a new schema with all attributes marked as joined
114
+ #
115
+ # @return [Schema]
116
+ #
80
117
  # @api public
81
118
  def joined
82
119
  new(map(&:joined))
@@ -102,6 +139,8 @@ module ROM
102
139
  new(EMPTY_ARRAY)
103
140
  end
104
141
 
142
+ # Finalize all attributes by qualifying them and initializing primary key names
143
+ #
105
144
  # @api private
106
145
  def finalize_attributes!(options = EMPTY_HASH)
107
146
  super do
@@ -110,6 +149,8 @@ module ROM
110
149
  end
111
150
  end
112
151
 
152
+ # Finalize associations
153
+ #
113
154
  # @api private
114
155
  def finalize_associations!(relations:)
115
156
  super do
@@ -3,14 +3,26 @@ require 'rom/sql/schema/index_dsl'
3
3
  module ROM
4
4
  module SQL
5
5
  class Schema < ROM::Schema
6
+ # Specialized schema DSL with SQL-specific features
7
+ #
6
8
  # @api public
7
9
  class DSL < ROM::Schema::DSL
10
+ # @!attribute [r] index_dsl
11
+ # @return [IndexDSL] Index DSL instance (created only if indexes block is called)
8
12
  attr_reader :index_dsl
9
13
 
14
+ # Define indexes within a block
15
+ #
16
+ # @api public
10
17
  def indexes(&block)
11
18
  @index_dsl = IndexDSL.new(options, &block)
12
19
  end
13
20
 
21
+ private
22
+
23
+ # Return schema options
24
+ #
25
+ # @api private
14
26
  def opts
15
27
  if index_dsl
16
28
  opts = super
@@ -1,8 +1,12 @@
1
1
  module ROM
2
2
  module SQL
3
+ # Type DSL used by Types.define method
4
+ #
5
+ # @api public
3
6
  class TypeDSL
4
7
  attr_reader :definition, :input_constructor, :output_constructor
5
8
 
9
+ # @api private
6
10
  def initialize(value_type)
7
11
  if value_type.class < ::Dry::Types::Type
8
12
  @definition = value_type
@@ -11,6 +15,7 @@ module ROM
11
15
  end
12
16
  end
13
17
 
18
+ # @api private
14
19
  def call(&block)
15
20
  instance_exec(&block)
16
21
 
@@ -18,10 +23,12 @@ module ROM
18
23
  .meta(read: definition.constructor(output_constructor))
19
24
  end
20
25
 
26
+ # @api private
21
27
  def input(&block)
22
28
  @input_constructor = block
23
29
  end
24
30
 
31
+ # @api private
25
32
  def output(&block)
26
33
  @output_constructor = block
27
34
  end
@@ -7,7 +7,7 @@ module ROM
7
7
  class << self
8
8
  # Gets extensions for a type
9
9
  #
10
- # @param [Dry::Types::Type] type
10
+ # @param [Dry::Types::Type] wrapped
11
11
  #
12
12
  # @return [Hash]
13
13
  #
data/lib/rom/sql/types.rb CHANGED
@@ -9,18 +9,32 @@ module ROM
9
9
  module Types
10
10
  include ROM::Types
11
11
 
12
- def self.Constructor(*args, &block)
13
- ROM::Types.Constructor(*args, &block)
14
- end
15
-
16
- def self.Definition(*args, &block)
17
- ROM::Types.Definition(*args, &block)
18
- end
19
-
12
+ # Define a foreign key attribute type
13
+ #
14
+ # @example with default Int type
15
+ # attribute :user_id, Types.ForeignKey(:users)
16
+ #
17
+ # @example with a custom type
18
+ # attribute :user_id, Types.ForeignKey(:users, Types::UUID)
19
+ #
20
+ # @return [Dry::Types::Definition]
21
+ #
22
+ # @api public
20
23
  def self.ForeignKey(relation, type = Types::Int.meta(index: true))
21
24
  super
22
25
  end
23
26
 
27
+ # Define a complex attribute type using Type DSL
28
+ #
29
+ # @example
30
+ # attribute :meta, Types.define(Types::JSON) do
31
+ # input { Types::PG::JSON }
32
+ # output { Types::Coercible::Hash }
33
+ # end
34
+ #
35
+ # @return [Dry::Types::Definition]
36
+ #
37
+ # @api public
24
38
  def self.define(value_type, &block)
25
39
  TypeDSL.new(value_type).call(&block)
26
40
  end
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module SQL
3
- VERSION = '2.0.0.beta3'.freeze
3
+ VERSION = '2.0.0.rc1'.freeze
4
4
  end
5
5
  end
data/lib/rom/sql/wrap.rb CHANGED
@@ -2,21 +2,32 @@ require 'rom/relation/wrap'
2
2
 
3
3
  module ROM
4
4
  module SQL
5
+ # Specialized wrap relation for SQL
6
+ #
7
+ # This type of relations is returned when using `Relation#wrap` and it uses
8
+ # a join, unlike `Relation#combine`, which means a relation is restricted
9
+ # only to tuples which have associated tuples. It should be used in cases
10
+ # where you want to rely on this restriction.
11
+ #
12
+ # @api public
5
13
  class Wrap < Relation::Wrap
14
+ # Return a schema which includes attributes from wrapped relations
15
+ #
16
+ # @return [Schema]
17
+ #
6
18
  # @api public
7
19
  def schema
8
20
  root.schema.merge(nodes.map(&:schema).reduce(:merge)).qualified
9
21
  end
10
22
 
23
+ # Internal method used by abstract `ROM::Relation::Wrap`
24
+ #
25
+ # @return [Relation]
26
+ #
11
27
  # @api private
12
28
  def relation
13
29
  relation = nodes.reduce(root) do |a, e|
14
- if associations.key?(e.name.key)
15
- a.associations[e.name.key].join(:join, a, e)
16
- else
17
- # TODO: deprecate this before 2.0
18
- a.qualified.join(e.name.dataset, e.meta[:keys])
19
- end
30
+ a.associations[e.name.key].join(:join, a, e)
20
31
  end
21
32
  schema.(relation)
22
33
  end
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rom-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta3
4
+ version: 2.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-08-14 00:00:00.000000000 Z
11
+ date: 2017-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.43'
19
+ version: '4.49'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '4.43'
26
+ version: '4.49'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: dry-equalizer
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,20 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.11'
48
- - - ">="
49
- - !ruby/object:Gem::Version
50
- version: 0.11.1
47
+ version: '0.12'
51
48
  type: :runtime
52
49
  prerelease: false
53
50
  version_requirements: !ruby/object:Gem::Requirement
54
51
  requirements:
55
52
  - - "~>"
56
53
  - !ruby/object:Gem::Version
57
- version: '0.11'
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: 0.11.1
54
+ version: '0.12'
61
55
  - !ruby/object:Gem::Dependency
62
56
  name: dry-core
63
57
  requirement: !ruby/object:Gem::Requirement
@@ -78,14 +72,14 @@ dependencies:
78
72
  requirements:
79
73
  - - "~>"
80
74
  - !ruby/object:Gem::Version
81
- version: 4.0.0.beta
75
+ version: 4.0.0.rc
82
76
  type: :runtime
83
77
  prerelease: false
84
78
  version_requirements: !ruby/object:Gem::Requirement
85
79
  requirements:
86
80
  - - "~>"
87
81
  - !ruby/object:Gem::Version
88
- version: 4.0.0.beta
82
+ version: 4.0.0.rc
89
83
  - !ruby/object:Gem::Dependency
90
84
  name: bundler
91
85
  requirement: !ruby/object:Gem::Requirement
@@ -228,7 +222,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
228
222
  requirements:
229
223
  - - ">="
230
224
  - !ruby/object:Gem::Version
231
- version: '0'
225
+ version: 2.3.0
232
226
  required_rubygems_version: !ruby/object:Gem::Requirement
233
227
  requirements:
234
228
  - - ">"