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
@@ -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