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 +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/rom/plugins/relation/sql/auto_restrictions.rb +1 -0
- data/lib/rom/plugins/relation/sql/postgres/explain.rb +2 -0
- data/lib/rom/sql/attribute.rb +10 -3
- data/lib/rom/sql/commands/error_wrapper.rb +4 -0
- data/lib/rom/sql/dsl.rb +11 -0
- data/lib/rom/sql/extensions/postgres/types/array.rb +1 -1
- data/lib/rom/sql/extensions/sqlite/type_builder.rb +1 -0
- data/lib/rom/sql/function.rb +17 -2
- data/lib/rom/sql/gateway.rb +1 -1
- data/lib/rom/sql/migration.rb +3 -2
- data/lib/rom/sql/plugin/associates.rb +8 -0
- data/lib/rom/sql/plugin/pagination.rb +48 -4
- data/lib/rom/sql/plugin/timestamps.rb +6 -6
- data/lib/rom/sql/projection_dsl.rb +13 -1
- data/lib/rom/sql/relation.rb +22 -2
- data/lib/rom/sql/relation/reading.rb +16 -16
- data/lib/rom/sql/relation/writing.rb +13 -8
- data/lib/rom/sql/restriction_dsl.rb +1 -1
- data/lib/rom/sql/schema.rb +48 -7
- data/lib/rom/sql/schema/dsl.rb +12 -0
- data/lib/rom/sql/type_dsl.rb +7 -0
- data/lib/rom/sql/type_extensions.rb +1 -1
- data/lib/rom/sql/types.rb +22 -8
- data/lib/rom/sql/version.rb +1 -1
- data/lib/rom/sql/wrap.rb +17 -6
- metadata +11 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6220c48972476b2eb922c0520c59eab29768cd3d
|
4
|
+
data.tar.gz: 4cc6885f74372fd464ffe85600350c6c64ba29ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
|
data/lib/rom/sql/attribute.rb
CHANGED
@@ -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>]
|
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)
|
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)
|
data/lib/rom/sql/function.rb
CHANGED
@@ -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
|
-
# @
|
63
|
+
# @see ROM::SQL::Attribute#is
|
64
|
+
#
|
65
|
+
# @api public
|
60
66
|
def is(other)
|
61
|
-
::
|
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
|
data/lib/rom/sql/gateway.rb
CHANGED
data/lib/rom/sql/migration.rb
CHANGED
@@ -47,8 +47,9 @@ module ROM
|
|
47
47
|
# end
|
48
48
|
# end
|
49
49
|
#
|
50
|
-
# @
|
51
|
-
#
|
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
|
-
#
|
63
|
-
#
|
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
|
-
#
|
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]
|
41
|
+
# @param [Array<Symbol>] names A list of attribute names
|
42
42
|
#
|
43
43
|
# @api public
|
44
|
-
def timestamps(*
|
45
|
-
timestamp_columns timestamp_columns.merge(
|
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]
|
65
|
+
# @param [Array<Symbol>] names A list of attribute names
|
66
66
|
#
|
67
67
|
# @api public
|
68
|
-
def datestamps(*
|
69
|
-
datestamp_columns datestamp_columns.merge(
|
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
|
-
#
|
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)
|
data/lib/rom/sql/relation.rb
CHANGED
@@ -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| "
|
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(
|
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(
|
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>]
|
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>]
|
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>]
|
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>]
|
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(
|
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]
|
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, *
|
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, *
|
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)
|
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>]
|
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>]
|
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) &&
|
953
|
-
type =
|
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]
|
33
|
+
# @param [Hash] args
|
32
34
|
#
|
33
|
-
# @return [
|
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]
|
47
|
+
# @param [Array<Hash>] args
|
46
48
|
#
|
47
|
-
# @return [
|
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 [
|
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 [
|
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
|
-
# @
|
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
|
-
# @
|
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)
|
data/lib/rom/sql/schema.rb
CHANGED
@@ -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
|
-
#
|
68
|
+
# Project a schema
|
48
69
|
#
|
49
|
-
# @
|
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
|
data/lib/rom/sql/schema/dsl.rb
CHANGED
@@ -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
|
data/lib/rom/sql/type_dsl.rb
CHANGED
@@ -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
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
data/lib/rom/sql/version.rb
CHANGED
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
|
-
|
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.
|
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-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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:
|
225
|
+
version: 2.3.0
|
232
226
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
233
227
|
requirements:
|
234
228
|
- - ">"
|