sequel 3.21.0 → 3.36.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 (311) hide show
  1. data/CHANGELOG +855 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +23 -7
  4. data/Rakefile +42 -24
  5. data/bin/sequel +26 -8
  6. data/doc/active_record.rdoc +1 -0
  7. data/doc/advanced_associations.rdoc +171 -11
  8. data/doc/association_basics.rdoc +293 -43
  9. data/doc/dataset_basics.rdoc +5 -5
  10. data/doc/dataset_filtering.rdoc +8 -0
  11. data/doc/mass_assignment.rdoc +56 -0
  12. data/doc/migration.rdoc +85 -552
  13. data/doc/model_hooks.rdoc +78 -27
  14. data/doc/object_model.rdoc +541 -0
  15. data/doc/opening_databases.rdoc +97 -50
  16. data/doc/prepared_statements.rdoc +40 -14
  17. data/doc/querying.rdoc +28 -4
  18. data/doc/reflection.rdoc +30 -4
  19. data/doc/release_notes/3.22.0.txt +39 -0
  20. data/doc/release_notes/3.23.0.txt +172 -0
  21. data/doc/release_notes/3.24.0.txt +420 -0
  22. data/doc/release_notes/3.25.0.txt +88 -0
  23. data/doc/release_notes/3.26.0.txt +88 -0
  24. data/doc/release_notes/3.27.0.txt +82 -0
  25. data/doc/release_notes/3.28.0.txt +304 -0
  26. data/doc/release_notes/3.29.0.txt +459 -0
  27. data/doc/release_notes/3.30.0.txt +135 -0
  28. data/doc/release_notes/3.31.0.txt +146 -0
  29. data/doc/release_notes/3.32.0.txt +202 -0
  30. data/doc/release_notes/3.33.0.txt +157 -0
  31. data/doc/release_notes/3.34.0.txt +671 -0
  32. data/doc/release_notes/3.35.0.txt +144 -0
  33. data/doc/release_notes/3.36.0.txt +245 -0
  34. data/doc/schema_modification.rdoc +572 -0
  35. data/doc/sharding.rdoc +95 -3
  36. data/doc/testing.rdoc +129 -0
  37. data/doc/thread_safety.rdoc +17 -0
  38. data/doc/transactions.rdoc +137 -0
  39. data/doc/virtual_rows.rdoc +51 -2
  40. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  41. data/lib/sequel/adapters/ado.rb +40 -18
  42. data/lib/sequel/adapters/amalgalite.rb +17 -9
  43. data/lib/sequel/adapters/db2.rb +175 -86
  44. data/lib/sequel/adapters/dbi.rb +15 -15
  45. data/lib/sequel/adapters/do/mysql.rb +17 -11
  46. data/lib/sequel/adapters/do/postgres.rb +1 -57
  47. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  48. data/lib/sequel/adapters/do.rb +18 -36
  49. data/lib/sequel/adapters/firebird.rb +27 -208
  50. data/lib/sequel/adapters/ibmdb.rb +453 -0
  51. data/lib/sequel/adapters/informix.rb +6 -23
  52. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  53. data/lib/sequel/adapters/jdbc/db2.rb +47 -0
  54. data/lib/sequel/adapters/jdbc/derby.rb +287 -0
  55. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  56. data/lib/sequel/adapters/jdbc/h2.rb +73 -31
  57. data/lib/sequel/adapters/jdbc/hsqldb.rb +184 -0
  58. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  59. data/lib/sequel/adapters/jdbc/jtds.rb +29 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  61. data/lib/sequel/adapters/jdbc/mysql.rb +7 -24
  62. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +58 -48
  64. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  65. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  66. data/lib/sequel/adapters/jdbc/sqlserver.rb +66 -0
  67. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  68. data/lib/sequel/adapters/jdbc.rb +265 -94
  69. data/lib/sequel/adapters/mock.rb +361 -0
  70. data/lib/sequel/adapters/mysql.rb +118 -222
  71. data/lib/sequel/adapters/mysql2.rb +58 -57
  72. data/lib/sequel/adapters/odbc/mssql.rb +15 -7
  73. data/lib/sequel/adapters/odbc.rb +28 -9
  74. data/lib/sequel/adapters/openbase.rb +3 -5
  75. data/lib/sequel/adapters/oracle.rb +349 -53
  76. data/lib/sequel/adapters/postgres.rb +356 -107
  77. data/lib/sequel/adapters/shared/access.rb +32 -9
  78. data/lib/sequel/adapters/shared/db2.rb +342 -0
  79. data/lib/sequel/adapters/shared/firebird.rb +221 -0
  80. data/lib/sequel/adapters/shared/informix.rb +53 -0
  81. data/lib/sequel/adapters/shared/mssql.rb +335 -118
  82. data/lib/sequel/adapters/shared/mysql.rb +340 -76
  83. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +155 -0
  84. data/lib/sequel/adapters/shared/oracle.rb +243 -45
  85. data/lib/sequel/adapters/shared/postgres.rb +524 -279
  86. data/lib/sequel/adapters/shared/progress.rb +5 -8
  87. data/lib/sequel/adapters/shared/sqlite.rb +265 -89
  88. data/lib/sequel/adapters/sqlite.rb +101 -39
  89. data/lib/sequel/adapters/swift/mysql.rb +9 -12
  90. data/lib/sequel/adapters/swift/postgres.rb +9 -74
  91. data/lib/sequel/adapters/swift/sqlite.rb +8 -6
  92. data/lib/sequel/adapters/swift.rb +8 -7
  93. data/lib/sequel/adapters/tinytds.rb +161 -21
  94. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +66 -0
  95. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  96. data/lib/sequel/ast_transformer.rb +194 -0
  97. data/lib/sequel/connection_pool/sharded_single.rb +11 -1
  98. data/lib/sequel/connection_pool/sharded_threaded.rb +28 -2
  99. data/lib/sequel/connection_pool/single.rb +5 -0
  100. data/lib/sequel/connection_pool/threaded.rb +38 -14
  101. data/lib/sequel/connection_pool.rb +10 -2
  102. data/lib/sequel/core.rb +118 -11
  103. data/lib/sequel/database/connecting.rb +38 -28
  104. data/lib/sequel/database/dataset.rb +4 -4
  105. data/lib/sequel/database/dataset_defaults.rb +63 -2
  106. data/lib/sequel/database/logging.rb +7 -2
  107. data/lib/sequel/database/misc.rb +150 -46
  108. data/lib/sequel/database/query.rb +215 -65
  109. data/lib/sequel/database/schema_generator.rb +64 -20
  110. data/lib/sequel/database/schema_methods.rb +212 -54
  111. data/lib/sequel/dataset/actions.rb +322 -85
  112. data/lib/sequel/dataset/features.rb +96 -8
  113. data/lib/sequel/dataset/graph.rb +38 -11
  114. data/lib/sequel/dataset/misc.rb +50 -23
  115. data/lib/sequel/dataset/mutation.rb +12 -7
  116. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  117. data/lib/sequel/dataset/query.rb +237 -44
  118. data/lib/sequel/dataset/sql.rb +698 -278
  119. data/lib/sequel/dataset.rb +8 -0
  120. data/lib/sequel/exceptions.rb +4 -0
  121. data/lib/sequel/extensions/_pretty_table.rb +79 -0
  122. data/lib/sequel/extensions/arbitrary_servers.rb +108 -0
  123. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  124. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +16 -34
  125. data/lib/sequel/extensions/migration.rb +97 -11
  126. data/lib/sequel/extensions/named_timezones.rb +5 -0
  127. data/lib/sequel/extensions/null_dataset.rb +90 -0
  128. data/lib/sequel/extensions/pg_array.rb +460 -0
  129. data/lib/sequel/extensions/pg_array_ops.rb +220 -0
  130. data/lib/sequel/extensions/pg_auto_parameterize.rb +169 -0
  131. data/lib/sequel/extensions/pg_hstore.rb +296 -0
  132. data/lib/sequel/extensions/pg_hstore_ops.rb +259 -0
  133. data/lib/sequel/extensions/pg_inet.rb +89 -0
  134. data/lib/sequel/extensions/pg_json.rb +178 -0
  135. data/lib/sequel/extensions/pg_statement_cache.rb +316 -0
  136. data/lib/sequel/extensions/pretty_table.rb +5 -71
  137. data/lib/sequel/extensions/query.rb +1 -1
  138. data/lib/sequel/extensions/query_literals.rb +79 -0
  139. data/lib/sequel/extensions/schema_caching.rb +76 -0
  140. data/lib/sequel/extensions/schema_dumper.rb +251 -37
  141. data/lib/sequel/extensions/select_remove.rb +35 -0
  142. data/lib/sequel/extensions/server_block.rb +139 -0
  143. data/lib/sequel/extensions/sql_expr.rb +4 -110
  144. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  145. data/lib/sequel/extensions/to_dot.rb +95 -83
  146. data/lib/sequel/model/associations.rb +1187 -393
  147. data/lib/sequel/model/base.rb +578 -170
  148. data/lib/sequel/model/errors.rb +1 -1
  149. data/lib/sequel/model/exceptions.rb +5 -1
  150. data/lib/sequel/model/inflections.rb +1 -1
  151. data/lib/sequel/model.rb +30 -11
  152. data/lib/sequel/no_core_ext.rb +2 -0
  153. data/lib/sequel/plugins/active_model.rb +13 -1
  154. data/lib/sequel/plugins/association_pks.rb +22 -4
  155. data/lib/sequel/plugins/caching.rb +25 -18
  156. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  157. data/lib/sequel/plugins/composition.rb +44 -12
  158. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  159. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  160. data/lib/sequel/plugins/dirty.rb +214 -0
  161. data/lib/sequel/plugins/eager_each.rb +59 -0
  162. data/lib/sequel/plugins/force_encoding.rb +6 -6
  163. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  164. data/lib/sequel/plugins/identity_map.rb +123 -8
  165. data/lib/sequel/plugins/instance_filters.rb +10 -0
  166. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  167. data/lib/sequel/plugins/json_serializer.rb +58 -6
  168. data/lib/sequel/plugins/list.rb +12 -2
  169. data/lib/sequel/plugins/many_through_many.rb +79 -28
  170. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  171. data/lib/sequel/plugins/nested_attributes.rb +139 -63
  172. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  173. data/lib/sequel/plugins/prepared_statements.rb +167 -0
  174. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  175. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  176. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  177. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  178. data/lib/sequel/plugins/schema.rb +7 -2
  179. data/lib/sequel/plugins/serialization.rb +98 -49
  180. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  181. data/lib/sequel/plugins/sharding.rb +21 -54
  182. data/lib/sequel/plugins/single_table_inheritance.rb +5 -3
  183. data/lib/sequel/plugins/static_cache.rb +99 -0
  184. data/lib/sequel/plugins/subclasses.rb +10 -2
  185. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  186. data/lib/sequel/plugins/timestamps.rb +1 -1
  187. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  188. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  189. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  190. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  191. data/lib/sequel/sql.rb +526 -59
  192. data/lib/sequel/timezones.rb +57 -38
  193. data/lib/sequel/version.rb +1 -1
  194. data/spec/adapters/db2_spec.rb +146 -0
  195. data/spec/adapters/firebird_spec.rb +1 -1
  196. data/spec/adapters/mssql_spec.rb +142 -40
  197. data/spec/adapters/mysql_spec.rb +380 -234
  198. data/spec/adapters/oracle_spec.rb +67 -77
  199. data/spec/adapters/postgres_spec.rb +1050 -212
  200. data/spec/adapters/spec_helper.rb +8 -5
  201. data/spec/adapters/sqlite_spec.rb +139 -27
  202. data/spec/core/connection_pool_spec.rb +196 -93
  203. data/spec/core/database_spec.rb +879 -510
  204. data/spec/core/dataset_spec.rb +1415 -1086
  205. data/spec/core/expression_filters_spec.rb +554 -109
  206. data/spec/core/mock_adapter_spec.rb +449 -0
  207. data/spec/core/object_graph_spec.rb +86 -114
  208. data/spec/core/schema_generator_spec.rb +4 -4
  209. data/spec/core/schema_spec.rb +482 -38
  210. data/spec/core/spec_helper.rb +6 -48
  211. data/spec/extensions/active_model_spec.rb +13 -0
  212. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  213. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  214. data/spec/extensions/association_dependencies_spec.rb +15 -25
  215. data/spec/extensions/association_pks_spec.rb +66 -32
  216. data/spec/extensions/association_proxies_spec.rb +4 -4
  217. data/spec/extensions/caching_spec.rb +47 -51
  218. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  219. data/spec/extensions/columns_introspection_spec.rb +91 -0
  220. data/spec/extensions/composition_spec.rb +18 -13
  221. data/spec/{core/core_sql_spec.rb → extensions/core_extensions_spec.rb} +119 -139
  222. data/spec/extensions/dataset_associations_spec.rb +199 -0
  223. data/spec/extensions/defaults_setter_spec.rb +64 -0
  224. data/spec/extensions/dirty_spec.rb +155 -0
  225. data/spec/extensions/eager_each_spec.rb +34 -0
  226. data/spec/extensions/force_encoding_spec.rb +4 -2
  227. data/spec/extensions/hook_class_methods_spec.rb +69 -92
  228. data/spec/extensions/identity_map_spec.rb +131 -22
  229. data/spec/extensions/inflector_spec.rb +0 -4
  230. data/spec/extensions/instance_filters_spec.rb +11 -21
  231. data/spec/extensions/instance_hooks_spec.rb +72 -0
  232. data/spec/extensions/json_serializer_spec.rb +49 -6
  233. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  234. data/spec/extensions/list_spec.rb +48 -39
  235. data/spec/extensions/many_through_many_spec.rb +283 -250
  236. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  237. data/spec/extensions/migration_spec.rb +140 -35
  238. data/spec/extensions/named_timezones_spec.rb +29 -11
  239. data/spec/extensions/nested_attributes_spec.rb +251 -89
  240. data/spec/extensions/null_dataset_spec.rb +85 -0
  241. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  242. data/spec/extensions/pg_array_ops_spec.rb +105 -0
  243. data/spec/extensions/pg_array_spec.rb +196 -0
  244. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  245. data/spec/extensions/pg_hstore_ops_spec.rb +136 -0
  246. data/spec/extensions/pg_hstore_spec.rb +195 -0
  247. data/spec/extensions/pg_inet_spec.rb +44 -0
  248. data/spec/extensions/pg_json_spec.rb +101 -0
  249. data/spec/extensions/pg_statement_cache_spec.rb +209 -0
  250. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  251. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  252. data/spec/extensions/prepared_statements_spec.rb +87 -0
  253. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  254. data/spec/extensions/pretty_table_spec.rb +7 -6
  255. data/spec/extensions/query_literals_spec.rb +168 -0
  256. data/spec/extensions/query_spec.rb +6 -0
  257. data/spec/extensions/rcte_tree_spec.rb +50 -43
  258. data/spec/extensions/schema_caching_spec.rb +41 -0
  259. data/spec/extensions/schema_dumper_spec.rb +429 -22
  260. data/spec/extensions/schema_spec.rb +25 -21
  261. data/spec/extensions/select_remove_spec.rb +38 -0
  262. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  263. data/spec/extensions/serialization_spec.rb +85 -43
  264. data/spec/extensions/server_block_spec.rb +90 -0
  265. data/spec/extensions/sharding_spec.rb +69 -143
  266. data/spec/extensions/single_table_inheritance_spec.rb +23 -29
  267. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  268. data/spec/extensions/spec_helper.rb +22 -61
  269. data/spec/extensions/sql_expr_spec.rb +29 -60
  270. data/spec/extensions/static_cache_spec.rb +145 -0
  271. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  272. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  273. data/spec/extensions/timestamps_spec.rb +6 -6
  274. data/spec/extensions/to_dot_spec.rb +4 -7
  275. data/spec/extensions/touch_spec.rb +13 -14
  276. data/spec/extensions/tree_spec.rb +11 -26
  277. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  278. data/spec/extensions/update_primary_key_spec.rb +30 -24
  279. data/spec/extensions/validation_class_methods_spec.rb +30 -51
  280. data/spec/extensions/validation_helpers_spec.rb +16 -35
  281. data/spec/extensions/xml_serializer_spec.rb +29 -4
  282. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  283. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  284. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  285. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  286. data/spec/integration/associations_test.rb +659 -21
  287. data/spec/integration/database_test.rb +9 -3
  288. data/spec/integration/dataset_test.rb +605 -91
  289. data/spec/integration/eager_loader_test.rb +19 -21
  290. data/spec/integration/migrator_test.rb +2 -3
  291. data/spec/integration/model_test.rb +81 -2
  292. data/spec/integration/plugin_test.rb +454 -143
  293. data/spec/integration/prepared_statement_test.rb +263 -128
  294. data/spec/integration/schema_test.rb +309 -43
  295. data/spec/integration/spec_helper.rb +40 -30
  296. data/spec/integration/timezone_test.rb +38 -12
  297. data/spec/integration/transaction_test.rb +183 -8
  298. data/spec/integration/type_test.rb +18 -4
  299. data/spec/model/association_reflection_spec.rb +201 -7
  300. data/spec/model/associations_spec.rb +1067 -661
  301. data/spec/model/base_spec.rb +329 -95
  302. data/spec/model/dataset_methods_spec.rb +7 -27
  303. data/spec/model/eager_loading_spec.rb +677 -676
  304. data/spec/model/hooks_spec.rb +349 -67
  305. data/spec/model/model_spec.rb +240 -168
  306. data/spec/model/plugins_spec.rb +24 -13
  307. data/spec/model/record_spec.rb +490 -232
  308. data/spec/model/spec_helper.rb +14 -71
  309. data/spec/model/validations_spec.rb +11 -0
  310. data/spec/rcov.opts +2 -0
  311. metadata +178 -80
@@ -7,20 +7,14 @@ module Sequel
7
7
  include Sequel::MSSQL::DatabaseMethods
8
8
  set_adapter_scheme :tinytds
9
9
 
10
- # Transfer the :host and :user options to the
11
- # :dataserver and :username options.
10
+ # Transfer the :user option to the :username option.
12
11
  def connect(server)
13
12
  opts = server_opts(server)
14
- opts[:dataserver] = opts[:host]
15
13
  opts[:username] = opts[:user]
14
+ set_mssql_unicode_strings
16
15
  TinyTds::Client.new(opts)
17
16
  end
18
17
 
19
- # Return instance of Sequel::TinyTDS::Dataset with the given options.
20
- def dataset(opts = nil)
21
- TinyTDS::Dataset.new(self, opts)
22
- end
23
-
24
18
  # Execute the given +sql+ on the server. If the :return option
25
19
  # is present, its value should be a method symbol that is called
26
20
  # on the TinyTds::Result object returned from executing the
@@ -32,13 +26,36 @@ module Sequel
32
26
  begin
33
27
  m = opts[:return]
34
28
  r = nil
35
- log_yield(sql) do
36
- r = c.execute(sql)
37
- return r.send(m) if m
29
+ if (args = opts[:arguments]) && !args.empty?
30
+ types = []
31
+ values = []
32
+ args.each_with_index do |(k, v), i|
33
+ v, type = ps_arg_type(v)
34
+ types << "@#{k} #{type}"
35
+ values << "@#{k} = #{v}"
36
+ end
37
+ case m
38
+ when :do
39
+ sql = "#{sql}; SELECT @@ROWCOUNT AS AffectedRows"
40
+ single_value = true
41
+ when :insert
42
+ sql = "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
43
+ single_value = true
44
+ end
45
+ sql = "EXEC sp_executesql N'#{c.escape(sql)}', N'#{c.escape(types.join(', '))}', #{values.join(', ')}"
46
+ log_yield(sql) do
47
+ r = c.execute(sql)
48
+ r.each{|row| return row.values.first} if single_value
49
+ end
50
+ else
51
+ log_yield(sql) do
52
+ r = c.execute(sql)
53
+ return r.send(m) if m
54
+ end
38
55
  end
39
56
  yield(r) if block_given?
40
57
  rescue TinyTds::Error => e
41
- raise_error(e)
58
+ raise_error(e, :disconnect=>!c.active?)
42
59
  ensure
43
60
  r.cancel if r && c.sqlsent?
44
61
  end
@@ -74,51 +91,174 @@ module Sequel
74
91
  super
75
92
  end
76
93
 
94
+ # tiny_tds uses TinyTds::Error as the base error class.
95
+ def database_error_classes
96
+ [TinyTds::Error]
97
+ end
98
+
77
99
  # Close the TinyTds::Client object.
78
100
  def disconnect_connection(c)
79
101
  c.close
80
102
  end
103
+
104
+ # Return true if the :conn argument is present and not active.
105
+ def disconnect_error?(e, opts)
106
+ super || (opts[:conn] && !opts[:conn].active?)
107
+ end
108
+
109
+ # Return a 2 element array with the literal value and type to use
110
+ # in the prepared statement call for the given value and connection.
111
+ def ps_arg_type(v)
112
+ case v
113
+ when Fixnum
114
+ [v, 'int']
115
+ when Bignum
116
+ [v, 'bigint']
117
+ when Float
118
+ [v, 'double precision']
119
+ when Numeric
120
+ [v, 'numeric']
121
+ when Time
122
+ if v.is_a?(SQLTime)
123
+ [literal(v), 'time']
124
+ else
125
+ [literal(v), 'datetime']
126
+ end
127
+ when DateTime
128
+ [literal(v), 'datetime']
129
+ when Date
130
+ [literal(v), 'date']
131
+ when nil
132
+ ['NULL', 'nvarchar(max)']
133
+ when true
134
+ ['1', 'int']
135
+ when false
136
+ ['0', 'int']
137
+ when SQL::Blob
138
+ [literal(v), 'varbinary(max)']
139
+ else
140
+ [literal(v), 'nvarchar(max)']
141
+ end
142
+ end
81
143
  end
82
144
 
83
145
  class Dataset < Sequel::Dataset
84
146
  include Sequel::MSSQL::DatasetMethods
147
+
148
+ Database::DatasetClass = self
149
+
150
+ # SQLite already supports named bind arguments, so use directly.
151
+ module ArgumentMapper
152
+ include Sequel::Dataset::ArgumentMapper
153
+
154
+ protected
155
+
156
+ # Return a hash with the same values as the given hash,
157
+ # but with the keys converted to strings.
158
+ def map_to_prepared_args(hash)
159
+ args = {}
160
+ hash.each{|k,v| args[k.to_s.gsub('.', '__')] = v}
161
+ args
162
+ end
163
+
164
+ private
165
+
166
+ # SQLite uses a : before the name of the argument for named
167
+ # arguments.
168
+ def prepared_arg(k)
169
+ LiteralString.new("@#{k.to_s.gsub('.', '__')}")
170
+ end
171
+
172
+ # Always assume a prepared argument.
173
+ def prepared_arg?(k)
174
+ true
175
+ end
176
+ end
177
+
178
+ # SQLite prepared statement uses a new prepared statement each time
179
+ # it is called, but it does use the bind arguments.
180
+ module PreparedStatementMethods
181
+ include ArgumentMapper
182
+
183
+ private
184
+
185
+ # Run execute_select on the database with the given SQL and the stored
186
+ # bind arguments.
187
+ def execute(sql, opts={}, &block)
188
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
189
+ end
190
+
191
+ # Same as execute, explicit due to intricacies of alias and super.
192
+ def execute_dui(sql, opts={}, &block)
193
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
194
+ end
195
+
196
+ # Same as execute, explicit due to intricacies of alias and super.
197
+ def execute_insert(sql, opts={}, &block)
198
+ super(prepared_sql, {:arguments=>bind_arguments}.merge(opts), &block)
199
+ end
200
+ end
85
201
 
86
202
  # Yield hashes with symbol keys, attempting to optimize for
87
203
  # various cases.
88
204
  def fetch_rows(sql)
89
205
  execute(sql) do |result|
90
206
  each_opts = {:cache_rows=>false}
91
- each_opts[:timezone] = :utc if Sequel.database_timezone == :utc
92
- offset = @opts[:offset]
93
- @columns = cols = result.fields.map{|c| output_identifier(c)}
94
- if identifier_output_method
207
+ each_opts[:timezone] = :utc if db.timezone == :utc
208
+ rn = row_number_column if offset = @opts[:offset]
209
+ columns = cols = result.fields.map{|c| output_identifier(c)}
210
+ if offset
211
+ rn = row_number_column
212
+ columns = columns.dup
213
+ columns.delete(rn)
214
+ end
215
+ @columns = columns
216
+ #if identifier_output_method
95
217
  each_opts[:as] = :array
96
218
  result.each(each_opts) do |r|
97
219
  h = {}
98
220
  cols.zip(r).each{|k, v| h[k] = v}
99
- h.delete(row_number_column) if offset
221
+ h.delete(rn) if rn
100
222
  yield h
101
223
  end
224
+ =begin
225
+ # Temporarily disable this optimization, as tiny_tds uses string keys
226
+ # if result.fields is called before result.each(:symbolize_keys=>true).
227
+ # See https://github.com/rails-sqlserver/tiny_tds/issues/57
102
228
  else
103
229
  each_opts[:symbolize_keys] = true
104
230
  if offset
105
231
  result.each(each_opts) do |r|
106
- r.delete(row_number_column) if offset
232
+ r.delete(rn) if rn
107
233
  yield r
108
234
  end
109
235
  else
110
- result.each(each_opts, &block)
236
+ result.each(each_opts, &Proc.new)
111
237
  end
112
238
  end
239
+ =end
113
240
  end
114
241
  self
115
242
  end
116
243
 
244
+ # Create a named prepared statement that is stored in the
245
+ # database (and connection) for reuse.
246
+ def prepare(type, name=nil, *values)
247
+ ps = to_prepared_statement(type, values)
248
+ ps.extend(PreparedStatementMethods)
249
+ if name
250
+ ps.prepared_statement_name = name
251
+ db.set_prepared_statement(name, ps)
252
+ end
253
+ ps
254
+ end
255
+
117
256
  private
118
257
 
119
258
  # Properly escape the given string +v+.
120
- def literal_string(v)
121
- db.synchronize{|c| "N'#{c.escape(v)}'"}
259
+ def literal_string_append(sql, v)
260
+ sql << (mssql_unicode_strings ? UNICODE_STRING_START : APOS)
261
+ sql << db.synchronize{|c| c.escape(v)}.gsub(BACKSLASH_CRLF_RE, BACKSLASH_CRLF_REPLACE) << APOS
122
262
  end
123
263
  end
124
264
  end
@@ -0,0 +1,66 @@
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_append(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(sql, 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
+ sql = @opts[:append_sql] || ''
48
+ subselect_sql_append(sql, unlimited.
49
+ unordered.
50
+ select_append{ROW_NUMBER(:over, :order=>order){}.as(rn)}.
51
+ from_self(:alias=>dsa1).
52
+ limit(@opts[:limit]).
53
+ where(SQL::Identifier.new(rn) > o).
54
+ order(rn))
55
+ sql
56
+ end
57
+
58
+ private
59
+
60
+ # This emulation adds an extra row number column that should be
61
+ # eliminated.
62
+ def offset_returns_row_number_column?
63
+ true
64
+ end
65
+ end
66
+ end
@@ -1,9 +1,6 @@
1
1
  module Sequel
2
2
  class Dataset
3
3
  module StoredProcedureMethods
4
- SQL_QUERY_TYPE = Hash.new{|h,k| h[k] = k}
5
- SQL_QUERY_TYPE[:first] = SQL_QUERY_TYPE[:all] = :select
6
-
7
4
  # The name of the stored procedure to call
8
5
  attr_accessor :sproc_name
9
6
 
@@ -44,14 +41,7 @@ module Sequel
44
41
  # ignored anyway).
45
42
  def sproc_type=(type)
46
43
  @sproc_type = type
47
- meta_def("#{sql_query_type}_sql"){|*a| ''}
48
- end
49
-
50
- private
51
-
52
- # The type of query (:select, :insert, :delete, :update).
53
- def sql_query_type
54
- SQL_QUERY_TYPE[@sproc_type]
44
+ @opts[:sql] = ''
55
45
  end
56
46
  end
57
47
 
@@ -0,0 +1,194 @@
1
+ module Sequel
2
+ # The +ASTTransformer+ class is designed to handle the abstract syntax trees
3
+ # that Sequel uses internally and produce modified copies of them. By itself
4
+ # it only produces a straight copy. It's designed to be subclassed and have
5
+ # subclasses returned modified copies of the specific nodes that need to
6
+ # be modified.
7
+ class ASTTransformer
8
+ # Return +obj+ or a potentially transformed version of it.
9
+ def transform(obj)
10
+ v(obj)
11
+ end
12
+
13
+ private
14
+
15
+ # Recursive version that handles all of Sequel's internal object types
16
+ # and produces copies of them.
17
+ def v(o)
18
+ case o
19
+ when Symbol, Numeric, String, Class, TrueClass, FalseClass, NilClass
20
+ o
21
+ when Array
22
+ o.map{|x| v(x)}
23
+ when Hash
24
+ h = {}
25
+ o.each{|k, val| h[v(k)] = v(val)}
26
+ h
27
+ when SQL::ComplexExpression
28
+ SQL::ComplexExpression.new(o.op, *v(o.args))
29
+ when SQL::Identifier
30
+ SQL::Identifier.new(v(o.value))
31
+ when SQL::QualifiedIdentifier
32
+ SQL::QualifiedIdentifier.new(v(o.table), v(o.column))
33
+ when SQL::OrderedExpression
34
+ SQL::OrderedExpression.new(v(o.expression), o.descending, :nulls=>o.nulls)
35
+ when SQL::AliasedExpression
36
+ SQL::AliasedExpression.new(v(o.expression), o.aliaz)
37
+ when SQL::CaseExpression
38
+ args = [v(o.conditions), v(o.default)]
39
+ args << v(o.expression) if o.expression?
40
+ SQL::CaseExpression.new(*args)
41
+ when SQL::Cast
42
+ SQL::Cast.new(v(o.expr), o.type)
43
+ when SQL::Function
44
+ SQL::Function.new(o.f, *v(o.args))
45
+ when SQL::Subscript
46
+ SQL::Subscript.new(v(o.f), v(o.sub))
47
+ when SQL::WindowFunction
48
+ SQL::WindowFunction.new(v(o.function), v(o.window))
49
+ when SQL::Window
50
+ opts = o.opts.dup
51
+ opts[:partition] = v(opts[:partition]) if opts[:partition]
52
+ opts[:order] = v(opts[:order]) if opts[:order]
53
+ SQL::Window.new(opts)
54
+ when SQL::PlaceholderLiteralString
55
+ args = if o.args.is_a?(Hash)
56
+ h = {}
57
+ o.args.each{|k,val| h[k] = v(val)}
58
+ h
59
+ else
60
+ v(o.args)
61
+ end
62
+ SQL::PlaceholderLiteralString.new(o.str, args, o.parens)
63
+ when SQL::JoinOnClause
64
+ SQL::JoinOnClause.new(v(o.on), o.join_type, v(o.table), v(o.table_alias))
65
+ when SQL::JoinUsingClause
66
+ SQL::JoinUsingClause.new(v(o.using), o.join_type, v(o.table), v(o.table_alias))
67
+ when SQL::JoinClause
68
+ SQL::JoinClause.new(o.join_type, v(o.table), v(o.table_alias))
69
+ when SQL::Wrapper
70
+ SQL::Wrapper.new(v(o.value))
71
+ else
72
+ o
73
+ end
74
+ end
75
+ end
76
+
77
+ # Handles qualifying existing datasets, so that unqualified columns
78
+ # in the dataset are qualified with a given table name.
79
+ class Qualifier < ASTTransformer
80
+ # Store the dataset to use as the basis for qualification,
81
+ # and the table used to qualify unqualified columns.
82
+ def initialize(ds, table)
83
+ @ds = ds
84
+ @table = table
85
+ end
86
+
87
+ private
88
+
89
+ # Turn <tt>SQL::Identifier</tt>s and symbols that aren't implicitly
90
+ # qualified into <tt>SQL::QualifiedIdentifier</tt>s. For symbols that
91
+ # are not implicitly qualified by are implicitly aliased, return an
92
+ # <tt>SQL::AliasedExpression</tt>s with a qualified version of the symbol.
93
+ def v(o)
94
+ case o
95
+ when Symbol
96
+ t, column, aliaz = @ds.send(:split_symbol, o)
97
+ if t
98
+ o
99
+ elsif aliaz
100
+ SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(@table, SQL::Identifier.new(column)), aliaz)
101
+ else
102
+ SQL::QualifiedIdentifier.new(@table, o)
103
+ end
104
+ when SQL::Identifier
105
+ SQL::QualifiedIdentifier.new(@table, o)
106
+ when SQL::QualifiedIdentifier, SQL::JoinClause
107
+ # Return these directly, so we don't accidentally qualify symbols in them.
108
+ o
109
+ else
110
+ super
111
+ end
112
+ end
113
+ end
114
+
115
+ # +Unbinder+ is used to take a dataset filter and return a modified version
116
+ # that unbinds already bound values and returns a dataset with bound value
117
+ # placeholders and a hash of bind values. You can then prepare the dataset
118
+ # and use the bound variables to execute it with the same values.
119
+ #
120
+ # This class only does a limited form of unbinding where the variable names
121
+ # and values can be associated unambiguously. The only cases it handles
122
+ # are <tt>SQL::ComplexExpression<tt> with an operator in +UNBIND_OPS+, a
123
+ # first argument that's an instance of a member of +UNBIND_KEY_CLASSES+, and
124
+ # a second argument that's an instance of a member of +UNBIND_VALUE_CLASSES+.
125
+ #
126
+ # So it can handle cases like:
127
+ #
128
+ # DB.filter(:a=>1).exclude(:b=>2).where{c > 3}
129
+ #
130
+ # But it cannot handle cases like:
131
+ #
132
+ # DB.filter(:a + 1 < 0)
133
+ class Unbinder < ASTTransformer
134
+ # The <tt>SQL::ComplexExpression<tt> operates that will be considered
135
+ # for transformation.
136
+ UNBIND_OPS = [:'=', :'!=', :<, :>, :<=, :>=]
137
+
138
+ # The key classes (first argument of the ComplexExpression) that will
139
+ # considered for transformation.
140
+ UNBIND_KEY_CLASSES = [Symbol, SQL::Identifier, SQL::QualifiedIdentifier]
141
+
142
+ # The value classes (second argument of the ComplexExpression) that
143
+ # will be considered for transformation.
144
+ UNBIND_VALUE_CLASSES = [Numeric, String, Date, Time]
145
+
146
+ # The hash of bind variables that were extracted from the dataset filter.
147
+ attr_reader :binds
148
+
149
+ # Intialize an empty +binds+ hash.
150
+ def initialize
151
+ @binds = {}
152
+ end
153
+
154
+ private
155
+
156
+ # Create a suitable bound variable key for the object, which should be
157
+ # an instance of one of the +UNBIND_KEY_CLASSES+.
158
+ def bind_key(obj)
159
+ case obj
160
+ when Symbol, String
161
+ obj
162
+ when SQL::Identifier
163
+ bind_key(obj.value)
164
+ when SQL::QualifiedIdentifier
165
+ :"#{bind_key(obj.table)}.#{bind_key(obj.column)}"
166
+ else
167
+ raise Error, "unhandled object in Sequel::Unbinder#bind_key: #{obj}"
168
+ end
169
+ end
170
+
171
+ # Handle <tt>SQL::ComplexExpression</tt> instances with suitable ops
172
+ # and arguments, substituting the value with a bound variable placeholder
173
+ # and assigning it an entry in the +binds+ hash with a matching key.
174
+ def v(o)
175
+ if o.is_a?(SQL::ComplexExpression) && UNBIND_OPS.include?(o.op)
176
+ l, r = o.args
177
+ l = l.value if l.is_a?(Sequel::SQL::Wrapper)
178
+ r = r.value if r.is_a?(Sequel::SQL::Wrapper)
179
+ if UNBIND_KEY_CLASSES.any?{|c| l.is_a?(c)} && UNBIND_VALUE_CLASSES.any?{|c| r.is_a?(c)} && !r.is_a?(LiteralString)
180
+ key = bind_key(l)
181
+ if (old = binds[key]) && old != r
182
+ raise UnbindDuplicate, "two different values for #{key.inspect}: #{[r, old].inspect}"
183
+ end
184
+ binds[key] = r
185
+ SQL::ComplexExpression.new(o.op, l, :"$#{key}")
186
+ else
187
+ super
188
+ end
189
+ else
190
+ super
191
+ end
192
+ end
193
+ end
194
+ end
@@ -26,6 +26,11 @@ class Sequel::ShardedSingleConnectionPool < Sequel::ConnectionPool
26
26
  def add_servers(servers)
27
27
  servers.each{|s| @servers[s] = s}
28
28
  end
29
+
30
+ # Yield all of the currently established connections
31
+ def all_connections
32
+ @conns.values.each{|c| yield c}
33
+ end
29
34
 
30
35
  # The connection for the given server.
31
36
  def conn(server=:default)
@@ -45,7 +50,7 @@ class Sequel::ShardedSingleConnectionPool < Sequel::ConnectionPool
45
50
  # This method simulates the ConnectionPool#hold API.
46
51
  def hold(server=:default)
47
52
  begin
48
- server = @servers[server]
53
+ server = pick_server(server)
49
54
  yield(@conns[server] ||= make_new(server))
50
55
  rescue Sequel::DatabaseDisconnectError
51
56
  disconnect_server(server, &@disconnection_proc)
@@ -83,6 +88,11 @@ class Sequel::ShardedSingleConnectionPool < Sequel::ConnectionPool
83
88
  block.call(conn) if block
84
89
  end
85
90
  end
91
+
92
+ # If the server given is in the hash, return it, otherwise, return the default server.
93
+ def pick_server(server)
94
+ @servers[server]
95
+ end
86
96
 
87
97
  CONNECTION_POOL_MAP[[true, true]] = self
88
98
  end
@@ -45,6 +45,23 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
45
45
  @allocated[server]
46
46
  end
47
47
 
48
+ # Yield all of the available connections, and the ones currently allocated to
49
+ # this thread. This will not yield connections currently allocated to other
50
+ # threads, as it is not safe to operate on them. This holds the mutex while
51
+ # it is yielding all of the connections, which means that until
52
+ # the method's block returns, the pool is locked.
53
+ def all_connections
54
+ t = Thread.current
55
+ sync do
56
+ @allocated.values.each do |threads|
57
+ threads.each do |thread, conn|
58
+ yield conn if t == thread
59
+ end
60
+ end
61
+ @available_connections.values.each{|v| v.each{|c| yield c}}
62
+ end
63
+ end
64
+
48
65
  # An array of connections opened but not currently used, for the given
49
66
  # server. Nonexistent servers will return nil. Treat this as read only, do
50
67
  # not modify the resulting object.
@@ -94,7 +111,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
94
111
  # connection can be acquired, a Sequel::PoolTimeout is
95
112
  # raised.
96
113
  def hold(server=:default)
97
- sync{server = @servers[server]}
114
+ server = pick_server(server)
98
115
  t = Thread.current
99
116
  if conn = owned_connection(t, server)
100
117
  return yield(conn)
@@ -190,6 +207,11 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
190
207
  def owned_connection(thread, server)
191
208
  sync{@allocated[server][thread]}
192
209
  end
210
+
211
+ # If the server given is in the hash, return it, otherwise, return the default server.
212
+ def pick_server(server)
213
+ sync{@servers[server]}
214
+ end
193
215
 
194
216
  # Releases the connection assigned to the supplied thread and server. If the
195
217
  # server or connection given is scheduled for disconnection, remove the
@@ -199,7 +221,11 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
199
221
  if @connections_to_remove.include?(conn)
200
222
  remove(thread, conn, server)
201
223
  else
202
- available_connections(server) << allocated(server).delete(thread)
224
+ if @queue
225
+ available_connections(server).unshift(allocated(server).delete(thread))
226
+ else
227
+ available_connections(server) << allocated(server).delete(thread)
228
+ end
203
229
  end
204
230
  end
205
231
 
@@ -8,6 +8,11 @@ class Sequel::SingleConnectionPool < Sequel::ConnectionPool
8
8
  @conn ? 1 : 0
9
9
  end
10
10
 
11
+ # Yield the connection if one has been made.
12
+ def all_connections
13
+ yield @conn if @conn
14
+ end
15
+
11
16
  # Disconnect the connection from the database.
12
17
  def disconnect(opts=nil, &block)
13
18
  return unless @conn