sequel 3.21.0 → 3.24.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 (104) hide show
  1. data/CHANGELOG +112 -0
  2. data/README.rdoc +15 -1
  3. data/doc/association_basics.rdoc +159 -40
  4. data/doc/model_hooks.rdoc +64 -27
  5. data/doc/prepared_statements.rdoc +8 -4
  6. data/doc/reflection.rdoc +8 -2
  7. data/doc/release_notes/3.22.0.txt +39 -0
  8. data/doc/release_notes/3.23.0.txt +172 -0
  9. data/doc/release_notes/3.24.0.txt +420 -0
  10. data/doc/virtual_rows.rdoc +2 -2
  11. data/lib/sequel/adapters/ado.rb +2 -1
  12. data/lib/sequel/adapters/db2.rb +8 -1
  13. data/lib/sequel/adapters/firebird.rb +25 -9
  14. data/lib/sequel/adapters/informix.rb +4 -19
  15. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  16. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  17. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  18. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  19. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  20. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  21. data/lib/sequel/adapters/jdbc.rb +39 -20
  22. data/lib/sequel/adapters/odbc.rb +2 -0
  23. data/lib/sequel/adapters/oracle.rb +12 -0
  24. data/lib/sequel/adapters/postgres.rb +30 -1
  25. data/lib/sequel/adapters/shared/access.rb +10 -0
  26. data/lib/sequel/adapters/shared/informix.rb +45 -0
  27. data/lib/sequel/adapters/shared/mssql.rb +106 -11
  28. data/lib/sequel/adapters/shared/mysql.rb +25 -7
  29. data/lib/sequel/adapters/shared/postgres.rb +39 -6
  30. data/lib/sequel/adapters/shared/sqlite.rb +57 -5
  31. data/lib/sequel/adapters/sqlite.rb +8 -3
  32. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  33. data/lib/sequel/adapters/tinytds.rb +4 -3
  34. data/lib/sequel/ast_transformer.rb +190 -0
  35. data/lib/sequel/core.rb +1 -1
  36. data/lib/sequel/database/connecting.rb +1 -1
  37. data/lib/sequel/database/misc.rb +6 -0
  38. data/lib/sequel/database/query.rb +33 -3
  39. data/lib/sequel/database/schema_methods.rb +13 -4
  40. data/lib/sequel/dataset/features.rb +6 -0
  41. data/lib/sequel/dataset/prepared_statements.rb +17 -2
  42. data/lib/sequel/dataset/query.rb +17 -0
  43. data/lib/sequel/dataset/sql.rb +2 -53
  44. data/lib/sequel/exceptions.rb +4 -0
  45. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  46. data/lib/sequel/extensions/migration.rb +4 -3
  47. data/lib/sequel/extensions/to_dot.rb +95 -83
  48. data/lib/sequel/model/associations.rb +234 -32
  49. data/lib/sequel/model/base.rb +187 -60
  50. data/lib/sequel/model/exceptions.rb +3 -1
  51. data/lib/sequel/model.rb +5 -0
  52. data/lib/sequel/plugins/association_pks.rb +6 -4
  53. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  54. data/lib/sequel/plugins/identity_map.rb +2 -2
  55. data/lib/sequel/plugins/many_through_many.rb +33 -3
  56. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  57. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  58. data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
  59. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  60. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  61. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  62. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  63. data/lib/sequel/sql.rb +8 -0
  64. data/lib/sequel/version.rb +1 -1
  65. data/spec/adapters/mssql_spec.rb +36 -0
  66. data/spec/adapters/mysql_spec.rb +6 -0
  67. data/spec/adapters/postgres_spec.rb +43 -18
  68. data/spec/adapters/spec_helper.rb +5 -0
  69. data/spec/core/connection_pool_spec.rb +56 -77
  70. data/spec/core/database_spec.rb +33 -0
  71. data/spec/core/dataset_spec.rb +127 -16
  72. data/spec/core/expression_filters_spec.rb +13 -0
  73. data/spec/core/schema_spec.rb +13 -1
  74. data/spec/core/spec_helper.rb +5 -0
  75. data/spec/extensions/association_pks_spec.rb +7 -0
  76. data/spec/extensions/columns_introspection_spec.rb +91 -0
  77. data/spec/extensions/defaults_setter_spec.rb +64 -0
  78. data/spec/extensions/many_through_many_spec.rb +77 -0
  79. data/spec/extensions/migration_spec.rb +17 -17
  80. data/spec/extensions/nested_attributes_spec.rb +1 -0
  81. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  82. data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
  83. data/spec/extensions/prepared_statements_spec.rb +72 -0
  84. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  85. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  86. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  87. data/spec/extensions/spec_helper.rb +3 -1
  88. data/spec/extensions/to_dot_spec.rb +3 -5
  89. data/spec/extensions/xml_serializer_spec.rb +12 -0
  90. data/spec/integration/associations_test.rb +212 -0
  91. data/spec/integration/dataset_test.rb +8 -1
  92. data/spec/integration/plugin_test.rb +134 -0
  93. data/spec/integration/prepared_statement_test.rb +72 -1
  94. data/spec/integration/schema_test.rb +66 -8
  95. data/spec/integration/spec_helper.rb +5 -0
  96. data/spec/integration/transaction_test.rb +40 -0
  97. data/spec/integration/type_test.rb +7 -0
  98. data/spec/model/associations_spec.rb +463 -5
  99. data/spec/model/base_spec.rb +59 -0
  100. data/spec/model/eager_loading_spec.rb +269 -1
  101. data/spec/model/hooks_spec.rb +161 -0
  102. data/spec/model/record_spec.rb +30 -0
  103. data/spec/model/spec_helper.rb +5 -0
  104. metadata +29 -4
@@ -11,10 +11,16 @@ module Sequel
11
11
  SQL_ROLLBACK_TO_SAVEPOINT = 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION autopoint_%d'.freeze
12
12
  SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
13
13
 
14
+ # Whether to use N'' to quote strings, which allows unicode characters inside the
15
+ # strings. True by default for compatibility, can be set to false for a possible
16
+ # performance increase. This sets the default for all datasets created from this
17
+ # Database object.
18
+ attr_accessor :mssql_unicode_strings
19
+
14
20
  # The types to check for 0 scale to transform :decimal types
15
21
  # to :integer.
16
22
  DECIMAL_TYPE_RE = /number|numeric|decimal/io
17
-
23
+
18
24
  # Microsoft SQL Server uses the :mssql type.
19
25
  def database_type
20
26
  :mssql
@@ -47,11 +53,13 @@ module Sequel
47
53
  # Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
48
54
  # information on tables.
49
55
  def tables(opts={})
50
- m = output_identifier_meth
51
- metadata_dataset.from(:information_schema__tables___t).
52
- select(:table_name).
53
- filter(:table_type=>'BASE TABLE', :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
54
- 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)
55
63
  end
56
64
 
57
65
  private
@@ -117,6 +125,15 @@ module Sequel
117
125
  "DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
118
126
  end
119
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
+
120
137
  # Always quote identifiers in the metadata_dataset, so schema parsing works.
121
138
  def metadata_dataset
122
139
  ds = super
@@ -139,6 +156,16 @@ module Sequel
139
156
  SQL_ROLLBACK
140
157
  end
141
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
+
142
169
  # MSSQL uses the INFORMATION_SCHEMA to hold column information. This method does
143
170
  # not support the parsing of primary key information.
144
171
  def schema_parse_table(table_name, opts)
@@ -163,6 +190,11 @@ module Sequel
163
190
  [m.call(row.delete(:column)), row]
164
191
  end
165
192
  end
193
+
194
+ # Set the mssql_unicode_strings settings from the given options.
195
+ def set_mssql_unicode_strings
196
+ @mssql_unicode_strings = typecast_value_boolean(@opts.fetch(:mssql_unicode_strings, true))
197
+ end
166
198
 
167
199
  # MSSQL has both datetime and timestamp classes, most people are going
168
200
  # to want datetime
@@ -211,6 +243,38 @@ module Sequel
211
243
  WILDCARD = LiteralString.new('*').freeze
212
244
  CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
213
245
 
246
+ # Allow overriding of the mssql_unicode_strings option at the dataset level.
247
+ attr_accessor :mssql_unicode_strings
248
+
249
+ # Copy the mssql_unicode_strings option from the +db+ object.
250
+ def initialize(db, opts={})
251
+ super
252
+ @mssql_unicode_strings = db.mssql_unicode_strings
253
+ end
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
+
214
278
  # MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
215
279
  def complex_expression_sql(op, args)
216
280
  case op
@@ -257,8 +321,8 @@ module Sequel
257
321
 
258
322
  # Use the OUTPUT clause to get the value of all columns for the newly inserted record.
259
323
  def insert_select(*values)
260
- return unless supports_output_clause?
261
- 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
262
326
  end
263
327
 
264
328
  # Specify a table for a SELECT ... INTO query.
@@ -347,6 +411,11 @@ module Sequel
347
411
  db.server_version(@opts[:server])
348
412
  end
349
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
+
350
419
  # MSSQL 2005+ supports INTERSECT and EXCEPT
351
420
  def supports_intersect_except?
352
421
  is_2005_or_later?
@@ -421,6 +490,22 @@ module Sequel
421
490
  alias insert_with_sql delete_with_sql
422
491
  alias update_with_sql delete_with_sql
423
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
+
424
509
  # MSSQL raises an error if you try to provide more than 3 decimal places
425
510
  # for a fractional timestamp. This probably doesn't work for smalldatetime
426
511
  # fields.
@@ -434,6 +519,16 @@ module Sequel
434
519
  INSERT_CLAUSE_METHODS
435
520
  end
436
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
+
437
532
  # MSSQL uses a literal hexidecimal number for blob strings
438
533
  def literal_blob(v)
439
534
  blob = '0x'
@@ -441,9 +536,10 @@ module Sequel
441
536
  blob
442
537
  end
443
538
 
444
- # Use unicode string syntax for all strings. Don't double backslashes.
539
+ # Optionally use unicode string syntax for all strings. Don't double
540
+ # backslashes.
445
541
  def literal_string(v)
446
- "N'#{v.gsub(/'/, "''")}'"
542
+ "#{'N' if mssql_unicode_strings}'#{v.gsub(/'/, "''")}'"
447
543
  end
448
544
 
449
545
  # Use 0 for false on MSSQL
@@ -508,7 +604,6 @@ module Sequel
508
604
  end
509
605
  alias delete_output_sql output_sql
510
606
  alias update_output_sql output_sql
511
- alias insert_output_sql output_sql
512
607
 
513
608
  # MSSQL supports the OUTPUT clause for UPDATE statements.
514
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
@@ -173,7 +173,7 @@ module Sequel
173
173
  end
174
174
  end
175
175
  unless cps
176
- cps = conn.prepare(sql)
176
+ cps = log_yield("Preparing #{name}: #{sql}"){conn.prepare(sql)}
177
177
  conn.prepared_statements[name] = [cps, sql]
178
178
  end
179
179
  if block
@@ -217,7 +217,7 @@ module Sequel
217
217
  # but with the keys converted to strings.
218
218
  def map_to_prepared_args(hash)
219
219
  args = {}
220
- hash.each{|k,v| args[k.to_s] = v}
220
+ hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
221
221
  args
222
222
  end
223
223
 
@@ -226,7 +226,12 @@ module Sequel
226
226
  # SQLite uses a : before the name of the argument for named
227
227
  # arguments.
228
228
  def prepared_arg(k)
229
- LiteralString.new("#{prepared_arg_placeholder}#{k}")
229
+ LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
230
+ end
231
+
232
+ # Always assume a prepared argument.
233
+ def prepared_arg?(k)
234
+ true
230
235
  end
231
236
  end
232
237
 
@@ -25,6 +25,15 @@ module Sequel
25
25
  def schema_column_type(db_type)
26
26
  db_type == 'tinyint(1)' ? :boolean : super
27
27
  end
28
+
29
+ # By default, MySQL 'where id is null' selects the last inserted id.
30
+ # Turn that off unless explicitly enabled.
31
+ def setup_connection(conn)
32
+ super
33
+ sql = "SET SQL_AUTO_IS_NULL=0"
34
+ log_yield(sql){conn.execute(sql)} unless opts[:auto_is_null]
35
+ conn
36
+ end
28
37
  end
29
38
 
30
39
  # Dataset class for MySQL datasets accessed via Swift.
@@ -13,6 +13,7 @@ module Sequel
13
13
  opts = server_opts(server)
14
14
  opts[:dataserver] = opts[:host]
15
15
  opts[:username] = opts[:user]
16
+ set_mssql_unicode_strings
16
17
  TinyTds::Client.new(opts)
17
18
  end
18
19
 
@@ -38,7 +39,7 @@ module Sequel
38
39
  end
39
40
  yield(r) if block_given?
40
41
  rescue TinyTds::Error => e
41
- raise_error(e)
42
+ raise_error(e, :disconnect=>(c.closed? || (c.respond_to?(:dead?) && c.dead?)))
42
43
  ensure
43
44
  r.cancel if r && c.sqlsent?
44
45
  end
@@ -107,7 +108,7 @@ module Sequel
107
108
  yield r
108
109
  end
109
110
  else
110
- result.each(each_opts, &block)
111
+ result.each(each_opts, &Proc.new)
111
112
  end
112
113
  end
113
114
  end
@@ -118,7 +119,7 @@ module Sequel
118
119
 
119
120
  # Properly escape the given string +v+.
120
121
  def literal_string(v)
121
- db.synchronize{|c| "N'#{c.escape(v)}'"}
122
+ s = db.synchronize{|c| "#{'N' if mssql_unicode_strings}'#{c.escape(v)}'"}
122
123
  end
123
124
  end
124
125
  end