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
@@ -1,5 +1,7 @@
1
1
  require 'rom/schema'
2
- require 'rom/support/constants'
2
+ require 'rom/sql/order_dsl'
3
+ require 'rom/sql/projection_dsl'
4
+ require 'rom/sql/restriction_dsl'
3
5
 
4
6
  module ROM
5
7
  module SQL
@@ -13,19 +15,83 @@ module ROM
13
15
  # @return [Array<Symbol>] A list of all pk names
14
16
  attr_reader :primary_key_names
15
17
 
18
+ # @api private
16
19
  def initialize(*)
17
20
  super
18
- @primary_key_name = nil
19
- @primary_key_names = EMPTY_ARRAY
21
+ initialize_primary_key_names
22
+ end
23
+
24
+ # @api public
25
+ def restriction(&block)
26
+ RestrictionDSL.new(self).call(&block)
27
+ end
28
+
29
+ # @api public
30
+ def order(&block)
31
+ OrderDSL.new(self).call(&block)
32
+ end
33
+
34
+ # Return a new schema with attributes marked as qualified
35
+ #
36
+ # @return [Schema]
37
+ #
38
+ # @api public
39
+ def qualified
40
+ new(map(&:qualified))
41
+ end
42
+
43
+ # @api public
44
+ def project(*names, &block)
45
+ if block
46
+ super(*(names + ProjectionDSL.new(self).(&block)))
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ # @api private
53
+ def project_pk
54
+ project(*primary_key_names)
55
+ end
56
+
57
+ # @api private
58
+ def project_fk(mapping)
59
+ new(rename(mapping).map(&:foreign_key))
60
+ end
61
+
62
+ # @api public
63
+ def join(other)
64
+ merge(other.joined)
65
+ end
66
+
67
+ # @api public
68
+ def joined
69
+ new(map(&:joined))
70
+ end
71
+
72
+ # Create a new relation based on the schema definition
73
+ #
74
+ # @param [Relation] relation The source relation
75
+ #
76
+ # @return [Relation]
77
+ #
78
+ # @api public
79
+ def call(relation)
80
+ relation.new(relation.dataset.select(*self), schema: self)
20
81
  end
21
82
 
22
83
  # @api private
23
84
  def finalize!(*)
24
85
  super do
25
- if primary_key.size > 0
26
- @primary_key_name = primary_key[0].meta[:name]
27
- @primary_key_names = primary_key.map { |type| type.meta[:name] }
28
- end
86
+ initialize_primary_key_names
87
+ end
88
+ end
89
+
90
+ # @api private
91
+ def initialize_primary_key_names
92
+ if primary_key.size > 0
93
+ @primary_key_name = primary_key[0].meta[:name]
94
+ @primary_key_names = primary_key.map { |type| type.meta[:name] }
29
95
  end
30
96
  end
31
97
  end
@@ -1,3 +1,4 @@
1
+ require 'dry/core/inflector'
1
2
  require 'rom/sql/association'
2
3
 
3
4
  module ROM
@@ -42,11 +43,11 @@ module ROM
42
43
  end
43
44
 
44
45
  def belongs_to(name, options = {})
45
- many_to_one(dataset_name(name), options.merge(as: options[:as] || name))
46
+ many_to_one(dataset_name(name), {as: name}.merge(options))
46
47
  end
47
48
 
48
49
  def has_one(name, options = {})
49
- one_to_one(dataset_name(name), options.merge(as: options[:as] || name))
50
+ one_to_one(dataset_name(name), {as: name}.merge(options))
50
51
  end
51
52
 
52
53
  def call
@@ -60,7 +61,7 @@ module ROM
60
61
  end
61
62
 
62
63
  def dataset_name(name)
63
- Inflector.pluralize(name).to_sym
64
+ ::Dry::Core::Inflector.pluralize(name).to_sym
64
65
  end
65
66
  end
66
67
  end
@@ -1,3 +1,4 @@
1
+ require 'rom/sql/type'
1
2
  require 'rom/sql/schema/inferrer'
2
3
  require 'rom/sql/schema/associations_dsl'
3
4
 
@@ -8,11 +9,13 @@ module ROM
8
9
  attr_reader :associations_dsl
9
10
 
10
11
  def associations(&block)
11
- @associations_dsl = AssociationsDSL.new(name, &block)
12
+ @associations_dsl = AssociationsDSL.new(relation, &block)
12
13
  end
13
14
 
14
15
  def call
15
- SQL::Schema.new(name, attributes, opts)
16
+ SQL::Schema.define(
17
+ relation, opts.merge(attributes: attributes.values, type_class: SQL::Type)
18
+ )
16
19
  end
17
20
 
18
21
  def opts
@@ -1,9 +1,11 @@
1
+ require 'dry/core/class_attributes'
2
+
1
3
  module ROM
2
4
  module SQL
3
5
  class Schema < ROM::Schema
4
6
  # @api private
5
7
  class Inferrer
6
- extend ClassMacros
8
+ extend Dry::Core::ClassAttributes
7
9
 
8
10
  defines :ruby_type_mapping, :numeric_pk_type, :db_type, :db_registry
9
11
 
@@ -37,14 +39,21 @@ module ROM
37
39
  end
38
40
 
39
41
  # @api private
40
- def call(dataset, gateway)
42
+ def call(source, gateway)
43
+ dataset = source.dataset
44
+
41
45
  columns = gateway.connection.schema(dataset)
42
46
  fks = fks_for(gateway, dataset)
43
47
 
44
- columns.each_with_object({}) do |(name, definition), attrs|
48
+ inferred = columns.map do |(name, definition)|
45
49
  type = build_type(definition.merge(foreign_key: fks[name]))
46
- attrs[name] = type.meta(name: name)
47
- end
50
+
51
+ if type
52
+ type.meta(name: name, source: source)
53
+ end
54
+ end.compact
55
+
56
+ [inferred, columns.map(&:first) - inferred.map { |attr| attr.meta[:name] }]
48
57
  end
49
58
 
50
59
  private
@@ -54,9 +63,12 @@ module ROM
54
63
  map_pk_type(type, db_type)
55
64
  else
56
65
  mapped_type = map_type(type, db_type, rest)
57
- mapped_type = mapped_type.optional if allow_null
58
- mapped_type = mapped_type.meta(foreign_key: true, relation: foreign_key) if foreign_key
59
- mapped_type
66
+
67
+ if mapped_type
68
+ mapped_type = mapped_type.optional if allow_null
69
+ mapped_type = mapped_type.meta(foreign_key: true, target: foreign_key) if foreign_key
70
+ mapped_type
71
+ end
60
72
  end
61
73
  end
62
74
 
@@ -65,9 +77,7 @@ module ROM
65
77
  end
66
78
 
67
79
  def map_type(ruby_type, db_type, **_kw)
68
- self.class.ruby_type_mapping.fetch(ruby_type) {
69
- raise UnknownDBTypeError, "Cannot find corresponding type for #{ruby_type || db_type}"
70
- }
80
+ self.class.ruby_type_mapping[ruby_type]
71
81
  end
72
82
 
73
83
  # @api private
@@ -0,0 +1,19 @@
1
+ module ROM
2
+ module SQL
3
+ # @api private
4
+ class Transaction < ::ROM::Transaction
5
+ attr_reader :connection
6
+ private :connection
7
+
8
+ def initialize(connection)
9
+ @connection = connection
10
+ end
11
+
12
+ def run(opts = EMPTY_HASH)
13
+ connection.transaction(opts) { yield(self) }
14
+ rescue ::ROM::Transaction::Rollback
15
+ # noop
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,76 @@
1
+ require 'rom/schema/type'
2
+
3
+ module ROM
4
+ module SQL
5
+ class Type < ROM::Schema::Type
6
+ # Return a new type marked as a FK
7
+ #
8
+ # @return [SQL::Type]
9
+ #
10
+ # @api public
11
+ def foreign_key
12
+ meta(foreign_key: true)
13
+ end
14
+
15
+ # Return a new type marked as qualified
16
+ #
17
+ # @return [SQL::Type]
18
+ #
19
+ # @api public
20
+ def qualified
21
+ meta(qualified: true)
22
+ end
23
+
24
+ # Return a new type marked as joined
25
+ #
26
+ # @return [SQL::Type]
27
+ #
28
+ # @api public
29
+ def joined
30
+ meta(joined: true)
31
+ end
32
+
33
+ # Return if an attribute was used in a join
34
+ #
35
+ # @return [Boolean]
36
+ #
37
+ # @api public
38
+ def joined?
39
+ meta[:joined].equal?(true)
40
+ end
41
+
42
+ # Return if an attribute type is qualified
43
+ #
44
+ # @return [Boolean]
45
+ #
46
+ # @api public
47
+ def qualified?
48
+ meta[:qualified].equal?(true)
49
+ end
50
+
51
+ # @api public
52
+ def to_sym
53
+ @_to_sym ||=
54
+ if qualified? && aliased?
55
+ :"#{source.dataset}__#{name}___#{meta[:alias]}"
56
+ elsif qualified?
57
+ :"#{source.dataset}__#{name}"
58
+ elsif aliased?
59
+ :"#{name}___#{meta[:alias]}"
60
+ else
61
+ name
62
+ end
63
+ end
64
+
65
+ # @api private
66
+ def sql_literal(ds)
67
+ sql_expr.sql_literal(ds)
68
+ end
69
+
70
+ # @api private
71
+ def sql_expr
72
+ Sequel.expr(to_sym)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,5 +1,5 @@
1
1
  module ROM
2
2
  module SQL
3
- VERSION = '0.9.1'.freeze
3
+ VERSION = '1.0.0.beta1'.freeze
4
4
  end
5
5
  end
data/rom-sql.gemspec CHANGED
@@ -18,12 +18,11 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_runtime_dependency 'sequel', '~> 4.25'
21
+ spec.add_runtime_dependency 'sequel', '~> 4.42'
22
22
  spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
23
23
  spec.add_runtime_dependency 'dry-types', '~> 0.9'
24
- spec.add_runtime_dependency 'dry-core', '~> 0.2'
25
- spec.add_runtime_dependency 'rom', '~> 2.0'
26
- spec.add_runtime_dependency 'rom-support', '~> 2.0'
24
+ spec.add_runtime_dependency 'dry-core', '~> 0.2', '>= 0.2.3'
25
+ spec.add_runtime_dependency 'rom', '~> 3.0.0.beta'
27
26
 
28
27
  spec.add_development_dependency 'bundler'
29
28
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -18,10 +18,15 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
18
18
  Decimal :money, null: false
19
19
  column :tags, "text[]"
20
20
  column :tag_ids, "bigint[]"
21
+ column :ip, "inet"
21
22
  rainbow :color
22
23
  end
23
24
  end
24
25
 
26
+ after do
27
+ conn.drop_table?(:test_inferrence)
28
+ end
29
+
25
30
  let(:dataset) { :test_inferrence }
26
31
 
27
32
  let(:schema) { container.relations[dataset].schema }
@@ -30,19 +35,24 @@ RSpec.describe 'ROM::SQL::Schema::PostgresInferrer', :postgres do
30
35
  before do
31
36
  dataset = self.dataset
32
37
  conf.relation(dataset) do
33
- schema(dataset, infer: true)
38
+ schema(dataset, infer: true) do
39
+ attribute :ip, ROM::SQL::Types::String
40
+ end
34
41
  end
35
42
  end
36
43
 
37
44
  it 'can infer attributes for dataset' do
38
- expect(schema.attributes).to eql(
39
- id: ROM::SQL::Types::PG::UUID.meta(name: :id, primary_key: true),
40
- json_data: ROM::SQL::Types::PG::JSON.optional.meta(name: :json_data),
41
- jsonb_data: ROM::SQL::Types::PG::JSONB.optional.meta(name: :jsonb_data),
42
- money: ROM::SQL::Types::Decimal.meta(name: :money),
43
- tags: ROM::SQL::Types::PG::Array('text').optional.meta(name: :tags),
44
- tag_ids: ROM::SQL::Types::PG::Array('biging').optional.meta(name: :tag_ids),
45
- color: ROM::SQL::Types::String.enum(*colors).optional.meta(name: :color)
45
+ source = container.relations[:test_inferrence].name
46
+
47
+ expect(schema.to_h).to eql(
48
+ id: ROM::SQL::Types::PG::UUID.meta(name: :id, source: source, primary_key: true),
49
+ json_data: ROM::SQL::Types::PG::JSON.optional.meta(name: :json_data, source: source),
50
+ jsonb_data: ROM::SQL::Types::PG::JSONB.optional.meta(name: :jsonb_data, source: source),
51
+ money: ROM::SQL::Types::Decimal.meta(name: :money, source: source),
52
+ tags: ROM::SQL::Types::PG::Array('text').optional.meta(name: :tags, source: source),
53
+ tag_ids: ROM::SQL::Types::PG::Array('biging').optional.meta(name: :tag_ids, source: source),
54
+ color: ROM::SQL::Types::String.enum(*colors).optional.meta(name: :color, source: source),
55
+ ip: ROM::SQL::Types::String.meta(name: :ip, source: source)
46
56
  )
47
57
  end
48
58
  end
@@ -0,0 +1,73 @@
1
+ RSpec.describe ROM::SQL::Association::ManyToMany, '#call' do
2
+ subject(:assoc) do
3
+ relations[:users].associations[:puzzles]
4
+ end
5
+
6
+ include_context 'database setup'
7
+
8
+ with_adapters do
9
+ before do
10
+ conn.create_table(:puzzles) do
11
+ primary_key :id
12
+ column :text, String, null: false
13
+ end
14
+
15
+ conn.create_table(:puzzle_solvers) do
16
+ foreign_key :solver_id, :users, null: false
17
+ foreign_key :puzzle_id, :puzzles, null: false
18
+ primary_key [:solver_id, :puzzle_id]
19
+ end
20
+
21
+ conf.relation(:puzzle_solvers) do
22
+ schema(infer: true) do
23
+ associations do
24
+ belongs_to :user, foreign_key: :solver_id
25
+ belongs_to :puzzle
26
+ end
27
+ end
28
+ end
29
+
30
+ conf.relation(:users) do
31
+ schema(infer: true) do
32
+ associations do
33
+ has_many :puzzle_solvers
34
+ has_many :puzzles, through: :puzzle_solvers, foreign_key: :solver_id
35
+ end
36
+ end
37
+ end
38
+
39
+ joe_id = relations[:users].insert(name: 'Joe')
40
+ jane_id = relations[:users].insert(name: 'Jane')
41
+
42
+ p1_id = relations[:puzzles].insert(text: 'P1')
43
+ p2_id = relations[:puzzles].insert(text: 'P2')
44
+ p3_id = relations[:puzzles].insert(text: 'P3')
45
+
46
+ relations[:puzzle_solvers].insert(solver_id: joe_id, puzzle_id: p2_id)
47
+ relations[:puzzle_solvers].insert(solver_id: jane_id, puzzle_id: p2_id)
48
+
49
+ relations[:puzzle_solvers].insert(solver_id: joe_id, puzzle_id: p1_id)
50
+ relations[:puzzle_solvers].insert(solver_id: jane_id, puzzle_id: p3_id)
51
+ end
52
+
53
+ after do
54
+ conn.drop_table?(:puzzle_solvers)
55
+ conn.drop_table?(:puzzles)
56
+ end
57
+
58
+ it 'prepares joined relations using custom FK' do
59
+ relation = assoc.call(relations).order(:puzzles__text)
60
+
61
+ expect(relation.schema.map(&:to_sym)).
62
+ to eql(%i[puzzles__id puzzles__text puzzle_solvers__solver_id])
63
+
64
+ expect(relation.to_a).
65
+ to eql([
66
+ { id: 1, solver_id: 1, text: 'P1' },
67
+ { id: 2, solver_id: 1, text: 'P2' },
68
+ { id: 2, solver_id: 2, text: 'P2' },
69
+ { id: 3, solver_id: 2, text: 'P3' }
70
+ ])
71
+ end
72
+ end
73
+ end