ruby-oci8 2.0.2-x86-mswin32-60 → 2.0.3-x86-mswin32-60

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/lib/oci8/object.rb CHANGED
@@ -84,7 +84,7 @@ EOS
84
84
  csr.bind_param(key, nil, :named_type_internal, tdo)
85
85
  csr[key].attributes = val
86
86
  else
87
- csr.bind_param(key, val)
87
+ csr.bind_param(key, val ? val : '')
88
88
  end
89
89
  end
90
90
  csr.exec
@@ -425,6 +425,9 @@ EOS
425
425
  'INTERVAL DAY TO SECOND' => :interval_ds,
426
426
  }
427
427
 
428
+ # for datetime_to_array and ocidate_to_datetime
429
+ extend OCI8::BindType::Util
430
+
428
431
  def self.check_metadata(con, metadata)
429
432
  case metadata.typecode
430
433
  when :char, :varchar, :varchar2
@@ -437,6 +440,11 @@ EOS
437
440
  [ATTR_INTEGER, nil, SIZE_OF_OCINUMBER, 2, ALIGNMENT_OF_OCINUMBER]
438
441
  when :real, :double, :float
439
442
  [ATTR_FLOAT, nil, SIZE_OF_OCINUMBER, 2, ALIGNMENT_OF_OCINUMBER]
443
+ when :date
444
+ [ATTR_OCIDATE, nil, SIZE_OF_OCIDATE, 2, ALIGNMENT_OF_OCIDATE,
445
+ Proc.new do |val| datetime_to_array(val, false) end, # set_proc
446
+ Proc.new do |val| ocidate_to_datetime(val) end, # get_proc
447
+ ]
440
448
  when :binary_double
441
449
  [ATTR_BINARY_DOUBLE, nil, SIZE_OF_DOUBLE, 2, ALIGNMENT_OF_DOUBLE]
442
450
  when :binary_float
@@ -461,11 +469,13 @@ EOS
461
469
  attr_reader :alignment
462
470
  attr_reader :datatype
463
471
  attr_reader :typeinfo
472
+ attr_reader :set_proc
473
+ attr_reader :get_proc
464
474
  def initialize(con, metadata, val_offset, ind_offset)
465
475
  if metadata.respond_to? :name
466
476
  @name = metadata.name.downcase.intern
467
477
  end
468
- @datatype, @typeinfo, @val_size, @ind_size, @alignment, = OCI8::TDO.check_metadata(con, metadata)
478
+ @datatype, @typeinfo, @val_size, @ind_size, @alignment, @set_proc, @get_proc, = OCI8::TDO.check_metadata(con, metadata)
469
479
  @val_offset = (val_offset + @alignment - 1) & ~(@alignment - 1)
470
480
  @ind_offset = ind_offset
471
481
  end
@@ -485,7 +495,9 @@ EOS
485
495
  def attributes
486
496
  attrs = {}
487
497
  tdo.attributes.each do |attr|
488
- attrs[attr.name] = get_attribute(attr.datatype, attr.typeinfo, attr.val_offset, attr.ind_offset)
498
+ attr_val = get_attribute(attr.datatype, attr.typeinfo, attr.val_offset, attr.ind_offset)
499
+ attr_val = attr.get_proc.call(attr_val) if attr.get_proc
500
+ attrs[attr.name] = attr_val
489
501
  end
490
502
  attrs
491
503
  end
@@ -493,7 +505,9 @@ EOS
493
505
  def attributes=(obj)
494
506
  obj = obj.instance_variable_get(:@attributes) unless obj.is_a? Hash
495
507
  tdo.attributes.each do |attr|
496
- set_attribute(attr.datatype, attr.typeinfo, attr.val_offset, attr.ind_offset, obj[attr.name])
508
+ attr_val = obj[attr.name]
509
+ attr_val = attr.set_proc.call(attr_val) if attr.set_proc
510
+ set_attribute(attr.datatype, attr.typeinfo, attr.val_offset, attr.ind_offset, attr_val)
497
511
  end
498
512
  end
499
513
  end
data/lib/oci8/oci8.rb CHANGED
@@ -1,16 +1,28 @@
1
- #--
2
- # oci8.rb -- OCI8 and OCI8::Cursor
1
+ # oci8.rb -- implements OCI8 and OCI8::Cursor
3
2
  #
4
3
  # Copyright (C) 2002-2009 KUBO Takehiro <kubo@jiubao.org>
5
4
  #
6
5
  # Original Copyright is:
7
6
  # Oracle module for Ruby
8
7
  # 1998-2000 by yoshidam
9
- #++
8
+ #
10
9
 
11
10
  require 'date'
12
11
 
13
- # The database connection class.
12
+ # A connection to a Oracle database server.
13
+ #
14
+ # example:
15
+ # # output the emp table's content as CSV format.
16
+ # conn = OCI8.new(username, password)
17
+ # conn.exec('select * from emp') do |row|
18
+ # puts row.join(',')
19
+ # end
20
+ #
21
+ # # execute PL/SQL block with bind variables.
22
+ # conn = OCI8.new(username, password)
23
+ # conn.exec('BEGIN procedure_name(:1, :2); END;',
24
+ # value_for_the_first_parameter,
25
+ # value_for_the_second_parameter)
14
26
  class OCI8
15
27
  # Executes the sql statement. The type of return value depends on
16
28
  # the type of sql statement: select; insert, update and delete;
@@ -331,6 +343,10 @@ class OCI8
331
343
  end
332
344
 
333
345
  bindclass = OCI8::BindType::Mapping[type]
346
+ if bindclass.nil? and type.is_a? Class
347
+ bindclass = OCI8::BindType::Mapping[type.to_s]
348
+ OCI8::BindType::Mapping[type] = bindclass if bindclass
349
+ end
334
350
  raise "unsupported dataType: #{type}" if bindclass.nil?
335
351
  bindobj = bindclass.create(@con, var_array, param, @max_array_size)
336
352
  __bind(key, bindobj)
@@ -440,6 +456,10 @@ class OCI8
440
456
  end
441
457
 
442
458
  bindclass = OCI8::BindType::Mapping[key]
459
+ if bindclass.nil? and key.is_a? Class
460
+ bindclass = OCI8::BindType::Mapping[key.to_s]
461
+ OCI8::BindType::Mapping[key] = bindclass if bindclass
462
+ end
443
463
  raise "unsupported datatype: #{key}" if bindclass.nil?
444
464
  bindclass.create(@con, val, param, max_array_size)
445
465
  end
data/lib/oci8lib_18.so CHANGED
Binary file
data/lib/oci8lib_191.so CHANGED
Binary file
data/test/test_appinfo.rb CHANGED
@@ -23,6 +23,40 @@ class TestAppInfo < Test::Unit::TestCase
23
23
  assert_nil(@conn.select_one("SELECT SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER') FROM DUAL")[0]);
24
24
  end
25
25
 
26
+ def test_set_module
27
+ # FIXME: check again after upgrading Oracle 9.2 to 9.2.0.4.
28
+ return if @conn.oracle_server_version < OCI8::ORAVER_10_1
29
+
30
+ # set module
31
+ @conn.module = 'ruby-oci8'
32
+ assert_equal('ruby-oci8', @conn.select_one("SELECT SYS_CONTEXT('USERENV', 'MODULE') FROM DUAL")[0]);
33
+ # clear module
34
+ @conn.module = nil
35
+ assert_nil(@conn.select_one("SELECT SYS_CONTEXT('USERENV', 'MODULE') FROM DUAL")[0]);
36
+ end
37
+
38
+ def test_set_action
39
+ # FIXME: check again after upgrading Oracle 9.2 to 9.2.0.4.
40
+ return if @conn.oracle_server_version < OCI8::ORAVER_10_1
41
+
42
+ # set action
43
+ @conn.action = 'test_set_action'
44
+ assert_equal('test_set_action', @conn.select_one("SELECT SYS_CONTEXT('USERENV', 'ACTION') FROM DUAL")[0]);
45
+ # clear action
46
+ @conn.action = nil
47
+ assert_nil(@conn.select_one("SELECT SYS_CONTEXT('USERENV', 'ACTION') FROM DUAL")[0]);
48
+ end
49
+
50
+ def test_set_client_info
51
+ # set client_info
52
+ client_info = "ruby-oci8:#{Process.pid()}"
53
+ @conn.client_info = client_info
54
+ assert_equal(client_info, @conn.select_one("SELECT SYS_CONTEXT('USERENV', 'CLIENT_INFO') FROM DUAL")[0]);
55
+ # clear client_info
56
+ @conn.client_info = nil
57
+ assert_nil(@conn.select_one("SELECT SYS_CONTEXT('USERENV', 'CLIENT_INFO') FROM DUAL")[0]);
58
+ end
59
+
26
60
  def teardown
27
61
  @conn.logoff
28
62
  end
data/test/test_clob.rb CHANGED
@@ -39,6 +39,19 @@ class TestCLob < Test::Unit::TestCase
39
39
  lob.close
40
40
  end
41
41
 
42
+ def test_insert_symbol
43
+ filename = 'test_symbol'
44
+ value = :foo_bar
45
+ @conn.exec("DELETE FROM test_clob WHERE filename = :1", filename)
46
+ @conn.exec("INSERT INTO test_clob(filename, content) VALUES (:1, EMPTY_CLOB())", filename)
47
+ cursor = @conn.exec("SELECT content FROM test_clob WHERE filename = :1 FOR UPDATE", filename)
48
+ lob = cursor.fetch[0]
49
+ lob.write(value)
50
+ lob.rewind
51
+ assert_equal(value.to_s, lob.read);
52
+ lob.close
53
+ end
54
+
42
55
  def test_read
43
56
  test_insert() # first insert data.
44
57
  filename = File.basename($lobfile)
@@ -13,6 +13,31 @@ class TestDateTime < Test::Unit::TestCase
13
13
  end
14
14
  end
15
15
 
16
+ def string_to_time(str)
17
+ /(\d+)-(\d+)-(\d+) ?(?:(\d+):(\d+):(\d+))?(?:\.(\d+))? ?([+-]\d+:\d+)?/ =~ str
18
+ args = []
19
+ args << $1.to_i # year
20
+ args << $2.to_i # month
21
+ args << $3.to_i # day
22
+ args << $4.to_i if $4 # hour
23
+ args << $5.to_i if $5 # minute
24
+ if $8
25
+ args << $6.to_i + $7.to_i.to_r / ('1' + '0' * ($7.length)).to_i
26
+ args << $8
27
+ Time.new(*args)
28
+ else
29
+ if $6
30
+ args << $6.to_i
31
+ end
32
+ if $7
33
+ args << $7.to_i.to_r * 1000000 / ('1' + '0' * ($7.length)).to_i
34
+ end
35
+ # no time zone
36
+ Time.local(*args)
37
+ end
38
+ #Time.local(*str.split(/[- :\.]/).collect do |n| n.to_i; end)
39
+ end
40
+
16
41
  def setup
17
42
  @conn = get_oci8_connection
18
43
  @local_timezone = timezone_string(*((::Time.now.utc_offset / 60).divmod 60))
@@ -129,7 +154,12 @@ EOS
129
154
  @conn.exec(<<-EOS) do |row|
130
155
  SELECT TO_TIMESTAMP_TZ('#{date}', 'YYYY-MM-DD HH24:MI:SS.FF TZH:TZM') FROM dual
131
156
  EOS
132
- assert_equal(DateTime.parse(date), row[0])
157
+ expected_val = begin
158
+ string_to_time(date)
159
+ rescue
160
+ DateTime.parse(date)
161
+ end
162
+ assert_equal(expected_val, row[0])
133
163
  end
134
164
  end
135
165
  end
@@ -274,6 +304,7 @@ EOS
274
304
  when Time
275
305
  assert_equal(tz, timezone_string(*((dt.utc_offset / 60).divmod 60)))
276
306
  when DateTime
307
+ tz = tz.gsub(/:/, '') if RUBY_VERSION <= '1.8.5'
277
308
  assert_equal(tz, dt.zone)
278
309
  else
279
310
  flunk "unexpedted type #{dt.class}"
@@ -352,9 +383,14 @@ EOS
352
383
  cursor.bind_param(:out, nil, String, 36)
353
384
  cursor.bind_param(:in1, nil, String, 36)
354
385
  cursor.bind_param(:in2, nil, :interval_ym)
355
- [['2006-01-01', -22],
356
- ['2006-01-01', -10],
386
+ [['2006-01-01', -25],
387
+ ['2006-01-01', -24],
388
+ ['2006-01-01', -23],
389
+ ['2006-01-01', -13],
390
+ ['2006-01-01', -12],
391
+ ['2006-01-01', -11],
357
392
  ['2006-01-01', +2],
393
+ ['2006-01-01', -2],
358
394
  ['2006-01-01', +12]
359
395
  ].each do |date, interval|
360
396
  cursor[:in1] = date
@@ -388,7 +424,7 @@ SELECT (TO_TIMESTAMP('#{date1}', 'YYYY-MM-DD HH24:MI:SS.FF')
388
424
  - TO_TIMESTAMP('#{date2}', 'YYYY-MM-DD HH24:MI:SS.FF')) DAY(3) TO SECOND
389
425
  FROM dual
390
426
  EOS
391
- assert_equal(DateTime.parse(date1) - DateTime.parse(date2), row[0])
427
+ assert_in_delta(string_to_time(date1) - string_to_time(date2), row[0], 0.0000000001)
392
428
  end
393
429
  end
394
430
  end
@@ -427,7 +463,7 @@ EOS
427
463
  cursor[:in1] = date1
428
464
  cursor[:in2] = date2
429
465
  cursor.exec
430
- assert_equal(DateTime.parse(date1) - DateTime.parse(date2), cursor[:out])
466
+ assert_in_delta(string_to_time(date1) - string_to_time(date2), cursor[:out], 0.0000000001)
431
467
  end
432
468
  cursor.close
433
469
  end
@@ -440,7 +476,7 @@ DECLARE
440
476
  ts1 TIMESTAMP;
441
477
  BEGIN
442
478
  ts1 := TO_TIMESTAMP(:in1, 'YYYY-MM-DD HH24:MI:SS.FF');
443
- :out := TO_CHAR(ts1 + :in2, 'YYYY-MM-DD HH24:MI:SS.FF');
479
+ :out := TO_CHAR(ts1 + :in2, 'YYYY-MM-DD HH24:MI:SS.FF6');
444
480
  END;
445
481
  EOS
446
482
  cursor.bind_param(:out, nil, String, 36)
@@ -459,11 +495,128 @@ EOS
459
495
  ['2006-01-01', +1.to_r / (24*60*60)], # one second
460
496
  ['2006-01-01', +999999.to_r / (24*60*60*1000000)] # 0.999999 seconds
461
497
  ].each do |date, interval|
498
+ interval *= 86400
462
499
  cursor[:in1] = date
463
500
  cursor[:in2] = interval
464
501
  cursor.exec
465
- assert_equal(DateTime.parse(date) + interval, DateTime.parse(cursor[:out]))
502
+ assert_equal(string_to_time(date) + interval, string_to_time(cursor[:out]))
503
+ end
504
+ cursor.close
505
+ end
506
+
507
+ def test_days_interval_ds_select
508
+ return if $oracle_version < OCI8::ORAVER_9_0
509
+
510
+ [['2006-01-01', '2004-03-01'],
511
+ ['2006-01-01', '2005-03-01'],
512
+ ['2006-01-01', '2006-03-01'],
513
+ ['2006-01-01', '2007-03-01'],
514
+ ['2006-01-01', '2006-01-01 23:00:00'],
515
+ ['2006-01-01', '2006-01-01 00:59:00'],
516
+ ['2006-01-01', '2006-01-01 00:00:59'],
517
+ ['2006-01-01', '2006-01-01 00:00:00.999999'],
518
+ ['2006-01-01', '2006-01-01 23:59:59.999999'],
519
+ ['2006-01-01', '2005-12-31 23:00:00'],
520
+ ['2006-01-01', '2005-12-31 00:59:00'],
521
+ ['2006-01-01', '2005-12-31 00:00:59'],
522
+ ['2006-01-01', '2005-12-31 00:00:00.999999'],
523
+ ['2006-01-01', '2005-12-31 23:59:59.999999']
524
+ ].each do |date1, date2|
525
+ begin
526
+ OCI8::BindType::IntervalDS.unit = :day
527
+ @conn.exec(<<-EOS) do |row|
528
+ SELECT (TO_TIMESTAMP('#{date1}', 'YYYY-MM-DD HH24:MI:SS.FF')
529
+ - TO_TIMESTAMP('#{date2}', 'YYYY-MM-DD HH24:MI:SS.FF')) DAY(3) TO SECOND
530
+ FROM dual
531
+ EOS
532
+ assert_equal(DateTime.parse(date1) - DateTime.parse(date2), row[0])
533
+ end
534
+ ensure
535
+ OCI8::BindType::IntervalDS.unit = :second
536
+ end
537
+ end
538
+ end
539
+
540
+ def test_days_interval_ds_out_bind
541
+ return if $oracle_version < OCI8::ORAVER_9_0
542
+
543
+ cursor = @conn.parse(<<-EOS)
544
+ DECLARE
545
+ ts1 TIMESTAMP;
546
+ ts2 TIMESTAMP;
547
+ BEGIN
548
+ ts1 := TO_TIMESTAMP(:in1, 'YYYY-MM-DD HH24:MI:SS.FF');
549
+ ts2 := TO_TIMESTAMP(:in2, 'YYYY-MM-DD HH24:MI:SS.FF');
550
+ :out := (ts1 - ts2) DAY TO SECOND(9);
551
+ END;
552
+ EOS
553
+ cursor.bind_param(:out, nil, :interval_ds)
554
+ cursor.bind_param(:in1, nil, String, 36)
555
+ cursor.bind_param(:in2, nil, String, 36)
556
+ [['2006-01-01', '2004-03-01'],
557
+ ['2006-01-01', '2005-03-01'],
558
+ ['2006-01-01', '2006-03-01'],
559
+ ['2006-01-01', '2007-03-01'],
560
+ ['2006-01-01', '2006-01-01 23:00:00'],
561
+ ['2006-01-01', '2006-01-01 00:59:00'],
562
+ ['2006-01-01', '2006-01-01 00:00:59'],
563
+ ['2006-01-01', '2006-01-01 00:00:00.999999'],
564
+ ['2006-01-01', '2006-01-01 23:59:59.999999'],
565
+ ['2006-01-01', '2005-12-31 23:00:00'],
566
+ ['2006-01-01', '2005-12-31 00:59:00'],
567
+ ['2006-01-01', '2005-12-31 00:00:59'],
568
+ ['2006-01-01', '2005-12-31 00:00:00.999999'],
569
+ ['2006-01-01', '2005-12-31 23:59:59.999999']
570
+ ].each do |date1, date2|
571
+ begin
572
+ OCI8::BindType::IntervalDS.unit = :day
573
+ cursor[:in1] = date1
574
+ cursor[:in2] = date2
575
+ cursor.exec
576
+ assert_equal(DateTime.parse(date1) - DateTime.parse(date2), cursor[:out])
577
+ ensure
578
+ OCI8::BindType::IntervalDS.unit = :second
579
+ end
466
580
  end
467
581
  cursor.close
468
582
  end
583
+
584
+ def test_days_interval_ds_in_bind
585
+ return if $oracle_version < OCI8::ORAVER_9_0
586
+
587
+ cursor = @conn.parse(<<-EOS)
588
+ DECLARE
589
+ ts1 TIMESTAMP;
590
+ BEGIN
591
+ ts1 := TO_TIMESTAMP(:in1, 'YYYY-MM-DD');
592
+ :out := TO_CHAR(ts1 + :in2, 'YYYY-MM-DD HH24:MI:SS.FF');
593
+ END;
594
+ EOS
595
+ cursor.bind_param(:out, nil, String, 36)
596
+ cursor.bind_param(:in1, nil, String, 36)
597
+ cursor.bind_param(:in2, nil, :interval_ds)
598
+ [['2006-01-01', -22],
599
+ ['2006-01-01', -10],
600
+ ['2006-01-01', +2],
601
+ ['2006-01-01', +12],
602
+ ['2006-01-01', -1.to_r / 24], # one hour
603
+ ['2006-01-01', -1.to_r / (24*60)], # one minute
604
+ ['2006-01-01', -1.to_r / (24*60*60)], # one second
605
+ ['2006-01-01', -999999.to_r / (24*60*60*1000000)], # 0.999999 seconds
606
+ ['2006-01-01', +1.to_r / 24], # one hour
607
+ ['2006-01-01', +1.to_r / (24*60)], # one minute
608
+ ['2006-01-01', +1.to_r / (24*60*60)], # one second
609
+ ['2006-01-01', +999999.to_r / (24*60*60*1000000)] # 0.999999 seconds
610
+ ].each do |date, interval|
611
+ begin
612
+ OCI8::BindType::IntervalDS.unit = :day
613
+ cursor[:in1] = date
614
+ cursor[:in2] = interval
615
+ cursor.exec
616
+ assert_equal(DateTime.parse(date) + interval, DateTime.parse(cursor[:out]))
617
+ ensure
618
+ OCI8::BindType::IntervalDS.unit = :second
619
+ end
620
+ end
621
+ end
469
622
  end # TestOCI8
data/test/test_oci8.rb CHANGED
@@ -167,6 +167,9 @@ EOS
167
167
  end
168
168
 
169
169
  def test_bind_cursor
170
+ # FIXME: check again after upgrading Oracle 9.2 to 9.2.0.4.
171
+ return if $oracle_version < OCI8::ORAVER_10_1
172
+
170
173
  drop_table('test_table')
171
174
  sql = <<-EOS
172
175
  CREATE TABLE test_table
@@ -357,12 +360,12 @@ EOS
357
360
  assert_equal(row[0], 12345678901234)
358
361
  assert_equal(row[1], 12345678901234567890)
359
362
  assert_equal(row[2], 123456789012.34)
360
- assert_equal(row[3], 1234567890123.45)
363
+ assert_equal(row[3], BigDecimal("1234567890123.45"))
361
364
  assert_equal(row[4], 1234.5)
362
- assert_instance_of(OraNumber, row[0])
365
+ assert_instance_of(BigDecimal, row[0])
363
366
  assert_instance_of(Bignum, row[1])
364
367
  assert_instance_of(Float, row[2])
365
- assert_instance_of(OraNumber, row[3])
368
+ assert_instance_of(BigDecimal, row[3])
366
369
  assert_instance_of(Float, row[4])
367
370
  end
368
371
  drop_table('test_table')
@@ -372,6 +375,8 @@ EOS
372
375
  src = [1, 1.2, BigDecimal("1.2"), Rational(12, 10)]
373
376
  int = [1, 1, 1, 1]
374
377
  flt = [1, 1.2, 1.2, 1.2]
378
+ dec = [BigDecimal("1"), BigDecimal("1.2"), BigDecimal("1.2"), BigDecimal("1.2")]
379
+ rat = [Rational(1), Rational(12, 10), Rational(12, 10), Rational(12, 10)]
375
380
 
376
381
  cursor = @conn.parse("begin :1 := :2; end;")
377
382
 
@@ -382,6 +387,7 @@ EOS
382
387
  cursor[2] = s
383
388
  cursor.exec
384
389
  assert_equal(cursor[1], flt[idx])
390
+ assert_kind_of(Float, cursor[1])
385
391
  end
386
392
 
387
393
  # Fixnum
@@ -391,6 +397,7 @@ EOS
391
397
  cursor[2] = s
392
398
  cursor.exec
393
399
  assert_equal(cursor[1], int[idx])
400
+ assert_kind_of(Fixnum, cursor[1])
394
401
  end
395
402
 
396
403
  # Integer
@@ -400,6 +407,27 @@ EOS
400
407
  cursor[2] = s
401
408
  cursor.exec
402
409
  assert_equal(cursor[1], int[idx])
410
+ assert_kind_of(Integer, cursor[1])
411
+ end
412
+
413
+ # BigDecimal
414
+ cursor.bind_param(1, nil, BigDecimal)
415
+ cursor.bind_param(2, nil, BigDecimal)
416
+ src.each_with_index do |s, idx|
417
+ cursor[2] = s
418
+ cursor.exec
419
+ assert_equal(cursor[1], dec[idx])
420
+ assert_kind_of(BigDecimal, cursor[1])
421
+ end
422
+
423
+ # Rational
424
+ cursor.bind_param(1, nil, Rational)
425
+ cursor.bind_param(2, nil, Rational)
426
+ src.each_with_index do |s, idx|
427
+ cursor[2] = s
428
+ cursor.exec
429
+ assert_equal(cursor[1], rat[idx])
430
+ assert_kind_of(Rational, cursor[1])
403
431
  end
404
432
  end
405
433