sequel 3.36.1 → 3.37.0

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 (108) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +13 -0
  3. data/bin/sequel +12 -16
  4. data/doc/advanced_associations.rdoc +36 -67
  5. data/doc/association_basics.rdoc +11 -16
  6. data/doc/release_notes/3.37.0.txt +338 -0
  7. data/doc/schema_modification.rdoc +4 -0
  8. data/lib/sequel/adapters/jdbc/h2.rb +1 -1
  9. data/lib/sequel/adapters/jdbc/postgresql.rb +26 -8
  10. data/lib/sequel/adapters/mysql2.rb +4 -3
  11. data/lib/sequel/adapters/odbc/mssql.rb +2 -2
  12. data/lib/sequel/adapters/postgres.rb +4 -60
  13. data/lib/sequel/adapters/shared/mssql.rb +2 -1
  14. data/lib/sequel/adapters/shared/mysql.rb +0 -5
  15. data/lib/sequel/adapters/shared/postgres.rb +68 -2
  16. data/lib/sequel/adapters/shared/sqlite.rb +17 -1
  17. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +12 -1
  18. data/lib/sequel/adapters/utils/pg_types.rb +76 -0
  19. data/lib/sequel/core.rb +13 -0
  20. data/lib/sequel/database/misc.rb +41 -1
  21. data/lib/sequel/database/schema_generator.rb +23 -10
  22. data/lib/sequel/database/schema_methods.rb +26 -4
  23. data/lib/sequel/dataset/graph.rb +2 -1
  24. data/lib/sequel/dataset/query.rb +62 -2
  25. data/lib/sequel/extensions/_pretty_table.rb +7 -3
  26. data/lib/sequel/extensions/arbitrary_servers.rb +5 -4
  27. data/lib/sequel/extensions/blank.rb +4 -0
  28. data/lib/sequel/extensions/columns_introspection.rb +13 -2
  29. data/lib/sequel/extensions/core_extensions.rb +6 -0
  30. data/lib/sequel/extensions/eval_inspect.rb +158 -0
  31. data/lib/sequel/extensions/inflector.rb +4 -0
  32. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  33. data/lib/sequel/extensions/migration.rb +4 -1
  34. data/lib/sequel/extensions/named_timezones.rb +4 -0
  35. data/lib/sequel/extensions/null_dataset.rb +4 -0
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pg_array.rb +219 -168
  38. data/lib/sequel/extensions/pg_array_ops.rb +7 -2
  39. data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -4
  40. data/lib/sequel/extensions/pg_hstore.rb +3 -1
  41. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -2
  42. data/lib/sequel/extensions/pg_inet.rb +28 -3
  43. data/lib/sequel/extensions/pg_interval.rb +192 -0
  44. data/lib/sequel/extensions/pg_json.rb +21 -9
  45. data/lib/sequel/extensions/pg_range.rb +487 -0
  46. data/lib/sequel/extensions/pg_range_ops.rb +122 -0
  47. data/lib/sequel/extensions/pg_statement_cache.rb +3 -2
  48. data/lib/sequel/extensions/pretty_table.rb +12 -1
  49. data/lib/sequel/extensions/query.rb +4 -0
  50. data/lib/sequel/extensions/query_literals.rb +6 -6
  51. data/lib/sequel/extensions/schema_dumper.rb +39 -38
  52. data/lib/sequel/extensions/select_remove.rb +4 -0
  53. data/lib/sequel/extensions/server_block.rb +3 -2
  54. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  55. data/lib/sequel/extensions/sql_expr.rb +4 -0
  56. data/lib/sequel/extensions/string_date_time.rb +4 -0
  57. data/lib/sequel/extensions/thread_local_timezones.rb +9 -3
  58. data/lib/sequel/extensions/to_dot.rb +4 -0
  59. data/lib/sequel/model/associations.rb +150 -91
  60. data/lib/sequel/plugins/identity_map.rb +2 -2
  61. data/lib/sequel/plugins/list.rb +1 -0
  62. data/lib/sequel/plugins/many_through_many.rb +33 -32
  63. data/lib/sequel/plugins/nested_attributes.rb +11 -3
  64. data/lib/sequel/plugins/rcte_tree.rb +2 -2
  65. data/lib/sequel/plugins/schema.rb +1 -1
  66. data/lib/sequel/sql.rb +14 -14
  67. data/lib/sequel/version.rb +2 -2
  68. data/spec/adapters/mysql_spec.rb +25 -0
  69. data/spec/adapters/postgres_spec.rb +572 -28
  70. data/spec/adapters/sqlite_spec.rb +16 -1
  71. data/spec/core/database_spec.rb +61 -2
  72. data/spec/core/dataset_spec.rb +92 -0
  73. data/spec/core/expression_filters_spec.rb +12 -0
  74. data/spec/extensions/arbitrary_servers_spec.rb +1 -1
  75. data/spec/extensions/boolean_readers_spec.rb +25 -25
  76. data/spec/extensions/eval_inspect_spec.rb +58 -0
  77. data/spec/extensions/json_serializer_spec.rb +0 -6
  78. data/spec/extensions/list_spec.rb +1 -1
  79. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  80. data/spec/extensions/many_through_many_spec.rb +81 -0
  81. data/spec/extensions/nested_attributes_spec.rb +21 -4
  82. data/spec/extensions/pg_array_ops_spec.rb +1 -11
  83. data/spec/extensions/pg_array_spec.rb +181 -90
  84. data/spec/extensions/pg_auto_parameterize_spec.rb +3 -3
  85. data/spec/extensions/pg_hstore_spec.rb +1 -3
  86. data/spec/extensions/pg_inet_spec.rb +6 -1
  87. data/spec/extensions/pg_interval_spec.rb +73 -0
  88. data/spec/extensions/pg_json_spec.rb +5 -9
  89. data/spec/extensions/pg_range_ops_spec.rb +49 -0
  90. data/spec/extensions/pg_range_spec.rb +372 -0
  91. data/spec/extensions/pg_statement_cache_spec.rb +1 -2
  92. data/spec/extensions/query_literals_spec.rb +1 -2
  93. data/spec/extensions/schema_dumper_spec.rb +48 -89
  94. data/spec/extensions/serialization_spec.rb +1 -5
  95. data/spec/extensions/server_block_spec.rb +2 -2
  96. data/spec/extensions/spec_helper.rb +12 -2
  97. data/spec/extensions/split_array_nil_spec.rb +24 -0
  98. data/spec/integration/associations_test.rb +4 -4
  99. data/spec/integration/database_test.rb +2 -2
  100. data/spec/integration/dataset_test.rb +4 -4
  101. data/spec/integration/eager_loader_test.rb +6 -6
  102. data/spec/integration/plugin_test.rb +2 -2
  103. data/spec/integration/spec_helper.rb +2 -2
  104. data/spec/model/association_reflection_spec.rb +5 -0
  105. data/spec/model/associations_spec.rb +156 -49
  106. data/spec/model/eager_loading_spec.rb +137 -2
  107. data/spec/model/model_spec.rb +10 -10
  108. metadata +15 -2
@@ -0,0 +1,122 @@
1
+ # The pg_range_ops extension adds support to Sequel's DSL to make
2
+ # it easier to call PostgreSQL range functions and operators.
3
+ #
4
+ # To load the extension:
5
+ #
6
+ # Sequel.extension :pg_range_ops
7
+ #
8
+ # The most common usage is taking an object that represents an SQL
9
+ # identifier (such as a :symbol), and calling #pg_range on it:
10
+ #
11
+ # r = :range.pg_range
12
+ #
13
+ # This creates a Sequel::Postgres::RangeOp object that can be used
14
+ # for easier querying:
15
+ #
16
+ # r.contains(:other) # range @> other
17
+ # r.contained_by(:other) # range <@ other
18
+ # r.overlaps(:other) # range && other
19
+ # r.left_of(:other) # range << other
20
+ # r.right_of(:other) # range >> other
21
+ # r.starts_before(:other) # range &< other
22
+ # r.ends_after(:other) # range &> other
23
+ # r.adjacent_to(:other) # range -|- other
24
+ #
25
+ # r.lower # lower(range)
26
+ # r.upper # upper(range)
27
+ # r.isempty # isempty(range)
28
+ # r.lower_inc # lower_inc(range)
29
+ # r.upper_inc # upper_inc(range)
30
+ # r.lower_inf # lower_inf(range)
31
+ # r.upper_inf # upper_inf(range)
32
+ #
33
+ # See the PostgreSQL range function and operator documentation for more
34
+ # details on what these functions and operators do.
35
+ #
36
+ # If you are also using the pg_range extension, you should load it before
37
+ # loading this extension. Doing so will allow you to use PGArray#op to get
38
+ # an RangeOp, allowing you to perform range operations on range literals.
39
+
40
+ module Sequel
41
+ module Postgres
42
+ # The RangeOp class is a simple container for a single object that
43
+ # defines methods that yield Sequel expression objects representing
44
+ # PostgreSQL range operators and functions.
45
+ #
46
+ # Most methods in this class are defined via metaprogramming, see
47
+ # the pg_range_ops extension documentation for details on the API.
48
+ class RangeOp < Sequel::SQL::Wrapper
49
+ OPERATORS = {
50
+ :contains => ["(".freeze, " @> ".freeze, ")".freeze].freeze,
51
+ :contained_by => ["(".freeze, " <@ ".freeze, ")".freeze].freeze,
52
+ :left_of => ["(".freeze, " << ".freeze, ")".freeze].freeze,
53
+ :right_of => ["(".freeze, " >> ".freeze, ")".freeze].freeze,
54
+ :starts_before => ["(".freeze, " &< ".freeze, ")".freeze].freeze,
55
+ :ends_after => ["(".freeze, " &> ".freeze, ")".freeze].freeze,
56
+ :adjacent_to => ["(".freeze, " -|- ".freeze, ")".freeze].freeze,
57
+ :overlaps => ["(".freeze, " && ".freeze, ")".freeze].freeze,
58
+ }
59
+ FUNCTIONS = %w'lower upper isempty lower_inc upper_inc lower_inf upper_inf'
60
+
61
+ FUNCTIONS.each do |f|
62
+ class_eval("def #{f}; function(:#{f}) end", __FILE__, __LINE__)
63
+ end
64
+ OPERATORS.keys.each do |f|
65
+ class_eval("def #{f}(v); operator(:#{f}, v) end", __FILE__, __LINE__)
66
+ end
67
+
68
+ # These operators are already supported by the wrapper, but for ranges they
69
+ # return ranges, so wrap the results in another RangeOp.
70
+ %w'+ * -'.each do |f|
71
+ class_eval("def #{f}(v); RangeOp.new(super) end", __FILE__, __LINE__)
72
+ end
73
+
74
+ # Return the receiver.
75
+ def pg_range
76
+ self
77
+ end
78
+
79
+ private
80
+
81
+ # Create a boolen expression for the given type and argument.
82
+ def operator(type, other)
83
+ Sequel::SQL::BooleanExpression.new(:NOOP, Sequel::SQL::PlaceholderLiteralString.new(OPERATORS[type], [value, other]))
84
+ end
85
+
86
+ # Return a function called with the receiver.
87
+ def function(name)
88
+ Sequel::SQL::Function.new(name, self)
89
+ end
90
+ end
91
+
92
+ module RangeOpMethods
93
+ # Wrap the receiver in an RangeOp so you can easily use the PostgreSQL
94
+ # range functions and operators with it.
95
+ def pg_range
96
+ RangeOp.new(self)
97
+ end
98
+ end
99
+
100
+ if defined?(PGRange)
101
+ class PGRange
102
+ # Wrap the PGRange instance in an RangeOp, allowing you to easily use
103
+ # the PostgreSQL range functions and operators with literal ranges.
104
+ def op
105
+ RangeOp.new(self)
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ class SQL::GenericExpression
112
+ include Sequel::Postgres::RangeOpMethods
113
+ end
114
+
115
+ class LiteralString
116
+ include Sequel::Postgres::RangeOpMethods
117
+ end
118
+ end
119
+
120
+ class Symbol
121
+ include Sequel::Postgres::RangeOpMethods
122
+ end
@@ -3,8 +3,7 @@
3
3
  # executed repeatedly. When combined with the pg_auto_parameterize
4
4
  # extension, it can take Sequel code such as:
5
5
  #
6
- # DB.extend Sequel::Postgres::AutoParameterize::DatabaseMethods
7
- # DB.extend Sequel::Postgres::StatementCache::DatabaseMethods
6
+ # DB.extension :pg_auto_parameterize, :pg_statement_cache
8
7
  # DB[:table].filter(:a=>1)
9
8
  # DB[:table].filter(:a=>2)
10
9
  # DB[:table].filter(:a=>3)
@@ -313,4 +312,6 @@ module Sequel
313
312
  end
314
313
  end
315
314
  end
315
+
316
+ Database.register_extension(:pg_statement_cache, Postgres::StatementCache::DatabaseMethods)
316
317
  end
@@ -1,6 +1,17 @@
1
1
  # The pretty_table extension adds Sequel::Dataset#print and the
2
2
  # Sequel::PrettyTable class for creating nice-looking plain-text
3
- # tables.
3
+ # tables. Example:
4
+ #
5
+ # +--+-------+
6
+ # |id|name |
7
+ # |--+-------|
8
+ # |1 |fasdfas|
9
+ # |2 |test |
10
+ # +--+-------+
11
+ #
12
+ # To load the extension:
13
+ #
14
+ # Sequel.extension :pretty_table
4
15
 
5
16
  module Sequel
6
17
  extension :_pretty_table
@@ -1,6 +1,10 @@
1
1
  # The query extension adds Sequel::Dataset#query which allows
2
2
  # a different way to construct queries instead of the usual
3
3
  # method chaining.
4
+ #
5
+ # To load the extension, do:
6
+ #
7
+ # Sequel.extension :query
4
8
 
5
9
  module Sequel
6
10
  class Database
@@ -21,17 +21,15 @@
21
21
  # desirable in this area, but for backwards compatibility, the
22
22
  # defaults won't be changed until the next major release.
23
23
  #
24
- # Loading this extension does nothing by default except make the
25
- # Sequel::QueryLiterals module available. You can extend specific
26
- # datasets with this module:
24
+ # You can load this extension into specific datasets:
27
25
  #
28
26
  # ds = DB[:table]
29
- # ds.extend(Sequel::QueryLiterals)
27
+ # ds.extension(:query_literals)
30
28
  #
31
- # Order you can extend all of a database's datasets with it, which
29
+ # Or you can load it into all of a database's datasets, which
32
30
  # is probably the desired behavior if you are using this extension:
33
31
  #
34
- # DB.extend_datasets(Sequel::QueryLiterals)
32
+ # DB.extension(:query_literals)
35
33
 
36
34
  module Sequel
37
35
  # The QueryLiterals module can be used to make select, group, and
@@ -76,4 +74,6 @@ module Sequel
76
74
  end
77
75
  end
78
76
  end
77
+
78
+ Dataset.register_extension(:query_literals, QueryLiterals)
79
79
  end
@@ -3,6 +3,10 @@
3
3
  # database (which can be the same type or a different type than
4
4
  # the current database). The main interface is through
5
5
  # Sequel::Database#dump_schema_migration.
6
+ #
7
+ # To load the extension:
8
+ #
9
+ # Sequel.extension :schema_dumper
6
10
 
7
11
  module Sequel
8
12
  class Database
@@ -17,7 +21,7 @@ module Sequel
17
21
  ts = tables(options)
18
22
  <<END_MIG
19
23
  Sequel.migration do
20
- up do
24
+ change do
21
25
  #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_foreign_keys(t)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
22
26
  end
23
27
  end
@@ -26,38 +30,35 @@ END_MIG
26
30
 
27
31
  # Dump indexes for all tables as a migration. This complements
28
32
  # the :indexes=>false option to dump_schema_migration. Options:
29
- # * :same_db - Create a dump for the same database type, so
30
- # don't ignore errors if the index statements fail.
31
- # * :index_names - If set to false, don't record names of indexes. If
32
- # set to :namespace, prepend the table name to the index name if the
33
- # database does not use a global index namespace.
33
+ # :same_db :: Create a dump for the same database type, so
34
+ # don't ignore errors if the index statements fail.
35
+ # :index_names :: If set to false, don't record names of indexes. If
36
+ # set to :namespace, prepend the table name to the index name if the
37
+ # database does not use a global index namespace.
34
38
  def dump_indexes_migration(options={})
35
39
  ts = tables(options)
36
40
  <<END_MIG
37
41
  Sequel.migration do
38
- up do
42
+ change do
39
43
  #{ts.sort_by{|t| t.to_s}.map{|t| dump_table_indexes(t, :add_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
40
44
  end
41
-
42
- down do
43
- #{ts.sort_by{|t| t.to_s}.reverse.map{|t| dump_table_indexes(t, :drop_index, options)}.reject{|x| x == ''}.join("\n\n").gsub(/^/o, ' ')}
44
- end
45
45
  end
46
46
  END_MIG
47
47
  end
48
48
 
49
49
  # Return a string that contains a Sequel::Migration subclass that when
50
50
  # run would recreate the database structure. Options:
51
- # * :same_db - Don't attempt to translate database types to ruby types.
52
- # If this isn't set to true, all database types will be translated to
53
- # ruby types, but there is no guarantee that the migration generated
54
- # will yield the same type. Without this set, types that aren't
55
- # recognized will be translated to a string-like type.
56
- # * :foreign_keys - If set to false, don't dump foreign_keys
57
- # * :indexes - If set to false, don't dump indexes (they can be added
58
- # later via dump_index_migration).
59
- # * :index_names - If set to false, don't record names of indexes. If
60
- # set to :namespace, prepend the table name to the index name.
51
+ # :same_db :: Don't attempt to translate database types to ruby types.
52
+ # If this isn't set to true, all database types will be translated to
53
+ # ruby types, but there is no guarantee that the migration generated
54
+ # will yield the same type. Without this set, types that aren't
55
+ # recognized will be translated to a string-like type.
56
+ # :foreign_keys :: If set to false, don't dump foreign_keys (they can be
57
+ # added later via #dump_foreign_key_migration)
58
+ # :indexes :: If set to false, don't dump indexes (they can be added
59
+ # later via #dump_index_migration).
60
+ # :index_names :: If set to false, don't record names of indexes. If
61
+ # set to :namespace, prepend the table name to the index name.
61
62
  def dump_schema_migration(options={})
62
63
  options = options.dup
63
64
  if options[:indexes] == false && !options.has_key?(:foreign_keys)
@@ -78,13 +79,9 @@ END_MIG
78
79
 
79
80
  <<END_MIG
80
81
  Sequel.migration do
81
- up do
82
+ change do
82
83
  #{ts.map{|t| dump_table_schema(t, options)}.join("\n\n").gsub(/^/o, ' ')}#{"\n \n" if skipped_fks}#{skipped_fks}
83
84
  end
84
-
85
- down do
86
- drop_table(#{ts.reverse.inspect[1...-1]})
87
- end
88
85
  end
89
86
  END_MIG
90
87
  end
@@ -113,16 +110,15 @@ END_MIG
113
110
  end
114
111
  end
115
112
 
116
- # Convert the given name and parsed database schema into an array with a method
117
- # name and arguments to it to pass to a Schema::Generator to recreate the column.
118
- def column_schema_to_generator_opts(name, schema, options)
113
+ # Recreate the column in the passed Schema::Generator from the given name and parsed database schema.
114
+ def recreate_column(name, schema, gen, options)
119
115
  if options[:single_pk] && schema_autoincrementing_primary_key?(schema)
120
116
  type_hash = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
121
117
  [:table, :key, :on_delete, :on_update, :deferrable].each{|f| type_hash[f] = schema[f] if schema[f]}
122
118
  if type_hash == {:type=>Integer} || type_hash == {:type=>"integer"}
123
- [:primary_key, name]
119
+ gen.primary_key(name)
124
120
  else
125
- [:primary_key, name, type_hash]
121
+ gen.primary_key(name, type_hash)
126
122
  end
127
123
  else
128
124
  col_opts = options[:same_db] ? {:type=>schema[:db_type]} : column_schema_to_ruby_type(schema)
@@ -137,9 +133,14 @@ END_MIG
137
133
  col_opts[:null] = false if schema[:allow_null] == false
138
134
  if table = schema[:table]
139
135
  [:key, :on_delete, :on_update, :deferrable].each{|f| col_opts[f] = schema[f] if schema[f]}
140
- [:foreign_key, name, table, col_opts]
136
+ col_opts[:type] = type unless type == Integer || type == 'integer'
137
+ gen.foreign_key(name, table, col_opts)
141
138
  else
142
- [:column, name, type, col_opts]
139
+ gen.column(name, type, col_opts)
140
+ if (type == Integer || type == Bignum) && schema[:db_type] =~ / unsigned\z/io
141
+ Sequel.extension :eval_inspect
142
+ gen.check(Sequel::SQL::Identifier.new(name) >= 0)
143
+ end
143
144
  end
144
145
  end
145
146
  end
@@ -197,7 +198,7 @@ END_MIG
197
198
  # string that would add the foreign keys if run in a migration.
198
199
  def dump_add_fk_constraints(table, fks)
199
200
  sfks = "alter_table(#{table.inspect}) do\n"
200
- sfks << Schema::Generator.new(self) do
201
+ sfks << create_table_generator do
201
202
  fks.sort_by{|fk| fk[:columns].map{|c| c.to_s}}.each do |fk|
202
203
  foreign_key fk[:columns], fk
203
204
  end
@@ -229,7 +230,7 @@ END_MIG
229
230
  s = schema(table).dup
230
231
  pks = s.find_all{|x| x.last[:primary_key] == true}.map{|x| x.first}
231
232
  options = options.merge(:single_pk=>true) if pks.length == 1
232
- m = method(:column_schema_to_generator_opts)
233
+ m = method(:recreate_column)
233
234
  im = method(:index_to_generator_opts)
234
235
 
235
236
  if options[:indexes] != false
@@ -269,8 +270,8 @@ END_MIG
269
270
  end
270
271
  end
271
272
 
272
- Schema::Generator.new(self) do
273
- s.each{|name, info| send(*m.call(name, info, options))}
273
+ create_table_generator do
274
+ s.each{|name, info| m.call(name, info, self, options)}
274
275
  primary_key(pks) if !@primary_key && pks.length > 0
275
276
  indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts, options))} if indexes
276
277
  composite_fks.each{|fk| send(:foreign_key, fk[:columns], fk)} if composite_fks
@@ -286,7 +287,7 @@ END_MIG
286
287
  return ''
287
288
  end
288
289
  im = method(:index_to_generator_opts)
289
- gen = Schema::Generator.new(self) do
290
+ gen = create_table_generator do
290
291
  indexes.each{|iname, iopts| send(:index, iopts[:columns], im.call(table, iname, iopts, options))}
291
292
  end
292
293
  gen.dump_indexes(meth=>table, :ignore_errors=>!options[:same_db])
@@ -1,6 +1,10 @@
1
1
  # The select_remove extension adds Sequel::Dataset#select_remove for removing existing selected
2
2
  # columns from a dataset. It's not part of Sequel core as it is rarely needed and has
3
3
  # some corner cases where it can't work correctly.
4
+ #
5
+ # To load the extension:
6
+ #
7
+ # Sequel.extension :select_remove
4
8
 
5
9
  module Sequel
6
10
  class Dataset
@@ -4,8 +4,7 @@
4
4
  #
5
5
  # First, you need to enable it on the database object:
6
6
  #
7
- # Sequel.extension :server_block
8
- # DB.extend Sequel::ServerBlock
7
+ # DB.extension :server_block
9
8
  #
10
9
  # Then you can call with_server:
11
10
  #
@@ -136,4 +135,6 @@ module Sequel
136
135
  end
137
136
  end
138
137
  end
138
+
139
+ Database.register_extension(:server_block, ServerBlock)
139
140
  end
@@ -0,0 +1,65 @@
1
+ # The split_array_nil extension overrides Sequel's default handling of
2
+ # IN/NOT IN with arrays of values to do specific nil checking. For example,
3
+ #
4
+ # ds = DB[:table].where(:column=>[1, nil])
5
+ #
6
+ # By default, that produces the following SQL:
7
+ #
8
+ # SELECT * FROM table WHERE (column IN (1, NULL))
9
+ #
10
+ # However, because NULL = NULL is not true in SQL (it is NULL), this
11
+ # will not return rows in the table where the column is NULL. This
12
+ # extension allows for an alternative behavior more similar to ruby,
13
+ # which will return rows in the table where the column is NULL, using
14
+ # a query like:
15
+ #
16
+ # SELECT * FROM table WHERE ((column IN (1)) OR (column IS NULL)))
17
+ #
18
+ # Similarly, for NOT IN queries:
19
+ #
20
+ # ds = DB[:table].exclude(:column=>[1, nil])
21
+ # # Default:
22
+ # # SELECT * FROM table WHERE (column NOT IN (1, NULL))
23
+ # # with split_array_nils extension:
24
+ # # SELECT * FROM table WHERE ((column NOT IN (1)) AND (column IS NOT NULL)))
25
+ #
26
+ # To use this extension with a single dataset:
27
+ #
28
+ # ds = ds.extension(:split_array_nil)
29
+ #
30
+ # To use this extension for all of a database's datasets:
31
+ #
32
+ # DB.extension(:split_array_nil)
33
+
34
+ module Sequel
35
+ class Dataset
36
+ module SplitArrayNil
37
+ # Over the IN/NOT IN handling with an array of values where one of the
38
+ # values in the array is nil, by removing nils from the array of values,
39
+ # and using a separate OR IS NULL clause for IN or AND IS NOT NULL clause
40
+ # for NOT IN.
41
+ def complex_expression_sql_append(sql, op, args)
42
+ case op
43
+ when :IN, :"NOT IN"
44
+ vals = args.at(1)
45
+ if vals.is_a?(Array) && vals.any?{|v| v.nil?}
46
+ cols = args.at(0)
47
+ vals = vals.compact
48
+ c = Sequel::SQL::BooleanExpression
49
+ if op == :IN
50
+ literal_append(sql, c.new(:OR, c.new(:IN, cols, vals), c.new(:IS, cols, nil)))
51
+ else
52
+ literal_append(sql, c.new(:AND, c.new(:"NOT IN", cols, vals), c.new(:"IS NOT", cols, nil)))
53
+ end
54
+ else
55
+ super
56
+ end
57
+ else
58
+ super
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ Dataset.register_extension(:split_array_nil, Dataset::SplitArrayNil)
65
+ end