rom-sql 1.0.0.beta2 → 1.0.0.beta3

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