sequel 3.21.0 → 3.28.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 (158) hide show
  1. data/CHANGELOG +294 -0
  2. data/README.rdoc +20 -6
  3. data/Rakefile +20 -15
  4. data/doc/association_basics.rdoc +210 -43
  5. data/doc/dataset_basics.rdoc +4 -4
  6. data/doc/mass_assignment.rdoc +54 -0
  7. data/doc/migration.rdoc +15 -538
  8. data/doc/model_hooks.rdoc +64 -27
  9. data/doc/opening_databases.rdoc +37 -10
  10. data/doc/prepared_statements.rdoc +16 -10
  11. data/doc/reflection.rdoc +8 -2
  12. data/doc/release_notes/3.22.0.txt +39 -0
  13. data/doc/release_notes/3.23.0.txt +172 -0
  14. data/doc/release_notes/3.24.0.txt +420 -0
  15. data/doc/release_notes/3.25.0.txt +88 -0
  16. data/doc/release_notes/3.26.0.txt +88 -0
  17. data/doc/release_notes/3.27.0.txt +82 -0
  18. data/doc/release_notes/3.28.0.txt +304 -0
  19. data/doc/schema_modification.rdoc +547 -0
  20. data/doc/testing.rdoc +106 -0
  21. data/doc/transactions.rdoc +97 -0
  22. data/doc/virtual_rows.rdoc +2 -2
  23. data/lib/sequel/adapters/ado.rb +12 -1
  24. data/lib/sequel/adapters/amalgalite.rb +4 -0
  25. data/lib/sequel/adapters/db2.rb +95 -58
  26. data/lib/sequel/adapters/do.rb +12 -0
  27. data/lib/sequel/adapters/firebird.rb +25 -203
  28. data/lib/sequel/adapters/ibmdb.rb +440 -0
  29. data/lib/sequel/adapters/informix.rb +4 -19
  30. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  31. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  32. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  33. data/lib/sequel/adapters/jdbc/h2.rb +16 -5
  34. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  35. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  36. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  37. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  38. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  39. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
  40. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  41. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  42. data/lib/sequel/adapters/jdbc.rb +62 -29
  43. data/lib/sequel/adapters/mysql.rb +22 -139
  44. data/lib/sequel/adapters/mysql2.rb +9 -14
  45. data/lib/sequel/adapters/odbc/db2.rb +21 -0
  46. data/lib/sequel/adapters/odbc.rb +15 -3
  47. data/lib/sequel/adapters/oracle.rb +17 -1
  48. data/lib/sequel/adapters/postgres.rb +111 -16
  49. data/lib/sequel/adapters/shared/access.rb +21 -0
  50. data/lib/sequel/adapters/shared/db2.rb +290 -0
  51. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  52. data/lib/sequel/adapters/shared/informix.rb +45 -0
  53. data/lib/sequel/adapters/shared/mssql.rb +85 -47
  54. data/lib/sequel/adapters/shared/mysql.rb +50 -7
  55. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
  56. data/lib/sequel/adapters/shared/oracle.rb +0 -4
  57. data/lib/sequel/adapters/shared/postgres.rb +75 -43
  58. data/lib/sequel/adapters/shared/sqlite.rb +56 -8
  59. data/lib/sequel/adapters/sqlite.rb +12 -11
  60. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  61. data/lib/sequel/adapters/tinytds.rb +139 -7
  62. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  63. data/lib/sequel/ast_transformer.rb +190 -0
  64. data/lib/sequel/connection_pool/threaded.rb +3 -2
  65. data/lib/sequel/connection_pool.rb +1 -1
  66. data/lib/sequel/core.rb +6 -5
  67. data/lib/sequel/database/connecting.rb +5 -5
  68. data/lib/sequel/database/dataset.rb +1 -1
  69. data/lib/sequel/database/dataset_defaults.rb +1 -1
  70. data/lib/sequel/database/logging.rb +1 -1
  71. data/lib/sequel/database/misc.rb +38 -17
  72. data/lib/sequel/database/query.rb +50 -19
  73. data/lib/sequel/database/schema_generator.rb +8 -5
  74. data/lib/sequel/database/schema_methods.rb +52 -27
  75. data/lib/sequel/dataset/actions.rb +167 -48
  76. data/lib/sequel/dataset/features.rb +57 -8
  77. data/lib/sequel/dataset/graph.rb +1 -1
  78. data/lib/sequel/dataset/misc.rb +39 -20
  79. data/lib/sequel/dataset/mutation.rb +3 -3
  80. data/lib/sequel/dataset/prepared_statements.rb +29 -14
  81. data/lib/sequel/dataset/query.rb +182 -32
  82. data/lib/sequel/dataset/sql.rb +31 -58
  83. data/lib/sequel/dataset.rb +8 -0
  84. data/lib/sequel/exceptions.rb +4 -0
  85. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  86. data/lib/sequel/extensions/migration.rb +6 -4
  87. data/lib/sequel/extensions/to_dot.rb +95 -83
  88. data/lib/sequel/model/associations.rb +893 -309
  89. data/lib/sequel/model/base.rb +302 -105
  90. data/lib/sequel/model/errors.rb +1 -1
  91. data/lib/sequel/model/exceptions.rb +5 -1
  92. data/lib/sequel/model.rb +13 -7
  93. data/lib/sequel/plugins/association_pks.rb +22 -4
  94. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  95. data/lib/sequel/plugins/identity_map.rb +113 -6
  96. data/lib/sequel/plugins/many_through_many.rb +67 -5
  97. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  98. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  99. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  100. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  101. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  102. data/lib/sequel/plugins/sharding.rb +12 -20
  103. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  104. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  105. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  106. data/lib/sequel/sql.rb +107 -51
  107. data/lib/sequel/timezones.rb +12 -3
  108. data/lib/sequel/version.rb +1 -1
  109. data/spec/adapters/db2_spec.rb +146 -0
  110. data/spec/adapters/mssql_spec.rb +36 -0
  111. data/spec/adapters/mysql_spec.rb +36 -19
  112. data/spec/adapters/postgres_spec.rb +115 -28
  113. data/spec/adapters/spec_helper.rb +6 -0
  114. data/spec/adapters/sqlite_spec.rb +11 -0
  115. data/spec/core/connection_pool_spec.rb +62 -77
  116. data/spec/core/database_spec.rb +244 -287
  117. data/spec/core/dataset_spec.rb +383 -34
  118. data/spec/core/expression_filters_spec.rb +159 -41
  119. data/spec/core/schema_spec.rb +326 -3
  120. data/spec/core/spec_helper.rb +45 -0
  121. data/spec/extensions/association_pks_spec.rb +38 -0
  122. data/spec/extensions/columns_introspection_spec.rb +91 -0
  123. data/spec/extensions/defaults_setter_spec.rb +64 -0
  124. data/spec/extensions/identity_map_spec.rb +162 -0
  125. data/spec/extensions/many_through_many_spec.rb +195 -20
  126. data/spec/extensions/migration_spec.rb +17 -17
  127. data/spec/extensions/nested_attributes_spec.rb +1 -0
  128. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  129. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  130. data/spec/extensions/prepared_statements_spec.rb +72 -0
  131. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  132. data/spec/extensions/schema_dumper_spec.rb +2 -2
  133. data/spec/extensions/schema_spec.rb +12 -20
  134. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  135. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  136. data/spec/extensions/spec_helper.rb +3 -1
  137. data/spec/extensions/to_dot_spec.rb +3 -5
  138. data/spec/extensions/xml_serializer_spec.rb +16 -4
  139. data/spec/integration/associations_test.rb +405 -15
  140. data/spec/integration/database_test.rb +4 -2
  141. data/spec/integration/dataset_test.rb +240 -20
  142. data/spec/integration/plugin_test.rb +142 -5
  143. data/spec/integration/prepared_statement_test.rb +174 -95
  144. data/spec/integration/schema_test.rb +128 -16
  145. data/spec/integration/spec_helper.rb +15 -0
  146. data/spec/integration/transaction_test.rb +40 -0
  147. data/spec/integration/type_test.rb +16 -2
  148. data/spec/model/association_reflection_spec.rb +91 -0
  149. data/spec/model/associations_spec.rb +476 -5
  150. data/spec/model/base_spec.rb +91 -1
  151. data/spec/model/eager_loading_spec.rb +519 -31
  152. data/spec/model/hooks_spec.rb +161 -0
  153. data/spec/model/model_spec.rb +89 -2
  154. data/spec/model/plugins_spec.rb +17 -0
  155. data/spec/model/record_spec.rb +184 -12
  156. data/spec/model/spec_helper.rb +5 -0
  157. data/spec/model/validations_spec.rb +11 -0
  158. metadata +85 -34
@@ -358,6 +358,11 @@ module Sequel
358
358
  @supports_prepared_transactions = self['SHOW max_prepared_transactions'].get.to_i > 0
359
359
  end
360
360
 
361
+ # PostgreSQL supports CREATE TABLE IF NOT EXISTS on 9.1+
362
+ def supports_create_table_if_not_exists?
363
+ server_version >= 90100
364
+ end
365
+
361
366
  # PostgreSQL supports savepoints
362
367
  def supports_savepoints?
363
368
  true
@@ -368,30 +373,24 @@ module Sequel
368
373
  true
369
374
  end
370
375
 
371
- # Whether the given table exists in the database
376
+ # Array of symbols specifying table names in the current database.
377
+ # The dataset used is yielded to the block if one is provided,
378
+ # otherwise, an array of symbols of table names is returned.
372
379
  #
373
380
  # Options:
374
381
  # * :schema - The schema to search (default_schema by default)
375
382
  # * :server - The server to use
376
- def table_exists?(table, opts={})
377
- im = input_identifier_meth
378
- schema, table = schema_and_table(table)
379
- opts[:schema] ||= schema
380
- tables(opts){|ds| !ds.first(:relname=>im.call(table)).nil?}
383
+ def tables(opts={}, &block)
384
+ pg_class_relname('r', opts, &block)
381
385
  end
382
-
383
- # Array of symbols specifying table names in the current database.
384
- # The dataset used is yielded to the block if one is provided,
385
- # otherwise, an array of symbols of table names is returned.
386
+
387
+ # Array of symbols specifying view names in the current database.
386
388
  #
387
389
  # Options:
388
390
  # * :schema - The schema to search (default_schema by default)
389
391
  # * :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])}
392
+ def views(opts={})
393
+ pg_class_relname('v', opts)
395
394
  end
396
395
 
397
396
  private
@@ -454,11 +453,6 @@ module Sequel
454
453
  "DROP LANGUAGE#{' IF EXISTS' if opts[:if_exists]} #{name}#{' CASCADE' if opts[:cascade]}"
455
454
  end
456
455
 
457
- # Always CASCADE the table drop
458
- def drop_table_sql(name)
459
- "DROP TABLE #{quote_schema_table(name)} CASCADE"
460
- end
461
-
462
456
  # SQL for dropping a trigger from the database.
463
457
  def drop_trigger_sql(table, name, opts={})
464
458
  "DROP TRIGGER#{' IF EXISTS' if opts[:if_exists]} #{name} ON #{quote_schema_table(table)}#{' CASCADE' if opts[:cascade]}"
@@ -540,6 +534,14 @@ module Sequel
540
534
  conn.execute(sql)
541
535
  end
542
536
 
537
+ # Backbone of the tables and views support.
538
+ def pg_class_relname(type, opts)
539
+ 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)
540
+ ds = filter_schema(ds, opts)
541
+ m = output_identifier_meth
542
+ block_given? ? yield(ds) : ds.map{|r| m.call(r[:relname])}
543
+ end
544
+
543
545
  # Use a dollar sign instead of question mark for the argument
544
546
  # placeholder.
545
547
  def prepared_arg_placeholder
@@ -628,10 +630,14 @@ module Sequel
628
630
  BOOL_TRUE = 'true'.freeze
629
631
  COMMA_SEPARATOR = ', '.freeze
630
632
  DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'from using where')
633
+ DELETE_CLAUSE_METHODS_91 = Dataset.clause_methods(:delete, %w'with from using where returning')
631
634
  EXCLUSIVE = 'EXCLUSIVE'.freeze
632
635
  EXPLAIN = 'EXPLAIN '.freeze
633
636
  EXPLAIN_ANALYZE = 'EXPLAIN ANALYZE '.freeze
634
637
  FOR_SHARE = ' FOR SHARE'.freeze
638
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values')
639
+ INSERT_CLAUSE_METHODS_82 = Dataset.clause_methods(:insert, %w'into columns values returning')
640
+ INSERT_CLAUSE_METHODS_91 = Dataset.clause_methods(:insert, %w'with into columns values returning')
635
641
  LOCK = 'LOCK TABLE %s IN %s MODE'.freeze
636
642
  NULL = LiteralString.new('NULL').freeze
637
643
  PG_TIMESTAMP_FORMAT = "TIMESTAMP '%Y-%m-%d %H:%M:%S".freeze
@@ -645,17 +651,23 @@ module Sequel
645
651
  SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
646
652
  SQL_WITH_RECURSIVE = "WITH RECURSIVE ".freeze
647
653
  UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'table set from where')
654
+ UPDATE_CLAUSE_METHODS_91 = Dataset.clause_methods(:update, %w'with table set from where returning')
648
655
 
649
656
  # Shared methods for prepared statements when used with PostgreSQL databases.
650
657
  module PreparedStatementMethods
651
658
  # Override insert action to use RETURNING if the server supports it.
659
+ def run(&block)
660
+ if @prepared_type == :insert && supports_insert_select?
661
+ fetch_rows(prepared_sql){|r| return r.values.first}
662
+ else
663
+ super
664
+ end
665
+ end
666
+
652
667
  def prepared_sql
653
668
  return @prepared_sql if @prepared_sql
669
+ @opts[:returning] = insert_pk if @prepared_type == :insert && supports_insert_select?
654
670
  super
655
- if @prepared_type == :insert and !@opts[:disable_insert_returning] and server_version >= 80200
656
- @prepared_sql = insert_returning_pk_sql(*@prepared_modify_values)
657
- meta_def(:insert_returning_pk_sql){|*args| prepared_sql}
658
- end
659
671
  @prepared_sql
660
672
  end
661
673
  end
@@ -709,25 +721,20 @@ module Sequel
709
721
  end
710
722
 
711
723
  # Insert given values into the database.
712
- def insert(*values)
713
- if @opts[:sql]
714
- execute_insert(insert_sql(*values))
715
- elsif @opts[:disable_insert_returning] || server_version < 80200
716
- execute_insert(insert_sql(*values), :table=>opts[:from].first, :values=>values.size == 1 ? values.first : values)
724
+ def insert(*values, &block)
725
+ if @opts[:sql] || @opts[:returning]
726
+ super
727
+ elsif supports_insert_select?
728
+ returning(insert_pk).insert(*values){|r| return r.values.first}
717
729
  else
718
- clone(default_server_opts(:sql=>insert_returning_pk_sql(*values))).single_value
730
+ execute_insert(insert_sql(*values), :table=>opts[:from].first, :values=>values.size == 1 ? values.first : values)
719
731
  end
720
732
  end
721
733
 
722
- # Use the RETURNING clause to return the columns listed in returning.
723
- def insert_returning_sql(returning, *values)
724
- "#{insert_sql(*values)} RETURNING #{column_list(Array(returning))}"
725
- end
726
-
727
734
  # Insert a record returning the record inserted
728
735
  def insert_select(*values)
729
- return if opts[:disable_insert_returning] || server_version < 80200
730
- naked.clone(default_server_opts(:sql=>insert_returning_sql(nil, *values))).single_record
736
+ return unless supports_insert_select?
737
+ returning.insert(*values){|r| return r}
731
738
  end
732
739
 
733
740
  # Locks all tables in the dataset's FROM clause (but not in JOINs) with
@@ -752,6 +759,12 @@ module Sequel
752
759
  [insert_sql(columns, LiteralString.new('VALUES ' + values.map {|r| literal(Array(r))}.join(COMMA_SEPARATOR)))]
753
760
  end
754
761
 
762
+ # PostgreSQL supports using the WITH clause in subqueries if it
763
+ # supports using WITH at all (i.e. on PostgreSQL 8.4+).
764
+ def supports_cte_in_subqueries?
765
+ supports_cte?
766
+ end
767
+
755
768
  # DISTINCT ON is a PostgreSQL extension
756
769
  def supports_distinct_on?
757
770
  true
@@ -762,6 +775,14 @@ module Sequel
762
775
  true
763
776
  end
764
777
 
778
+ def supports_returning?(type)
779
+ if type == :insert
780
+ server_version >= 80200 && !opts[:disable_insert_returning]
781
+ else
782
+ server_version >= 90100
783
+ end
784
+ end
785
+
765
786
  # PostgreSQL supports timezones in literal timestamps
766
787
  def supports_timestamp_timezones?
767
788
  true
@@ -781,7 +802,7 @@ module Sequel
781
802
 
782
803
  # PostgreSQL allows deleting from joined datasets
783
804
  def delete_clause_methods
784
- DELETE_CLAUSE_METHODS
805
+ server_version >= 90100 ? DELETE_CLAUSE_METHODS_91 : DELETE_CLAUSE_METHODS
785
806
  end
786
807
 
787
808
  # Only include the primary table in the main delete clause
@@ -794,12 +815,23 @@ module Sequel
794
815
  join_from_sql(:USING, sql)
795
816
  end
796
817
 
797
- # Use the RETURNING clause to return the primary key of the inserted record, if it exists
798
- def insert_returning_pk_sql(*values)
818
+ # PostgreSQL allows a RETURNING clause.
819
+ def insert_clause_methods
820
+ if server_version >= 90100
821
+ INSERT_CLAUSE_METHODS_91
822
+ elsif server_version >= 80200
823
+ INSERT_CLAUSE_METHODS_82
824
+ else
825
+ INSERT_CLAUSE_METHODS
826
+ end
827
+ end
828
+
829
+ # Return the primary key to use for RETURNING in an INSERT statement
830
+ def insert_pk
799
831
  pk = db.primary_key(opts[:from].first) if opts[:from] && !opts[:from].empty?
800
- insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : NULL, *values)
832
+ pk ? Sequel::SQL::Identifier.new(pk) : NULL
801
833
  end
802
-
834
+
803
835
  # For multiple table support, PostgreSQL requires at least
804
836
  # two from tables, with joins allowed.
805
837
  def join_from_sql(type, sql)
@@ -866,7 +898,7 @@ module Sequel
866
898
 
867
899
  # PostgreSQL splits the main table from the joined tables
868
900
  def update_clause_methods
869
- UPDATE_CLAUSE_METHODS
901
+ server_version >= 90100 ? UPDATE_CLAUSE_METHODS_91 : UPDATE_CLAUSE_METHODS
870
902
  end
871
903
 
872
904
  # Use FROM to specify additional tables in an update query
@@ -7,9 +7,10 @@ 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
-
12
+ VIEWS_FILTER = "type = 'view'".freeze
13
+
13
14
  # Run all alter_table commands in a transaction. This is technically only
14
15
  # needed for drop column.
15
16
  def alter_table(name, generator=nil, &block)
@@ -96,11 +97,29 @@ 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
102
108
  end
103
109
 
110
+ # Override the default setting for whether to use timezones in timestamps.
111
+ # For backwards compatibility, it is set to +true+ by default.
112
+ # Anyone wanting to use SQLite's datetime functions should set it to +false+
113
+ # using this method. It's possible that the default will change in a future version,
114
+ # so anyone relying on timezones in timestamps should set this to +true+.
115
+ attr_writer :use_timestamp_timezones
116
+
117
+ # SQLite supports timezones in timestamps, since it just stores them as strings,
118
+ # but it breaks the usage of SQLite's datetime functions.
119
+ def use_timestamp_timezones?
120
+ defined?(@use_timestamp_timezones) ? @use_timestamp_timezones : (@use_timestamp_timezones = true)
121
+ end
122
+
104
123
  # A symbol signifying the value of the synchronous PRAGMA.
105
124
  def synchronous
106
125
  SYNCHRONOUS[pragma_get(:synchronous).to_i]
@@ -118,8 +137,7 @@ module Sequel
118
137
  # Options:
119
138
  # * :server - Set the server to use.
120
139
  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])}
140
+ tables_and_views(TABLES_FILTER, opts)
123
141
  end
124
142
 
125
143
  # A symbol signifying the value of the temp_store PRAGMA.
@@ -134,6 +152,14 @@ module Sequel
134
152
  pragma_set(:temp_store, value)
135
153
  end
136
154
 
155
+ # Array of symbols specifying the view names in the current database.
156
+ #
157
+ # Options:
158
+ # * :server - Set the server to use.
159
+ def views(opts={})
160
+ tables_and_views(VIEWS_FILTER, opts)
161
+ end
162
+
137
163
  private
138
164
 
139
165
  # SQLite supports limited table modification. You can add a column
@@ -306,6 +332,12 @@ module Sequel
306
332
  end
307
333
  end
308
334
 
335
+ # Backbone of the tables and views support.
336
+ def tables_and_views(filter, opts)
337
+ m = output_identifier_meth
338
+ metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(filter).map{|r| m.call(r[:name])}
339
+ end
340
+
309
341
  # SQLite uses the integer data type even for bignums. This is because they
310
342
  # are both stored internally as text, and converted when returned from
311
343
  # the database. Using an integer type instead of bigint makes it more likely
@@ -320,7 +352,8 @@ module Sequel
320
352
  SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
321
353
  COMMA_SEPARATOR = ', '.freeze
322
354
  CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
323
-
355
+ EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}
356
+
324
357
  # SQLite does not support pattern matching via regular expressions.
325
358
  # SQLite is case insensitive (depending on pragma), so use LIKE for
326
359
  # ILIKE.
@@ -331,6 +364,15 @@ module Sequel
331
364
  when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
332
365
  # SQLite is case insensitive for ASCII, and non case sensitive for other character sets
333
366
  "#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
367
+ when :^
368
+ a = literal(args.at(0))
369
+ b = literal(args.at(1))
370
+ "((~(#{a} & #{b})) & (#{a} | #{b}))"
371
+ when :extract
372
+ part = args.at(0)
373
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
374
+ expr = args.at(1)
375
+ "CAST(strftime(#{format}, #{literal(expr)}) AS #{part == :second ? 'NUMERIC' : 'INTEGER'})"
334
376
  else
335
377
  super(op, args)
336
378
  end
@@ -382,11 +424,17 @@ module Sequel
382
424
  end
383
425
 
384
426
  # SQLite supports timezones in literal timestamps, since it stores them
385
- # as text.
427
+ # as text. But using timezones in timestamps breaks SQLite datetime
428
+ # functions, so we allow the user to override the default per database.
386
429
  def supports_timestamp_timezones?
387
- true
430
+ db.use_timestamp_timezones?
388
431
  end
389
432
 
433
+ # SQLite cannot use WHERE 't'.
434
+ def supports_where_true?
435
+ false
436
+ end
437
+
390
438
  private
391
439
 
392
440
  # SQLite uses string literals instead of identifiers in AS clauses.
@@ -406,7 +454,7 @@ module Sequel
406
454
  v.each_byte{|x| blob << sprintf('%02x', x)}
407
455
  "X'#{blob}'"
408
456
  end
409
-
457
+
410
458
  # SQLite does not support the SQL WITH clause
411
459
  def select_clause_methods
412
460
  SELECT_CLAUSE_METHODS
@@ -13,26 +13,22 @@ module Sequel
13
13
  TYPE_TRANSLATOR = tt = Class.new do
14
14
  FALSE_VALUES = %w'0 false f no n'.freeze
15
15
  def boolean(s) !FALSE_VALUES.include?(s.downcase) end
16
- def blob(s) ::Sequel::SQL::Blob.new(s) end
17
16
  def integer(s) s.to_i end
18
17
  def float(s) s.to_f end
19
18
  def numeric(s) ::BigDecimal.new(s) rescue s end
20
- def date(s) ::Sequel.string_to_date(s) end
21
- def time(s) ::Sequel.string_to_time(s) end
22
- def timestamp(s) ::Sequel.database_to_application_timestamp(s) end
23
19
  end.new
24
20
 
25
21
  # Hash with string keys and callable values for converting SQLite types.
26
22
  SQLITE_TYPES = {}
27
23
  {
28
- %w'timestamp datetime' => tt.method(:timestamp),
29
- %w'date' => tt.method(:date),
30
- %w'time' => tt.method(:time),
24
+ %w'timestamp datetime' => ::Sequel.method(:database_to_application_timestamp),
25
+ %w'date' => ::Sequel.method(:string_to_date),
26
+ %w'time' => ::Sequel.method(:string_to_time),
31
27
  %w'bit bool boolean' => tt.method(:boolean),
32
28
  %w'integer smallint mediumint int bigint' => tt.method(:integer),
33
29
  %w'numeric decimal money' => tt.method(:numeric),
34
30
  %w'float double real dec fixed' + ['double precision'] => tt.method(:float),
35
- %w'blob' => tt.method(:blob)
31
+ %w'blob' => ::Sequel::SQL::Blob.method(:new)
36
32
  }.each do |k,v|
37
33
  k.each{|n| SQLITE_TYPES[n] = v}
38
34
  end
@@ -173,7 +169,7 @@ module Sequel
173
169
  end
174
170
  end
175
171
  unless cps
176
- cps = conn.prepare(sql)
172
+ cps = log_yield("Preparing #{name}: #{sql}"){conn.prepare(sql)}
177
173
  conn.prepared_statements[name] = [cps, sql]
178
174
  end
179
175
  if block
@@ -217,7 +213,7 @@ module Sequel
217
213
  # but with the keys converted to strings.
218
214
  def map_to_prepared_args(hash)
219
215
  args = {}
220
- hash.each{|k,v| args[k.to_s] = v}
216
+ hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
221
217
  args
222
218
  end
223
219
 
@@ -226,7 +222,12 @@ module Sequel
226
222
  # SQLite uses a : before the name of the argument for named
227
223
  # arguments.
228
224
  def prepared_arg(k)
229
- LiteralString.new("#{prepared_arg_placeholder}#{k}")
225
+ LiteralString.new("#{prepared_arg_placeholder}#{k.to_s.gsub('.', '__')}")
226
+ end
227
+
228
+ # Always assume a prepared argument.
229
+ def prepared_arg?(k)
230
+ true
230
231
  end
231
232
  end
232
233
 
@@ -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.
@@ -11,8 +11,8 @@ module Sequel
11
11
  # :dataserver and :username options.
12
12
  def connect(server)
13
13
  opts = server_opts(server)
14
- opts[:dataserver] = opts[:host]
15
14
  opts[:username] = opts[:user]
15
+ set_mssql_unicode_strings
16
16
  TinyTds::Client.new(opts)
17
17
  end
18
18
 
@@ -32,13 +32,36 @@ module Sequel
32
32
  begin
33
33
  m = opts[:return]
34
34
  r = nil
35
- log_yield(sql) do
36
- r = c.execute(sql)
37
- return r.send(m) if m
35
+ if (args = opts[:arguments]) && !args.empty?
36
+ types = []
37
+ values = []
38
+ args.each_with_index do |(k, v), i|
39
+ v, type = ps_arg_type(v)
40
+ types << "@#{k} #{type}"
41
+ values << "@#{k} = #{v}"
42
+ end
43
+ case m
44
+ when :do
45
+ sql = "#{sql}; SELECT @@ROWCOUNT AS AffectedRows"
46
+ single_value = true
47
+ when :insert
48
+ sql = "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
49
+ single_value = true
50
+ end
51
+ sql = "EXEC sp_executesql N'#{c.escape(sql)}', N'#{c.escape(types.join(', '))}', #{values.join(', ')}"
52
+ log_yield(sql) do
53
+ r = c.execute(sql)
54
+ r.each{|row| return row.values.first} if single_value
55
+ end
56
+ else
57
+ log_yield(sql) do
58
+ r = c.execute(sql)
59
+ return r.send(m) if m
60
+ end
38
61
  end
39
62
  yield(r) if block_given?
40
63
  rescue TinyTds::Error => e
41
- raise_error(e)
64
+ raise_error(e, :disconnect=>!c.active?)
42
65
  ensure
43
66
  r.cancel if r && c.sqlsent?
44
67
  end
@@ -74,15 +97,112 @@ module Sequel
74
97
  super
75
98
  end
76
99
 
100
+ # tiny_tds uses TinyTds::Error as the base error class.
101
+ def database_error_classes
102
+ [TinyTds::Error]
103
+ end
104
+
77
105
  # Close the TinyTds::Client object.
78
106
  def disconnect_connection(c)
79
107
  c.close
80
108
  end
109
+
110
+ # Return true if the :conn argument is present and not active.
111
+ def disconnect_error?(e, opts)
112
+ super || (opts[:conn] && !opts[:conn].active?)
113
+ end
114
+
115
+ # Return a 2 element array with the literal value and type to use
116
+ # in the prepared statement call for the given value and connection.
117
+ def ps_arg_type(v)
118
+ case v
119
+ when Fixnum
120
+ [v, 'int']
121
+ when Bignum
122
+ [v, 'bigint']
123
+ when Float
124
+ [v, 'double precision']
125
+ when Numeric
126
+ [v, 'numeric']
127
+ when Time
128
+ if v.is_a?(SQLTime)
129
+ [literal(v), 'time']
130
+ else
131
+ [literal(v), 'datetime']
132
+ end
133
+ when DateTime
134
+ [literal(v), 'datetime']
135
+ when Date
136
+ [literal(v), 'date']
137
+ when nil
138
+ ['NULL', 'nvarchar(max)']
139
+ when true
140
+ ['1', 'int']
141
+ when false
142
+ ['0', 'int']
143
+ when SQL::Blob
144
+ [literal(v), 'varbinary(max)']
145
+ else
146
+ [literal(v), 'nvarchar(max)']
147
+ end
148
+ end
81
149
  end
82
150
 
83
151
  class Dataset < Sequel::Dataset
84
152
  include Sequel::MSSQL::DatasetMethods
85
153
 
154
+ # SQLite already supports named bind arguments, so use directly.
155
+ module ArgumentMapper
156
+ include Sequel::Dataset::ArgumentMapper
157
+
158
+ protected
159
+
160
+ # Return a hash with the same values as the given hash,
161
+ # but with the keys converted to strings.
162
+ def map_to_prepared_args(hash)
163
+ args = {}
164
+ hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
165
+ args
166
+ end
167
+
168
+ private
169
+
170
+ # SQLite uses a : before the name of the argument for named
171
+ # arguments.
172
+ def prepared_arg(k)
173
+ LiteralString.new("@#{k.to_s.gsub('.', '__')}")
174
+ end
175
+
176
+ # Always assume a prepared argument.
177
+ def prepared_arg?(k)
178
+ true
179
+ end
180
+ end
181
+
182
+ # SQLite prepared statement uses a new prepared statement each time
183
+ # it is called, but it does use the bind arguments.
184
+ module PreparedStatementMethods
185
+ include ArgumentMapper
186
+
187
+ private
188
+
189
+ # Run execute_select on the database with the given SQL and the stored
190
+ # bind arguments.
191
+ def execute(sql, opts={}, &block)
192
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
193
+ end
194
+
195
+ # Same as execute, explicit due to intricacies of alias and super.
196
+ def execute_dui(sql, opts={}, &block)
197
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
198
+ end
199
+
200
+ # Same as execute, explicit due to intricacies of alias and super.
201
+ def execute_insert(sql, opts={}, &block)
202
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
203
+ end
204
+ end
205
+
86
206
  # Yield hashes with symbol keys, attempting to optimize for
87
207
  # various cases.
88
208
  def fetch_rows(sql)
@@ -107,18 +227,30 @@ module Sequel
107
227
  yield r
108
228
  end
109
229
  else
110
- result.each(each_opts, &block)
230
+ result.each(each_opts, &Proc.new)
111
231
  end
112
232
  end
113
233
  end
114
234
  self
115
235
  end
116
236
 
237
+ # Create a named prepared statement that is stored in the
238
+ # database (and connection) for reuse.
239
+ def prepare(type, name=nil, *values)
240
+ ps = to_prepared_statement(type, values)
241
+ ps.extend(PreparedStatementMethods)
242
+ if name
243
+ ps.prepared_statement_name = name
244
+ db.prepared_statements[name] = ps
245
+ end
246
+ ps
247
+ end
248
+
117
249
  private
118
250
 
119
251
  # Properly escape the given string +v+.
120
252
  def literal_string(v)
121
- db.synchronize{|c| "N'#{c.escape(v)}'"}
253
+ s = db.synchronize{|c| "#{'N' if mssql_unicode_strings}'#{c.escape(v)}'"}
122
254
  end
123
255
  end
124
256
  end
@@ -0,0 +1,55 @@
1
+ module Sequel
2
+ module EmulateOffsetWithRowNumber
3
+ # When a subselect that uses :offset is used in IN or NOT IN,
4
+ # use a nested subselect that only includes the first column
5
+ # instead of the ROW_NUMBER column added by the emulated offset support.
6
+ def complex_expression_sql(op, args)
7
+ case op
8
+ when :IN, :"NOT IN"
9
+ ds = args.at(1)
10
+ if ds.is_a?(Sequel::Dataset) && ds.opts[:offset]
11
+ c = ds.opts[:select].first
12
+ case c
13
+ when Symbol
14
+ t, cl, a = split_symbol(c)
15
+ if a
16
+ c = SQL::Identifier.new(a)
17
+ elsif t
18
+ c = SQL::Identifier.new(cl)
19
+ end
20
+ when SQL::AliasedExpression
21
+ c = SQL::Identifier.new(c.aliaz)
22
+ when SQL::QualifiedIdentifier
23
+ c = SQL::Identifier.new(c.column)
24
+ end
25
+ super(op, [args.at(0), ds.from_self.select(c)])
26
+ else
27
+ super
28
+ end
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ # Emulate OFFSET support with the ROW_NUMBER window function
35
+ #
36
+ # The implementation is ugly, cloning the current dataset and modifying
37
+ # the clone to add a ROW_NUMBER window function (and some other things),
38
+ # then using the modified clone in a subselect which is selected from.
39
+ #
40
+ # If offset is used, an order must be provided, because the use of ROW_NUMBER
41
+ # requires an order.
42
+ def select_sql
43
+ return super unless o = @opts[:offset]
44
+ raise(Error, "#{db.database_type} requires an order be provided if using an offset") unless order = @opts[:order]
45
+ dsa1 = dataset_alias(1)
46
+ rn = row_number_column
47
+ subselect_sql(unlimited.
48
+ unordered.
49
+ select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
50
+ from_self(:alias=>dsa1).
51
+ limit(@opts[:limit]).
52
+ where(SQL::Identifier.new(rn) > o))
53
+ end
54
+ end
55
+ end