rom-sql 0.9.1 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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