sequel 3.5.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/CHANGELOG +108 -0
  2. data/README.rdoc +25 -14
  3. data/Rakefile +20 -1
  4. data/doc/advanced_associations.rdoc +61 -64
  5. data/doc/cheat_sheet.rdoc +16 -7
  6. data/doc/opening_databases.rdoc +3 -3
  7. data/doc/prepared_statements.rdoc +1 -1
  8. data/doc/reflection.rdoc +2 -1
  9. data/doc/release_notes/3.6.0.txt +366 -0
  10. data/doc/schema.rdoc +19 -14
  11. data/lib/sequel/adapters/amalgalite.rb +5 -27
  12. data/lib/sequel/adapters/jdbc.rb +13 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +17 -0
  14. data/lib/sequel/adapters/jdbc/mysql.rb +20 -7
  15. data/lib/sequel/adapters/mysql.rb +4 -3
  16. data/lib/sequel/adapters/oracle.rb +1 -1
  17. data/lib/sequel/adapters/postgres.rb +87 -28
  18. data/lib/sequel/adapters/shared/mssql.rb +47 -6
  19. data/lib/sequel/adapters/shared/mysql.rb +12 -31
  20. data/lib/sequel/adapters/shared/postgres.rb +15 -12
  21. data/lib/sequel/adapters/shared/sqlite.rb +18 -0
  22. data/lib/sequel/adapters/sqlite.rb +1 -16
  23. data/lib/sequel/connection_pool.rb +1 -1
  24. data/lib/sequel/core.rb +1 -1
  25. data/lib/sequel/database.rb +1 -1
  26. data/lib/sequel/database/schema_generator.rb +2 -0
  27. data/lib/sequel/database/schema_sql.rb +1 -1
  28. data/lib/sequel/dataset.rb +5 -179
  29. data/lib/sequel/dataset/actions.rb +123 -0
  30. data/lib/sequel/dataset/convenience.rb +18 -10
  31. data/lib/sequel/dataset/features.rb +65 -0
  32. data/lib/sequel/dataset/prepared_statements.rb +29 -23
  33. data/lib/sequel/dataset/query.rb +429 -0
  34. data/lib/sequel/dataset/sql.rb +67 -435
  35. data/lib/sequel/model/associations.rb +77 -13
  36. data/lib/sequel/model/base.rb +30 -8
  37. data/lib/sequel/model/errors.rb +4 -4
  38. data/lib/sequel/plugins/caching.rb +38 -15
  39. data/lib/sequel/plugins/force_encoding.rb +18 -7
  40. data/lib/sequel/plugins/hook_class_methods.rb +4 -0
  41. data/lib/sequel/plugins/many_through_many.rb +1 -1
  42. data/lib/sequel/plugins/nested_attributes.rb +40 -11
  43. data/lib/sequel/plugins/serialization.rb +17 -3
  44. data/lib/sequel/plugins/validation_helpers.rb +65 -18
  45. data/lib/sequel/sql.rb +23 -1
  46. data/lib/sequel/version.rb +1 -1
  47. data/spec/adapters/mssql_spec.rb +96 -10
  48. data/spec/adapters/mysql_spec.rb +19 -0
  49. data/spec/adapters/postgres_spec.rb +65 -2
  50. data/spec/adapters/sqlite_spec.rb +10 -0
  51. data/spec/core/core_sql_spec.rb +9 -0
  52. data/spec/core/database_spec.rb +8 -4
  53. data/spec/core/dataset_spec.rb +122 -29
  54. data/spec/core/expression_filters_spec.rb +17 -0
  55. data/spec/extensions/caching_spec.rb +43 -3
  56. data/spec/extensions/force_encoding_spec.rb +43 -1
  57. data/spec/extensions/nested_attributes_spec.rb +55 -2
  58. data/spec/extensions/validation_helpers_spec.rb +71 -0
  59. data/spec/integration/associations_test.rb +281 -0
  60. data/spec/integration/dataset_test.rb +383 -9
  61. data/spec/integration/eager_loader_test.rb +0 -65
  62. data/spec/integration/model_test.rb +110 -0
  63. data/spec/integration/plugin_test.rb +306 -0
  64. data/spec/integration/prepared_statement_test.rb +32 -0
  65. data/spec/integration/schema_test.rb +8 -3
  66. data/spec/integration/spec_helper.rb +1 -25
  67. data/spec/model/association_reflection_spec.rb +38 -0
  68. data/spec/model/associations_spec.rb +184 -8
  69. data/spec/model/eager_loading_spec.rb +23 -0
  70. data/spec/model/model_spec.rb +8 -0
  71. data/spec/model/record_spec.rb +84 -1
  72. metadata +9 -2
@@ -1,4 +1,7 @@
1
1
  module Sequel
2
+ Dataset::NON_SQL_OPTIONS << :insert_ignore
3
+ Dataset::NON_SQL_OPTIONS << :on_duplicate_key_update
4
+
2
5
  module MySQL
3
6
  class << self
4
7
  # Set the default options used for CREATE TABLE
@@ -311,37 +314,7 @@ module Sequel
311
314
  # MySQL specific syntax for REPLACE (aka UPSERT, or update if exists,
312
315
  # insert if it doesn't).
313
316
  def replace_sql(*values)
314
- from = source_list(@opts[:from])
315
- if values.empty?
316
- "REPLACE INTO #{from} DEFAULT VALUES"
317
- else
318
- values = values[0] if values.size == 1
319
-
320
- case values
321
- when Array
322
- if values.empty?
323
- "REPLACE INTO #{from} DEFAULT VALUES"
324
- else
325
- "REPLACE INTO #{from} VALUES #{literal(values)}"
326
- end
327
- when Hash
328
- if values.empty?
329
- "REPLACE INTO #{from} DEFAULT VALUES"
330
- else
331
- fl, vl = [], []
332
- values.each {|k, v| fl << literal(k.is_a?(String) ? k.to_sym : k); vl << literal(v)}
333
- "REPLACE INTO #{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
334
- end
335
- when Dataset
336
- "REPLACE INTO #{from} #{literal(values)}"
337
- else
338
- if values.respond_to?(:values)
339
- replace_sql(values.values)
340
- else
341
- "REPLACE INTO #{from} VALUES (#{literal(values)})"
342
- end
343
- end
344
- end
317
+ clone(:replace=>true).insert_sql(*values)
345
318
  end
346
319
 
347
320
  # does not support DISTINCT ON
@@ -360,6 +333,13 @@ module Sequel
360
333
  def supports_timestamp_usecs?
361
334
  false
362
335
  end
336
+
337
+ protected
338
+
339
+ # If this is an replace instead of an insert, use replace instead
340
+ def _insert_sql
341
+ @opts[:replace] ? clause_sql(:replace) : super
342
+ end
363
343
 
364
344
  private
365
345
 
@@ -372,6 +352,7 @@ module Sequel
372
352
  def insert_clause_methods
373
353
  INSERT_CLAUSE_METHODS
374
354
  end
355
+ alias replace_clause_methods insert_clause_methods
375
356
 
376
357
  # MySQL doesn't use the SQL standard DEFAULT VALUES.
377
358
  def insert_columns_sql(sql)
@@ -1,4 +1,6 @@
1
1
  module Sequel
2
+ Dataset::NON_SQL_OPTIONS << :disable_insert_returning
3
+
2
4
  # Top level module for holding all PostgreSQL-related modules and classes
3
5
  # for Sequel. There are a few module level accessors that are added via
4
6
  # metaprogramming. These are:
@@ -601,7 +603,7 @@ module Sequel
601
603
  return @prepared_sql if @prepared_sql
602
604
  super
603
605
  if @prepared_type == :insert and !@opts[:disable_insert_returning] and server_version >= 80200
604
- @prepared_sql = insert_returning_pk_sql(@prepared_modify_values)
606
+ @prepared_sql = insert_returning_pk_sql(*@prepared_modify_values)
605
607
  meta_def(:insert_returning_pk_sql){|*args| prepared_sql}
606
608
  end
607
609
  @prepared_sql
@@ -672,17 +674,18 @@ module Sequel
672
674
  naked.clone(default_server_opts(:sql=>insert_returning_sql(nil, *values))).single_record
673
675
  end
674
676
 
675
- # Locks the table with the specified mode.
676
- def lock(mode, server=nil)
677
- sql = LOCK % [source_list(@opts[:from]), mode]
678
- @db.synchronize(server) do
679
- if block_given? # perform locking inside a transaction and yield to block
680
- @db.transaction(server){@db.execute(sql, :server=>server); yield}
681
- else
682
- @db.execute(sql, :server=>server) # lock without a transaction
683
- self
684
- end
677
+ # Locks all tables in the dataset's FROM clause (but not in JOINs) with
678
+ # the specified mode (e.g. 'EXCLUSIVE'). If a block is given, starts
679
+ # a new transaction, locks the table, and yields. If a block is not given
680
+ # just locks the tables. Note that PostgreSQL will probably raise an error
681
+ # if you lock the table outside of an existing transaction. Returns nil.
682
+ def lock(mode, opts={})
683
+ if block_given? # perform locking inside a transaction and yield to block
684
+ @db.transaction(opts){lock(mode, opts); yield}
685
+ else
686
+ @db.execute(LOCK % [source_list(@opts[:from]), mode], opts) # lock without a transaction
685
687
  end
688
+ nil
686
689
  end
687
690
 
688
691
  # For PostgreSQL version > 8.2, allow inserting multiple rows at once.
@@ -705,7 +708,7 @@ module Sequel
705
708
 
706
709
  # Return a clone of the dataset with an addition named window that can be referenced in window functions.
707
710
  def window(name, opts)
708
- clone(:window=>(@opts[:windows]||[]) + [[name, SQL::Window.new(opts)]])
711
+ clone(:window=>(@opts[:window]||[]) + [[name, SQL::Window.new(opts)]])
709
712
  end
710
713
 
711
714
  private
@@ -265,6 +265,19 @@ module Sequel
265
265
  @opts[:where] ? super : filter(1=>1).delete
266
266
  end
267
267
 
268
+ # Return an array of strings specifying a query explanation for a SELECT of the
269
+ # current dataset.
270
+ def explain
271
+ db.send(:metadata_dataset).clone(:sql=>"EXPLAIN #{select_sql}").
272
+ map{|x| "#{x[:addr]}|#{x[:opcode]}|#{(1..5).map{|i| x[:"p#{i}"]}.join('|')}|#{x[:comment]}"}
273
+ end
274
+
275
+ # HAVING requires GROUP BY on SQLite
276
+ def having(*cond, &block)
277
+ raise(InvalidOperation, "Can only specify a HAVING clause on a grouped dataset") unless @opts[:group]
278
+ super
279
+ end
280
+
268
281
  # SQLite uses the nonstandard ` (backtick) for quoting identifiers.
269
282
  def quoted_identifier(c)
270
283
  "`#{c}`"
@@ -280,6 +293,11 @@ module Sequel
280
293
  false
281
294
  end
282
295
 
296
+ # SQLite does not support multiple columns for the IN/NOT IN operators
297
+ def supports_multiple_column_in?
298
+ false
299
+ end
300
+
283
301
  # SQLite supports timezones in literal timestamps, since it stores them
284
302
  # as text.
285
303
  def supports_timestamp_timezones?
@@ -122,7 +122,6 @@ module Sequel
122
122
  class Dataset < Sequel::Dataset
123
123
  include ::Sequel::SQLite::DatasetMethods
124
124
 
125
- EXPLAIN = 'EXPLAIN %s'.freeze
126
125
  PREPARED_ARG_PLACEHOLDER = ':'.freeze
127
126
 
128
127
  # SQLite already supports named bind arguments, so use directly.
@@ -172,20 +171,6 @@ module Sequel
172
171
  end
173
172
  end
174
173
 
175
- # Prepare an unnamed statement of the given type and call it with the
176
- # given values.
177
- def call(type, hash, values=nil, &block)
178
- prepare(type, nil, values).call(hash, &block)
179
- end
180
-
181
- # Return an array of strings specifying a query explanation for the
182
- # current dataset.
183
- def explain
184
- res = []
185
- @db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r}
186
- res
187
- end
188
-
189
174
  # Yield a hash for each row in the dataset.
190
175
  def fetch_rows(sql)
191
176
  execute(sql) do |result|
@@ -203,7 +188,7 @@ module Sequel
203
188
  # Prepare the given type of query with the given name and store
204
189
  # it in the database. Note that a new native prepared statement is
205
190
  # created on each call to this prepared statement.
206
- def prepare(type, name=nil, values=nil)
191
+ def prepare(type, name=nil, *values)
207
192
  ps = to_prepared_statement(type, values)
208
193
  ps.extend(PreparedStatementMethods)
209
194
  db.prepared_statements[name] = ps if name
@@ -11,7 +11,7 @@ class Sequel::ConnectionPool
11
11
  # The maximum number of connections.
12
12
  attr_reader :max_size
13
13
 
14
- # The mutex that protects access to the other internal vairables. You must use
14
+ # The mutex that protects access to the other internal variables. You must use
15
15
  # this if you want to manipulate the variables safely.
16
16
  attr_reader :mutex
17
17
 
@@ -249,7 +249,7 @@ module Sequel
249
249
 
250
250
  require(%w"metaprogramming sql connection_pool exceptions dataset database timezones version")
251
251
  require(%w"schema_generator schema_methods schema_sql", 'database')
252
- require(%w"convenience graph prepared_statements sql", 'dataset')
252
+ require(%w"actions convenience features graph prepared_statements query sql", 'dataset')
253
253
  require('core_sql') if !defined?(::SEQUEL_NO_CORE_EXTENSIONS) && !ENV.has_key?('SEQUEL_NO_CORE_EXTENSIONS')
254
254
 
255
255
  # Add the database adapter class methods to Sequel via metaprogramming
@@ -576,7 +576,7 @@ module Sequel
576
576
  t = begin_transaction(conn)
577
577
  yield(conn)
578
578
  rescue Exception => e
579
- rollback_transaction(t)
579
+ rollback_transaction(t) if t
580
580
  transaction_error(e)
581
581
  ensure
582
582
  begin
@@ -78,6 +78,8 @@ module Sequel
78
78
  # See Schema::SQL#on_delete_clause for options.
79
79
  # * :size - The size of the column, generally used with string
80
80
  # columns to specify the maximum number of characters the column will hold.
81
+ # An array of two integers can be provided to set the size and the
82
+ # precision, respectively, of decimal columns.
81
83
  # * :unique - Mark the column as unique, generally has the same effect as
82
84
  # creating a unique index on the column.
83
85
  # * :unsigned - Make the column type unsigned, only useful for integer
@@ -159,7 +159,7 @@ module Sequel
159
159
  elsif index[:where]
160
160
  raise Error, "Partial indexes are not supported for this database"
161
161
  else
162
- "CREATE #{'UNIQUE ' if index[:unique]}INDEX #{quote_identifier(index_name)} ON #{quote_identifier(table_name)} #{literal(index[:columns])}"
162
+ "CREATE #{'UNIQUE ' if index[:unique]}INDEX #{quote_identifier(index_name)} ON #{quote_schema_table(table_name)} #{literal(index[:columns])}"
163
163
  end
164
164
  end
165
165
 
@@ -20,17 +20,6 @@ module Sequel
20
20
  #
21
21
  # Datasets are Enumerable objects, so they can be manipulated using any
22
22
  # of the Enumerable methods, such as map, inject, etc.
23
- #
24
- # === Methods added via metaprogramming
25
- #
26
- # Some methods are added via metaprogramming:
27
- #
28
- # * ! methods - These methods are the same as their non-! counterparts,
29
- # but they modify the receiver instead of returning a modified copy
30
- # of the dataset.
31
- # * inner_join, full_outer_join, right_outer_join, left_outer_join -
32
- # This methods are shortcuts to join_table with the join type
33
- # already specified.
34
23
  class Dataset
35
24
  extend Metaprogramming
36
25
  include Metaprogramming
@@ -50,6 +39,10 @@ module Sequel
50
39
  set_defaults set_graph_aliases set_overrides unfiltered ungraphed ungrouped union
51
40
  unlimited unordered where with with_recursive with_sql'.collect{|x| x.to_sym}
52
41
 
42
+ # Which options don't affect the SQL generation. Used by simple_select_all?
43
+ # to determine if this is a simple SELECT * FROM table.
44
+ NON_SQL_OPTIONS = [:server, :defaults, :overrides]
45
+
53
46
  NOTIMPL_MSG = "This method must be overridden in Sequel adapters".freeze
54
47
  WITH_SUPPORTED=:select_with_sql
55
48
 
@@ -103,12 +96,6 @@ module Sequel
103
96
 
104
97
  ### Instance Methods ###
105
98
 
106
- # Alias for insert, but not aliased directly so subclasses
107
- # don't have to override both methods.
108
- def <<(*args)
109
- insert(*args)
110
- end
111
-
112
99
  # Return the dataset as an aliased expression with the given alias. You can
113
100
  # use this as a FROM or JOIN dataset, or as a column if this dataset
114
101
  # returns a single row and column.
@@ -116,16 +103,6 @@ module Sequel
116
103
  ::Sequel::SQL::AliasedExpression.new(self, aliaz)
117
104
  end
118
105
 
119
- # Returns an array with all records in the dataset. If a block is given,
120
- # the array is iterated over after all items have been loaded.
121
- def all(&block)
122
- a = []
123
- each{|r| a << r}
124
- post_load(a)
125
- a.each(&block) if block
126
- a
127
- end
128
-
129
106
  # Returns a new clone of the dataset with with the given options merged.
130
107
  # If the options changed include options in COLUMN_CHANGE_OPTS, the cached
131
108
  # columns are deleted.
@@ -136,30 +113,6 @@ module Sequel
136
113
  c
137
114
  end
138
115
 
139
- # Returns the columns in the result set in order.
140
- # If the columns are currently cached, returns the cached value. Otherwise,
141
- # a SELECT query is performed to get a single row. Adapters are expected
142
- # to fill the columns cache with the column information when a query is performed.
143
- # If the dataset does not have any rows, this may be an empty array depending on how
144
- # the adapter is programmed.
145
- #
146
- # If you are looking for all columns for a single table and maybe some information about
147
- # each column (e.g. type), see Database#schema.
148
- def columns
149
- return @columns if @columns
150
- ds = unfiltered.unordered.clone(:distinct => nil, :limit => 1)
151
- ds.each{break}
152
- @columns = ds.instance_variable_get(:@columns)
153
- @columns || []
154
- end
155
-
156
- # Remove the cached list of columns and do a SELECT query to find
157
- # the columns.
158
- def columns!
159
- @columns = nil
160
- columns
161
- end
162
-
163
116
  # Add a mutation method to this dataset instance.
164
117
  def def_mutation_method(*meths)
165
118
  meths.each do |meth|
@@ -167,44 +120,6 @@ module Sequel
167
120
  end
168
121
  end
169
122
 
170
- # Deletes the records in the dataset. The returned value is generally the
171
- # number of records deleted, but that is adapter dependent. See delete_sql.
172
- def delete
173
- execute_dui(delete_sql)
174
- end
175
-
176
- # Iterates over the records in the dataset as they are yielded from the
177
- # database adapter, and returns self.
178
- #
179
- # Note that this method is not safe to use on many adapters if you are
180
- # running additional queries inside the provided block. If you are
181
- # running queries inside the block, you use should all instead of each.
182
- def each(&block)
183
- if @opts[:graph]
184
- graph_each(&block)
185
- else
186
- if row_proc = @row_proc
187
- fetch_rows(select_sql){|r| yield row_proc.call(r)}
188
- else
189
- fetch_rows(select_sql, &block)
190
- end
191
- end
192
- self
193
- end
194
-
195
- # Executes a select query and fetches records, passing each record to the
196
- # supplied block. The yielded records should be hashes with symbol keys.
197
- def fetch_rows(sql, &block)
198
- raise NotImplementedError, NOTIMPL_MSG
199
- end
200
-
201
- # Inserts values into the associated table. The returned value is generally
202
- # the value of the primary key for the inserted row, but that is adapter dependent.
203
- # See insert_sql.
204
- def insert(*values)
205
- execute_insert(insert_sql(*values))
206
- end
207
-
208
123
  # Returns a string representation of the dataset including the class name
209
124
  # and the corresponding SQL select statement.
210
125
  def inspect
@@ -219,17 +134,6 @@ module Sequel
219
134
  ds
220
135
  end
221
136
 
222
- # Whether this dataset quotes identifiers.
223
- def quote_identifiers?
224
- @quote_identifiers
225
- end
226
-
227
- # Whether the dataset requires SQL standard datetimes (false by default,
228
- # as most allow strings with ISO 8601 format.
229
- def requires_sql_standard_datetimes?
230
- false
231
- end
232
-
233
137
  # Set the server for this dataset to use. Used to pick a specific database
234
138
  # shard to run a query against, or to override the default (which is SELECT uses
235
139
  # :read_only database and all other queries use the :default database).
@@ -237,12 +141,6 @@ module Sequel
237
141
  clone(:server=>servr)
238
142
  end
239
143
 
240
- # Alias for set, but not aliased directly so subclasses
241
- # don't have to override both methods.
242
- def set(*args)
243
- update(*args)
244
- end
245
-
246
144
  # Set the default values for insert and update statements. The values hash passed
247
145
  # to insert or update are merged into this hash.
248
146
  def set_defaults(hash)
@@ -255,57 +153,6 @@ module Sequel
255
153
  clone(:overrides=>hash.merge(@opts[:overrides]||{}))
256
154
  end
257
155
 
258
- # Whether the dataset supports common table expressions (the WITH clause).
259
- def supports_cte?
260
- select_clause_methods.include?(WITH_SUPPORTED)
261
- end
262
-
263
- # Whether the dataset supports the DISTINCT ON clause, true by default.
264
- def supports_distinct_on?
265
- true
266
- end
267
-
268
- # Whether the dataset supports the INTERSECT and EXCEPT compound operations, true by default.
269
- def supports_intersect_except?
270
- true
271
- end
272
-
273
- # Whether the dataset supports the INTERSECT ALL and EXCEPT ALL compound operations, true by default.
274
- def supports_intersect_except_all?
275
- true
276
- end
277
-
278
- # Whether the dataset supports the IS TRUE syntax.
279
- def supports_is_true?
280
- true
281
- end
282
-
283
- # Whether the dataset supports timezones in literal timestamps
284
- def supports_timestamp_timezones?
285
- false
286
- end
287
-
288
- # Whether the dataset supports fractional seconds in literal timestamps
289
- def supports_timestamp_usecs?
290
- true
291
- end
292
-
293
- # Whether the dataset supports window functions.
294
- def supports_window_functions?
295
- false
296
- end
297
-
298
- # Truncates the dataset. Returns nil.
299
- def truncate
300
- execute_ddl(truncate_sql)
301
- end
302
-
303
- # Updates values for the dataset. The returned value is generally the
304
- # number of rows updated, but that is adapter dependent. See update_sql.
305
- def update(values={})
306
- execute_dui(update_sql(values))
307
- end
308
-
309
156
  # Add the mutation methods via metaprogramming
310
157
  def_mutation_method(*MUTATION_METHODS)
311
158
 
@@ -318,7 +165,7 @@ module Sequel
318
165
 
319
166
  # Whether this dataset is a simple SELECT * FROM table.
320
167
  def simple_select_all?
321
- o = @opts.reject{|k,v| v.nil?}
168
+ o = @opts.reject{|k,v| v.nil? || NON_SQL_OPTIONS.include?(k)}
322
169
  o.length == 1 && (f = o[:from]) && f.length == 1 && f.first.is_a?(Symbol)
323
170
  end
324
171
 
@@ -329,27 +176,6 @@ module Sequel
329
176
  {:server=>@opts[:server] || :default}.merge(opts)
330
177
  end
331
178
 
332
- # Execute the given SQL on the database using execute.
333
- def execute(sql, opts={}, &block)
334
- @db.execute(sql, {:server=>@opts[:server] || :read_only}.merge(opts), &block)
335
- end
336
-
337
- # Execute the given SQL on the database using execute_ddl.
338
- def execute_ddl(sql, opts={}, &block)
339
- @db.execute_ddl(sql, default_server_opts(opts), &block)
340
- nil
341
- end
342
-
343
- # Execute the given SQL on the database using execute_dui.
344
- def execute_dui(sql, opts={}, &block)
345
- @db.execute_dui(sql, default_server_opts(opts), &block)
346
- end
347
-
348
- # Execute the given SQL on the database using execute_insert.
349
- def execute_insert(sql, opts={}, &block)
350
- @db.execute_insert(sql, default_server_opts(opts), &block)
351
- end
352
-
353
179
  # Modify the identifier returned from the database based on the
354
180
  # identifier_output_method.
355
181
  def input_identifier(v)