rom-sql 3.1.0 → 3.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +214 -101
- data/LICENSE +1 -1
- data/README.md +12 -54
- data/lib/rom-sql.rb +2 -0
- data/lib/rom/plugins/relation/sql/auto_restrictions.rb +2 -0
- data/lib/rom/plugins/relation/sql/instrumentation.rb +2 -0
- data/lib/rom/plugins/relation/sql/postgres/explain.rb +6 -7
- data/lib/rom/plugins/relation/sql/postgres/full_text_search.rb +53 -0
- data/lib/rom/plugins/relation/sql/postgres/streaming.rb +108 -0
- data/lib/rom/sql.rb +2 -0
- data/lib/rom/sql/associations.rb +2 -0
- data/lib/rom/sql/associations/core.rb +3 -1
- data/lib/rom/sql/associations/many_to_many.rb +3 -1
- data/lib/rom/sql/associations/many_to_one.rb +2 -0
- data/lib/rom/sql/associations/one_to_many.rb +2 -0
- data/lib/rom/sql/associations/one_to_one.rb +2 -0
- data/lib/rom/sql/associations/one_to_one_through.rb +2 -0
- data/lib/rom/sql/associations/self_ref.rb +2 -0
- data/lib/rom/sql/attribute.rb +26 -7
- data/lib/rom/sql/attribute_aliasing.rb +28 -0
- data/lib/rom/sql/commands.rb +2 -0
- data/lib/rom/sql/commands/create.rb +2 -0
- data/lib/rom/sql/commands/delete.rb +2 -0
- data/lib/rom/sql/commands/error_wrapper.rb +2 -0
- data/lib/rom/sql/commands/update.rb +2 -0
- data/lib/rom/sql/dsl.rb +11 -3
- data/lib/rom/sql/error.rb +2 -0
- data/lib/rom/sql/errors.rb +2 -0
- data/lib/rom/sql/extensions.rb +2 -0
- data/lib/rom/sql/extensions/active_support_notifications.rb +2 -0
- data/lib/rom/sql/extensions/mysql.rb +2 -0
- data/lib/rom/sql/extensions/mysql/type_builder.rb +2 -0
- data/lib/rom/sql/extensions/postgres.rb +3 -0
- data/lib/rom/sql/extensions/postgres/commands.rb +3 -1
- data/lib/rom/sql/extensions/postgres/type_builder.rb +6 -4
- data/lib/rom/sql/extensions/postgres/type_serializer.rb +2 -0
- data/lib/rom/sql/extensions/postgres/types.rb +2 -0
- data/lib/rom/sql/extensions/postgres/types/array.rb +6 -4
- data/lib/rom/sql/extensions/postgres/types/array_types.rb +3 -1
- data/lib/rom/sql/extensions/postgres/types/geometric.rb +2 -0
- data/lib/rom/sql/extensions/postgres/types/json.rb +13 -11
- data/lib/rom/sql/extensions/postgres/types/ltree.rb +25 -23
- data/lib/rom/sql/extensions/postgres/types/network.rb +2 -0
- data/lib/rom/sql/extensions/postgres/types/range.rb +2 -0
- data/lib/rom/sql/extensions/rails_log_subscriber.rb +2 -0
- data/lib/rom/sql/extensions/sqlite.rb +2 -0
- data/lib/rom/sql/extensions/sqlite/type_builder.rb +2 -0
- data/lib/rom/sql/extensions/sqlite/types.rb +2 -0
- data/lib/rom/sql/foreign_key.rb +3 -1
- data/lib/rom/sql/function.rb +14 -3
- data/lib/rom/sql/gateway.rb +6 -1
- data/lib/rom/sql/group_dsl.rb +2 -0
- data/lib/rom/sql/index.rb +2 -0
- data/lib/rom/sql/join_dsl.rb +2 -0
- data/lib/rom/sql/mapper_compiler.rb +2 -0
- data/lib/rom/sql/migration.rb +5 -3
- data/lib/rom/sql/migration/inline_runner.rb +2 -0
- data/lib/rom/sql/migration/migrator.rb +4 -2
- data/lib/rom/sql/migration/recorder.rb +2 -0
- data/lib/rom/sql/migration/runner.rb +4 -2
- data/lib/rom/sql/migration/schema_diff.rb +2 -0
- data/lib/rom/sql/migration/template.rb +2 -0
- data/lib/rom/sql/migration/writer.rb +12 -4
- data/lib/rom/sql/order_dsl.rb +2 -0
- data/lib/rom/sql/plugin/associates.rb +4 -2
- data/lib/rom/sql/plugin/nullify.rb +2 -0
- data/lib/rom/sql/plugin/pagination.rb +2 -0
- data/lib/rom/sql/plugins.rb +2 -0
- data/lib/rom/sql/projection_dsl.rb +3 -1
- data/lib/rom/sql/rake_task.rb +2 -0
- data/lib/rom/sql/relation.rb +3 -1
- data/lib/rom/sql/relation/reading.rb +6 -4
- data/lib/rom/sql/relation/writing.rb +2 -0
- data/lib/rom/sql/restriction_dsl.rb +9 -1
- data/lib/rom/sql/schema.rb +16 -2
- data/lib/rom/sql/schema/attributes_inferrer.rb +5 -3
- data/lib/rom/sql/schema/dsl.rb +3 -1
- data/lib/rom/sql/schema/index_dsl.rb +5 -2
- data/lib/rom/sql/schema/inferrer.rb +12 -8
- data/lib/rom/sql/schema/type_builder.rb +4 -2
- data/lib/rom/sql/spec/support.rb +5 -3
- data/lib/rom/sql/tasks/migration_tasks.rake +16 -11
- data/lib/rom/sql/transaction.rb +2 -0
- data/lib/rom/sql/type_dsl.rb +2 -0
- data/lib/rom/sql/type_extensions.rb +3 -1
- data/lib/rom/sql/type_serializer.rb +2 -0
- data/lib/rom/sql/types.rb +2 -0
- data/lib/rom/sql/version.rb +3 -1
- data/lib/rom/sql/wrap.rb +2 -0
- data/lib/rom/types/values.rb +2 -0
- metadata +25 -45
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,70 +1,28 @@
|
|
1
1
|
[gem]: https://rubygems.org/gems/rom-sql
|
2
2
|
[actions]: https://github.com/rom-rb/rom-sql/actions
|
3
|
-
[
|
4
|
-
[inchpages]: http://inch-ci.org/github/rom-rb/rom-sql
|
3
|
+
[codacy]: https://www.codacy.com/gh/rom-rb/rom-sql
|
5
4
|
[chat]: https://rom-rb.zulipchat.com
|
5
|
+
[inchpages]: http://inch-ci.org/github/rom-rb/rom-sql
|
6
6
|
|
7
|
-
# rom-sql [![Join the chat at https://rom-rb.zulipchat.com](https://img.shields.io/badge/rom--rb-join%20chat
|
7
|
+
# rom-sql [![Join the chat at https://rom-rb.zulipchat.com](https://img.shields.io/badge/rom--rb-join%20chat-%23346b7a.svg)][chat]
|
8
8
|
|
9
9
|
[![Gem Version](https://badge.fury.io/rb/rom-sql.svg)][gem]
|
10
10
|
[![CI Status](https://github.com/rom-rb/rom-sql/workflows/ci/badge.svg)][actions]
|
11
|
-
[![
|
12
|
-
[![
|
11
|
+
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/8e2cbaf78af44185876c8fa41540d7ea)][codacy]
|
12
|
+
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/8e2cbaf78af44185876c8fa41540d7ea)][codacy]
|
13
13
|
[![Inline docs](http://inch-ci.org/github/rom-rb/rom-sql.svg?branch=master)][inchpages]
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
Resources:
|
18
|
-
|
19
|
-
- [User Documentation](http://rom-rb.org/learn/sql/)
|
20
|
-
- [API Documentation](http://rubydoc.info/gems/rom-sql)
|
21
|
-
|
22
|
-
## Installation
|
23
|
-
|
24
|
-
Add this line to your application's Gemfile:
|
25
|
-
|
26
|
-
```ruby
|
27
|
-
gem 'rom-sql'
|
28
|
-
```
|
29
|
-
|
30
|
-
And then execute:
|
31
|
-
|
32
|
-
$ bundle
|
33
|
-
|
34
|
-
Or install it yourself as:
|
35
|
-
|
36
|
-
$ gem install rom-sql
|
37
|
-
|
38
|
-
## Docker
|
39
|
-
|
40
|
-
### Development
|
41
|
-
|
42
|
-
In order to have reproducible environment for development, Docker can be used. Provided it's installed, in order to start developing, one can simply execute:
|
43
|
-
|
44
|
-
```bash
|
45
|
-
docker-compose run --rm gem "bash"
|
46
|
-
```
|
47
|
-
|
48
|
-
If this is the first time this command is executed, it will take some time to set up the dependencies and build the rom-sql container. This should happen only on first execution and in case dependency images are removed.
|
49
|
-
|
50
|
-
After dependencies are set container will be started in a bash shell.
|
51
|
-
|
52
|
-
### Testing
|
53
|
-
|
54
|
-
In order to test the changes, execute:
|
15
|
+
## Links
|
55
16
|
|
56
|
-
|
57
|
-
|
58
|
-
bin/run-specs
|
59
|
-
```
|
17
|
+
* [User documentation](http://rom-rb.org/learn/sql)
|
18
|
+
* [API documentation](http://rubydoc.info/gems/rom-sql)
|
60
19
|
|
61
|
-
|
20
|
+
## Supported Ruby versions
|
62
21
|
|
63
|
-
|
22
|
+
This library officially supports the following Ruby versions:
|
64
23
|
|
65
|
-
|
66
|
-
|
67
|
-
```
|
24
|
+
* MRI >= `2.5`
|
25
|
+
* jruby >= `9.2`
|
68
26
|
|
69
27
|
## License
|
70
28
|
|
data/lib/rom-sql.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ROM
|
2
4
|
module Plugins
|
3
5
|
module Relation
|
@@ -24,14 +26,11 @@ module ROM
|
|
24
26
|
#
|
25
27
|
# @api public
|
26
28
|
def explain(format: :text, **options)
|
27
|
-
bool_options = options.map { |opt, value| "#{
|
28
|
-
format_option = "FORMAT #{
|
29
|
+
bool_options = options.map { |opt, value| "#{opt.to_s.upcase} #{!!value}" }
|
30
|
+
format_option = "FORMAT #{format.to_s.upcase}"
|
31
|
+
explain_value = [format_option, *bool_options].join(', ')
|
29
32
|
|
30
|
-
query =
|
31
|
-
"EXPLAIN (" <<
|
32
|
-
[format_option, *bool_options].join(', ') <<
|
33
|
-
") " <<
|
34
|
-
dataset.sql
|
33
|
+
query = "EXPLAIN (#{explain_value}) #{dataset.sql}"
|
35
34
|
|
36
35
|
rows = dataset.with_sql(query).map(:'QUERY PLAN')
|
37
36
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Plugins
|
5
|
+
module Relation
|
6
|
+
module SQL
|
7
|
+
module Postgres
|
8
|
+
# PG-specific extensions which adds `Relation#full_text_search` method
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
module FullTextSearch
|
12
|
+
# Run a full text search on PostgreSQL.
|
13
|
+
# By default, searching for the inclusion of any of the terms in any of the cols.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# posts.full_text_search([:title, :content], 'apples', language: 'english') # => Relation which match the 'apples' phrase
|
17
|
+
#
|
18
|
+
# @option :headline [String] Append a expression to the selected columns aliased to headline that contains an extract of the matched text.
|
19
|
+
#
|
20
|
+
# @option :language [String] The language to use for the search (default: 'simple')
|
21
|
+
#
|
22
|
+
# @option :plain [Boolean] Whether a plain search should be used (default: false). In this case, terms should be a single string, and it will do a search where cols contains all of the words in terms. This ignores search operators in terms.
|
23
|
+
#
|
24
|
+
# @option :phrase [Boolean] Similar to :plain, but also adding an ILIKE filter to ensure that returned rows also include the exact phrase used.
|
25
|
+
#
|
26
|
+
# @option :rank [Boolean] Set to true to order by the rank, so that closer matches are returned first.
|
27
|
+
#
|
28
|
+
# @option :to_tsquery [Symbol] Can be set to :plain or :phrase to specify the function to use to convert the terms to a ts_query.
|
29
|
+
#
|
30
|
+
# @option :tsquery [Boolean] Specifies the terms argument is already a valid SQL expression returning a tsquery, and can be used directly in the query.
|
31
|
+
#
|
32
|
+
# @option :tsvector [Boolean] Specifies the cols argument is already a valid SQL expression returning a tsvector, and can be used directly in the query.
|
33
|
+
#
|
34
|
+
# @return [Relation]
|
35
|
+
#
|
36
|
+
# @see https://www.postgresql.org/docs/current/textsearch.html PostgreSQL docs
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
def full_text_search(*args, &block)
|
40
|
+
new dataset.__send__(__method__, *args, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
ROM.plugins do
|
50
|
+
adapter :sql do
|
51
|
+
register :pg_full_text_search, ROM::Plugins::Relation::SQL::Postgres::FullTextSearch, type: :relation
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
module Plugins
|
5
|
+
module Relation
|
6
|
+
module SQL
|
7
|
+
module Postgres
|
8
|
+
# PG-specific extensions which adds `Relation#stream` method
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
module Streaming
|
12
|
+
extend Notifications::Listener
|
13
|
+
|
14
|
+
class StreamingNotSupportedError < StandardError; end
|
15
|
+
|
16
|
+
subscribe("configuration.gateway.connected") do |opts|
|
17
|
+
conn = opts[:connection]
|
18
|
+
|
19
|
+
next unless conn.database_type.to_sym == :postgres
|
20
|
+
|
21
|
+
next if defined?(JRUBY_VERSION)
|
22
|
+
|
23
|
+
begin
|
24
|
+
require "sequel_pg"
|
25
|
+
rescue LoadError
|
26
|
+
raise StreamingNotSupportedError, "add sequel_pg to Gemfile to use pg_streaming"
|
27
|
+
end
|
28
|
+
|
29
|
+
unless Sequel::Postgres.supports_streaming?
|
30
|
+
raise StreamingNotSupportedError, "postgres version does not support streaming"
|
31
|
+
end
|
32
|
+
|
33
|
+
conn.extension(:pg_streaming)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.included(klass)
|
37
|
+
super
|
38
|
+
ROM::Relation::Graph.include(Combined)
|
39
|
+
ROM::Relation::Composite.include(Composite)
|
40
|
+
end
|
41
|
+
|
42
|
+
if defined?(JRUBY_VERSION)
|
43
|
+
# Allows you to stream returned rows one at a time, instead of
|
44
|
+
# collecting the entire result set in memory. Requires the `sequel_pg` gem
|
45
|
+
#
|
46
|
+
# @see https://github.com/jeremyevans/sequel_pg#streaming- sequel_pg docs
|
47
|
+
#
|
48
|
+
# @example
|
49
|
+
# posts.steam_each { |post| puts CSV.generate_line(post) }
|
50
|
+
#
|
51
|
+
# @return [Relation]
|
52
|
+
#
|
53
|
+
# @api publicY_VERSION
|
54
|
+
def stream_each
|
55
|
+
raise StreamingNotSupportedError, "not supported on jruby"
|
56
|
+
end
|
57
|
+
else
|
58
|
+
# Allows you to stream returned rows one at a time, instead of
|
59
|
+
# collecting the entire result set in memory. Requires the `sequel_pg` gem
|
60
|
+
#
|
61
|
+
# @see https://github.com/jeremyevans/sequel_pg#streaming- sequel_pg docs
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# posts.steam_each { |post| puts CSV.generate_line(post) }
|
65
|
+
#
|
66
|
+
# @return [Relation]
|
67
|
+
#
|
68
|
+
# @api public
|
69
|
+
def stream_each
|
70
|
+
return to_enum unless block_given?
|
71
|
+
|
72
|
+
ds = dataset.stream
|
73
|
+
|
74
|
+
if auto_map?
|
75
|
+
ds.each { |tuple| yield(mapper.([output_schema[tuple]]).first) }
|
76
|
+
else
|
77
|
+
ds.each { |tuple| yield(output_schema[tuple]) }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module Combined
|
82
|
+
def stream_each
|
83
|
+
raise StreamingNotSupportedError, "not supported on combined relations"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
module Composite
|
88
|
+
def stream_each
|
89
|
+
return to_enum unless block_given?
|
90
|
+
|
91
|
+
left.stream_each do |tuple|
|
92
|
+
yield right.call([tuple]).first
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
ROM.plugins do
|
105
|
+
adapter :sql do
|
106
|
+
register :pg_streaming, ROM::Plugins::Relation::SQL::Postgres::Streaming, type: :relation
|
107
|
+
end
|
108
|
+
end
|
data/lib/rom/sql.rb
CHANGED
data/lib/rom/sql/associations.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ROM
|
2
4
|
module SQL
|
3
5
|
module Associations
|
@@ -19,7 +21,7 @@ module ROM
|
|
19
21
|
def wrapped
|
20
22
|
new_target = view ? target.send(view) : target
|
21
23
|
to_wrap = self.class.allocate
|
22
|
-
to_wrap.send(:initialize, definition, options
|
24
|
+
to_wrap.send(:initialize, definition, **options, target: new_target)
|
23
25
|
to_wrap.wrap
|
24
26
|
end
|
25
27
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rom/associations/many_to_many'
|
2
4
|
require 'rom/sql/associations/core'
|
3
5
|
require 'rom/sql/associations/self_ref'
|
@@ -18,7 +20,7 @@ module ROM
|
|
18
20
|
if target != self.target
|
19
21
|
target.schema.merge(join_schema)
|
20
22
|
else
|
21
|
-
left.schema.project(*columns)
|
23
|
+
left.schema.uniq.project(*columns)
|
22
24
|
end
|
23
25
|
else
|
24
26
|
target_schema
|
data/lib/rom/sql/attribute.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'sequel/core'
|
2
4
|
require 'dry/core/cache'
|
3
5
|
|
@@ -19,16 +21,18 @@ module ROM
|
|
19
21
|
|
20
22
|
OPERATORS = %i[>= <= > <].freeze
|
21
23
|
NONSTANDARD_EQUALITY_VALUES = [true, false, nil].freeze
|
22
|
-
META_KEYS = %i
|
24
|
+
META_KEYS = %i[index foreign_key target sql_expr qualified].freeze
|
23
25
|
|
24
26
|
# Error raised when an attribute cannot be qualified
|
25
27
|
QualifyError = Class.new(StandardError)
|
26
28
|
|
27
29
|
extend Dry::Core::Cache
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
31
|
+
class << self
|
32
|
+
# @api private
|
33
|
+
def [](type, options = EMPTY_HASH)
|
34
|
+
fetch_or_store([type, options]) { new(type, **options) }
|
35
|
+
end
|
32
36
|
end
|
33
37
|
|
34
38
|
# Return a new attribute in its canonical form
|
@@ -63,6 +67,21 @@ module ROM
|
|
63
67
|
end
|
64
68
|
end
|
65
69
|
|
70
|
+
# Return a new attribute that is aliased and marked as qualified
|
71
|
+
#
|
72
|
+
# Intended to be used when passing attributes to `dataset#select`
|
73
|
+
#
|
74
|
+
# @return [SQL::Attribute]
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def qualified_projection(table_alias = nil)
|
78
|
+
if aliased?
|
79
|
+
qualified(table_alias).aliased(self.alias)
|
80
|
+
else
|
81
|
+
qualified(table_alias)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
66
85
|
# Return a new attribute marked as joined
|
67
86
|
#
|
68
87
|
# Whenever you join two schemas, the right schema's attribute
|
@@ -281,11 +300,11 @@ module ROM
|
|
281
300
|
# @api private
|
282
301
|
def to_sql_name
|
283
302
|
@_to_sql_name ||=
|
284
|
-
if qualified? &&
|
303
|
+
if qualified? && aliased_projection?
|
285
304
|
Sequel.qualify(table_name, name).as(self.alias)
|
286
305
|
elsif qualified?
|
287
306
|
Sequel.qualify(table_name, name)
|
288
|
-
elsif
|
307
|
+
elsif aliased_projection?
|
289
308
|
Sequel.as(name, self.alias)
|
290
309
|
else
|
291
310
|
Sequel[name]
|
@@ -318,7 +337,7 @@ module ROM
|
|
318
337
|
cleaned_meta = meta.reject { |k, _| META_KEYS.include?(k) }
|
319
338
|
type = optional? ? right : self.type
|
320
339
|
|
321
|
-
self.class.new(type.with(meta: cleaned_meta), options)
|
340
|
+
self.class.new(type.with(meta: cleaned_meta), **options)
|
322
341
|
end
|
323
342
|
|
324
343
|
# Wrap a value with the type, it allows using attribute and type specific methods
|