rom-sql 3.2.0 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +176 -101
- data/LICENSE +1 -1
- data/README.md +12 -54
- data/lib/rom/plugins/relation/sql/postgres/full_text_search.rb +53 -0
- data/lib/rom/plugins/relation/sql/postgres/streaming.rb +97 -0
- data/lib/rom/sql/associations/many_to_many.rb +1 -1
- data/lib/rom/sql/attribute.rb +17 -2
- data/lib/rom/sql/attribute_aliasing.rb +28 -0
- data/lib/rom/sql/extensions/postgres.rb +2 -0
- data/lib/rom/sql/extensions/postgres/commands.rb +1 -1
- data/lib/rom/sql/function.rb +11 -2
- data/lib/rom/sql/gateway.rb +3 -0
- data/lib/rom/sql/projection_dsl.rb +1 -1
- data/lib/rom/sql/relation.rb +1 -1
- data/lib/rom/sql/relation/reading.rb +1 -1
- data/lib/rom/sql/schema.rb +13 -1
- data/lib/rom/sql/tasks/migration_tasks.rake +1 -1
- data/lib/rom/sql/type_extensions.rb +1 -1
- data/lib/rom/sql/version.rb +1 -1
- metadata +21 -41
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
|
|
@@ -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,97 @@
|
|
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
|
+
end
|
40
|
+
|
41
|
+
if defined?(JRUBY_VERSION)
|
42
|
+
# Allows you to stream returned rows one at a time, instead of
|
43
|
+
# collecting the entire result set in memory. Requires the `sequel_pg` gem
|
44
|
+
#
|
45
|
+
# @see https://github.com/jeremyevans/sequel_pg#streaming- sequel_pg docs
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# posts.steam_each { |post| puts CSV.generate_line(post) }
|
49
|
+
#
|
50
|
+
# @return [Relation]
|
51
|
+
#
|
52
|
+
# @api publicY_VERSION
|
53
|
+
def stream_each
|
54
|
+
raise StreamingNotSupportedError, "not supported on jruby"
|
55
|
+
end
|
56
|
+
else
|
57
|
+
# Allows you to stream returned rows one at a time, instead of
|
58
|
+
# collecting the entire result set in memory. Requires the `sequel_pg` gem
|
59
|
+
#
|
60
|
+
# @see https://github.com/jeremyevans/sequel_pg#streaming- sequel_pg docs
|
61
|
+
#
|
62
|
+
# @example
|
63
|
+
# posts.steam_each { |post| puts CSV.generate_line(post) }
|
64
|
+
#
|
65
|
+
# @return [Relation]
|
66
|
+
#
|
67
|
+
# @api public
|
68
|
+
def stream_each
|
69
|
+
return to_enum unless block_given?
|
70
|
+
|
71
|
+
ds = dataset.stream
|
72
|
+
|
73
|
+
if auto_map?
|
74
|
+
ds.each { |tuple| yield(mapper.([output_schema[tuple]]).first) }
|
75
|
+
else
|
76
|
+
ds.each { |tuple| yield(output_schema[tuple]) }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module Combined
|
81
|
+
def stream_each
|
82
|
+
raise StreamingNotSupportedError, "not supported on combined relations"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
ROM.plugins do
|
94
|
+
adapter :sql do
|
95
|
+
register :pg_streaming, ROM::Plugins::Relation::SQL::Postgres::Streaming, type: :relation
|
96
|
+
end
|
97
|
+
end
|
data/lib/rom/sql/attribute.rb
CHANGED
@@ -67,6 +67,21 @@ module ROM
|
|
67
67
|
end
|
68
68
|
end
|
69
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
|
+
|
70
85
|
# Return a new attribute marked as joined
|
71
86
|
#
|
72
87
|
# Whenever you join two schemas, the right schema's attribute
|
@@ -285,11 +300,11 @@ module ROM
|
|
285
300
|
# @api private
|
286
301
|
def to_sql_name
|
287
302
|
@_to_sql_name ||=
|
288
|
-
if qualified? &&
|
303
|
+
if qualified? && aliased_projection?
|
289
304
|
Sequel.qualify(table_name, name).as(self.alias)
|
290
305
|
elsif qualified?
|
291
306
|
Sequel.qualify(table_name, name)
|
292
|
-
elsif
|
307
|
+
elsif aliased_projection?
|
293
308
|
Sequel.as(name, self.alias)
|
294
309
|
else
|
295
310
|
Sequel[name]
|
@@ -21,6 +21,34 @@ module ROM
|
|
21
21
|
end
|
22
22
|
alias as aliased
|
23
23
|
|
24
|
+
|
25
|
+
# Return true if this attribute is an aliased projection
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# class Tasks < ROM::Relation[:memory]
|
29
|
+
# schema do
|
30
|
+
# attribute :user_id, Types::Integer, alias: :id
|
31
|
+
# attribute :name, Types::String
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# Users.schema[:user_id].aliased?
|
36
|
+
# # => true
|
37
|
+
# Users.schema[:user_id].aliased_projection?
|
38
|
+
# # => false
|
39
|
+
#
|
40
|
+
# Users.schema[:user_id].qualified_projection.aliased?
|
41
|
+
# # => true
|
42
|
+
# Users.schema[:user_id].qualified_projection.aliased_projection?
|
43
|
+
# # => true
|
44
|
+
#
|
45
|
+
# @return [TrueClass,FalseClass]
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
def aliased_projection?
|
49
|
+
self.meta[:sql_expr].is_a?(Sequel::SQL::AliasedExpression)
|
50
|
+
end
|
51
|
+
|
24
52
|
private
|
25
53
|
|
26
54
|
# @api private
|
@@ -5,3 +5,5 @@ require 'rom/sql/extensions/postgres/types'
|
|
5
5
|
require 'rom/sql/extensions/postgres/type_builder'
|
6
6
|
require 'rom/sql/extensions/postgres/type_serializer'
|
7
7
|
require 'rom/plugins/relation/sql/postgres/explain'
|
8
|
+
require 'rom/plugins/relation/sql/postgres/full_text_search'
|
9
|
+
require "rom/plugins/relation/sql/postgres/streaming"
|
data/lib/rom/sql/function.rb
CHANGED
@@ -78,6 +78,15 @@ module ROM
|
|
78
78
|
)
|
79
79
|
end
|
80
80
|
|
81
|
+
# @see Attribute#qualified_projection
|
82
|
+
#
|
83
|
+
# @api private
|
84
|
+
def qualified_projection(table_alias = nil)
|
85
|
+
meta(
|
86
|
+
func: ::Sequel::SQL::Function.new(func.name, *func.args.map { |arg| arg.respond_to?(:qualified_projection) ? arg.qualified_projection(table_alias) : arg })
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
81
90
|
# @see Attribute#qualified?
|
82
91
|
#
|
83
92
|
# @api private
|
@@ -186,7 +195,7 @@ module ROM
|
|
186
195
|
# users.project { integer::count(:id).filter(name.is("Jack")).as(:jacks) }.order(nil)
|
187
196
|
# users.project { integer::count(:id).filter { name.is("John") }).as(:johns) }.order(nil)
|
188
197
|
#
|
189
|
-
# @param [Hash,SQL::Attribute] Conditions
|
198
|
+
# @param condition [Hash,SQL::Attribute] Conditions
|
190
199
|
# @yield [block] A block with restrictions
|
191
200
|
#
|
192
201
|
# @return [SQL::Function]
|
@@ -211,7 +220,7 @@ module ROM
|
|
211
220
|
# @example
|
212
221
|
# households.project { fload::percentile_cont(0.5).within_group(income).as(:percentile) }
|
213
222
|
#
|
214
|
-
# @param [Array] A list of expressions for sorting within a group
|
223
|
+
# @param args [Array] A list of expressions for sorting within a group
|
215
224
|
# @yield [block] A block for getting the expressions using the Order DSL
|
216
225
|
#
|
217
226
|
# @return [SQL::Function]
|
data/lib/rom/sql/gateway.rb
CHANGED
@@ -81,6 +81,7 @@ module ROM
|
|
81
81
|
def initialize(uri, options = EMPTY_HASH)
|
82
82
|
@connection = connect(uri, options)
|
83
83
|
load_extensions(Array(options[:extensions]))
|
84
|
+
Notifications.trigger("configuration.gateway.connected", connection: @connection)
|
84
85
|
|
85
86
|
@options = options
|
86
87
|
|
@@ -247,4 +248,6 @@ module ROM
|
|
247
248
|
end
|
248
249
|
end
|
249
250
|
end
|
251
|
+
|
252
|
+
Configuration.register_event("configuration.gateway.connected")
|
250
253
|
end
|
data/lib/rom/sql/relation.rb
CHANGED
@@ -37,7 +37,7 @@ module ROM
|
|
37
37
|
table = opts[:from].first
|
38
38
|
|
39
39
|
if db.table_exists?(table)
|
40
|
-
select(*schema.
|
40
|
+
select(*schema.qualified_projection).order(*schema.project(*schema.primary_key_names).qualified)
|
41
41
|
else
|
42
42
|
self
|
43
43
|
end
|
@@ -1094,7 +1094,7 @@ module ROM
|
|
1094
1094
|
join_opts = EMPTY_HASH
|
1095
1095
|
end
|
1096
1096
|
|
1097
|
-
new(dataset.__send__(type, other.name.to_sym, join_cond, join_opts))
|
1097
|
+
new(dataset.__send__(type, other.name.dataset.to_sym, join_cond, join_opts))
|
1098
1098
|
else
|
1099
1099
|
associations[other.name.key].join(type, self, other)
|
1100
1100
|
end
|
data/lib/rom/sql/schema.rb
CHANGED
@@ -67,6 +67,18 @@ module ROM
|
|
67
67
|
new(map { |attr| attr.qualified(table_alias) })
|
68
68
|
end
|
69
69
|
|
70
|
+
# Return a new schema with attributes that are aliased
|
71
|
+
# and marked as qualified
|
72
|
+
#
|
73
|
+
# Intended to be used when passing attributes to `dataset#select`
|
74
|
+
#
|
75
|
+
# @return [Schema]
|
76
|
+
#
|
77
|
+
# @api public
|
78
|
+
def qualified_projection(table_alias = nil)
|
79
|
+
new(map { |attr| attr.qualified_projection(table_alias) })
|
80
|
+
end
|
81
|
+
|
70
82
|
# Project a schema
|
71
83
|
#
|
72
84
|
# @see ROM::Schema#project
|
@@ -129,7 +141,7 @@ module ROM
|
|
129
141
|
#
|
130
142
|
# @api public
|
131
143
|
def call(relation)
|
132
|
-
relation.new(relation.dataset.select(*self), schema: self)
|
144
|
+
relation.new(relation.dataset.select(*self.qualified_projection), schema: self)
|
133
145
|
end
|
134
146
|
|
135
147
|
# Return an empty schema
|