rom-sql 0.9.1 → 1.0.0.beta1

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/.travis.yml +1 -1
  3. data/CHANGELOG.md +32 -0
  4. data/Gemfile +4 -1
  5. data/lib/rom/plugins/relation/sql/auto_wrap.rb +1 -3
  6. data/lib/rom/sql/association.rb +33 -14
  7. data/lib/rom/sql/association/many_to_many.rb +17 -10
  8. data/lib/rom/sql/association/many_to_one.rb +29 -13
  9. data/lib/rom/sql/association/name.rb +12 -4
  10. data/lib/rom/sql/association/one_to_many.rb +21 -10
  11. data/lib/rom/sql/commands/create.rb +0 -1
  12. data/lib/rom/sql/commands/update.rb +1 -49
  13. data/lib/rom/sql/dsl.rb +29 -0
  14. data/lib/rom/sql/expression.rb +26 -0
  15. data/lib/rom/sql/function.rb +23 -0
  16. data/lib/rom/sql/gateway.rb +24 -9
  17. data/lib/rom/sql/migration.rb +6 -7
  18. data/lib/rom/sql/migration/migrator.rb +7 -8
  19. data/lib/rom/sql/order_dsl.rb +20 -0
  20. data/lib/rom/sql/plugin/associates.rb +58 -45
  21. data/lib/rom/sql/plugin/pagination.rb +8 -11
  22. data/lib/rom/sql/plugins.rb +0 -2
  23. data/lib/rom/sql/projection_dsl.rb +41 -0
  24. data/lib/rom/sql/qualified_attribute.rb +2 -2
  25. data/lib/rom/sql/relation.rb +35 -67
  26. data/lib/rom/sql/relation/reading.rb +77 -25
  27. data/lib/rom/sql/restriction_dsl.rb +24 -0
  28. data/lib/rom/sql/schema.rb +73 -7
  29. data/lib/rom/sql/schema/associations_dsl.rb +4 -3
  30. data/lib/rom/sql/schema/dsl.rb +5 -2
  31. data/lib/rom/sql/schema/inferrer.rb +21 -11
  32. data/lib/rom/sql/transaction.rb +19 -0
  33. data/lib/rom/sql/type.rb +76 -0
  34. data/lib/rom/sql/version.rb +1 -1
  35. data/rom-sql.gemspec +3 -4
  36. data/spec/extensions/postgres/inferrer_spec.rb +19 -9
  37. data/spec/integration/association/many_to_many/custom_fks_spec.rb +73 -0
  38. data/spec/integration/association/many_to_many/from_view_spec.rb +81 -0
  39. data/spec/integration/association/many_to_many_spec.rb +2 -2
  40. data/spec/integration/association/many_to_one/custom_fks_spec.rb +59 -0
  41. data/spec/integration/association/many_to_one/from_view_spec.rb +74 -0
  42. data/spec/integration/association/many_to_one/self_ref_spec.rb +51 -0
  43. data/spec/integration/association/many_to_one_spec.rb +4 -2
  44. data/spec/integration/association/one_to_many/custom_fks_spec.rb +48 -0
  45. data/spec/integration/association/one_to_many/from_view_spec.rb +57 -0
  46. data/spec/integration/association/one_to_many/self_ref_spec.rb +52 -0
  47. data/spec/integration/association/one_to_many_spec.rb +1 -1
  48. data/spec/integration/association/one_to_one_spec.rb +1 -1
  49. data/spec/integration/association/one_to_one_through_spec.rb +2 -2
  50. data/spec/integration/commands/create_spec.rb +11 -27
  51. data/spec/integration/commands/update_spec.rb +54 -109
  52. data/spec/integration/gateway_spec.rb +31 -17
  53. data/spec/integration/plugins/associates_spec.rb +27 -0
  54. data/spec/integration/plugins/auto_wrap_spec.rb +8 -8
  55. data/spec/integration/schema/call_spec.rb +24 -0
  56. data/spec/integration/schema/prefix_spec.rb +18 -0
  57. data/spec/integration/schema/qualified_spec.rb +18 -0
  58. data/spec/integration/schema/rename_spec.rb +23 -0
  59. data/spec/integration/schema/view_spec.rb +29 -0
  60. data/spec/integration/schema_inference_spec.rb +31 -14
  61. data/spec/spec_helper.rb +2 -2
  62. data/spec/support/helpers.rb +7 -0
  63. data/spec/unit/gateway_spec.rb +5 -4
  64. data/spec/unit/projection_dsl_spec.rb +54 -0
  65. data/spec/unit/relation/dataset_spec.rb +3 -3
  66. data/spec/unit/relation/distinct_spec.rb +8 -7
  67. data/spec/unit/relation/exclude_spec.rb +2 -4
  68. data/spec/unit/relation/having_spec.rb +6 -4
  69. data/spec/unit/relation/inner_join_spec.rb +47 -2
  70. data/spec/unit/relation/invert_spec.rb +2 -3
  71. data/spec/unit/relation/left_join_spec.rb +44 -3
  72. data/spec/unit/relation/order_spec.rb +40 -0
  73. data/spec/unit/relation/prefix_spec.rb +2 -0
  74. data/spec/unit/relation/project_spec.rb +3 -1
  75. data/spec/unit/relation/qualified_columns_spec.rb +2 -0
  76. data/spec/unit/relation/rename_spec.rb +2 -0
  77. data/spec/unit/relation/right_join_spec.rb +59 -0
  78. data/spec/unit/relation/select_append_spec.rb +21 -0
  79. data/spec/unit/relation/select_spec.rb +41 -0
  80. data/spec/unit/relation/where_spec.rb +28 -0
  81. data/spec/unit/restriction_dsl_spec.rb +34 -0
  82. metadata +62 -40
  83. data/lib/rom/plugins/relation/sql/base_view.rb +0 -31
  84. data/lib/rom/sql/header.rb +0 -61
  85. data/lib/rom/sql/plugin/assoc_macros.rb +0 -133
  86. data/lib/rom/sql/plugin/assoc_macros/class_interface.rb +0 -128
  87. data/spec/integration/read_spec.rb +0 -111
  88. data/spec/unit/association_errors_spec.rb +0 -19
  89. data/spec/unit/plugin/assoc_macros/combined_associations_spec.rb +0 -73
  90. data/spec/unit/plugin/assoc_macros/many_to_many_spec.rb +0 -53
  91. data/spec/unit/plugin/assoc_macros/many_to_one_spec.rb +0 -61
  92. data/spec/unit/plugin/assoc_macros/one_to_many_spec.rb +0 -78
  93. data/spec/unit/plugin/base_view_spec.rb +0 -18
@@ -0,0 +1,29 @@
1
+ module ROM
2
+ module SQL
3
+ class DSL < BasicObject
4
+ # @api private
5
+ attr_reader :schema
6
+
7
+ # @api private
8
+ def initialize(schema)
9
+ @schema = schema
10
+ end
11
+
12
+ # @api private
13
+ def call(&block)
14
+ result = instance_exec(&block)
15
+
16
+ if result.is_a?(::Array)
17
+ result
18
+ else
19
+ [result]
20
+ end
21
+ end
22
+
23
+ # @api private
24
+ def respond_to_missing?(name, include_private = false)
25
+ super || schema.key?(name)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ module ROM
2
+ module SQL
3
+ class Expression
4
+ attr_reader :expr, :type
5
+
6
+ def initialize(type, expr = type.sql_expr)
7
+ @type = type
8
+ @expr = expr
9
+ end
10
+
11
+ def sql_literal(ds)
12
+ expr.sql_literal(ds)
13
+ end
14
+
15
+ private
16
+
17
+ def method_missing(meth, *args, &block)
18
+ if type.respond_to?(meth)
19
+ self.class.new(type.__send__(meth, *args, &block))
20
+ else
21
+ self.class.new(type, expr.__send__(meth, *args, &block))
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,23 @@
1
+ module ROM
2
+ module SQL
3
+ class Function < ROM::Schema::Type
4
+ def as(name)
5
+ meta(name: name)
6
+ end
7
+
8
+ def sql_literal(ds)
9
+ func.as(name).sql_literal(ds)
10
+ end
11
+
12
+ private
13
+
14
+ def func
15
+ Sequel::SQL::Function.new(meta[:op], *meta[:args])
16
+ end
17
+
18
+ def method_missing(op, *args)
19
+ meta(op: op, args: args)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,8 +1,12 @@
1
1
  require 'logger'
2
2
 
3
+ require 'dry/core/constants'
4
+
5
+ require 'rom/types'
3
6
  require 'rom/gateway'
4
7
  require 'rom/sql/migration'
5
8
  require 'rom/sql/commands'
9
+ require 'rom/sql/transaction'
6
10
 
7
11
  module ROM
8
12
  module SQL
@@ -16,7 +20,7 @@ module ROM
16
20
  #
17
21
  # @api public
18
22
  class Gateway < ROM::Gateway
19
- include Options
23
+ include Dry::Core::Constants
20
24
  include Migration
21
25
 
22
26
  class << self
@@ -24,7 +28,7 @@ module ROM
24
28
  end
25
29
 
26
30
  CONNECTION_EXTENSIONS = {
27
- postgres: %i(pg_array pg_json)
31
+ postgres: %i(pg_array pg_json pg_enum)
28
32
  }.freeze
29
33
 
30
34
  # Return optionally configured logger
@@ -34,6 +38,9 @@ module ROM
34
38
  # @api public
35
39
  attr_reader :logger
36
40
 
41
+ # @api private
42
+ attr_reader :options
43
+
37
44
  # SQL gateway interface
38
45
  #
39
46
  # @overload connect(uri, options)
@@ -55,14 +62,13 @@ module ROM
55
62
  # gateway = ROM::SQL::Gateway.new(DB)
56
63
  #
57
64
  # @api public
58
- def initialize(uri, options = {})
59
- repo_options = self.class.option_definitions.names
60
- conn_options = options.reject { |k, _| repo_options.include?(k) }
61
-
62
- @connection = connect(uri, conn_options)
65
+ def initialize(uri, options = EMPTY_HASH)
66
+ @connection = connect(uri, options)
63
67
  load_extensions(Array(options[:extensions]))
64
68
 
65
- super(uri, options.reject { |k, _| conn_options.keys.include?(k) })
69
+ @options = options
70
+
71
+ super
66
72
 
67
73
  self.class.instance = self
68
74
  end
@@ -191,8 +197,17 @@ module ROM
191
197
  ROM::SQL.load_extensions(db_type)
192
198
  end
193
199
 
194
- extensions = (CONNECTION_EXTENSIONS.fetch(db_type) { [] } + exts).uniq
200
+ extensions = (CONNECTION_EXTENSIONS.fetch(db_type, EMPTY_ARRAY) + exts).uniq
195
201
  connection.extension(*extensions)
202
+
203
+ # this will be default in Sequel 5.0.0 and since we don't rely
204
+ # on dataset mutation it is safe to enable it already
205
+ connection.extension(:freeze_datasets) unless RUBY_ENGINE == 'rbx'
206
+ end
207
+
208
+ # @api private
209
+ def transaction_runner(_)
210
+ ROM::SQL::Transaction.new(connection)
196
211
  end
197
212
  end
198
213
  end
@@ -30,13 +30,12 @@ module ROM
30
30
  module Migration
31
31
  Sequel.extension :migration
32
32
 
33
- def self.included(klass)
34
- super
35
- klass.class_eval do
36
- option :migrator, reader: true, default: proc { |gateway|
37
- Migrator.new(gateway.connection)
38
- }
39
- end
33
+ # @api public
34
+ attr_reader :migrator
35
+
36
+ # @api private
37
+ def initialize(uri, options = EMPTY_HASH)
38
+ @migrator = options.fetch(:migrator) { Migrator.new(connection) }
40
39
  end
41
40
 
42
41
  # @see ROM::SQL::Migration.pending?
@@ -1,20 +1,19 @@
1
+ require 'pathname'
2
+ require 'rom/types'
3
+ require 'rom/initializer'
4
+
1
5
  module ROM
2
6
  module SQL
3
7
  module Migration
4
8
  class Migrator
5
- include Options
9
+ extend Initializer
6
10
 
7
11
  DEFAULT_PATH = 'db/migrate'.freeze
8
12
  VERSION_FORMAT = '%Y%m%d%H%M%S'.freeze
9
13
 
10
- option :path, reader: true, default: DEFAULT_PATH
11
-
12
- attr_reader :connection
14
+ param :connection
13
15
 
14
- def initialize(connection, options = {})
15
- super
16
- @connection = connection
17
- end
16
+ option :path, type: ROM::Types.Definition(Pathname), reader: true, default: proc { DEFAULT_PATH }
18
17
 
19
18
  def run(options = {})
20
19
  Sequel::Migrator.run(connection, path.to_s, options)
@@ -0,0 +1,20 @@
1
+ require 'rom/sql/dsl'
2
+ require 'rom/sql/expression'
3
+
4
+ module ROM
5
+ module SQL
6
+ class OrderDSL < DSL
7
+ private
8
+
9
+ # @api private
10
+ def method_missing(meth, *args, &block)
11
+ if schema.key?(meth)
12
+ attr = schema[meth]
13
+ ::ROM::SQL::Expression.new(schema[meth])
14
+ else
15
+ ::Sequel::VIRTUAL_ROW.__send__(meth, *args, &block)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,5 +1,3 @@
1
- require 'rom/support/deprecations'
2
-
3
1
  module ROM
4
2
  module SQL
5
3
  module Plugin
@@ -9,51 +7,38 @@ module ROM
9
7
  module Associates
10
8
  # @api private
11
9
  def self.included(klass)
12
- klass.extend(ClassMethods)
13
- super
14
- end
10
+ klass.class_eval do
11
+ extend ClassMethods
12
+ include InstanceMethods
13
+ defines :associations
15
14
 
16
- module InstanceMethods
17
- attr_reader :assoc, :__registry__
15
+ associations []
18
16
 
19
- # @api private
20
- def initialize(*)
21
- super
22
- @__registry__ = relation.__registry__
23
- assoc_name, assoc_opts = self.class.associations[0]
24
- @assoc =
25
- if assoc_opts.any?
26
- assoc_opts[:key]
27
- else
28
- relation.associations[assoc_name]
29
- end
17
+ option :associations, reader: true, optional: true, default: -> cmd { cmd.class.associations }
30
18
  end
19
+ super
20
+ end
31
21
 
22
+ module InstanceMethods
32
23
  # Set fk on tuples from parent tuple
33
24
  #
34
25
  # @param [Array<Hash>, Hash] tuples The input tuple(s)
35
26
  # @param [Hash] parent The parent tuple with its pk already set
36
27
  #
37
- # @return [Array<Hash>,Hash]
38
- #
39
- # @overload SQL::Commands::Create#execute
28
+ # @return [Array<Hash>]
40
29
  #
41
30
  # @api public
42
- def execute(tuples, parent)
31
+ def associate(tuples, parent, assoc:, keys:)
43
32
  input_tuples =
44
33
  case assoc
45
- when Array
46
- fk, pk = assoc
34
+ when Symbol
35
+ fk, pk = keys
47
36
 
48
- input_tuples = with_input_tuples(tuples).map { |tuple|
37
+ with_input_tuples(tuples).map { |tuple|
49
38
  tuple.merge(fk => parent.fetch(pk))
50
39
  }
51
-
52
- super(input_tuples)
53
40
  when Association::ManyToMany
54
- new_tuples = super(tuples)
55
-
56
- join_tuples = assoc.associate(__registry__, new_tuples, parent)
41
+ join_tuples = assoc.associate(__registry__, tuples, parent)
57
42
  join_relation = assoc.join_relation(__registry__)
58
43
  join_relation.multi_insert(join_tuples)
59
44
 
@@ -63,22 +48,54 @@ module ROM
63
48
 
64
49
  pk_extend = { fk => parent[pk] }
65
50
 
66
- new_tuples.map { |tuple| tuple.update(pk_extend) }
51
+ tuples.map { |tuple| tuple.update(pk_extend) }
67
52
  when Association
68
- input_tuples = with_input_tuples(tuples).map { |tuple|
53
+ with_input_tuples(tuples).map { |tuple|
69
54
  assoc.associate(relation.__registry__, tuple, parent)
70
55
  }
71
- super(input_tuples)
72
56
  end
73
57
  end
58
+
59
+ # @api public
60
+ def with_association(name, opts = EMPTY_HASH)
61
+ self.class.build(relation, options.merge(associations: [[name, opts]]))
62
+ end
63
+
64
+ # @api private
65
+ def __registry__
66
+ relation.__registry__
67
+ end
74
68
  end
75
69
 
76
70
  module ClassMethods
77
- # @api private
78
- def inherited(klass)
79
- klass.defines :associations
80
- klass.associations []
81
- super
71
+ # @see ROM::Command::ClassInterface.build
72
+ #
73
+ # @api public
74
+ def build(relation, options = EMPTY_HASH)
75
+ command = super
76
+ associations = command.associations
77
+
78
+ before_hooks = associations.each_with_object([]) do |(name, opts), acc|
79
+ relation.associations.try(name) do |assoc|
80
+ unless assoc.is_a?(Association::ManyToMany)
81
+ acc << { associate: { assoc: assoc, keys: assoc.join_keys(relation.__registry__) } }
82
+ else
83
+ true
84
+ end
85
+ end or acc << { associate: { assoc: name, keys: opts[:key] } }
86
+ end
87
+
88
+ after_hooks = associations.each_with_object([]) do |(name, opts), acc|
89
+ next unless relation.associations.key?(name)
90
+
91
+ assoc = relation.associations[name]
92
+
93
+ if assoc.is_a?(Association::ManyToMany)
94
+ acc << { associate: { assoc: assoc, keys: assoc.join_keys(relation.__registry__) } }
95
+ end
96
+ end
97
+
98
+ command.before(*before_hooks).after(*after_hooks)
82
99
  end
83
100
 
84
101
  # Set command to associate tuples with a parent tuple using provided keys
@@ -102,17 +119,13 @@ module ROM
102
119
  # @option options [Array] :key The association keys
103
120
  #
104
121
  # @api public
105
- def associates(name, options = {})
122
+ def associates(name, options = EMPTY_HASH)
106
123
  if associations.map(&:first).include?(name)
107
124
  raise ArgumentError,
108
- "#{name} association is already defined for #{self.class}"
125
+ "#{name} association is already defined for #{self.class}"
109
126
  end
110
127
 
111
- option :association, reader: true, default: {}
112
-
113
- include InstanceMethods
114
-
115
- associations << [name, options]
128
+ associations(associations.dup << [name, options])
116
129
  end
117
130
  end
118
131
  end
@@ -1,20 +1,17 @@
1
+ require 'rom/initializer'
2
+
1
3
  module ROM
2
4
  module SQL
3
5
  module Plugin
4
6
  module Pagination
5
7
  class Pager
6
- include Options
8
+ extend Initializer
7
9
  include Dry::Equalizer(:dataset, :options)
8
10
 
9
- option :current_page, reader: true, default: 1
10
- option :per_page, reader: true
11
-
12
- attr_reader :dataset
11
+ param :dataset
13
12
 
14
- def initialize(dataset, options = {})
15
- super
16
- @dataset = dataset
17
- end
13
+ option :current_page, reader: true, default: proc { 1 }
14
+ option :per_page, reader: true
18
15
 
19
16
  def next_page
20
17
  num = current_page + 1
@@ -71,7 +68,7 @@ module ROM
71
68
  # @api public
72
69
  def page(num)
73
70
  next_pager = pager.at(dataset, num)
74
- __new__(next_pager.dataset, pager: next_pager)
71
+ new(next_pager.dataset, pager: next_pager)
75
72
  end
76
73
 
77
74
  # Set limit for pagination
@@ -82,7 +79,7 @@ module ROM
82
79
  # @api public
83
80
  def per_page(num)
84
81
  next_pager = pager.at(dataset, pager.current_page, num)
85
- __new__(next_pager.dataset, pager: next_pager)
82
+ new(next_pager.dataset, pager: next_pager)
86
83
  end
87
84
  end
88
85
  end
@@ -1,10 +1,8 @@
1
- require 'rom/sql/plugin/assoc_macros'
2
1
  require 'rom/sql/plugin/associates'
3
2
  require 'rom/sql/plugin/pagination'
4
3
 
5
4
  ROM.plugins do
6
5
  adapter :sql do
7
- register :assoc_macros, ROM::SQL::Plugin::AssocMacros, type: :relation
8
6
  register :pagination, ROM::SQL::Plugin::Pagination, type: :relation
9
7
  register :associates, ROM::SQL::Plugin::Associates, type: :command
10
8
  end
@@ -0,0 +1,41 @@
1
+ require 'rom/sql/dsl'
2
+ require 'rom/sql/function'
3
+
4
+ module ROM
5
+ module SQL
6
+ class ProjectionDSL < DSL
7
+ # @api private
8
+ def respond_to_missing?(name, include_private = false)
9
+ super || type(name)
10
+ end
11
+
12
+ private
13
+
14
+ # @api private
15
+ def type(identifier)
16
+ type_name = ::Dry::Core::Inflector.classify(identifier)
17
+ types.const_get(type_name) if types.const_defined?(type_name)
18
+ end
19
+
20
+ # @api private
21
+ def types
22
+ ::ROM::SQL::Types
23
+ end
24
+
25
+ # @api private
26
+ def method_missing(meth, *args, &block)
27
+ if schema.key?(meth)
28
+ schema[meth]
29
+ else
30
+ type = type(meth)
31
+
32
+ if type
33
+ ::ROM::SQL::Function.new(type)
34
+ else
35
+ super
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end