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.
Files changed (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. 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