viking-sequel 3.10.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 (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,241 @@
1
+ # Sequel extends the Array class to add methods to implement the SQL DSL.
2
+ # Most of these methods require that the array not be empty and that it
3
+ # must consist solely of other arrays that have exactly two elements.
4
+ class Array
5
+ # Return a Sequel::SQL::BooleanExpression created from this array, not matching all of the
6
+ # conditions.
7
+ #
8
+ # ~[[:a, true]] # SQL: a IS NOT TRUE
9
+ # ~[[:a, 1], [:b, [2, 3]]] # SQL: a != 1 OR b NOT IN (2, 3)
10
+ def ~
11
+ sql_expr_if_all_two_pairs(:OR, true)
12
+ end
13
+
14
+ # True if the array is not empty and all of its elements are
15
+ # arrays of size 2, false otherwise. This is used to determine if the array
16
+ # could be a specifier of conditions, used similarly to a hash
17
+ # but allowing for duplicate keys and a specific order.
18
+ #
19
+ # [].to_a.all_two_pairs? # => false
20
+ # [:a].to_a.all_two_pairs? # => false
21
+ # [[:b]].to_a.all_two_pairs? # => false
22
+ # [[:a, 1]].to_a.all_two_pairs? # => true
23
+ def all_two_pairs?
24
+ !empty? && all?{|i| (Array === i) && (i.length == 2)}
25
+ end
26
+
27
+ # Return a Sequel::SQL::CaseExpression with this array as the conditions and the given
28
+ # default value and expression.
29
+ #
30
+ # [[{:a=>[2,3]}, 1]].case(0) # SQL: CASE WHEN a IN (2, 3) THEN 1 ELSE 0 END
31
+ # [[:a, 1], [:b, 2]].case(:d, :c) # SQL: CASE c WHEN a THEN 1 WHEN b THEN 2 ELSE d END
32
+ def case(default, expression = nil)
33
+ ::Sequel::SQL::CaseExpression.new(self, default, expression)
34
+ end
35
+
36
+ # Return a Sequel::SQL::Array created from this array. Used if this array contains
37
+ # all two pairs and you want it treated as an SQL array instead of a ordered hash-like
38
+ # conditions.
39
+ #
40
+ # [[1, 2], [3, 4]] # SQL: 1 = 2 AND 3 = 4
41
+ # [[1, 2], [3, 4]].sql_array # SQL: ((1, 2), (3, 4))
42
+ def sql_array
43
+ ::Sequel::SQL::SQLArray.new(self)
44
+ end
45
+
46
+ # Return a Sequel::SQL::BooleanExpression created from this array, matching all of the
47
+ # conditions. Rarely do you need to call this explicitly, as Sequel generally
48
+ # assumes that arrays of all two pairs specify this type of condition.
49
+ #
50
+ # [[:a, true]].sql_expr # SQL: a IS TRUE
51
+ # [[:a, 1], [:b, [2, 3]]].sql_expr # SQL: a = 1 AND b IN (2, 3)
52
+ def sql_expr
53
+ sql_expr_if_all_two_pairs
54
+ end
55
+
56
+ # Return a Sequel::SQL::BooleanExpression created from this array, matching none
57
+ # of the conditions.
58
+ #
59
+ # [[:a, true]].sql_negate # SQL: a IS NOT TRUE
60
+ # [[:a, 1], [:b, [2, 3]]].sql_negate # SQL: a != 1 AND b NOT IN (2, 3)
61
+ def sql_negate
62
+ sql_expr_if_all_two_pairs(:AND, true)
63
+ end
64
+
65
+ # Return a Sequel::SQL::BooleanExpression created from this array, matching any of the
66
+ # conditions.
67
+ #
68
+ # [[:a, true]].sql_or # SQL: a IS TRUE
69
+ # [[:a, 1], [:b, [2, 3]]].sql_or # SQL: a = 1 OR b IN (2, 3)
70
+ def sql_or
71
+ sql_expr_if_all_two_pairs(:OR)
72
+ end
73
+
74
+ # Return a Sequel::SQL::BooleanExpression representing an SQL string made up of the
75
+ # concatenation of this array's elements. If an argument is passed
76
+ # it is used in between each element of the array in the SQL
77
+ # concatenation. This does not require that the array be made up of all two pairs.
78
+ #
79
+ # [:a].sql_string_join # SQL: a
80
+ # [:a, :b].sql_string_join # SQL: a || b
81
+ # [:a, 'b'].sql_string_join # SQL: a || 'b'
82
+ # ['a', :b].sql_string_join(' ') # SQL: 'a' || ' ' || b
83
+ def sql_string_join(joiner=nil)
84
+ if joiner
85
+ args = zip([joiner]*length).flatten
86
+ args.pop
87
+ else
88
+ args = self
89
+ end
90
+ args = args.collect{|a| [Symbol, ::Sequel::SQL::Expression, ::Sequel::LiteralString, TrueClass, FalseClass, NilClass].any?{|c| a.is_a?(c)} ? a : a.to_s}
91
+ ::Sequel::SQL::StringExpression.new(:'||', *args)
92
+ end
93
+
94
+ private
95
+
96
+ # Raise an error if this array is not made up of all two pairs, otherwise create a Sequel::SQL::BooleanExpression from this array.
97
+ def sql_expr_if_all_two_pairs(*args)
98
+ raise(Sequel::Error, 'Not all elements of the array are arrays of size 2, so it cannot be converted to an SQL expression') unless all_two_pairs?
99
+ ::Sequel::SQL::BooleanExpression.from_value_pairs(self, *args)
100
+ end
101
+ end
102
+
103
+ # Sequel extends the Hash class to add methods to implement the SQL DSL.
104
+ class Hash
105
+ # Return a Sequel::SQL::BooleanExpression created from this hash, matching
106
+ # all of the conditions in this hash and the condition specified by
107
+ # the given argument.
108
+ #
109
+ # {:a=>1} & :b # SQL: a = 1 AND b
110
+ # {:a=>true} & ~:b # SQL: a IS TRUE AND NOT b
111
+ def &(ce)
112
+ ::Sequel::SQL::BooleanExpression.new(:AND, self, ce)
113
+ end
114
+
115
+ # Return a Sequel::SQL::BooleanExpression created from this hash, matching
116
+ # all of the conditions in this hash or the condition specified by
117
+ # the given argument.
118
+ #
119
+ # {:a=>1} | :b # SQL: a = 1 OR b
120
+ # {:a=>true} | ~:b # SQL: a IS TRUE OR NOT b
121
+ def |(ce)
122
+ ::Sequel::SQL::BooleanExpression.new(:OR, self, ce)
123
+ end
124
+
125
+ # Return a Sequel::SQL::BooleanExpression created from this hash, not matching all of the
126
+ # conditions.
127
+ #
128
+ # ~{:a=>true} # SQL: a IS NOT TRUE
129
+ # ~{:a=>1, :b=>[2, 3]} # SQL: a != 1 OR b NOT IN (2, 3)
130
+ def ~
131
+ ::Sequel::SQL::BooleanExpression.from_value_pairs(self, :OR, true)
132
+ end
133
+
134
+ # Return a Sequel::SQL::CaseExpression with this hash as the conditions and the given
135
+ # default value. Note that the order of the conditions will be arbitrary, so all
136
+ # conditions should be orthogonal.
137
+ #
138
+ # {{:a=>[2,3]}=>1}.case(0) # SQL: CASE WHEN a IN (2, 3) THEN 1 ELSE 0 END
139
+ # {:a=>1, {:b=>2}].case(:d, :c) # SQL: CASE c WHEN a THEN 1 WHEN b THEN 2 ELSE d END
140
+ # # or: CASE c WHEN b THEN 2 WHEN a THEN 1 ELSE d END
141
+ def case(default, expression = nil)
142
+ ::Sequel::SQL::CaseExpression.new(to_a, default, expression)
143
+ end
144
+
145
+ # Return a Sequel::SQL::BooleanExpression created from this hash, matching all of the
146
+ # conditions. Rarely do you need to call this explicitly, as Sequel generally
147
+ # assumes that hashes specify this type of condition.
148
+ #
149
+ # {:a=>true}.sql_expr # SQL: a IS TRUE
150
+ # {:a=>1, :b=>[2, 3]}.sql_expr # SQL: a = 1 AND b IN (2, 3)
151
+ def sql_expr
152
+ ::Sequel::SQL::BooleanExpression.from_value_pairs(self)
153
+ end
154
+
155
+ # Return a Sequel::SQL::BooleanExpression created from this hash, matching none
156
+ # of the conditions.
157
+ #
158
+ # {:a=>true}.sql_negate # SQL: a IS NOT TRUE
159
+ # {:a=>1, :b=>[2, 3]}.sql_negate # SQL: a != 1 AND b NOT IN (2, 3)
160
+ def sql_negate
161
+ ::Sequel::SQL::BooleanExpression.from_value_pairs(self, :AND, true)
162
+ end
163
+
164
+ # Return a Sequel::SQL::BooleanExpression created from this hash, matching any of the
165
+ # conditions.
166
+ #
167
+ # {:a=>true}.sql_or # SQL: a IS TRUE
168
+ # {:a=>1, :b=>[2, 3]}.sql_or # SQL: a = 1 OR b IN (2, 3)
169
+ def sql_or
170
+ ::Sequel::SQL::BooleanExpression.from_value_pairs(self, :OR)
171
+ end
172
+ end
173
+
174
+ # Sequel extends the String class to add methods to implement the SQL DSL.
175
+ class String
176
+ include Sequel::SQL::AliasMethods
177
+ include Sequel::SQL::CastMethods
178
+
179
+ # Converts a string into a Sequel::LiteralString, in order to override string
180
+ # literalization, e.g.:
181
+ #
182
+ # DB[:items].filter(:abc => 'def').sql #=>
183
+ # "SELECT * FROM items WHERE (abc = 'def')"
184
+ #
185
+ # DB[:items].filter(:abc => 'def'.lit).sql #=>
186
+ # "SELECT * FROM items WHERE (abc = def)"
187
+ #
188
+ # You can also provide arguments, to create a Sequel::SQL::PlaceholderLiteralString:
189
+ #
190
+ # DB[:items].select{|o| o.count('DISTINCT ?'.lit(:a))}.sql #=>
191
+ # "SELECT count(DISTINCT a) FROM items"
192
+ def lit(*args)
193
+ args.empty? ? Sequel::LiteralString.new(self) : Sequel::SQL::PlaceholderLiteralString.new(self, args)
194
+ end
195
+
196
+ # Returns a Sequel::SQL::Blob that holds the same data as this string. Blobs provide proper
197
+ # escaping of binary data.
198
+ def to_sequel_blob
199
+ ::Sequel::SQL::Blob.new(self)
200
+ end
201
+ end
202
+
203
+ # Sequel extends the Symbol class to add methods to implement the SQL DSL.
204
+ class Symbol
205
+ include Sequel::SQL::QualifyingMethods
206
+ include Sequel::SQL::IdentifierMethods
207
+ include Sequel::SQL::AliasMethods
208
+ include Sequel::SQL::CastMethods
209
+ include Sequel::SQL::OrderMethods
210
+ include Sequel::SQL::BooleanMethods
211
+ include Sequel::SQL::NumericMethods
212
+ include Sequel::SQL::StringMethods
213
+ include Sequel::SQL::SubscriptMethods
214
+ include Sequel::SQL::ComplexExpressionMethods
215
+ include Sequel::SQL::InequalityMethods if RUBY_VERSION < '1.9.0'
216
+
217
+ # If no argument is given, returns a Sequel::SQL::ColumnAll object specifying all
218
+ # columns for this table.
219
+ # If an argument is given, returns a Sequel::SQL::NumericExpression using the *
220
+ # (multiplication) operator with this and the given argument.
221
+ #
222
+ # :table.* # SQL: table.*
223
+ # :column * 2 # SQL: column * 2
224
+ def *(ce=(arg=false;nil))
225
+ return super(ce) unless arg == false
226
+ Sequel::SQL::ColumnAll.new(self);
227
+ end
228
+
229
+ # Returns a Sequel::SQL::Function with this as the function name,
230
+ # and the given arguments. This is aliased as Symbol#[] if the RUBY_VERSION
231
+ # is less than 1.9.0. Ruby 1.9 defines Symbol#[], and Sequel
232
+ # doesn't override methods defined by ruby itself.
233
+ #
234
+ # :now.sql_function # SQL: now()
235
+ # :sum.sql_function(:a) # SQL: sum(a)
236
+ # :concat.sql_function(:a, :b) # SQL: concat(a, b)
237
+ def sql_function(*args)
238
+ Sequel::SQL::Function.new(self, *args)
239
+ end
240
+ alias_method(:[], :sql_function) if RUBY_VERSION < '1.9.0'
241
+ end
@@ -0,0 +1,1079 @@
1
+ module Sequel
2
+ # Hash of adapters that have been used. The key is the adapter scheme
3
+ # symbol, and the value is the Database subclass.
4
+ ADAPTER_MAP = {}
5
+
6
+ # Array of all databases to which Sequel has connected. If you are
7
+ # developing an application that can connect to an arbitrary number of
8
+ # databases, delete the database objects from this or they will not get
9
+ # garbage collected.
10
+ DATABASES = []
11
+
12
+ # A Database object represents a virtual connection to a database.
13
+ # The Database class is meant to be subclassed by database adapters in order
14
+ # to provide the functionality needed for executing queries.
15
+ class Database
16
+ extend Metaprogramming
17
+ include Metaprogramming
18
+
19
+ # Array of supported database adapters
20
+ ADAPTERS = %w'ado amalgalite db2 dbi do firebird informix jdbc mysql odbc openbase oracle postgres sqlite'.collect{|x| x.to_sym}
21
+
22
+ SQL_BEGIN = 'BEGIN'.freeze
23
+ SQL_COMMIT = 'COMMIT'.freeze
24
+ SQL_RELEASE_SAVEPOINT = 'RELEASE SAVEPOINT autopoint_%d'.freeze
25
+ SQL_ROLLBACK = 'ROLLBACK'.freeze
26
+ SQL_ROLLBACK_TO_SAVEPOINT = 'ROLLBACK TO SAVEPOINT autopoint_%d'.freeze
27
+ SQL_SAVEPOINT = 'SAVEPOINT autopoint_%d'.freeze
28
+
29
+ TRANSACTION_BEGIN = 'Transaction.begin'.freeze
30
+ TRANSACTION_COMMIT = 'Transaction.commit'.freeze
31
+ TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
32
+
33
+ POSTGRES_DEFAULT_RE = /\A(?:B?('.*')::[^']+|\((-?\d+(?:\.\d+)?)\))\z/
34
+ MSSQL_DEFAULT_RE = /\A(?:\(N?('.*')\)|\(\((-?\d+(?:\.\d+)?)\)\))\z/
35
+ MYSQL_TIMESTAMP_RE = /\ACURRENT_(?:DATE|TIMESTAMP)?\z/
36
+ STRING_DEFAULT_RE = /\A'(.*)'\z/
37
+
38
+ # The identifier input method to use by default
39
+ @@identifier_input_method = nil
40
+
41
+ # The identifier output method to use by default
42
+ @@identifier_output_method = nil
43
+
44
+ # Whether to use the single threaded connection pool by default
45
+ @@single_threaded = false
46
+
47
+ # Whether to quote identifiers (columns and tables) by default
48
+ @@quote_identifiers = nil
49
+
50
+ # The default schema to use, generally should be nil.
51
+ attr_accessor :default_schema
52
+
53
+ # Numeric specifying the duration beyond which queries are logged at warn
54
+ # level instead of info level.
55
+ attr_accessor :log_warn_duration
56
+
57
+ # Array of SQL loggers to use for this database
58
+ attr_accessor :loggers
59
+
60
+ # The options for this database
61
+ attr_reader :opts
62
+
63
+ # The connection pool for this database
64
+ attr_reader :pool
65
+
66
+ # The prepared statement objects for this database, keyed by name
67
+ attr_reader :prepared_statements
68
+
69
+ # Constructs a new instance of a database connection with the specified
70
+ # options hash.
71
+ #
72
+ # Sequel::Database is an abstract class that is not useful by itself.
73
+ #
74
+ # Takes the following options:
75
+ # * :default_schema : The default schema to use, should generally be nil
76
+ # * :disconnection_proc: A proc used to disconnect the connection.
77
+ # * :identifier_input_method: A string method symbol to call on identifiers going into the database
78
+ # * :identifier_output_method: A string method symbol to call on identifiers coming from the database
79
+ # * :loggers : An array of loggers to use.
80
+ # * :quote_identifiers : Whether to quote identifiers
81
+ # * :single_threaded : Whether to use a single-threaded connection pool
82
+ #
83
+ # All options given are also passed to the ConnectionPool. If a block
84
+ # is given, it is used as the connection_proc for the ConnectionPool.
85
+ def initialize(opts = {}, &block)
86
+ @opts ||= opts
87
+ @opts = connection_pool_default_options.merge(@opts)
88
+ @loggers = Array(@opts[:logger]) + Array(@opts[:loggers])
89
+ self.log_warn_duration = @opts[:log_warn_duration]
90
+ @opts[:disconnection_proc] ||= proc{|conn| disconnect_connection(conn)}
91
+ block ||= proc{|server| connect(server)}
92
+ @opts[:servers] = {} if @opts[:servers].is_a?(String)
93
+
94
+ @opts[:single_threaded] = @single_threaded = typecast_value_boolean(@opts.fetch(:single_threaded, @@single_threaded))
95
+ @schemas = {}
96
+ @default_schema = @opts.fetch(:default_schema, default_schema_default)
97
+ @prepared_statements = {}
98
+ @transactions = []
99
+ @identifier_input_method = nil
100
+ @identifier_output_method = nil
101
+ @quote_identifiers = nil
102
+ @pool = ConnectionPool.get_pool(@opts, &block)
103
+
104
+ ::Sequel::DATABASES.push(self)
105
+ end
106
+
107
+ ### Class Methods ###
108
+
109
+ # The Database subclass for the given adapter scheme.
110
+ # Raises Sequel::AdapterNotFound if the adapter
111
+ # could not be loaded.
112
+ def self.adapter_class(scheme)
113
+ scheme = scheme.to_s.gsub('-', '_').to_sym
114
+
115
+ unless klass = ADAPTER_MAP[scheme]
116
+ # attempt to load the adapter file
117
+ begin
118
+ Sequel.tsk_require "sequel/adapters/#{scheme}"
119
+ rescue LoadError => e
120
+ raise Sequel.convert_exception_class(e, AdapterNotFound)
121
+ end
122
+
123
+ # make sure we actually loaded the adapter
124
+ unless klass = ADAPTER_MAP[scheme]
125
+ raise AdapterNotFound, "Could not load #{scheme} adapter"
126
+ end
127
+ end
128
+ klass
129
+ end
130
+
131
+ # Returns the scheme for the Database class.
132
+ def self.adapter_scheme
133
+ @scheme
134
+ end
135
+
136
+ # Connects to a database. See Sequel.connect.
137
+ def self.connect(conn_string, opts = {})
138
+ case conn_string
139
+ when String
140
+ if match = /\A(jdbc|do):/o.match(conn_string)
141
+ c = adapter_class(match[1].to_sym)
142
+ opts = {:uri=>conn_string}.merge(opts)
143
+ else
144
+ uri = URI.parse(conn_string)
145
+ scheme = uri.scheme
146
+ scheme = :dbi if scheme =~ /\Adbi-/
147
+ c = adapter_class(scheme)
148
+ uri_options = c.send(:uri_to_options, uri)
149
+ uri.query.split('&').collect{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v if k && !k.empty?} unless uri.query.to_s.strip.empty?
150
+ uri_options.entries.each{|k,v| uri_options[k] = URI.unescape(v) if v.is_a?(String)}
151
+ opts = uri_options.merge(opts)
152
+ end
153
+ when Hash
154
+ opts = conn_string.merge(opts)
155
+ c = adapter_class(opts[:adapter] || opts['adapter'])
156
+ else
157
+ raise Error, "Sequel::Database.connect takes either a Hash or a String, given: #{conn_string.inspect}"
158
+ end
159
+ # process opts a bit
160
+ opts = opts.inject({}) do |m, kv| k, v = *kv
161
+ k = :user if k.to_s == 'username'
162
+ m[k.to_sym] = v
163
+ m
164
+ end
165
+ begin
166
+ db = c.new(opts)
167
+ db.test_connection if opts[:test] && db.send(:typecast_value_boolean, opts[:test])
168
+ result = yield(db) if block_given?
169
+ ensure
170
+ if block_given?
171
+ db.disconnect if db
172
+ ::Sequel::DATABASES.delete(db)
173
+ end
174
+ end
175
+ block_given? ? result : db
176
+ end
177
+
178
+ # The method to call on identifiers going into the database
179
+ def self.identifier_input_method
180
+ @@identifier_input_method
181
+ end
182
+
183
+ # Set the method to call on identifiers going into the database
184
+ # See Sequel.identifier_input_method=.
185
+ def self.identifier_input_method=(v)
186
+ @@identifier_input_method = v || ""
187
+ end
188
+
189
+ # The method to call on identifiers coming from the database
190
+ def self.identifier_output_method
191
+ @@identifier_output_method
192
+ end
193
+
194
+ # Set the method to call on identifiers coming from the database
195
+ # See Sequel.identifier_output_method=.
196
+ def self.identifier_output_method=(v)
197
+ @@identifier_output_method = v || ""
198
+ end
199
+
200
+ # Sets the default quote_identifiers mode for new databases.
201
+ # See Sequel.quote_identifiers=.
202
+ def self.quote_identifiers=(value)
203
+ @@quote_identifiers = value
204
+ end
205
+
206
+ # Sets the default single_threaded mode for new databases.
207
+ # See Sequel.single_threaded=.
208
+ def self.single_threaded=(value)
209
+ @@single_threaded = value
210
+ end
211
+
212
+ ### Private Class Methods ###
213
+
214
+ # Sets the adapter scheme for the Database class. Call this method in
215
+ # descendants of Database to allow connection using a URL. For example the
216
+ # following:
217
+ #
218
+ # class Sequel::MyDB::Database < Sequel::Database
219
+ # set_adapter_scheme :mydb
220
+ # ...
221
+ # end
222
+ #
223
+ # would allow connection using:
224
+ #
225
+ # Sequel.connect('mydb://user:password@dbserver/mydb')
226
+ def self.set_adapter_scheme(scheme) # :nodoc:
227
+ @scheme = scheme
228
+ ADAPTER_MAP[scheme.to_sym] = self
229
+ end
230
+
231
+ # Converts a uri to an options hash. These options are then passed
232
+ # to a newly created database object.
233
+ def self.uri_to_options(uri) # :nodoc:
234
+ { :user => uri.user,
235
+ :password => uri.password,
236
+ :host => uri.host,
237
+ :port => uri.port,
238
+ :database => (m = /\/(.*)/.match(uri.path)) && (m[1]) }
239
+ end
240
+
241
+ private_class_method :set_adapter_scheme, :uri_to_options
242
+
243
+ ### Instance Methods ###
244
+
245
+ # Runs the supplied SQL statement string on the database server.
246
+ # Alias for run.
247
+ def <<(sql)
248
+ run(sql)
249
+ end
250
+
251
+ # Returns a dataset from the database. If the first argument is a string,
252
+ # the method acts as an alias for Database#fetch, returning a dataset for
253
+ # arbitrary SQL:
254
+ #
255
+ # DB['SELECT * FROM items WHERE name = ?', my_name].all
256
+ #
257
+ # Otherwise, acts as an alias for Database#from, setting the primary
258
+ # table for the dataset:
259
+ #
260
+ # DB[:items].sql #=> "SELECT * FROM items"
261
+ def [](*args)
262
+ (String === args.first) ? fetch(*args) : from(*args)
263
+ end
264
+
265
+ # Dynamically add new servers or modify server options at runtime. Also adds new
266
+ # servers to the connection pool. Intended for use with master/slave or shard
267
+ # configurations where it is useful to add new server hosts at runtime.
268
+ #
269
+ # servers argument should be a hash with server name symbol keys and hash or
270
+ # proc values. If a servers key is already in use, it's value is overridden
271
+ # with the value provided.
272
+ #
273
+ # DB.add_servers(:f=>{:host=>"hash_host_f"})
274
+ def add_servers(servers)
275
+ @opts[:servers] = @opts[:servers] ? @opts[:servers].merge(servers) : servers
276
+ @pool.add_servers(servers.keys)
277
+ end
278
+
279
+ # Call the prepared statement with the given name with the given hash
280
+ # of arguments.
281
+ def call(ps_name, hash={})
282
+ prepared_statements[ps_name].call(hash)
283
+ end
284
+
285
+ # Cast the given type to a literal type
286
+ def cast_type_literal(type)
287
+ type_literal(:type=>type)
288
+ end
289
+
290
+ # Connects to the database. This method should be overridden by descendants.
291
+ def connect(server)
292
+ raise NotImplementedError, "#connect should be overridden by adapters"
293
+ end
294
+
295
+ # The database type for this database object, the same as the adapter scheme
296
+ # by default. Should be overridden in adapters (especially shared adapters)
297
+ # to be the correct type, so that even if two separate Database objects are
298
+ # using different adapters you can tell that they are using the same database
299
+ # type. Even better, you can tell that two Database objects that are using
300
+ # the same adapter are connecting to different database types (think JDBC or
301
+ # DataObjects).
302
+ def database_type
303
+ self.class.adapter_scheme
304
+ end
305
+
306
+ # Returns a blank dataset for this database
307
+ def dataset
308
+ ds = Sequel::Dataset.new(self)
309
+ end
310
+
311
+ # Disconnects all available connections from the connection pool. Any
312
+ # connections currently in use will not be disconnected. Options:
313
+ # * :servers - Should be a symbol specifing the server to disconnect from,
314
+ # or an array of symbols to specify multiple servers.
315
+ def disconnect(opts = {})
316
+ pool.disconnect(opts)
317
+ end
318
+
319
+ # Yield a new database object for every server in the connection pool.
320
+ # Intended for use in sharded environments where there is a need to make schema
321
+ # modifications (DDL queries) on each shard.
322
+ #
323
+ # DB.each_server{|db| db.create_table(:users){primary_key :id; String :name}}
324
+ def each_server(&block)
325
+ servers.each{|s| self.class.connect(server_opts(s), &block)}
326
+ end
327
+
328
+ # Executes the given SQL on the database. This method should be overridden in descendants.
329
+ # This method should not be called directly by user code.
330
+ def execute(sql, opts={})
331
+ raise NotImplementedError, "#execute should be overridden by adapters"
332
+ end
333
+
334
+ # Method that should be used when submitting any DDL (Data Definition
335
+ # Language) SQL. By default, calls execute_dui.
336
+ # This method should not be called directly by user code.
337
+ def execute_ddl(sql, opts={}, &block)
338
+ execute_dui(sql, opts, &block)
339
+ end
340
+
341
+ # Method that should be used when issuing a DELETE, UPDATE, or INSERT
342
+ # statement. By default, calls execute.
343
+ # This method should not be called directly by user code.
344
+ def execute_dui(sql, opts={}, &block)
345
+ execute(sql, opts, &block)
346
+ end
347
+
348
+ # Method that should be used when issuing a INSERT
349
+ # statement. By default, calls execute_dui.
350
+ # This method should not be called directly by user code.
351
+ def execute_insert(sql, opts={}, &block)
352
+ execute_dui(sql, opts, &block)
353
+ end
354
+
355
+ # Fetches records for an arbitrary SQL statement. If a block is given,
356
+ # it is used to iterate over the records:
357
+ #
358
+ # DB.fetch('SELECT * FROM items'){|r| p r}
359
+ #
360
+ # The method returns a dataset instance:
361
+ #
362
+ # DB.fetch('SELECT * FROM items').all
363
+ #
364
+ # Fetch can also perform parameterized queries for protection against SQL
365
+ # injection:
366
+ #
367
+ # DB.fetch('SELECT * FROM items WHERE name = ?', my_name).all
368
+ def fetch(sql, *args, &block)
369
+ ds = dataset.with_sql(sql, *args)
370
+ ds.each(&block) if block
371
+ ds
372
+ end
373
+
374
+ # Returns a new dataset with the from method invoked. If a block is given,
375
+ # it is used as a filter on the dataset.
376
+ def from(*args, &block)
377
+ ds = dataset.from(*args)
378
+ block ? ds.filter(&block) : ds
379
+ end
380
+
381
+ # Returns a single value from the database, e.g.:
382
+ #
383
+ # # SELECT 1
384
+ # DB.get(1) #=> 1
385
+ #
386
+ # # SELECT version()
387
+ # DB.get(:version.sql_function) #=> ...
388
+ def get(*args, &block)
389
+ dataset.get(*args, &block)
390
+ end
391
+
392
+ # The method to call on identifiers going into the database
393
+ def identifier_input_method
394
+ case @identifier_input_method
395
+ when nil
396
+ @identifier_input_method = @opts.fetch(:identifier_input_method, (@@identifier_input_method.nil? ? identifier_input_method_default : @@identifier_input_method))
397
+ @identifier_input_method == "" ? nil : @identifier_input_method
398
+ when ""
399
+ nil
400
+ else
401
+ @identifier_input_method
402
+ end
403
+ end
404
+
405
+ # Set the method to call on identifiers going into the database
406
+ def identifier_input_method=(v)
407
+ reset_schema_utility_dataset
408
+ @identifier_input_method = v || ""
409
+ end
410
+
411
+ # The method to call on identifiers coming from the database
412
+ def identifier_output_method
413
+ case @identifier_output_method
414
+ when nil
415
+ @identifier_output_method = @opts.fetch(:identifier_output_method, (@@identifier_output_method.nil? ? identifier_output_method_default : @@identifier_output_method))
416
+ @identifier_output_method == "" ? nil : @identifier_output_method
417
+ when ""
418
+ nil
419
+ else
420
+ @identifier_output_method
421
+ end
422
+ end
423
+
424
+ # Set the method to call on identifiers coming from the database
425
+ def identifier_output_method=(v)
426
+ reset_schema_utility_dataset
427
+ @identifier_output_method = v || ""
428
+ end
429
+
430
+ # Returns a string representation of the database object including the
431
+ # class name and the connection URI (or the opts if the URI
432
+ # cannot be constructed).
433
+ def inspect
434
+ "#<#{self.class}: #{(uri rescue opts).inspect}>"
435
+ end
436
+
437
+ # Proxy the literal call to the dataset, used for default values.
438
+ def literal(v)
439
+ schema_utility_dataset.literal(v)
440
+ end
441
+
442
+ # Log a message at level info to all loggers.
443
+ def log_info(message, args=nil)
444
+ log_each(:info, args ? "#{message}; #{args.inspect}" : message)
445
+ end
446
+
447
+ # Yield to the block, logging any errors at error level to all loggers,
448
+ # and all other queries with the duration at warn or info level.
449
+ def log_yield(sql, args=nil)
450
+ return yield if @loggers.empty?
451
+ sql = "#{sql}; #{args.inspect}" if args
452
+ start = Time.now
453
+ begin
454
+ yield
455
+ rescue => e
456
+ log_each(:error, "#{e.class}: #{e.message.strip}: #{sql}")
457
+ raise
458
+ ensure
459
+ log_duration(Time.now - start, sql) unless e
460
+ end
461
+ end
462
+
463
+ # Remove any existing loggers and just use the given logger.
464
+ def logger=(logger)
465
+ @loggers = Array(logger)
466
+ end
467
+
468
+ # Whether to quote identifiers (columns and tables) for this database
469
+ def quote_identifiers=(v)
470
+ reset_schema_utility_dataset
471
+ @quote_identifiers = v
472
+ end
473
+
474
+ # Returns true if the database quotes identifiers.
475
+ def quote_identifiers?
476
+ return @quote_identifiers unless @quote_identifiers.nil?
477
+ @quote_identifiers = @opts.fetch(:quote_identifiers, (@@quote_identifiers.nil? ? quote_identifiers_default : @@quote_identifiers))
478
+ end
479
+
480
+ # Dynamically remove existing servers from the connection pool. Intended for
481
+ # use with master/slave or shard configurations where it is useful to remove
482
+ # existing server hosts at runtime.
483
+ #
484
+ # servers should be symbols or arrays of symbols. If a nonexistent server
485
+ # is specified, it is ignored. If no servers have been specified for
486
+ # this database, no changes are made. If you attempt to remove the :default server,
487
+ # an error will be raised.
488
+ #
489
+ # DB.remove_servers(:f1, :f2)
490
+ def remove_servers(*servers)
491
+ if @opts[:servers] && !@opts[:servers].empty?
492
+ servs = @opts[:servers].dup
493
+ servers.flatten!
494
+ servers.each{|s| servs.delete(s)}
495
+ @opts[:servers] = servs
496
+ @pool.remove_servers(servers)
497
+ end
498
+ end
499
+
500
+ # Runs the supplied SQL statement string on the database server. Returns nil.
501
+ # Options:
502
+ # * :server - The server to run the SQL on.
503
+ def run(sql, opts={})
504
+ execute_ddl(sql, opts)
505
+ nil
506
+ end
507
+
508
+ # Parse the schema from the database.
509
+ # Returns the schema for the given table as an array with all members being arrays of length 2,
510
+ # the first member being the column name, and the second member being a hash of column information.
511
+ # Available options are:
512
+ #
513
+ # * :reload - Get fresh information from the database, instead of using
514
+ # cached information. If table_name is blank, :reload should be used
515
+ # unless you are sure that schema has not been called before with a
516
+ # table_name, otherwise you may only getting the schemas for tables
517
+ # that have been requested explicitly.
518
+ # * :schema - An explicit schema to use. It may also be implicitly provided
519
+ # via the table name.
520
+ def schema(table, opts={})
521
+ raise(Error, 'schema parsing is not implemented on this database') unless respond_to?(:schema_parse_table, true)
522
+
523
+ sch, table_name = schema_and_table(table)
524
+ quoted_name = quote_schema_table(table)
525
+ opts = opts.merge(:schema=>sch) if sch && !opts.include?(:schema)
526
+
527
+ @schemas.delete(quoted_name) if opts[:reload]
528
+ return @schemas[quoted_name] if @schemas[quoted_name]
529
+
530
+ cols = schema_parse_table(table_name, opts)
531
+ raise(Error, 'schema parsing returned no columns, table probably doesn\'t exist') if cols.nil? || cols.empty?
532
+ cols.each{|_,c| c[:ruby_default] = column_schema_to_ruby_default(c[:default], c[:type])}
533
+ @schemas[quoted_name] = cols
534
+ end
535
+
536
+ # Returns a new dataset with the select method invoked.
537
+ def select(*args, &block)
538
+ dataset.select(*args, &block)
539
+ end
540
+
541
+ # An array of servers/shards for this Database object.
542
+ def servers
543
+ pool.servers
544
+ end
545
+
546
+ # Returns true if the database is using a single-threaded connection pool.
547
+ def single_threaded?
548
+ @single_threaded
549
+ end
550
+
551
+ # Acquires a database connection, yielding it to the passed block.
552
+ def synchronize(server=nil, &block)
553
+ @pool.hold(server || :default, &block)
554
+ end
555
+
556
+ # Whether the database and adapter support savepoints, false by default
557
+ def supports_savepoints?
558
+ false
559
+ end
560
+
561
+ # Returns true if a table with the given name exists. This requires a query
562
+ # to the database.
563
+ def table_exists?(name)
564
+ begin
565
+ from(name).first
566
+ true
567
+ rescue
568
+ false
569
+ end
570
+ end
571
+
572
+ # Attempts to acquire a database connection. Returns true if successful.
573
+ # Will probably raise an error if unsuccessful.
574
+ def test_connection(server=nil)
575
+ synchronize(server){|conn|}
576
+ true
577
+ end
578
+
579
+ # Starts a database transaction. When a database transaction is used,
580
+ # either all statements are successful or none of the statements are
581
+ # successful. Note that MySQL MyISAM tabels do not support transactions.
582
+ #
583
+ # The following options are respected:
584
+ #
585
+ # * :server - The server to use for the transaction
586
+ # * :savepoint - Whether to create a new savepoint for this transaction,
587
+ # only respected if the database adapter supports savepoints. By
588
+ # default Sequel will reuse an existing transaction, so if you want to
589
+ # use a savepoint you must use this option.
590
+ def transaction(opts={}, &block)
591
+ synchronize(opts[:server]) do |conn|
592
+ return yield(conn) if already_in_transaction?(conn, opts)
593
+ _transaction(conn, &block)
594
+ end
595
+ end
596
+
597
+ # Typecast the value to the given column_type. Calls
598
+ # typecast_value_#{column_type} if the method exists,
599
+ # otherwise returns the value.
600
+ # This method should raise Sequel::InvalidValue if assigned value
601
+ # is invalid.
602
+ def typecast_value(column_type, value)
603
+ return nil if value.nil?
604
+ meth = "typecast_value_#{column_type}"
605
+ begin
606
+ respond_to?(meth, true) ? send(meth, value) : value
607
+ rescue ArgumentError, TypeError => e
608
+ raise Sequel.convert_exception_class(e, InvalidValue)
609
+ end
610
+ end
611
+
612
+ # Returns the URI identifying the database.
613
+ # This method can raise an error if the database used options
614
+ # instead of a connection string.
615
+ def uri
616
+ uri = URI::Generic.new(
617
+ self.class.adapter_scheme.to_s,
618
+ nil,
619
+ @opts[:host],
620
+ @opts[:port],
621
+ nil,
622
+ "/#{@opts[:database]}",
623
+ nil,
624
+ nil,
625
+ nil
626
+ )
627
+ uri.user = @opts[:user]
628
+ uri.password = @opts[:password] if uri.user
629
+ uri.to_s
630
+ end
631
+
632
+ # Explicit alias of uri for easier subclassing.
633
+ def url
634
+ uri
635
+ end
636
+
637
+ private
638
+
639
+ # Internal generic transaction method. Any exception raised by the given
640
+ # block will cause the transaction to be rolled back. If the exception is
641
+ # not Sequel::Rollback, the error will be reraised. If no exception occurs
642
+ # inside the block, the transaction is commited.
643
+ def _transaction(conn)
644
+ begin
645
+ add_transaction
646
+ t = begin_transaction(conn)
647
+ yield(conn)
648
+ rescue Exception => e
649
+ rollback_transaction(t) if t
650
+ transaction_error(e)
651
+ ensure
652
+ begin
653
+ commit_transaction(t) unless e
654
+ rescue Exception => e
655
+ raise_error(e, :classes=>database_error_classes)
656
+ ensure
657
+ remove_transaction(t)
658
+ end
659
+ end
660
+ end
661
+
662
+ # Add the current thread to the list of active transactions
663
+ def add_transaction
664
+ th = Thread.current
665
+ if supports_savepoints?
666
+ unless @transactions.include?(th)
667
+ th[:sequel_transaction_depth] = 0
668
+ @transactions << th
669
+ end
670
+ else
671
+ @transactions << th
672
+ end
673
+ end
674
+
675
+ # Whether the current thread/connection is already inside a transaction
676
+ def already_in_transaction?(conn, opts)
677
+ @transactions.include?(Thread.current) && (!supports_savepoints? || !opts[:savepoint])
678
+ end
679
+
680
+ # SQL to start a new savepoint
681
+ def begin_savepoint_sql(depth)
682
+ SQL_SAVEPOINT % depth
683
+ end
684
+
685
+ # Start a new database transaction on the given connection.
686
+ def begin_transaction(conn)
687
+ if supports_savepoints?
688
+ th = Thread.current
689
+ depth = th[:sequel_transaction_depth]
690
+ conn = transaction_statement_object(conn) if respond_to?(:transaction_statement_object, true)
691
+ log_connection_execute(conn, depth > 0 ? begin_savepoint_sql(depth) : begin_transaction_sql)
692
+ th[:sequel_transaction_depth] += 1
693
+ else
694
+ log_connection_execute(conn, begin_transaction_sql)
695
+ end
696
+ conn
697
+ end
698
+
699
+ # SQL to BEGIN a transaction.
700
+ def begin_transaction_sql
701
+ SQL_BEGIN
702
+ end
703
+
704
+ # Returns true when the object is considered blank.
705
+ # The only objects that are blank are nil, false,
706
+ # strings with all whitespace, and ones that respond
707
+ # true to empty?
708
+ def blank_object?(obj)
709
+ return obj.blank? if obj.respond_to?(:blank?)
710
+ case obj
711
+ when NilClass, FalseClass
712
+ true
713
+ when Numeric, TrueClass
714
+ false
715
+ when String
716
+ obj.strip.empty?
717
+ else
718
+ obj.respond_to?(:empty?) ? obj.empty? : false
719
+ end
720
+ end
721
+
722
+ # Convert the given default, which should be a database specific string, into
723
+ # a ruby object.
724
+ def column_schema_to_ruby_default(default, type)
725
+ return if default.nil?
726
+ orig_default = default
727
+ if database_type == :postgres and m = POSTGRES_DEFAULT_RE.match(default)
728
+ default = m[1] || m[2]
729
+ end
730
+ if database_type == :mssql and m = MSSQL_DEFAULT_RE.match(default)
731
+ default = m[1] || m[2]
732
+ end
733
+ if [:string, :blob, :date, :datetime, :time, :enum].include?(type)
734
+ if database_type == :mysql
735
+ return if [:date, :datetime, :time].include?(type) && MYSQL_TIMESTAMP_RE.match(default)
736
+ orig_default = default = "'#{default.gsub("'", "''").gsub('\\', '\\\\')}'"
737
+ end
738
+ return unless m = STRING_DEFAULT_RE.match(default)
739
+ default = m[1].gsub("''", "'")
740
+ end
741
+ res = begin
742
+ case type
743
+ when :boolean
744
+ case default
745
+ when /[f0]/i
746
+ false
747
+ when /[t1]/i
748
+ true
749
+ end
750
+ when :string, :enum
751
+ default
752
+ when :blob
753
+ Sequel::SQL::Blob.new(default)
754
+ when :integer
755
+ Integer(default)
756
+ when :float
757
+ Float(default)
758
+ when :date
759
+ Sequel.string_to_date(default)
760
+ when :datetime
761
+ DateTime.parse(default)
762
+ when :time
763
+ Sequel.string_to_time(default)
764
+ when :decimal
765
+ BigDecimal.new(default)
766
+ end
767
+ rescue
768
+ nil
769
+ end
770
+ end
771
+
772
+ # SQL to commit a savepoint
773
+ def commit_savepoint_sql(depth)
774
+ SQL_RELEASE_SAVEPOINT % depth
775
+ end
776
+
777
+ # Commit the active transaction on the connection
778
+ def commit_transaction(conn)
779
+ if supports_savepoints?
780
+ depth = Thread.current[:sequel_transaction_depth]
781
+ log_connection_execute(conn, depth > 1 ? commit_savepoint_sql(depth-1) : commit_transaction_sql)
782
+ else
783
+ log_connection_execute(conn, commit_transaction_sql)
784
+ end
785
+ end
786
+
787
+ # SQL to COMMIT a transaction.
788
+ def commit_transaction_sql
789
+ SQL_COMMIT
790
+ end
791
+
792
+ # Method called on the connection object to execute SQL on the database,
793
+ # used by the transaction code.
794
+ def connection_execute_method
795
+ :execute
796
+ end
797
+
798
+ # The default options for the connection pool.
799
+ def connection_pool_default_options
800
+ {}
801
+ end
802
+
803
+ # Which transaction errors to translate, blank by default.
804
+ def database_error_classes
805
+ []
806
+ end
807
+
808
+ # The default value for default_schema.
809
+ def default_schema_default
810
+ nil
811
+ end
812
+
813
+ # The method to apply to identifiers going into the database by default.
814
+ # Should be overridden in subclasses for databases that fold unquoted
815
+ # identifiers to lower case instead of uppercase, such as
816
+ # MySQL, PostgreSQL, and SQLite.
817
+ def identifier_input_method_default
818
+ :upcase
819
+ end
820
+
821
+ # The method to apply to identifiers coming the database by default.
822
+ # Should be overridden in subclasses for databases that fold unquoted
823
+ # identifiers to lower case instead of uppercase, such as
824
+ # MySQL, PostgreSQL, and SQLite.
825
+ def identifier_output_method_default
826
+ :downcase
827
+ end
828
+
829
+ # Return a Method object for the dataset's output_identifier_method.
830
+ # Used in metadata parsing to make sure the returned information is in the
831
+ # correct format.
832
+ def input_identifier_meth
833
+ dataset.method(:input_identifier)
834
+ end
835
+
836
+ # Log the given SQL and then execute it on the connection, used by
837
+ # the transaction code.
838
+ def log_connection_execute(conn, sql)
839
+ log_yield(sql){conn.send(connection_execute_method, sql)}
840
+ end
841
+
842
+ # Log message with message prefixed by duration at info level, or
843
+ # warn level if duration is greater than log_warn_duration.
844
+ def log_duration(duration, message)
845
+ log_each((lwd = log_warn_duration and duration >= lwd) ? :warn : :info, "(#{sprintf('%0.6fs', duration)}) #{message}")
846
+ end
847
+
848
+ # Log message at level (which should be :error, :warn, or :info)
849
+ # to all loggers.
850
+ def log_each(level, message)
851
+ @loggers.each{|logger| logger.send(level, message)}
852
+ end
853
+
854
+ # Return a dataset that uses the default identifier input and output methods
855
+ # for this database. Used when parsing metadata so that column symbols are
856
+ # returned as expected.
857
+ def metadata_dataset
858
+ return @metadata_dataset if @metadata_dataset
859
+ ds = dataset
860
+ ds.identifier_input_method = identifier_input_method_default
861
+ ds.identifier_output_method = identifier_output_method_default
862
+ @metadata_dataset = ds
863
+ end
864
+
865
+ # Return a Method object for the dataset's output_identifier_method.
866
+ # Used in metadata parsing to make sure the returned information is in the
867
+ # correct format.
868
+ def output_identifier_meth
869
+ dataset.method(:output_identifier)
870
+ end
871
+
872
+ # Whether to quote identifiers by default for this database, true
873
+ # by default.
874
+ def quote_identifiers_default
875
+ true
876
+ end
877
+
878
+ # SQL to ROLLBACK a transaction.
879
+ def rollback_transaction_sql
880
+ SQL_ROLLBACK
881
+ end
882
+
883
+ # Convert the given exception to a DatabaseError, keeping message
884
+ # and traceback.
885
+ def raise_error(exception, opts={})
886
+ if !opts[:classes] || Array(opts[:classes]).any?{|c| exception.is_a?(c)}
887
+ raise Sequel.convert_exception_class(exception, opts[:disconnect] ? DatabaseDisconnectError : DatabaseError)
888
+ else
889
+ raise exception
890
+ end
891
+ end
892
+
893
+ # Remove the cached schema for the given schema name
894
+ def remove_cached_schema(table)
895
+ @schemas.delete(quote_schema_table(table)) if @schemas
896
+ end
897
+
898
+ # Remove the current thread from the list of active transactions
899
+ def remove_transaction(conn)
900
+ th = Thread.current
901
+ @transactions.delete(th) if !supports_savepoints? || ((th[:sequel_transaction_depth] -= 1) <= 0)
902
+ end
903
+
904
+ # Remove the cached schema_utility_dataset, because the identifier
905
+ # quoting has changed.
906
+ def reset_schema_utility_dataset
907
+ @schema_utility_dataset = nil
908
+ end
909
+
910
+ # SQL to rollback to a savepoint
911
+ def rollback_savepoint_sql(depth)
912
+ SQL_ROLLBACK_TO_SAVEPOINT % depth
913
+ end
914
+
915
+ # Rollback the active transaction on the connection
916
+ def rollback_transaction(conn)
917
+ if supports_savepoints?
918
+ depth = Thread.current[:sequel_transaction_depth]
919
+ log_connection_execute(conn, depth > 1 ? rollback_savepoint_sql(depth-1) : rollback_transaction_sql)
920
+ else
921
+ log_connection_execute(conn, rollback_transaction_sql)
922
+ end
923
+ end
924
+
925
+ # Split the schema information from the table
926
+ def schema_and_table(table_name)
927
+ schema_utility_dataset.schema_and_table(table_name)
928
+ end
929
+
930
+ # Return true if the given column schema represents an autoincrementing primary key.
931
+ def schema_autoincrementing_primary_key?(schema)
932
+ !!schema[:primary_key]
933
+ end
934
+
935
+ # Match the database's column type to a ruby type via a
936
+ # regular expression. The following ruby types are supported:
937
+ # integer, string, date, datetime, boolean, and float.
938
+ def schema_column_type(db_type)
939
+ case db_type
940
+ when /\Ainterval\z/io
941
+ :interval
942
+ when /\A(character( varying)?|n?(var)?char|n?text)/io
943
+ :string
944
+ when /\A(int(eger)?|(big|small|tiny)int)/io
945
+ :integer
946
+ when /\Adate\z/io
947
+ :date
948
+ when /\A((small)?datetime|timestamp( with(out)? time zone)?)\z/io
949
+ :datetime
950
+ when /\Atime( with(out)? time zone)?\z/io
951
+ :time
952
+ when /\A(boolean|bit)\z/io
953
+ :boolean
954
+ when /\A(real|float|double( precision)?)\z/io
955
+ :float
956
+ when /\A(((numeric|decimal)(\(\d+,\d+\))?)|(small)?money)\z/io
957
+ :decimal
958
+ when /bytea|blob|image|(var)?binary/io
959
+ :blob
960
+ when /\Aenum/
961
+ :enum
962
+ end
963
+ end
964
+
965
+ # The dataset to use for proxying certain schema methods.
966
+ def schema_utility_dataset
967
+ @schema_utility_dataset ||= dataset
968
+ end
969
+
970
+ # Return the options for the given server by merging the generic
971
+ # options for all server with the specific options for the given
972
+ # server specified in the :servers option.
973
+ def server_opts(server)
974
+ opts = if @opts[:servers] && server_options = @opts[:servers][server]
975
+ case server_options
976
+ when Hash
977
+ @opts.merge(server_options)
978
+ when Proc
979
+ @opts.merge(server_options.call(self))
980
+ else
981
+ raise Error, 'Server opts should be a hash or proc'
982
+ end
983
+ else
984
+ @opts.dup
985
+ end
986
+ opts.delete(:servers)
987
+ opts
988
+ end
989
+
990
+ # Raise a database error unless the exception is an Rollback.
991
+ def transaction_error(e)
992
+ raise_error(e, :classes=>database_error_classes) unless e.is_a?(Rollback)
993
+ end
994
+
995
+ # Typecast the value to an SQL::Blob
996
+ def typecast_value_blob(value)
997
+ value.is_a?(Sequel::SQL::Blob) ? value : Sequel::SQL::Blob.new(value)
998
+ end
999
+
1000
+ # Typecast the value to true, false, or nil
1001
+ def typecast_value_boolean(value)
1002
+ case value
1003
+ when false, 0, "0", /\Af(alse)?\z/i
1004
+ false
1005
+ else
1006
+ blank_object?(value) ? nil : true
1007
+ end
1008
+ end
1009
+
1010
+ # Typecast the value to a Date
1011
+ def typecast_value_date(value)
1012
+ case value
1013
+ when Date
1014
+ value
1015
+ when DateTime, Time
1016
+ Date.new(value.year, value.month, value.day)
1017
+ when String
1018
+ Sequel.string_to_date(value)
1019
+ when Hash
1020
+ Date.new(*[:year, :month, :day].map{|x| (value[x] || value[x.to_s]).to_i})
1021
+ else
1022
+ raise InvalidValue, "invalid value for Date: #{value.inspect}"
1023
+ end
1024
+ end
1025
+
1026
+ # Typecast the value to a DateTime or Time depending on Sequel.datetime_class
1027
+ def typecast_value_datetime(value)
1028
+ raise(Sequel::InvalidValue, "invalid value for Datetime: #{value.inspect}") unless [DateTime, Date, Time, String, Hash].any?{|c| value.is_a?(c)}
1029
+ klass = Sequel.datetime_class
1030
+ if value.is_a?(Hash)
1031
+ klass.send(klass == Time ? :mktime : :new, *[:year, :month, :day, :hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
1032
+ else
1033
+ Sequel.typecast_to_application_timestamp(value)
1034
+ end
1035
+ end
1036
+
1037
+ # Typecast the value to a BigDecimal
1038
+ def typecast_value_decimal(value)
1039
+ case value
1040
+ when BigDecimal
1041
+ value
1042
+ when String, Numeric
1043
+ BigDecimal.new(value.to_s)
1044
+ else
1045
+ raise InvalidValue, "invalid value for BigDecimal: #{value.inspect}"
1046
+ end
1047
+ end
1048
+
1049
+ # Typecast the value to a Float
1050
+ def typecast_value_float(value)
1051
+ Float(value)
1052
+ end
1053
+
1054
+ # Typecast the value to an Integer
1055
+ def typecast_value_integer(value)
1056
+ Integer(value)
1057
+ end
1058
+
1059
+ # Typecast the value to a String
1060
+ def typecast_value_string(value)
1061
+ value.to_s
1062
+ end
1063
+
1064
+ # Typecast the value to a Time
1065
+ def typecast_value_time(value)
1066
+ case value
1067
+ when Time
1068
+ value
1069
+ when String
1070
+ Sequel.string_to_time(value)
1071
+ when Hash
1072
+ t = Time.now
1073
+ Time.mktime(t.year, t.month, t.day, *[:hour, :minute, :second].map{|x| (value[x] || value[x.to_s]).to_i})
1074
+ else
1075
+ raise Sequel::InvalidValue, "invalid value for Time: #{value.inspect}"
1076
+ end
1077
+ end
1078
+ end
1079
+ end