sequel 5.39.0 → 5.66.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +336 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +57 -25
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +13 -13
  7. data/doc/association_basics.rdoc +119 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/migration.rdoc +12 -6
  10. data/doc/model_hooks.rdoc +1 -1
  11. data/doc/object_model.rdoc +8 -8
  12. data/doc/opening_databases.rdoc +18 -11
  13. data/doc/postgresql.rdoc +16 -8
  14. data/doc/querying.rdoc +5 -3
  15. data/doc/release_notes/5.40.0.txt +40 -0
  16. data/doc/release_notes/5.41.0.txt +25 -0
  17. data/doc/release_notes/5.42.0.txt +136 -0
  18. data/doc/release_notes/5.43.0.txt +98 -0
  19. data/doc/release_notes/5.44.0.txt +32 -0
  20. data/doc/release_notes/5.45.0.txt +34 -0
  21. data/doc/release_notes/5.46.0.txt +87 -0
  22. data/doc/release_notes/5.47.0.txt +59 -0
  23. data/doc/release_notes/5.48.0.txt +14 -0
  24. data/doc/release_notes/5.49.0.txt +59 -0
  25. data/doc/release_notes/5.50.0.txt +78 -0
  26. data/doc/release_notes/5.51.0.txt +47 -0
  27. data/doc/release_notes/5.52.0.txt +87 -0
  28. data/doc/release_notes/5.53.0.txt +23 -0
  29. data/doc/release_notes/5.54.0.txt +27 -0
  30. data/doc/release_notes/5.55.0.txt +21 -0
  31. data/doc/release_notes/5.56.0.txt +51 -0
  32. data/doc/release_notes/5.57.0.txt +23 -0
  33. data/doc/release_notes/5.58.0.txt +31 -0
  34. data/doc/release_notes/5.59.0.txt +73 -0
  35. data/doc/release_notes/5.60.0.txt +22 -0
  36. data/doc/release_notes/5.61.0.txt +43 -0
  37. data/doc/release_notes/5.62.0.txt +132 -0
  38. data/doc/release_notes/5.63.0.txt +33 -0
  39. data/doc/release_notes/5.64.0.txt +50 -0
  40. data/doc/release_notes/5.65.0.txt +21 -0
  41. data/doc/release_notes/5.66.0.txt +24 -0
  42. data/doc/schema_modification.rdoc +1 -1
  43. data/doc/security.rdoc +9 -9
  44. data/doc/sql.rdoc +28 -16
  45. data/doc/testing.rdoc +22 -11
  46. data/doc/transactions.rdoc +6 -6
  47. data/doc/virtual_rows.rdoc +2 -2
  48. data/lib/sequel/adapters/ado/access.rb +1 -1
  49. data/lib/sequel/adapters/ado.rb +17 -17
  50. data/lib/sequel/adapters/amalgalite.rb +3 -5
  51. data/lib/sequel/adapters/ibmdb.rb +2 -2
  52. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  53. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  54. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  55. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  56. data/lib/sequel/adapters/jdbc.rb +16 -18
  57. data/lib/sequel/adapters/mysql.rb +80 -67
  58. data/lib/sequel/adapters/mysql2.rb +54 -49
  59. data/lib/sequel/adapters/odbc.rb +6 -2
  60. data/lib/sequel/adapters/oracle.rb +4 -3
  61. data/lib/sequel/adapters/postgres.rb +83 -40
  62. data/lib/sequel/adapters/shared/access.rb +11 -1
  63. data/lib/sequel/adapters/shared/db2.rb +30 -0
  64. data/lib/sequel/adapters/shared/mssql.rb +83 -7
  65. data/lib/sequel/adapters/shared/mysql.rb +47 -2
  66. data/lib/sequel/adapters/shared/oracle.rb +82 -1
  67. data/lib/sequel/adapters/shared/postgres.rb +430 -174
  68. data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
  69. data/lib/sequel/adapters/shared/sqlite.rb +116 -11
  70. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  71. data/lib/sequel/adapters/sqlite.rb +60 -18
  72. data/lib/sequel/adapters/tinytds.rb +1 -1
  73. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  74. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  75. data/lib/sequel/ast_transformer.rb +6 -0
  76. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  77. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  78. data/lib/sequel/connection_pool/single.rb +6 -8
  79. data/lib/sequel/connection_pool/threaded.rb +8 -8
  80. data/lib/sequel/connection_pool/timed_queue.rb +257 -0
  81. data/lib/sequel/connection_pool.rb +47 -30
  82. data/lib/sequel/core.rb +28 -18
  83. data/lib/sequel/database/connecting.rb +26 -2
  84. data/lib/sequel/database/misc.rb +69 -14
  85. data/lib/sequel/database/query.rb +73 -2
  86. data/lib/sequel/database/schema_generator.rb +45 -52
  87. data/lib/sequel/database/schema_methods.rb +17 -1
  88. data/lib/sequel/dataset/actions.rb +108 -14
  89. data/lib/sequel/dataset/features.rb +20 -0
  90. data/lib/sequel/dataset/misc.rb +12 -2
  91. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  92. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  93. data/lib/sequel/dataset/query.rb +118 -16
  94. data/lib/sequel/dataset/sql.rb +182 -47
  95. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  96. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  97. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  98. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  99. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  100. data/lib/sequel/extensions/blank.rb +8 -0
  101. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  102. data/lib/sequel/extensions/core_refinements.rb +36 -11
  103. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  104. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  105. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  106. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  107. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  108. data/lib/sequel/extensions/inflector.rb +9 -1
  109. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  110. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  111. data/lib/sequel/extensions/migration.rb +7 -2
  112. data/lib/sequel/extensions/named_timezones.rb +26 -6
  113. data/lib/sequel/extensions/pagination.rb +1 -1
  114. data/lib/sequel/extensions/pg_array.rb +23 -3
  115. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  116. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  117. data/lib/sequel/extensions/pg_enum.rb +1 -1
  118. data/lib/sequel/extensions/pg_extended_date_support.rb +28 -25
  119. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  120. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  121. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  122. data/lib/sequel/extensions/pg_inet.rb +10 -11
  123. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  124. data/lib/sequel/extensions/pg_interval.rb +45 -19
  125. data/lib/sequel/extensions/pg_json.rb +13 -15
  126. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  127. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  128. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  129. data/lib/sequel/extensions/pg_range.rb +10 -23
  130. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  131. data/lib/sequel/extensions/pg_row.rb +19 -13
  132. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  133. data/lib/sequel/extensions/query.rb +2 -0
  134. data/lib/sequel/extensions/s.rb +2 -1
  135. data/lib/sequel/extensions/schema_dumper.rb +13 -2
  136. data/lib/sequel/extensions/server_block.rb +8 -12
  137. data/lib/sequel/extensions/sql_comments.rb +110 -3
  138. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  139. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  140. data/lib/sequel/extensions/string_agg.rb +1 -1
  141. data/lib/sequel/extensions/string_date_time.rb +19 -23
  142. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  143. data/lib/sequel/model/associations.rb +345 -101
  144. data/lib/sequel/model/base.rb +51 -27
  145. data/lib/sequel/model/errors.rb +10 -1
  146. data/lib/sequel/model/inflections.rb +1 -1
  147. data/lib/sequel/model/plugins.rb +5 -0
  148. data/lib/sequel/plugins/association_proxies.rb +2 -0
  149. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  150. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  151. data/lib/sequel/plugins/auto_validations.rb +87 -15
  152. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  153. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  154. data/lib/sequel/plugins/column_encryption.rb +728 -0
  155. data/lib/sequel/plugins/composition.rb +10 -4
  156. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  157. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  158. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  159. data/lib/sequel/plugins/dirty.rb +1 -1
  160. data/lib/sequel/plugins/enum.rb +124 -0
  161. data/lib/sequel/plugins/finder.rb +4 -2
  162. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  163. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  164. data/lib/sequel/plugins/json_serializer.rb +39 -24
  165. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  166. data/lib/sequel/plugins/list.rb +3 -1
  167. data/lib/sequel/plugins/many_through_many.rb +109 -10
  168. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  169. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  170. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -1
  171. data/lib/sequel/plugins/prepared_statements.rb +10 -1
  172. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  173. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  174. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  175. data/lib/sequel/plugins/serialization.rb +9 -3
  176. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  177. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  178. data/lib/sequel/plugins/sql_comments.rb +189 -0
  179. data/lib/sequel/plugins/static_cache.rb +1 -1
  180. data/lib/sequel/plugins/subclasses.rb +28 -11
  181. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  182. data/lib/sequel/plugins/timestamps.rb +1 -1
  183. data/lib/sequel/plugins/unused_associations.rb +521 -0
  184. data/lib/sequel/plugins/update_or_create.rb +1 -1
  185. data/lib/sequel/plugins/validate_associated.rb +22 -12
  186. data/lib/sequel/plugins/validation_helpers.rb +38 -11
  187. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  188. data/lib/sequel/sql.rb +1 -1
  189. data/lib/sequel/timezones.rb +12 -14
  190. data/lib/sequel/version.rb +1 -1
  191. metadata +103 -43
@@ -44,11 +44,51 @@
44
44
  #
45
45
  # DB.extension(:sql_comments)
46
46
  #
47
+ # Loading the sql_comments extension into the database also adds
48
+ # support for block-level comment support via Database#with_comments.
49
+ # You call #with_comments with a hash. Queries inside the hash will
50
+ # include a comment based on the hash (assuming they are inside the
51
+ # same thread):
52
+ #
53
+ # DB.with_comments(model: Album, action: :all) do
54
+ # DB[:albums].all
55
+ # # SELECT * FROM albums -- model:Album,action:all
56
+ # end
57
+ #
58
+ # You can nest calls to #with_comments, which will combine the
59
+ # entries from both calls:
60
+ #
61
+ # DB.with_comments(application: App, path: :scrubbed_path) do
62
+ # DB.with_comments(model: Album, action: :all) do
63
+ # ds = DB[:albums].all
64
+ # # SELECT * FROM albums
65
+ # # -- application:App,path:scrubbed_path,model:Album,action:all
66
+ # end
67
+ # end
68
+ #
69
+ # You can override comment entries specified in earlier blocks, or
70
+ # remove entries specified earlier using a nil value:
71
+ #
72
+ # DB.with_comments(application: App, path: :scrubbed_path) do
73
+ # DB.with_comments(application: Foo, path: nil) do
74
+ # ds = DB[:albums].all
75
+ # # SELECT * FROM albums # -- application:Foo
76
+ # end
77
+ # end
78
+ #
79
+ # You can combine block-level comments with dataset-specific
80
+ # comments:
81
+ #
82
+ # DB.with_comments(model: Album, action: :all) do
83
+ # DB[:table].comment("Some Comment").all
84
+ # # SELECT * FROM albums -- model:Album,action:all -- Some Comment
85
+ # end
86
+ #
47
87
  # Note that Microsoft Access does not support inline comments,
48
88
  # and attempting to use comments on it will result in SQL syntax
49
89
  # errors.
50
90
  #
51
- # Related module: Sequel::SQLComments
91
+ # Related modules: Sequel::SQLComments, Sequel::Database::SQLComments
52
92
 
53
93
  #
54
94
  module Sequel
@@ -62,7 +102,7 @@ module Sequel
62
102
  %w'select insert update delete'.each do |type|
63
103
  define_method(:"#{type}_sql") do |*a|
64
104
  sql = super(*a)
65
- if comment = @opts[:comment]
105
+ if comment = _sql_comment
66
106
  # This assumes that the comment stored in the dataset has
67
107
  # already been formatted. If not, this could result in SQL
68
108
  # injection.
@@ -74,8 +114,10 @@ module Sequel
74
114
  if sql.frozen?
75
115
  sql += comment
76
116
  sql.freeze
77
- else
117
+ elsif @opts[:append_sql] || @opts[:placeholder_literalizer]
78
118
  sql << comment
119
+ else
120
+ sql += comment
79
121
  end
80
122
  end
81
123
  sql
@@ -84,6 +126,11 @@ module Sequel
84
126
 
85
127
  private
86
128
 
129
+ # The comment to include in the SQL query, if any.
130
+ def _sql_comment
131
+ @opts[:comment]
132
+ end
133
+
87
134
  # Format the comment. For maximum compatibility, this uses a
88
135
  # single line SQL comment, and converts all consecutive whitespace
89
136
  # in the comment to a single space.
@@ -92,5 +139,65 @@ module Sequel
92
139
  end
93
140
  end
94
141
 
142
+ module Database::SQLComments
143
+ def self.extended(db)
144
+ db.instance_variable_set(:@comment_hashes, {})
145
+ db.extend_datasets DatasetSQLComments
146
+ end
147
+
148
+ # A map of threads to comment hashes, used for correctly setting
149
+ # comments for all queries inside #with_comments blocks.
150
+ attr_reader :comment_hashes
151
+
152
+ # Store the comment hash and use it to create comments inside the block
153
+ def with_comments(comment_hash)
154
+ hashes = @comment_hashes
155
+ t = Sequel.current
156
+ new_hash = if hash = Sequel.synchronize{hashes[t]}
157
+ hash.merge(comment_hash)
158
+ else
159
+ comment_hash.dup
160
+ end
161
+ yield Sequel.synchronize{hashes[t] = new_hash}
162
+ ensure
163
+ if hash
164
+ Sequel.synchronize{hashes[t] = hash}
165
+ else
166
+ t && Sequel.synchronize{hashes.delete(t)}
167
+ end
168
+ end
169
+
170
+ module DatasetSQLComments
171
+ include Sequel::SQLComments
172
+
173
+ private
174
+
175
+ # Include comments added via Database#with_comments in the output SQL.
176
+ def _sql_comment
177
+ specific_comment = super
178
+ return specific_comment if @opts[:append_sql]
179
+
180
+ t = Sequel.current
181
+ hashes = db.comment_hashes
182
+ block_comment = if comment_hash = Sequel.synchronize{hashes[t]}
183
+ comment_array = comment_hash.map{|k,v| "#{k}:#{v}" unless v.nil?}
184
+ comment_array.compact!
185
+ comment_array.join(",")
186
+ end
187
+
188
+ if block_comment
189
+ if specific_comment
190
+ format_sql_comment(block_comment + specific_comment)
191
+ else
192
+ format_sql_comment(block_comment)
193
+ end
194
+ else
195
+ specific_comment
196
+ end
197
+ end
198
+ end
199
+ end
200
+
95
201
  Dataset.register_extension(:sql_comments, SQLComments)
202
+ Database.register_extension(:sql_comments, Database::SQLComments)
96
203
  end
@@ -0,0 +1,108 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The sql_log_normalizer extension normalizes the SQL that is logged,
4
+ # removing the literal strings and numbers in the SQL, and removing the
5
+ # logging of any bound variables:
6
+ #
7
+ # ds = DB[:table].first(a: 1, b: 'something')
8
+ # # Without sql_log_normalizer extension
9
+ # # SELECT * FROM "table" WHERE (("a" = 1) AND ("b" = 'something')) LIMIT 1
10
+ #
11
+ # # With sql_log_normalizer_extension
12
+ # # SELECT * FROM "table" WHERE (("a" = ?) AND ("b" = ?)) LIMIT ?
13
+ #
14
+ # The normalization is done by scanning the SQL string being executed
15
+ # for literal strings and numbers, and replacing them with question
16
+ # marks. While this should work for all or almost all production queries,
17
+ # there are pathlogical queries that will not be handled correctly, such as
18
+ # the use of apostrophes in identifiers:
19
+ #
20
+ # DB[:"asf'bar"].where(a: 1, b: 'something').first
21
+ # # Logged as:
22
+ # # SELECT * FROM "asf?something')) LIMIT ?
23
+ #
24
+ # The expected use case for this extension is when you want to normalize
25
+ # logs to group similar queries, or when you want to protect sensitive
26
+ # data from being stored in the logs.
27
+ #
28
+ # Related module: Sequel::SQLLogNormalizer
29
+
30
+ #
31
+ module Sequel
32
+ module SQLLogNormalizer
33
+ def self.extended(db)
34
+ type = case db.literal("'")
35
+ when "''''"
36
+ :standard
37
+ when "'\\''"
38
+ :backslash
39
+ when "N''''"
40
+ :n_standard
41
+ else
42
+ raise Error, "SQL log normalization is not supported on this database (' literalized as #{db.literal("'").inspect})"
43
+ end
44
+ db.instance_variable_set(:@sql_string_escape_type, type)
45
+ end
46
+
47
+ # Normalize the SQL before calling super.
48
+ def log_connection_yield(sql, conn, args=nil)
49
+ unless skip_logging?
50
+ sql = normalize_logged_sql(sql)
51
+ args = nil
52
+ end
53
+ super
54
+ end
55
+
56
+ # Replace literal strings and numbers in SQL with question mark placeholders.
57
+ def normalize_logged_sql(sql)
58
+ sql = sql.dup
59
+ sql.force_encoding('BINARY')
60
+ start_index = 0
61
+ check_n = @sql_string_escape_type == :n_standard
62
+ outside_string = true
63
+
64
+ if @sql_string_escape_type == :backslash
65
+ search_char = /[\\']/
66
+ escape_char_offset = 0
67
+ escape_char_value = 92 # backslash
68
+ else
69
+ search_char = "'"
70
+ escape_char_offset = 1
71
+ escape_char_value = 39 # apostrophe
72
+ end
73
+
74
+ # The approach used here goes against Sequel's philosophy of never attempting
75
+ # to parse SQL. However, parsing the SQL is basically the only way to implement
76
+ # this support with Sequel's design, and it's better to be pragmatic and accept
77
+ # this than not be able to support this.
78
+
79
+ # Replace literal strings
80
+ while outside_string && (index = start_index = sql.index("'", start_index))
81
+ if check_n && index != 0 && sql.getbyte(index-1) == 78 # N' start
82
+ start_index -= 1
83
+ end
84
+ index += 1
85
+ outside_string = false
86
+
87
+ while (index = sql.index(search_char, index)) && (sql.getbyte(index + escape_char_offset) == escape_char_value)
88
+ # skip escaped characters inside string literal
89
+ index += 2
90
+ end
91
+
92
+ if index
93
+ # Found end of string
94
+ sql[start_index..index] = '?'
95
+ start_index += 1
96
+ outside_string = true
97
+ end
98
+ end
99
+
100
+ # Replace integer and decimal floating point numbers
101
+ sql.gsub!(/\b-?\d+(?:\.\d+)?\b/, '?')
102
+
103
+ sql
104
+ end
105
+ end
106
+
107
+ Database.register_extension(:sql_log_normalizer, SQLLogNormalizer)
108
+ end
@@ -0,0 +1,255 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The sqlite_json_ops extension adds support to Sequel's DSL to make
4
+ # it easier to call SQLite JSON functions and operators (added
5
+ # first in SQLite 3.38.0).
6
+ #
7
+ # To load the extension:
8
+ #
9
+ # Sequel.extension :sqlite_json_ops
10
+ #
11
+ # This extension works by calling methods on Sequel::SQLite::JSONOp objects,
12
+ # which you can create via Sequel.sqlite_json_op:
13
+ #
14
+ # j = Sequel.sqlite_json_op(:json_column)
15
+ #
16
+ # Also, on most Sequel expression objects, you can call the sqlite_json_op method
17
+ # to create a Sequel::SQLite::JSONOp object:
18
+ #
19
+ # j = Sequel[:json_column].sqlite_json_op
20
+ #
21
+ # If you have loaded the {core_extensions extension}[rdoc-ref:doc/core_extensions.rdoc],
22
+ # or you have loaded the core_refinements extension
23
+ # and have activated refinements for the file, you can also use Symbol#sqlite_json_op:
24
+ #
25
+ # j = :json_column.sqlite_json_op
26
+ #
27
+ # The following methods are available for Sequel::SQLite::JSONOp instances:
28
+ #
29
+ # j[1] # (json_column ->> 1)
30
+ # j.get(1) # (json_column ->> 1)
31
+ # j.get_text(1) # (json_column -> 1)
32
+ # j.extract('$.a') # json_extract(json_column, '$.a')
33
+ #
34
+ # j.array_length # json_array_length(json_column)
35
+ # j.type # json_type(json_column)
36
+ # j.valid # json_valid(json_column)
37
+ # j.json # json(json_column)
38
+ #
39
+ # j.insert('$.a', 1) # json_insert(json_column, '$.a', 1)
40
+ # j.set('$.a', 1) # json_set(json_column, '$.a', 1)
41
+ # j.replace('$.a', 1) # json_replace(json_column, '$.a', 1)
42
+ # j.remove('$.a') # json_remove(json_column, '$.a')
43
+ # j.patch('{"a":2}') # json_patch(json_column, '{"a":2}')
44
+ #
45
+ # j.each # json_each(json_column)
46
+ # j.tree # json_tree(json_column)
47
+ #
48
+ # Related modules: Sequel::SQLite::JSONOp
49
+
50
+ #
51
+ module Sequel
52
+ module SQLite
53
+ # The JSONOp class is a simple container for a single object that
54
+ # defines methods that yield Sequel expression objects representing
55
+ # SQLite json operators and functions.
56
+ #
57
+ # In the method documentation examples, assume that:
58
+ #
59
+ # json_op = Sequel.sqlite_json_op(:json)
60
+ class JSONOp < Sequel::SQL::Wrapper
61
+ GET = ["(".freeze, " ->> ".freeze, ")".freeze].freeze
62
+ private_constant :GET
63
+
64
+ GET_JSON = ["(".freeze, " -> ".freeze, ")".freeze].freeze
65
+ private_constant :GET_JSON
66
+
67
+ # Returns an expression for getting the JSON array element or object field
68
+ # at the specified path as a SQLite value.
69
+ #
70
+ # json_op[1] # (json ->> 1)
71
+ # json_op['a'] # (json ->> 'a')
72
+ # json_op['$.a.b'] # (json ->> '$.a.b')
73
+ # json_op['$[1][2]'] # (json ->> '$[1][2]')
74
+ def [](key)
75
+ json_op(GET, key)
76
+ end
77
+ alias get []
78
+
79
+ # Returns an expression for the length of the JSON array, or the JSON array at
80
+ # the given path.
81
+ #
82
+ # json_op.array_length # json_array_length(json)
83
+ # json_op.array_length('$[1]') # json_array_length(json, '$[1]')
84
+ def array_length(*args)
85
+ Sequel::SQL::NumericExpression.new(:NOOP, function(:array_length, *args))
86
+ end
87
+
88
+ # Returns an expression for a set of information extracted from the top-level
89
+ # members of the JSON array or object, or the top-level members of the JSON array
90
+ # or object at the given path.
91
+ #
92
+ # json_op.each # json_each(json)
93
+ # json_op.each('$.a') # json_each(json, '$.a')
94
+ def each(*args)
95
+ function(:each, *args)
96
+ end
97
+
98
+ # Returns an expression for the JSON array element or object field at the specified
99
+ # path as a SQLite value, but only accept paths as arguments, and allow the use of
100
+ # multiple paths.
101
+ #
102
+ # json_op.extract('$.a') # json_extract(json, '$.a')
103
+ # json_op.extract('$.a', '$.b') # json_extract(json, '$.a', '$.b')
104
+ def extract(*a)
105
+ function(:extract, *a)
106
+ end
107
+
108
+ # Returns an expression for getting the JSON array element or object field at the
109
+ # specified path as a JSON value.
110
+ #
111
+ # json_op.get_json(1) # (json -> 1)
112
+ # json_op.get_json('a') # (json -> 'a')
113
+ # json_op.get_json('$.a.b') # (json -> '$.a.b')
114
+ # json_op.get_json('$[1][2]') # (json -> '$[1][2]')
115
+ def get_json(key)
116
+ self.class.new(json_op(GET_JSON, key))
117
+ end
118
+
119
+ # Returns an expression for creating new entries at the given paths in the JSON array
120
+ # or object, but not overwriting existing entries.
121
+ #
122
+ # json_op.insert('$.a', 1) # json_insert(json, '$.a', 1)
123
+ # json_op.insert('$.a', 1, '$.b', 2) # json_insert(json, '$.a', 1, '$.b', 2)
124
+ def insert(path, value, *args)
125
+ wrapped_function(:insert, path, value, *args)
126
+ end
127
+
128
+ # Returns an expression for a minified version of the JSON.
129
+ #
130
+ # json_op.json # json(json)
131
+ def json
132
+ self.class.new(SQL::Function.new(:json, self))
133
+ end
134
+ alias minify json
135
+
136
+ # Returns an expression for updating the JSON object using the RFC 7396 MergePatch algorithm
137
+ #
138
+ # json_op.patch('{"a": 1, "b": null}') # json_patch(json, '{"a": 1, "b": null}')
139
+ def patch(json_patch)
140
+ wrapped_function(:patch, json_patch)
141
+ end
142
+
143
+ # Returns an expression for removing entries at the given paths from the JSON array or object.
144
+ #
145
+ # json_op.remove('$.a') # json_remove(json, '$.a')
146
+ # json_op.remove('$.a', '$.b') # json_remove(json, '$.a', '$.b')
147
+ def remove(path, *paths)
148
+ wrapped_function(:remove, path, *paths)
149
+ end
150
+
151
+ # Returns an expression for replacing entries at the given paths in the JSON array or object,
152
+ # but not creating new entries.
153
+ #
154
+ # json_op.replace('$.a', 1) # json_replace(json, '$.a', 1)
155
+ # json_op.replace('$.a', 1, '$.b', 2) # json_replace(json, '$.a', 1, '$.b', 2)
156
+ def replace(path, value, *args)
157
+ wrapped_function(:replace, path, value, *args)
158
+ end
159
+
160
+ # Returns an expression for creating or replacing entries at the given paths in the
161
+ # JSON array or object.
162
+ #
163
+ # json_op.set('$.a', 1) # json_set(json, '$.a', 1)
164
+ # json_op.set('$.a', 1, '$.b', 2) # json_set(json, '$.a', 1, '$.b', 2)
165
+ def set(path, value, *args)
166
+ wrapped_function(:set, path, value, *args)
167
+ end
168
+
169
+ # Returns an expression for a set of information extracted from the JSON array or object, or
170
+ # the JSON array or object at the given path.
171
+ #
172
+ # json_op.tree # json_tree(json)
173
+ # json_op.tree('$.a') # json_tree(json, '$.a')
174
+ def tree(*args)
175
+ function(:tree, *args)
176
+ end
177
+
178
+ # Returns an expression for the type of the JSON value or the JSON value at the given path.
179
+ #
180
+ # json_op.type # json_type(json)
181
+ # json_op.type('$[1]') # json_type(json, '$[1]')
182
+ def type(*args)
183
+ Sequel::SQL::StringExpression.new(:NOOP, function(:type, *args))
184
+ end
185
+ alias typeof type
186
+
187
+ # Returns a boolean expression for whether the JSON is valid or not.
188
+ def valid
189
+ Sequel::SQL::BooleanExpression.new(:NOOP, function(:valid))
190
+ end
191
+
192
+ private
193
+
194
+ # Internals of the [], get, get_json methods, using a placeholder literal string.
195
+ def json_op(str, args)
196
+ self.class.new(Sequel::SQL::PlaceholderLiteralString.new(str, [self, args]))
197
+ end
198
+
199
+ # Internals of the methods that return functions prefixed with +json_+.
200
+ def function(name, *args)
201
+ SQL::Function.new("json_#{name}", self, *args)
202
+ end
203
+
204
+ # Internals of the methods that return functions prefixed with +json_+, that
205
+ # return JSON values.
206
+ def wrapped_function(*args)
207
+ self.class.new(function(*args))
208
+ end
209
+ end
210
+
211
+ module JSONOpMethods
212
+ # Wrap the receiver in an JSONOp so you can easily use the SQLite
213
+ # json functions and operators with it.
214
+ def sqlite_json_op
215
+ JSONOp.new(self)
216
+ end
217
+ end
218
+ end
219
+
220
+ module SQL::Builders
221
+ # Return the object wrapped in an SQLite::JSONOp.
222
+ def sqlite_json_op(v)
223
+ case v
224
+ when SQLite::JSONOp
225
+ v
226
+ else
227
+ SQLite::JSONOp.new(v)
228
+ end
229
+ end
230
+ end
231
+
232
+ class SQL::GenericExpression
233
+ include Sequel::SQLite::JSONOpMethods
234
+ end
235
+
236
+ class LiteralString
237
+ include Sequel::SQLite::JSONOpMethods
238
+ end
239
+ end
240
+
241
+ # :nocov:
242
+ if Sequel.core_extensions?
243
+ class Symbol
244
+ include Sequel::SQLite::JSONOpMethods
245
+ end
246
+ end
247
+
248
+ if defined?(Sequel::CoreRefinements)
249
+ module Sequel::CoreRefinements
250
+ refine Symbol do
251
+ send INCLUDE_METH, Sequel::SQLite::JSONOpMethods
252
+ end
253
+ end
254
+ end
255
+ # :nocov:
@@ -147,7 +147,7 @@ module Sequel
147
147
  def initialize(expr, separator=nil)
148
148
  @expr = expr
149
149
  @separator = separator
150
- yield self if block_given?
150
+ yield self if defined?(yield)
151
151
  freeze
152
152
  end
153
153
 
@@ -4,6 +4,10 @@
4
4
  # for converting the strings to a date (e.g. String#to_date), allowing
5
5
  # for backwards compatibility with legacy Sequel code.
6
6
  #
7
+ # These methods calls +parse+ on the related class, and as such, can
8
+ # result in denial of service in older versions of Ruby for large
9
+ # untrusted input, and raise exceptions in newer versions of Ruby.
10
+ #
7
11
  # To load the extension:
8
12
  #
9
13
  # Sequel.extension :string_date_time
@@ -11,42 +15,34 @@
11
15
  class String
12
16
  # Converts a string into a Date object.
13
17
  def to_date
14
- begin
15
- Date.parse(self, Sequel.convert_two_digit_years)
16
- rescue => e
17
- raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
18
- end
18
+ Date.parse(self, Sequel.convert_two_digit_years)
19
+ rescue => e
20
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
19
21
  end
20
22
 
21
23
  # Converts a string into a DateTime object.
22
24
  def to_datetime
23
- begin
24
- DateTime.parse(self, Sequel.convert_two_digit_years)
25
- rescue => e
26
- raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
27
- end
25
+ DateTime.parse(self, Sequel.convert_two_digit_years)
26
+ rescue => e
27
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
28
28
  end
29
29
 
30
30
  # Converts a string into a Time or DateTime object, depending on the
31
31
  # value of Sequel.datetime_class
32
32
  def to_sequel_time
33
- begin
34
- if Sequel.datetime_class == DateTime
35
- DateTime.parse(self, Sequel.convert_two_digit_years)
36
- else
37
- Sequel.datetime_class.parse(self)
38
- end
39
- rescue => e
40
- raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
33
+ if Sequel.datetime_class == DateTime
34
+ DateTime.parse(self, Sequel.convert_two_digit_years)
35
+ else
36
+ Sequel.datetime_class.parse(self)
41
37
  end
38
+ rescue => e
39
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
42
40
  end
43
41
 
44
42
  # Converts a string into a Time object.
45
43
  def to_time
46
- begin
47
- Time.parse(self)
48
- rescue => e
49
- raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
50
- end
44
+ Time.parse(self)
45
+ rescue => e
46
+ raise Sequel.convert_exception_class(e, Sequel::InvalidValue)
51
47
  end
52
48
  end
@@ -35,6 +35,7 @@ if RUBY_VERSION >= '2.0'
35
35
  class Symbol
36
36
  prepend Sequel::SymbolAref
37
37
  end
38
+ # :nocov:
38
39
  else
39
40
  class Symbol
40
41
  if method_defined?(:[])
@@ -51,3 +52,4 @@ else
51
52
  end
52
53
  end
53
54
  end
55
+ # :nocov: