sequel 3.21.0 → 3.41.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 (363) hide show
  1. data/CHANGELOG +1131 -3
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +106 -65
  4. data/Rakefile +59 -27
  5. data/bin/sequel +50 -26
  6. data/doc/active_record.rdoc +68 -52
  7. data/doc/advanced_associations.rdoc +201 -75
  8. data/doc/association_basics.rdoc +327 -79
  9. data/doc/bin_sequel.rdoc +144 -0
  10. data/doc/cheat_sheet.rdoc +21 -21
  11. data/doc/core_extensions.rdoc +374 -0
  12. data/doc/dataset_basics.rdoc +10 -10
  13. data/doc/dataset_filtering.rdoc +54 -42
  14. data/doc/mass_assignment.rdoc +56 -0
  15. data/doc/migration.rdoc +89 -557
  16. data/doc/model_hooks.rdoc +80 -29
  17. data/doc/object_model.rdoc +576 -0
  18. data/doc/opening_databases.rdoc +121 -53
  19. data/doc/prepared_statements.rdoc +41 -15
  20. data/doc/querying.rdoc +137 -56
  21. data/doc/reflection.rdoc +36 -10
  22. data/doc/release_notes/3.22.0.txt +39 -0
  23. data/doc/release_notes/3.23.0.txt +172 -0
  24. data/doc/release_notes/3.24.0.txt +420 -0
  25. data/doc/release_notes/3.25.0.txt +88 -0
  26. data/doc/release_notes/3.26.0.txt +88 -0
  27. data/doc/release_notes/3.27.0.txt +82 -0
  28. data/doc/release_notes/3.28.0.txt +304 -0
  29. data/doc/release_notes/3.29.0.txt +459 -0
  30. data/doc/release_notes/3.30.0.txt +135 -0
  31. data/doc/release_notes/3.31.0.txt +146 -0
  32. data/doc/release_notes/3.32.0.txt +202 -0
  33. data/doc/release_notes/3.33.0.txt +157 -0
  34. data/doc/release_notes/3.34.0.txt +671 -0
  35. data/doc/release_notes/3.35.0.txt +144 -0
  36. data/doc/release_notes/3.36.0.txt +245 -0
  37. data/doc/release_notes/3.37.0.txt +338 -0
  38. data/doc/release_notes/3.38.0.txt +234 -0
  39. data/doc/release_notes/3.39.0.txt +237 -0
  40. data/doc/release_notes/3.40.0.txt +73 -0
  41. data/doc/release_notes/3.41.0.txt +155 -0
  42. data/doc/schema_modification.rdoc +585 -0
  43. data/doc/sharding.rdoc +99 -8
  44. data/doc/sql.rdoc +154 -112
  45. data/doc/testing.rdoc +169 -0
  46. data/doc/thread_safety.rdoc +17 -0
  47. data/doc/transactions.rdoc +137 -0
  48. data/doc/validations.rdoc +1 -1
  49. data/doc/virtual_rows.rdoc +78 -43
  50. data/lib/sequel/adapters/ado/access.rb +334 -0
  51. data/lib/sequel/adapters/ado/mssql.rb +18 -12
  52. data/lib/sequel/adapters/ado.rb +69 -21
  53. data/lib/sequel/adapters/amalgalite.rb +17 -14
  54. data/lib/sequel/adapters/cubrid.rb +143 -0
  55. data/lib/sequel/adapters/db2.rb +178 -85
  56. data/lib/sequel/adapters/dbi.rb +19 -21
  57. data/lib/sequel/adapters/do/mysql.rb +17 -11
  58. data/lib/sequel/adapters/do/postgres.rb +2 -61
  59. data/lib/sequel/adapters/do/sqlite.rb +0 -10
  60. data/lib/sequel/adapters/do.rb +20 -39
  61. data/lib/sequel/adapters/firebird.rb +33 -210
  62. data/lib/sequel/adapters/ibmdb.rb +452 -0
  63. data/lib/sequel/adapters/informix.rb +6 -29
  64. data/lib/sequel/adapters/jdbc/as400.rb +16 -34
  65. data/lib/sequel/adapters/jdbc/cubrid.rb +52 -0
  66. data/lib/sequel/adapters/jdbc/db2.rb +78 -0
  67. data/lib/sequel/adapters/jdbc/derby.rb +325 -0
  68. data/lib/sequel/adapters/jdbc/firebird.rb +24 -0
  69. data/lib/sequel/adapters/jdbc/h2.rb +68 -31
  70. data/lib/sequel/adapters/jdbc/hsqldb.rb +189 -0
  71. data/lib/sequel/adapters/jdbc/informix.rb +21 -0
  72. data/lib/sequel/adapters/jdbc/jtds.rb +40 -0
  73. data/lib/sequel/adapters/jdbc/mssql.rb +3 -33
  74. data/lib/sequel/adapters/jdbc/mysql.rb +16 -28
  75. data/lib/sequel/adapters/jdbc/oracle.rb +88 -25
  76. data/lib/sequel/adapters/jdbc/postgresql.rb +77 -52
  77. data/lib/sequel/adapters/jdbc/progress.rb +21 -0
  78. data/lib/sequel/adapters/jdbc/sqlite.rb +20 -10
  79. data/lib/sequel/adapters/jdbc/sqlserver.rb +72 -0
  80. data/lib/sequel/adapters/jdbc/transactions.rb +83 -0
  81. data/lib/sequel/adapters/jdbc.rb +308 -114
  82. data/lib/sequel/adapters/mock.rb +372 -0
  83. data/lib/sequel/adapters/mysql.rb +130 -230
  84. data/lib/sequel/adapters/mysql2.rb +64 -63
  85. data/lib/sequel/adapters/odbc/mssql.rb +17 -9
  86. data/lib/sequel/adapters/odbc.rb +29 -10
  87. data/lib/sequel/adapters/openbase.rb +7 -11
  88. data/lib/sequel/adapters/oracle.rb +364 -57
  89. data/lib/sequel/adapters/postgres.rb +379 -145
  90. data/lib/sequel/adapters/shared/access.rb +236 -11
  91. data/lib/sequel/adapters/shared/cubrid.rb +216 -0
  92. data/lib/sequel/adapters/shared/db2.rb +353 -0
  93. data/lib/sequel/adapters/shared/firebird.rb +231 -0
  94. data/lib/sequel/adapters/shared/informix.rb +53 -0
  95. data/lib/sequel/adapters/shared/mssql.rb +412 -116
  96. data/lib/sequel/adapters/shared/mysql.rb +396 -89
  97. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +166 -0
  98. data/lib/sequel/adapters/shared/oracle.rb +262 -45
  99. data/lib/sequel/adapters/shared/postgres.rb +688 -279
  100. data/lib/sequel/adapters/shared/progress.rb +5 -8
  101. data/lib/sequel/adapters/shared/sqlite.rb +317 -96
  102. data/lib/sequel/adapters/sqlite.rb +109 -46
  103. data/lib/sequel/adapters/swift/mysql.rb +10 -12
  104. data/lib/sequel/adapters/swift/postgres.rb +13 -79
  105. data/lib/sequel/adapters/swift/sqlite.rb +9 -6
  106. data/lib/sequel/adapters/swift.rb +31 -27
  107. data/lib/sequel/adapters/tinytds.rb +177 -25
  108. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +77 -0
  109. data/lib/sequel/adapters/utils/pg_types.rb +81 -0
  110. data/lib/sequel/adapters/utils/split_alter_table.rb +36 -0
  111. data/lib/sequel/adapters/utils/stored_procedures.rb +1 -11
  112. data/lib/sequel/ast_transformer.rb +194 -0
  113. data/lib/sequel/connection_pool/sharded_single.rb +23 -13
  114. data/lib/sequel/connection_pool/sharded_threaded.rb +64 -13
  115. data/lib/sequel/connection_pool/single.rb +11 -3
  116. data/lib/sequel/connection_pool/threaded.rb +69 -20
  117. data/lib/sequel/connection_pool.rb +20 -18
  118. data/lib/sequel/core.rb +143 -11
  119. data/lib/sequel/database/connecting.rb +66 -29
  120. data/lib/sequel/database/dataset.rb +4 -4
  121. data/lib/sequel/database/dataset_defaults.rb +63 -2
  122. data/lib/sequel/database/logging.rb +7 -2
  123. data/lib/sequel/database/misc.rb +192 -51
  124. data/lib/sequel/database/query.rb +299 -114
  125. data/lib/sequel/database/schema_generator.rb +103 -36
  126. data/lib/sequel/database/schema_methods.rb +310 -69
  127. data/lib/sequel/dataset/actions.rb +344 -90
  128. data/lib/sequel/dataset/features.rb +101 -8
  129. data/lib/sequel/dataset/graph.rb +42 -15
  130. data/lib/sequel/dataset/misc.rb +52 -25
  131. data/lib/sequel/dataset/mutation.rb +12 -7
  132. data/lib/sequel/dataset/prepared_statements.rb +68 -30
  133. data/lib/sequel/dataset/query.rb +322 -68
  134. data/lib/sequel/dataset/sql.rb +716 -293
  135. data/lib/sequel/dataset.rb +8 -0
  136. data/lib/sequel/exceptions.rb +4 -0
  137. data/lib/sequel/extensions/_pretty_table.rb +83 -0
  138. data/lib/sequel/extensions/arbitrary_servers.rb +109 -0
  139. data/lib/sequel/extensions/blank.rb +4 -0
  140. data/lib/sequel/extensions/columns_introspection.rb +72 -0
  141. data/lib/sequel/extensions/connection_validator.rb +109 -0
  142. data/lib/sequel/extensions/constraint_validations.rb +451 -0
  143. data/lib/sequel/{core_sql.rb → extensions/core_extensions.rb} +30 -46
  144. data/lib/sequel/extensions/eval_inspect.rb +173 -0
  145. data/lib/sequel/extensions/inflector.rb +4 -0
  146. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  147. data/lib/sequel/extensions/migration.rb +100 -11
  148. data/lib/sequel/extensions/named_timezones.rb +9 -0
  149. data/lib/sequel/extensions/null_dataset.rb +94 -0
  150. data/lib/sequel/extensions/pagination.rb +4 -0
  151. data/lib/sequel/extensions/pg_array.rb +539 -0
  152. data/lib/sequel/extensions/pg_array_ops.rb +263 -0
  153. data/lib/sequel/extensions/pg_auto_parameterize.rb +175 -0
  154. data/lib/sequel/extensions/pg_hstore.rb +334 -0
  155. data/lib/sequel/extensions/pg_hstore_ops.rb +293 -0
  156. data/lib/sequel/extensions/pg_inet.rb +113 -0
  157. data/lib/sequel/extensions/pg_interval.rb +191 -0
  158. data/lib/sequel/extensions/pg_json.rb +212 -0
  159. data/lib/sequel/extensions/pg_range.rb +513 -0
  160. data/lib/sequel/extensions/pg_range_ops.rb +150 -0
  161. data/lib/sequel/extensions/pg_row.rb +576 -0
  162. data/lib/sequel/extensions/pg_row_ops.rb +182 -0
  163. data/lib/sequel/extensions/pg_statement_cache.rb +317 -0
  164. data/lib/sequel/extensions/pretty_table.rb +17 -72
  165. data/lib/sequel/extensions/query.rb +8 -4
  166. data/lib/sequel/extensions/query_literals.rb +79 -0
  167. data/lib/sequel/extensions/schema_caching.rb +76 -0
  168. data/lib/sequel/extensions/schema_dumper.rb +282 -76
  169. data/lib/sequel/extensions/select_remove.rb +39 -0
  170. data/lib/sequel/extensions/server_block.rb +140 -0
  171. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  172. data/lib/sequel/extensions/sql_expr.rb +8 -110
  173. data/lib/sequel/extensions/string_date_time.rb +4 -0
  174. data/lib/sequel/extensions/thread_local_timezones.rb +10 -4
  175. data/lib/sequel/extensions/to_dot.rb +99 -83
  176. data/lib/sequel/model/associations.rb +1263 -409
  177. data/lib/sequel/model/base.rb +624 -171
  178. data/lib/sequel/model/errors.rb +1 -1
  179. data/lib/sequel/model/exceptions.rb +24 -2
  180. data/lib/sequel/model/inflections.rb +1 -1
  181. data/lib/sequel/model.rb +30 -11
  182. data/lib/sequel/no_core_ext.rb +2 -0
  183. data/lib/sequel/plugins/active_model.rb +13 -1
  184. data/lib/sequel/plugins/association_pks.rb +137 -16
  185. data/lib/sequel/plugins/caching.rb +25 -18
  186. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  187. data/lib/sequel/plugins/composition.rb +44 -12
  188. data/lib/sequel/plugins/constraint_validations.rb +198 -0
  189. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  190. data/lib/sequel/plugins/defaults_setter.rb +72 -0
  191. data/lib/sequel/plugins/dirty.rb +214 -0
  192. data/lib/sequel/plugins/eager_each.rb +59 -0
  193. data/lib/sequel/plugins/force_encoding.rb +6 -6
  194. data/lib/sequel/plugins/hook_class_methods.rb +1 -1
  195. data/lib/sequel/plugins/identity_map.rb +134 -15
  196. data/lib/sequel/plugins/instance_filters.rb +10 -0
  197. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  198. data/lib/sequel/plugins/json_serializer.rb +77 -6
  199. data/lib/sequel/plugins/list.rb +13 -2
  200. data/lib/sequel/plugins/many_through_many.rb +103 -51
  201. data/lib/sequel/plugins/many_to_one_pk_lookup.rb +71 -0
  202. data/lib/sequel/plugins/nested_attributes.rb +150 -66
  203. data/lib/sequel/plugins/optimistic_locking.rb +8 -0
  204. data/lib/sequel/plugins/pg_row.rb +121 -0
  205. data/lib/sequel/plugins/pg_typecast_on_load.rb +65 -0
  206. data/lib/sequel/plugins/prepared_statements.rb +168 -0
  207. data/lib/sequel/plugins/prepared_statements_associations.rb +87 -0
  208. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  209. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  210. data/lib/sequel/plugins/rcte_tree.rb +31 -17
  211. data/lib/sequel/plugins/schema.rb +8 -3
  212. data/lib/sequel/plugins/serialization.rb +98 -49
  213. data/lib/sequel/plugins/serialization_modification_detection.rb +63 -0
  214. data/lib/sequel/plugins/sharding.rb +21 -54
  215. data/lib/sequel/plugins/single_table_inheritance.rb +57 -12
  216. data/lib/sequel/plugins/static_cache.rb +99 -0
  217. data/lib/sequel/plugins/subclasses.rb +29 -3
  218. data/lib/sequel/plugins/tactical_eager_loading.rb +7 -7
  219. data/lib/sequel/plugins/timestamps.rb +1 -1
  220. data/lib/sequel/plugins/touch.rb +18 -6
  221. data/lib/sequel/plugins/tree.rb +3 -3
  222. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  223. data/lib/sequel/plugins/update_primary_key.rb +2 -2
  224. data/lib/sequel/plugins/validation_class_methods.rb +2 -1
  225. data/lib/sequel/plugins/validation_helpers.rb +57 -4
  226. data/lib/sequel/plugins/xml_serializer.rb +15 -4
  227. data/lib/sequel/sql.rb +721 -141
  228. data/lib/sequel/timezones.rb +67 -40
  229. data/lib/sequel/version.rb +1 -1
  230. data/spec/adapters/db2_spec.rb +146 -0
  231. data/spec/adapters/firebird_spec.rb +53 -39
  232. data/spec/adapters/mssql_spec.rb +261 -66
  233. data/spec/adapters/mysql_spec.rb +597 -352
  234. data/spec/adapters/oracle_spec.rb +92 -117
  235. data/spec/adapters/postgres_spec.rb +2103 -341
  236. data/spec/adapters/spec_helper.rb +16 -6
  237. data/spec/adapters/sqlite_spec.rb +237 -76
  238. data/spec/core/connection_pool_spec.rb +270 -141
  239. data/spec/core/database_spec.rb +980 -524
  240. data/spec/core/dataset_spec.rb +1658 -1243
  241. data/spec/core/expression_filters_spec.rb +741 -334
  242. data/spec/core/mock_adapter_spec.rb +453 -0
  243. data/spec/core/object_graph_spec.rb +88 -129
  244. data/spec/core/schema_generator_spec.rb +4 -4
  245. data/spec/core/schema_spec.rb +556 -53
  246. data/spec/core/spec_helper.rb +7 -48
  247. data/spec/core_extensions_spec.rb +626 -0
  248. data/spec/extensions/active_model_spec.rb +13 -0
  249. data/spec/extensions/arbitrary_servers_spec.rb +110 -0
  250. data/spec/extensions/association_autoreloading_spec.rb +18 -10
  251. data/spec/extensions/association_dependencies_spec.rb +15 -25
  252. data/spec/extensions/association_pks_spec.rb +227 -33
  253. data/spec/extensions/association_proxies_spec.rb +4 -4
  254. data/spec/extensions/boolean_readers_spec.rb +25 -25
  255. data/spec/extensions/caching_spec.rb +47 -51
  256. data/spec/extensions/class_table_inheritance_spec.rb +31 -83
  257. data/spec/extensions/columns_introspection_spec.rb +91 -0
  258. data/spec/extensions/composition_spec.rb +18 -13
  259. data/spec/extensions/connection_validator_spec.rb +118 -0
  260. data/spec/extensions/constraint_validations_plugin_spec.rb +196 -0
  261. data/spec/extensions/constraint_validations_spec.rb +316 -0
  262. data/spec/extensions/dataset_associations_spec.rb +199 -0
  263. data/spec/extensions/defaults_setter_spec.rb +88 -0
  264. data/spec/extensions/dirty_spec.rb +155 -0
  265. data/spec/extensions/eager_each_spec.rb +34 -0
  266. data/spec/extensions/eval_inspect_spec.rb +67 -0
  267. data/spec/extensions/force_encoding_spec.rb +4 -2
  268. data/spec/extensions/hook_class_methods_spec.rb +97 -128
  269. data/spec/extensions/identity_map_spec.rb +142 -24
  270. data/spec/extensions/inflector_spec.rb +0 -4
  271. data/spec/extensions/instance_filters_spec.rb +11 -21
  272. data/spec/extensions/instance_hooks_spec.rb +72 -0
  273. data/spec/extensions/json_serializer_spec.rb +49 -12
  274. data/spec/extensions/lazy_attributes_spec.rb +16 -20
  275. data/spec/extensions/list_spec.rb +49 -40
  276. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  277. data/spec/extensions/many_through_many_spec.rb +368 -254
  278. data/spec/extensions/many_to_one_pk_lookup_spec.rb +140 -0
  279. data/spec/extensions/migration_spec.rb +140 -35
  280. data/spec/extensions/named_timezones_spec.rb +29 -11
  281. data/spec/extensions/nested_attributes_spec.rb +268 -89
  282. data/spec/extensions/null_dataset_spec.rb +85 -0
  283. data/spec/extensions/optimistic_locking_spec.rb +24 -21
  284. data/spec/extensions/pg_array_ops_spec.rb +112 -0
  285. data/spec/extensions/pg_array_spec.rb +324 -0
  286. data/spec/extensions/pg_auto_parameterize_spec.rb +65 -0
  287. data/spec/extensions/pg_hstore_ops_spec.rb +140 -0
  288. data/spec/extensions/pg_hstore_spec.rb +200 -0
  289. data/spec/extensions/pg_inet_spec.rb +47 -0
  290. data/spec/extensions/pg_interval_spec.rb +72 -0
  291. data/spec/extensions/pg_json_spec.rb +99 -0
  292. data/spec/extensions/pg_range_ops_spec.rb +56 -0
  293. data/spec/extensions/pg_range_spec.rb +395 -0
  294. data/spec/extensions/pg_row_ops_spec.rb +58 -0
  295. data/spec/extensions/pg_row_plugin_spec.rb +49 -0
  296. data/spec/extensions/pg_row_spec.rb +340 -0
  297. data/spec/extensions/pg_statement_cache_spec.rb +208 -0
  298. data/spec/extensions/pg_typecast_on_load_spec.rb +58 -0
  299. data/spec/extensions/prepared_statements_associations_spec.rb +111 -0
  300. data/spec/extensions/prepared_statements_safe_spec.rb +61 -0
  301. data/spec/extensions/prepared_statements_spec.rb +87 -0
  302. data/spec/extensions/prepared_statements_with_pk_spec.rb +31 -0
  303. data/spec/extensions/pretty_table_spec.rb +7 -6
  304. data/spec/extensions/query_literals_spec.rb +167 -0
  305. data/spec/extensions/query_spec.rb +9 -3
  306. data/spec/extensions/rcte_tree_spec.rb +50 -43
  307. data/spec/extensions/schema_caching_spec.rb +41 -0
  308. data/spec/extensions/schema_dumper_spec.rb +434 -49
  309. data/spec/extensions/schema_spec.rb +30 -49
  310. data/spec/extensions/select_remove_spec.rb +38 -0
  311. data/spec/extensions/serialization_modification_detection_spec.rb +72 -0
  312. data/spec/extensions/serialization_spec.rb +86 -48
  313. data/spec/extensions/server_block_spec.rb +90 -0
  314. data/spec/extensions/sharding_spec.rb +69 -143
  315. data/spec/extensions/single_table_inheritance_spec.rb +65 -29
  316. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  317. data/spec/extensions/spec_helper.rb +34 -67
  318. data/spec/extensions/split_array_nil_spec.rb +24 -0
  319. data/spec/extensions/sql_expr_spec.rb +29 -60
  320. data/spec/extensions/static_cache_spec.rb +145 -0
  321. data/spec/extensions/subclasses_spec.rb +14 -0
  322. data/spec/extensions/tactical_eager_loading_spec.rb +17 -19
  323. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  324. data/spec/extensions/timestamps_spec.rb +6 -6
  325. data/spec/extensions/to_dot_spec.rb +8 -11
  326. data/spec/extensions/touch_spec.rb +53 -14
  327. data/spec/extensions/tree_spec.rb +11 -26
  328. data/spec/extensions/typecast_on_load_spec.rb +9 -6
  329. data/spec/extensions/update_primary_key_spec.rb +30 -24
  330. data/spec/extensions/validation_class_methods_spec.rb +77 -68
  331. data/spec/extensions/validation_helpers_spec.rb +48 -37
  332. data/spec/extensions/xml_serializer_spec.rb +29 -4
  333. data/spec/files/transaction_specified_migrations/001_create_alt_basic.rb +4 -0
  334. data/spec/files/transaction_specified_migrations/002_create_basic.rb +4 -0
  335. data/spec/files/transaction_unspecified_migrations/001_create_alt_basic.rb +3 -0
  336. data/spec/files/transaction_unspecified_migrations/002_create_basic.rb +3 -0
  337. data/spec/integration/associations_test.rb +659 -21
  338. data/spec/integration/database_test.rb +25 -5
  339. data/spec/integration/dataset_test.rb +729 -141
  340. data/spec/integration/eager_loader_test.rb +38 -54
  341. data/spec/integration/migrator_test.rb +2 -3
  342. data/spec/integration/model_test.rb +81 -2
  343. data/spec/integration/plugin_test.rb +871 -160
  344. data/spec/integration/prepared_statement_test.rb +272 -128
  345. data/spec/integration/schema_test.rb +377 -57
  346. data/spec/integration/spec_helper.rb +49 -32
  347. data/spec/integration/timezone_test.rb +38 -12
  348. data/spec/integration/transaction_test.rb +183 -8
  349. data/spec/integration/type_test.rb +40 -8
  350. data/spec/model/association_reflection_spec.rb +207 -8
  351. data/spec/model/associations_spec.rb +1206 -693
  352. data/spec/model/base_spec.rb +334 -100
  353. data/spec/model/dataset_methods_spec.rb +45 -27
  354. data/spec/model/eager_loading_spec.rb +828 -692
  355. data/spec/model/hooks_spec.rb +355 -67
  356. data/spec/model/model_spec.rb +251 -179
  357. data/spec/model/plugins_spec.rb +24 -13
  358. data/spec/model/record_spec.rb +506 -232
  359. data/spec/model/spec_helper.rb +16 -77
  360. data/spec/model/validations_spec.rb +38 -2
  361. data/spec/rcov.opts +2 -0
  362. metadata +223 -81
  363. data/spec/core/core_sql_spec.rb +0 -451
@@ -6,16 +6,10 @@ module Sequel
6
6
  def database_type
7
7
  :progress
8
8
  end
9
-
10
- def dataset(opts = nil)
11
- ds = super
12
- ds.extend(DatasetMethods)
13
- ds
14
- end
15
9
  end
16
10
 
17
11
  module DatasetMethods
18
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'limit distinct columns from join where group order having compounds')
12
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select limit distinct columns from join where group order having compounds')
19
13
 
20
14
  # Progress requires SQL standard datetimes
21
15
  def requires_sql_standard_datetimes?
@@ -37,7 +31,10 @@ module Sequel
37
31
  # The Progress adapter targets Progress 9, so it silently ignores the option.
38
32
  def select_limit_sql(sql)
39
33
  raise(Error, "OFFSET not supported") if @opts[:offset]
40
- #sql << " TOP #{@opts[:limit]}" if @opts[:limit]
34
+ # if l = @opts[:limit]
35
+ # sql << " TOP "
36
+ # literal_append(sql, l)
37
+ # end
41
38
  end
42
39
  end
43
40
  end
@@ -7,16 +7,19 @@ 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
-
13
- # Run all alter_table commands in a transaction. This is technically only
14
- # needed for drop column.
15
- def alter_table(name, generator=nil, &block)
16
- remove_cached_schema(name)
17
- generator ||= Schema::AlterTableGenerator.new(self, &block)
18
- transaction{generator.operations.each{|op| alter_table_sql_list(name, [op]).flatten.each{|sql| execute_ddl(sql)}}}
19
- end
12
+ VIEWS_FILTER = "type = 'view'".freeze
13
+ TRANSACTION_MODE = {
14
+ :deferred => "BEGIN DEFERRED TRANSACTION".freeze,
15
+ :immediate => "BEGIN IMMEDIATE TRANSACTION".freeze,
16
+ :exclusive => "BEGIN EXCLUSIVE TRANSACTION".freeze,
17
+ nil => Sequel::Database::SQL_BEGIN,
18
+ }.freeze
19
+
20
+ # Whether to use integers for booleans in the database. SQLite recommends
21
+ # booleans be stored as integers, but historically Sequel has used 't'/'f'.
22
+ attr_accessor :integer_booleans
20
23
 
21
24
  # A symbol signifying the value of the auto_vacuum PRAGMA.
22
25
  def auto_vacuum
@@ -30,7 +33,27 @@ module Sequel
30
33
  value = AUTO_VACUUM.index(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
31
34
  pragma_set(:auto_vacuum, value)
32
35
  end
33
-
36
+
37
+ # Set the case_sensitive_like PRAGMA using the given boolean value, if using
38
+ # SQLite 3.2.3+. If not using 3.2.3+, no error is raised. See pragma_set.
39
+ # Consider using the :case_sensitive_like Database option instead.
40
+ def case_sensitive_like=(value)
41
+ pragma_set(:case_sensitive_like, !!value ? 'on' : 'off') if sqlite_version >= 30203
42
+ end
43
+
44
+ # A symbol signifying the value of the default transaction mode
45
+ def transaction_mode
46
+ defined?(@transaction_mode) ? @transaction_mode : (@transaction_mode = nil)
47
+ end
48
+
49
+ def transaction_mode=(value)
50
+ if TRANSACTION_MODE.include?(value)
51
+ @transaction_mode = value
52
+ else
53
+ raise Error, "Invalid value for transaction_mode. Please specify one of :deferred, :immediate, :exclusive, nil"
54
+ end
55
+ end
56
+
34
57
  # SQLite uses the :sqlite database type.
35
58
  def database_type
36
59
  :sqlite
@@ -49,22 +72,33 @@ module Sequel
49
72
  pragma_set(:foreign_keys, !!value ? 'on' : 'off') if sqlite_version >= 30619
50
73
  end
51
74
 
75
+ # Return the array of foreign key info hashes using the foreign_key_list PRAGMA,
76
+ # including information for the :on_update and :on_delete entries.
77
+ def foreign_key_list(table, opts={})
78
+ m = output_identifier_meth
79
+ h = {}
80
+ metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
81
+ if r = h[row[:id]]
82
+ r[:columns] << m.call(row[:from])
83
+ r[:key] << m.call(row[:to]) if r[:key]
84
+ else
85
+ h[row[:id]] = {:columns=>[m.call(row[:from])], :table=>m.call(row[:table]), :key=>([m.call(row[:to])] if row[:to]), :on_update=>on_delete_sql_to_sym(row[:on_update]), :on_delete=>on_delete_sql_to_sym(row[:on_delete])}
86
+ end
87
+ end
88
+ h.values
89
+ end
90
+
52
91
  # Use the index_list and index_info PRAGMAs to determine the indexes on the table.
53
92
  def indexes(table, opts={})
54
93
  m = output_identifier_meth
55
94
  im = input_identifier_meth
56
95
  indexes = {}
57
- begin
58
- metadata_dataset.with_sql("PRAGMA index_list(?)", im.call(table)).each do |r|
59
- next if r[:name] =~ PRIMARY_KEY_INDEX_RE
60
- indexes[m.call(r[:name])] = {:unique=>r[:unique].to_i==1}
61
- end
62
- rescue Sequel::DatabaseError
63
- nil
64
- else
65
- indexes.each do |k, v|
66
- v[:columns] = metadata_dataset.with_sql("PRAGMA index_info(?)", im.call(k)).map(:name).map{|x| m.call(x)}
67
- end
96
+ metadata_dataset.with_sql("PRAGMA index_list(?)", im.call(table)).each do |r|
97
+ next if r[:name] =~ PRIMARY_KEY_INDEX_RE
98
+ indexes[m.call(r[:name])] = {:unique=>r[:unique].to_i==1}
99
+ end
100
+ indexes.each do |k, v|
101
+ v[:columns] = metadata_dataset.with_sql("PRAGMA index_info(?)", im.call(k)).map(:name).map{|x| m.call(x)}
68
102
  end
69
103
  indexes
70
104
  end
@@ -83,6 +117,11 @@ module Sequel
83
117
  def pragma_set(name, value)
84
118
  execute_ddl("PRAGMA #{name} = #{value}")
85
119
  end
120
+
121
+ # Set the integer_booleans option using the passed in :integer_boolean option.
122
+ def set_integer_booleans
123
+ @integer_booleans = typecast_value_boolean(@opts[:integer_booleans])
124
+ end
86
125
 
87
126
  # The version of the server as an integer, where 3.6.19 = 30619.
88
127
  # If the server version can't be determined, 0 is used.
@@ -96,11 +135,29 @@ module Sequel
96
135
  end
97
136
  end
98
137
 
138
+ # SQLite supports CREATE TABLE IF NOT EXISTS syntax since 3.3.0.
139
+ def supports_create_table_if_not_exists?
140
+ sqlite_version >= 30300
141
+ end
142
+
99
143
  # SQLite 3.6.8+ supports savepoints.
100
144
  def supports_savepoints?
101
145
  sqlite_version >= 30608
102
146
  end
103
147
 
148
+ # Override the default setting for whether to use timezones in timestamps.
149
+ # For backwards compatibility, it is set to +true+ by default.
150
+ # Anyone wanting to use SQLite's datetime functions should set it to +false+
151
+ # using this method. It's possible that the default will change in a future version,
152
+ # so anyone relying on timezones in timestamps should set this to +true+.
153
+ attr_writer :use_timestamp_timezones
154
+
155
+ # SQLite supports timezones in timestamps, since it just stores them as strings,
156
+ # but it breaks the usage of SQLite's datetime functions.
157
+ def use_timestamp_timezones?
158
+ defined?(@use_timestamp_timezones) ? @use_timestamp_timezones : (@use_timestamp_timezones = true)
159
+ end
160
+
104
161
  # A symbol signifying the value of the synchronous PRAGMA.
105
162
  def synchronous
106
163
  SYNCHRONOUS[pragma_get(:synchronous).to_i]
@@ -118,8 +175,7 @@ module Sequel
118
175
  # Options:
119
176
  # * :server - Set the server to use.
120
177
  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])}
178
+ tables_and_views(TABLES_FILTER, opts)
123
179
  end
124
180
 
125
181
  # A symbol signifying the value of the temp_store PRAGMA.
@@ -134,8 +190,35 @@ module Sequel
134
190
  pragma_set(:temp_store, value)
135
191
  end
136
192
 
193
+ # Array of symbols specifying the view names in the current database.
194
+ #
195
+ # Options:
196
+ # * :server - Set the server to use.
197
+ def views(opts={})
198
+ tables_and_views(VIEWS_FILTER, opts)
199
+ end
200
+
137
201
  private
138
202
 
203
+ # Run all alter_table commands in a transaction. This is technically only
204
+ # needed for drop column.
205
+ def apply_alter_table(table, ops)
206
+ fks = foreign_keys
207
+ self.foreign_keys = false if fks
208
+ transaction do
209
+ if ops.length > 1 && ops.all?{|op| op[:op] == :add_constraint}
210
+ # If you are just doing constraints, apply all of them at the same time,
211
+ # as otherwise all but the last one get lost.
212
+ alter_table_sql_list(table, [{:op=>:add_constraints, :ops=>ops}]).flatten.each{|sql| execute_ddl(sql)}
213
+ else
214
+ # Run each operation separately, as later operations may depend on the
215
+ # results of earlier operations.
216
+ ops.each{|op| alter_table_sql_list(table, [op]).flatten.each{|sql| execute_ddl(sql)}}
217
+ end
218
+ end
219
+ self.foreign_keys = true if fks
220
+ end
221
+
139
222
  # SQLite supports limited table modification. You can add a column
140
223
  # or an index. Dropping columns is supported by copying the table into
141
224
  # a temporary table, dropping the table, and creating a new table without
@@ -161,13 +244,33 @@ module Sequel
161
244
  when :set_column_null
162
245
  duplicate_table(table){|columns| columns.each{|s| s[:null] = op[:null] if s[:name].to_s == op[:name].to_s}}
163
246
  when :set_column_type
164
- duplicate_table(table){|columns| columns.each{|s| s[:type] = op[:type] if s[:name].to_s == op[:name].to_s}}
247
+ duplicate_table(table){|columns| columns.each{|s| s.merge!(op) if s[:name].to_s == op[:name].to_s}}
248
+ when :drop_constraint
249
+ case op[:type]
250
+ when :primary_key
251
+ duplicate_table(table){|columns| columns.each{|s| s[:primary_key] = nil}}
252
+ when :foreign_key
253
+ duplicate_table(table, :no_foreign_keys=>true)
254
+ else
255
+ duplicate_table(table)
256
+ end
257
+ when :add_constraint
258
+ duplicate_table(table, :constraints=>[op])
259
+ when :add_constraints
260
+ duplicate_table(table, :constraints=>op[:ops])
165
261
  else
166
- raise Error, "Unsupported ALTER TABLE operation"
262
+ raise Error, "Unsupported ALTER TABLE operation: #{op[:op].inspect}"
167
263
  end
168
264
  end
169
-
170
- # The array of column symbols in the table, except for ones given in opts[:except]
265
+
266
+ def begin_new_transaction(conn, opts)
267
+ mode = opts[:mode] || @transaction_mode
268
+ sql = TRANSACTION_MODE[mode] or raise Error, "transaction :mode must be one of: :deferred, :immediate, :exclusive, nil"
269
+ log_connection_execute(conn, sql)
270
+ set_transaction_isolation(conn, opts)
271
+ end
272
+
273
+ # A name to use for the backup table
171
274
  def backup_table_name(table, opts={})
172
275
  table = table.gsub('`', '')
173
276
  (opts[:times]||1000).times do |i|
@@ -176,17 +279,25 @@ module Sequel
176
279
  end
177
280
  end
178
281
 
179
- # Allow use without a generator, needed for the alter table hackery that Sequel allows.
180
- def column_list_sql(generator)
181
- generator.is_a?(Schema::Generator) ? super : generator.map{|c| column_definition_sql(c)}.join(', ')
282
+ # Surround default with parens to appease SQLite
283
+ def column_definition_default_sql(sql, column)
284
+ sql << " DEFAULT (#{literal(column[:default])})" if column.include?(:default)
182
285
  end
183
-
286
+
287
+ # Add null/not null SQL fragment to column creation SQL.
288
+ def column_definition_null_sql(sql, column)
289
+ column = column.merge(:null=>false) if column[:primary_key]
290
+ super(sql, column)
291
+ end
292
+
184
293
  # Array of PRAGMA SQL statements based on the Database options that should be applied to
185
294
  # new connections.
186
295
  def connection_pragmas
187
296
  ps = []
188
297
  v = typecast_value_boolean(opts.fetch(:foreign_keys, 1))
189
298
  ps << "PRAGMA foreign_keys = #{v ? 1 : 0}"
299
+ v = typecast_value_boolean(opts.fetch(:case_sensitive_like, 1))
300
+ ps << "PRAGMA case_sensitive_like = #{v ? 1 : 0}"
190
301
  [[:auto_vacuum, AUTO_VACUUM], [:synchronous, SYNCHRONOUS], [:temp_store, TEMP_STORE]].each do |prag, con|
191
302
  if v = opts[prag]
192
303
  raise(Error, "Value for PRAGMA #{prag} not supported, should be one of #{con.join(', ')}") unless v = con.index(v.to_sym)
@@ -196,28 +307,13 @@ module Sequel
196
307
  ps
197
308
  end
198
309
 
199
- # The array of column schema hashes, except for the ones given in opts[:except]
200
- def defined_columns_for(table, opts={})
310
+ # The array of column schema hashes for the current columns in the table
311
+ def defined_columns_for(table)
201
312
  cols = parse_pragma(table, {})
202
313
  cols.each do |c|
203
314
  c[:default] = LiteralString.new(c[:default]) if c[:default]
204
315
  c[:type] = c[:db_type]
205
316
  end
206
- if opts[:except]
207
- nono= Array(opts[:except]).compact.map{|n| n.to_s}
208
- cols.reject!{|c| nono.include? c[:name] }
209
- end
210
-
211
- if foreign_keys
212
- metadata_dataset.with_sql("PRAGMA foreign_key_list(?)", input_identifier_meth.call(table)).each do |row|
213
- c = cols.find {|co| co[:name] == row[:from] } or next
214
- c[:table] = row[:table]
215
- c[:key] = row[:to]
216
- c[:on_update] = on_delete_sql_to_sym(row[:on_update])
217
- c[:on_delete] = on_delete_sql_to_sym(row[:on_delete])
218
- # is there any way to get deferrable status?
219
- end
220
- end
221
317
  cols
222
318
  end
223
319
 
@@ -231,17 +327,40 @@ module Sequel
231
327
  opts[:old_columns_proc].call(old_columns) if opts[:old_columns_proc]
232
328
 
233
329
  yield def_columns if block_given?
234
- def_columns_str = column_list_sql(def_columns)
330
+
331
+ constraints = (opts[:constraints] || []).dup
332
+ pks = []
333
+ def_columns.each{|c| pks << c[:name] if c[:primary_key]}
334
+ if pks.length > 1
335
+ constraints << {:type=>:primary_key, :columns=>pks}
336
+ def_columns.each{|c| c[:primary_key] = false if c[:primary_key]}
337
+ end
338
+
339
+ # If dropping a foreign key constraint, drop all foreign key constraints,
340
+ # as there is no way to determine which one to drop.
341
+ unless opts[:no_foreign_keys]
342
+ fks = foreign_key_list(table)
343
+
344
+ # If dropping a column, if there is a foreign key with that
345
+ # column, don't include it when building a copy of the table.
346
+ if ocp = opts[:old_columns_proc]
347
+ fks.delete_if{|c| ocp.call(c[:columns].dup) != c[:columns]}
348
+ end
349
+
350
+ constraints.concat(fks.each{|h| h[:type] = :foreign_key})
351
+ end
352
+
353
+ def_columns_str = (def_columns.map{|c| column_definition_sql(c)} + constraints.map{|c| constraint_definition_sql(c)}).join(', ')
235
354
  new_columns = old_columns.dup
236
355
  opts[:new_columns_proc].call(new_columns) if opts[:new_columns_proc]
237
356
 
238
357
  qt = quote_schema_table(table)
239
358
  bt = quote_identifier(backup_table_name(qt))
240
359
  a = [
241
- "CREATE TABLE #{bt}(#{def_columns_str})",
242
- "INSERT INTO #{bt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{qt}",
243
- "DROP TABLE #{qt}",
244
- "ALTER TABLE #{bt} RENAME TO #{qt}"
360
+ "ALTER TABLE #{qt} RENAME TO #{bt}",
361
+ "CREATE TABLE #{qt}(#{def_columns_str})",
362
+ "INSERT INTO #{qt}(#{dataset.send(:identifier_list, new_columns)}) SELECT #{dataset.send(:identifier_list, old_columns)} FROM #{bt}",
363
+ "DROP TABLE #{bt}"
245
364
  ]
246
365
  indexes(table).each do |name, h|
247
366
  if (h[:columns].map{|x| x.to_s} - new_columns).empty?
@@ -280,7 +399,7 @@ module Sequel
280
399
 
281
400
  # Parse the output of the table_info pragma
282
401
  def parse_pragma(table_name, opts)
283
- metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth.call(table_name)).map do |row|
402
+ metadata_dataset.with_sql("PRAGMA table_info(?)", input_identifier_meth(opts[:dataset]).call(table_name)).map do |row|
284
403
  row.delete(:cid)
285
404
  row[:allow_null] = row.delete(:notnull).to_i == 0
286
405
  row[:default] = row.delete(:dflt_value)
@@ -294,65 +413,123 @@ module Sequel
294
413
 
295
414
  # SQLite treats integer primary keys as autoincrementing (alias of rowid).
296
415
  def schema_autoincrementing_primary_key?(schema)
297
- super and schema[:db_type].downcase == 'integer'
416
+ super && schema[:db_type].downcase == 'integer'
298
417
  end
299
418
 
300
419
  # SQLite supports schema parsing using the table_info PRAGMA, so
301
420
  # parse the output of that into the format Sequel expects.
302
421
  def schema_parse_table(table_name, opts)
303
- m = output_identifier_meth
422
+ m = output_identifier_meth(opts[:dataset])
304
423
  parse_pragma(table_name, opts).map do |row|
305
424
  [m.call(row.delete(:name)), row]
306
425
  end
307
426
  end
308
427
 
309
- # SQLite uses the integer data type even for bignums. This is because they
310
- # are both stored internally as text, and converted when returned from
311
- # the database. Using an integer type instead of bigint makes it more likely
312
- # that software will automatically return the column as an integer.
428
+ # Backbone of the tables and views support.
429
+ def tables_and_views(filter, opts)
430
+ m = output_identifier_meth
431
+ metadata_dataset.from(:sqlite_master).server(opts[:server]).filter(filter).map{|r| m.call(r[:name])}
432
+ end
433
+
434
+ # SQLite only supports AUTOINCREMENT on integer columns, not
435
+ # bigint columns, so use integer instead of bigint for those
436
+ # columns.
313
437
  def type_literal_generic_bignum(column)
314
- :integer
438
+ column[:auto_increment] ? :integer : super
315
439
  end
316
440
  end
317
441
 
318
442
  # Instance methods for datasets that connect to an SQLite database
319
443
  module DatasetMethods
320
- SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'distinct columns from join where group having compounds order limit')
321
- COMMA_SEPARATOR = ', '.freeze
444
+ SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'select distinct columns from join where group having compounds order limit')
322
445
  CONSTANT_MAP = {:CURRENT_DATE=>"date(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIMESTAMP=>"datetime(CURRENT_TIMESTAMP, 'localtime')".freeze, :CURRENT_TIME=>"time(CURRENT_TIMESTAMP, 'localtime')".freeze}
323
-
324
- # SQLite does not support pattern matching via regular expressions.
325
- # SQLite is case insensitive (depending on pragma), so use LIKE for
326
- # ILIKE.
327
- def complex_expression_sql(op, args)
446
+ EMULATED_FUNCTION_MAP = {:char_length=>'length'.freeze}
447
+ EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}
448
+ NOT_SPACE = Dataset::NOT_SPACE
449
+ COMMA = Dataset::COMMA
450
+ PAREN_CLOSE = Dataset::PAREN_CLOSE
451
+ AS = Dataset::AS
452
+ APOS = Dataset::APOS
453
+ EXTRACT_OPEN = "CAST(strftime(".freeze
454
+ EXTRACT_CLOSE = ') AS '.freeze
455
+ NUMERIC = 'NUMERIC'.freeze
456
+ INTEGER = 'INTEGER'.freeze
457
+ BACKTICK = '`'.freeze
458
+ BACKTICK_RE = /`/.freeze
459
+ DOUBLE_BACKTICK = '``'.freeze
460
+ BLOB_START = "X'".freeze
461
+ HSTAR = "H*".freeze
462
+ DATE_OPEN = "date(".freeze
463
+ DATETIME_OPEN = "datetime(".freeze
464
+
465
+ def cast_sql_append(sql, expr, type)
466
+ if type == Time or type == DateTime
467
+ sql << DATETIME_OPEN
468
+ literal_append(sql, expr)
469
+ sql << PAREN_CLOSE
470
+ elsif type == Date
471
+ sql << DATE_OPEN
472
+ literal_append(sql, expr)
473
+ sql << PAREN_CLOSE
474
+ else
475
+ super
476
+ end
477
+ end
478
+
479
+ # SQLite is case insensitive (depending on pragma), so use LIKE for ILIKE.
480
+ # It also doesn't support a NOT LIKE b, you need to use NOT (a LIKE b).
481
+ # It doesn't support xor or the extract function natively, so those have to be emulated.
482
+ def complex_expression_sql_append(sql, op, args)
328
483
  case op
329
- when :~, :'!~', :'~*', :'!~*'
330
- raise Error, "SQLite does not support pattern matching via regular expressions"
331
- when :LIKE, :'NOT LIKE', :ILIKE, :'NOT ILIKE'
332
- # SQLite is case insensitive for ASCII, and non case sensitive for other character sets
333
- "#{'NOT ' if [:'NOT LIKE', :'NOT ILIKE'].include?(op)}(#{literal(args.at(0))} LIKE #{literal(args.at(1))})"
484
+ when :ILIKE
485
+ super(sql, :LIKE, args.map{|a| SQL::Function.new(:upper, a)})
486
+ when :"NOT LIKE", :"NOT ILIKE"
487
+ sql << NOT_SPACE
488
+ complex_expression_sql_append(sql, (op == :"NOT ILIKE" ? :ILIKE : :LIKE), args)
489
+ when :^
490
+ sql << complex_expression_arg_pairs(args) do |a, b|
491
+ a = literal(a)
492
+ b = literal(b)
493
+ "((~(#{a} & #{b})) & (#{a} | #{b}))"
494
+ end
495
+ when :extract
496
+ part = args.at(0)
497
+ raise(Sequel::Error, "unsupported extract argument: #{part.inspect}") unless format = EXTRACT_MAP[part]
498
+ sql << EXTRACT_OPEN << format << COMMA
499
+ literal_append(sql, args.at(1))
500
+ sql << EXTRACT_CLOSE << (part == :second ? NUMERIC : INTEGER) << PAREN_CLOSE
334
501
  else
335
- super(op, args)
502
+ super
336
503
  end
337
504
  end
338
505
 
339
- # MSSQL doesn't support the SQL standard CURRENT_DATE or CURRENT_TIME
340
- def constant_sql(constant)
341
- CONSTANT_MAP[constant] || super
506
+ # SQLite has CURRENT_TIMESTAMP and related constants in UTC instead
507
+ # of in localtime, so convert those constants to local time.
508
+ def constant_sql_append(sql, constant)
509
+ if c = CONSTANT_MAP[constant]
510
+ sql << c
511
+ else
512
+ super
513
+ end
342
514
  end
343
515
 
344
516
  # SQLite performs a TRUNCATE style DELETE if no filter is specified.
345
517
  # Since we want to always return the count of records, add a condition
346
518
  # that is always true and then delete.
347
519
  def delete
348
- @opts[:where] ? super : filter(1=>1).delete
520
+ @opts[:where] ? super : where(1=>1).delete
349
521
  end
350
522
 
351
523
  # Return an array of strings specifying a query explanation for a SELECT of the
352
- # current dataset.
353
- def explain
354
- db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}").
355
- map{|x| "#{x[:addr]}|#{x[:opcode]}|#{(1..5).map{|i| x[:"p#{i}"]}.join('|')}|#{x[:comment]}"}
524
+ # current dataset. Currently, the options are ignore, but it accepts options
525
+ # to be compatible with other adapters.
526
+ def explain(opts=nil)
527
+ # Load the PrettyTable class, needed for explain output
528
+ Sequel.extension(:_pretty_table) unless defined?(Sequel::PrettyTable)
529
+
530
+ ds = db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}")
531
+ rows = ds.all
532
+ Sequel::PrettyTable.string(rows, ds.columns)
356
533
  end
357
534
 
358
535
  # HAVING requires GROUP BY on SQLite
@@ -362,8 +539,20 @@ module Sequel
362
539
  end
363
540
 
364
541
  # SQLite uses the nonstandard ` (backtick) for quoting identifiers.
365
- def quoted_identifier(c)
366
- "`#{c}`"
542
+ def quoted_identifier_append(sql, c)
543
+ sql << BACKTICK << c.to_s.gsub(BACKTICK_RE, DOUBLE_BACKTICK) << BACKTICK
544
+ end
545
+
546
+ # When a qualified column is selected on SQLite and the qualifier
547
+ # is a subselect, the column name used is the full qualified name
548
+ # (including the qualifier) instead of just the column name. To
549
+ # get correct column names, you must use an alias.
550
+ def select(*cols)
551
+ if ((f = @opts[:from]) && f.any?{|t| t.is_a?(Dataset) || (t.is_a?(SQL::AliasedExpression) && t.expression.is_a?(Dataset))}) || ((j = @opts[:join]) && j.any?{|t| t.table.is_a?(Dataset)})
552
+ super(*cols.map{|c| alias_qualified_column(c)})
553
+ else
554
+ super
555
+ end
367
556
  end
368
557
 
369
558
  # SQLite does not support INTERSECT ALL or EXCEPT ALL
@@ -382,31 +571,63 @@ module Sequel
382
571
  end
383
572
 
384
573
  # SQLite supports timezones in literal timestamps, since it stores them
385
- # as text.
574
+ # as text. But using timezones in timestamps breaks SQLite datetime
575
+ # functions, so we allow the user to override the default per database.
386
576
  def supports_timestamp_timezones?
387
- true
577
+ db.use_timestamp_timezones?
388
578
  end
389
579
 
580
+ # SQLite cannot use WHERE 't'.
581
+ def supports_where_true?
582
+ false
583
+ end
584
+
390
585
  private
391
586
 
392
587
  # SQLite uses string literals instead of identifiers in AS clauses.
393
- def as_sql(expression, aliaz)
588
+ def as_sql_append(sql, aliaz)
394
589
  aliaz = aliaz.value if aliaz.is_a?(SQL::Identifier)
395
- "#{expression} AS #{literal(aliaz.to_s)}"
590
+ sql << AS
591
+ literal_append(sql, aliaz.to_s)
396
592
  end
397
-
593
+
594
+ # If col is a qualified column, alias it to the same as the column name
595
+ def alias_qualified_column(col)
596
+ case col
597
+ when Symbol
598
+ t, c, a = split_symbol(col)
599
+ if t && !a
600
+ alias_qualified_column(SQL::QualifiedIdentifier.new(t, c))
601
+ else
602
+ col
603
+ end
604
+ when SQL::QualifiedIdentifier
605
+ SQL::AliasedExpression.new(col, col.column)
606
+ else
607
+ col
608
+ end
609
+ end
610
+
398
611
  # SQL fragment specifying a list of identifiers
399
612
  def identifier_list(columns)
400
- columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
613
+ columns.map{|i| quote_identifier(i)}.join(COMMA)
401
614
  end
402
615
 
403
616
  # SQLite uses a preceding X for hex escaping strings
404
- def literal_blob(v)
405
- blob = ''
406
- v.each_byte{|x| blob << sprintf('%02x', x)}
407
- "X'#{blob}'"
617
+ def literal_blob_append(sql, v)
618
+ sql << BLOB_START << v.unpack(HSTAR).first << APOS
408
619
  end
409
-
620
+
621
+ # Respect the database integer_booleans setting, using 0 or 'f'.
622
+ def literal_false
623
+ @db.integer_booleans ? '0' : "'f'"
624
+ end
625
+
626
+ # Respect the database integer_booleans setting, using 1 or 't'.
627
+ def literal_true
628
+ @db.integer_booleans ? '1' : "'t'"
629
+ end
630
+
410
631
  # SQLite does not support the SQL WITH clause
411
632
  def select_clause_methods
412
633
  SELECT_CLAUSE_METHODS
@@ -416,7 +637,7 @@ module Sequel
416
637
  def select_lock_sql(sql)
417
638
  super unless @opts[:lock] == :update
418
639
  end
419
-
640
+
420
641
  # SQLite treats a DELETE with no WHERE clause as a TRUNCATE
421
642
  def _truncate_sql(table)
422
643
  "DELETE FROM #{table}"