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.
- data/CHANGELOG +3134 -0
- data/COPYING +19 -0
- data/README.rdoc +723 -0
- data/Rakefile +193 -0
- data/bin/sequel +196 -0
- data/doc/advanced_associations.rdoc +644 -0
- data/doc/cheat_sheet.rdoc +218 -0
- data/doc/dataset_basics.rdoc +106 -0
- data/doc/dataset_filtering.rdoc +158 -0
- data/doc/opening_databases.rdoc +296 -0
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/reflection.rdoc +84 -0
- data/doc/release_notes/1.0.txt +38 -0
- data/doc/release_notes/1.1.txt +143 -0
- data/doc/release_notes/1.3.txt +101 -0
- data/doc/release_notes/1.4.0.txt +53 -0
- data/doc/release_notes/1.5.0.txt +155 -0
- data/doc/release_notes/2.0.0.txt +298 -0
- data/doc/release_notes/2.1.0.txt +271 -0
- data/doc/release_notes/2.10.0.txt +328 -0
- data/doc/release_notes/2.11.0.txt +215 -0
- data/doc/release_notes/2.12.0.txt +534 -0
- data/doc/release_notes/2.2.0.txt +253 -0
- data/doc/release_notes/2.3.0.txt +88 -0
- data/doc/release_notes/2.4.0.txt +106 -0
- data/doc/release_notes/2.5.0.txt +137 -0
- data/doc/release_notes/2.6.0.txt +157 -0
- data/doc/release_notes/2.7.0.txt +166 -0
- data/doc/release_notes/2.8.0.txt +171 -0
- data/doc/release_notes/2.9.0.txt +97 -0
- data/doc/release_notes/3.0.0.txt +221 -0
- data/doc/release_notes/3.1.0.txt +406 -0
- data/doc/release_notes/3.10.0.txt +286 -0
- data/doc/release_notes/3.2.0.txt +268 -0
- data/doc/release_notes/3.3.0.txt +192 -0
- data/doc/release_notes/3.4.0.txt +325 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/release_notes/3.7.0.txt +179 -0
- data/doc/release_notes/3.8.0.txt +151 -0
- data/doc/release_notes/3.9.0.txt +233 -0
- data/doc/schema.rdoc +36 -0
- data/doc/sharding.rdoc +113 -0
- data/doc/virtual_rows.rdoc +205 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +90 -0
- data/lib/sequel/adapters/ado/mssql.rb +30 -0
- data/lib/sequel/adapters/amalgalite.rb +176 -0
- data/lib/sequel/adapters/db2.rb +139 -0
- data/lib/sequel/adapters/dbi.rb +113 -0
- data/lib/sequel/adapters/do.rb +188 -0
- data/lib/sequel/adapters/do/mysql.rb +49 -0
- data/lib/sequel/adapters/do/postgres.rb +91 -0
- data/lib/sequel/adapters/do/sqlite.rb +40 -0
- data/lib/sequel/adapters/firebird.rb +283 -0
- data/lib/sequel/adapters/informix.rb +77 -0
- data/lib/sequel/adapters/jdbc.rb +587 -0
- data/lib/sequel/adapters/jdbc/as400.rb +58 -0
- data/lib/sequel/adapters/jdbc/h2.rb +133 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
- data/lib/sequel/adapters/mysql.rb +421 -0
- data/lib/sequel/adapters/odbc.rb +143 -0
- data/lib/sequel/adapters/odbc/mssql.rb +42 -0
- data/lib/sequel/adapters/openbase.rb +64 -0
- data/lib/sequel/adapters/oracle.rb +131 -0
- data/lib/sequel/adapters/postgres.rb +504 -0
- data/lib/sequel/adapters/shared/mssql.rb +490 -0
- data/lib/sequel/adapters/shared/mysql.rb +498 -0
- data/lib/sequel/adapters/shared/oracle.rb +195 -0
- data/lib/sequel/adapters/shared/postgres.rb +830 -0
- data/lib/sequel/adapters/shared/progress.rb +44 -0
- data/lib/sequel/adapters/shared/sqlite.rb +389 -0
- data/lib/sequel/adapters/sqlite.rb +224 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
- data/lib/sequel/connection_pool.rb +99 -0
- data/lib/sequel/connection_pool/sharded_single.rb +84 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
- data/lib/sequel/connection_pool/single.rb +29 -0
- data/lib/sequel/connection_pool/threaded.rb +150 -0
- data/lib/sequel/core.rb +293 -0
- data/lib/sequel/core_sql.rb +241 -0
- data/lib/sequel/database.rb +1079 -0
- data/lib/sequel/database/schema_generator.rb +327 -0
- data/lib/sequel/database/schema_methods.rb +203 -0
- data/lib/sequel/database/schema_sql.rb +320 -0
- data/lib/sequel/dataset.rb +32 -0
- data/lib/sequel/dataset/actions.rb +441 -0
- data/lib/sequel/dataset/features.rb +86 -0
- data/lib/sequel/dataset/graph.rb +254 -0
- data/lib/sequel/dataset/misc.rb +119 -0
- data/lib/sequel/dataset/mutation.rb +64 -0
- data/lib/sequel/dataset/prepared_statements.rb +227 -0
- data/lib/sequel/dataset/query.rb +709 -0
- data/lib/sequel/dataset/sql.rb +996 -0
- data/lib/sequel/exceptions.rb +51 -0
- data/lib/sequel/extensions/blank.rb +43 -0
- data/lib/sequel/extensions/inflector.rb +242 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/migration.rb +239 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/pagination.rb +100 -0
- data/lib/sequel/extensions/pretty_table.rb +82 -0
- data/lib/sequel/extensions/query.rb +52 -0
- data/lib/sequel/extensions/schema_dumper.rb +271 -0
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +46 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/metaprogramming.rb +9 -0
- data/lib/sequel/model.rb +120 -0
- data/lib/sequel/model/associations.rb +1514 -0
- data/lib/sequel/model/base.rb +1069 -0
- data/lib/sequel/model/default_inflections.rb +45 -0
- data/lib/sequel/model/errors.rb +39 -0
- data/lib/sequel/model/exceptions.rb +21 -0
- data/lib/sequel/model/inflections.rb +162 -0
- data/lib/sequel/model/plugins.rb +70 -0
- data/lib/sequel/plugins/active_model.rb +59 -0
- data/lib/sequel/plugins/association_dependencies.rb +103 -0
- data/lib/sequel/plugins/association_proxies.rb +41 -0
- data/lib/sequel/plugins/boolean_readers.rb +53 -0
- data/lib/sequel/plugins/caching.rb +141 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/composition.rb +138 -0
- data/lib/sequel/plugins/force_encoding.rb +72 -0
- data/lib/sequel/plugins/hook_class_methods.rb +126 -0
- data/lib/sequel/plugins/identity_map.rb +116 -0
- data/lib/sequel/plugins/instance_filters.rb +98 -0
- data/lib/sequel/plugins/instance_hooks.rb +57 -0
- data/lib/sequel/plugins/lazy_attributes.rb +77 -0
- data/lib/sequel/plugins/many_through_many.rb +208 -0
- data/lib/sequel/plugins/nested_attributes.rb +206 -0
- data/lib/sequel/plugins/optimistic_locking.rb +81 -0
- data/lib/sequel/plugins/rcte_tree.rb +281 -0
- data/lib/sequel/plugins/schema.rb +66 -0
- data/lib/sequel/plugins/serialization.rb +166 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
- data/lib/sequel/plugins/timestamps.rb +87 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +72 -0
- data/lib/sequel/plugins/validation_class_methods.rb +405 -0
- data/lib/sequel/plugins/validation_helpers.rb +223 -0
- data/lib/sequel/sql.rb +1020 -0
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +12 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/firebird_spec.rb +407 -0
- data/spec/adapters/informix_spec.rb +97 -0
- data/spec/adapters/mssql_spec.rb +403 -0
- data/spec/adapters/mysql_spec.rb +1019 -0
- data/spec/adapters/oracle_spec.rb +286 -0
- data/spec/adapters/postgres_spec.rb +969 -0
- data/spec/adapters/spec_helper.rb +51 -0
- data/spec/adapters/sqlite_spec.rb +432 -0
- data/spec/core/connection_pool_spec.rb +808 -0
- data/spec/core/core_sql_spec.rb +417 -0
- data/spec/core/database_spec.rb +1662 -0
- data/spec/core/dataset_spec.rb +3827 -0
- data/spec/core/expression_filters_spec.rb +595 -0
- data/spec/core/object_graph_spec.rb +296 -0
- data/spec/core/schema_generator_spec.rb +159 -0
- data/spec/core/schema_spec.rb +830 -0
- data/spec/core/spec_helper.rb +56 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/active_model_spec.rb +76 -0
- data/spec/extensions/association_dependencies_spec.rb +127 -0
- data/spec/extensions/association_proxies_spec.rb +50 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/boolean_readers_spec.rb +92 -0
- data/spec/extensions/caching_spec.rb +250 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/composition_spec.rb +194 -0
- data/spec/extensions/force_encoding_spec.rb +117 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/identity_map_spec.rb +202 -0
- data/spec/extensions/inflector_spec.rb +181 -0
- data/spec/extensions/instance_filters_spec.rb +55 -0
- data/spec/extensions/instance_hooks_spec.rb +133 -0
- data/spec/extensions/lazy_attributes_spec.rb +153 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +884 -0
- data/spec/extensions/migration_spec.rb +332 -0
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +396 -0
- data/spec/extensions/optimistic_locking_spec.rb +100 -0
- data/spec/extensions/pagination_spec.rb +99 -0
- data/spec/extensions/pretty_table_spec.rb +91 -0
- data/spec/extensions/query_spec.rb +85 -0
- data/spec/extensions/rcte_tree_spec.rb +205 -0
- data/spec/extensions/schema_dumper_spec.rb +357 -0
- data/spec/extensions/schema_spec.rb +127 -0
- data/spec/extensions/serialization_spec.rb +209 -0
- data/spec/extensions/single_table_inheritance_spec.rb +96 -0
- data/spec/extensions/spec_helper.rb +91 -0
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/timestamps_spec.rb +150 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +69 -0
- data/spec/extensions/validation_class_methods_spec.rb +984 -0
- data/spec/extensions/validation_helpers_spec.rb +438 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/database_test.rb +26 -0
- data/spec/integration/dataset_test.rb +963 -0
- data/spec/integration/eager_loader_test.rb +734 -0
- data/spec/integration/model_test.rb +130 -0
- data/spec/integration/plugin_test.rb +814 -0
- data/spec/integration/prepared_statement_test.rb +213 -0
- data/spec/integration/schema_test.rb +361 -0
- data/spec/integration/spec_helper.rb +73 -0
- data/spec/integration/timezone_test.rb +55 -0
- data/spec/integration/transaction_test.rb +122 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +175 -0
- data/spec/model/associations_spec.rb +2633 -0
- data/spec/model/base_spec.rb +418 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1391 -0
- data/spec/model/hooks_spec.rb +240 -0
- data/spec/model/inflector_spec.rb +26 -0
- data/spec/model/model_spec.rb +593 -0
- data/spec/model/plugins_spec.rb +236 -0
- data/spec/model/record_spec.rb +1500 -0
- data/spec/model/spec_helper.rb +97 -0
- data/spec/model/validations_spec.rb +153 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec_config.rb.example +10 -0
- 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
|