sequel 3.23.0 → 3.24.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/CHANGELOG +64 -0
  2. data/doc/association_basics.rdoc +43 -5
  3. data/doc/model_hooks.rdoc +64 -27
  4. data/doc/prepared_statements.rdoc +8 -4
  5. data/doc/reflection.rdoc +8 -2
  6. data/doc/release_notes/3.23.0.txt +1 -1
  7. data/doc/release_notes/3.24.0.txt +420 -0
  8. data/lib/sequel/adapters/db2.rb +8 -1
  9. data/lib/sequel/adapters/firebird.rb +25 -9
  10. data/lib/sequel/adapters/informix.rb +4 -19
  11. data/lib/sequel/adapters/jdbc.rb +34 -17
  12. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  13. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  14. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  15. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  16. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  17. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  18. data/lib/sequel/adapters/postgres.rb +30 -1
  19. data/lib/sequel/adapters/shared/access.rb +10 -0
  20. data/lib/sequel/adapters/shared/informix.rb +45 -0
  21. data/lib/sequel/adapters/shared/mssql.rb +82 -8
  22. data/lib/sequel/adapters/shared/mysql.rb +25 -7
  23. data/lib/sequel/adapters/shared/postgres.rb +39 -6
  24. data/lib/sequel/adapters/shared/sqlite.rb +57 -5
  25. data/lib/sequel/adapters/sqlite.rb +8 -3
  26. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  27. data/lib/sequel/ast_transformer.rb +190 -0
  28. data/lib/sequel/core.rb +1 -1
  29. data/lib/sequel/database/misc.rb +6 -0
  30. data/lib/sequel/database/query.rb +33 -3
  31. data/lib/sequel/database/schema_methods.rb +6 -2
  32. data/lib/sequel/dataset/features.rb +6 -0
  33. data/lib/sequel/dataset/prepared_statements.rb +17 -2
  34. data/lib/sequel/dataset/query.rb +17 -0
  35. data/lib/sequel/dataset/sql.rb +2 -53
  36. data/lib/sequel/exceptions.rb +4 -0
  37. data/lib/sequel/extensions/to_dot.rb +95 -83
  38. data/lib/sequel/model.rb +5 -0
  39. data/lib/sequel/model/associations.rb +80 -14
  40. data/lib/sequel/model/base.rb +182 -55
  41. data/lib/sequel/model/exceptions.rb +3 -1
  42. data/lib/sequel/plugins/association_pks.rb +6 -4
  43. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  44. data/lib/sequel/plugins/many_through_many.rb +8 -3
  45. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  46. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  47. data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
  48. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  49. data/lib/sequel/sql.rb +8 -0
  50. data/lib/sequel/version.rb +1 -1
  51. data/spec/adapters/postgres_spec.rb +43 -18
  52. data/spec/core/connection_pool_spec.rb +56 -77
  53. data/spec/core/database_spec.rb +25 -0
  54. data/spec/core/dataset_spec.rb +127 -16
  55. data/spec/core/expression_filters_spec.rb +13 -0
  56. data/spec/core/schema_spec.rb +6 -1
  57. data/spec/extensions/association_pks_spec.rb +7 -0
  58. data/spec/extensions/defaults_setter_spec.rb +64 -0
  59. data/spec/extensions/many_through_many_spec.rb +60 -4
  60. data/spec/extensions/nested_attributes_spec.rb +1 -0
  61. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  62. data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
  63. data/spec/extensions/prepared_statements_spec.rb +72 -0
  64. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  65. data/spec/extensions/to_dot_spec.rb +3 -5
  66. data/spec/integration/associations_test.rb +155 -1
  67. data/spec/integration/dataset_test.rb +8 -1
  68. data/spec/integration/plugin_test.rb +119 -0
  69. data/spec/integration/prepared_statement_test.rb +72 -1
  70. data/spec/integration/schema_test.rb +66 -8
  71. data/spec/integration/transaction_test.rb +40 -0
  72. data/spec/model/associations_spec.rb +349 -8
  73. data/spec/model/base_spec.rb +59 -0
  74. data/spec/model/hooks_spec.rb +161 -0
  75. data/spec/model/record_spec.rb +24 -0
  76. metadata +21 -4
@@ -1,6 +1,7 @@
1
1
  module Sequel
2
2
  module Access
3
3
  module DatabaseMethods
4
+ # Access uses type :access as the database_type
4
5
  def database_type
5
6
  :access
6
7
  end
@@ -16,6 +17,7 @@ module Sequel
16
17
  from(:MSysObjects).filter(:Type=>1, :Flags=>0).select_map(:Name).map{|x| x.to_sym}
17
18
  end
18
19
 
20
+ # Access uses type Counter for an autoincrementing keys
19
21
  def serial_primary_key_options
20
22
  {:primary_key => true, :type=>:Counter}
21
23
  end
@@ -34,16 +36,24 @@ module Sequel
34
36
  module DatasetMethods
35
37
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'limit distinct columns from join where group order having compounds')
36
38
 
39
+ # Access doesn't support INTERSECT or EXCEPT
37
40
  def supports_intersect_except?
38
41
  false
39
42
  end
40
43
 
41
44
  private
42
45
 
46
+ # Access uses TOP for limits
47
+ def select_limit_sql(sql)
48
+ sql << " TOP #{@opts[:limit]}" if @opts[:limit]
49
+ end
50
+
51
+ # Access uses [] for quoting identifiers
43
52
  def quoted_identifier(v)
44
53
  "[#{v}]"
45
54
  end
46
55
 
56
+ # Access requires the limit clause come before other clauses
47
57
  def select_clause_methods
48
58
  SELECT_CLAUSE_METHODS
49
59
  end
@@ -0,0 +1,45 @@
1
+ module Sequel
2
+ module Informix
3
+ module DatabaseMethods
4
+ TEMPORARY = 'TEMP '.freeze
5
+
6
+ # Informix uses the :informix database type
7
+ def database_type
8
+ :informix
9
+ end
10
+
11
+ private
12
+
13
+ # Informix has issues with quoted identifiers, so
14
+ # turn off database quoting by default.
15
+ def quote_identifiers_default
16
+ false
17
+ end
18
+
19
+ # SQL fragment for showing a table is temporary
20
+ def temporary_table_sql
21
+ TEMPORARY
22
+ end
23
+ end
24
+
25
+ module DatasetMethods
26
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'limit distinct columns from join where having group compounds order')
27
+
28
+ private
29
+
30
+ # Informix does not support INTERSECT or EXCEPT
31
+ def supports_intersect_except?
32
+ false
33
+ end
34
+
35
+ def select_clause_methods
36
+ SELECT_CLAUSE_METHODS
37
+ end
38
+
39
+ def select_limit_sql(sql)
40
+ sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
41
+ sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
42
+ end
43
+ end
44
+ end
45
+ end
@@ -53,11 +53,13 @@ module Sequel
53
53
  # Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
54
54
  # information on tables.
55
55
  def tables(opts={})
56
- m = output_identifier_meth
57
- metadata_dataset.from(:information_schema__tables___t).
58
- select(:table_name).
59
- filter(:table_type=>'BASE TABLE', :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
60
- map{|x| m.call(x[:table_name])}
56
+ information_schema_tables('BASE TABLE', opts)
57
+ end
58
+
59
+ # Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
60
+ # information on views.
61
+ def views(opts={})
62
+ information_schema_tables('VIEW', opts)
61
63
  end
62
64
 
63
65
  private
@@ -123,6 +125,15 @@ module Sequel
123
125
  "DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
124
126
  end
125
127
 
128
+ # Backbone of the tables and views support.
129
+ def information_schema_tables(type, opts)
130
+ m = output_identifier_meth
131
+ metadata_dataset.from(:information_schema__tables___t).
132
+ select(:table_name).
133
+ filter(:table_type=>type, :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
134
+ map{|x| m.call(x[:table_name])}
135
+ end
136
+
126
137
  # Always quote identifiers in the metadata_dataset, so schema parsing works.
127
138
  def metadata_dataset
128
139
  ds = super
@@ -145,6 +156,16 @@ module Sequel
145
156
  SQL_ROLLBACK
146
157
  end
147
158
 
159
+ # The closest MSSQL equivalent of a boolean datatype is the bit type.
160
+ def schema_column_type(db_type)
161
+ case db_type
162
+ when /\A(bit)\z/io
163
+ :boolean
164
+ else
165
+ super
166
+ end
167
+ end
168
+
148
169
  # MSSQL uses the INFORMATION_SCHEMA to hold column information. This method does
149
170
  # not support the parsing of primary key information.
150
171
  def schema_parse_table(table_name, opts)
@@ -231,6 +252,29 @@ module Sequel
231
252
  @mssql_unicode_strings = db.mssql_unicode_strings
232
253
  end
233
254
 
255
+ # Ugly hack. While MSSQL supports TRUE and FALSE values, you can't
256
+ # actually specify them directly in SQL. Unfortunately, you also cannot
257
+ # use an integer value when a boolean is required. Also unforunately, you
258
+ # cannot use an expression that yields a boolean type in cases where in an
259
+ # integer type is needed, such as inserting into a bit field (the closest thing
260
+ # MSSQL has to a boolean).
261
+ #
262
+ # In filters, SQL::BooleanConstants are used more, while in other places
263
+ # the ruby true/false values are used more, so use expressions that return booleans
264
+ # for SQL::BooleanConstants, and 1/0 for other places.
265
+ # The correct fix for this would require separate literalization paths for
266
+ # filters compared to other values, but that's more work than I want to do right now.
267
+ def boolean_constant_sql(constant)
268
+ case constant
269
+ when true
270
+ '(1 = 1)'
271
+ when false
272
+ '(1 = 0)'
273
+ else
274
+ super
275
+ end
276
+ end
277
+
234
278
  # MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
235
279
  def complex_expression_sql(op, args)
236
280
  case op
@@ -277,8 +321,8 @@ module Sequel
277
321
 
278
322
  # Use the OUTPUT clause to get the value of all columns for the newly inserted record.
279
323
  def insert_select(*values)
280
- return unless supports_output_clause?
281
- naked.clone(default_server_opts(:sql=>output(nil, [:inserted.*]).insert_sql(*values))).single_record unless opts[:disable_insert_output]
324
+ return unless supports_insert_select?
325
+ naked.clone(default_server_opts(:sql=>output(nil, [SQL::ColumnAll.new(:inserted)]).insert_sql(*values))).single_record
282
326
  end
283
327
 
284
328
  # Specify a table for a SELECT ... INTO query.
@@ -367,6 +411,11 @@ module Sequel
367
411
  db.server_version(@opts[:server])
368
412
  end
369
413
 
414
+ # MSSQL supports insert_select via the OUTPUT clause.
415
+ def supports_insert_select?
416
+ supports_output_clause? && !opts[:disable_insert_output]
417
+ end
418
+
370
419
  # MSSQL 2005+ supports INTERSECT and EXCEPT
371
420
  def supports_intersect_except?
372
421
  is_2005_or_later?
@@ -441,6 +490,22 @@ module Sequel
441
490
  alias insert_with_sql delete_with_sql
442
491
  alias update_with_sql delete_with_sql
443
492
 
493
+ # Special case when true or false is provided directly to filter.
494
+ def filter_expr(expr)
495
+ if block_given?
496
+ super
497
+ else
498
+ case expr
499
+ when true
500
+ Sequel::TRUE
501
+ when false
502
+ Sequel::FALSE
503
+ else
504
+ super
505
+ end
506
+ end
507
+ end
508
+
444
509
  # MSSQL raises an error if you try to provide more than 3 decimal places
445
510
  # for a fractional timestamp. This probably doesn't work for smalldatetime
446
511
  # fields.
@@ -454,6 +519,16 @@ module Sequel
454
519
  INSERT_CLAUSE_METHODS
455
520
  end
456
521
 
522
+ # Use OUTPUT INSERTED.* to return all columns of the inserted row,
523
+ # for use with the prepared statement code.
524
+ def insert_output_sql(sql)
525
+ if @opts.has_key?(:returning)
526
+ sql << " OUTPUT INSERTED.*"
527
+ else
528
+ output_sql(sql)
529
+ end
530
+ end
531
+
457
532
  # MSSQL uses a literal hexidecimal number for blob strings
458
533
  def literal_blob(v)
459
534
  blob = '0x'
@@ -529,7 +604,6 @@ module Sequel
529
604
  end
530
605
  alias delete_output_sql output_sql
531
606
  alias update_output_sql output_sql
532
- alias insert_output_sql output_sql
533
607
 
534
608
  # MSSQL supports the OUTPUT clause for UPDATE statements.
535
609
  # It also allows prepending a WITH clause.
@@ -83,13 +83,9 @@ module Sequel
83
83
  @server_version ||= (m[1].to_i * 10000) + (m[2].to_i * 100) + m[3].to_i
84
84
  end
85
85
 
86
- # Return an array of symbols specifying table names in the current database.
87
- #
88
- # Options:
89
- # * :server - Set the server to use
90
- def tables(opts={})
91
- m = output_identifier_meth
92
- metadata_dataset.with_sql('SHOW TABLES').server(opts[:server]).map{|r| m.call(r.values.first)}
86
+ # MySQL supports CREATE TABLE IF NOT EXISTS syntax.
87
+ def supports_create_table_if_not_exists?
88
+ true
93
89
  end
94
90
 
95
91
  # MySQL supports prepared transactions (two-phase commit) using XA
@@ -107,6 +103,14 @@ module Sequel
107
103
  true
108
104
  end
109
105
 
106
+ # Return an array of symbols specifying table names in the current database.
107
+ #
108
+ # Options:
109
+ # * :server - Set the server to use
110
+ def tables(opts={})
111
+ full_tables('BASE TABLE', opts)
112
+ end
113
+
110
114
  # Changes the database in use by issuing a USE statement. I would be
111
115
  # very careful if I used this.
112
116
  def use(db_name)
@@ -116,6 +120,14 @@ module Sequel
116
120
  self
117
121
  end
118
122
 
123
+ # Return an array of symbols specifying view names in the current database.
124
+ #
125
+ # Options:
126
+ # * :server - Set the server to use
127
+ def views(opts={})
128
+ full_tables('VIEW', opts)
129
+ end
130
+
119
131
  private
120
132
 
121
133
  # Use MySQL specific syntax for rename column, set column type, and
@@ -205,6 +217,12 @@ module Sequel
205
217
  "#{super}#{" ENGINE=#{engine}" if engine}#{" DEFAULT CHARSET=#{charset}" if charset}#{" DEFAULT COLLATE=#{collate}" if collate}"
206
218
  end
207
219
 
220
+ # Backbone of the tables and views support using SHOW FULL TABLES.
221
+ def full_tables(type, opts)
222
+ m = output_identifier_meth
223
+ metadata_dataset.with_sql('SHOW FULL TABLES').server(opts[:server]).map{|r| m.call(r.values.first) if r.delete(:Table_type) == type}.compact
224
+ end
225
+
208
226
  # MySQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers on input.
209
227
  def identifier_input_method_default
210
228
  nil
@@ -387,11 +387,17 @@ module Sequel
387
387
  # Options:
388
388
  # * :schema - The schema to search (default_schema by default)
389
389
  # * :server - The server to use
390
- def tables(opts={})
391
- ds = metadata_dataset.from(:pg_class).filter(:relkind=>'r').select(:relname).exclude(SQL::StringExpression.like(:relname, SYSTEM_TABLE_REGEXP)).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
392
- ds = filter_schema(ds, opts)
393
- m = output_identifier_meth
394
- block_given? ? yield(ds) : ds.map{|r| m.call(r[:relname])}
390
+ def tables(opts={}, &block)
391
+ pg_class_relname('r', opts, &block)
392
+ end
393
+
394
+ # Array of symbols specifying view names in the current database.
395
+ #
396
+ # Options:
397
+ # * :schema - The schema to search (default_schema by default)
398
+ # * :server - The server to use
399
+ def views(opts={})
400
+ pg_class_relname('v', opts)
395
401
  end
396
402
 
397
403
  private
@@ -540,6 +546,14 @@ module Sequel
540
546
  conn.execute(sql)
541
547
  end
542
548
 
549
+ # Backbone of the tables and views support.
550
+ def pg_class_relname(type, opts)
551
+ ds = metadata_dataset.from(:pg_class).filter(:relkind=>type).select(:relname).exclude(SQL::StringExpression.like(:relname, SYSTEM_TABLE_REGEXP)).server(opts[:server]).join(:pg_namespace, :oid=>:relnamespace)
552
+ ds = filter_schema(ds, opts)
553
+ m = output_identifier_meth
554
+ block_given? ? yield(ds) : ds.map{|r| m.call(r[:relname])}
555
+ end
556
+
543
557
  # Use a dollar sign instead of question mark for the argument
544
558
  # placeholder.
545
559
  def prepared_arg_placeholder
@@ -632,6 +646,7 @@ module Sequel
632
646
  EXPLAIN = 'EXPLAIN '.freeze
633
647
  EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
634
648
  FOR_SHARE = ' FOR SHARE'.freeze
649
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values returning_select')
635
650
  LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
636
651
  NULL = LiteralString.new('NULL').freeze
637
652
  PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
@@ -726,7 +741,7 @@ module Sequel
726
741
 
727
742
  # Insert a record returning the record inserted
728
743
  def insert_select(*values)
729
- return if opts[:disable_insert_returning] || server_version < 80200
744
+ return unless supports_insert_select?
730
745
  naked.clone(default_server_opts(:sql=>insert_returning_sql(nil, *values))).single_record
731
746
  end
732
747
 
@@ -757,6 +772,11 @@ module Sequel
757
772
  true
758
773
  end
759
774
 
775
+ # PostgreSQL support insert_select using the RETURNING clause.
776
+ def supports_insert_select?
777
+ server_version >= 80200 && !opts[:disable_insert_returning]
778
+ end
779
+
760
780
  # PostgreSQL supports modifying joined datasets
761
781
  def supports_modifying_joins?
762
782
  true
@@ -794,11 +814,24 @@ module Sequel
794
814
  join_from_sql(:USING, sql)
795
815
  end
796
816
 
817
+ # PostgreSQL allows a RETURNING clause.
818
+ def insert_clause_methods
819
+ INSERT_CLAUSE_METHODS
820
+ end
821
+
797
822
  # Use the RETURNING clause to return the primary key of the inserted record, if it exists
798
823
  def insert_returning_pk_sql(*values)
799
824
  pk = db.primary_key(opts[:from].first) if opts[:from] && !opts[:from].empty?
800
825
  insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : NULL, *values)
801
826
  end
827
+
828
+ # Add a RETURNING clause if it is set and the database supports it and
829
+ # this dataset hasn't disabled it.
830
+ def insert_returning_select_sql(sql)
831
+ if supports_insert_select? && opts.has_key?(:returning)
832
+ sql << " RETURNING #{column_list(Array(opts[:returning]))}"
833
+ end
834
+ end
802
835
 
803
836
  # For multiple table support, PostgreSQL requires at least
804
837
  # two from tables, with joins allowed.
@@ -7,8 +7,9 @@ module Sequel
7
7
  AUTO_VACUUM = [:none, :full, :incremental].freeze
8
8
  PRIMARY_KEY_INDEX_RE = /\Asqlite_autoindex_/.freeze
9
9
  SYNCHRONOUS = [:off, :normal, :full].freeze
10
- TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
10
+ TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'".freeze
11
11
  TEMP_STORE = [:default, :file, :memory].freeze
12
+ VIEWS_FILTER = "type = 'view'".freeze
12
13
 
13
14
  # Run all alter_table commands in a transaction. This is technically only
14
15
  # needed for drop column.
@@ -96,6 +97,11 @@ module Sequel
96
97
  end
97
98
  end
98
99
 
100
+ # SQLite supports CREATE TABLE IF NOT EXISTS syntax since 3.3.0.
101
+ def supports_create_table_if_not_exists?
102
+ sqlite_version >= 30300
103
+ end
104
+
99
105
  # SQLite 3.6.8+ supports savepoints.
100
106
  def supports_savepoints?
101
107
  sqlite_version >= 30608
@@ -118,8 +124,7 @@ module Sequel
118
124
  # Options:
119
125
  # * :server - Set the server to use.
120
126
  def tables(opts={})
121
- m = output_identifier_meth
122
- metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(TABLES_FILTER).map{|r| m.call(r[:name])}
127
+ tables_and_views(TABLES_FILTER, opts)
123
128
  end
124
129
 
125
130
  # A symbol signifying the value of the temp_store PRAGMA.
@@ -134,6 +139,14 @@ module Sequel
134
139
  pragma_set(:temp_store, value)
135
140
  end
136
141
 
142
+ # Array of symbols specifying the view names in the current database.
143
+ #
144
+ # Options:
145
+ # * :server - Set the server to use.
146
+ def views(opts={})
147
+ tables_and_views(VIEWS_FILTER, opts)
148
+ end
149
+
137
150
  private
138
151
 
139
152
  # SQLite supports limited table modification. You can add a column
@@ -306,6 +319,12 @@ module Sequel
306
319
  end
307
320
  end
308
321
 
322
+ # Backbone of the tables and views support.
323
+ def tables_and_views(filter, opts)
324
+ m = output_identifier_meth
325
+ metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(filter).map{|r| m.call(r[:name])}
326
+ end
327
+
309
328
  # SQLite uses the integer data type even for bignums. This is because they
310
329
  # are both stored internally as text, and converted when returned from
311
330
  # the database. Using an integer type instead of bigint makes it more likely
@@ -320,7 +339,24 @@ module Sequel
320
339
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
321
340
  COMMA_SEPARATOR = ', '.freeze
322
341
  CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
323
-
342
+
343
+ # Ugly hack. Really, SQLite uses 0 for false and 1 for true
344
+ # but then you can't differentiate between integers and booleans.
345
+ # In filters, SQL::BooleanConstants are used more, while in other places
346
+ # the ruby true/false values are used more, so use 1/0 for SQL::BooleanConstants.
347
+ # The correct fix for this would require separate literalization paths for
348
+ # filters compared to other values, but that's more work than I want to do right now.
349
+ def boolean_constant_sql(constant)
350
+ case constant
351
+ when true
352
+ '1'
353
+ when false
354
+ '0'
355
+ else
356
+ super
357
+ end
358
+ end
359
+
324
360
  # SQLite does not support pattern matching via regular expressions.
325
361
  # SQLite is case insensitive (depending on pragma), so use LIKE for
326
362
  # ILIKE.
@@ -395,6 +431,22 @@ module Sequel
395
431
  "#{expression} AS #{literal(aliaz.to_s)}"
396
432
  end
397
433
 
434
+ # Special case when true or false is provided directly to filter.
435
+ def filter_expr(expr)
436
+ if block_given?
437
+ super
438
+ else
439
+ case expr
440
+ when true
441
+ 1
442
+ when false
443
+ 0
444
+ else
445
+ super
446
+ end
447
+ end
448
+ end
449
+
398
450
  # SQL fragment specifying a list of identifiers
399
451
  def identifier_list(columns)
400
452
  columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
@@ -406,7 +458,7 @@ module Sequel
406
458
  v.each_byte{|x| blob << sprintf('%02x', x)}
407
459
  "X'#{blob}'"
408
460
  end
409
-
461
+
410
462
  # SQLite does not support the SQL WITH clause
411
463
  def select_clause_methods
412
464
  SELECT_CLAUSE_METHODS