rom-sql 2.0.0.beta2 → 2.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 (170) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/lib/rom/plugins/relation/sql/postgres/explain.rb +54 -0
  4. data/lib/rom/sql.rb +1 -1
  5. data/lib/rom/sql/attribute.rb +17 -18
  6. data/lib/rom/sql/errors.rb +3 -0
  7. data/lib/rom/sql/extensions/mysql.rb +1 -1
  8. data/lib/rom/sql/extensions/mysql/type_builder.rb +28 -0
  9. data/lib/rom/sql/extensions/postgres.rb +3 -1
  10. data/lib/rom/sql/extensions/postgres/commands.rb +30 -13
  11. data/lib/rom/sql/extensions/postgres/{attributes_inferrer.rb → type_builder.rb} +24 -28
  12. data/lib/rom/sql/extensions/postgres/type_serializer.rb +39 -0
  13. data/lib/rom/sql/extensions/postgres/types.rb +24 -477
  14. data/lib/rom/sql/extensions/postgres/types/array.rb +163 -0
  15. data/lib/rom/sql/extensions/postgres/types/geometric.rb +135 -0
  16. data/lib/rom/sql/extensions/postgres/types/json.rb +235 -0
  17. data/lib/rom/sql/extensions/postgres/types/network.rb +15 -0
  18. data/lib/rom/sql/extensions/sqlite.rb +1 -1
  19. data/lib/rom/sql/extensions/sqlite/{attributes_inferrer.rb → type_builder.rb} +5 -5
  20. data/lib/rom/sql/extensions/sqlite/types.rb +8 -3
  21. data/lib/rom/sql/foreign_key.rb +17 -0
  22. data/lib/rom/sql/function.rb +86 -8
  23. data/lib/rom/sql/gateway.rb +26 -26
  24. data/lib/rom/sql/index.rb +4 -0
  25. data/lib/rom/sql/migration.rb +3 -3
  26. data/lib/rom/sql/migration/inline_runner.rb +9 -83
  27. data/lib/rom/sql/migration/migrator.rb +35 -12
  28. data/lib/rom/sql/migration/recorder.rb +21 -0
  29. data/lib/rom/sql/migration/runner.rb +115 -0
  30. data/lib/rom/sql/migration/schema_diff.rb +108 -53
  31. data/lib/rom/sql/migration/writer.rb +61 -0
  32. data/lib/rom/sql/relation.rb +2 -1
  33. data/lib/rom/sql/relation/reading.rb +63 -3
  34. data/lib/rom/sql/relation/writing.rb +38 -0
  35. data/lib/rom/sql/schema.rb +9 -3
  36. data/lib/rom/sql/schema/attributes_inferrer.rb +3 -119
  37. data/lib/rom/sql/schema/inferrer.rb +99 -18
  38. data/lib/rom/sql/schema/type_builder.rb +94 -0
  39. data/lib/rom/sql/type_dsl.rb +30 -0
  40. data/lib/rom/sql/type_extensions.rb +11 -6
  41. data/lib/rom/sql/type_serializer.rb +46 -0
  42. data/lib/rom/sql/types.rb +12 -0
  43. data/lib/rom/sql/version.rb +1 -1
  44. metadata +26 -244
  45. data/.codeclimate.yml +0 -15
  46. data/.gitignore +0 -17
  47. data/.rspec +0 -3
  48. data/.travis.yml +0 -39
  49. data/.yardopts +0 -2
  50. data/Gemfile +0 -33
  51. data/Guardfile +0 -24
  52. data/LICENSE.txt +0 -22
  53. data/Rakefile +0 -19
  54. data/circle.yml +0 -10
  55. data/lib/rom/sql/extensions/mysql/attributes_inferrer.rb +0 -10
  56. data/lib/rom/sql/relation/sequel_api.rb +0 -133
  57. data/log/.gitkeep +0 -0
  58. data/rom-sql.gemspec +0 -29
  59. data/spec/extensions/postgres/attribute_spec.rb +0 -217
  60. data/spec/extensions/postgres/integration_spec.rb +0 -59
  61. data/spec/extensions/postgres/types_spec.rb +0 -252
  62. data/spec/extensions/sqlite/types_spec.rb +0 -11
  63. data/spec/fixtures/migrations/20150403090603_create_carrots.rb +0 -8
  64. data/spec/integration/associations/many_to_many/custom_fks_spec.rb +0 -76
  65. data/spec/integration/associations/many_to_many/from_view_spec.rb +0 -88
  66. data/spec/integration/associations/many_to_many_spec.rb +0 -162
  67. data/spec/integration/associations/many_to_one/custom_fks_spec.rb +0 -64
  68. data/spec/integration/associations/many_to_one/from_view_spec.rb +0 -84
  69. data/spec/integration/associations/many_to_one/self_ref_spec.rb +0 -53
  70. data/spec/integration/associations/many_to_one_spec.rb +0 -117
  71. data/spec/integration/associations/one_to_many/custom_fks_spec.rb +0 -54
  72. data/spec/integration/associations/one_to_many/from_view_spec.rb +0 -57
  73. data/spec/integration/associations/one_to_many/self_ref_spec.rb +0 -54
  74. data/spec/integration/associations/one_to_many_spec.rb +0 -86
  75. data/spec/integration/associations/one_to_one_spec.rb +0 -69
  76. data/spec/integration/associations/one_to_one_through_spec.rb +0 -92
  77. data/spec/integration/auto_migrations/errors_spec.rb +0 -31
  78. data/spec/integration/auto_migrations/indexes_spec.rb +0 -253
  79. data/spec/integration/auto_migrations/managing_columns_spec.rb +0 -156
  80. data/spec/integration/auto_migrations/postgres/column_types_spec.rb +0 -63
  81. data/spec/integration/combine_with_spec.rb +0 -43
  82. data/spec/integration/commands/create_spec.rb +0 -304
  83. data/spec/integration/commands/delete_spec.rb +0 -84
  84. data/spec/integration/commands/update_spec.rb +0 -90
  85. data/spec/integration/commands/upsert_spec.rb +0 -83
  86. data/spec/integration/gateway_spec.rb +0 -107
  87. data/spec/integration/migration_spec.rb +0 -55
  88. data/spec/integration/plugins/associates/many_to_many_spec.rb +0 -69
  89. data/spec/integration/plugins/associates_spec.rb +0 -250
  90. data/spec/integration/plugins/auto_restrictions_spec.rb +0 -74
  91. data/spec/integration/relation_schema_spec.rb +0 -271
  92. data/spec/integration/schema/call_spec.rb +0 -24
  93. data/spec/integration/schema/inferrer/mysql_spec.rb +0 -45
  94. data/spec/integration/schema/inferrer/postgres_spec.rb +0 -203
  95. data/spec/integration/schema/inferrer/sqlite_spec.rb +0 -37
  96. data/spec/integration/schema/inferrer_spec.rb +0 -390
  97. data/spec/integration/schema/prefix_spec.rb +0 -16
  98. data/spec/integration/schema/qualified_spec.rb +0 -16
  99. data/spec/integration/schema/rename_spec.rb +0 -21
  100. data/spec/integration/schema/view_spec.rb +0 -29
  101. data/spec/integration/sequel_api_spec.rb +0 -36
  102. data/spec/integration/setup_spec.rb +0 -26
  103. data/spec/integration/support/active_support_notifications_spec.rb +0 -24
  104. data/spec/integration/support/rails_log_subscriber_spec.rb +0 -30
  105. data/spec/integration/wrap_spec.rb +0 -91
  106. data/spec/shared/accounts.rb +0 -48
  107. data/spec/shared/database_setup.rb +0 -70
  108. data/spec/shared/notes.rb +0 -23
  109. data/spec/shared/posts.rb +0 -34
  110. data/spec/shared/puppies.rb +0 -15
  111. data/spec/shared/relations.rb +0 -8
  112. data/spec/shared/users.rb +0 -32
  113. data/spec/shared/users_and_tasks.rb +0 -50
  114. data/spec/spec_helper.rb +0 -122
  115. data/spec/support/env_helper.rb +0 -25
  116. data/spec/support/helpers.rb +0 -24
  117. data/spec/support/oracle/create_users.sql +0 -7
  118. data/spec/support/oracle/set_sys_passwords.sql +0 -2
  119. data/spec/support/test_configuration.rb +0 -16
  120. data/spec/unit/attribute_spec.rb +0 -104
  121. data/spec/unit/function_spec.rb +0 -48
  122. data/spec/unit/gateway_spec.rb +0 -70
  123. data/spec/unit/logger_spec.rb +0 -14
  124. data/spec/unit/migration_tasks_spec.rb +0 -111
  125. data/spec/unit/migrator_spec.rb +0 -25
  126. data/spec/unit/order_dsl_spec.rb +0 -43
  127. data/spec/unit/plugin/associates_spec.rb +0 -94
  128. data/spec/unit/plugin/pagination_spec.rb +0 -91
  129. data/spec/unit/plugin/timestamp_spec.rb +0 -117
  130. data/spec/unit/projection_dsl_spec.rb +0 -110
  131. data/spec/unit/relation/assoc_spec.rb +0 -87
  132. data/spec/unit/relation/associations_spec.rb +0 -27
  133. data/spec/unit/relation/avg_spec.rb +0 -11
  134. data/spec/unit/relation/by_pk_spec.rb +0 -62
  135. data/spec/unit/relation/dataset_spec.rb +0 -50
  136. data/spec/unit/relation/distinct_spec.rb +0 -15
  137. data/spec/unit/relation/exclude_spec.rb +0 -11
  138. data/spec/unit/relation/exist_predicate_spec.rb +0 -25
  139. data/spec/unit/relation/exists_spec.rb +0 -18
  140. data/spec/unit/relation/fetch_spec.rb +0 -21
  141. data/spec/unit/relation/group_spec.rb +0 -61
  142. data/spec/unit/relation/having_spec.rb +0 -22
  143. data/spec/unit/relation/inner_join_spec.rb +0 -158
  144. data/spec/unit/relation/inspect_spec.rb +0 -11
  145. data/spec/unit/relation/instrument_spec.rb +0 -45
  146. data/spec/unit/relation/invert_spec.rb +0 -11
  147. data/spec/unit/relation/left_join_spec.rb +0 -55
  148. data/spec/unit/relation/lock_spec.rb +0 -93
  149. data/spec/unit/relation/map_spec.rb +0 -16
  150. data/spec/unit/relation/max_spec.rb +0 -11
  151. data/spec/unit/relation/min_spec.rb +0 -11
  152. data/spec/unit/relation/order_spec.rb +0 -51
  153. data/spec/unit/relation/pluck_spec.rb +0 -11
  154. data/spec/unit/relation/prefix_spec.rb +0 -29
  155. data/spec/unit/relation/primary_key_spec.rb +0 -27
  156. data/spec/unit/relation/project_spec.rb +0 -24
  157. data/spec/unit/relation/qualified_columns_spec.rb +0 -30
  158. data/spec/unit/relation/qualified_spec.rb +0 -25
  159. data/spec/unit/relation/read_spec.rb +0 -25
  160. data/spec/unit/relation/rename_spec.rb +0 -23
  161. data/spec/unit/relation/right_join_spec.rb +0 -57
  162. data/spec/unit/relation/select_append_spec.rb +0 -21
  163. data/spec/unit/relation/select_spec.rb +0 -40
  164. data/spec/unit/relation/sum_spec.rb +0 -11
  165. data/spec/unit/relation/union_spec.rb +0 -19
  166. data/spec/unit/relation/unique_predicate_spec.rb +0 -18
  167. data/spec/unit/relation/where_spec.rb +0 -133
  168. data/spec/unit/restriction_dsl_spec.rb +0 -34
  169. data/spec/unit/schema_spec.rb +0 -25
  170. data/spec/unit/types_spec.rb +0 -65
@@ -0,0 +1,163 @@
1
+ require 'sequel/core'
2
+
3
+ Sequel.extension(*%i(pg_array pg_array_ops))
4
+
5
+ module ROM
6
+ module SQL
7
+ module Postgres
8
+ module Types
9
+ Array = SQL::Types::Array
10
+
11
+ ArrayRead = Array.constructor { |v| v.respond_to?(:to_ary) ? v.to_ary : v }
12
+
13
+ constructor = -> type { -> value { Sequel.pg_array(value, type) } }
14
+
15
+ @array_types = ::Hash.new do |hash, type|
16
+ name = "#{ type }[]"
17
+ array_type = Type(name, Array.constructor(constructor.(type))).
18
+ meta(type: type, read: ArrayRead)
19
+ TypeExtensions.register(array_type) { include ArrayMethods }
20
+ hash[type] = array_type
21
+ end
22
+
23
+ def self.Array(db_type)
24
+ @array_types[db_type]
25
+ end
26
+
27
+ # @!parse
28
+ # class SQL::Attribute
29
+ # # @!method contain(other)
30
+ # # Check whether the array includes another array
31
+ # # Translates to the @> operator
32
+ # #
33
+ # # @param [Array] other
34
+ # #
35
+ # # @return [SQL::Attribute<Types::Bool>]
36
+ # #
37
+ # # @api public
38
+ #
39
+ # # @!method get(idx)
40
+ # # Get element by index (PG uses 1-based indexing)
41
+ # #
42
+ # # @param [Integer] idx
43
+ # #
44
+ # # @return [SQL::Attribute]
45
+ # #
46
+ # # @api public
47
+ #
48
+ # # @!method any(value)
49
+ # # Check whether the array includes a value
50
+ # # Translates to the ANY operator
51
+ # #
52
+ # # @param [Object] value
53
+ # #
54
+ # # @return [SQL::Attribute<Types::Bool>]
55
+ # #
56
+ # # @api public
57
+ #
58
+ # # @!method contained_by(other)
59
+ # # Check whether the array is contained by another array
60
+ # # Translates to the <@ operator
61
+ # #
62
+ # # @param [Array] other
63
+ # #
64
+ # # @return [SQL::Attribute<Types::Bool>]
65
+ # #
66
+ # # @api public
67
+ #
68
+ # # @!method length
69
+ # # Return array size
70
+ # #
71
+ # # @return [SQL::Attribute<Types::Int>]
72
+ # #
73
+ # # @api public
74
+ #
75
+ # # @!method overlaps(other)
76
+ # # Check whether the arrays have common values
77
+ # # Translates to &&
78
+ # #
79
+ # # @param [Array] other
80
+ # #
81
+ # # @return [SQL::Attribute<Types::Bool>]
82
+ # #
83
+ # # @api public
84
+ #
85
+ # # @!method remove_value(value)
86
+ # # Remove elements by value
87
+ # #
88
+ # # @param [Object] value
89
+ # #
90
+ # # @return [SQL::Attribute<Types::PG::Array>]
91
+ # #
92
+ # # @api public
93
+ #
94
+ # # @!method join(delimiter, null_repr)
95
+ # # Convert the array to a string by joining
96
+ # # values with a delimiter (empty stirng by default)
97
+ # # and optional filler for NULL values
98
+ # # Translates to an `array_to_string` call
99
+ # #
100
+ # # @param [Object] delimiter
101
+ # # @param [Object] null
102
+ # #
103
+ # # @return [SQL::Attribute<Types::String>]
104
+ # #
105
+ # # @api public
106
+ #
107
+ # # @!method +(other)
108
+ # # Concatenate two arrays
109
+ # #
110
+ # # @param [Array] other
111
+ # #
112
+ # # @return [SQL::Attribute<Types::PG::Array>]
113
+ # #
114
+ # # @api public
115
+ # end
116
+ module ArrayMethods
117
+ def contain(type, expr, other)
118
+ Attribute[SQL::Types::Bool].meta(sql_expr: expr.pg_array.contains(type[other]))
119
+ end
120
+
121
+ def get(type, expr, idx)
122
+ Attribute[type].meta(sql_expr: expr.pg_array[idx])
123
+ end
124
+
125
+ def any(type, expr, value)
126
+ Attribute[SQL::Types::Bool].meta(sql_expr: { value => expr.pg_array.any })
127
+ end
128
+
129
+ def contained_by(type, expr, other)
130
+ Attribute[SQL::Types::Bool].meta(sql_expr: expr.pg_array.contained_by(type[other]))
131
+ end
132
+
133
+ def length(type, expr)
134
+ Attribute[SQL::Types::Int].meta(sql_expr: expr.pg_array.length)
135
+ end
136
+
137
+ def overlaps(type, expr, other_array)
138
+ Attribute[SQL::Types::Bool].meta(sql_expr: expr.pg_array.overlaps(type[other_array]))
139
+ end
140
+
141
+ def remove_value(type, expr, value)
142
+ Attribute[type].meta(sql_expr: expr.pg_array.remove(cast(type, value)))
143
+ end
144
+
145
+ def join(type, expr, delimiter = '', null = nil)
146
+ Attribute[SQL::Types::String].meta(sql_expr: expr.pg_array.join(delimiter, null))
147
+ end
148
+
149
+ def +(type, expr, other)
150
+ Attribute[type].meta(sql_expr: expr.pg_array.concat(other))
151
+ end
152
+
153
+ private
154
+
155
+ def cast(type, value)
156
+ db_type = type.optional? ? type.right.meta[:type] : type.meta[:type]
157
+ Sequel.cast(value, db_type)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,135 @@
1
+ module ROM
2
+ module SQL
3
+ module Postgres
4
+ module Values
5
+ Point = ::Struct.new(:x, :y)
6
+
7
+ Line = ::Struct.new(:a, :b, :c)
8
+
9
+ Circle = ::Struct.new(:center, :radius)
10
+
11
+ Box = ::Struct.new(:upper_right, :lower_left)
12
+
13
+ LineSegment = ::Struct.new(:begin, :end)
14
+
15
+ Path = ::Struct.new(:points, :type) do
16
+ def open?
17
+ type == :open
18
+ end
19
+
20
+ def closed?
21
+ type == :closed
22
+ end
23
+
24
+ def to_a
25
+ points
26
+ end
27
+ end
28
+ end
29
+
30
+ # @api public
31
+ module Types
32
+ # The list of geometric data types supported by PostgreSQL
33
+ # @see https://www.postgresql.org/docs/current/static/datatype-geometric.html
34
+
35
+ Point = SQL::Types.define(Values::Point) do
36
+ input do |point|
37
+ "(#{ point.x },#{ point.y })"
38
+ end
39
+
40
+ output do |point|
41
+ x, y = point.to_s[1...-1].split(',', 2)
42
+ Values::Point.new(Float(x), Float(y))
43
+ end
44
+ end
45
+
46
+ Line = SQL::Types.define(Values::Line) do
47
+ input do |line|
48
+ "{#{ line.a },#{ line.b },#{line.c}}"
49
+ end
50
+
51
+ output do |line|
52
+ a, b, c = line.to_s[1..-2].split(',', 3)
53
+ Values::Line.new(Float(a), Float(b), Float(c))
54
+ end
55
+ end
56
+
57
+ Circle = SQL::Types.define(Values::Circle) do
58
+ input do |circle|
59
+ "<(#{ circle.center.x },#{ circle.center.y }),#{ circle.radius }>"
60
+ end
61
+
62
+ output do |circle|
63
+ x, y, r = circle.to_s.tr('()<>', '').split(',', 3)
64
+ center = Values::Point.new(Float(x), Float(y))
65
+ Values::Circle.new(center, Float(r))
66
+ end
67
+ end
68
+
69
+ Box = SQL::Types.define(Values::Box) do
70
+ input do |box|
71
+ "((#{ box.upper_right.x },#{ box.upper_right.y }),"\
72
+ "(#{ box.lower_left.x },#{ box.lower_left.y }))"
73
+ end
74
+
75
+ output do |box|
76
+ x_right, y_right, x_left, y_left = box.to_s.tr('()', '').split(',', 4)
77
+ upper_right = Values::Point.new(Float(x_right), Float(y_right))
78
+ lower_left = Values::Point.new(Float(x_left), Float(y_left))
79
+ Values::Box.new(upper_right, lower_left)
80
+ end
81
+ end
82
+
83
+ LineSegment = SQL::Types.define(Values::LineSegment) do
84
+ input do |segment|
85
+ "[(#{ segment.begin.x },#{ segment.begin.y }),"\
86
+ "(#{ segment.end.x },#{ segment.end.y })]"
87
+ end
88
+
89
+ output do |segment|
90
+ x_begin, y_begin, x_end, y_end = segment.to_s.tr('()[]', '').split(',', 4)
91
+ point_begin = Values::Point.new(Float(x_begin), Float(y_begin))
92
+ point_end = Values::Point.new(Float(x_end), Float(y_end))
93
+ Values::LineSegment.new(point_begin, point_end)
94
+ end
95
+ end
96
+
97
+ Polygon = SQL::Types.define(::Array) do
98
+ input do |points|
99
+ points_joined = points.map { |p| "(#{ p.x },#{ p.y })" }.join(',')
100
+ "(#{ points_joined })"
101
+ end
102
+
103
+ output do |polygon|
104
+ coordinates = polygon.to_s.tr('()', '').split(',').each_slice(2)
105
+ coordinates.map { |x, y| Values::Point.new(Float(x), Float(y)) }
106
+ end
107
+ end
108
+
109
+ Path = SQL::Types.define(Values::Path) do
110
+ input do |path|
111
+ points_joined = path.to_a.map { |p| "(#{ p.x },#{ p.y })" }.join(',')
112
+
113
+ if path.open?
114
+ "[#{ points_joined }]"
115
+ else
116
+ "(#{ points_joined })"
117
+ end
118
+ end
119
+
120
+ output do |path|
121
+ open = path.to_s.start_with?('[') && path.to_s.end_with?(']')
122
+ coordinates = path.to_s.tr('()[]', '').split(',').each_slice(2)
123
+ points = coordinates.map { |x, y| Values::Point.new(Float(x), Float(y)) }
124
+
125
+ if open
126
+ Values::Path.new(points, :open)
127
+ else
128
+ Values::Path.new(points, :closed)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,235 @@
1
+ require 'sequel/core'
2
+
3
+ Sequel.extension(*%i(pg_json pg_json_ops))
4
+
5
+ module ROM
6
+ module SQL
7
+ module Postgres
8
+ module Types
9
+ JSONRead = (SQL::Types::Array | SQL::Types::Hash).constructor do |value|
10
+ if value.respond_to?(:to_hash)
11
+ value.to_hash
12
+ elsif value.respond_to?(:to_ary)
13
+ value.to_ary
14
+ else
15
+ value
16
+ end
17
+ end
18
+
19
+ JSON = Type('json') do
20
+ (SQL::Types::Array | SQL::Types::Hash)
21
+ .constructor(Sequel.method(:pg_json))
22
+ .meta(read: JSONRead)
23
+ end
24
+
25
+ JSONB = Type('jsonb') do
26
+ (SQL::Types::Array | SQL::Types::Hash)
27
+ .constructor(Sequel.method(:pg_jsonb))
28
+ .meta(read: JSONRead)
29
+ end
30
+
31
+ # @!parse
32
+ # class SQL::Attribute
33
+ # # @!method contain(value)
34
+ # # Check whether the JSON value includes a json value
35
+ # # Translates to the @> operator
36
+ # #
37
+ # # @example
38
+ # # people.where { fields.contain(gender: 'Female') }
39
+ # # people.where(people[:fields].contain([name: 'age']))
40
+ # # people.select { fields.contain(gender: 'Female').as(:is_female) }
41
+ # #
42
+ # # @param [Hash,Array,Object] value
43
+ # #
44
+ # # @return [SQL::Attribute<Types::Bool>]
45
+ # #
46
+ # # @api public
47
+ #
48
+ # # @!method contained_by(value)
49
+ # # Check whether the JSON value is contained by other value
50
+ # # Translates to the <@ operator
51
+ # #
52
+ # # @example
53
+ # # people.where { custom_values.contained_by(age: 25, foo: 'bar') }
54
+ # #
55
+ # # @param [Hash,Array] value
56
+ # #
57
+ # # @return [SQL::Attribute<Types::Bool>]
58
+ # #
59
+ # # @api public
60
+ #
61
+ # # @!method get(*path)
62
+ # # Extract the JSON value using at the specified path
63
+ # # Translates to -> or #> depending on the number of arguments
64
+ # #
65
+ # # @example
66
+ # # people.select { data.get('age').as(:person_age) }
67
+ # # people.select { fields.get(0).as(:first_field) }
68
+ # # people.select { fields.get('0', 'value').as(:first_field_value) }
69
+ # #
70
+ # # @param [Array<Integer>,Array<String>] path Path to extract
71
+ # #
72
+ # # @return [SQL::Attribute<Types::PG::JSON>,SQL::Attribute<Types::PG::JSONB>]
73
+ # #
74
+ # # @api public
75
+ #
76
+ # # @!method get_text(*path)
77
+ # # Extract the JSON value as text using at the specified path
78
+ # # Translates to ->> or #>> depending on the number of arguments
79
+ # #
80
+ # # @example
81
+ # # people.select { data.get('age').as(:person_age) }
82
+ # # people.select { fields.get(0).as(:first_field) }
83
+ # # people.select { fields.get('0', 'value').as(:first_field_value) }
84
+ # #
85
+ # # @param [Array<Integer>,Array<String>] path Path to extract
86
+ # #
87
+ # # @return [SQL::Attribute<Types::String>]
88
+ # #
89
+ # # @api public
90
+ #
91
+ # # @!method has_key(key)
92
+ # # Does the JSON value have the specified top-level key
93
+ # # Translates to ?
94
+ # #
95
+ # # @example
96
+ # # people.where { data.has_key('age') }
97
+ # #
98
+ # # @param [String] key
99
+ # #
100
+ # # @return [SQL::Attribute<Types::Bool>]
101
+ # #
102
+ # # @api public
103
+ #
104
+ # # @!method has_any_key(*keys)
105
+ # # Does the JSON value have any of the specified top-level keys
106
+ # # Translates to ?|
107
+ # #
108
+ # # @example
109
+ # # people.where { data.has_any_key('age', 'height') }
110
+ # #
111
+ # # @param [Array<String>] keys
112
+ # #
113
+ # # @return [SQL::Attribute<Types::Bool>]
114
+ # #
115
+ # # @api public
116
+ #
117
+ # # @!method has_all_keys(*keys)
118
+ # # Does the JSON value have all the specified top-level keys
119
+ # # Translates to ?&
120
+ # #
121
+ # # @example
122
+ # # people.where { data.has_all_keys('age', 'height') }
123
+ # #
124
+ # # @param [Array<String>] keys
125
+ # #
126
+ # # @return [SQL::Attribute<Types::Bool>]
127
+ # #
128
+ # # @api public
129
+ #
130
+ # # @!method merge(value)
131
+ # # Concatenate two JSON values
132
+ # # Translates to ||
133
+ # #
134
+ # # @example
135
+ # # people.select { data.merge(fetched_at: Time.now).as(:data) }
136
+ # # people.select { (fields + [name: 'height', value: 165]).as(:fields) }
137
+ # #
138
+ # # @param [Hash,Array] value
139
+ # #
140
+ # # @return [SQL::Attribute<Types::PG::JSONB>]
141
+ # #
142
+ # # @api public
143
+ #
144
+ # # @!method +(value)
145
+ # # An alias for SQL::Attribute<JSONB>#merge
146
+ # #
147
+ # # @api public
148
+ #
149
+ # # @!method delete(*path)
150
+ # # Deletes the specified value by key, index, or path
151
+ # # Translates to - or #- depending on the number of arguments
152
+ # #
153
+ # # @example
154
+ # # people.select { data.delete('age').as(:data_without_age) }
155
+ # # people.select { fields.delete(0).as(:fields_without_first) }
156
+ # # people.select { fields.delete(-1).as(:fields_without_last) }
157
+ # # people.select { data.delete('deeply', 'nested', 'value').as(:data) }
158
+ # # people.select { fields.delete('0', 'name').as(:data) }
159
+ # #
160
+ # # @param [Array<String>] path
161
+ # #
162
+ # # @return [SQL::Attribute<Types::PG::JSONB>]
163
+ # #
164
+ # # @api public
165
+ # end
166
+ module JSONMethods
167
+ def self.[](type, wrap)
168
+ parent = self
169
+ Module.new do
170
+ include parent
171
+ define_method(:json_type) { type }
172
+ define_method(:wrap, wrap)
173
+ end
174
+ end
175
+
176
+ def get(type, expr, *path)
177
+ Attribute[json_type].meta(sql_expr: wrap(expr)[path_args(path)])
178
+ end
179
+
180
+ def get_text(type, expr, *path)
181
+ Attribute[SQL::Types::String].meta(sql_expr: wrap(expr).get_text(path_args(path)))
182
+ end
183
+
184
+ private
185
+
186
+ def path_args(path)
187
+ case path.size
188
+ when 0 then raise ArgumentError, "wrong number of arguments (given 0, expected 1+)"
189
+ when 1 then path[0]
190
+ else path
191
+ end
192
+ end
193
+ end
194
+
195
+ TypeExtensions.register(JSON) do
196
+ include JSONMethods[JSON, :pg_json.to_proc]
197
+ end
198
+
199
+ TypeExtensions.register(JSONB) do
200
+ include JSONMethods[JSONB, :pg_jsonb.to_proc]
201
+
202
+ def contain(type, expr, value)
203
+ Attribute[SQL::Types::Bool].meta(sql_expr: wrap(expr).contains(value))
204
+ end
205
+
206
+ def contained_by(type, expr, value)
207
+ Attribute[SQL::Types::Bool].meta(sql_expr: wrap(expr).contained_by(value))
208
+ end
209
+
210
+ def has_key(type, expr, key)
211
+ Attribute[SQL::Types::Bool].meta(sql_expr: wrap(expr).has_key?(key))
212
+ end
213
+
214
+ def has_any_key(type, expr, *keys)
215
+ Attribute[SQL::Types::Bool].meta(sql_expr: wrap(expr).contain_any(keys))
216
+ end
217
+
218
+ def has_all_keys(type, expr, *keys)
219
+ Attribute[SQL::Types::Bool].meta(sql_expr: wrap(expr).contain_all(keys))
220
+ end
221
+
222
+ def merge(type, expr, value)
223
+ Attribute[JSONB].meta(sql_expr: wrap(expr).concat(value))
224
+ end
225
+ alias_method :+, :merge
226
+
227
+ def delete(type, expr, *path)
228
+ sql_expr = path.size == 1 ? wrap(expr) - path : wrap(expr).delete_path(path)
229
+ Attribute[JSONB].meta(sql_expr: sql_expr)
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end