pg 1.3.0.rc1 → 1.3.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/binary-gems.yml +14 -9
- data/.github/workflows/source-gem.yml +9 -8
- data/Gemfile +1 -1
- data/History.rdoc +4 -2
- data/Rakefile.cross +2 -1
- data/ext/pg.c +24 -3
- data/ext/pg_connection.c +88 -274
- data/lib/pg/basic_type_registry.rb +1 -1
- data/lib/pg/connection.rb +236 -27
- data/lib/pg/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +27 -19
- metadata.gz.sig +0 -0
data/lib/pg/connection.rb
CHANGED
@@ -364,14 +364,44 @@ class PG::Connection
|
|
364
364
|
end
|
365
365
|
end
|
366
366
|
|
367
|
-
|
368
|
-
|
367
|
+
# call-seq:
|
368
|
+
# conn.get_result() -> PG::Result
|
369
|
+
# conn.get_result() {|pg_result| block }
|
370
|
+
#
|
371
|
+
# Blocks waiting for the next result from a call to
|
372
|
+
# #send_query (or another asynchronous command), and returns
|
373
|
+
# it. Returns +nil+ if no more results are available.
|
374
|
+
#
|
375
|
+
# Note: call this function repeatedly until it returns +nil+, or else
|
376
|
+
# you will not be able to issue further commands.
|
377
|
+
#
|
378
|
+
# If the optional code block is given, it will be passed <i>result</i> as an argument,
|
379
|
+
# and the PG::Result object will automatically be cleared when the block terminates.
|
380
|
+
# In this instance, <code>conn.exec</code> returns the value of the block.
|
381
|
+
def get_result(*args)
|
369
382
|
block
|
370
383
|
sync_get_result
|
371
384
|
end
|
385
|
+
alias async_get_result get_result
|
372
386
|
|
373
|
-
|
374
|
-
|
387
|
+
# call-seq:
|
388
|
+
# conn.get_copy_data( [ nonblock = false [, decoder = nil ]] ) -> Object
|
389
|
+
#
|
390
|
+
# Return one row of data, +nil+
|
391
|
+
# if the copy is done, or +false+ if the call would
|
392
|
+
# block (only possible if _nonblock_ is true).
|
393
|
+
#
|
394
|
+
# If _decoder_ is not set or +nil+, data is returned as binary string.
|
395
|
+
#
|
396
|
+
# If _decoder_ is set to a PG::Coder derivation, the return type depends on this decoder.
|
397
|
+
# PG::TextDecoder::CopyRow decodes the received data fields from one row of PostgreSQL's
|
398
|
+
# COPY text format to an Array of Strings.
|
399
|
+
# Optionally the decoder can type cast the single fields to various Ruby types in one step,
|
400
|
+
# if PG::TextDecoder::CopyRow#type_map is set accordingly.
|
401
|
+
#
|
402
|
+
# See also #copy_data.
|
403
|
+
#
|
404
|
+
def get_copy_data(async=false, decoder=nil)
|
375
405
|
if async
|
376
406
|
return sync_get_copy_data(async, decoder)
|
377
407
|
else
|
@@ -382,56 +412,143 @@ class PG::Connection
|
|
382
412
|
return res
|
383
413
|
end
|
384
414
|
end
|
415
|
+
alias async_get_copy_data get_copy_data
|
385
416
|
|
386
|
-
# In async_api=false mode all send calls run directly on libpq.
|
387
|
-
# Blocking vs. nonblocking state can be changed in libpq.
|
388
|
-
alias sync_setnonblocking setnonblocking
|
389
417
|
|
390
418
|
# In async_api=true mode (default) all send calls run nonblocking.
|
391
419
|
# The difference is that setnonblocking(true) disables automatic handling of would-block cases.
|
392
|
-
|
420
|
+
# In async_api=false mode all send calls run directly on libpq.
|
421
|
+
# Blocking vs. nonblocking state can be changed in libpq.
|
422
|
+
|
423
|
+
# call-seq:
|
424
|
+
# conn.setnonblocking(Boolean) -> nil
|
425
|
+
#
|
426
|
+
# Sets the nonblocking status of the connection.
|
427
|
+
# In the blocking state, calls to #send_query
|
428
|
+
# will block until the message is sent to the server,
|
429
|
+
# but will not wait for the query results.
|
430
|
+
# In the nonblocking state, calls to #send_query
|
431
|
+
# will return an error if the socket is not ready for
|
432
|
+
# writing.
|
433
|
+
# Note: This function does not affect #exec, because
|
434
|
+
# that function doesn't return until the server has
|
435
|
+
# processed the query and returned the results.
|
436
|
+
#
|
437
|
+
# Returns +nil+.
|
438
|
+
def setnonblocking(enabled)
|
393
439
|
singleton_class.async_send_api = !enabled
|
394
440
|
self.flush_data = !enabled
|
395
441
|
sync_setnonblocking(true)
|
396
442
|
end
|
443
|
+
alias async_setnonblocking setnonblocking
|
397
444
|
|
398
445
|
# sync/async isnonblocking methods are switched by async_setnonblocking()
|
399
|
-
|
400
|
-
|
446
|
+
|
447
|
+
# call-seq:
|
448
|
+
# conn.isnonblocking() -> Boolean
|
449
|
+
#
|
450
|
+
# Returns the blocking status of the database connection.
|
451
|
+
# Returns +true+ if the connection is set to nonblocking mode and +false+ if blocking.
|
452
|
+
def isnonblocking
|
401
453
|
false
|
402
454
|
end
|
455
|
+
alias async_isnonblocking isnonblocking
|
456
|
+
alias nonblocking? isnonblocking
|
403
457
|
|
404
|
-
|
405
|
-
|
458
|
+
# call-seq:
|
459
|
+
# conn.put_copy_data( buffer [, encoder] ) -> Boolean
|
460
|
+
#
|
461
|
+
# Transmits _buffer_ as copy data to the server.
|
462
|
+
# Returns true if the data was sent, false if it was
|
463
|
+
# not sent (false is only possible if the connection
|
464
|
+
# is in nonblocking mode, and this command would block).
|
465
|
+
#
|
466
|
+
# _encoder_ can be a PG::Coder derivation (typically PG::TextEncoder::CopyRow).
|
467
|
+
# This encodes the data fields given as _buffer_ from an Array of Strings to
|
468
|
+
# PostgreSQL's COPY text format inclusive proper escaping. Optionally
|
469
|
+
# the encoder can type cast the fields from various Ruby types in one step,
|
470
|
+
# if PG::TextEncoder::CopyRow#type_map is set accordingly.
|
471
|
+
#
|
472
|
+
# Raises an exception if an error occurs.
|
473
|
+
#
|
474
|
+
# See also #copy_data.
|
475
|
+
#
|
476
|
+
def put_copy_data(buffer, encoder=nil)
|
406
477
|
until sync_put_copy_data(buffer, encoder)
|
407
478
|
flush
|
408
479
|
end
|
409
480
|
flush
|
410
481
|
end
|
411
|
-
alias
|
412
|
-
|
482
|
+
alias async_put_copy_data put_copy_data
|
483
|
+
|
484
|
+
# call-seq:
|
485
|
+
# conn.put_copy_end( [ error_message ] ) -> Boolean
|
486
|
+
#
|
487
|
+
# Sends end-of-data indication to the server.
|
488
|
+
#
|
489
|
+
# _error_message_ is an optional parameter, and if set,
|
490
|
+
# forces the COPY command to fail with the string
|
491
|
+
# _error_message_.
|
492
|
+
#
|
493
|
+
# Returns true if the end-of-data was sent, #false* if it was
|
494
|
+
# not sent (*false* is only possible if the connection
|
495
|
+
# is in nonblocking mode, and this command would block).
|
496
|
+
def put_copy_end(*args)
|
413
497
|
until sync_put_copy_end(*args)
|
414
498
|
flush
|
415
499
|
end
|
416
500
|
flush
|
417
501
|
end
|
502
|
+
alias async_put_copy_end put_copy_end
|
418
503
|
|
419
|
-
if method_defined? :
|
420
|
-
|
421
|
-
|
504
|
+
if method_defined? :sync_encrypt_password
|
505
|
+
# call-seq:
|
506
|
+
# conn.encrypt_password( password, username, algorithm=nil ) -> String
|
507
|
+
#
|
508
|
+
# This function is intended to be used by client applications that wish to send commands like <tt>ALTER USER joe PASSWORD 'pwd'</tt>.
|
509
|
+
# It is good practice not to send the original cleartext password in such a command, because it might be exposed in command logs, activity displays, and so on.
|
510
|
+
# Instead, use this function to convert the password to encrypted form before it is sent.
|
511
|
+
#
|
512
|
+
# The +password+ and +username+ arguments are the cleartext password, and the SQL name of the user it is for.
|
513
|
+
# +algorithm+ specifies the encryption algorithm to use to encrypt the password.
|
514
|
+
# Currently supported algorithms are +md5+ and +scram-sha-256+ (+on+ and +off+ are also accepted as aliases for +md5+, for compatibility with older server versions).
|
515
|
+
# Note that support for +scram-sha-256+ was introduced in PostgreSQL version 10, and will not work correctly with older server versions.
|
516
|
+
# If algorithm is omitted or +nil+, this function will query the server for the current value of the +password_encryption+ setting.
|
517
|
+
# That can block, and will fail if the current transaction is aborted, or if the connection is busy executing another query.
|
518
|
+
# If you wish to use the default algorithm for the server but want to avoid blocking, query +password_encryption+ yourself before calling #encrypt_password, and pass that value as the algorithm.
|
519
|
+
#
|
520
|
+
# Return value is the encrypted password.
|
521
|
+
# The caller can assume the string doesn't contain any special characters that would require escaping.
|
522
|
+
#
|
523
|
+
# Available since PostgreSQL-10.
|
524
|
+
# See also corresponding {libpq function}[https://www.postgresql.org/docs/current/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORDCONN].
|
525
|
+
def encrypt_password( password, username, algorithm=nil )
|
422
526
|
algorithm ||= exec("SHOW password_encryption").getvalue(0,0)
|
423
527
|
sync_encrypt_password(password, username, algorithm)
|
424
528
|
end
|
529
|
+
alias async_encrypt_password encrypt_password
|
425
530
|
end
|
426
531
|
|
427
|
-
|
428
|
-
|
532
|
+
# call-seq:
|
533
|
+
# conn.reset()
|
534
|
+
#
|
535
|
+
# Resets the backend connection. This method closes the
|
536
|
+
# backend connection and tries to re-connect.
|
537
|
+
def reset
|
429
538
|
reset_start
|
430
539
|
async_connect_or_reset(:reset_poll)
|
431
540
|
end
|
541
|
+
alias async_reset reset
|
432
542
|
|
433
|
-
|
434
|
-
|
543
|
+
# call-seq:
|
544
|
+
# conn.cancel() -> String
|
545
|
+
#
|
546
|
+
# Requests cancellation of the command currently being
|
547
|
+
# processed.
|
548
|
+
#
|
549
|
+
# Returns +nil+ on success, or a string containing the
|
550
|
+
# error message if a failure occurs.
|
551
|
+
def cancel
|
435
552
|
be_pid = backend_pid
|
436
553
|
be_key = backend_key
|
437
554
|
cancel_request = [0x10, 1234, 5678, be_pid, be_key].pack("NnnNN")
|
@@ -484,6 +601,7 @@ class PG::Connection
|
|
484
601
|
rescue SystemCallError => err
|
485
602
|
err.to_s
|
486
603
|
end
|
604
|
+
alias async_cancel cancel
|
487
605
|
|
488
606
|
private def async_connect_or_reset(poll_meth)
|
489
607
|
# Now grab a reference to the underlying socket so we know when the connection is established
|
@@ -520,9 +638,58 @@ class PG::Connection
|
|
520
638
|
end
|
521
639
|
|
522
640
|
class << self
|
523
|
-
|
524
|
-
|
525
|
-
|
641
|
+
# call-seq:
|
642
|
+
# PG::Connection.new -> conn
|
643
|
+
# PG::Connection.new(connection_hash) -> conn
|
644
|
+
# PG::Connection.new(connection_string) -> conn
|
645
|
+
# PG::Connection.new(host, port, options, tty, dbname, user, password) -> conn
|
646
|
+
#
|
647
|
+
# Create a connection to the specified server.
|
648
|
+
#
|
649
|
+
# +connection_hash+ must be a ruby Hash with connection parameters.
|
650
|
+
# See the {list of valid parameters}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS] in the PostgreSQL documentation.
|
651
|
+
#
|
652
|
+
# There are two accepted formats for +connection_string+: plain <code>keyword = value</code> strings and URIs.
|
653
|
+
# See the documentation of {connection strings}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING].
|
654
|
+
#
|
655
|
+
# The positional parameter form has the same functionality except that the missing parameters will always take on default values. The parameters are:
|
656
|
+
# [+host+]
|
657
|
+
# server hostname
|
658
|
+
# [+port+]
|
659
|
+
# server port number
|
660
|
+
# [+options+]
|
661
|
+
# backend options
|
662
|
+
# [+tty+]
|
663
|
+
# (ignored in all versions of PostgreSQL)
|
664
|
+
# [+dbname+]
|
665
|
+
# connecting database name
|
666
|
+
# [+user+]
|
667
|
+
# login user name
|
668
|
+
# [+password+]
|
669
|
+
# login password
|
670
|
+
#
|
671
|
+
# Examples:
|
672
|
+
#
|
673
|
+
# # Connect using all defaults
|
674
|
+
# PG::Connection.new
|
675
|
+
#
|
676
|
+
# # As a Hash
|
677
|
+
# PG::Connection.new( dbname: 'test', port: 5432 )
|
678
|
+
#
|
679
|
+
# # As a String
|
680
|
+
# PG::Connection.new( "dbname=test port=5432" )
|
681
|
+
#
|
682
|
+
# # As an Array
|
683
|
+
# PG::Connection.new( nil, 5432, nil, nil, 'test', nil, nil )
|
684
|
+
#
|
685
|
+
# # As an URI
|
686
|
+
# PG::Connection.new( "postgresql://user:pass@pgsql.example.com:5432/testdb?sslmode=require" )
|
687
|
+
#
|
688
|
+
# If the Ruby default internal encoding is set (i.e., <code>Encoding.default_internal != nil</code>), the
|
689
|
+
# connection will have its +client_encoding+ set accordingly.
|
690
|
+
#
|
691
|
+
# Raises a PG::Error if the connection fails.
|
692
|
+
def new(*args, **kwargs)
|
526
693
|
conn = PG::Connection.connect_start(*args, **kwargs ) or
|
527
694
|
raise(PG::Error, "Unable to create a new connection")
|
528
695
|
|
@@ -530,9 +697,31 @@ class PG::Connection
|
|
530
697
|
|
531
698
|
conn.send(:async_connect_or_reset, :connect_poll)
|
532
699
|
end
|
700
|
+
alias async_connect new
|
701
|
+
alias connect new
|
702
|
+
alias open new
|
703
|
+
alias setdb new
|
704
|
+
alias setdblogin new
|
533
705
|
|
534
|
-
|
535
|
-
|
706
|
+
# call-seq:
|
707
|
+
# PG::Connection.ping(connection_hash) -> Integer
|
708
|
+
# PG::Connection.ping(connection_string) -> Integer
|
709
|
+
# PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
|
710
|
+
#
|
711
|
+
# Check server status.
|
712
|
+
#
|
713
|
+
# See PG::Connection.new for a description of the parameters.
|
714
|
+
#
|
715
|
+
# Returns one of:
|
716
|
+
# [+PQPING_OK+]
|
717
|
+
# server is accepting connections
|
718
|
+
# [+PQPING_REJECT+]
|
719
|
+
# server is alive but rejecting connections
|
720
|
+
# [+PQPING_NO_RESPONSE+]
|
721
|
+
# could not establish connection
|
722
|
+
# [+PQPING_NO_ATTEMPT+]
|
723
|
+
# connection not attempted (bad params)
|
724
|
+
def ping(*args)
|
536
725
|
if Fiber.respond_to?(:scheduler) && Fiber.scheduler
|
537
726
|
# Run PQping in a second thread to avoid blocking of the scheduler.
|
538
727
|
# Unfortunately there's no nonblocking way to run ping.
|
@@ -541,9 +730,14 @@ class PG::Connection
|
|
541
730
|
sync_ping(*args)
|
542
731
|
end
|
543
732
|
end
|
733
|
+
alias async_ping ping
|
544
734
|
|
545
735
|
REDIRECT_CLASS_METHODS = {
|
546
736
|
:new => [:async_connect, :sync_connect],
|
737
|
+
:connect => [:async_connect, :sync_connect],
|
738
|
+
:open => [:async_connect, :sync_connect],
|
739
|
+
:setdb => [:async_connect, :sync_connect],
|
740
|
+
:setdblogin => [:async_connect, :sync_connect],
|
547
741
|
:ping => [:async_ping, :sync_ping],
|
548
742
|
}
|
549
743
|
|
@@ -586,6 +780,22 @@ class PG::Connection
|
|
586
780
|
end
|
587
781
|
end
|
588
782
|
|
783
|
+
# Switch between sync and async libpq API.
|
784
|
+
#
|
785
|
+
# PG::Connection.async_api = true
|
786
|
+
# this is the default.
|
787
|
+
# It sets an alias from #exec to #async_exec, #reset to #async_reset and so on.
|
788
|
+
#
|
789
|
+
# PG::Connection.async_api = false
|
790
|
+
# sets an alias from #exec to #sync_exec, #reset to #sync_reset and so on.
|
791
|
+
#
|
792
|
+
# pg-1.1.0+ defaults to libpq's async API for query related blocking methods.
|
793
|
+
# pg-1.3.0+ defaults to libpq's async API for all possibly blocking methods.
|
794
|
+
#
|
795
|
+
# _PLEASE_ _NOTE_: This method is not part of the public API and is for debug and development use only.
|
796
|
+
# Do not use this method in production code.
|
797
|
+
# Any issues with the default setting of <tt>async_api=true</tt> should be reported to the maintainers instead.
|
798
|
+
#
|
589
799
|
def async_api=(enable)
|
590
800
|
self.async_send_api = enable
|
591
801
|
REDIRECT_METHODS.each do |ali, (async, sync)|
|
@@ -599,6 +809,5 @@ class PG::Connection
|
|
599
809
|
end
|
600
810
|
end
|
601
811
|
|
602
|
-
# pg-1.1.0+ defaults to libpq's async API for query related blocking methods
|
603
812
|
self.async_api = true
|
604
813
|
end # class PG::Connection
|
data/lib/pg/version.rb
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.0.
|
4
|
+
version: 1.3.0.rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Granger
|
@@ -11,24 +11,32 @@ bindir: bin
|
|
11
11
|
cert_chain:
|
12
12
|
- |
|
13
13
|
-----BEGIN CERTIFICATE-----
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
14
|
+
MIIETTCCArWgAwIBAgIBATANBgkqhkiG9w0BAQsFADAoMSYwJAYDVQQDDB1sYXJz
|
15
|
+
L0RDPWdyZWl6LXJlaW5zZG9yZi9EQz1kZTAeFw0yMTAzMTAyMDIxNDBaFw0yMjAz
|
16
|
+
MTAyMDIxNDBaMCgxJjAkBgNVBAMMHWxhcnMvREM9Z3JlaXotcmVpbnNkb3JmL0RD
|
17
|
+
PWRlMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAoJSMECFMhBiOcic1
|
18
|
+
y1cPjgrfxw/R7wK81sQbiilecqw7zcTRZKzhy7sFQzEF0Wbiy2WmStbktq8cXmet
|
19
|
+
44ZEQI5LtyDhkGl7AFMSows5eMu1ChBdOr45OJsHaidrZfVU2vkkohu2+ZJmcqCB
|
20
|
+
TmjBIxTrKpSjMbL1TFd/C491L/SyKhJq90QMs3OfA12SUBD5wlgdfkQ5ZDi1LNTY
|
21
|
+
rKCOqGaa/zkr7/5BWpsgYcC6ziaA956ktyuQFVUgZgyJTzYStRuYjbDmaZv2sldW
|
22
|
+
zwx3Z2YTEItsdGAoZGbiLNuULmzzwyu8yGaWycpK8l8Al+vMpPaa/dgKUFUkAPjy
|
23
|
+
neRjP+N6qWW0hxt0acdw3nXN5ITxo618dQmMzWdLw94wxsWzSUMGldLfNfu9hFnV
|
24
|
+
zLrh1bFBYdXk6Mg1OnejZFBc2YlzF1u8Us+NNydHR8dMfRcAhp7sPeHOEKH8V6z2
|
25
|
+
VgCHlzf1xq+P+FR8svIqGEBVPHuidr1GguIEqJe7enbjrF2ZAgMBAAGjgYEwfzAJ
|
26
|
+
BgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUZT8nEztzNp6aQDDOuZHX
|
27
|
+
K9PjoW0wIgYDVR0RBBswGYEXbGFyc0BncmVpei1yZWluc2RvcmYuZGUwIgYDVR0S
|
28
|
+
BBswGYEXbGFyc0BncmVpei1yZWluc2RvcmYuZGUwDQYJKoZIhvcNAQELBQADggGB
|
29
|
+
AHZW9LEmp+sptD9VhxGbMSacFwlf03IdkEfmd+7MGzS4nQCQJvs/B5JGTm6q20ML
|
30
|
+
IJXpCnjBPjwnAflyV9rSr9DL2ShuAIJVNWuWs0uhUtz5HEOuV/fVSAFgLHpXP1yV
|
31
|
+
weeoJfLmVeXhRUNo/mH0sjpuRm+C1EVb8QuKFItVa5VBf111Zgn7cFXuOjAtflQ2
|
32
|
+
n3EGZATdVzduNvUENzg6l1Ba+q/nRKKHq5CnG6+1YzOhdzYKFOwlYMi6jLQK33aV
|
33
|
+
aLvq6jWUIpNbL/95ZdOR8Cd6KsCmK5Zvxd5FMMjrQCsZD6OgReshsok5r6tSiNXc
|
34
|
+
YgnBIIAFeoeGz8q+dsm6hPtkii/zr25MPRPmKnbRV7bV/zlbmwpIPxhso95lq3R5
|
35
|
+
H5q2yo7qMajDFkxRffqQO8ONvDLGecxbuv1QEyzNBdehVt4I7UedIfxyOvKd9cg2
|
36
|
+
d5T9wAD7jW/0seVujw+76/YOJWO3Dft+FQmMBbdd8s3J47HbN9R2mDt6fl6he+X/
|
37
|
+
gw==
|
30
38
|
-----END CERTIFICATE-----
|
31
|
-
date:
|
39
|
+
date: 2022-01-08 00:00:00.000000000 Z
|
32
40
|
dependencies: []
|
33
41
|
description: Pg is the Ruby interface to the PostgreSQL RDBMS. It works with PostgreSQL
|
34
42
|
9.3 and later.
|
@@ -169,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
177
|
- !ruby/object:Gem::Version
|
170
178
|
version: 1.3.1
|
171
179
|
requirements: []
|
172
|
-
rubygems_version: 3.2.
|
180
|
+
rubygems_version: 3.2.22
|
173
181
|
signing_key:
|
174
182
|
specification_version: 4
|
175
183
|
summary: Pg is the Ruby interface to the PostgreSQL RDBMS
|
metadata.gz.sig
CHANGED
Binary file
|