rom-sql 1.0.0.beta2 → 1.0.0.beta3

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGELOG.md +4 -0
  4. data/circle.yml +1 -1
  5. data/lib/rom/plugins/relation/sql/auto_combine.rb +1 -1
  6. data/lib/rom/sql/association.rb +5 -1
  7. data/lib/rom/sql/association/many_to_many.rb +8 -4
  8. data/lib/rom/sql/association/many_to_one.rb +2 -3
  9. data/lib/rom/sql/association/one_to_many.rb +2 -3
  10. data/lib/rom/sql/commands/create.rb +8 -1
  11. data/lib/rom/sql/commands/update.rb +7 -0
  12. data/lib/rom/sql/extensions.rb +8 -0
  13. data/lib/rom/sql/extensions/active_support_notifications.rb +7 -18
  14. data/lib/rom/sql/extensions/mysql.rb +1 -0
  15. data/lib/rom/sql/extensions/mysql/inferrer.rb +10 -0
  16. data/lib/rom/sql/extensions/postgres/commands.rb +1 -1
  17. data/lib/rom/sql/extensions/postgres/inferrer.rb +4 -0
  18. data/lib/rom/sql/extensions/postgres/types.rb +20 -22
  19. data/lib/rom/sql/extensions/sqlite.rb +1 -0
  20. data/lib/rom/sql/extensions/sqlite/inferrer.rb +10 -0
  21. data/lib/rom/sql/function.rb +6 -2
  22. data/lib/rom/sql/order_dsl.rb +1 -1
  23. data/lib/rom/sql/plugin/associates.rb +28 -5
  24. data/lib/rom/sql/relation.rb +18 -0
  25. data/lib/rom/sql/relation/reading.rb +2 -2
  26. data/lib/rom/sql/relation/sequel_api.rb +119 -0
  27. data/lib/rom/sql/schema/inferrer.rb +26 -2
  28. data/lib/rom/sql/tasks/migration_tasks.rake +1 -1
  29. data/lib/rom/sql/type.rb +13 -2
  30. data/lib/rom/sql/types.rb +5 -3
  31. data/lib/rom/sql/version.rb +1 -1
  32. data/spec/extensions/postgres/types_spec.rb +29 -0
  33. data/spec/integration/association/many_to_many_spec.rb +8 -0
  34. data/spec/integration/association/many_to_one_spec.rb +11 -0
  35. data/spec/integration/association/one_to_many_spec.rb +9 -0
  36. data/spec/integration/schema/inferrer/mysql_spec.rb +36 -0
  37. data/spec/integration/schema/inferrer/postgres_spec.rb +118 -0
  38. data/spec/integration/schema/inferrer/sqlite_spec.rb +36 -0
  39. data/spec/integration/{schema_inference_spec.rb → schema/inferrer_spec.rb} +44 -15
  40. data/spec/integration/sequel_api_spec.rb +31 -0
  41. data/spec/support/helpers.rb +4 -0
  42. data/spec/unit/function_spec.rb +35 -0
  43. data/spec/unit/order_dsl_spec.rb +35 -0
  44. data/spec/unit/relation/assoc_spec.rb +38 -0
  45. data/spec/unit/relation/inner_join_spec.rb +15 -0
  46. data/spec/unit/types_spec.rb +53 -1
  47. metadata +23 -6
  48. data/spec/extensions/postgres/inferrer_spec.rb +0 -59
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 240c81ee43a6d5edfbf9b599d7a031196a686978
4
- data.tar.gz: 6abdfcd3fdeef06cbbf0794dbb0ebbde658e043e
3
+ metadata.gz: 3c94e3593d4f6fbe8a49111c097a6721ae9d5dfc
4
+ data.tar.gz: 0b12c00984a1b6943465d608fe97e66c78beda47
5
5
  SHA512:
6
- metadata.gz: cdfd38e021013963c48d176d44f7872dd737728254ad066b03a13ded7c90553fd6017651300bd169a6231841f4386e57ce8eb58275c3218b70938a13e42d56cf
7
- data.tar.gz: 81459e9c88292c2c37218074ab7adf37070852455604829d95cf360bb12565aca97b6b782bbd1f88b0466b86b73421d9ce05157f008246bcd7d278092e196535
6
+ metadata.gz: 3fc3303b1f6c7b5415d44186bc857b7f8a5add043b1b9f990f2b003003bfa8fddd87fc6feb30532759b4548919dbfbffeea2c560a42faff3225b0894933ee2c8
7
+ data.tar.gz: 449816a9fe18c46ba4b65a64e0397121d57c9593a3fffd1ffdab7da04e29eb33a2a40a49cdf8c1859b3e974c68e71a6971eae3afc47a8316715c9c1d8746958d
data/.travis.yml CHANGED
@@ -14,7 +14,7 @@ after_success:
14
14
  script: "bundle exec rake ci"
15
15
  rvm:
16
16
  - 2.2.5
17
- - 2.3.1
17
+ - 2.3.3
18
18
  - 2.4.0
19
19
  - rbx-3
20
20
  - jruby
data/CHANGELOG.md CHANGED
@@ -12,10 +12,14 @@ Please refer to [the upgrading guide](https://github.com/rom-rb/rom-sql/wiki/Upg
12
12
  * Extended query API with support for schema attributes (solnic)
13
13
  * Schemas can project relations automatically (solnic)
14
14
  * New `Schema#qualified` (solnic)
15
+ * New `Relation#assoc` method which is a shortcut for accessing relation created by the given association (solnic)
15
16
  * Schema attribute types are now SQL-specific and compatible with query DSL (ie you can pass relation attributes to `select` and they will be automatically converted to valid SQL expressions) (solnic)
16
17
  * Associations support setting custom `view` that will be used to extend association relation (solnic)
17
18
  * Associations support setting custom `foreign_key` names (solnic)
18
19
  * Support for self-referencing associations (ie categories have_many child categories) (solnic)
20
+ * Inferrers for mysql and sqlite were added (flash-gordon)
21
+ * PG's auto-inferrer can handle `inet`/`cidr` data types in a two-way manner, i.e. converting them back and forth on reading and writing. Same for `point` datatype (flash-gordon)
22
+ * `ROM::SQL::Relation::SequelAPI` extension for backward-compatible query API (this will be deprecated in 1.1.0 and removed in 2.0.0) (solnic)
19
23
 
20
24
  ### Changed
21
25
 
data/circle.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  machine:
2
2
  ruby:
3
- version: 2.3.1
3
+ version: 2.3.3
4
4
  dependencies:
5
5
  bundler:
6
6
  without: [yard guard benchmarks tools]
@@ -32,7 +32,7 @@ module ROM
32
32
  source_key, target_key, target =
33
33
  case spec
34
34
  when ROM::SQL::Association
35
- [*spec.join_keys(__registry__).flatten, spec.call(__registry__)]
35
+ [*spec.join_keys(__registry__).flatten, spec.call(__registry__, self)]
36
36
  else
37
37
  [*spec.flatten, self]
38
38
  end
@@ -73,7 +73,11 @@ module ROM
73
73
  QualifiedAttribute[name.to_sym, attribute]
74
74
  end
75
75
 
76
- protected
76
+ # @api protected
77
+ def apply_view(schema, relation)
78
+ view_rel = relation.public_send(view)
79
+ schema.merge(view_rel.schema.qualified).uniq(&:to_sym).(view_rel)
80
+ end
77
81
 
78
82
  # @api private
79
83
  def join_key_map(relations)
@@ -17,18 +17,22 @@ module ROM
17
17
  end
18
18
 
19
19
  # @api public
20
- def call(relations)
20
+ def call(relations, target_rel = nil)
21
21
  join_rel = join_relation(relations)
22
22
  assocs = join_rel.associations
23
23
 
24
- left = assocs[target].call(relations)
24
+ left = target_rel ? assocs[target].(relations, target_rel) : assocs[target].(relations)
25
25
  right = relations[target.relation]
26
26
 
27
27
  left_fk = foreign_key || join_rel.foreign_key(source.relation)
28
28
 
29
29
  schema =
30
30
  if left.schema.key?(left_fk)
31
- left.schema.project(*(right.schema.map(&:name) + [left_fk]))
31
+ if target_rel
32
+ target_rel.schema.merge(left.schema.project(left_fk))
33
+ else
34
+ left.schema.project(*(right.schema.map(&:name) + [left_fk]))
35
+ end
32
36
  else
33
37
  right.schema.merge(join_rel.schema.project(left_fk))
34
38
  end.qualified
@@ -38,7 +42,7 @@ module ROM
38
42
  .order(*right.schema.project_pk.qualified)
39
43
 
40
44
  if view
41
- schema.(relation.public_send(view))
45
+ apply_view(schema, relation)
42
46
  else
43
47
  schema.(relation)
44
48
  end
@@ -5,8 +5,7 @@ module ROM
5
5
  result :one
6
6
 
7
7
  # @api public
8
- def call(relations)
9
- left = relations[target.relation]
8
+ def call(relations, left = relations[target.relation])
10
9
  right = relations[source.relation]
11
10
 
12
11
  left_pk = left.primary_key
@@ -27,7 +26,7 @@ module ROM
27
26
  .order(*right_schema.qualified)
28
27
 
29
28
  if view
30
- schema.(relation.public_send(view))
29
+ apply_view(schema, relation)
31
30
  else
32
31
  schema.(relation)
33
32
  end
@@ -5,8 +5,7 @@ module ROM
5
5
  result :many
6
6
 
7
7
  # @api public
8
- def call(relations)
9
- right = relations[target.relation]
8
+ def call(relations, right = relations[target.relation])
10
9
  schema = right.schema.qualified
11
10
 
12
11
  relation = right
@@ -14,7 +13,7 @@ module ROM
14
13
  .order(*right.schema.project_pk.qualified)
15
14
 
16
15
  if view
17
- schema.(relation.public_send(view))
16
+ apply_view(schema, relation)
18
17
  else
19
18
  schema.(relation)
20
19
  end
@@ -16,6 +16,8 @@ module ROM
16
16
  use :associates
17
17
  use :schema
18
18
 
19
+ after :finalize
20
+
19
21
  # Inserts provided tuples into the database table
20
22
  #
21
23
  # @api public
@@ -34,6 +36,11 @@ module ROM
34
36
 
35
37
  private
36
38
 
39
+ # @api private
40
+ def finalize(tuples, *)
41
+ tuples.map { |t| relation.output_schema[t] }
42
+ end
43
+
37
44
  # Executes insert statement and returns inserted tuples
38
45
  #
39
46
  # @api private
@@ -54,7 +61,7 @@ module ROM
54
61
  #
55
62
  # @api private
56
63
  def with_input_tuples(tuples)
57
- input_tuples = Array([tuples]).flatten.map
64
+ input_tuples = Array([tuples]).flatten(1).map
58
65
  return input_tuples unless block_given?
59
66
  input_tuples.each { |tuple| yield(tuple) }
60
67
  end
@@ -15,6 +15,8 @@ module ROM
15
15
 
16
16
  use :schema
17
17
 
18
+ after :finalize
19
+
18
20
  # Updates existing tuple in a relation
19
21
  #
20
22
  # @return [Array<Hash>, Hash]
@@ -26,6 +28,11 @@ module ROM
26
28
 
27
29
  private
28
30
 
31
+ # @api private
32
+ def finalize(tuples, *)
33
+ tuples.map { |t| relation.output_schema[t] }
34
+ end
35
+
29
36
  # Executes update statement for a given tuple
30
37
  #
31
38
  # @api private
@@ -8,6 +8,14 @@ module ROM
8
8
  require 'rom/sql/extensions/postgres'
9
9
  end
10
10
 
11
+ register_extension(:mysql) do
12
+ require 'rom/sql/extensions/mysql'
13
+ end
14
+
15
+ register_extension(:sqlite) do
16
+ require 'rom/sql/extensions/sqlite'
17
+ end
18
+
11
19
  register_extension(:active_support_notifications) do
12
20
  require 'rom/sql/extensions/active_support_notifications'
13
21
  end
@@ -4,24 +4,13 @@ require 'active_support/notifications'
4
4
  module ROM
5
5
  module SQL
6
6
  module ActiveSupportInstrumentation
7
- if Sequel::MAJOR == 4 && Sequel::MINOR < 35
8
- def log_yield(sql, args = nil)
9
- ActiveSupport::Notifications.instrument(
10
- 'sql.rom',
11
- sql: sql,
12
- name: instrumentation_name,
13
- binds: args
14
- ) { super }
15
- end
16
- else
17
- def log_connection_yield(sql, _conn, args = nil)
18
- ActiveSupport::Notifications.instrument(
19
- 'sql.rom',
20
- sql: sql,
21
- name: instrumentation_name,
22
- binds: args
23
- ) { super }
24
- end
7
+ def log_connection_yield(sql, _conn, args = nil)
8
+ ActiveSupport::Notifications.instrument(
9
+ 'sql.rom',
10
+ sql: sql,
11
+ name: instrumentation_name,
12
+ binds: args
13
+ ) { super }
25
14
  end
26
15
 
27
16
  private
@@ -0,0 +1 @@
1
+ require 'rom/sql/extensions/mysql/inferrer'
@@ -0,0 +1,10 @@
1
+ require 'rom/sql/schema/inferrer'
2
+
3
+ module ROM
4
+ module SQL
5
+ class Schema
6
+ class MysqlInferrer < Inferrer[:mysql]
7
+ end
8
+ end
9
+ end
10
+ end
@@ -12,7 +12,7 @@ module ROM
12
12
  def insert(tuples)
13
13
  tuples.map do |tuple|
14
14
  relation.dataset.returning(*relation.columns).insert(tuple)
15
- end.flatten
15
+ end.flatten(1)
16
16
  end
17
17
 
18
18
  # Executes multi_insert statement and returns inserted tuples
@@ -20,6 +20,10 @@ module ROM
20
20
  'bytea' => Types::Blob,
21
21
  'json' => Types::PG::JSON,
22
22
  'jsonb' => Types::PG::JSONB,
23
+ 'inet' => Types::PG::IPAddress,
24
+ 'cidr' => Types::PG::IPAddress,
25
+ 'macaddr' => Types::String,
26
+ 'point' => Types::PG::PointT
23
27
  ).freeze
24
28
 
25
29
  db_array_type_matcher Sequel::Postgres::PGArray::EMPTY_BRACKET
@@ -1,5 +1,6 @@
1
1
  require 'dry-types'
2
2
  require 'sequel'
3
+ require 'ipaddr'
3
4
 
4
5
  Sequel.extension(*%i(pg_array pg_array_ops pg_json pg_json_ops))
5
6
 
@@ -22,43 +23,40 @@ module ROM
22
23
 
23
24
  # JSON
24
25
 
25
- JSONArray = Dry::Types::Definition
26
- .new(Sequel::Postgres::JSONArray)
27
- .constructor(Sequel.method(:pg_json))
26
+ JSONArray = Types.Constructor(Sequel::Postgres::JSONArray, &Sequel.method(:pg_json))
28
27
 
29
- JSONHash = Dry::Types::Definition
30
- .new(Sequel::Postgres::JSONHash)
31
- .constructor(Sequel.method(:pg_json))
28
+ JSONHash = Types.Constructor(Sequel::Postgres::JSONArray, &Sequel.method(:pg_json))
32
29
 
33
- JSONOp = Dry::Types::Definition
34
- .new(Sequel::Postgres::JSONOp)
35
- .constructor(Sequel.method(:pg_json))
30
+ JSONOp = Types.Constructor(Sequel::Postgres::JSONOp, &Sequel.method(:pg_json))
36
31
 
37
32
  JSON = JSONArray | JSONHash | JSONOp
38
33
 
39
34
  # JSONB
40
35
 
41
- JSONBArray = Dry::Types::Definition
42
- .new(Sequel::Postgres::JSONBArray)
43
- .constructor(Sequel.method(:pg_jsonb))
36
+ JSONBArray = Types.Constructor(Sequel::Postgres::JSONBArray, &Sequel.method(:pg_jsonb))
44
37
 
45
- JSONBHash = Dry::Types::Definition
46
- .new(Sequel::Postgres::JSONBHash)
47
- .constructor(Sequel.method(:pg_jsonb))
38
+ JSONBHash = Types.Constructor(Sequel::Postgres::JSONBHash, &Sequel.method(:pg_jsonb))
48
39
 
49
- JSONBOp = Dry::Types::Definition
50
- .new(Sequel::Postgres::JSONBOp)
51
- .constructor(Sequel.method(:pg_jsonb))
40
+ JSONBOp = Types.Constructor(Sequel::Postgres::JSONBOp, &Sequel.method(:pg_jsonb))
52
41
 
53
42
  JSONB = JSONBArray | JSONBHash | JSONBOp
54
43
 
55
- Bytea = Dry::Types::Definition
56
- .new(Sequel::SQL::Blob)
57
- .constructor(Sequel::SQL::Blob.method(:new))
44
+ Bytea = Types.Constructor(Sequel::SQL::Blob, &Sequel::SQL::Blob.method(:new))
58
45
 
59
- # MONEY
46
+ IPAddressR = Types.Constructor(IPAddr) { |ip| IPAddr.new(ip.to_s) }
47
+
48
+ IPAddress = Types.Constructor(IPAddr, &:to_s).meta(read: IPAddressR)
60
49
 
61
50
  Money = Types::Decimal
51
+
52
+ Point = ::Struct.new(:x, :y)
53
+
54
+ PointTR = Types.Constructor(Point) do |p|
55
+ x, y = p.to_s[1...-1].split(',', 2)
56
+ Point.new(Float(x), Float(y))
57
+ end
58
+
59
+ PointT = Types.Constructor(Point) { |p| "(#{ p.x },#{ p.y })" }.meta(read: PointTR)
62
60
  end
63
61
  end
64
62
  end
@@ -0,0 +1 @@
1
+ require 'rom/sql/extensions/sqlite/inferrer'
@@ -0,0 +1,10 @@
1
+ require 'rom/sql/schema/inferrer'
2
+
3
+ module ROM
4
+ module SQL
5
+ class Schema
6
+ class SqliteInferrer < Inferrer[:sqlite]
7
+ end
8
+ end
9
+ end
10
+ end
@@ -20,8 +20,12 @@ module ROM
20
20
  end
21
21
 
22
22
  def method_missing(meth, *args)
23
- if func && func.respond_to?(meth)
24
- meta(func: func.__send__(meth, *args))
23
+ if func
24
+ if func.respond_to?(meth)
25
+ meta(func: func.__send__(meth, *args))
26
+ else
27
+ super
28
+ end
25
29
  else
26
30
  meta(func: Sequel::SQL::Function.new(meth.to_s.upcase, *args))
27
31
  end
@@ -10,7 +10,7 @@ module ROM
10
10
  if schema.key?(meth)
11
11
  schema[meth]
12
12
  else
13
- ::Sequel::VIRTUAL_ROW.__send__(meth, *args, &block)
13
+ ::Sequel::VIRTUAL_ROW.__send__(meth.to_s.upcase, *args, &block)
14
14
  end
15
15
  end
16
16
  end
@@ -12,9 +12,10 @@ module ROM
12
12
  include InstanceMethods
13
13
  defines :associations
14
14
 
15
- associations []
15
+ associations Hash.new
16
16
 
17
17
  option :associations, reader: true, optional: true, default: -> cmd { cmd.class.associations }
18
+ option :configured_associations, reader: true, optional: true, default: proc { [] }
18
19
  end
19
20
  super
20
21
  end
@@ -58,7 +59,17 @@ module ROM
58
59
 
59
60
  # @api public
60
61
  def with_association(name, opts = EMPTY_HASH)
61
- self.class.build(relation, options.merge(associations: [[name, opts]]))
62
+ self.class.build(
63
+ relation, options.merge(associations: associations.merge(name => opts))
64
+ )
65
+ end
66
+
67
+ def associations_configured?
68
+ if configured_associations.empty?
69
+ false
70
+ else
71
+ configured_associations.all? { |name| associations.key?(name) }
72
+ end
62
73
  end
63
74
 
64
75
  # @api private
@@ -73,7 +84,13 @@ module ROM
73
84
  # @api public
74
85
  def build(relation, options = EMPTY_HASH)
75
86
  command = super
87
+
88
+ if command.associations_configured?
89
+ return command
90
+ end
91
+
76
92
  associations = command.associations
93
+ assoc_names = []
77
94
 
78
95
  before_hooks = associations.each_with_object([]) do |(name, opts), acc|
79
96
  relation.associations.try(name) do |assoc|
@@ -83,6 +100,8 @@ module ROM
83
100
  true
84
101
  end
85
102
  end or acc << { associate: { assoc: name, keys: opts[:key] } }
103
+
104
+ assoc_names << name
86
105
  end
87
106
 
88
107
  after_hooks = associations.each_with_object([]) do |(name, opts), acc|
@@ -92,10 +111,14 @@ module ROM
92
111
 
93
112
  if assoc.is_a?(Association::ManyToMany)
94
113
  acc << { associate: { assoc: assoc, keys: assoc.join_keys(relation.__registry__) } }
114
+ assoc_names << name
95
115
  end
96
116
  end
97
117
 
98
- command.before(*before_hooks).after(*after_hooks)
118
+ command.
119
+ with_opts(configured_associations: assoc_names).
120
+ before(*before_hooks).
121
+ after(*after_hooks)
99
122
  end
100
123
 
101
124
  # Set command to associate tuples with a parent tuple using provided keys
@@ -120,12 +143,12 @@ module ROM
120
143
  #
121
144
  # @api public
122
145
  def associates(name, options = EMPTY_HASH)
123
- if associations.map(&:first).include?(name)
146
+ if associations.key?(name)
124
147
  raise ArgumentError,
125
148
  "#{name} association is already defined for #{self.class}"
126
149
  end
127
150
 
128
- associations(associations.dup << [name, options])
151
+ associations(associations.merge(name => options))
129
152
  end
130
153
  end
131
154
  end