rom-sql 3.0.1 → 3.3.2

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +428 -297
  3. data/LICENSE +20 -0
  4. data/README.md +14 -56
  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 +97 -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 +10 -0
  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 +31 -22
  21. data/lib/rom/sql/attribute_aliasing.rb +88 -0
  22. data/lib/rom/sql/attribute_wrapping.rb +30 -0
  23. data/lib/rom/sql/commands.rb +2 -0
  24. data/lib/rom/sql/commands/create.rb +2 -0
  25. data/lib/rom/sql/commands/delete.rb +2 -0
  26. data/lib/rom/sql/commands/error_wrapper.rb +2 -0
  27. data/lib/rom/sql/commands/update.rb +2 -0
  28. data/lib/rom/sql/dsl.rb +11 -3
  29. data/lib/rom/sql/error.rb +2 -0
  30. data/lib/rom/sql/errors.rb +2 -0
  31. data/lib/rom/sql/extensions.rb +2 -0
  32. data/lib/rom/sql/extensions/active_support_notifications.rb +2 -0
  33. data/lib/rom/sql/extensions/mysql.rb +2 -0
  34. data/lib/rom/sql/extensions/mysql/type_builder.rb +2 -0
  35. data/lib/rom/sql/extensions/postgres.rb +3 -0
  36. data/lib/rom/sql/extensions/postgres/commands.rb +3 -1
  37. data/lib/rom/sql/extensions/postgres/type_builder.rb +6 -4
  38. data/lib/rom/sql/extensions/postgres/type_serializer.rb +2 -0
  39. data/lib/rom/sql/extensions/postgres/types.rb +2 -0
  40. data/lib/rom/sql/extensions/postgres/types/array.rb +7 -6
  41. data/lib/rom/sql/extensions/postgres/types/array_types.rb +3 -1
  42. data/lib/rom/sql/extensions/postgres/types/geometric.rb +2 -0
  43. data/lib/rom/sql/extensions/postgres/types/json.rb +76 -19
  44. data/lib/rom/sql/extensions/postgres/types/ltree.rb +25 -23
  45. data/lib/rom/sql/extensions/postgres/types/network.rb +2 -0
  46. data/lib/rom/sql/extensions/postgres/types/range.rb +2 -0
  47. data/lib/rom/sql/extensions/rails_log_subscriber.rb +2 -0
  48. data/lib/rom/sql/extensions/sqlite.rb +2 -0
  49. data/lib/rom/sql/extensions/sqlite/type_builder.rb +2 -0
  50. data/lib/rom/sql/extensions/sqlite/types.rb +2 -0
  51. data/lib/rom/sql/foreign_key.rb +3 -1
  52. data/lib/rom/sql/function.rb +38 -6
  53. data/lib/rom/sql/gateway.rb +9 -1
  54. data/lib/rom/sql/group_dsl.rb +2 -0
  55. data/lib/rom/sql/index.rb +2 -0
  56. data/lib/rom/sql/join_dsl.rb +2 -0
  57. data/lib/rom/sql/mapper_compiler.rb +12 -1
  58. data/lib/rom/sql/migration.rb +5 -3
  59. data/lib/rom/sql/migration/inline_runner.rb +2 -0
  60. data/lib/rom/sql/migration/migrator.rb +4 -2
  61. data/lib/rom/sql/migration/recorder.rb +2 -0
  62. data/lib/rom/sql/migration/runner.rb +4 -2
  63. data/lib/rom/sql/migration/schema_diff.rb +2 -0
  64. data/lib/rom/sql/migration/template.rb +2 -0
  65. data/lib/rom/sql/migration/writer.rb +12 -4
  66. data/lib/rom/sql/order_dsl.rb +2 -0
  67. data/lib/rom/sql/plugin/associates.rb +4 -2
  68. data/lib/rom/sql/plugin/nullify.rb +37 -0
  69. data/lib/rom/sql/plugin/pagination.rb +2 -0
  70. data/lib/rom/sql/plugins.rb +4 -0
  71. data/lib/rom/sql/projection_dsl.rb +3 -1
  72. data/lib/rom/sql/rake_task.rb +2 -0
  73. data/lib/rom/sql/relation.rb +3 -1
  74. data/lib/rom/sql/relation/reading.rb +32 -6
  75. data/lib/rom/sql/relation/writing.rb +2 -0
  76. data/lib/rom/sql/restriction_dsl.rb +9 -1
  77. data/lib/rom/sql/schema.rb +16 -2
  78. data/lib/rom/sql/schema/attributes_inferrer.rb +5 -3
  79. data/lib/rom/sql/schema/dsl.rb +3 -1
  80. data/lib/rom/sql/schema/index_dsl.rb +5 -2
  81. data/lib/rom/sql/schema/inferrer.rb +12 -8
  82. data/lib/rom/sql/schema/type_builder.rb +4 -2
  83. data/lib/rom/sql/spec/support.rb +5 -3
  84. data/lib/rom/sql/tasks/migration_tasks.rake +16 -11
  85. data/lib/rom/sql/transaction.rb +2 -0
  86. data/lib/rom/sql/type_dsl.rb +2 -0
  87. data/lib/rom/sql/type_extensions.rb +4 -4
  88. data/lib/rom/sql/type_serializer.rb +2 -0
  89. data/lib/rom/sql/types.rb +2 -0
  90. data/lib/rom/sql/version.rb +3 -1
  91. data/lib/rom/sql/wrap.rb +2 -0
  92. data/lib/rom/types/values.rb +2 -0
  93. metadata +34 -32
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/sql/dsl'
2
4
 
3
5
  module ROM
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/sql/associations'
2
4
 
3
5
  module ROM
@@ -48,7 +50,7 @@ module ROM
48
50
  # @see ROM::Command::ClassInterface.build
49
51
  #
50
52
  # @api public
51
- def build(relation, options = EMPTY_HASH)
53
+ def build(relation, **options)
52
54
  command = super
53
55
 
54
56
  configured_assocs = command.configured_associations
@@ -146,7 +148,7 @@ module ROM
146
148
  def with_association(name, opts = EMPTY_HASH)
147
149
  self.class.build(
148
150
  relation,
149
- { **options, associations: associations.merge(name => opts) }
151
+ **options, associations: associations.merge(name => opts)
150
152
  )
151
153
  end
152
154
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module SQL
5
+ module Plugin
6
+ # Nullify relation by
7
+ #
8
+ # @api public
9
+ module Nullify
10
+ if defined? JRUBY_VERSION
11
+ # Returns a relation that will never issue a query to the database. It
12
+ # implements the null object pattern for relations.
13
+ # Dataset#nullify doesn't work on JRuby, hence we fall back to SQL
14
+ #
15
+ # @api public
16
+ def nullify
17
+ where { `1 = 0` }
18
+ end
19
+ else
20
+ # Returns a relation that will never issue a query to the database. It
21
+ # implements the null object pattern for relations.
22
+ #
23
+ # @see http://sequel.jeremyevans.net/rdoc-plugins/files/lib/sequel/extensions/null_dataset_rb.html
24
+ # @example result will always be empty, regardless if records exists
25
+ # users.where(name: 'Alice').nullify
26
+ #
27
+ # @return [SQL::Relation]
28
+ #
29
+ # @api public
30
+ def nullify
31
+ new(dataset.where { `1 = 0` }.__send__(__method__))
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/initializer'
2
4
 
3
5
  module ROM
@@ -1,11 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/plugins/relation/sql/instrumentation'
2
4
  require 'rom/plugins/relation/sql/auto_restrictions'
3
5
 
4
6
  require 'rom/sql/plugin/associates'
7
+ require 'rom/sql/plugin/nullify'
5
8
  require 'rom/sql/plugin/pagination'
6
9
 
7
10
  ROM.plugins do
8
11
  adapter :sql do
12
+ register :nullify, ROM::SQL::Plugin::Nullify, type: :relation
9
13
  register :pagination, ROM::SQL::Plugin::Pagination, type: :relation
10
14
  register :associates, ROM::SQL::Plugin::Associates, type: :command
11
15
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/sql/dsl'
2
4
  require 'rom/sql/function'
3
5
 
@@ -29,7 +31,7 @@ module ROM
29
31
  # users.select { function(:count, :id).as(:total) }
30
32
  #
31
33
  # @param [Symbol] name SQL function
32
- # @param [Symbol] attr
34
+ # @param [Symbol] attrs
33
35
  #
34
36
  # @return [Rom::SQL::Function]
35
37
  #
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rake'
2
4
  load 'rom/sql/tasks/migration_tasks.rake'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/sql/types'
2
4
  require 'rom/sql/schema'
3
5
  require 'rom/sql/attribute'
@@ -35,7 +37,7 @@ module ROM
35
37
  table = opts[:from].first
36
38
 
37
39
  if db.table_exists?(table)
38
- select(*schema.map(&:qualified)).order(*schema.project(*schema.primary_key_names).qualified)
40
+ select(*schema.qualified_projection).order(*schema.project(*schema.primary_key_names).qualified)
39
41
  else
40
42
  self
41
43
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/support/inflector'
2
4
  require 'rom/sql/join_dsl'
3
5
 
@@ -807,7 +809,16 @@ module ROM
807
809
  #
808
810
  # @api public
809
811
  def union(relation, options = EMPTY_HASH, &block)
810
- new(dataset.__send__(__method__, relation.dataset, options, &block))
812
+ # We use the original relation name here if both relations have the
813
+ # same name. This makes it so if the user at some point references
814
+ # the relation directly by name later on things won't break in
815
+ # confusing ways.
816
+ same_relation = name == relation.name
817
+ alias_name = same_relation ? name : "#{name.to_sym}__#{relation.name.to_sym}"
818
+ opts = { alias: alias_name.to_sym, **options }
819
+
820
+ new_schema = schema.qualified(opts[:alias])
821
+ new_schema.(new(dataset.__send__(__method__, relation.dataset, opts, &block)))
811
822
  end
812
823
 
813
824
  # Checks whether a relation has at least one tuple
@@ -882,8 +893,8 @@ module ROM
882
893
  # @yieldparam relation [Array]
883
894
  #
884
895
  # @api public
885
- def lock(options = EMPTY_HASH, &block)
886
- clause = lock_clause(options)
896
+ def lock(**options, &block)
897
+ clause = lock_clause(**options)
887
898
 
888
899
  if block
889
900
  transaction do
@@ -986,7 +997,7 @@ module ROM
986
997
  # @return [SQL::Attribute]
987
998
  def query
988
999
  attr = schema.to_a[0]
989
- subquery = schema.project(attr).(self).dataset.unordered
1000
+ subquery = schema.project(attr).(self).dataset
990
1001
  SQL::Attribute[attr.type].meta(sql_expr: subquery)
991
1002
  end
992
1003
 
@@ -1002,6 +1013,21 @@ module ROM
1002
1013
  new(dataset.__send__(__method__))
1003
1014
  end
1004
1015
 
1016
+ # Wrap other relations using association names
1017
+ #
1018
+ # @example
1019
+ # tasks.wrap(:owner)
1020
+ #
1021
+ # @param [Array<Symbol>] names A list with association identifiers
1022
+ #
1023
+ # @return [Wrap]
1024
+ #
1025
+ # @api public
1026
+ def wrap(*names)
1027
+ others = names.map { |name| associations[name].wrapped }
1028
+ wrap_around(*others)
1029
+ end
1030
+
1005
1031
  private
1006
1032
 
1007
1033
  # Build a locking clause
@@ -1012,7 +1038,7 @@ module ROM
1012
1038
  stmt << ' OF ' << Array(of).join(', ') if of
1013
1039
 
1014
1040
  if skip_locked
1015
- raise ArgumentError, "SKIP LOCKED cannot be used with (NO)WAIT clause" if !wait.nil?
1041
+ raise ArgumentError, 'SKIP LOCKED cannot be used with (NO)WAIT clause' if !wait.nil?
1016
1042
 
1017
1043
  stmt << ' SKIP LOCKED'
1018
1044
  else
@@ -1068,7 +1094,7 @@ module ROM
1068
1094
  join_opts = EMPTY_HASH
1069
1095
  end
1070
1096
 
1071
- 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))
1072
1098
  else
1073
1099
  associations[other.name.key].join(type, self, other)
1074
1100
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ROM
2
4
  module SQL
3
5
  class Relation < ROM::Relation
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/sql/dsl'
2
4
 
3
5
  module ROM
@@ -6,7 +8,13 @@ module ROM
6
8
  class RestrictionDSL < DSL
7
9
  # @api private
8
10
  def call(&block)
9
- instance_exec(select_relations(block.parameters), &block)
11
+ arg, kwargs = select_relations(block.parameters)
12
+
13
+ if kwargs.nil?
14
+ instance_exec(arg, &block)
15
+ else
16
+ instance_exec(**kwargs, &block)
17
+ end
10
18
  end
11
19
 
12
20
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/schema'
2
4
 
3
5
  require 'rom/sql/schema/dsl'
@@ -65,6 +67,18 @@ module ROM
65
67
  new(map { |attr| attr.qualified(table_alias) })
66
68
  end
67
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
+
68
82
  # Project a schema
69
83
  #
70
84
  # @see ROM::Schema#project
@@ -127,7 +141,7 @@ module ROM
127
141
  #
128
142
  # @api public
129
143
  def call(relation)
130
- relation.new(relation.dataset.select(*self), schema: self)
144
+ relation.new(relation.dataset.select(*self.qualified_projection), schema: self)
131
145
  end
132
146
 
133
147
  # Return an empty schema
@@ -142,7 +156,7 @@ module ROM
142
156
  # Finalize all attributes by qualifying them and initializing primary key names
143
157
  #
144
158
  # @api private
145
- def finalize_attributes!(options = EMPTY_HASH)
159
+ def finalize_attributes!(**options)
146
160
  super do
147
161
  @attributes = map(&:qualified)
148
162
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dry/core/class_attributes'
2
4
 
3
5
  module ROM
@@ -22,8 +24,8 @@ module ROM
22
24
 
23
25
  columns = filter_columns(gateway.connection.schema(dataset))
24
26
 
25
- inferred = columns.map do |(name, definition)|
26
- type = type_builder.(definition)
27
+ inferred = columns.map do |name, definition|
28
+ type = type_builder.(**definition)
27
29
 
28
30
  attr_class.new(type.meta(source: schema.name), name: name) if type
29
31
  end.compact
@@ -42,7 +44,7 @@ module ROM
42
44
 
43
45
  # @api private
44
46
  def filter_columns(schema)
45
- schema.reject { |(_, definition)| definition[:db_type] == CONSTRAINT_DB_TYPE }
47
+ schema.reject { |_, definition| definition[:db_type] == CONSTRAINT_DB_TYPE }
46
48
  end
47
49
  end
48
50
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rom/sql/schema/index_dsl'
2
4
 
3
5
  module ROM
@@ -15,7 +17,7 @@ module ROM
15
17
  #
16
18
  # @api public
17
19
  def indexes(&block)
18
- @index_dsl = IndexDSL.new(options, &block)
20
+ @index_dsl = IndexDSL.new(**options, &block)
19
21
  end
20
22
 
21
23
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module ROM
@@ -19,6 +21,7 @@ module ROM
19
21
 
20
22
  instance_exec(&block)
21
23
  end
24
+ ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
22
25
 
23
26
  # @api public
24
27
  def index(*attributes, **options)
@@ -28,7 +31,7 @@ module ROM
28
31
  # @api private
29
32
  def call(schema_name, attrs)
30
33
  attributes = attrs.map do |attr|
31
- attr_class.new(attr[:type], attr[:options] || {}).meta(source: schema_name)
34
+ attr_class.new(attr[:type], **(attr[:options] || {})).meta(source: schema_name)
32
35
  end
33
36
 
34
37
  registry.map { |attr_names, options|
@@ -44,7 +47,7 @@ module ROM
44
47
  attributes.find { |a| a.name == name }.unwrap
45
48
  end
46
49
 
47
- Index.new(index_attributes, options)
50
+ Index.new(index_attributes, **options)
48
51
  end
49
52
  end
50
53
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  require 'rom/sql/schema/type_builder'
@@ -32,9 +34,9 @@ module ROM
32
34
  # @api private
33
35
  def call(schema, gateway)
34
36
  if enabled?
35
- infer_from_database(gateway, schema, super)
37
+ infer_from_database(gateway, schema, **super)
36
38
  else
37
- infer_from_attributes(gateway, schema, super)
39
+ infer_from_attributes(gateway, schema, **super)
38
40
  end
39
41
  rescue Sequel::Error => error
40
42
  on_error(schema.name, error)
@@ -54,7 +56,7 @@ module ROM
54
56
  end
55
57
 
56
58
  # @api private
57
- def infer_from_attributes(gateway, schema, attributes:, **rest)
59
+ def infer_from_attributes(_gateway, schema, attributes:, **rest)
58
60
  indexes = schema.indexes | indexes_from_attributes(attributes)
59
61
  foreign_keys = foreign_keys_from_attributes(attributes)
60
62
 
@@ -69,7 +71,8 @@ module ROM
69
71
  if gateway.connection.respond_to?(:indexes)
70
72
  dataset = schema.name.dataset
71
73
 
72
- gateway.connection.indexes(dataset).map { |index_name, columns:, unique:, **rest|
74
+ gateway.connection.indexes(dataset).map { |index_name, definition|
75
+ columns, unique = definition.values_at(:columns, :unique)
73
76
  attrs = columns.map { |name| attributes[name] }
74
77
 
75
78
  SQL::Index.new(attrs, name: index_name, unique: unique)
@@ -83,7 +86,8 @@ module ROM
83
86
  def foreign_keys_from_database(gateway, schema, attributes)
84
87
  dataset = schema.name.dataset
85
88
 
86
- gateway.connection.foreign_key_list(dataset).map { |columns:, table:, key:, **rest|
89
+ gateway.connection.foreign_key_list(dataset).map { |definition|
90
+ columns, table, key = definition.values_at(:columns, :table, :key)
87
91
  attrs = columns.map { |name| attributes[name] }
88
92
 
89
93
  SQL::ForeignKey.new(attrs, table, parent_keys: key)
@@ -147,9 +151,9 @@ module ROM
147
151
  raise e
148
152
  elsif !silent
149
153
  warn "[#{dataset}] failed to infer schema. " \
150
- "Make sure tables exist before ROM container is set up. " \
151
- "This may also happen when your migration tasks load ROM container, " \
152
- "which is not needed for migrations as only the connection is required " \
154
+ 'Make sure tables exist before ROM container is set up. ' \
155
+ 'This may also happen when your migration tasks load ROM container, ' \
156
+ 'which is not needed for migrations as only the connection is required ' \
153
157
  "(#{e.message})"
154
158
  end
155
159
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ROM
2
4
  module SQL
3
5
  class Schema
@@ -37,9 +39,9 @@ module ROM
37
39
 
38
40
  def call(primary_key:, db_type:, type:, allow_null:, **rest)
39
41
  if primary_key
40
- map_pk_type(type, db_type, rest)
42
+ map_pk_type(type, db_type, **rest)
41
43
  else
42
- mapped_type = map_type(type, db_type, rest)
44
+ mapped_type = map_type(type, db_type, **rest)
43
45
 
44
46
  if mapped_type
45
47
  read_type = mapped_type.meta[:read]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  if defined? JRUBY_VERSION
2
4
  USING_JRUBY = true
3
5
  else
@@ -5,15 +7,15 @@ else
5
7
  end
6
8
 
7
9
  if USING_JRUBY
8
- SEQUEL_TEST_DB_URI = "jdbc:sqlite::memory:"
10
+ SEQUEL_TEST_DB_URI = 'jdbc:sqlite::memory:'
9
11
  else
10
- SEQUEL_TEST_DB_URI = "sqlite::memory"
12
+ SEQUEL_TEST_DB_URI = 'sqlite::memory'
11
13
  end
12
14
 
13
15
  DB = Sequel.connect(SEQUEL_TEST_DB_URI)
14
16
 
15
17
  def seed(db = DB)
16
- db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name STRING)")
18
+ db.run('CREATE TABLE users (id INTEGER PRIMARY KEY, name STRING)')
17
19
 
18
20
  db[:users].insert(id: 1, name: 'Jane')
19
21
  db[:users].insert(id: 2, name: 'Joe')