sequel 3.21.0 → 3.29.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 (200) hide show
  1. data/CHANGELOG +413 -3
  2. data/README.rdoc +20 -6
  3. data/Rakefile +23 -16
  4. data/bin/sequel +1 -5
  5. data/doc/association_basics.rdoc +210 -43
  6. data/doc/dataset_basics.rdoc +4 -4
  7. data/doc/mass_assignment.rdoc +54 -0
  8. data/doc/migration.rdoc +15 -538
  9. data/doc/model_hooks.rdoc +72 -27
  10. data/doc/opening_databases.rdoc +86 -50
  11. data/doc/prepared_statements.rdoc +40 -13
  12. data/doc/reflection.rdoc +8 -2
  13. data/doc/release_notes/3.22.0.txt +39 -0
  14. data/doc/release_notes/3.23.0.txt +172 -0
  15. data/doc/release_notes/3.24.0.txt +420 -0
  16. data/doc/release_notes/3.25.0.txt +88 -0
  17. data/doc/release_notes/3.26.0.txt +88 -0
  18. data/doc/release_notes/3.27.0.txt +82 -0
  19. data/doc/release_notes/3.28.0.txt +304 -0
  20. data/doc/release_notes/3.29.0.txt +459 -0
  21. data/doc/schema_modification.rdoc +547 -0
  22. data/doc/sharding.rdoc +7 -1
  23. data/doc/testing.rdoc +115 -0
  24. data/doc/transactions.rdoc +137 -0
  25. data/doc/virtual_rows.rdoc +2 -2
  26. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  27. data/lib/sequel/adapters/ado.rb +40 -18
  28. data/lib/sequel/adapters/amalgalite.rb +15 -7
  29. data/lib/sequel/adapters/db2.rb +175 -86
  30. data/lib/sequel/adapters/dbi.rb +15 -15
  31. data/lib/sequel/adapters/do/mysql.rb +0 -5
  32. data/lib/sequel/adapters/do/postgres.rb +0 -5
  33. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  34. data/lib/sequel/adapters/do.rb +17 -36
  35. data/lib/sequel/adapters/firebird.rb +27 -208
  36. data/lib/sequel/adapters/ibmdb.rb +448 -0
  37. data/lib/sequel/adapters/informix.rb +6 -23
  38. data/lib/sequel/adapters/jdbc/as400.rb +5 -31
  39. data/lib/sequel/adapters/jdbc/db2.rb +44 -0
  40. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  41. data/lib/sequel/adapters/jdbc/firebird.rb +29 -0
  42. data/lib/sequel/adapters/jdbc/h2.rb +26 -17
  43. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  44. data/lib/sequel/adapters/jdbc/informix.rb +26 -0
  45. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  46. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  47. data/lib/sequel/adapters/jdbc/mysql.rb +9 -10
  48. data/lib/sequel/adapters/jdbc/oracle.rb +67 -25
  49. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -11
  50. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  51. data/lib/sequel/adapters/jdbc/sqlserver.rb +41 -0
  52. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  53. data/lib/sequel/adapters/jdbc.rb +107 -38
  54. data/lib/sequel/adapters/mock.rb +315 -0
  55. data/lib/sequel/adapters/mysql.rb +78 -182
  56. data/lib/sequel/adapters/mysql2.rb +24 -23
  57. data/lib/sequel/adapters/odbc/db2.rb +17 -0
  58. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  59. data/lib/sequel/adapters/odbc.rb +28 -9
  60. data/lib/sequel/adapters/openbase.rb +2 -4
  61. data/lib/sequel/adapters/oracle.rb +349 -51
  62. data/lib/sequel/adapters/postgres.rb +169 -21
  63. data/lib/sequel/adapters/shared/access.rb +21 -6
  64. data/lib/sequel/adapters/shared/db2.rb +288 -0
  65. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  66. data/lib/sequel/adapters/shared/informix.rb +45 -0
  67. data/lib/sequel/adapters/shared/mssql.rb +108 -65
  68. data/lib/sequel/adapters/shared/mysql.rb +56 -13
  69. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +149 -0
  70. data/lib/sequel/adapters/shared/oracle.rb +185 -34
  71. data/lib/sequel/adapters/shared/postgres.rb +107 -58
  72. data/lib/sequel/adapters/shared/progress.rb +0 -6
  73. data/lib/sequel/adapters/shared/sqlite.rb +168 -41
  74. data/lib/sequel/adapters/sqlite.rb +27 -18
  75. data/lib/sequel/adapters/swift/mysql.rb +9 -5
  76. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  77. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  78. data/lib/sequel/adapters/swift.rb +5 -5
  79. data/lib/sequel/adapters/tinytds.rb +152 -17
  80. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +63 -0
  81. data/lib/sequel/ast_transformer.rb +190 -0
  82. data/lib/sequel/connection_pool/threaded.rb +3 -2
  83. data/lib/sequel/connection_pool.rb +1 -1
  84. data/lib/sequel/core.rb +46 -5
  85. data/lib/sequel/database/connecting.rb +5 -6
  86. data/lib/sequel/database/dataset.rb +4 -4
  87. data/lib/sequel/database/dataset_defaults.rb +59 -1
  88. data/lib/sequel/database/logging.rb +1 -1
  89. data/lib/sequel/database/misc.rb +100 -19
  90. data/lib/sequel/database/query.rb +153 -58
  91. data/lib/sequel/database/schema_generator.rb +8 -5
  92. data/lib/sequel/database/schema_methods.rb +59 -29
  93. data/lib/sequel/dataset/actions.rb +194 -57
  94. data/lib/sequel/dataset/features.rb +81 -8
  95. data/lib/sequel/dataset/graph.rb +8 -7
  96. data/lib/sequel/dataset/misc.rb +50 -23
  97. data/lib/sequel/dataset/mutation.rb +5 -6
  98. data/lib/sequel/dataset/prepared_statements.rb +32 -15
  99. data/lib/sequel/dataset/query.rb +223 -42
  100. data/lib/sequel/dataset/sql.rb +58 -61
  101. data/lib/sequel/dataset.rb +8 -0
  102. data/lib/sequel/exceptions.rb +4 -0
  103. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  104. data/lib/sequel/extensions/migration.rb +6 -4
  105. data/lib/sequel/extensions/named_timezones.rb +5 -0
  106. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  107. data/lib/sequel/extensions/to_dot.rb +95 -83
  108. data/lib/sequel/model/associations.rb +997 -331
  109. data/lib/sequel/model/base.rb +386 -129
  110. data/lib/sequel/model/errors.rb +1 -1
  111. data/lib/sequel/model/exceptions.rb +5 -1
  112. data/lib/sequel/model.rb +15 -8
  113. data/lib/sequel/plugins/association_pks.rb +22 -4
  114. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  115. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  116. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  117. data/lib/sequel/plugins/force_encoding.rb +6 -6
  118. data/lib/sequel/plugins/identity_map.rb +114 -7
  119. data/lib/sequel/plugins/many_through_many.rb +65 -7
  120. data/lib/sequel/plugins/prepared_statements.rb +151 -0
  121. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  122. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  123. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  124. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  125. data/lib/sequel/plugins/serialization.rb +6 -1
  126. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  127. data/lib/sequel/plugins/sharding.rb +12 -25
  128. data/lib/sequel/plugins/single_table_inheritance.rb +3 -1
  129. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  130. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  131. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  132. data/lib/sequel/sql.rb +107 -51
  133. data/lib/sequel/timezones.rb +44 -35
  134. data/lib/sequel/version.rb +1 -1
  135. data/spec/adapters/db2_spec.rb +146 -0
  136. data/spec/adapters/mssql_spec.rb +65 -29
  137. data/spec/adapters/mysql_spec.rb +122 -123
  138. data/spec/adapters/oracle_spec.rb +48 -76
  139. data/spec/adapters/postgres_spec.rb +213 -61
  140. data/spec/adapters/spec_helper.rb +6 -5
  141. data/spec/adapters/sqlite_spec.rb +35 -21
  142. data/spec/core/connection_pool_spec.rb +71 -92
  143. data/spec/core/core_sql_spec.rb +20 -31
  144. data/spec/core/database_spec.rb +729 -508
  145. data/spec/core/dataset_spec.rb +980 -1044
  146. data/spec/core/expression_filters_spec.rb +159 -42
  147. data/spec/core/mock_adapter_spec.rb +378 -0
  148. data/spec/core/object_graph_spec.rb +48 -114
  149. data/spec/core/schema_generator_spec.rb +3 -3
  150. data/spec/core/schema_spec.rb +298 -38
  151. data/spec/core/spec_helper.rb +6 -48
  152. data/spec/extensions/association_pks_spec.rb +38 -0
  153. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  154. data/spec/extensions/columns_introspection_spec.rb +91 -0
  155. data/spec/extensions/dataset_associations_spec.rb +199 -0
  156. data/spec/extensions/defaults_setter_spec.rb +64 -0
  157. data/spec/extensions/identity_map_spec.rb +162 -0
  158. data/spec/extensions/instance_hooks_spec.rb +71 -0
  159. data/spec/extensions/many_through_many_spec.rb +195 -20
  160. data/spec/extensions/migration_spec.rb +17 -17
  161. data/spec/extensions/named_timezones_spec.rb +22 -2
  162. data/spec/extensions/nested_attributes_spec.rb +4 -0
  163. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  164. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  165. data/spec/extensions/prepared_statements_spec.rb +72 -0
  166. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  167. data/spec/extensions/schema_dumper_spec.rb +2 -2
  168. data/spec/extensions/schema_spec.rb +13 -21
  169. data/spec/extensions/serialization_modification_detection_spec.rb +37 -0
  170. data/spec/extensions/serialization_spec.rb +5 -8
  171. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  172. data/spec/extensions/spec_helper.rb +7 -1
  173. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  174. data/spec/extensions/to_dot_spec.rb +3 -5
  175. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  176. data/spec/extensions/xml_serializer_spec.rb +16 -4
  177. data/spec/integration/associations_test.rb +522 -21
  178. data/spec/integration/database_test.rb +4 -2
  179. data/spec/integration/dataset_test.rb +375 -62
  180. data/spec/integration/eager_loader_test.rb +19 -21
  181. data/spec/integration/model_test.rb +80 -1
  182. data/spec/integration/plugin_test.rb +304 -116
  183. data/spec/integration/prepared_statement_test.rb +200 -120
  184. data/spec/integration/schema_test.rb +161 -30
  185. data/spec/integration/spec_helper.rb +39 -30
  186. data/spec/integration/timezone_test.rb +38 -12
  187. data/spec/integration/transaction_test.rb +172 -5
  188. data/spec/integration/type_test.rb +17 -3
  189. data/spec/model/association_reflection_spec.rb +174 -7
  190. data/spec/model/associations_spec.rb +849 -661
  191. data/spec/model/base_spec.rb +255 -95
  192. data/spec/model/dataset_methods_spec.rb +7 -27
  193. data/spec/model/eager_loading_spec.rb +640 -676
  194. data/spec/model/hooks_spec.rb +309 -67
  195. data/spec/model/model_spec.rb +207 -167
  196. data/spec/model/plugins_spec.rb +24 -13
  197. data/spec/model/record_spec.rb +321 -218
  198. data/spec/model/spec_helper.rb +13 -71
  199. data/spec/model/validations_spec.rb +11 -0
  200. metadata +95 -38
@@ -0,0 +1,214 @@
1
+ module Sequel
2
+ module Firebird
3
+ module DatabaseMethods
4
+ AUTO_INCREMENT = ''.freeze
5
+ TEMPORARY = 'GLOBAL TEMPORARY '.freeze
6
+
7
+ def clear_primary_key(*tables)
8
+ tables.each{|t| @primary_keys.delete(dataset.send(:input_identifier, t))}
9
+ end
10
+
11
+ def create_trigger(*args)
12
+ self << create_trigger_sql(*args)
13
+ end
14
+
15
+ def database_type
16
+ :firebird
17
+ end
18
+
19
+ def drop_sequence(name)
20
+ self << drop_sequence_sql(name)
21
+ end
22
+
23
+ # Return primary key for the given table.
24
+ def primary_key(table)
25
+ t = dataset.send(:input_identifier, table)
26
+ @primary_keys.fetch(t) do
27
+ pk = fetch("SELECT RDB$FIELD_NAME FROM RDB$INDEX_SEGMENTS NATURAL JOIN RDB$RELATION_CONSTRAINTS WHERE RDB$CONSTRAINT_TYPE = 'PRIMARY KEY' AND RDB$RELATION_NAME = ?", t).single_value
28
+ @primary_keys[t] = dataset.send(:output_identifier, pk.rstrip) if pk
29
+ end
30
+ end
31
+
32
+ def restart_sequence(*args)
33
+ self << restart_sequence_sql(*args)
34
+ end
35
+
36
+ def sequences(opts={})
37
+ ds = self[:"rdb$generators"].server(opts[:server]).filter(:"rdb$system_flag" => 0).select(:"rdb$generator_name")
38
+ block_given? ? yield(ds) : ds.map{|r| ds.send(:output_identifier, r[:"rdb$generator_name"])}
39
+ end
40
+
41
+ def tables(opts={})
42
+ tables_or_views(0, opts)
43
+ end
44
+
45
+ def views(opts={})
46
+ tables_or_views(1, opts)
47
+ end
48
+
49
+ private
50
+
51
+ # Use Firebird specific syntax for add column
52
+ def alter_table_sql(table, op)
53
+ case op[:op]
54
+ when :add_column
55
+ "ALTER TABLE #{quote_schema_table(table)} ADD #{column_definition_sql(op)}"
56
+ when :drop_column
57
+ "ALTER TABLE #{quote_schema_table(table)} DROP #{column_definition_sql(op)}"
58
+ when :rename_column
59
+ "ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TO #{quote_identifier(op[:new_name])}"
60
+ when :set_column_type
61
+ "ALTER TABLE #{quote_schema_table(table)} ALTER #{quote_identifier(op[:name])} TYPE #{type_literal(op)}"
62
+ else
63
+ super(table, op)
64
+ end
65
+ end
66
+
67
+ def auto_increment_sql()
68
+ AUTO_INCREMENT
69
+ end
70
+
71
+ def create_sequence_sql(name, opts={})
72
+ "CREATE SEQUENCE #{quote_identifier(name)}"
73
+ end
74
+
75
+ # Firebird gets an override because of the mess of creating a
76
+ # sequence and trigger for auto-incrementing primary keys.
77
+ def create_table_from_generator(name, generator, options)
78
+ drop_statement, create_statements = create_table_sql_list(name, generator, options)
79
+ (execute_ddl(drop_statement) rescue nil) if drop_statement
80
+ create_statements.each{|sql| execute_ddl(sql)}
81
+ end
82
+
83
+ def create_table_sql_list(name, generator, options={})
84
+ statements = [create_table_sql(name, generator, options)]
85
+ drop_seq_statement = nil
86
+ generator.columns.each do |c|
87
+ if c[:auto_increment]
88
+ c[:sequence_name] ||= "seq_#{name}_#{c[:name]}"
89
+ unless c[:create_sequence] == false
90
+ drop_seq_statement = drop_sequence_sql(c[:sequence_name])
91
+ statements << create_sequence_sql(c[:sequence_name])
92
+ statements << restart_sequence_sql(c[:sequence_name], {:restart_position => c[:sequence_start_position]}) if c[:sequence_start_position]
93
+ end
94
+ unless c[:create_trigger] == false
95
+ c[:trigger_name] ||= "BI_#{name}_#{c[:name]}"
96
+ c[:quoted_name] = quote_identifier(c[:name])
97
+ trigger_definition = <<-END
98
+ begin
99
+ if ((new.#{c[:quoted_name]} is null) or (new.#{c[:quoted_name]} = 0)) then
100
+ begin
101
+ new.#{c[:quoted_name]} = next value for #{c[:sequence_name]};
102
+ end
103
+ end
104
+ END
105
+ statements << create_trigger_sql(name, c[:trigger_name], trigger_definition, {:events => [:insert]})
106
+ end
107
+ end
108
+ end
109
+ [drop_seq_statement, statements]
110
+ end
111
+
112
+ def create_trigger_sql(table, name, definition, opts={})
113
+ events = opts[:events] ? Array(opts[:events]) : [:insert, :update, :delete]
114
+ whence = opts[:after] ? 'AFTER' : 'BEFORE'
115
+ inactive = opts[:inactive] ? 'INACTIVE' : 'ACTIVE'
116
+ position = opts.fetch(:position, 0)
117
+ sql = <<-end_sql
118
+ CREATE TRIGGER #{quote_identifier(name)} for #{quote_identifier(table)}
119
+ #{inactive} #{whence} #{events.map{|e| e.to_s.upcase}.join(' OR ')} position #{position}
120
+ as #{definition}
121
+ end_sql
122
+ sql
123
+ end
124
+
125
+ def drop_sequence_sql(name)
126
+ "DROP SEQUENCE #{quote_identifier(name)}"
127
+ end
128
+
129
+ def remove_cached_schema(table)
130
+ clear_primary_key(table)
131
+ super
132
+ end
133
+
134
+ def restart_sequence_sql(name, opts={})
135
+ seq_name = quote_identifier(name)
136
+ "ALTER SEQUENCE #{seq_name} RESTART WITH #{opts[:restart_position]}"
137
+ end
138
+
139
+ def tables_or_views(type, opts)
140
+ ds = self[:"rdb$relations"].server(opts[:server]).filter(:"rdb$relation_type" => type, Sequel::SQL::Function.new(:COALESCE, :"rdb$system_flag", 0) => 0).select(:"rdb$relation_name")
141
+ ds.map{|r| ds.send(:output_identifier, r[:"rdb$relation_name"].rstrip)}
142
+ end
143
+
144
+ def type_literal_generic_string(column)
145
+ column[:text] ? :"BLOB SUB_TYPE TEXT" : super
146
+ end
147
+ end
148
+
149
+ module DatasetMethods
150
+ BOOL_TRUE = '1'.freeze
151
+ BOOL_FALSE = '0'.freeze
152
+ NULL = LiteralString.new('NULL').freeze
153
+ COMMA_SEPARATOR = ', '.freeze
154
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with distinct limit columns from join where group having compounds order')
155
+ INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'into columns values returning')
156
+
157
+ # Insert given values into the database.
158
+ def insert(*values)
159
+ if @opts[:sql] || @opts[:returning]
160
+ super
161
+ else
162
+ returning(insert_pk).insert(*values){|r| return r.values.first}
163
+ end
164
+ end
165
+
166
+ # Insert a record returning the record inserted
167
+ def insert_select(*values)
168
+ returning.insert(*values){|r| return r}
169
+ end
170
+
171
+ def requires_sql_standard_datetimes?
172
+ true
173
+ end
174
+
175
+ def supports_insert_select?
176
+ true
177
+ end
178
+
179
+ # Firebird does not support INTERSECT or EXCEPT
180
+ def supports_intersect_except?
181
+ false
182
+ end
183
+
184
+ private
185
+
186
+ def insert_clause_methods
187
+ INSERT_CLAUSE_METHODS
188
+ end
189
+
190
+ def insert_pk(*values)
191
+ pk = db.primary_key(opts[:from].first)
192
+ pk ? Sequel::SQL::Identifier.new(pk) : NULL
193
+ end
194
+
195
+ def literal_false
196
+ BOOL_FALSE
197
+ end
198
+
199
+ def literal_true
200
+ BOOL_TRUE
201
+ end
202
+
203
+ # The order of clauses in the SELECT SQL statement
204
+ def select_clause_methods
205
+ SELECT_CLAUSE_METHODS
206
+ end
207
+
208
+ def select_limit_sql(sql)
209
+ sql << " FIRST #{@opts[:limit]}" if @opts[:limit]
210
+ sql << " SKIP #{@opts[:offset]}" if @opts[:offset]
211
+ end
212
+ end
213
+ end
214
+ 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
@@ -1,3 +1,5 @@
1
+ Sequel.require 'adapters/utils/emulate_offset_with_row_number'
2
+
1
3
  module Sequel
2
4
  Dataset::NON_SQL_OPTIONS << :disable_insert_output
3
5
  module MSSQL
@@ -11,10 +13,16 @@ module Sequel
11
13
  SQL_ROLLBACK_TO_SAVEPOINT = 'IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION autopoint_%d'.freeze
12
14
  SQL_SAVEPOINT = 'SAVE TRANSACTION autopoint_%d'.freeze
13
15
 
16
+ # Whether to use N'' to quote strings, which allows unicode characters inside the
17
+ # strings. True by default for compatibility, can be set to false for a possible
18
+ # performance increase. This sets the default for all datasets created from this
19
+ # Database object.
20
+ attr_accessor :mssql_unicode_strings
21
+
14
22
  # The types to check for 0 scale to transform :decimal types
15
23
  # to :integer.
16
24
  DECIMAL_TYPE_RE = /number|numeric|decimal/io
17
-
25
+
18
26
  # Microsoft SQL Server uses the :mssql type.
19
27
  def database_type
20
28
  :mssql
@@ -47,11 +55,13 @@ module Sequel
47
55
  # Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
48
56
  # information on tables.
49
57
  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])}
58
+ information_schema_tables('BASE TABLE', opts)
59
+ end
60
+
61
+ # Microsoft SQL Server supports using the INFORMATION_SCHEMA to get
62
+ # information on views.
63
+ def views(opts={})
64
+ information_schema_tables('VIEW', opts)
55
65
  end
56
66
 
57
67
  private
@@ -97,7 +107,7 @@ module Sequel
97
107
  # Commit the active transaction on the connection, does not commit/release
98
108
  # savepoints.
99
109
  def commit_transaction(conn, opts={})
100
- log_connection_execute(conn, commit_transaction_sql) unless Thread.current[:sequel_transaction_depth] > 1
110
+ log_connection_execute(conn, commit_transaction_sql) unless @transactions[conn][:savepoint_level] > 1
101
111
  end
102
112
 
103
113
  # SQL to COMMIT a transaction.
@@ -117,6 +127,15 @@ module Sequel
117
127
  "DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))} ON #{quote_schema_table(table)}"
118
128
  end
119
129
 
130
+ # Backbone of the tables and views support.
131
+ def information_schema_tables(type, opts)
132
+ m = output_identifier_meth
133
+ metadata_dataset.from(:information_schema__tables___t).
134
+ select(:table_name).
135
+ filter(:table_type=>type, :table_schema=>(opts[:schema]||default_schema||'dbo').to_s).
136
+ map{|x| m.call(x[:table_name])}
137
+ end
138
+
120
139
  # Always quote identifiers in the metadata_dataset, so schema parsing works.
121
140
  def metadata_dataset
122
141
  ds = super
@@ -139,20 +158,41 @@ module Sequel
139
158
  SQL_ROLLBACK
140
159
  end
141
160
 
142
- # MSSQL uses the INFORMATION_SCHEMA to hold column information. This method does
143
- # not support the parsing of primary key information.
161
+ # The closest MSSQL equivalent of a boolean datatype is the bit type.
162
+ def schema_column_type(db_type)
163
+ case db_type
164
+ when /\A(bit)\z/io
165
+ :boolean
166
+ else
167
+ super
168
+ end
169
+ end
170
+
171
+ # MSSQL uses the INFORMATION_SCHEMA to hold column information, and
172
+ # parses primary key information from the sysindexes, sysindexkeys,
173
+ # and syscolumns system tables.
144
174
  def schema_parse_table(table_name, opts)
145
- m = output_identifier_meth
146
- m2 = input_identifier_meth
175
+ m = output_identifier_meth(opts[:dataset])
176
+ m2 = input_identifier_meth(opts[:dataset])
177
+ tn = m2.call(table_name.to_s)
178
+ table_id = get{object_id(tn)}
179
+ pk_index_id = metadata_dataset.from(:sysindexes).
180
+ where(:id=>table_id, :indid=>1..254){{(status & 2048)=>2048}}.
181
+ get(:indid)
182
+ pk_cols = metadata_dataset.from(:sysindexkeys___sik).
183
+ join(:syscolumns___sc, :id=>:id, :colid=>:colid).
184
+ where(:sik__id=>table_id, :sik__indid=>pk_index_id).
185
+ select_order_map(:sc__name)
147
186
  ds = metadata_dataset.from(:information_schema__tables___t).
148
187
  join(:information_schema__columns___c, :table_catalog=>:table_catalog,
149
188
  :table_schema => :table_schema, :table_name => :table_name).
150
189
  select(:column_name___column, :data_type___db_type, :character_maximum_length___max_chars, :column_default___default, :is_nullable___allow_null, :numeric_precision___column_size, :numeric_scale___scale).
151
- filter(:c__table_name=>m2.call(table_name.to_s))
190
+ filter(:c__table_name=>tn)
152
191
  if schema = opts[:schema] || default_schema
153
192
  ds.filter!(:c__table_schema=>schema)
154
193
  end
155
194
  ds.map do |row|
195
+ row[:primary_key] = pk_cols.include?(row[:column])
156
196
  row[:allow_null] = row[:allow_null] == 'YES' ? true : false
157
197
  row[:default] = nil if blank_object?(row[:default])
158
198
  row[:type] = if row[:db_type] =~ DECIMAL_TYPE_RE && row[:scale] == 0
@@ -163,6 +203,11 @@ module Sequel
163
203
  [m.call(row.delete(:column)), row]
164
204
  end
165
205
  end
206
+
207
+ # Set the mssql_unicode_strings settings from the given options.
208
+ def set_mssql_unicode_strings
209
+ @mssql_unicode_strings = typecast_value_boolean(@opts.fetch(:mssql_unicode_strings, true))
210
+ end
166
211
 
167
212
  # MSSQL has both datetime and timestamp classes, most people are going
168
213
  # to want datetime
@@ -199,6 +244,8 @@ module Sequel
199
244
  end
200
245
 
201
246
  module DatasetMethods
247
+ include EmulateOffsetWithRowNumber
248
+
202
249
  BOOL_TRUE = '1'.freeze
203
250
  BOOL_FALSE = '0'.freeze
204
251
  COMMA_SEPARATOR = ', '.freeze
@@ -210,20 +257,37 @@ module Sequel
210
257
  UPDLOCK = ' WITH (UPDLOCK)'.freeze
211
258
  WILDCARD = LiteralString.new('*').freeze
212
259
  CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
260
+ EXTRACT_MAP = {:year=>"yy", :month=>"m", :day=>"d", :hour=>"hh", :minute=>"n", :second=>"s"}
261
+
262
+ # Allow overriding of the mssql_unicode_strings option at the dataset level.
263
+ attr_accessor :mssql_unicode_strings
264
+
265
+ # Copy the mssql_unicode_strings option from the +db+ object.
266
+ def initialize(db, opts={})
267
+ super
268
+ @mssql_unicode_strings = db.mssql_unicode_strings
269
+ end
213
270
 
214
271
  # MSSQL uses + for string concatenation, and LIKE is case insensitive by default.
215
272
  def complex_expression_sql(op, args)
216
273
  case op
217
274
  when :'||'
218
275
  super(:+, args)
219
- when :ILIKE
220
- super(:LIKE, args)
221
- when :"NOT ILIKE"
222
- super(:"NOT LIKE", args)
276
+ when :LIKE, :"NOT LIKE"
277
+ super(op, args.map{|a| LiteralString.new("(#{literal(a)} COLLATE Latin1_General_CS_AS)")})
278
+ when :ILIKE, :"NOT ILIKE"
279
+ super((op == :ILIKE ? :LIKE : :"NOT LIKE"), args)
223
280
  when :<<
224
- "(#{literal(args[0])} * POWER(2, #{literal(args[1])}))"
281
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} * POWER(2, #{literal(b)}))"}
225
282
  when :>>
226
- "(#{literal(args[0])} / POWER(2, #{literal(args[1])}))"
283
+ complex_expression_arg_pairs(args){|a, b| "(#{literal(a)} / POWER(2, #{literal(b)}))"}
284
+ when :extract
285
+ part = args.at(0)
286
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
287
+ expr = literal(args.at(1))
288
+ s = "datepart(#{format}, #{expr})"
289
+ s = "CAST((#{s} + datepart(ns, #{expr})/1000000000.0) AS double precision)" if part == :second
290
+ s
227
291
  else
228
292
  super(op, args)
229
293
  end
@@ -244,12 +308,6 @@ module Sequel
244
308
  mutation_method(:disable_insert_output)
245
309
  end
246
310
 
247
- # When returning all rows, if an offset is used, delete the row_number column
248
- # before yielding the row.
249
- def fetch_rows(sql, &block)
250
- @opts[:offset] ? super(sql){|r| r.delete(row_number_column); yield r} : super(sql, &block)
251
- end
252
-
253
311
  # MSSQL uses the CONTAINS keyword for full text search
254
312
  def full_text_search(cols, terms, opts = {})
255
313
  filter("CONTAINS (#{literal(cols)}, #{literal(terms)})")
@@ -257,8 +315,8 @@ module Sequel
257
315
 
258
316
  # Use the OUTPUT clause to get the value of all columns for the newly inserted record.
259
317
  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]
318
+ return unless supports_insert_select?
319
+ naked.clone(default_server_opts(:sql=>output(nil, [SQL::ColumnAll.new(:inserted)]).insert_sql(*values))).single_record
262
320
  end
263
321
 
264
322
  # Specify a table for a SELECT ... INTO query.
@@ -266,14 +324,6 @@ module Sequel
266
324
  clone(:into => table)
267
325
  end
268
326
 
269
- # SQL Server does not support CTEs on subqueries, so move any CTEs
270
- # on joined datasets to the top level. The user is responsible for
271
- # resolving any name clashes this may cause.
272
- def join_table(type, table, expr=nil, table_alias={}, &block)
273
- return super unless Dataset === table && table.opts[:with]
274
- clone(:with => (opts[:with] || []) + table.opts[:with]).join_table(type, table.clone(:with => nil), expr, table_alias, &block)
275
- end
276
-
277
327
  # MSSQL uses a UNION ALL statement to insert multiple values at once.
278
328
  def multi_insert_sql(columns, values)
279
329
  [insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
@@ -319,34 +369,16 @@ module Sequel
319
369
  "[#{name}]"
320
370
  end
321
371
 
322
- # MSSQL Requires the use of the ROW_NUMBER window function to emulate
323
- # an offset. This implementation requires MSSQL 2005 or greater (offset
324
- # can't be emulated well in MSSQL 2000).
325
- #
326
- # The implementation is ugly, cloning the current dataset and modifying
327
- # the clone to add a ROW_NUMBER window function (and some other things),
328
- # then using the modified clone in a subselect which is selected from.
329
- #
330
- # If offset is used, an order must be provided, because the use of ROW_NUMBER
331
- # requires an order.
332
- def select_sql
333
- return super unless o = @opts[:offset]
334
- raise(Error, 'MSSQL requires an order be provided if using an offset') unless order = @opts[:order]
335
- dsa1 = dataset_alias(1)
336
- rn = row_number_column
337
- subselect_sql(unlimited.
338
- unordered.
339
- select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
340
- from_self(:alias=>dsa1).
341
- limit(@opts[:limit]).
342
- where(SQL::Identifier.new(rn) > o))
343
- end
344
-
345
372
  # The version of the database server.
346
373
  def server_version
347
374
  db.server_version(@opts[:server])
348
375
  end
349
376
 
377
+ # MSSQL supports insert_select via the OUTPUT clause.
378
+ def supports_insert_select?
379
+ supports_output_clause? && !opts[:disable_insert_output]
380
+ end
381
+
350
382
  # MSSQL 2005+ supports INTERSECT and EXCEPT
351
383
  def supports_intersect_except?
352
384
  is_2005_or_later?
@@ -381,6 +413,11 @@ module Sequel
381
413
  def supports_window_functions?
382
414
  true
383
415
  end
416
+
417
+ # MSSQL cannot use WHERE 1.
418
+ def supports_where_true?
419
+ false
420
+ end
384
421
 
385
422
  protected
386
423
  # MSSQL does not allow ordering in sub-clauses unless 'top' (limit) is specified
@@ -389,6 +426,7 @@ module Sequel
389
426
  end
390
427
 
391
428
  private
429
+
392
430
  def is_2005_or_later?
393
431
  server_version >= 9000000
394
432
  end
@@ -434,6 +472,16 @@ module Sequel
434
472
  INSERT_CLAUSE_METHODS
435
473
  end
436
474
 
475
+ # Use OUTPUT INSERTED.* to return all columns of the inserted row,
476
+ # for use with the prepared statement code.
477
+ def insert_output_sql(sql)
478
+ if @opts.has_key?(:returning)
479
+ sql << " OUTPUT INSERTED.*"
480
+ else
481
+ output_sql(sql)
482
+ end
483
+ end
484
+
437
485
  # MSSQL uses a literal hexidecimal number for blob strings
438
486
  def literal_blob(v)
439
487
  blob = '0x'
@@ -441,9 +489,10 @@ module Sequel
441
489
  blob
442
490
  end
443
491
 
444
- # Use unicode string syntax for all strings. Don't double backslashes.
492
+ # Optionally use unicode string syntax for all strings. Don't double
493
+ # backslashes.
445
494
  def literal_string(v)
446
- "N'#{v.gsub(/'/, "''")}'"
495
+ "#{'N' if mssql_unicode_strings}'#{v.gsub(/'/, "''")}'"
447
496
  end
448
497
 
449
498
  # Use 0 for false on MSSQL
@@ -456,11 +505,6 @@ module Sequel
456
505
  BOOL_TRUE
457
506
  end
458
507
 
459
- # The alias to use for the row_number column when emulating OFFSET
460
- def row_number_column
461
- :x_sequel_row_number_x
462
- end
463
-
464
508
  # MSSQL adds the limit before the columns
465
509
  def select_clause_methods
466
510
  SELECT_CLAUSE_METHODS
@@ -508,7 +552,6 @@ module Sequel
508
552
  end
509
553
  alias delete_output_sql output_sql
510
554
  alias update_output_sql output_sql
511
- alias insert_output_sql output_sql
512
555
 
513
556
  # MSSQL supports the OUTPUT clause for UPDATE statements.
514
557
  # It also allows prepending a WITH clause.