epugh-sequel 0.0.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/README.rdoc +652 -0
- data/VERSION.yml +4 -0
- data/bin/sequel +104 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +85 -0
- data/lib/sequel/adapters/db2.rb +132 -0
- data/lib/sequel/adapters/dbi.rb +101 -0
- data/lib/sequel/adapters/do.rb +197 -0
- data/lib/sequel/adapters/do/mysql.rb +38 -0
- data/lib/sequel/adapters/do/postgres.rb +92 -0
- data/lib/sequel/adapters/do/sqlite.rb +31 -0
- data/lib/sequel/adapters/firebird.rb +307 -0
- data/lib/sequel/adapters/informix.rb +75 -0
- data/lib/sequel/adapters/jdbc.rb +485 -0
- data/lib/sequel/adapters/jdbc/h2.rb +62 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
- data/lib/sequel/adapters/mysql.rb +370 -0
- data/lib/sequel/adapters/odbc.rb +184 -0
- data/lib/sequel/adapters/openbase.rb +57 -0
- data/lib/sequel/adapters/oracle.rb +140 -0
- data/lib/sequel/adapters/postgres.rb +453 -0
- data/lib/sequel/adapters/shared/mssql.rb +93 -0
- data/lib/sequel/adapters/shared/mysql.rb +341 -0
- data/lib/sequel/adapters/shared/oracle.rb +62 -0
- data/lib/sequel/adapters/shared/postgres.rb +743 -0
- data/lib/sequel/adapters/shared/progress.rb +34 -0
- data/lib/sequel/adapters/shared/sqlite.rb +263 -0
- data/lib/sequel/adapters/sqlite.rb +243 -0
- data/lib/sequel/adapters/utils/date_format.rb +21 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
- data/lib/sequel/adapters/utils/unsupported.rb +62 -0
- data/lib/sequel/connection_pool.rb +258 -0
- data/lib/sequel/core.rb +204 -0
- data/lib/sequel/core_sql.rb +185 -0
- data/lib/sequel/database.rb +687 -0
- data/lib/sequel/database/schema_generator.rb +324 -0
- data/lib/sequel/database/schema_methods.rb +164 -0
- data/lib/sequel/database/schema_sql.rb +324 -0
- data/lib/sequel/dataset.rb +422 -0
- data/lib/sequel/dataset/convenience.rb +237 -0
- data/lib/sequel/dataset/prepared_statements.rb +220 -0
- data/lib/sequel/dataset/sql.rb +1105 -0
- data/lib/sequel/deprecated.rb +529 -0
- data/lib/sequel/exceptions.rb +44 -0
- data/lib/sequel/extensions/blank.rb +42 -0
- data/lib/sequel/extensions/inflector.rb +288 -0
- data/lib/sequel/extensions/pagination.rb +96 -0
- data/lib/sequel/extensions/pretty_table.rb +78 -0
- data/lib/sequel/extensions/query.rb +48 -0
- data/lib/sequel/extensions/string_date_time.rb +47 -0
- data/lib/sequel/metaprogramming.rb +44 -0
- data/lib/sequel/migration.rb +212 -0
- data/lib/sequel/model.rb +142 -0
- data/lib/sequel/model/association_reflection.rb +263 -0
- data/lib/sequel/model/associations.rb +1024 -0
- data/lib/sequel/model/base.rb +911 -0
- data/lib/sequel/model/deprecated.rb +188 -0
- data/lib/sequel/model/deprecated_hooks.rb +103 -0
- data/lib/sequel/model/deprecated_inflector.rb +335 -0
- data/lib/sequel/model/deprecated_validations.rb +384 -0
- data/lib/sequel/model/errors.rb +37 -0
- data/lib/sequel/model/exceptions.rb +7 -0
- data/lib/sequel/model/inflections.rb +230 -0
- data/lib/sequel/model/plugins.rb +74 -0
- data/lib/sequel/object_graph.rb +230 -0
- data/lib/sequel/plugins/caching.rb +122 -0
- data/lib/sequel/plugins/hook_class_methods.rb +122 -0
- data/lib/sequel/plugins/schema.rb +53 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
- data/lib/sequel/plugins/validation_class_methods.rb +373 -0
- data/lib/sequel/sql.rb +854 -0
- data/lib/sequel/version.rb +11 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/ado_spec.rb +46 -0
- data/spec/adapters/firebird_spec.rb +376 -0
- data/spec/adapters/informix_spec.rb +96 -0
- data/spec/adapters/mysql_spec.rb +875 -0
- data/spec/adapters/oracle_spec.rb +272 -0
- data/spec/adapters/postgres_spec.rb +692 -0
- data/spec/adapters/spec_helper.rb +10 -0
- data/spec/adapters/sqlite_spec.rb +550 -0
- data/spec/core/connection_pool_spec.rb +526 -0
- data/spec/core/core_ext_spec.rb +156 -0
- data/spec/core/core_sql_spec.rb +528 -0
- data/spec/core/database_spec.rb +1214 -0
- data/spec/core/dataset_spec.rb +3513 -0
- data/spec/core/expression_filters_spec.rb +363 -0
- data/spec/core/migration_spec.rb +261 -0
- data/spec/core/object_graph_spec.rb +280 -0
- data/spec/core/pretty_table_spec.rb +58 -0
- data/spec/core/schema_generator_spec.rb +167 -0
- data/spec/core/schema_spec.rb +778 -0
- data/spec/core/spec_helper.rb +82 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/caching_spec.rb +201 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/inflector_spec.rb +122 -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/schema_spec.rb +111 -0
- data/spec/extensions/single_table_inheritance_spec.rb +53 -0
- data/spec/extensions/spec_helper.rb +90 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/validation_class_methods_spec.rb +1054 -0
- data/spec/integration/dataset_test.rb +160 -0
- data/spec/integration/eager_loader_test.rb +683 -0
- data/spec/integration/prepared_statement_test.rb +130 -0
- data/spec/integration/schema_test.rb +183 -0
- data/spec/integration/spec_helper.rb +75 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +93 -0
- data/spec/model/associations_spec.rb +1780 -0
- data/spec/model/base_spec.rb +494 -0
- data/spec/model/caching_spec.rb +217 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1165 -0
- data/spec/model/hooks_spec.rb +472 -0
- data/spec/model/inflector_spec.rb +126 -0
- data/spec/model/model_spec.rb +588 -0
- data/spec/model/plugins_spec.rb +142 -0
- data/spec/model/record_spec.rb +1243 -0
- data/spec/model/schema_spec.rb +92 -0
- data/spec/model/spec_helper.rb +124 -0
- data/spec/model/validations_spec.rb +1080 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec.opts +0 -0
- data/spec/spec_config.rb.example +10 -0
- metadata +202 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
class Dataset
|
|
3
|
+
COMMA_SEPARATOR = ', '.freeze
|
|
4
|
+
COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, LiteralString.new('*'.freeze)).as(:count)
|
|
5
|
+
|
|
6
|
+
# Returns the first record matching the conditions.
|
|
7
|
+
def [](*conditions)
|
|
8
|
+
Deprecation.deprecate('Using an Integer argument to Dataset#[] is deprecated and will raise an error in a future version. Use Dataset#first.') if conditions.length == 1 and conditions.is_a?(Integer)
|
|
9
|
+
first(*conditions)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Update all records matching the conditions
|
|
13
|
+
# with the values specified.
|
|
14
|
+
def []=(conditions, values)
|
|
15
|
+
filter(conditions).update(values)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Returns the average value for the given column.
|
|
19
|
+
def avg(column)
|
|
20
|
+
get{|o| o.avg(column)}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Returns true if no records exists in the dataset
|
|
24
|
+
def empty?
|
|
25
|
+
get(1).nil?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the first record in the dataset. If a numeric argument is
|
|
29
|
+
# given, it is interpreted as a limit, and then returns all
|
|
30
|
+
# matching records up to that limit. If no argument is passed,
|
|
31
|
+
# it returns the first matching record. If any other type of
|
|
32
|
+
# argument(s) is passed, it is given to filter and the
|
|
33
|
+
# first matching record is returned. If a block is given, it is used
|
|
34
|
+
# to filter the dataset before returning anything.
|
|
35
|
+
#
|
|
36
|
+
# Examples:
|
|
37
|
+
#
|
|
38
|
+
# ds.first => {:id=>7}
|
|
39
|
+
# ds.first(2) => [{:id=>6}, {:id=>4}]
|
|
40
|
+
# ds.order(:id).first(2) => [{:id=>1}, {:id=>2}]
|
|
41
|
+
# ds.first(:id=>2) => {:id=>2}
|
|
42
|
+
# ds.first("id = 3") => {:id=>3}
|
|
43
|
+
# ds.first("id = ?", 4) => {:id=>4}
|
|
44
|
+
# ds.first{|o| o.id > 2} => {:id=>5}
|
|
45
|
+
# ds.order(:id).first{|o| o.id > 2} => {:id=>3}
|
|
46
|
+
# ds.first{|o| o.id > 2} => {:id=>5}
|
|
47
|
+
# ds.first("id > ?", 4){|o| o.id < 6} => {:id=>5}
|
|
48
|
+
# ds.order(:id).first(2){|o| o.id < 2} => [{:id=>1}]
|
|
49
|
+
def first(*args, &block)
|
|
50
|
+
ds = block ? filter(&block) : self
|
|
51
|
+
|
|
52
|
+
if args.empty?
|
|
53
|
+
ds.single_record
|
|
54
|
+
else
|
|
55
|
+
args = (args.size == 1) ? args.first : args
|
|
56
|
+
if Integer === args
|
|
57
|
+
ds.limit(args).all
|
|
58
|
+
else
|
|
59
|
+
ds.filter(args).single_record
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Return the column value for the first matching record in the dataset.
|
|
65
|
+
def get(column=nil, &block)
|
|
66
|
+
raise(Error, 'must provide argument or block to Dataset#get, not both') if column && block
|
|
67
|
+
(column ? select(column) : select(&block)).single_value
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns a dataset grouped by the given column with count by group.
|
|
71
|
+
def group_and_count(*columns)
|
|
72
|
+
group(*columns).select(*(columns + [COUNT_OF_ALL_AS_COUNT])).order(:count)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns the interval between minimum and maximum values for the given
|
|
76
|
+
# column.
|
|
77
|
+
def interval(column)
|
|
78
|
+
get{|o| o.max(column) - o.min(column)}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Reverses the order and then runs first. Note that this
|
|
82
|
+
# will not necessarily give you the last record in the dataset,
|
|
83
|
+
# unless you have an unambiguous order. If there is not
|
|
84
|
+
# currently an order for this dataset, raises an Error.
|
|
85
|
+
def last(*args, &block)
|
|
86
|
+
raise(Error, 'No order specified') unless @opts[:order]
|
|
87
|
+
reverse.first(*args, &block)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Maps column values for each record in the dataset (if a column name is
|
|
91
|
+
# given), or performs the stock mapping functionality of Enumerable.
|
|
92
|
+
def map(column_name = nil, &block)
|
|
93
|
+
if column_name
|
|
94
|
+
super(){|r| r[column_name]}
|
|
95
|
+
else
|
|
96
|
+
super(&block)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Returns the maximum value for the given column.
|
|
101
|
+
def max(column)
|
|
102
|
+
get{|o| o.max(column)}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Returns the minimum value for the given column.
|
|
106
|
+
def min(column)
|
|
107
|
+
get{|o| o.min(column)}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Inserts multiple records into the associated table. This method can be
|
|
111
|
+
# to efficiently insert a large amounts of records into a table. Inserts
|
|
112
|
+
# are automatically wrapped in a transaction.
|
|
113
|
+
#
|
|
114
|
+
# This method should be called with a columns array and an array of value arrays:
|
|
115
|
+
#
|
|
116
|
+
# dataset.multi_insert([:x, :y], [[1, 2], [3, 4]])
|
|
117
|
+
#
|
|
118
|
+
# This method can also be called with an array of hashes:
|
|
119
|
+
#
|
|
120
|
+
# dataset.multi_insert({:x => 1}, {:x => 2})
|
|
121
|
+
#
|
|
122
|
+
# Be aware that all hashes should have the same keys if you use this calling method,
|
|
123
|
+
# otherwise some columns could be missed or set to null instead of to default
|
|
124
|
+
# values.
|
|
125
|
+
#
|
|
126
|
+
# The method also accepts a :slice or :commit_every option that specifies
|
|
127
|
+
# the number of records to insert per transaction. This is useful especially
|
|
128
|
+
# when inserting a large number of records, e.g.:
|
|
129
|
+
#
|
|
130
|
+
# # this will commit every 50 records
|
|
131
|
+
# dataset.multi_insert(lots_of_records, :slice => 50)
|
|
132
|
+
def multi_insert(*args)
|
|
133
|
+
if args.empty?
|
|
134
|
+
return
|
|
135
|
+
elsif args[0].is_a?(Array) && args[1].is_a?(Array)
|
|
136
|
+
columns, values, opts = *args
|
|
137
|
+
elsif args[0].is_a?(Array) && args[1].is_a?(Dataset)
|
|
138
|
+
table = @opts[:from].first
|
|
139
|
+
columns, dataset = *args
|
|
140
|
+
sql = "INSERT INTO #{quote_identifier(table)} (#{identifier_list(columns)}) VALUES #{literal(dataset)}"
|
|
141
|
+
return @db.transaction{execute_dui(sql)}
|
|
142
|
+
else
|
|
143
|
+
# we assume that an array of hashes is given
|
|
144
|
+
hashes, opts = *args
|
|
145
|
+
return if hashes.empty?
|
|
146
|
+
columns = hashes.first.keys
|
|
147
|
+
# convert the hashes into arrays
|
|
148
|
+
values = hashes.map {|h| columns.map {|c| h[c]}}
|
|
149
|
+
end
|
|
150
|
+
# make sure there's work to do
|
|
151
|
+
return if columns.empty? || values.empty?
|
|
152
|
+
|
|
153
|
+
slice_size = opts && (opts[:commit_every] || opts[:slice])
|
|
154
|
+
|
|
155
|
+
if slice_size
|
|
156
|
+
values.each_slice(slice_size) do |slice|
|
|
157
|
+
statements = multi_insert_sql(columns, slice)
|
|
158
|
+
@db.transaction(opts){statements.each{|st| execute_dui(st)}}
|
|
159
|
+
end
|
|
160
|
+
else
|
|
161
|
+
statements = multi_insert_sql(columns, values)
|
|
162
|
+
@db.transaction{statements.each{|st| execute_dui(st)}}
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns a Range object made from the minimum and maximum values for the
|
|
167
|
+
# given column.
|
|
168
|
+
def range(column)
|
|
169
|
+
if r = select{|o| [o.min(column).as(:v1), o.max(column).as(:v2)]}.first
|
|
170
|
+
(r[:v1]..r[:v2])
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Returns the first record in the dataset.
|
|
175
|
+
def single_record(opts = (defarg=true;nil))
|
|
176
|
+
Deprecation.deprecate("Calling Dataset#single_record with an argument is deprecated and will raise an error in a future version. Use dataset.clone(opts).single_record.") unless defarg
|
|
177
|
+
ds = clone(:limit=>1)
|
|
178
|
+
opts = opts.merge(:limit=>1) if opts and opts[:limit]
|
|
179
|
+
defarg ? ds.each{|r| return r} : ds.each(opts){|r| return r}
|
|
180
|
+
nil
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Returns the first value of the first record in the dataset.
|
|
184
|
+
# Returns nil if dataset is empty.
|
|
185
|
+
def single_value(opts = (defarg=true;nil))
|
|
186
|
+
Deprecation.deprecate("Calling Dataset#single_value with an argument is deprecated and will raise an error in a future version. Use dataset.clone(opts).single_value.") unless defarg
|
|
187
|
+
ds = naked.clone(:graph=>false)
|
|
188
|
+
if r = (defarg ? ds.single_record : ds.single_record(opts))
|
|
189
|
+
r.values.first
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Returns the sum for the given column.
|
|
194
|
+
def sum(column)
|
|
195
|
+
get{|o| o.sum(column)}
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Returns true if the table exists. Will raise an error
|
|
199
|
+
# if the dataset has fixed SQL or selects from another dataset
|
|
200
|
+
# or more than one table.
|
|
201
|
+
def table_exists?
|
|
202
|
+
raise(Sequel::Error, "this dataset has fixed SQL") if @opts[:sql]
|
|
203
|
+
raise(Sequel::Error, "this dataset selects from multiple sources") if @opts[:from].size != 1
|
|
204
|
+
t = @opts[:from].first
|
|
205
|
+
raise(Sequel::Error, "this dataset selects from a sub query") if t.is_a?(Dataset)
|
|
206
|
+
@db.table_exists?(t)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Returns a string in CSV format containing the dataset records. By
|
|
210
|
+
# default the CSV representation includes the column titles in the
|
|
211
|
+
# first line. You can turn that off by passing false as the
|
|
212
|
+
# include_column_titles argument.
|
|
213
|
+
#
|
|
214
|
+
# This does not use a CSV library or handle quoting of values in
|
|
215
|
+
# any way. If any values in any of the rows could include commas or line
|
|
216
|
+
# endings, you probably shouldn't use this.
|
|
217
|
+
def to_csv(include_column_titles = true)
|
|
218
|
+
n = naked
|
|
219
|
+
cols = n.columns
|
|
220
|
+
csv = ''
|
|
221
|
+
csv << "#{cols.join(COMMA_SEPARATOR)}\r\n" if include_column_titles
|
|
222
|
+
n.each{|r| csv << "#{cols.collect{|c| r[c]}.join(COMMA_SEPARATOR)}\r\n"}
|
|
223
|
+
csv
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Returns a hash with one column used as key and another used as value.
|
|
227
|
+
# If rows have duplicate values for the key column, the latter row(s)
|
|
228
|
+
# will overwrite the value of the previous row(s). If the value_column
|
|
229
|
+
# is not given or nil, uses the entire hash as the value.
|
|
230
|
+
def to_hash(key_column, value_column = nil)
|
|
231
|
+
inject({}) do |m, r|
|
|
232
|
+
m[r[key_column]] = value_column ? r[value_column] : r
|
|
233
|
+
m
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
class Dataset
|
|
3
|
+
PREPARED_ARG_PLACEHOLDER = LiteralString.new('?').freeze
|
|
4
|
+
|
|
5
|
+
# Default implementation of the argument mapper to allow
|
|
6
|
+
# native database support for bind variables and prepared
|
|
7
|
+
# statements (as opposed to the emulated ones used by default).
|
|
8
|
+
module ArgumentMapper
|
|
9
|
+
SQL_QUERY_TYPE = Hash.new{|h,k| h[k] = k}
|
|
10
|
+
SQL_QUERY_TYPE[:first] = SQL_QUERY_TYPE[:all] = :select
|
|
11
|
+
|
|
12
|
+
# The name of the prepared statement, if any.
|
|
13
|
+
attr_accessor :prepared_statement_name
|
|
14
|
+
|
|
15
|
+
# The bind arguments to use for running this prepared statement
|
|
16
|
+
attr_accessor :bind_arguments
|
|
17
|
+
|
|
18
|
+
# Set the bind arguments based on the hash and call super.
|
|
19
|
+
def call(hash, &block)
|
|
20
|
+
ds = clone
|
|
21
|
+
ds.prepared_sql
|
|
22
|
+
ds.bind_arguments = ds.map_to_prepared_args(hash)
|
|
23
|
+
ds.prepared_args = hash
|
|
24
|
+
ds.run(&block)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Override the given *_sql method based on the type, and
|
|
28
|
+
# cache the result of the sql.
|
|
29
|
+
def prepared_sql
|
|
30
|
+
return @prepared_sql if @prepared_sql
|
|
31
|
+
@prepared_args ||= []
|
|
32
|
+
@prepared_sql = super
|
|
33
|
+
meta_def("#{sql_query_type}_sql"){|*args| prepared_sql}
|
|
34
|
+
@prepared_sql
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# The type of query (:select, :insert, :delete, :update).
|
|
40
|
+
def sql_query_type
|
|
41
|
+
SQL_QUERY_TYPE[@prepared_type]
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Backbone of the prepared statement support. Grafts bind variable
|
|
46
|
+
# support into datasets by hijacking #literal and using placeholders.
|
|
47
|
+
# By default, emulates prepared statements and bind variables by
|
|
48
|
+
# taking the hash of bind variables and directly substituting them
|
|
49
|
+
# into the query, which works on all databases, as it is no different
|
|
50
|
+
# from using the dataset without bind variables.
|
|
51
|
+
module PreparedStatementMethods
|
|
52
|
+
PLACEHOLDER_RE = /\A\$(.*)\z/
|
|
53
|
+
|
|
54
|
+
# The type of prepared statement, should be one of :select, :first,
|
|
55
|
+
# :insert, :update, or :delete
|
|
56
|
+
attr_accessor :prepared_type
|
|
57
|
+
|
|
58
|
+
# The bind variable hash to use when substituting
|
|
59
|
+
attr_accessor :prepared_args
|
|
60
|
+
|
|
61
|
+
# The argument to supply to insert and update, which may use
|
|
62
|
+
# placeholders specified by prepared_args
|
|
63
|
+
attr_accessor :prepared_modify_values
|
|
64
|
+
|
|
65
|
+
# Sets the prepared_args to the given hash and runs the
|
|
66
|
+
# prepared statement.
|
|
67
|
+
def call(hash, &block)
|
|
68
|
+
ds = clone
|
|
69
|
+
ds.prepared_args = hash
|
|
70
|
+
ds.run(&block)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Returns the SQL for the prepared statement, depending on
|
|
74
|
+
# the type of the statement and the prepared_modify_values.
|
|
75
|
+
def prepared_sql
|
|
76
|
+
case @prepared_type
|
|
77
|
+
when :select, :all
|
|
78
|
+
select_sql
|
|
79
|
+
when :first
|
|
80
|
+
clone(:limit=>1).select_sql
|
|
81
|
+
when :insert
|
|
82
|
+
insert_sql(@prepared_modify_values)
|
|
83
|
+
when :update
|
|
84
|
+
update_sql(@prepared_modify_values)
|
|
85
|
+
when :delete
|
|
86
|
+
delete_sql
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Changes the values of symbols if they start with $ and
|
|
91
|
+
# prepared_args is present. If so, they are considered placeholders,
|
|
92
|
+
# and they are substituted using prepared_arg.
|
|
93
|
+
def literal(v)
|
|
94
|
+
case v
|
|
95
|
+
when Symbol
|
|
96
|
+
if match = PLACEHOLDER_RE.match(v.to_s) and @prepared_args
|
|
97
|
+
super(prepared_arg(match[1].to_sym))
|
|
98
|
+
else
|
|
99
|
+
super
|
|
100
|
+
end
|
|
101
|
+
else
|
|
102
|
+
super
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Programmer friendly string showing this is a prepared statement,
|
|
107
|
+
# with the prepared SQL it represents (which in general won't have
|
|
108
|
+
# substituted variables).
|
|
109
|
+
def inspect
|
|
110
|
+
"<#{self.class.name}/PreparedStatement #{prepared_sql.inspect}>"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
protected
|
|
114
|
+
|
|
115
|
+
# Run the method based on the type of prepared statement, with
|
|
116
|
+
# :select running #all to get all of the rows, and the other
|
|
117
|
+
# types running the method with the same name as the type.
|
|
118
|
+
def run(&block)
|
|
119
|
+
case @prepared_type
|
|
120
|
+
when :select, :all
|
|
121
|
+
all(&block)
|
|
122
|
+
when :first
|
|
123
|
+
first
|
|
124
|
+
when :insert
|
|
125
|
+
insert(@prepared_modify_values)
|
|
126
|
+
when :update
|
|
127
|
+
update(@prepared_modify_values)
|
|
128
|
+
when :delete
|
|
129
|
+
delete
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
# Returns the value of the prepared_args hash for the given key.
|
|
136
|
+
def prepared_arg(k)
|
|
137
|
+
@prepared_args[k]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Use a clone of the dataset extended with prepared statement
|
|
141
|
+
# support and using the same argument hash so that you can use
|
|
142
|
+
# bind variables/prepared arguments in subselects.
|
|
143
|
+
def subselect_sql(ds)
|
|
144
|
+
ps = ds.prepare(:select)
|
|
145
|
+
ps.prepared_args = prepared_args
|
|
146
|
+
ps.prepared_sql
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Default implementation for an argument mapper that uses
|
|
151
|
+
# unnumbered SQL placeholder arguments. Keeps track of which
|
|
152
|
+
# arguments have been used, and allows arguments to
|
|
153
|
+
# be used more than once.
|
|
154
|
+
module UnnumberedArgumentMapper
|
|
155
|
+
include ArgumentMapper
|
|
156
|
+
|
|
157
|
+
protected
|
|
158
|
+
|
|
159
|
+
# Returns a single output array mapping the values of the input hash.
|
|
160
|
+
# Keys in the input hash that are used more than once in the query
|
|
161
|
+
# have multiple entries in the output array.
|
|
162
|
+
def map_to_prepared_args(hash)
|
|
163
|
+
@prepared_args.map{|v| hash[v]}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
# Associates the argument with name k with the next position in
|
|
169
|
+
# the output array.
|
|
170
|
+
def prepared_arg(k)
|
|
171
|
+
@prepared_args << k
|
|
172
|
+
prepared_arg_placeholder
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# For the given type (:select, :insert, :update, or :delete),
|
|
177
|
+
# run the sql with the bind variables
|
|
178
|
+
# specified in the hash. values is a hash of passed to
|
|
179
|
+
# insert or update (if one of those types is used),
|
|
180
|
+
# which may contain placeholders.
|
|
181
|
+
def call(type, bind_variables={}, values=nil)
|
|
182
|
+
prepare(type, nil, values).call(bind_variables)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Prepare an SQL statement for later execution. This returns
|
|
186
|
+
# a clone of the dataset extended with PreparedStatementMethods,
|
|
187
|
+
# on which you can call call with the hash of bind variables to
|
|
188
|
+
# do substitution. The prepared statement is also stored in
|
|
189
|
+
# the associated database. The following usage is identical:
|
|
190
|
+
#
|
|
191
|
+
# ps = prepare(:select, :select_by_name)
|
|
192
|
+
# ps.call(:name=>'Blah')
|
|
193
|
+
# db.call(:select_by_name, :name=>'Blah')
|
|
194
|
+
def prepare(type, name=nil, values=nil)
|
|
195
|
+
ps = to_prepared_statement(type, values)
|
|
196
|
+
db.prepared_statements[name] = ps if name
|
|
197
|
+
ps
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
protected
|
|
201
|
+
|
|
202
|
+
# Return a cloned copy of the current dataset extended with
|
|
203
|
+
# PreparedStatementMethods, setting the type and modify values.
|
|
204
|
+
def to_prepared_statement(type, values=nil)
|
|
205
|
+
ps = clone
|
|
206
|
+
ps.extend(PreparedStatementMethods)
|
|
207
|
+
ps.prepared_type = type
|
|
208
|
+
ps.prepared_modify_values = values
|
|
209
|
+
ps
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
private
|
|
213
|
+
|
|
214
|
+
# The argument placeholder. Most databases used unnumbered
|
|
215
|
+
# arguments with question marks, so that is the default.
|
|
216
|
+
def prepared_arg_placeholder
|
|
217
|
+
PREPARED_ARG_PLACEHOLDER
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|