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
@@ -0,0 +1,316 @@
1
+ # This extension adds a statement cache to Sequel's postgres adapter,
2
+ # with the ability to automatically prepare statements that are
3
+ # executed repeatedly. When combined with the pg_auto_parameterize
4
+ # extension, it can take Sequel code such as:
5
+ #
6
+ # DB.extend Sequel::Postgres::AutoParameterize::DatabaseMethods
7
+ # DB.extend Sequel::Postgres::StatementCache::DatabaseMethods
8
+ # DB[:table].filter(:a=>1)
9
+ # DB[:table].filter(:a=>2)
10
+ # DB[:table].filter(:a=>3)
11
+ #
12
+ # And use the same prepared statement to execute the queries.
13
+ #
14
+ # The backbone of this extension is a modified LRU cache. It considers
15
+ # both the last executed time and the number of executions when
16
+ # determining which queries to keep in the cache. It only cleans the
17
+ # cache when a high water mark has been passed, and removes queries
18
+ # until it reaches the low water mark, in order to avoid thrashing when
19
+ # you are using more than the maximum number of queries. To avoid
20
+ # preparing queries when it isn't necessary, it does not prepare them
21
+ # on the server side unless they are being executed more than once.
22
+ # The cache is very tunable, allowing you to set the high and low
23
+ # water marks, the number of executions before preparing the query,
24
+ # and even use a custom callback for determine which queries to keep
25
+ # in the cache.
26
+ #
27
+ # Note that automatically preparing statements does have some issues.
28
+ # Most notably, if you change the result type that the query returns,
29
+ # PostgreSQL will raise an error. This can happen if you have
30
+ # prepared a statement that selects all columns from a table, and then
31
+ # you add or remove a column from that table. This extension does
32
+ # attempt to check that case and clear the statement caches if you use
33
+ # alter_table from within Sequel, but it cannot fix the case when such
34
+ # a change is made externally.
35
+ #
36
+ # This extension only works when the pg driver is used as the backend
37
+ # for the postgres adapter.
38
+
39
+ module Sequel
40
+ module Postgres
41
+ module StatementCache
42
+ # A simple structure used for the values in the StatementCache's hash.
43
+ # It does not hold the related SQL, since that is used as the key for
44
+ # the StatementCache's hash.
45
+ class Statement
46
+ # The last time this statement was seen by the cache, persumably the
47
+ # last time it was executed.
48
+ attr_accessor :last_seen
49
+
50
+ # The total number of executions since the statement entered the cache.
51
+ attr_accessor :num_executes
52
+
53
+ # The id related to the statement, used as part of the prepared statement
54
+ # name.
55
+ attr_reader :cache_id
56
+
57
+ # Used when adding entries to the cache, just sets their id. Uses
58
+ # 0 for num_executes since that is incremented elsewhere. Does not
59
+ # set last_seen since that is set elsewhere to reduce branching.
60
+ def initialize(cache_id)
61
+ @num_executes = 0
62
+ @cache_id = cache_id
63
+ end
64
+
65
+ # The name to use for the server side prepared statement. Note that this
66
+ # statement might not actually be prepared.
67
+ def name
68
+ "sequel_pgap_#{cache_id}"
69
+ end
70
+ end
71
+
72
+ # The backbone of the block, a modified LRU (least recently used) cache
73
+ # mapping SQL query strings to Statement objects.
74
+ class StatementCache
75
+ include Enumerable
76
+
77
+ # Set the options for the statement cache. These are generally set at
78
+ # the database level using the :statement_cache_opts Database option.
79
+ #
80
+ # :max_size :: The maximum size (high water mark) for the cache. If
81
+ # an entry is added when the current size of the cache is
82
+ # equal to the maximum size, the cache is cleaned up to
83
+ # reduce the number of entries to the :min_size. Defaults
84
+ # to 1000.
85
+ # :min_size :: The minimum size (low water mark) for the cache. On
86
+ # cleanup, the size of the cache is reduced to this
87
+ # number. Note that there could be fewer than this
88
+ # number of entries in the cache. Defaults to :max_size/2.
89
+ # :prepare_after :: The number of executions to wait for before preparing
90
+ # the query server-side. If set to 1, prepares all executed
91
+ # queries server-side. If set to 5, does not attempt to
92
+ # prepare the query until the 5th execution. Defaults to 2.
93
+ # :sorter :: A callable object that takes two arguments, the current time
94
+ # and the related Statement instance, and should return some
95
+ # Comparable (usually a numeric) such that the lowest values
96
+ # returned are the first to be removed when it comes time to
97
+ # clean the pool. The default is basically:
98
+ #
99
+ # lambda{|t, stmt| (stmt.last_seen - t)/stmt.num_executes}
100
+ #
101
+ # so that it doesn't remove statements that have been executed
102
+ # many times just because many less-frequently executed statements
103
+ # have been executed recently.
104
+ #
105
+ # The block passed is called with the Statement object's name, only for
106
+ # statements that have been prepared, and should be used to deallocate the
107
+ # statements.
108
+ def initialize(opts={}, &block)
109
+ @cleanup_proc = block
110
+ @prepare_after = opts.fetch(:prepare_after, 2)
111
+ @max_size = opts.fetch(:max_size, 1000)
112
+ @min_size = opts.fetch(:min_size, @max_size/2)
113
+ @sorter = opts.fetch(:sorter){method(:default_sorter)}
114
+ @ids = (1..@max_size).to_a.reverse
115
+ @hash = {}
116
+ #
117
+ # We add one so that when we clean the cache, the entry
118
+ # about to be added brings us to the min_size.
119
+ @size_diff = @max_size - @min_size + 1
120
+ end
121
+
122
+ # Completely clear the statement cache, deallocating on
123
+ # the server side all statements that have been prepared.
124
+ def clear
125
+ @hash.keys.each{|k| remove(k)}
126
+ end
127
+
128
+ # Yield each SQL string and Statement instance in the cache
129
+ # to the block.
130
+ def each(&block)
131
+ @hash.each(&block)
132
+ end
133
+
134
+ # Get the related statement name from the cache. If the
135
+ # entry is already in the cache, just bump it's last seen
136
+ # time and the number of executions. Otherwise, add it
137
+ # to the cache. If the cache is already full, clean it up
138
+ # before adding it.
139
+ #
140
+ # If the num of executions has passed the threshhold, yield
141
+ # the statement name to the block, which should be used to
142
+ # prepare the statement on the server side.
143
+ #
144
+ # This method should return the prepared statment name if
145
+ # the statement has been prepared, and nil if the query
146
+ # has not been prepared and the statement should be executed
147
+ # normally.
148
+ def fetch(sql)
149
+ unless stmt = @hash[sql]
150
+ # Get the next id from the id pool.
151
+ unless id = @ids.pop
152
+ # No id left, cache must be full, so cleanup and then
153
+ # get the next id from the id pool.
154
+ cleanup
155
+ id = @ids.pop
156
+ end
157
+ @hash[sql] = stmt = Statement.new(id)
158
+ end
159
+
160
+ stmt.last_seen = Time.now
161
+ stmt.num_executes += 1
162
+
163
+ if stmt.num_executes >= @prepare_after
164
+ if stmt.num_executes == @prepare_after
165
+ begin
166
+ yield(stmt.name)
167
+ rescue PGError
168
+ # An error occurred while preparing the statement,
169
+ # execute it normally (which will probably raise
170
+ # the error again elsewhere), but decrement the
171
+ # number of executions so we don't think we've
172
+ # prepared the statement when we haven't.
173
+ stmt.num_executes -= 1
174
+ return nil
175
+ end
176
+ end
177
+ stmt.name
178
+ end
179
+ end
180
+
181
+ # The current size of the statement cache.
182
+ def size
183
+ @hash.length
184
+ end
185
+
186
+ private
187
+
188
+ # Sort by time since last execution and number of executions.
189
+ # We don't want to throw stuff out of the
190
+ # cache if it has been executed a lot,
191
+ # but a bunch of queries that were
192
+ # executed only once came in more recently.
193
+ def default_sorter(t, stmt)
194
+ (stmt.last_seen - t)/stmt.num_executes
195
+ end
196
+
197
+ # After sorting the cache appropriately (so that the least important
198
+ # items are first), reduce the number of entries in the cache to
199
+ # the low water mark by removing the related query. Should only be
200
+ # called when the cache is full.
201
+ def cleanup
202
+ t = Time.now
203
+ @hash.sort_by{|k,v| @sorter.call(t, v)}.first(@size_diff).each{|sql, stmt| remove(sql)}
204
+ end
205
+
206
+ # Remove the query from the cache. If it has been prepared,
207
+ # call the cleanup_proc to deallocate the statement.
208
+ def remove(sql)
209
+ stmt = @hash.delete(sql)
210
+ if stmt.num_executes >= @prepare_after
211
+ @cleanup_proc.call(stmt.name)
212
+ end
213
+
214
+ # Return id to the pool of ids
215
+ @ids.push(stmt.cache_id)
216
+ end
217
+ end
218
+
219
+ module AdapterMethods
220
+ # A regular expression for the types of queries to cache. Any queries not
221
+ # matching this regular expression are not cached.
222
+ DML_RE = /\A(WITH|SELECT|INSERT|UPDATE|DELETE) /
223
+
224
+ # The StatementCache instance for this connection. Note that
225
+ # each connection has a separate StatementCache, because prepared
226
+ # statements are connection-specific.
227
+ attr_reader :statement_cache
228
+
229
+ # Set the statement_cache for the connection, using the database's
230
+ # :statement_cache_opts option.
231
+ def self.extended(c)
232
+ c.instance_variable_set(:@statement_cache, StatementCache.new(c.sequel_db.opts[:statement_cache_opts] || {}){|name| c.deallocate(name)})
233
+ end
234
+
235
+ # pg seems to already use the db method (but not the @db instance variable),
236
+ # so use the sequel_db method to access the related Sequel::Database object.
237
+ def sequel_db
238
+ @db
239
+ end
240
+
241
+ # Deallocate on the server the prepared statement with the given name.
242
+ def deallocate(name)
243
+ begin
244
+ execute("DEALLOCATE #{name}")
245
+ rescue PGError
246
+ # table probably got removed, just ignore it
247
+ end
248
+ end
249
+
250
+ private
251
+
252
+ # If the sql query string is one we should cache, cache it. If the query already
253
+ # has a related prepared statement with it, execute the prepared statement instead
254
+ # of executing the query normally.
255
+ def execute_query(sql, args=nil)
256
+ if sql =~ DML_RE
257
+ if name = statement_cache.fetch(sql){|stmt_name| sequel_db.log_yield("PREPARE #{stmt_name} AS #{sql}"){prepare(stmt_name, sql)}}
258
+ if args
259
+ sequel_db.log_yield("EXECUTE #{name} (#{sql})", args){exec_prepared(name, args)}
260
+ else
261
+ sequel_db.log_yield("EXECUTE #{name} (#{sql})"){exec_prepared(name)}
262
+ end
263
+ else
264
+ super
265
+ end
266
+ else
267
+ super
268
+ end
269
+ end
270
+ end
271
+
272
+ module DatabaseMethods
273
+ # Setup the after_connect proc for the connection pool to make
274
+ # sure the connection object is extended with the appropriate
275
+ # module. This disconnects any existing connections to ensure
276
+ # that all connections in the pool have been extended appropriately.
277
+ def self.extended(db)
278
+ # Respect existing after_connect proc if one is present
279
+ pr = db.opts[:after_connect]
280
+
281
+ # Set the after_connect proc to extend the adapter with
282
+ # the statement cache support.
283
+ db.pool.after_connect = db.opts[:after_connect] = proc do |c|
284
+ pr.call(c) if pr
285
+ c.extend(AdapterMethods)
286
+ end
287
+
288
+ # Disconnect to make sure all connections get set up with
289
+ # statement cache.
290
+ db.disconnect
291
+ end
292
+
293
+ # Clear statement caches for all connections when altering tables.
294
+ def alter_table(*)
295
+ clear_statement_caches
296
+ super
297
+ end
298
+
299
+ # Clear statement caches for all connections when dropping tables.
300
+ def drop_table(*)
301
+ clear_statement_caches
302
+ super
303
+ end
304
+
305
+ private
306
+
307
+ # Clear the statement cache for all connections. Note that for
308
+ # the threaded pools, this will not affect connections currently
309
+ # allocated to other threads.
310
+ def clear_statement_caches
311
+ pool.all_connections{|c| c.statement_cache.clear}
312
+ end
313
+ end
314
+ end
315
+ end
316
+ end
@@ -3,80 +3,14 @@
3
3
  # tables.
4
4
 
5
5
  module Sequel
6
+ extension :_pretty_table
7
+
6
8
  class Dataset
7
9
  # Pretty prints the records in the dataset as plain-text table.
8
10
  def print(*cols)
9
- Sequel::PrettyTable.print(naked.all, cols.empty? ? columns : cols)
11
+ ds = naked
12
+ rows = ds.all
13
+ Sequel::PrettyTable.print(rows, cols.empty? ? ds.columns : cols)
10
14
  end
11
15
  end
12
-
13
- module PrettyTable
14
- # Prints nice-looking plain-text tables via puts
15
- #
16
- # +--+-------+
17
- # |id|name |
18
- # |--+-------|
19
- # |1 |fasdfas|
20
- # |2 |test |
21
- # +--+-------+
22
- def self.print(records, columns = nil) # records is an array of hashes
23
- columns ||= records.first.keys.sort_by{|x|x.to_s}
24
- sizes = column_sizes(records, columns)
25
- sep_line = separator_line(columns, sizes)
26
-
27
- puts sep_line
28
- puts header_line(columns, sizes)
29
- puts sep_line
30
- records.each {|r| puts data_line(columns, sizes, r)}
31
- puts sep_line
32
- end
33
-
34
- ### Private Module Methods ###
35
-
36
- # Hash of the maximum size of the value for each column
37
- def self.column_sizes(records, columns) # :nodoc:
38
- sizes = Hash.new {0}
39
- columns.each do |c|
40
- s = c.to_s.size
41
- sizes[c.to_sym] = s if s > sizes[c.to_sym]
42
- end
43
- records.each do |r|
44
- columns.each do |c|
45
- s = r[c].to_s.size
46
- sizes[c.to_sym] = s if s > sizes[c.to_sym]
47
- end
48
- end
49
- sizes
50
- end
51
-
52
- # String for each data line
53
- def self.data_line(columns, sizes, record) # :nodoc:
54
- '|' << columns.map {|c| format_cell(sizes[c], record[c])}.join('|') << '|'
55
- end
56
-
57
- # Format the value so it takes up exactly size characters
58
- def self.format_cell(size, v) # :nodoc:
59
- case v
60
- when Bignum, Fixnum
61
- "%#{size}d" % v
62
- when Float
63
- "%#{size}g" % v
64
- else
65
- "%-#{size}s" % v.to_s
66
- end
67
- end
68
-
69
- # String for header line
70
- def self.header_line(columns, sizes) # :nodoc:
71
- '|' << columns.map {|c| "%-#{sizes[c]}s" % c.to_s}.join('|') << '|'
72
- end
73
-
74
- # String for separtor line
75
- def self.separator_line(columns, sizes) # :nodoc:
76
- '+' << columns.map {|c| '-' * sizes[c]}.join('+') << '+'
77
- end
78
-
79
- private_class_method :column_sizes, :data_line, :format_cell, :header_line, :separator_line
80
- end
81
16
  end
82
-
@@ -43,7 +43,7 @@ module Sequel
43
43
 
44
44
  # Merge the given options into the receiver's options and return the receiver
45
45
  # instead of cloning the receiver.
46
- def clone(opts = nil)
46
+ def clone(opts = {})
47
47
  @opts.merge!(opts)
48
48
  self
49
49
  end
@@ -0,0 +1,79 @@
1
+ # The query_literals extension changes Sequel's default behavior of
2
+ # the select, order and group methods so that if the first argument
3
+ # is a regular string, it is treated as a literal string, with the
4
+ # rest of the arguments (if any) treated as placeholder values. This
5
+ # allows you to write code such as:
6
+ #
7
+ # DB[:table].select('a, b, ?', 2).group('a, b').order('c')
8
+ #
9
+ # The default Sequel behavior would literalize that as:
10
+ #
11
+ # SELECT 'a, b, ?', 2 FROM table GROUP BY 'a, b' ORDER BY 'c'
12
+ #
13
+ # Using this extension changes the literalization to:
14
+ #
15
+ # SELECT a, b, 2, FROM table GROUP BY a, b ORDER BY c
16
+ #
17
+ # This extension makes select, group, and order methods operate
18
+ # like filter methods, which support the same interface.
19
+ #
20
+ # There are very few places where Sequel's default behavior is
21
+ # desirable in this area, but for backwards compatibility, the
22
+ # defaults won't be changed until the next major release.
23
+ #
24
+ # Loading this extension does nothing by default except make the
25
+ # Sequel::QueryLiterals module available. You can extend specific
26
+ # datasets with this module:
27
+ #
28
+ # ds = DB[:table]
29
+ # ds.extend(Sequel::QueryLiterals)
30
+ #
31
+ # Order you can extend all of a database's datasets with it, which
32
+ # is probably the desired behavior if you are using this extension:
33
+ #
34
+ # DB.extend_datasets(Sequel::QueryLiterals)
35
+
36
+ module Sequel
37
+ # The QueryLiterals module can be used to make select, group, and
38
+ # order methods operate similar to the filter methods if the first
39
+ # argument is a plain string, treating it like a literal string,
40
+ # with any remaining arguments treated as placeholder values.
41
+ #
42
+ # This adds such support to the following methods: select, select_append,
43
+ # select_group, select_more, group, group_and_count, order, order_append,
44
+ # and order_more.
45
+ #
46
+ # Note that if you pass a block to these methods, it will use the default
47
+ # implementation without the special literal handling.
48
+ module QueryLiterals
49
+ %w'select select_append select_group select_more group group_and_count order order_append order_more'.each do |m|
50
+ class_eval(<<-END, __FILE__, __LINE__ + 1)
51
+ def #{m}(*args)
52
+ if !block_given? && (l = query_literal(args))
53
+ super(l)
54
+ else
55
+ super
56
+ end
57
+ end
58
+ END
59
+ end
60
+
61
+ private
62
+
63
+ # If the first argument is a plain string, return a literal string
64
+ # if there are no additional args or a placeholder literal string with
65
+ # the remaining args. Otherwise, return nil.
66
+ def query_literal(args)
67
+ case (s = args[0])
68
+ when LiteralString, SQL::Blob
69
+ nil
70
+ when String
71
+ if args.length == 1
72
+ LiteralString.new(s)
73
+ else
74
+ SQL::PlaceholderLiteralString.new(s, args[1..-1])
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,76 @@
1
+ # The schema_caching extension adds a few methods to Sequel::Database
2
+ # that make it easy to dump the parsed schema information to a file,
3
+ # and load it from that file. Loading the schema information from a
4
+ # dumped file is faster than parsing it from the database, so this
5
+ # can save bootup time for applications with large numbers of models.
6
+ #
7
+ # Basic usage in application code:
8
+ #
9
+ # Sequel.extension :schema_caching
10
+ #
11
+ # DB = Sequel.connect('...')
12
+ #
13
+ # DB.load_schema_cache('/path/to/schema.dump')
14
+ #
15
+ # # load model files
16
+ #
17
+ # Then, whenever the database schema is modified, write a new cached
18
+ # file. You can do that with <tt>bin/sequel</tt>'s -S option:
19
+ #
20
+ # bin/sequel -S /path/to/schema.dump postgres://...
21
+ #
22
+ # Alternatively, if you don't want to dump the schema information for
23
+ # all tables, and you don't worry about race conditions, you can
24
+ # choose to use the following in your application code:
25
+ #
26
+ # Sequel.extension :schema_caching
27
+ #
28
+ # DB = Sequel.connect('...')
29
+ #
30
+ # DB.load_schema_cache?('/path/to/schema.dump')
31
+ #
32
+ # # load model files
33
+ #
34
+ # DB.dump_schema_cache?('/path/to/schema.dump')
35
+ #
36
+ # With this method, you just have to delete the schema dump file if
37
+ # the schema is modified, and the application will recreate it for you
38
+ # using just the tables that your models use.
39
+ #
40
+ # Note that it is up to the application to ensure that the dumped
41
+ # cached schema reflects the current state of the database. Sequel
42
+ # does no checking to ensure this, as checking would take time and the
43
+ # purpose of this code is to take a shortcut.
44
+ #
45
+ # The cached schema is dumped in Marshal format, since it is the fastest
46
+ # and it handles all ruby objects used in the schema hash. Because of this,
47
+ # you should not attempt to load the schema from a untrusted file.
48
+
49
+ module Sequel
50
+ class Database
51
+ # Dump the cached schema to the filename given in Marshal format.
52
+ def dump_schema_cache(file)
53
+ File.open(file, 'wb'){|f| f.write(Marshal.dump(@schemas))}
54
+ nil
55
+ end
56
+
57
+ # Dump the cached schema to the filename given unless the file
58
+ # already exists.
59
+ def dump_schema_cache?(file)
60
+ dump_schema_cache(file) unless File.exist?(file)
61
+ end
62
+
63
+ # Replace the schema cache with the data from the given file, which
64
+ # should be in Marshal format.
65
+ def load_schema_cache(file)
66
+ @schemas = Marshal.load(File.read(file))
67
+ nil
68
+ end
69
+
70
+ # Replace the schema cache with the data from the given file if the
71
+ # file exists.
72
+ def load_schema_cache?(file)
73
+ load_schema_cache(file) if File.exist?(file)
74
+ end
75
+ end
76
+ end