rom-sql 3.1.0 → 3.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +214 -101
  3. data/LICENSE +1 -1
  4. data/README.md +12 -54
  5. data/lib/rom-sql.rb +2 -0
  6. data/lib/rom/plugins/relation/sql/auto_restrictions.rb +2 -0
  7. data/lib/rom/plugins/relation/sql/instrumentation.rb +2 -0
  8. data/lib/rom/plugins/relation/sql/postgres/explain.rb +6 -7
  9. data/lib/rom/plugins/relation/sql/postgres/full_text_search.rb +53 -0
  10. data/lib/rom/plugins/relation/sql/postgres/streaming.rb +108 -0
  11. data/lib/rom/sql.rb +2 -0
  12. data/lib/rom/sql/associations.rb +2 -0
  13. data/lib/rom/sql/associations/core.rb +3 -1
  14. data/lib/rom/sql/associations/many_to_many.rb +3 -1
  15. data/lib/rom/sql/associations/many_to_one.rb +2 -0
  16. data/lib/rom/sql/associations/one_to_many.rb +2 -0
  17. data/lib/rom/sql/associations/one_to_one.rb +2 -0
  18. data/lib/rom/sql/associations/one_to_one_through.rb +2 -0
  19. data/lib/rom/sql/associations/self_ref.rb +2 -0
  20. data/lib/rom/sql/attribute.rb +26 -7
  21. data/lib/rom/sql/attribute_aliasing.rb +28 -0
  22. data/lib/rom/sql/commands.rb +2 -0
  23. data/lib/rom/sql/commands/create.rb +2 -0
  24. data/lib/rom/sql/commands/delete.rb +2 -0
  25. data/lib/rom/sql/commands/error_wrapper.rb +2 -0
  26. data/lib/rom/sql/commands/update.rb +2 -0
  27. data/lib/rom/sql/dsl.rb +11 -3
  28. data/lib/rom/sql/error.rb +2 -0
  29. data/lib/rom/sql/errors.rb +2 -0
  30. data/lib/rom/sql/extensions.rb +2 -0
  31. data/lib/rom/sql/extensions/active_support_notifications.rb +2 -0
  32. data/lib/rom/sql/extensions/mysql.rb +2 -0
  33. data/lib/rom/sql/extensions/mysql/type_builder.rb +2 -0
  34. data/lib/rom/sql/extensions/postgres.rb +3 -0
  35. data/lib/rom/sql/extensions/postgres/commands.rb +3 -1
  36. data/lib/rom/sql/extensions/postgres/type_builder.rb +6 -4
  37. data/lib/rom/sql/extensions/postgres/type_serializer.rb +2 -0
  38. data/lib/rom/sql/extensions/postgres/types.rb +2 -0
  39. data/lib/rom/sql/extensions/postgres/types/array.rb +6 -4
  40. data/lib/rom/sql/extensions/postgres/types/array_types.rb +3 -1
  41. data/lib/rom/sql/extensions/postgres/types/geometric.rb +2 -0
  42. data/lib/rom/sql/extensions/postgres/types/json.rb +13 -11
  43. data/lib/rom/sql/extensions/postgres/types/ltree.rb +25 -23
  44. data/lib/rom/sql/extensions/postgres/types/network.rb +2 -0
  45. data/lib/rom/sql/extensions/postgres/types/range.rb +2 -0
  46. data/lib/rom/sql/extensions/rails_log_subscriber.rb +2 -0
  47. data/lib/rom/sql/extensions/sqlite.rb +2 -0
  48. data/lib/rom/sql/extensions/sqlite/type_builder.rb +2 -0
  49. data/lib/rom/sql/extensions/sqlite/types.rb +2 -0
  50. data/lib/rom/sql/foreign_key.rb +3 -1
  51. data/lib/rom/sql/function.rb +14 -3
  52. data/lib/rom/sql/gateway.rb +6 -1
  53. data/lib/rom/sql/group_dsl.rb +2 -0
  54. data/lib/rom/sql/index.rb +2 -0
  55. data/lib/rom/sql/join_dsl.rb +2 -0
  56. data/lib/rom/sql/mapper_compiler.rb +2 -0
  57. data/lib/rom/sql/migration.rb +5 -3
  58. data/lib/rom/sql/migration/inline_runner.rb +2 -0
  59. data/lib/rom/sql/migration/migrator.rb +4 -2
  60. data/lib/rom/sql/migration/recorder.rb +2 -0
  61. data/lib/rom/sql/migration/runner.rb +4 -2
  62. data/lib/rom/sql/migration/schema_diff.rb +2 -0
  63. data/lib/rom/sql/migration/template.rb +2 -0
  64. data/lib/rom/sql/migration/writer.rb +12 -4
  65. data/lib/rom/sql/order_dsl.rb +2 -0
  66. data/lib/rom/sql/plugin/associates.rb +4 -2
  67. data/lib/rom/sql/plugin/nullify.rb +2 -0
  68. data/lib/rom/sql/plugin/pagination.rb +2 -0
  69. data/lib/rom/sql/plugins.rb +2 -0
  70. data/lib/rom/sql/projection_dsl.rb +3 -1
  71. data/lib/rom/sql/rake_task.rb +2 -0
  72. data/lib/rom/sql/relation.rb +3 -1
  73. data/lib/rom/sql/relation/reading.rb +6 -4
  74. data/lib/rom/sql/relation/writing.rb +2 -0
  75. data/lib/rom/sql/restriction_dsl.rb +9 -1
  76. data/lib/rom/sql/schema.rb +16 -2
  77. data/lib/rom/sql/schema/attributes_inferrer.rb +5 -3
  78. data/lib/rom/sql/schema/dsl.rb +3 -1
  79. data/lib/rom/sql/schema/index_dsl.rb +5 -2
  80. data/lib/rom/sql/schema/inferrer.rb +12 -8
  81. data/lib/rom/sql/schema/type_builder.rb +4 -2
  82. data/lib/rom/sql/spec/support.rb +5 -3
  83. data/lib/rom/sql/tasks/migration_tasks.rake +16 -11
  84. data/lib/rom/sql/transaction.rb +2 -0
  85. data/lib/rom/sql/type_dsl.rb +2 -0
  86. data/lib/rom/sql/type_extensions.rb +3 -1
  87. data/lib/rom/sql/type_serializer.rb +2 -0
  88. data/lib/rom/sql/types.rb +2 -0
  89. data/lib/rom/sql/version.rb +3 -1
  90. data/lib/rom/sql/wrap.rb +2 -0
  91. data/lib/rom/types/values.rb +2 -0
  92. metadata +25 -45
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2019 rom-rb team
3
+ Copyright (c) 2015-2020 rom-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
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
- [codeclimate]: https://codeclimate.com/github/rom-rb/rom-sql
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-942283.svg)][chat]
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
- [![Code Climate](https://codeclimate.com/github/rom-rb/rom-sql/badges/gpa.svg)][codeclimate]
12
- [![Test Coverage](https://codeclimate.com/github/rom-rb/rom-sql/badges/coverage.svg)][codeclimate]
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
- SQL support for [rom-rb](https://github.com/rom-rb/rom).
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
- ```bash
57
- docker-compose build gem
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
- ### Stopping the dependencies
20
+ ## Supported Ruby versions
62
21
 
63
- In order to stop the dependencies, execute:
22
+ This library officially supports the following Ruby versions:
64
23
 
65
- ```bash
66
- docker-compose down --remove-orphans --volumes
67
- ```
24
+ * MRI >= `2.5`
25
+ * jruby >= `9.2`
68
26
 
69
27
  ## License
70
28
 
data/lib/rom-sql.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/sql'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/support/notifications'
2
4
 
3
5
  module ROM
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ROM
2
4
  module Plugins
3
5
  module Relation
@@ -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| "#{ opt.to_s.upcase } #{ !!value }" }
28
- format_option = "FORMAT #{ format.to_s.upcase }"
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/equalizer'
2
4
 
3
5
  require 'rom/core'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/sql/associations/many_to_many'
2
4
  require 'rom/sql/associations/many_to_one'
3
5
  require 'rom/sql/associations/one_to_many'
@@ -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.merge(target: new_target))
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/associations/many_to_one'
2
4
  require 'rom/sql/associations/core'
3
5
  require 'rom/sql/associations/self_ref'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/associations/one_to_many'
2
4
  require 'rom/sql/associations/core'
3
5
  require 'rom/sql/associations/self_ref'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/sql/associations/one_to_many'
2
4
 
3
5
  module ROM
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/sql/associations/many_to_many'
2
4
 
3
5
  module ROM
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ROM
2
4
  module SQL
3
5
  module Associations
@@ -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(index foreign_key target sql_expr qualified).freeze
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
- # @api private
30
- def self.[](*args)
31
- fetch_or_store(args) { new(*args) }
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? && aliased?
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 aliased?
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