sequel 5.24.0 → 5.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +58 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/doc/cheat_sheet.rdoc +1 -0
  6. data/doc/postgresql.rdoc +2 -2
  7. data/doc/release_notes/5.25.0.txt +32 -0
  8. data/doc/release_notes/5.26.0.txt +35 -0
  9. data/doc/release_notes/5.27.0.txt +21 -0
  10. data/doc/release_notes/5.28.0.txt +16 -0
  11. data/doc/release_notes/5.29.0.txt +22 -0
  12. data/doc/testing.rdoc +11 -6
  13. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
  14. data/lib/sequel/adapters/postgres.rb +5 -1
  15. data/lib/sequel/adapters/shared/mssql.rb +4 -2
  16. data/lib/sequel/adapters/shared/mysql.rb +1 -1
  17. data/lib/sequel/adapters/shared/postgres.rb +15 -0
  18. data/lib/sequel/adapters/shared/sqlite.rb +7 -2
  19. data/lib/sequel/adapters/tinytds.rb +1 -1
  20. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  21. data/lib/sequel/database/schema_generator.rb +1 -1
  22. data/lib/sequel/database/transactions.rb +3 -3
  23. data/lib/sequel/dataset/features.rb +6 -0
  24. data/lib/sequel/dataset/misc.rb +2 -2
  25. data/lib/sequel/dataset/query.rb +15 -2
  26. data/lib/sequel/dataset/sql.rb +17 -4
  27. data/lib/sequel/extensions/any_not_empty.rb +45 -0
  28. data/lib/sequel/extensions/exclude_or_null.rb +68 -0
  29. data/lib/sequel/extensions/pg_array_ops.rb +10 -6
  30. data/lib/sequel/extensions/pg_enum.rb +4 -1
  31. data/lib/sequel/extensions/pg_json.rb +1 -1
  32. data/lib/sequel/extensions/pg_json_ops.rb +124 -0
  33. data/lib/sequel/extensions/pg_range.rb +9 -0
  34. data/lib/sequel/extensions/sql_comments.rb +2 -2
  35. data/lib/sequel/model/base.rb +12 -5
  36. data/lib/sequel/plugins/association_multi_add_remove.rb +83 -0
  37. data/lib/sequel/plugins/caching.rb +3 -0
  38. data/lib/sequel/plugins/csv_serializer.rb +26 -9
  39. data/lib/sequel/plugins/dirty.rb +3 -9
  40. data/lib/sequel/plugins/empty_failure_backtraces.rb +38 -0
  41. data/lib/sequel/plugins/json_serializer.rb +15 -4
  42. data/lib/sequel/plugins/nested_attributes.rb +7 -0
  43. data/lib/sequel/plugins/sharding.rb +11 -5
  44. data/lib/sequel/plugins/throw_failures.rb +1 -1
  45. data/lib/sequel/plugins/typecast_on_load.rb +3 -2
  46. data/lib/sequel/sql.rb +4 -1
  47. data/lib/sequel/version.rb +1 -1
  48. data/spec/adapters/postgres_spec.rb +82 -17
  49. data/spec/adapters/sqlite_spec.rb +1 -1
  50. data/spec/bin_spec.rb +1 -1
  51. data/spec/core/database_spec.rb +1 -1
  52. data/spec/core/dataset_spec.rb +0 -3
  53. data/spec/core/expression_filters_spec.rb +26 -7
  54. data/spec/core/spec_helper.rb +1 -1
  55. data/spec/core_extensions_spec.rb +1 -1
  56. data/spec/extensions/any_not_empty_spec.rb +23 -0
  57. data/spec/extensions/association_multi_add_remove_spec.rb +1041 -0
  58. data/spec/extensions/caller_logging_spec.rb +1 -1
  59. data/spec/extensions/dirty_spec.rb +33 -0
  60. data/spec/extensions/empty_failure_backtraces_spec.rb +60 -0
  61. data/spec/extensions/exclude_or_null_spec.rb +15 -0
  62. data/spec/extensions/json_serializer_spec.rb +10 -0
  63. data/spec/extensions/named_timezones_spec.rb +5 -5
  64. data/spec/extensions/nested_attributes_spec.rb +48 -0
  65. data/spec/extensions/pg_array_ops_spec.rb +3 -3
  66. data/spec/extensions/pg_json_ops_spec.rb +67 -0
  67. data/spec/extensions/pg_range_spec.rb +35 -21
  68. data/spec/extensions/sharding_spec.rb +8 -0
  69. data/spec/extensions/spec_helper.rb +1 -1
  70. data/spec/guards_helper.rb +1 -1
  71. data/spec/integration/associations_test.rb +1 -1
  72. data/spec/integration/dataset_test.rb +57 -17
  73. data/spec/integration/plugin_test.rb +1 -1
  74. data/spec/integration/schema_test.rb +9 -0
  75. data/spec/integration/spec_helper.rb +7 -1
  76. data/spec/model/plugins_spec.rb +2 -2
  77. data/spec/model/spec_helper.rb +1 -1
  78. data/spec/sequel_warning.rb +1 -0
  79. metadata +35 -3
@@ -230,6 +230,12 @@ module Sequel
230
230
  supports_cte_in_subqueries?
231
231
  end
232
232
 
233
+ # Whether the dataset supports the FILTER clause for aggregate functions.
234
+ # If not, support is emulated using CASE.
235
+ def supports_filtered_aggregates?
236
+ false
237
+ end
238
+
233
239
  # Whether the database supports quoting function names.
234
240
  def supports_quoted_function_names?
235
241
  false
@@ -329,7 +329,7 @@ module Sequel
329
329
  end
330
330
 
331
331
  # Set the db, opts, and cache for the copy of the dataset.
332
- def initialize_copy(c)
332
+ def initialize_clone(c, _=nil)
333
333
  @db = c.db
334
334
  @opts = Hash[c.opts]
335
335
  if cols = c.cache_get(:_columns)
@@ -338,7 +338,7 @@ module Sequel
338
338
  @cache = {}
339
339
  end
340
340
  end
341
- alias initialize_clone initialize_copy
341
+ alias initialize_copy initialize_clone
342
342
 
343
343
  # Internal recursive version of unqualified_column_for, handling Strings inside
344
344
  # of other objects.
@@ -1237,6 +1237,20 @@ module Sequel
1237
1237
  self
1238
1238
  end
1239
1239
 
1240
+ # If invert is true, invert the condition.
1241
+ def _invert_filter(cond, invert)
1242
+ if invert
1243
+ SQL::BooleanExpression.invert(cond)
1244
+ else
1245
+ cond
1246
+ end
1247
+ end
1248
+
1249
+ # Add the given filter condition. Arguments:
1250
+ # clause :: Symbol or which SQL clause to effect, should be :where or :having
1251
+ # cond :: The filter condition to add
1252
+ # invert :: Whether the condition should be inverted (true or false)
1253
+ # combine :: How to combine the condition with an existing condition, should be :AND or :OR
1240
1254
  def add_filter(clause, cond, invert=false, combine=:AND, &block)
1241
1255
  if cond == EMPTY_ARRAY && !block
1242
1256
  raise Error, "must provide an argument to a filtering method if not passing a block"
@@ -1256,8 +1270,7 @@ module Sequel
1256
1270
  cond = nil
1257
1271
  end
1258
1272
 
1259
- cond = filter_expr(cond, &block)
1260
- cond = SQL::BooleanExpression.invert(cond) if invert
1273
+ cond = _invert_filter(filter_expr(cond, &block), invert)
1261
1274
  cond = SQL::BooleanExpression.new(combine, @opts[clause], cond) if @opts[clause]
1262
1275
 
1263
1276
  if cond.nil?
@@ -492,11 +492,24 @@ module Sequel
492
492
  end
493
493
 
494
494
  sql << '('
495
+ if filter = opts[:filter]
496
+ filter = filter_expr(filter, &opts[:filter_block])
497
+ end
495
498
  if opts[:*]
496
- sql << '*'
499
+ if filter && !supports_filtered_aggregates?
500
+ literal_append(sql, Sequel.case({filter=>1}, nil))
501
+ filter = nil
502
+ else
503
+ sql << '*'
504
+ end
497
505
  else
498
506
  sql << "DISTINCT " if opts[:distinct]
499
- expression_list_append(sql, f.args)
507
+ if filter && !supports_filtered_aggregates?
508
+ expression_list_append(sql, f.args.map{|arg| Sequel.case({filter=>arg}, nil)})
509
+ filter = nil
510
+ else
511
+ expression_list_append(sql, f.args)
512
+ end
500
513
  if order = opts[:order]
501
514
  sql << " ORDER BY "
502
515
  expression_list_append(sql, order)
@@ -510,9 +523,9 @@ module Sequel
510
523
  sql << ')'
511
524
  end
512
525
 
513
- if filter = opts[:filter]
526
+ if filter
514
527
  sql << " FILTER (WHERE "
515
- literal_append(sql, filter_expr(filter, &opts[:filter_block]))
528
+ literal_append(sql, filter)
516
529
  sql << ')'
517
530
  end
518
531
 
@@ -0,0 +1,45 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The any_not_empty extension changes the behavior of Dataset#any?
4
+ # if called without a block. By default, this method uses the
5
+ # standard Enumerable behavior of enumerating results and seeing
6
+ # if any result is not false or nil. With this extension, it
7
+ # just checks whether the dataset is empty. This approach can
8
+ # be much faster if the dataset is currently large.
9
+ #
10
+ # DB[:table].any?
11
+ # # SELECT * FROM table
12
+ #
13
+ # DB[:table].extension(:any_not_empty).any?
14
+ # # SELECT 1 as one FROM table LIMIT 1
15
+ #
16
+ # You can load this extension into specific datasets:
17
+ #
18
+ # ds = DB[:table]
19
+ # ds = ds.extension(:any_not_empty)
20
+ #
21
+ # Or you can load it into all of a database's datasets, which
22
+ # is probably the desired behavior if you are using this extension:
23
+ #
24
+ # DB.extension(:any_not_empty)
25
+ #
26
+ # Note that this can result in any? returning a different result if
27
+ # the dataset has a row_proc that can return false or nil.
28
+ #
29
+ # Related module: Sequel::AnyNotEmpty
30
+
31
+ #
32
+ module Sequel
33
+ module AnyNotEmpty
34
+ # If a block is not given, return whether the dataset is not empty.
35
+ def any?
36
+ if block_given?
37
+ super
38
+ else
39
+ !empty?
40
+ end
41
+ end
42
+ end
43
+
44
+ Dataset.register_extension(:any_not_empty, AnyNotEmpty)
45
+ end
@@ -0,0 +1,68 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The exclude_or_null extension adds Dataset#exclude_or_null and
4
+ # Dataset#exclude_or_null_having. These methods are similar to
5
+ # Dataset#exclude and Dataset#exclude_having, except that they
6
+ # will also exclude rows where the condition IS NULL.
7
+ #
8
+ # DB[:table].exclude_or_null(foo: 1)
9
+ # # SELECT * FROM table WHERE NOT coalesce((foo = 1), false)
10
+ #
11
+ # DB[:table].exclude_or_null{foo(bar) =~ 1}
12
+ # # SELECT * FROM table HAVING NOT coalesce((foo(bar) = 1), false))
13
+ #
14
+ # You can load this extension into specific datasets:
15
+ #
16
+ # ds = DB[:table]
17
+ # ds = ds.extension(:exclude_or_null)
18
+ #
19
+ # Or you can load it into all of a database's datasets, which
20
+ # is probably the desired behavior if you are using this extension:
21
+ #
22
+ # DB.extension(:exclude_or_null)
23
+ #
24
+ # Note, this extension works correctly on PostgreSQL, SQLite, MySQL,
25
+ # H2, and HSQLDB. However, it does not work correctly on Microsoft SQL Server,
26
+ # Oracle, DB2, SQLAnywhere, or Derby.
27
+ #
28
+ # Related module: Sequel::ExcludeOrNull
29
+
30
+ #
31
+ module Sequel
32
+ module ExcludeOrNull
33
+ # Performs the inverse of Dataset#where, but also excludes rows where the given
34
+ # condition IS NULL.
35
+ #
36
+ # DB[:items].exclude_or_null(category: 'software')
37
+ # # SELECT * FROM items WHERE NOT coalesce((category = 'software'), false)
38
+ #
39
+ # DB[:items].exclude_or_null(category: 'software', id: 3)
40
+ # # SELECT * FROM items WHERE NOT coalesce(((category = 'software') AND (id = 3)), false)
41
+ def exclude_or_null(*cond, &block)
42
+ add_filter(:where, cond, :or_null, &block)
43
+ end
44
+
45
+ # The same as exclude_or_null, but affecting the HAVING clause instead of the
46
+ # WHERE clause.
47
+ #
48
+ # DB[:items].select_group(:name).exclude_or_null_having{count(name) < 2}
49
+ # # SELECT name FROM items GROUP BY name HAVING NOT coalesce((count(name) < 2), true)
50
+ def exclude_or_null_having(*cond, &block)
51
+ add_filter(:having, cond, :or_null, &block)
52
+ end
53
+
54
+ private
55
+
56
+ # Recognize :or_null value for invert, returning an expression for
57
+ # the invert of the condition or the condition being null.
58
+ def _invert_filter(cond, invert)
59
+ if invert == :or_null
60
+ ~SQL::Function.new(:coalesce, cond, SQL::Constants::SQLFALSE)
61
+ else
62
+ super
63
+ end
64
+ end
65
+ end
66
+
67
+ Dataset.register_extension(:exclude_or_null, ExcludeOrNull)
68
+ end
@@ -51,8 +51,8 @@
51
51
  # ia.length(2) # array_length(int_array_column, 2)
52
52
  # ia.lower # array_lower(int_array_column, 1)
53
53
  # ia.lower(2) # array_lower(int_array_column, 2)
54
- # ia.join # array_to_string(int_array_column, '', NULL)
55
- # ia.join(':') # array_to_string(int_array_column, ':', NULL)
54
+ # ia.join # array_to_string(int_array_column, '')
55
+ # ia.join(':') # array_to_string(int_array_column, ':')
56
56
  # ia.join(':', ' ') # array_to_string(int_array_column, ':', ' ')
57
57
  # ia.unnest # unnest(int_array_column)
58
58
  # ia.unnest(:b) # unnest(int_array_column, b)
@@ -217,12 +217,16 @@ module Sequel
217
217
 
218
218
  # Call the array_to_string method:
219
219
  #
220
- # array_op.join # array_to_string(array, '', NULL)
221
- # array_op.to_string # array_to_string(array, '', NULL)
222
- # array_op.join(":") # array_to_string(array, ':', NULL)
220
+ # array_op.join # array_to_string(array, '')
221
+ # array_op.to_string # array_to_string(array, '')
222
+ # array_op.join(":") # array_to_string(array, ':')
223
223
  # array_op.join(":", "*") # array_to_string(array, ':', '*')
224
224
  def to_string(joiner="", null=nil)
225
- function(:array_to_string, joiner, null)
225
+ if null.nil?
226
+ function(:array_to_string, joiner)
227
+ else
228
+ function(:array_to_string, joiner, null)
229
+ end
226
230
  end
227
231
  alias join to_string
228
232
 
@@ -133,8 +133,11 @@ module Sequel
133
133
  # the pg_type table to get names and array oids for
134
134
  # enums.
135
135
  def parse_enum_labels
136
+ order = [:enumtypid]
137
+ order << :enumsortorder if server_version >= 90100
138
+
136
139
  enum_labels = metadata_dataset.from(:pg_enum).
137
- order(:enumtypid, :enumsortorder).
140
+ order(*order).
138
141
  select_hash_groups(Sequel.cast(:enumtypid, Integer).as(:v), :enumlabel).freeze
139
142
  enum_labels.each_value(&:freeze)
140
143
 
@@ -12,7 +12,7 @@
12
12
  #
13
13
  # Note that wrapping JSON primitives changes the behavior for
14
14
  # JSON false and null values. Because only +false+ and +nil+
15
- # in Ruby are considered falesy, wrapping these objects results
15
+ # in Ruby are considered falsey, wrapping these objects results
16
16
  # in unexpected behavior if you use the values directly in
17
17
  # conditionals:
18
18
  #
@@ -73,6 +73,23 @@
73
73
  # j.pretty # jsonb_pretty(jsonb_column)
74
74
  # j.set(%w'0 a', :h) # jsonb_set(jsonb_column, ARRAY['0','a'], h, true)
75
75
  #
76
+ # On PostgreSQL 12+ SQL/JSON functions and operators are supported:
77
+ #
78
+ # j.path_exists('$.foo') # (jsonb_column @? '$.foo')
79
+ # j.path_match('$.foo') # (jsonb_column @@ '$.foo')
80
+ #
81
+ # j.path_exists!('$.foo') # jsonb_path_exists(jsonb_column, '$.foo')
82
+ # j.path_match!('$.foo') # jsonb_path_match(jsonb_column, '$.foo')
83
+ # j.path_query('$.foo') # jsonb_path_query(jsonb_column, '$.foo')
84
+ # j.path_query_array('$.foo') # jsonb_path_query_array(jsonb_column, '$.foo')
85
+ # j.path_query_first('$.foo') # jsonb_path_query_first(jsonb_column, '$.foo')
86
+ #
87
+ # For the PostgreSQL 12+ SQL/JSON functions, one argument is required (+path+) and
88
+ # two more arguments are optional (+vars+ and +silent+). +path+ specifies the JSON path.
89
+ # +vars+ specifies a hash or a string in JSON format of named variables to be
90
+ # substituted in +path+. +silent+ specifies whether errors are suppressed. By default,
91
+ # errors are not suppressed.
92
+ #
76
93
  # If you are also using the pg_json extension, you should load it before
77
94
  # loading this extension. Doing so will allow you to use the #op method on
78
95
  # JSONHash, JSONHarray, JSONBHash, and JSONBArray, allowing you to perform json/jsonb operations
@@ -292,6 +309,8 @@ module Sequel
292
309
  CONTAINED_BY = ["(".freeze, " <@ ".freeze, ")".freeze].freeze
293
310
  DELETE_PATH = ["(".freeze, " #- ".freeze, ")".freeze].freeze
294
311
  HAS_KEY = ["(".freeze, " ? ".freeze, ")".freeze].freeze
312
+ PATH_EXISTS = ["(".freeze, " @? ".freeze, ")".freeze].freeze
313
+ PATH_MATCH = ["(".freeze, " @@ ".freeze, ")".freeze].freeze
295
314
 
296
315
  # jsonb expression for deletion of the given argument from the
297
316
  # current jsonb.
@@ -362,6 +381,95 @@ module Sequel
362
381
  self.class.new(function(:insert, wrap_input_array(path), wrap_input_jsonb(other), insert_after))
363
382
  end
364
383
 
384
+ # Returns whether the JSON path returns any item for the json object.
385
+ #
386
+ # json_op.path_exists("$.foo") # (json @? '$.foo')
387
+ def path_exists(path)
388
+ bool_op(PATH_EXISTS, path)
389
+ end
390
+
391
+ # Returns whether the JSON path returns any item for the json object.
392
+ #
393
+ # json_op.path_exists!("$.foo")
394
+ # # jsonb_path_exists(json, '$.foo')
395
+ #
396
+ # json_op.path_exists!("$.foo ? ($ > $x)", x: 2)
397
+ # # jsonb_path_exists(json, '$.foo ? ($ > $x)', '{"x":2}')
398
+ #
399
+ # json_op.path_exists!("$.foo ? ($ > $x)", {x: 2}, true)
400
+ # # jsonb_path_exists(json, '$.foo ? ($ > $x)', '{"x":2}', true)
401
+ def path_exists!(path, vars=nil, silent=nil)
402
+ Sequel::SQL::BooleanExpression.new(:NOOP, _path_function(:jsonb_path_exists, path, vars, silent))
403
+ end
404
+
405
+ # Returns the first item of the result of JSON path predicate check for the json object.
406
+ # Returns nil if the first item is not true or false.
407
+ #
408
+ # json_op.path_match("$.foo") # (json @@ '$.foo')
409
+ def path_match(path)
410
+ bool_op(PATH_MATCH, path)
411
+ end
412
+
413
+ # Returns the first item of the result of JSON path predicate check for the json object.
414
+ # Returns nil if the first item is not true or false and silent is true.
415
+ #
416
+ # json_op.path_match!("$.foo")
417
+ # # jsonb_path_match(json, '$.foo')
418
+ #
419
+ # json_op.path_match!("$.foo ? ($ > $x)", x: 2)
420
+ # # jsonb_path_match(json, '$.foo ? ($ > $x)', '{"x":2}')
421
+ #
422
+ # json_op.path_match!("$.foo ? ($ > $x)", {x: 2}, true)
423
+ # # jsonb_path_match(json, '$.foo ? ($ > $x)', '{"x":2}', true)
424
+ def path_match!(path, vars=nil, silent=nil)
425
+ Sequel::SQL::BooleanExpression.new(:NOOP, _path_function(:jsonb_path_match, path, vars, silent))
426
+ end
427
+
428
+ # Returns a set of all jsonb values specified by the JSON path
429
+ # for the json object.
430
+ #
431
+ # json_op.path_query("$.foo")
432
+ # # jsonb_path_query(json, '$.foo')
433
+ #
434
+ # json_op.path_query("$.foo ? ($ > $x)", x: 2)
435
+ # # jsonb_path_query(json, '$.foo ? ($ > $x)', '{"x":2}')
436
+ #
437
+ # json_op.path_query("$.foo ? ($ > $x)", {x: 2}, true)
438
+ # # jsonb_path_query(json, '$.foo ? ($ > $x)', '{"x":2}', true)
439
+ def path_query(path, vars=nil, silent=nil)
440
+ _path_function(:jsonb_path_query, path, vars, silent)
441
+ end
442
+
443
+ # Returns a jsonb array of all values specified by the JSON path
444
+ # for the json object.
445
+ #
446
+ # json_op.path_query_array("$.foo")
447
+ # # jsonb_path_query_array(json, '$.foo')
448
+ #
449
+ # json_op.path_query_array("$.foo ? ($ > $x)", x: 2)
450
+ # # jsonb_path_query_array(json, '$.foo ? ($ > $x)', '{"x":2}')
451
+ #
452
+ # json_op.path_query_array("$.foo ? ($ > $x)", {x: 2}, true)
453
+ # # jsonb_path_query_array(json, '$.foo ? ($ > $x)', '{"x":2}', true)
454
+ def path_query_array(path, vars=nil, silent=nil)
455
+ JSONBOp.new(_path_function(:jsonb_path_query_array, path, vars, silent))
456
+ end
457
+
458
+ # Returns the first item of the result specified by the JSON path
459
+ # for the json object.
460
+ #
461
+ # json_op.path_query_first("$.foo")
462
+ # # jsonb_path_query_first(json, '$.foo')
463
+ #
464
+ # json_op.path_query_first("$.foo ? ($ > $x)", x: 2)
465
+ # # jsonb_path_query_first(json, '$.foo ? ($ > $x)', '{"x":2}')
466
+ #
467
+ # json_op.path_query_first("$.foo ? ($ > $x)", {x: 2}, true)
468
+ # # jsonb_path_query_first(json, '$.foo ? ($ > $x)', '{"x":2}', true)
469
+ def path_query_first(path, vars=nil, silent=nil)
470
+ JSONBOp.new(_path_function(:jsonb_path_query_first, path, vars, silent))
471
+ end
472
+
365
473
  # Return the receiver, since it is already a JSONBOp.
366
474
  def pg_jsonb
367
475
  self
@@ -386,6 +494,22 @@ module Sequel
386
494
 
387
495
  private
388
496
 
497
+ # Internals of the jsonb SQL/JSON path functions.
498
+ def _path_function(func, path, vars, silent)
499
+ args = []
500
+ if vars
501
+ if vars.is_a?(Hash)
502
+ vars = vars.to_json
503
+ end
504
+ args << vars
505
+
506
+ unless silent.nil?
507
+ args << silent
508
+ end
509
+ end
510
+ SQL::Function.new(func, self, path, *args)
511
+ end
512
+
389
513
  # Return a placeholder literal with the given str and args, wrapped
390
514
  # in a boolean expression, used by operators that return booleans.
391
515
  def bool_op(str, other)
@@ -402,6 +402,15 @@ module Sequel
402
402
  end
403
403
  alias == eql?
404
404
 
405
+ # Make sure equal ranges have the same hash.
406
+ def hash
407
+ if @empty
408
+ @db_type.hash
409
+ else
410
+ [@begin, @end, @exclude_begin, @exclude_end, @db_type].hash
411
+ end
412
+ end
413
+
405
414
  # Allow PGRange values in case statements, where they return true if they
406
415
  # are equal to each other using eql?, or if this PGRange can be converted
407
416
  # to a Range, delegating to that range.
@@ -9,8 +9,8 @@
9
9
  # #
10
10
  #
11
11
  # As you can see, this uses single line SQL comments (--) suffixed
12
- # by a newline. This # plugin transforms all consecutive
13
- # whitespace in the comment to # a single string:
12
+ # by a newline. This plugin transforms all consecutive whitespace
13
+ # in the comment to a single string:
14
14
  #
15
15
  # ds = DB[:table].comment("Some\r\nComment Here").all
16
16
  # # SELECT * FROM table -- Some Comment Here