sequel 4.40.0 → 4.41.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +30 -0
  3. data/Rakefile +6 -3
  4. data/doc/association_basics.rdoc +3 -3
  5. data/doc/opening_databases.rdoc +3 -3
  6. data/doc/release_notes/4.41.0.txt +77 -0
  7. data/doc/schema_modification.rdoc +11 -11
  8. data/lib/sequel/adapters/ado.rb +137 -9
  9. data/lib/sequel/adapters/ado/mssql.rb +1 -1
  10. data/lib/sequel/adapters/jdbc.rb +1 -1
  11. data/lib/sequel/adapters/mysql2.rb +0 -1
  12. data/lib/sequel/adapters/shared/db2.rb +30 -10
  13. data/lib/sequel/adapters/shared/mssql.rb +11 -6
  14. data/lib/sequel/database/query.rb +3 -6
  15. data/lib/sequel/database/schema_generator.rb +7 -1
  16. data/lib/sequel/database/schema_methods.rb +0 -6
  17. data/lib/sequel/dataset/actions.rb +4 -4
  18. data/lib/sequel/dataset/graph.rb +3 -2
  19. data/lib/sequel/dataset/misc.rb +23 -0
  20. data/lib/sequel/dataset/mutation.rb +15 -14
  21. data/lib/sequel/dataset/query.rb +25 -5
  22. data/lib/sequel/extensions/constraint_validations.rb +1 -3
  23. data/lib/sequel/extensions/sql_comments.rb +6 -1
  24. data/lib/sequel/model/associations.rb +7 -1
  25. data/lib/sequel/model/base.rb +1 -1
  26. data/lib/sequel/plugins/class_table_inheritance.rb +6 -6
  27. data/lib/sequel/plugins/hook_class_methods.rb +2 -2
  28. data/lib/sequel/plugins/json_serializer.rb +29 -7
  29. data/lib/sequel/version.rb +1 -1
  30. data/spec/adapters/firebird_spec.rb +2 -8
  31. data/spec/adapters/mssql_spec.rb +12 -12
  32. data/spec/adapters/mysql_spec.rb +11 -11
  33. data/spec/adapters/postgres_spec.rb +8 -9
  34. data/spec/adapters/spec_helper.rb +1 -0
  35. data/spec/adapters/sqlite_spec.rb +1 -3
  36. data/spec/core/database_spec.rb +3 -2
  37. data/spec/core/dataset_spec.rb +66 -22
  38. data/spec/core/expression_filters_spec.rb +4 -0
  39. data/spec/core/mock_adapter_spec.rb +1 -1
  40. data/spec/core/schema_generator_spec.rb +1 -1
  41. data/spec/core/schema_spec.rb +10 -1
  42. data/spec/core/spec_helper.rb +1 -0
  43. data/spec/extensions/class_table_inheritance_spec.rb +3 -0
  44. data/spec/extensions/connection_expiration_spec.rb +1 -1
  45. data/spec/extensions/graph_each_spec.rb +6 -0
  46. data/spec/extensions/hook_class_methods_spec.rb +46 -0
  47. data/spec/extensions/json_serializer_spec.rb +8 -3
  48. data/spec/extensions/set_overrides_spec.rb +4 -0
  49. data/spec/extensions/single_table_inheritance_spec.rb +2 -2
  50. data/spec/extensions/spec_helper.rb +1 -0
  51. data/spec/extensions/string_agg_spec.rb +4 -0
  52. data/spec/extensions/uuid_spec.rb +1 -2
  53. data/spec/integration/associations_test.rb +14 -0
  54. data/spec/integration/dataset_test.rb +17 -22
  55. data/spec/integration/schema_test.rb +3 -3
  56. data/spec/integration/spec_helper.rb +1 -0
  57. data/spec/integration/type_test.rb +1 -7
  58. data/spec/model/associations_spec.rb +26 -1
  59. data/spec/model/model_spec.rb +7 -1
  60. data/spec/model/spec_helper.rb +2 -0
  61. data/spec/sequel_warning.rb +4 -0
  62. metadata +6 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7036daa139080700c287bce9f5c7c2d068d5476e
4
- data.tar.gz: cd75becb35bf05f4a74b70c6468bbfbd214359e5
3
+ metadata.gz: '04857dbc303fa5d8cd9cb242dee35021eada5410'
4
+ data.tar.gz: f05247db19fc6bfb7afdb6730d301eed82fd398e
5
5
  SHA512:
6
- metadata.gz: 46cb6f09318eab28fa222d4e37484bb5781d1f94cbe4e341dd2c70c521819513e3f877e74a31dd90b998fa35abeea4ab1366e67a3bd883f85d8eec531f1841c3
7
- data.tar.gz: 71ec283114e83204a544ccdc22fd5f73cad5cd403e6c2c17d55318a6dccbe1ae5b131d62ff9cd6a3552047f4808bc41bce1f04755d123ece61a16020aa7dffe9
6
+ metadata.gz: 320e1e83b8f39e3baa396ec77fefd70f780cd96960c5e86353419d3f2f905f4fbdfc3acf18bb1e37c35620e5dca9d2401a7895b77a205ccf52d31dd8d25b07b6
7
+ data.tar.gz: f1ea15ae50f0b4e398966a5fbedde61f943032a157406e6e4d39fdc01dbcae4dc3b97432395267ae82b848af251f39ece08cf20f65893258f636d9397483fb70
data/CHANGELOG CHANGED
@@ -1,3 +1,33 @@
1
+ === 4.41.0 (2016-12-01)
2
+
3
+ * Add Dataset#with_mssql_unicode_strings on Microsoft SQL Server, returning a clone with mssql_unicode_strings set (jeremyevans)
4
+
5
+ * Add Dataset#with_identifier_output_method, returning a clone with identifier_output_method set (jeremyevans)
6
+
7
+ * Add Dataset#with_identifier_input_method, returning a clone with identifier_input_method set (jeremyevans)
8
+
9
+ * Add Dataset#with_quote_identifiers, returning a clone with quote_identifiers set (jeremyevans)
10
+
11
+ * Add Dataset#with_extend, returning a clone extended with given modules (jeremyevans)
12
+
13
+ * Add Dataset#with_row_proc, returning a clone with row_proc set (jeremyevans)
14
+
15
+ * Support use of SQL::AliasedExpressions as Model#to_json :include option keys in the json_serializer plugin (sensadrome) (#1269)
16
+
17
+ * Major improvements to type conversion in the ado adapter (vais, jeremyevans) (#1265)
18
+
19
+ * Avoid memory leak in ado adapter by closing result sets after yielding them (vais, jeremyevans) (#1259)
20
+
21
+ * Fix hook_class_methods plugin handling of commit hooks (jeremyevans)
22
+
23
+ * Make association dataset method correctly handle cases where key fields are nil (jeremyevans)
24
+
25
+ * Handle pure java exceptions that don't support message= when reraising the exception in the jdbc adapter (jeremyevans)
26
+
27
+ * Add support for :offset_strategy Database option on DB2, with :limit_offset and :offset_fetch values, to disable OFFSET emulation (#1254) (jeremyevans)
28
+
29
+ * Remove deprecated support for using Bignum class as a generic type (jeremyevans)
30
+
1
31
  === 4.40.0 (2016-10-28)
2
32
 
3
33
  * Make column_select plugin not raise an exception if the model's table does not exist (jeremyevans)
data/Rakefile CHANGED
@@ -98,9 +98,12 @@ spec_task = proc do |description, name, file, coverage|
98
98
 
99
99
  desc "#{description} with warnings, some warnings filtered"
100
100
  task :"#{name}_w" do
101
- ENV['RUBYOPT'] ? (ENV['RUBYOPT'] += " -w") : (ENV['RUBYOPT'] = '-w')
102
- rake = ENV['RAKE'] || "#{FileUtils::RUBY} -S rake"
103
- sh "#{rake} #{name} 2>&1 | egrep -v \"(: warning: instance variable @.* not initialized|: warning: method redefined; discarding old|: warning: previous definition of)\""
101
+ rubyopt = ENV['RUBYOPT']
102
+ ENV['RUBYOPT'] = "#{rubyopt} -w"
103
+ ENV['WARNING'] = '1'
104
+ run_spec.call(file)
105
+ ENV.delete('WARNING')
106
+ ENV['RUBYOPT'] = rubyopt
104
107
  end
105
108
 
106
109
  if coverage
@@ -318,11 +318,11 @@ Associations are cached after being retrieved:
318
318
  @album.artists # Cached - No Database Query
319
319
 
320
320
  You can choose to ignore the cached versions and do a database query to
321
- retrieve results by passing a true argument to the association method:
321
+ retrieve results by passing a :reload=>true option to the association method:
322
322
 
323
323
  @album.artists # Not cached - Database Query
324
324
  @album.artists # Cached - No Database Query
325
- @album.artists(true) # Ignore cache - Database Query
325
+ @album.artists(:reload=>true) # Ignore cache - Database Query
326
326
 
327
327
  If you reload/refresh the object, it will automatically clear the
328
328
  associations cache for the object:
@@ -654,7 +654,7 @@ used is not correct, you need to specify the full class name with the
654
654
  In all of these methods, _association_ is replaced by the symbol you
655
655
  pass to the association.
656
656
 
657
- === _association_(reload = false) (e.g. albums)
657
+ === _association_(opts={}) (e.g. albums)
658
658
 
659
659
  For +many_to_one+ and +one_to_one+ associations, the _association_ method
660
660
  returns either the single object associated, or nil if no object is
@@ -123,9 +123,9 @@ The following options are supported:
123
123
  for every query, which breaks things such as transactions and temporary tables.
124
124
 
125
125
  Pay special attention to the :provider option, as without specifying a provider,
126
- many things will be broken. The SQLNCLI10 provider appears to work well if you
127
- are connecting to Microsoft SQL Server, but it is not the default as that would
128
- break backwards compatability.
126
+ many things will be broken. The SQLNCLI10 and SQLNCLI11 providers work well if you
127
+ are connecting to Microsoft SQL Server, but it is not the default as it depends on
128
+ those providers being installed.
129
129
 
130
130
  Example connections:
131
131
 
@@ -0,0 +1,77 @@
1
+ = New Features
2
+
3
+ * Dataset#with_* methods have been added as equivalents for a
4
+ few Dataset#*= methods, but instead of modifying the receiver, they
5
+ return a modified copy, similar to the dataset query methods.
6
+ Specific methods added:
7
+
8
+ with_extend :: Extends clone with given modules
9
+ with_row_proc :: Modifies row_proc in clone
10
+ with_quote_identifiers :: Modifies quote_identifiers setting in
11
+ clone
12
+ with_identifier_input_method :: Modifies identifier_input_method
13
+ setting in clone
14
+ with_identifier_output_method :: Modifies identifier_output_method
15
+ setting in clone
16
+
17
+ Similarly, on Microsoft SQL Server, a with_mssql_unicode_strings
18
+ method has been added, which returns a clone with the
19
+ mssql_unicode_strings setting modified.
20
+
21
+ * On DB2, Sequel now supports an :offset_strategy Database option,
22
+ which can be set to :limit_offset for "LIMIT X OFFSET Y" or
23
+ :offset_fetch for "OFFSET Y FETCH FIRST X ROWS ONLY". Depending
24
+ on what version of DB2 is used and how DB2 is configured, it's
25
+ possible one of these strategies will work. For backwards
26
+ compatibility, the current default is still to emulate offsets
27
+ using the ROW_NUMBER window function.
28
+
29
+ * In the json_serializer plugin, you can now use an
30
+ Sequel::SQL::AliasedExpression instance as an association name
31
+ value, which allows you to rename the association in the resulting
32
+ JSON:
33
+
34
+ album.to_json(:include=>{Sequel.as(:album, :s)=>{:only=>:name}})
35
+ # => '{"id":1,"name":"RF","artist_id":2,"s":{"name":"YJM"}}'
36
+
37
+ = Other Improvements
38
+
39
+ * The association dataset methods now correctly handle cases where
40
+ one of the keys is nil. Previously, they would incorrectly use an
41
+ IS NULL predicate in such cases. Now, they use a false predicate.
42
+
43
+ * The hook_class_methods plugin handling of commit hooks has been
44
+ fixed. The implementation of commit hooks (but not rollback
45
+ hooks) was broken in hook_class_methods starting in 4.39.0 due to
46
+ changes to avoid keeping references to all model instances until
47
+ the transaction was committed or rolled back.
48
+
49
+ * Using the Fixnum schema method no longer raises a warning on ruby
50
+ 2.4+, as it now uses the Integer class instead of the Fixnum
51
+ constant.
52
+
53
+ * The ado adapter has been greatly improved. It now avoids memory
54
+ leaks, has much better type handling, and passes almost all specs.
55
+ Note that the ado adapter's behavior can change depending on the
56
+ version of ruby in use, try to use ruby 2.2+ for best compatibility.
57
+
58
+ * Dataset#graph no longer mutates the receiver. Previously, it set
59
+ an empty hash as the :graph option in the receiver, which was
60
+ unintentional and not desired.
61
+
62
+ * Pure java exceptions that don't support the message= method are now
63
+ handled properly when reraising the exception on connection errors
64
+ in the jdbc adapter.
65
+
66
+ = Backwards Compatibility
67
+
68
+ * Support for using the Bignum constant as a generic type has been
69
+ removed, as was preannounced in the 4.36.0 release notes. Users
70
+ should switch to using the :Bignum constant if they haven't already.
71
+
72
+ * Users of the ado adapter may need to update their code now that the
73
+ ado adapter correctly handles most types.
74
+
75
+ * The spec_*_w rake tasks in the repository now require ruby 2.4+ and
76
+ use the warning library for filtering warnings, instead of trying to
77
+ filter warnings with egrep.
@@ -22,7 +22,7 @@ Columns are generally created by specifying the column type as the method
22
22
  name, followed by the column name symbol to use, and after that any options that should be used.
23
23
  If the method is a ruby class name that Sequel recognizes, Sequel will transform it into the appropriate
24
24
  type for the given database. So while you specified +String+, Sequel will actually use +varchar+ or
25
- +text+ depending on the underlying database. Here's a list of all of ruby classes that Sequel will
25
+ +text+ depending on the underlying database. Here's a list of all ruby classes that Sequel will
26
26
  convert to database types:
27
27
 
28
28
  create_table(:columns_types) do # common database type used
@@ -75,7 +75,7 @@ as the second argument, either as ruby classes, symbols, or strings:
75
75
  end
76
76
 
77
77
  If you use a ruby class as the type, Sequel will try to guess the appropriate type name for the
78
- database you are using. If a symbol or string as used as the type, it is used verbatim as the type
78
+ database you are using. If a symbol or string is used as the type, it is used verbatim as the type
79
79
  name in SQL, with the exception of :Bignum. Using the symbol :Bignum as a type will use the
80
80
  appropriate 64-bit integer type for the database you are using.
81
81
 
@@ -102,7 +102,7 @@ method, the fourth argument is the options hash. The following options are supp
102
102
 
103
103
  === Other methods
104
104
 
105
- In addition to the +column+ method and other methods that create columns, there are a other methods that can be used:
105
+ In addition to the +column+ method and other methods that create columns, there are other methods that can be used:
106
106
 
107
107
  ==== +primary_key+
108
108
 
@@ -129,7 +129,7 @@ via the :name option:
129
129
  Integer :group_id
130
130
  Integer :position
131
131
  primary_key [:group_id, :position], :name=>:items_pk
132
- end
132
+ end
133
133
 
134
134
  If provided with an array, +primary_key+ does not create a column, it just sets up the primary key constraint.
135
135
 
@@ -159,7 +159,7 @@ as its third argument. A simple example is:
159
159
  references the primary key of the associated table, at least
160
160
  on most databases.
161
161
  :on_delete :: Specify the behavior of this foreign key column when the row with the primary key
162
- it references is deleted , can be :restrict, :cascade, :set_null, or :set_default.
162
+ it references is deleted, can be :restrict, :cascade, :set_null, or :set_default.
163
163
  You can also use a string, which is used literally.
164
164
  :on_update :: Specify the behavior of this foreign key column when the row with the primary key
165
165
  it references modifies the value of the primary key. Takes the same options as
@@ -276,7 +276,7 @@ both take the same options as +index+.
276
276
  String :name
277
277
  constraint(:name_min_length){char_length(name) > 2}
278
278
  end
279
-
279
+
280
280
  Instead of using a block, you can use arguments that will be handled similarly
281
281
  to <tt>Dataset#where</tt>:
282
282
 
@@ -285,7 +285,7 @@ to <tt>Dataset#where</tt>:
285
285
  String :name
286
286
  constraint(:name_length_range, Sequel.function(:char_length, :name)=>3..50)
287
287
  end
288
-
288
+
289
289
  ==== +check+
290
290
 
291
291
  +check+ operates just like +constraint+, except that it doesn't take a name
@@ -296,7 +296,7 @@ and it creates an unnamed constraint:
296
296
  String :name
297
297
  check{char_length(name) > 2}
298
298
  end
299
-
299
+
300
300
  It's recommended that you use the +constraint+ method and provide a name for the
301
301
  constraint, as that makes it easier to drop the constraint later if necessary.
302
302
 
@@ -478,8 +478,8 @@ method:
478
478
  add_constraint(:name_min_length){char_length(name) > 2}
479
479
  end
480
480
 
481
- There is no method to add an unnamed constraint, but you can pass nil as the first
482
- argument of +add_constraint+ to do so. However, it's not recommend to do that
481
+ There is no method to add an unnamed constraint, but you can pass +nil+ as the first
482
+ argument of +add_constraint+ to do so. However, it's not recommended to do that
483
483
  as it is difficult to drop such a constraint.
484
484
 
485
485
  === +add_unique_constraint+
@@ -640,7 +640,7 @@ is the same as:
640
640
  unless table_exists?(:artists)
641
641
  create_table(:artists) do
642
642
  primary_key :id
643
- end
643
+ end
644
644
  end
645
645
 
646
646
  Like <tt>create_table!</tt>, it should not be used inside migrations.
@@ -5,11 +5,92 @@ require 'win32ole'
5
5
  module Sequel
6
6
  # The ADO adapter provides connectivity to ADO databases in Windows.
7
7
  module ADO
8
+ # ADO constants (DataTypeEnum)
9
+ # Source: https://msdn.microsoft.com/en-us/library/ms675318(v=vs.85).aspx
10
+ AdBigInt = 20
11
+ AdBinary = 128
12
+ #AdBoolean = 11
13
+ #AdBSTR = 8
14
+ #AdChapter = 136
15
+ #AdChar = 129
16
+ #AdCurrency = 6
17
+ #AdDate = 7
18
+ AdDBDate = 133
19
+ #AdDBTime = 134
20
+ AdDBTimeStamp = 135
21
+ #AdDecimal = 14
22
+ #AdDouble = 5
23
+ #AdEmpty = 0
24
+ #AdError = 10
25
+ #AdFileTime = 64
26
+ #AdGUID = 72
27
+ #AdIDispatch = 9
28
+ #AdInteger = 3
29
+ #AdIUnknown = 13
30
+ AdLongVarBinary = 205
31
+ #AdLongVarChar = 201
32
+ #AdLongVarWChar = 203
33
+ AdNumeric = 131
34
+ #AdPropVariant = 138
35
+ #AdSingle = 4
36
+ #AdSmallInt = 2
37
+ #AdTinyInt = 16
38
+ #AdUnsignedBigInt = 21
39
+ #AdUnsignedInt = 19
40
+ #AdUnsignedSmallInt = 18
41
+ #AdUnsignedTinyInt = 17
42
+ #AdUserDefined = 132
43
+ AdVarBinary = 204
44
+ #AdVarChar = 200
45
+ #AdVariant = 12
46
+ AdVarNumeric = 139
47
+ #AdVarWChar = 202
48
+ #AdWChar = 130
49
+
50
+ cp = Object.new
51
+
52
+ def cp.bigint(v)
53
+ v.to_i
54
+ end
55
+
56
+ def cp.numeric(v)
57
+ BigDecimal.new(v)
58
+ end
59
+
60
+ def cp.binary(v)
61
+ Sequel.blob(v.pack('c*'))
62
+ end
63
+
64
+ if RUBY_VERSION >= '1.9'
65
+ def cp.date(v)
66
+ Date.new(v.year, v.month, v.day)
67
+ end
68
+ else
69
+ def cp.date(v)
70
+ Date.new(*v[0...10].split('/').map{|x| x.to_i})
71
+ end
72
+ end
73
+
74
+ CONVERSION_PROCS = {}
75
+ [
76
+ [:bigint, AdBigInt],
77
+ [:numeric, AdNumeric, AdVarNumeric],
78
+ [:date, AdDBDate],
79
+ [:binary, AdBinary, AdVarBinary, AdLongVarBinary]
80
+ ].each do |meth, *types|
81
+ method = cp.method(meth)
82
+ types.each do |i|
83
+ CONVERSION_PROCS[i] = method
84
+ end
85
+ end
86
+
8
87
  class Database < Sequel::Database
9
88
  DISCONNECT_ERROR_RE = /Communication link failure/
10
89
 
11
90
  set_adapter_scheme :ado
12
91
 
92
+ attr_reader :conversion_procs
93
+
13
94
  # In addition to the usual database options,
14
95
  # the following options have an effect:
15
96
  #
@@ -76,7 +157,14 @@ module Sequel
76
157
  synchronize(opts[:server]) do |conn|
77
158
  begin
78
159
  r = log_connection_yield(sql, conn){conn.Execute(sql)}
79
- yield(r) if block_given?
160
+ begin
161
+ yield r if block_given?
162
+ ensure
163
+ begin
164
+ r.close
165
+ rescue ::WIN32OLERuntimeError
166
+ end
167
+ end
80
168
  rescue ::WIN32OLERuntimeError => e
81
169
  raise_error(e)
82
170
  end
@@ -102,6 +190,9 @@ module Sequel
102
190
  set_mssql_unicode_strings
103
191
  end
104
192
  end
193
+
194
+ @conversion_procs = CONVERSION_PROCS.dup
195
+
105
196
  super
106
197
  end
107
198
 
@@ -132,15 +223,52 @@ module Sequel
132
223
  class Dataset < Sequel::Dataset
133
224
  Database::DatasetClass = self
134
225
 
226
+
227
+
135
228
  def fetch_rows(sql)
136
- execute(sql) do |s|
137
- columns = cols = s.Fields.extend(Enumerable).map{|column| output_identifier(column.Name)}
138
- self.columns = columns
139
- s.getRows.transpose.each do |r|
140
- row = {}
141
- cols.each{|c| row[c] = r.shift}
142
- yield row
143
- end unless s.eof
229
+ execute(sql) do |recordset|
230
+ cols = []
231
+ conversion_procs = db.conversion_procs
232
+
233
+ i = -1
234
+ ts_cp = nil
235
+ recordset.Fields.each do |field|
236
+ type = field.Type
237
+ cp = if type == AdDBTimeStamp
238
+ ts_cp ||= if RUBY_VERSION >= '1.9'
239
+ nsec_div = 1000000000.0/(10**(timestamp_precision))
240
+ nsec_mul = 10**(timestamp_precision+3)
241
+ meth = db.method(:to_application_timestamp)
242
+ lambda do |v|
243
+ # Fractional second handling is not correct on ruby <2.2
244
+ meth.call([v.year, v.month, v.day, v.hour, v.min, v.sec, (v.nsec/nsec_div).round * nsec_mul])
245
+ end
246
+ else
247
+ # Ruby 1.8 returns AdDBTimeStamp values as a string
248
+ db.method(:to_application_timestamp)
249
+ end
250
+ else
251
+ conversion_procs[type]
252
+ end
253
+ cols << [output_identifier(field.Name), cp, i+=1]
254
+ end
255
+
256
+ self.columns = cols.map(&:first)
257
+ return if recordset.EOF
258
+
259
+ recordset.GetRows.transpose.each do |field_values|
260
+ h = {}
261
+
262
+ cols.each do |name, cp, i|
263
+ h[name] = if (v = field_values[i]) && cp
264
+ cp[v]
265
+ else
266
+ v
267
+ end
268
+ end
269
+
270
+ yield h
271
+ end
144
272
  end
145
273
  end
146
274
 
@@ -56,7 +56,7 @@ module Sequel
56
56
  # is necessary as ADO's default :provider uses a separate native
57
57
  # connection for each query.
58
58
  def insert(*values)
59
- return super if @opts[:sql]
59
+ return super if @opts[:sql] || @opts[:returning]
60
60
  with_sql("SET NOCOUNT ON; #{insert_sql(*values)}; SELECT CAST(SCOPE_IDENTITY() AS INTEGER)").single_value
61
61
  end
62
62