diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 224e7f0..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.pc/ diff --git a/README.md b/README.md index 816c366..b7ebf99 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,56 @@ -Slonik Duke +Slonik Duke # PostgreSQL JDBC Driver PostgreSQL JDBC Driver (PgJDBC for short) allows Java programs to connect to a PostgreSQL database using standard, database independent Java code. Is an open source JDBC driver written in Pure Java (Type 4), and communicates in the PostgreSQL native network protocol. ### Status +[![GitHub CI](https://github.com/pgjdbc/pgjdbc/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/pgjdbc/pgjdbc/actions/workflows/main.yml) [![Build status](https://ci.appveyor.com/api/projects/status/d8ucmegnmourohwu/branch/master?svg=true)](https://ci.appveyor.com/project/davecramer/pgjdbc/branch/master) -[![Build Status](https://travis-ci.com/pgjdbc/pgjdbc.svg?branch=master)](https://travis-ci.com/pgjdbc/pgjdbc) +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/pgjdbc/pgjdbc/badge)](https://scorecard.dev/viewer/?uri=github.com/pgjdbc/pgjdbc) [![codecov.io](http://codecov.io/github/pgjdbc/pgjdbc/coverage.svg?branch=master)](http://codecov.io/github/pgjdbc/pgjdbc?branch=master) -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.postgresql/postgresql/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.postgresql/postgresql) -[![Javadocs](http://javadoc.io/badge/org.postgresql/postgresql.svg)](http://javadoc.io/doc/org.postgresql/postgresql) [![License](https://img.shields.io/badge/License-BSD--2--Clause-blue.svg)](https://opensource.org/licenses/BSD-2-Clause) [![Join the chat at https://gitter.im/pgjdbc/pgjdbc](https://badges.gitter.im/pgjdbc/pgjdbc.svg)](https://gitter.im/pgjdbc/pgjdbc?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Maven Central](https://img.shields.io/maven-central/v/org.postgresql/postgresql)](https://maven-badges.herokuapp.com/maven-central/org.postgresql/postgresql) +[![Javadocs](http://javadoc.io/badge/org.postgresql/postgresql.svg)](http://javadoc.io/doc/org.postgresql/postgresql) + ## Supported PostgreSQL and Java versions The current version of the driver should be compatible with **PostgreSQL 8.4 and higher** using the version 3.0 of the protocol and **Java 8** (JDBC 4.2) or above. Unless you have unusual requirements (running old applications or JVMs), this is the driver you should be using. PgJDBC regression tests are run against all PostgreSQL versions since 9.1, including "build PostgreSQL from git master" version. There are other derived forks of PostgreSQL but they have not been certified to run with PgJDBC. If you find a bug or regression on supported versions, please file an [Issue](https://github.com/pgjdbc/pgjdbc/issues). +> **Note:** PgJDBC versions since 42.8.0 are not guaranteed to work with PostgreSQL older than 9.1. + ## Get the Driver -Most people do not need to compile PgJDBC. You can download the precompiled driver (jar) from the [PostgreSQL JDBC site](https://jdbc.postgresql.org/download.html) or using your chosen dependency management tool: +Most people do not need to compile PgJDBC. You can download the precompiled driver (jar) from the [PostgreSQL JDBC site](https://jdbc.postgresql.org/download/) or using your chosen dependency management tool: ### Maven Central -You can search on The Central Repository with GroupId and ArtifactId [![Maven Search](https://img.shields.io/badge/org.postgresql-postgresql-yellow.svg)][mvn-search] for: +You can search on The Central Repository with GroupId and ArtifactId [org.postgresql:postgresql][mvn-search]. -[![Java 8](https://img.shields.io/badge/Java_8-42.3.0-blue.svg)][mvn-jre8] -```xml - - org.postgresql - postgresql - 42.3.0 - -``` +[![Maven Central](https://img.shields.io/maven-central/v/org.postgresql/postgresql)](https://central.sonatype.com/artifact/org.postgresql/postgresql) -[mvn-search]: https://search.maven.org/search?q=g:org.postgresql%20AND%20a:postgresql "Search on Maven Central" -[mvn-jre8]: https://search.maven.org/artifact/org.postgresql/postgresql/42.3.0/jar - -#### Development snapshots -Snapshot builds (builds from `master` branch) are also deployed to Maven Central, so you can test current development version (test some bugfix) using: ```xml + + + org.postgresql postgresql - 42.3.1-SNAPSHOT + LATEST ``` +[mvn-search]: https://central.sonatype.com/artifact/org.postgresql/postgresql "Search on Maven Central" + +#### Development snapshots +Snapshot builds (builds from `master` branch) are also deployed to OSS Sonatype Snapshot Repository, so you can test current development version (test some bugfix) by enabling the repository and using the latest [SNAPSHOT](https://oss.sonatype.org/content/repositories/snapshots/org/postgresql/postgresql/) version. + There are also available (snapshot) binary RPMs in [Fedora's Copr repository](https://copr.fedorainfracloud.org/coprs/g/pgjdbc/pgjdbc-travis/). ---------------------------------------------------- ## Documentation -For more information you can read [the PgJDBC driver documentation](https://jdbc.postgresql.org/documentation/head/) or for general JDBC documentation please refer to [The Java™ Tutorials](http://docs.oracle.com/javase/tutorial/jdbc/). +For more information you can read [the PgJDBC driver documentation](https://jdbc.postgresql.org/documentation/) or for general JDBC documentation please refer to [The Java™ Tutorials](http://docs.oracle.com/javase/tutorial/jdbc/). ### Driver and DataSource class @@ -70,6 +70,7 @@ jdbc:postgresql://host/database jdbc:postgresql://host/ jdbc:postgresql://host:port/database jdbc:postgresql://host:port/ +jdbc:postgresql://?service=myservice ``` The general format for a JDBC URL for connecting to a PostgreSQL server is as follows, with items in square brackets ([ ]) being optional: ``` @@ -82,63 +83,88 @@ where: * **database** (Optional) is the database name. Defaults to the same name as the *user name* used in the connection. * **propertyX** (Optional) is one or more option connection properties. For more information see *Connection properties*. +### Logging +PgJDBC uses java.util.logging for logging. +To configure log levels and control log output destination (e.g. file or console), configure your java.util.logging properties accordingly for the org.postgresql logger. +Note that the most detailed log levels, "`FINEST`", may include sensitive information such as connection details, query SQL, or command parameters. + #### Connection Properties In addition to the standard connection parameters the driver supports a number of additional properties which can be used to specify additional driver behaviour specific to PostgreSQL™. These properties may be specified in either the connection URL or an additional Properties object parameter to DriverManager.getConnection. -| Property | Type | Default | Description | -| ----------------------------- | ------- | :-----: | ------------- | -| user | String | null | The database user on whose behalf the connection is being made. | -| password | String | null | The database user's password. | -| options | String | null | Specify 'options' connection initialization parameter. | -| ssl | Boolean | false | Control use of SSL (true value causes SSL to be required) | -| sslfactory | String | null | Provide a SSLSocketFactory class when using SSL. | -| sslfactoryarg (deprecated) | String | null | Argument forwarded to constructor of SSLSocketFactory class. | -| sslmode | String | prefer | Controls the preference for opening using an SSL encrypted connection. | -| sslcert | String | null | The location of the client's SSL certificate | -| sslkey | String | null | The location of the client's PKCS#8 SSL key | -| sslrootcert | String | null | The location of the root certificate for authenticating the server. | -| sslhostnameverifier | String | null | The name of a class (for use in [Class.forName(String)](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#forName%28java.lang.String%29)) that implements javax.net.ssl.HostnameVerifier and can verify the server hostname. | -| sslpasswordcallback | String | null | The name of a class (for use in [Class.forName(String)](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#forName%28java.lang.String%29)) that implements javax.security.auth.callback.CallbackHandler and can handle PasswordCallback for the ssl password. | -| sslpassword | String | null | The password for the client's ssl key (ignored if sslpasswordcallback is set) | -| sendBufferSize | Integer | -1 | Socket write buffer size | -| receiveBufferSize | Integer | -1 | Socket read buffer size | -| loggerLevel | String | null | Logger level of the driver using java.util.logging. Allowed values: OFF, DEBUG or TRACE. | -| loggerFile | String | null | File name output of the Logger, if set, the Logger will use a FileHandler to write to a specified file. If the parameter is not set or the file can't be created the ConsoleHandler will be used instead. | -| logServerErrorDetail | Boolean | true | Allows server error detail (such as sql statements and values) to be logged and passed on in exceptions. Setting to false will mask these errors so they won't be exposed to users, or logs. | -| allowEncodingChanges | Boolean | false | Allow for changes in client_encoding | -| logUnclosedConnections | Boolean | false | When connections that are not explicitly closed are garbage collected, log the stacktrace from the opening of the connection to trace the leak source | -| binaryTransferEnable | String | "" | Comma separated list of types to enable binary transfer. Either OID numbers or names | -| binaryTransferDisable | String | "" | Comma separated list of types to disable binary transfer. Either OID numbers or names. Overrides values in the driver default set and values set with binaryTransferEnable. | -| prepareThreshold | Integer | 5 | Statement prepare threshold. A value of -1 stands for forceBinary | -| preparedStatementCacheQueries | Integer | 256 | Specifies the maximum number of entries in per-connection cache of prepared statements. A value of 0 disables the cache. | -| preparedStatementCacheSizeMiB | Integer | 5 | Specifies the maximum size (in megabytes) of a per-connection prepared statement cache. A value of 0 disables the cache. | -| defaultRowFetchSize | Integer | 0 | Positive number of rows that should be fetched from the database when more rows are needed for ResultSet by each fetch iteration | -| loginTimeout | Integer | 0 | Specify how long to wait for establishment of a database connection.| -| connectTimeout | Integer | 10 | The timeout value used for socket connect operations. | -| socketTimeout | Integer | 0 | The timeout value used for socket read operations. | -| tcpKeepAlive | Boolean | false | Enable or disable TCP keep-alive. | -| ApplicationName | String | PostgreSQL JDBC Driver | The application name (require server version >= 9.0). If assumeMinServerVersion is set to >= 9.0 this will be sent in the startup packets, otherwise after the connection is made | -| readOnly | Boolean | false | Puts this connection in read-only mode | -| disableColumnSanitiser | Boolean | false | Enable optimization that disables column name sanitiser | -| assumeMinServerVersion | String | null | Assume the server is at least that version | -| currentSchema | String | null | Specify the schema (or several schema separated by commas) to be set in the search-path | -| targetServerType | String | any | Specifies what kind of server to connect, possible values: any, master, slave (deprecated), secondary, preferSlave (deprecated), preferSecondary | -| hostRecheckSeconds | Integer | 10 | Specifies period (seconds) after which the host status is checked again in case it has changed | -| loadBalanceHosts | Boolean | false | If disabled hosts are connected in the given order. If enabled hosts are chosen randomly from the set of suitable candidates | -| socketFactory | String | null | Specify a socket factory for socket creation | -| socketFactoryArg (deprecated) | String | null | Argument forwarded to constructor of SocketFactory class. | -| autosave | String | never | Specifies what the driver should do if a query fails, possible values: always, never, conservative | -| cleanupSavepoints | Boolean | false | In Autosave mode the driver sets a SAVEPOINT for every query. It is possible to exhaust the server shared buffers. Setting this to true will release each SAVEPOINT at the cost of an additional round trip. | -| preferQueryMode | String | extended | Specifies which mode is used to execute queries to database, possible values: extended, extendedForPrepared, extendedCacheEverything, simple | -| reWriteBatchedInserts | Boolean | false | Enable optimization to rewrite and collapse compatible INSERT statements that are batched. | -| escapeSyntaxCallMode | String | select | Specifies how JDBC escape call syntax is transformed into underlying SQL (CALL/SELECT), for invoking procedures or functions (requires server version >= 11), possible values: select, callIfNoReturn, call | -| maxResultBuffer | String | null | Specifies size of result buffer in bytes, which can't be exceeded during reading result set. Can be specified as particular size (i.e. "100", "200M" "2G") or as percent of max heap memory (i.e. "10p", "20pct", "50percent") | -| gssEncMode | String | allow | Controls the preference for using GSSAPI encryption for the connection, values are disable, allow, prefer, and require | -| adaptiveFetch | Boolean | false | Specifies if number of rows fetched in ResultSet by each fetch iteration should be dynamic. Number of rows will be calculated by dividing maxResultBuffer size into max row size observed so far. Requires declaring maxResultBuffer and defaultRowFetchSize for first iteration. -| adaptiveFetchMinimum | Integer | 0 | Specifies minimum number of rows, which can be calculated by adaptiveFetch. Number of rows used by adaptiveFetch cannot go below this value. -| adaptiveFetchMaximum | Integer | -1 | Specifies maximum number of rows, which can be calculated by adaptiveFetch. Number of rows used by adaptiveFetch cannot go above this value. Any negative number set as adaptiveFetchMaximum is used by adaptiveFetch as infinity number of rows. -| localSocketAddress | String | null | Hostname or IP address given to explicitly configure the interface that the driver will bind the client side of the TCP/IP connection to when connecting. -| quoteReturningIdentifiers | Boolean | true | By default we double quote returning identifiers. Some ORM's already quote them. Switch allows them to turn this off +| Property | Type | Default | Description | +|-------------------------------| -- |:-----------------------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| user | String | null | The database user on whose behalf the connection is being made. | +| password | String | null | The database user's password. | +| options | String | null | Specify 'options' connection initialization parameter. | +| service | String | null | Specify 'service' name described in pg_service.conf file. References: [The Connection Service File](https://www.postgresql.org/docs/current/libpq-pgservice.html) and [The Password File](https://www.postgresql.org/docs/current/libpq-pgpass.html). 'service' file can provide all properties including 'hostname=', 'port=' and 'dbname='. | +| ssl | Boolean | false | Control use of SSL (true value causes SSL to be required) | +| sslfactory | String | org.postgresql.ssl.LibPQFactory | Provide a SSLSocketFactory class when using SSL. | +| sslfactoryarg (deprecated) | String | null | Argument forwarded to constructor of SSLSocketFactory class. | +| sslmode | String | prefer | Controls the preference for opening using an SSL encrypted connection. | +| sslcert | String | null | The location of the client's SSL certificate | +| sslkey | String | null | The location of the client's PKCS#8 or PKCS#12 SSL key, for PKCS the extension must be .p12 or .pfx and the alias must be `user` | +| sslrootcert | String | null | The location of the root certificate for authenticating the server. | +| sslhostnameverifier | String | null | The name of a class (for use in [Class.forName(String)](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#forName%28java.lang.String%29)) that implements javax.net.ssl.HostnameVerifier and can verify the server hostname. | +| sslpasswordcallback | String | null | The name of a class (for use in [Class.forName(String)](https://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#forName%28java.lang.String%29)) that implements javax.security.auth.callback.CallbackHandler and can handle PasswordCallback for the ssl password. | +| sslpassword | String | null | The password for the client's ssl key (ignored if sslpasswordcallback is set) | +| sslnegotiation | String | postgres | Determines if ALPN ssl negotiation will be used or not. Set to `direct` to choose ALPN. | +| sendBufferSize | Integer | -1 | Socket write buffer size | +| maxSendBufferSize | Integer | 65536 | Maximum amount of bytes buffered before sending to the backend. pgjdbc uses `least(maxSendBufferSize, greatest(8192, SO_SNDBUF))` to determine the buffer size. | +| receiveBufferSize | Integer | -1 | Socket read buffer size | +| logServerErrorDetail | Boolean | true | Allows server error detail (such as sql statements and values) to be logged and passed on in exceptions. Setting to false will mask these errors so they won't be exposed to users, or logs. | +| allowEncodingChanges | Boolean | false | Allow for changes in client_encoding | +| logUnclosedConnections | Boolean | false | When connections that are not explicitly closed are garbage collected, log the stacktrace from the opening of the connection to trace the leak source | +| binaryTransfer | Boolean | true | Enable binary transfer for supported built-in types if possible. Setting this to false disables any binary transfer unless it's individually activated for each type with `binaryTransferEnable`. Whether it is possible to use binary transfer at all depends on server side prepared statements (see `prepareThreshold` ). | +| binaryTransferEnable | String | "" | Comma separated list of types to enable binary transfer. Either OID numbers or names. | +| binaryTransferDisable | String | "" | Comma separated list of types to disable binary transfer. Either OID numbers or names. Overrides values in the driver default set and values set with binaryTransferEnable. | +| prepareThreshold | Integer | 5 | Determine the number of `PreparedStatement` executions required before switching over to use server side prepared statements. The default is five, meaning start using server side prepared statements on the fifth execution of the same `PreparedStatement` object. A value of -1 activates server side prepared statements and forces binary transfer for enabled types (see `binaryTransfer` ). | +| preparedStatementCacheQueries | Integer | 256 | Specifies the maximum number of entries in per-connection cache of prepared statements. A value of 0 disables the cache. | +| preparedStatementCacheSizeMiB | Integer | 5 | Specifies the maximum size (in megabytes) of a per-connection prepared statement cache. A value of 0 disables the cache. | +| defaultRowFetchSize | Integer | 0 | Positive number of rows that should be fetched from the database when more rows are needed for ResultSet by each fetch iteration | +| queryTimeout | Integer | 0 | The timeout value in seconds that the driver will wait for a query to execute if not explicitly set by [Statement.setQueryTimeout(int)](https://docs.oracle.com/javase/6/docs/api/java/sql/Statement.html#setQueryTimeout%28int%29)). A value of 0 means no timeout. | +| loginTimeout | Integer | 0 | Specify how long in seconds max(2147484) to wait for establishment of a database connection. | +| connectTimeout | Integer | 10 | The timeout value in seconds max(2147484) used for socket connect operations. | +| socketTimeout | Integer | 0 | The timeout value in seconds max(2147484) used for socket read operations. | +| cancelSignalTimeout | Integer | 10 | The timeout that is used for sending cancel command. | +| sslResponseTimeout | Integer | 5000 | Socket timeout in milliseconds waiting for a response from a request for SSL upgrade from the server. | +| tcpKeepAlive | Boolean | false | Enable or disable TCP keep-alive. | +| tcpNoDelay | Boolean | true | Enable or disable TCP no delay. | +| ApplicationName | String | PostgreSQL JDBC Driver | The application name (require server version >= 9.0). If assumeMinServerVersion is set to >= 9.0 this will be sent in the startup packets, otherwise after the connection is made | +| readOnly | Boolean | false | Puts this connection in read-only mode | +| readOnlyMode | String | transaction | Specifies the behavior when a connection is set to be read only, possible values: ignore, transaction, always | +| disableColumnSanitiser | Boolean | false | Enable optimization that disables column name sanitiser | +| assumeMinServerVersion | String | null | Assume the server is at least that version | +| currentSchema | String | null | Specify the schema (or several schema separated by commas) to be set in the search-path | +| targetServerType | String | any | Specifies what kind of server to connect, possible values: any, master, slave (deprecated), secondary, preferSlave (deprecated), preferSecondary, preferPrimary | +| hostRecheckSeconds | Integer | 10 | Specifies period (seconds) after which the host status is checked again in case it has changed | +| loadBalanceHosts | Boolean | false | If disabled hosts are connected in the given order. If enabled hosts are chosen randomly from the set of suitable candidates | +| socketFactory | String | null | Specify a socket factory for socket creation | +| socketFactoryArg (deprecated) | String | null | Argument forwarded to constructor of SocketFactory class. | +| autosave | String | never | Specifies what the driver should do if a query fails, possible values: always, never, conservative | +| cleanupSavepoints | Boolean | false | In Autosave mode the driver sets a SAVEPOINT for every query. It is possible to exhaust the server shared buffers. Setting this to true will release each SAVEPOINT at the cost of an additional round trip. | +| preferQueryMode | String | extended | Specifies which mode is used to execute queries to database, possible values: extended, extendedForPrepared, extendedCacheEverything, simple | +| reWriteBatchedInserts | Boolean | false | Enable optimization to rewrite and collapse compatible INSERT statements that are batched. | +| escapeSyntaxCallMode | String | select | Specifies how JDBC escape call syntax is transformed into underlying SQL (CALL/SELECT), for invoking procedures or functions (requires server version >= 11), possible values: select, callIfNoReturn, call | +| maxResultBuffer | String | null | Specifies size of result buffer in bytes, which can't be exceeded during reading result set. Can be specified as particular size (i.e. "100", "200M" "2G") or as percent of max heap memory (i.e. "10p", "20pct", "50percent") | +| gssLib | String | auto | Permissible values are auto (default, see below), sspi (force SSPI) or gssapi (force GSSAPI-JSSE). | +| gssResponseTimeout | Integer | 5000 | Socket timeout in milliseconds waiting for a response from a request for GSS encrypted connection from the server. | +| gssEncMode | String | allow | Controls the preference for using GSSAPI encryption for the connection, values are disable, allow, prefer, and require | +| useSpnego | String | false | Use SPNEGO in SSPI authentication requests | +| adaptiveFetch | Boolean | false | Specifies if number of rows fetched in ResultSet by each fetch iteration should be dynamic. Number of rows will be calculated by dividing maxResultBuffer size into max row size observed so far. Requires declaring maxResultBuffer and defaultRowFetchSize for first iteration. | +| adaptiveFetchMinimum | Integer | 0 | Specifies minimum number of rows, which can be calculated by adaptiveFetch. Number of rows used by adaptiveFetch cannot go below this value. | +| adaptiveFetchMaximum | Integer | -1 | Specifies maximum number of rows, which can be calculated by adaptiveFetch. Number of rows used by adaptiveFetch cannot go above this value. Any negative number set as adaptiveFetchMaximum is used by adaptiveFetch as infinity number of rows. | +| localSocketAddress | String | null | Hostname or IP address given to explicitly configure the interface that the driver will bind the client side of the TCP/IP connection to when connecting. | +| quoteReturningIdentifiers | Boolean | true | By default we double quote returning identifiers. Some ORM's already quote them. Switch allows them to turn this off | +| requireAuth | String | null | Comma-separated list of acceptable authentication methods. Use '!' prefix to reject methods (e.g., '!password' to reject cleartext). Supported: password, md5, gss, sspi, scram-sha-256, none. Cannot mix positive and negative options. | +| authenticationPluginClassName | String | null | Fully qualified class name of the class implementing the AuthenticationPlugin interface. If this is null, the password value in the connection properties will be used. | +| unknownLength | Integer | Integer.MAX_LENGTH | Specifies the length to return for types of unknown length | +| stringtype | String | null | Specify the type to use when binding `PreparedStatement` parameters set via `setString()` | +| channelBinding | String | prefer | This option controls the client's use of channel binding. `require` means that the connection must employ channel binding, `prefer` means that the client will choose channel binding if available, and `disable` prevents the use of channel binding. | + +#### System Properties +| Property | Type | Default | Description | +|-------------------------------| -- |:-----------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| pgjdbc.config.cleanup.thread.ttl | long | 30000 | The driver has an internal cleanup thread which monitors and cleans up unclosed connections. This property sets the duration (in milliseconds) the cleanup thread will keep running if there is nothing to clean up. | ## Contributing For information on how to contribute to the project see the [Contributing Guidelines](CONTRIBUTING.md) diff --git a/build.properties b/build.properties index a62afc7..2a07a04 100644 --- a/build.properties +++ b/build.properties @@ -1,22 +1,18 @@ # Default build parameters. These may be overridden by local configuration # settings in build.local.properties. -# loggerLevel can be OFF, DEBUG, TRACE # -server=localhost -port=5432 +test.url.PGHOST=localhost +test.url.PGPORT=5432 secondaryServer1=localhost secondaryPort1=5433 secondaryServer2=localhost secondaryPort2=5434 -database=test -username=test +test.url.PGDBNAME=test +user=test password=test privilegedUser=postgres privilegedPassword= sspiusername=testsspi preparethreshold=5 -loggerLevel=OFF -loggerFile=target/pgjdbc-tests.log -protocolVersion=0 sslpassword=sslpwd diff --git a/certdir/Makefile b/certdir/Makefile index c29f605..9f5e010 100644 --- a/certdir/Makefile +++ b/certdir/Makefile @@ -5,21 +5,17 @@ SERVER_CRT_DIR=server/ all : $(SERVER_CRT_DIR)root.key $(SERVER_CRT_DIR)root.crt $(SERVER_CRT_DIR)server.crt goodroot.crt goodclient badclient -goodclient: goodclient.crt goodclient.pk8 goodclient.p12 +goodclient: goodclient.crt goodclient.p12 -badclient: badclient.crt badclient.pk8 badclient.p12 +badclient: badclient.crt badclient.p12 .PHONY: clean clean: @echo Removing certificate files - @rm -f *.crt *.key *.csr *.srl *.p12 *.pk8 + @rm -f *.crt *.key *.csr *.srl *.p12 @rm -rf $(SERVER_CRT_DIR)*.crt $(SERVER_CRT_DIR)*.key $(SERVER_CRT_DIR)*.csr $(SERVER_CRT_DIR)*.srl $(SERVER_CRT_DIR)*.p12 $(SERVER_CRT_DIR)*.pk8 @echo -%.pk8 : %.key - @echo Exporting key $@ - openssl pkcs8 -topk8 -in $< -out $@ -outform DER -v1 PBE-MD5-DES -passout pass:$(PK8_PASSWORD) - %.p12 : %.crt @echo Exporting certificate $@ openssl pkcs12 -export -in $< -inkey $*.key -out $@ -name user -CAfile $(SERVER_CRT_DIR)root.crt -caname local -passout pass:$(P12_PASSWORD) diff --git a/certdir/badclient.crt b/certdir/badclient.crt index 37f6436..c7ccb67 100644 --- a/certdir/badclient.crt +++ b/certdir/badclient.crt @@ -1,24 +1,26 @@ -----BEGIN CERTIFICATE----- -MIIEEjCCAfoCFBBZnyyHCR5q6FiHA+joyxjVXLQSMA0GCSqGSIb3DQEBCwUAMEsx -CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UECgwLUGdKZGJjIHRlc3Qx -GTAXBgNVBAMMEHJvb3QgY2VydGlmaWNhdGUwHhcNMjEwNjI3MTM1NjM2WhcNMzEw -NjI1MTM1NjM2WjBAMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFTATBgNVBAoM -DFBnSmRiYyB0ZXN0czENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMlX9z5tQZL9tJLCCXvFZ83ShKhby+IbVOVEywkerlivA+CJ -WIKxSkyoUUWCsmdw4R5JSKETBCt5XzImhUxnvaKX1bFZwh61gQJ2lfCp55eFJ6Nz -asiZ9PJFrnR9XXhtjw5TtMQrRmDcb8xKuT7FKIRRPND+u8ocEtgqiUUocHUVD8Ln -p2iHgX/5O9TCtnByfCcHDp9BNFZPZkIP/yvoVFpZdQzKDa1GkFhQ4dF0Rlm8O6pu -osIK39AZ6uyNkrrAGdPv1gZCCB2CKrWOQglhr13CWKRrYyyowsYrJ6I5qg8V6XBq -tS4PwqKpOIjNyhg/LiHcAaj8O/aUUOzl7IjhcRMCAwEAATANBgkqhkiG9w0BAQsF -AAOCAgEAxvZkk5lJG94Sbla8z7NL675Kry9zJZcIOXUXxlL8ywa0bj8BLyASsU4s -96em+5Zj/4elOZtwlNM6YMN0WqT49v9i0tOKJ3R830pGzY6I40YYTpPHNBziIEol -HplelhWTdrOyf4mjAA4DS9adyaU1zUbv0hIEHTLogsrDzwGQJADltPdo9fVK53GM -uyfr84evQcnQH+SjX8h5QPlD+My2eBCcxAvBPsGW9J8N6A7wihSS9Bhpx73apLkW -dJZm7rERMmnfHd+n+zlPvOfR36WbjWBlocIku/xO03iBo5fRyUh56HYVXIYRHoD9 -5MNLhMxdaqTepPUxc/vnuoD/ThNfqNmkF1t7CF8ASct/KV9EvtBjYSIR+yR+nIao -r5Hxmaw6wRDG+2BUoCBYyYg3/jDe+cE5jur+VmC9sgQRtLX7FjQWYeFyi1hX+gB8 -iv7CU9a7oDAeGGAqM+pBiZ16OCQSmZPKam3zhyhONHo0PFMq5oSqwOkgpjTUeYza -cCAWDJtXqZbaogVQpN32gkEonLMGalLCEU+f9peODeULGoxZZV8jVbBUEoDkS1i3 -i329YTUJWoF6D/OwhHqPd5UIf9EJKBd/hTP5bbmalPmJQvlzsXtvFCPw3+f4bZxD -OAKO599JHNkYWa8Q5ML5IWg7NKh/BX0PITGkv2WwqBLeTMCqgig= +MIIEWzCCAkOgAwIBAgIUR+OPYmMSaGoE6zA0kLvTqHT7JiUwDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQKDAtQZ0pkYmMg +dGVzdDEZMBcGA1UEAwwQcm9vdCBjZXJ0aWZpY2F0ZTAeFw0yNDA1MjIxMzIyMjRa +Fw0zNDA1MjAxMzIyMjRaMEAxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEVMBMG +A1UECgwMUGdKZGJjIHRlc3RzMQ0wCwYDVQQDDAR0ZXN0MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEArCpUDNmaR+0Qc5Mahqv829bsio6JUwPE6KjIe/Gz +6xfX6aQR798hh/uTvjByj8glzMTSFyC7T77Y7Jbi8RaN6HIcgtY8YOTyLmHSqSDf +G/vWZ+XoLd31b2NWhnEwNXGs5cCKQxUpsI19PQHlVlNq6xGSweEZVWsiAtbSCwT4 +rCeXxwgj/ggMUigqj6ExblAOas95ukU1d29iGYhcmp83Dro9ItLmM9P6yBibAAO2 +bJXiMVRdG5UHT8q+cF1gglefRPAjiHwufJN9PpXSGV1v6dwIlfop0DILMNGhtUA8 +VSnosibIo8tpIw+FwV6Ok4V8oO4LzAPoXCycVEHrwKsdhwIDAQABo0IwQDAdBgNV +HQ4EFgQUBhZzEBoFjAn7B0ETEN6bw1xpveMwHwYDVR0jBBgwFoAUuD+A0QzRyFb/ +JUXY2iY9lC7ZMVYwDQYJKoZIhvcNAQELBQADggIBAHerogpLNAdbfig4aPXGEnP/ +KadlVN+jOMbG+DF74SmOvT+7RPY1p6wgNjJGYVLNpxUBamtEWCPmf3J1kCZDOCxz +nVwRmVcUMcCEW4NN7ciDGs6pbEJkl28OGx/NMg2WCg2mtCZotxABwhfEA4FTwuab +KNhS/4ZeehcNPm7FApeCyNBeP/M4icHu4TLttjCxOvKUJ1ExuzgxvoQcbjxTN+zA +5Bzifc+LsLkuilmPf5aUYy1pK4LTF6raYuerMGfF9dIJPWfDq9RkM/ZyH0otyD8S +he5s9uR1aXFdY0/BcmrFpC9WbBSB/0sBQWAxnLLiop9tjzvw/ibHPoE1TD+n0ssW +4mU0mGJu4xp2UMpexFhmXx3hrBCITdAmQvf0S/RoxVbFSn9GlqGcvgqqOSwP33e2 +wuSFELzVH1RNHyx063b3EHnUdEIEq9CiPhtowh0LeR/jCeVvyxLddhIBlxRSz/To +sFHzOAmdXbH8+smoZ9ajU/yku1bU1IyNjqhUMb/bQMx7VsLgOeSsah5cFI0iFWbn +QYya4c6ueBo0kp+HQ4jnUlM8/vFWY+tDnwnCEdNbID63DBKOomLOGDXHyE2pTx1u +zRdpNQP5s5bQ7tLGZEW00eic/rTD9ipfGdQNEFh6727eWeerGM1LQmblv2Hjq9se +2zvv+MEAPJD1lFWMPGDB -----END CERTIFICATE----- diff --git a/certdir/badclient.key b/certdir/badclient.key index 55de3cd..f200fa7 100644 --- a/certdir/badclient.key +++ b/certdir/badclient.key @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAyVf3Pm1Bkv20ksIJe8VnzdKEqFvL4htU5UTLCR6uWK8D4IlY -grFKTKhRRYKyZ3DhHklIoRMEK3lfMiaFTGe9opfVsVnCHrWBAnaV8Knnl4Uno3Nq -yJn08kWudH1deG2PDlO0xCtGYNxvzEq5PsUohFE80P67yhwS2CqJRShwdRUPwuen -aIeBf/k71MK2cHJ8JwcOn0E0Vk9mQg//K+hUWll1DMoNrUaQWFDh0XRGWbw7qm6i -wgrf0Bnq7I2SusAZ0+/WBkIIHYIqtY5CCWGvXcJYpGtjLKjCxisnojmqDxXpcGq1 -Lg/Coqk4iM3KGD8uIdwBqPw79pRQ7OXsiOFxEwIDAQABAoIBAQCqmAW8twWgbaXZ -0t4GKLRTB9OucljFMzMzLp55E63VJjS6wqRj16OEX/i8VIikbFfROXZ4Q4x86VFn -RIwcuKlMYimFIu/+5PpyA9f3GX5IO2Hic6A+Z3PK8o9l0/KmXu2ezf2TWLdAyoVP -KuDZ9mLl+Y715V9nV3IABcpY9nKSP80RGhWT9rPQjFG1rLxWHDVpFhkPulSibiM3 -s6zW0MYoAZzIokLZyiIU36Z49arZvMwVnbrNd+mQrwiPnsJdPJYuI8rWXiviHsWx -g3wlZuMtDvxQhpSuxY9JupHUX3Z8jiagtXxKsb4vYZe+EenkuG83ZOXRs1u3O8ub -67mERwhBAoGBAOzXNnmLSzAsM0zBaqx+b43awF1etwkzAt7SwWiEwYj/YcCPOKXc -QBDB0hUjAxce9cFa02EBcpcyUOOxAy99gsFt2//t0ImSuuIJBsFhE1bBRnI/H2sX -Z16p+vuY+NYBr9DfwOYWmHXbdBIxQ4UnQ4RnZ51wBi6gbiCxbMmqZow/AoGBANmh -oulrbxsKOrFcvzB5Z2HJ9G9afT1V9mICXJ6lGN3nTkhXXMIo0bO1Bp8hBC4D5WUq -kXspzrnz8IYA07zFYIPoQ0ygDgycgbAXL/3eZfwqgfqu5oTNGnPJ0kpI1RT7yNog -7YnmT5xKi+al1WYRZFEZ2aVv1ikrsy0isPzhWrYtAoGAZjbLb9FJ9dRdn3aqDx/S -DSqncqR54iJ9zqSui+kfjXyKN4yYKhzQGWtMu4qMvuHBtlz4dRkm11IDzwCKG2jT -kZ9UHzQHmBgXR44VuEepDPwE5zGO4a0ME7LQet0eJ146/q5SlfqSeeroQSG5vjGf -1fi6oxvBz0W1wa5RAQflkgkCgYEA1qOwBP7MaaLBGEQc3DYgXDXOOjTI6EFr6mXh -6yVxTQngD0D6XxPbHp4flbn+YVO+XvSI3yvwkz2frsoKClewROhB18TTlmSVE5MK -5hr+AqH557+v4rJWsHQQTuteHH/nLBrlq+fWBJMRP722ph/pDIOuQJf4ZEqRQKbC -X+XyjVECgYEAjXt6uwSi6ovZoxsjjFIok9EeHf8VbsOsQN33iJkWRZFp/fxmy0Qw -lSwb2ByNOgP0XZ1qIlMpzkxkIaHSp5iwF1rPwT+4lXiUcyoLK/GtR2oD+KZ5UJK3 -PgEBk5eL+UrhI9eiedGQjGQIjqYJ2zl0BgDJMXHvbjFluchxkTwAtsA= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCsKlQM2ZpH7RBz +kxqGq/zb1uyKjolTA8ToqMh78bPrF9fppBHv3yGH+5O+MHKPyCXMxNIXILtPvtjs +luLxFo3ochyC1jxg5PIuYdKpIN8b+9Zn5egt3fVvY1aGcTA1cazlwIpDFSmwjX09 +AeVWU2rrEZLB4RlVayIC1tILBPisJ5fHCCP+CAxSKCqPoTFuUA5qz3m6RTV3b2IZ +iFyanzcOuj0i0uYz0/rIGJsAA7ZsleIxVF0blQdPyr5wXWCCV59E8COIfC58k30+ +ldIZXW/p3AiV+inQMgsw0aG1QDxVKeiyJsijy2kjD4XBXo6ThXyg7gvMA+hcLJxU +QevAqx2HAgMBAAECggEASx4a1xJ4jMpkI7E64SiYZokTYu8G/rgFyaee9elDIZ+h +Hrlt9BWMiUrcrx5vVcmzqHD1445t+8De3/eDpEI9f36Obw2vgW1TN6aJ5i5H+ms6 +pNF8qMqP5F5Y6cboDGGBWStViChrLLPHQX+awzwZSeA8gqhGx+y7BbcaJSGxqFbU +wbjh+or2VfkQmJsVX2dAwcEzvI5qKaYjPuVZJEB8rsxTGNO8g1i3pCsGMAs/zJn8 +wG2q3S3PNVvJByqzFvLDueT8PC54mnu/8Dg9a12eTw9kKM238iXgFKg5ngyL5M3h +3mR1RmR+kK/dz+Y4jFMwOv0B+j0/QoaK+j/NbvMYfQKBgQDZsl02P+0Lb7j6PWIj +m2fyX4CwNl3DiOj0ejqKAjNv+KMKJOCiCvBb/cH8e240qRBi8MM4iLv1O5Cs5rnT +OZhVC0Bhal687SIFFqVezCMLw5r2n9xLOxWbz7xwlerxdHCaD3oCBWCME8BiqlQj +jtQPB9iN+WYBtyl+qzf1xA2ojQKBgQDKdRwzqhZt/zoyWh5gZ4IT7tIGoi0wjJDQ +en1GxkxfN9n43WCupKsHqOFgASvccpzbLhWueXqMW+KGXN974hUnhwcT9Tis5S0h +C7thsMzrbb2B2fPNeZQT7k0Rce4ScN60sKpH8D5MCEFE7LPnRf833u5sAkwYU1Z6 +xDnY/bBrYwKBgQCPiERg4mEeKzlg9FvicjJk4Ybtp8rGg1xk5Ln4CF91sg99PrdE +38V+QZ9HIvKUkeuV8HwmqDCVDgLjnPRkShMf710xXXq9QfQuOrIqAJoSxZNEjD4o +8/nZU2xBJvdS35zmVtHwxy7S7Krp4re7Ag7bIicr5IXXF7aqJ6WryuneeQKBgGdx ++xzy1U8Sz9uWbFr5yF8C8dFt4AUkbYH4dDZLcA5e7ULZamMRxEm/oph5QL382eSr +4WwqqdE5yb65iX80/3YY6ibqbzD4UFzIL9A0lB/fDKtif84HRwAADOTS/7/wZ/qP +IhJr0Ijs1tyuzSVogIU9pTkO2266dj94L86NoSzdAoGARhmXyNLpLB2mRgH4lEVz +Ln/eIzX6U/h8ihX4IbDMmKjt3eFmBpUrz0ka1EO3WlwtSkQmEtIVU7hjyeQAHtGe +md7ASwLKqUdhhVpmrA7AUl1FPWpVLSWHpO1ojszhxudhDY+qwJJmGzOU+HpnEwoO +0yV9m/amwgabrEbX9W1pa/g= +-----END PRIVATE KEY----- diff --git a/certdir/badclient.p12 b/certdir/badclient.p12 index 046ebde..09b5dc3 100644 Binary files a/certdir/badclient.p12 and b/certdir/badclient.p12 differ diff --git a/certdir/badclient.pk8 b/certdir/badclient.pk8 deleted file mode 100644 index aea112f..0000000 Binary files a/certdir/badclient.pk8 and /dev/null differ diff --git a/certdir/badroot.crt b/certdir/badroot.crt index 197462a..e1050d1 100644 --- a/certdir/badroot.crt +++ b/certdir/badroot.crt @@ -1,32 +1,32 @@ -----BEGIN CERTIFICATE----- -MIIFdzCCA1+gAwIBAgIUEjAlC/uP068o5ssD61kJIeqlil8wDQYJKoZIhvcNAQEL +MIIFdzCCA1+gAwIBAgIUPK+ZieDBMlPNBV5Afk+Q8CFwG8AwDQYJKoZIhvcNAQEL BQAwSzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQKDAtQZ0pkYmMg -dGVzdDEZMBcGA1UEAwwQcm9vdCBjZXJ0aWZpY2F0ZTAeFw0yMTA2MjcxMzU2MzZa -Fw0zMTA2MjUxMzU2MzZaMEsxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIG +dGVzdDEZMBcGA1UEAwwQcm9vdCBjZXJ0aWZpY2F0ZTAeFw0yNDA1MjIxMzIyMjRa +Fw0zNDA1MjAxMzIyMjRaMEsxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIG A1UECgwLUGdKZGJjIHRlc3QxGTAXBgNVBAMMEHJvb3QgY2VydGlmaWNhdGUwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDfA9I8At3NfZC3EPeHzGGu8zvy -oCP45pVi/26ZLYcJZvD6hh0vaV8c0HlE7eKiHtfEC6IIPDD/w5heu0OIKMstrNZ4 -t7VsUikDmQpfV2G0yHqO1/A9HVM8DZWn4o2Ahg4qslmFpxpVyLG7E8Qk/3LjcfYC -EvtLdO/09zrsSfB2t+TiNQozOa8i3FuCdZ/tEI3+a5I8iMm7C7PDsJwTeVNExkJH -flLa7Sc5HF845K2/8hxmQg83MVELG2d3IKaCWI5jgtlghlE/egxU57+Iien1w76K -/jnvlADcOkTozRzPvV1QcNFekxNjKiL1pJ5B+c59HIeOGQTzun8P2/UGzipUWc+x -FN93r7lNX25kMC4d9a2pBBV1jIrxRXx6jU0SbjG++VwJ/LqJcCQS8x9a9+7tpcqb -OCE5P4bd2ez+J2694RpdW6WclgreDYmpWrHI1HZp4aGRxslmrgedK7i42iluX1P+ -1DEsqXeAuk+2VNgg0XTtkqVD0DNTkV22KF/liPfP5aw0EDEzVY3gywbkP54DOowu -+yqdzyIEhyxmiyI41H3AnnGQ4sg2QJR9VVqKVjnkH4V/sSAx17YXgWruYgZGFiFY -4BdPYWb/3Gqo7yjO/yw/QZsm5Sebbc6f43X0gmY8Bbgze+28NFV4AI8cBV8jrLa2 -3aytLIW8bvesh4lofQIDAQABo1MwUTAdBgNVHQ4EFgQUvdZn8MeAbNNGQRpYQ1At -Sv6xiwEwHwYDVR0jBBgwFoAUvdZn8MeAbNNGQRpYQ1AtSv6xiwEwDwYDVR0TAQH/ -BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAR8PaXvjR8tQBcF/bXJIghxqBZkyO -wAfT/wUXrKcX0OuLdK0kv8IRtnSxVvSc4X15pqvc+yZEDYDVFgjBxwO43ax6V0DX -7j1Co3jRoqjrZHlc7XMTF25EcdCif79NXQLdxTNiGCKdunSMm9rNz8A52BnxoBqM -XciQUjbWeDD2Zst1/8jTakOAJQHQa5GbFMSvE1Qoke8+epb7l8sj0327O3YXxTQL -u4c0Z1duJehy8u9Tfu6yBCiYh4H3OD/MJ5ah95GH0AjXW6g4AqR3lov/9gkc4qx/ -BMrewtidFZ8mkG4cgPZ5AcqJKDkaFqXHBfIwstLc1GcvmZumhv2lH4YdkKWCSuyX -w4sW8Ii4XvUFvzXMCuS5jTP3qe42kDZPuoLxrRgiBHxSX6coEroORAdXeu3/1iS+ -MTz/Dypq7VPE2h9xWtzJb7YFsLg342x5YxW59G+vtBy0u4tf6AoomZk0EME68P7H -VicKoFBEdTVudn4Ts1jhcfahBMwgD7b1d59eQLCAST/5vmEBTdpzCtzIzf7gjH9s -rgPZ6nDJcFhHi0BjUbMiKh2wqvOL6erpwtbDOTccFfEYWMT3uteOaMsPZLNMeq6R -xKM8NitvBXuqIZOcgrrpABwek+/XdRnHqpsFTCNzPfHzP8/p/6tp7+DbZ1IF/uqz -3HcpkqolJfg6Rlo= +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDMd5u75xLNZBUPO+C9ctEiuSol +4O2Wby1mMJMBbiVNAuE8SgJlo8ghzhETwagkB7fXVUrCWFS21nLu54I7coLtm8Jx +D8xjN2DV4oZZl/+u1r0QEOl2dIsIEWFXaZq32JKY9tiEW7nGghUAbi26HIpbR/eE +OCJtKU58BgU0pRA68ufUV2FfwvlCsOEgNr3ZTtrqFa9l/orlifDVXpgUkPoFCw08 +TeOFABkyoB8T2H/sZVk/WfvnH5nxG3fqE+1y8HaoG5iKS6JQIH66K8Q9DR98cMKw +9tEZf0F1gM0Y0feBsBI9dD3kPhBlNsv1zfggcz4NoKNj5cjfod1qnWx61TG/zJNG +nGmgsMbHNACF3tTUNVVN1flcpVWz6RILODBoS6E2Mm4+3wOWXbEVuGCp1s+4Tetv +QlIBYvNJZTuDsvmDRb4q6TkhpcBm1m853/g4KBZJf/eBmVsUoi7w0Pp1fzbtgkCH +vtaj+1bT8N9xbS2EkINeG0qSvM/WZPq5cL06nVdbN3hub+lMC0cPGKN9P0uZHkhO +EzV8a2C9buVTqu5/8Ge5P2CRHKlRVI1IwkvtsrAc5wkW9ct4gPjdXNnISrZncjCt +h9gooUthso58BSZr82B8Lcgfmg7WuMaG969xfK5XhDk0lkOhFCG6jDSJHMP4G9d5 +b3sC/nOQv7jWzyKKVwIDAQABo1MwUTAdBgNVHQ4EFgQUuD+A0QzRyFb/JUXY2iY9 +lC7ZMVYwHwYDVR0jBBgwFoAUuD+A0QzRyFb/JUXY2iY9lC7ZMVYwDwYDVR0TAQH/ +BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAchqLUBR5upauJvCD38iQiqx0OtcS +OCP8fE2fOHNGYbMJyJLp/fRmE0+nneyU63+7xaXAPbfqE1T/CX0cNd0Eoa5SXEcB +65bl7iSISL9Ehk1i7Xc/Hu7aEOWGv29Utt+7mwXTHdRTZ38TB5cPMI88xVETR9c9 +3gZZd4b269ne0orfBdt2dG7u+TKl+UiOr7ti5Aa1FIqa6Tou/3W4OXXidW/zH8cT +yIglY7OBsgSDuz+N6ySaNvNFsAuP8vgZBSezkZFPeKzV72eLmBTnzN+33GLmDZTo +0c7VNcl8KHyrcZP4Yjd02Itj6VZdeyffhGoNf1IkAc6eDdIKsF0UEXSNE7eRBBif +F9bSUwbpePBzbGwam82lGvDRf+9K7NZDbObF3Ocvzmu44E6QoyXnLO/R+C9Awdfe +GsjsL89xoQhtoWTTJS0198qG+USc1sT0+N8j6FWCWtEy1IIzuA+R0YbXTCAuZOlk +D9/Te9xcZkx4lt+laLaqJWIxES+FbayNMrke8jxQeusdlRBnH5SfRBqnHmydyseW +xnUtePvhuDBQtGWul9CkJv9uI/K56m9xDvVHvzl8TxMwnU8oXNtT3BxenYG2u4k/ +tWJKGwHRgq1TNhlQ/1ZxQiHR+rDoqAxKoze+yIojlcVyF9mRhvV470aMXIXxlhDt +HMrstAWa6L1TdEw= -----END CERTIFICATE----- diff --git a/certdir/badroot.key b/certdir/badroot.key index 1b7034c..53b2cbd 100644 --- a/certdir/badroot.key +++ b/certdir/badroot.key @@ -1,54 +1,54 @@ ------BEGIN RSA PRIVATE KEY----- -Proc-Type: 4,ENCRYPTED -DEK-Info: AES-256-CBC,BF4CD6E429942664D50549895DA4C8D8 - -ZkfDkMDMgXCddN7U9REjV9twFyj0NzEOItYzo7NI8vRGCxeBpul87bmziH7tkPGo -R9NGCJdyP5i3lZyZBBd9s7tT75GOlZoMRO9ZfEf6hzTjwbnc0fxWbyBV4UTh/+ld -Fxou2kKDXaKMkeLOBllnv3DKOJU4d4gwtqolumJ+d6/ZCcMRkxSFjvByQrF3aM7L -KffaD9TBzKXkGT+l7oBANsVLwhejqQ0UWX1oZb5WYHMPvjozB+/sEhiGJAm1ITsZ -4RZC8T874sfBUHianKrbFagvMqX76tZUOCIzGxkXuqPnX/eeNn2h73Y9aVRlXG68 -OkzqE93a6ttY0FMZ5el8NDxE/YzhJscD5kN6b1Yg3/X9bjTe+NR0afz/LS1YVkwR -G9wZpRu+Mstbc3hnlO75JkH//qSHW/mljwE5z9iLPh058pjIopPWLJyCo4HVhcdW -SOTaSczCIq1Yq7PT12fllWBP1Rlf2ZP63XnZmPO+FzflUB25eLBuStTLPR6ueXpR -H1lSHB4d2KtgA7/IdOxKJJoN/YbhD+zJQmWfvbQvzyRUi5yuBChDSR5dVXJe0AQu -6G1xxpReVOoJvcISh7/I7Y7mHsVVRMuvsTnXE3GkrtZhNQYVsKzig4xMWEiyRaZX -sPIoUs967wY0W3p1jXFpYjpLL09aHwylngSVGrxNO0CO18GiDySO3Cl5m0XZjg7Y -0TCzY+J+PGROnTaOF6pD32A69SQZmcwk1qNCvEsu1QeF0zh+FzPz+mV5S89wuAqY -nRIm71LwwJzwO0bGlwbo9IzDcHmYjGkxLBEportDtNyrV+2pHGzQbtLeMETYh/aM -FMceQo1y9VzydNgMhTg02PgU0K2mpBS5mbxk05El3B/iaEdGFi4RIm3DWF/N97y/ -8XkAh8iKXLihNcUlUpIn5l6PewhHuW3Wp+n+juGYKfp28FFfz/9nbipUsDSsWmKY -uaTANuL0G43ed37whSS/zpDxj4+UCsf22mfCbNkkb5SyPdSkb9zZntSPR9V1z98W -rIIOMvRzE1WApIUAwr8fgKE1t1rl+QUDmExfxws03hcdmOM7givWC4jV5PtStlnv -2UGNV1BIUHlRAzBolv4/spwA73F71glqiLnPboBx/tngwXTjidxZivcYGDcWu/+V -Uad4aIlw0gddJlK08rAObTd18mwbJ61roD6haNALVuRdiiSY3QfXY4rFcP7Hr7a3 -WUP0rN2tNxM9kCY/nTIkNVXL6cJ5pDh8ZV1vkb2c6RvEgDzrghwBx9h1hY5f4BEy -lHY/PK6tmK1Gt4zPFRomia8Ihgg8UHDVgiVbt6zgfb58Q0w9pr2410oX4r4GNmt/ -6LZWIIOSuwdIIRtNzszsPixLTjwJaC4/9ul+VYb7ab6C/3ncv3XD10fe/1vK2uTQ -MkcXpTt6lou/qNCc3K8wio4bLUWU+8C3n3MzMiZTWi0XP7alawLh6QqIGNPkS1+N -ogHuGD2DmqB48pYcmeKGDCQeAQSqRbUIxagFWL946z6rjCssrjTCQURztB8nEcn2 -5YunsZ/DCUT6Sc++U6HqaNXemmg5W9HZsrErdRGrtX2kV9c9gdeqzcGRwXR3gWGW -vFXCDG2sw9JonJUbDrDCfhIoU4CB89XVu6LlDrjx4F37j+hEpz34HdrbPNtiUh4x -WmvBQoYYa3PSs6JxJ7gKdRsu8cZqKiHBQqs6lsZhVQ3e5CDrXcKaVy/FuGf2EXps -sHA4FCnbj4rxCAcqvd1Rx9KhVoxMTvsEQrzs6fEifMiVjv6uCrRWimU08Vnteq9z -wghlj1CcwLyb2ohRmC8KdVRNQCCAZHiP/JtNOvrCf4tmu4v3/WE/7o4i8qFGMbwU -RUMHiDC5d8qmUt2F+WGTHuULdI+NRkayFAFH5rI9XUhVnT8W57fM5xOaF5y4VwRY -L4mXrgpHdTb8KmJptnOowpbQ+WLLzQyQS97EXZSyzX+2FwBtXmtvhsvwUokJsf6i -MECwariyP1JKnsnVgY3zFGCYhVHhynxmo0JqDgdYJEa0ph9LsnHgp0uaSc+atdGg -trHyncURaIV7YvkWO1rDdVSS8rHKEXFLSGUnPFISwa27H6xWz2ch8De1u0TKVLbM -8D3UFBhl/hSi/8BVNAvcYuJ7Yvyt2IkyULMnWVHLSeIZi/pCYaYvZV2r27XaCUxL -PfvmW+y1wGgNukVU0vcyMKF3upKr6s9YKk+A6g6doUP/+EDWkIl7FEGhV1NZTIJ6 -olqj4MtpSXyZZ6vzfvAiOHrY0nXM0p4HjF/7B+Hd67i+eyTD2KZPda/KvllZHI9P -8BqduzULQqgzbskKty7kPdLuhWRGyompwcGCaqFe/EhPD/C3EL1mcL8hH68Ywdb2 -2V8Tg6gNWXxmtXewv3wVysix9s4uzPBUUnyluBwHShYoUgLEpPZqU0vBubeZ6/ID -lsxijw92xcxEa3qaudEBF97xmL+DeW7Mx2q2nR1qDLyBPrw41D6gyTLw1X8tBQ3m -zczLZzOn/egEDCXK3LE+kYYAkZ4s9+sYSnNyVGc+nrFyZU13V/1JCIdsJFuXFRKb -/ht6Mwtl9D/ZO+S7A+lDUkZGE3hLsj+44fifJfb0cUuo53vKcqClQRDRYej03e3h -Gy9lieHFDMXmG/DXDpmDbsclPEE43613cdxqDT5cYVNBX/2GUblcrRDUzysr7S5Z -OLRYEAAGvlLrefNtTYcTt3kG1W8y53SGZYsCdTjkqRt0v7JzAvOeH/EVPAeSwGbQ -jF7uVj3P8kZAEEQlqrHdJ/XkD9J0dQ+jGkVBWqH+9oHFesjAf53aCazHJFCqIFs9 -EqIY5/xXOXYUTZj6e9UR2QtxQxaFvOb7rnn6Pk9Cj3wg+pOsAY6Xi0/fgy+ti43k -TM5/eEAxElXUEbqCL4+miH8g0pOCv1BuKlhre5yZb6In6jmErc9KvvA+jabn2u3d -moPov5+c5g5o/yqKgAFmysTCDg3WUjPOtNLNdlX/0G2vqABTgR+oOVAd/pA26nck -qxIwtmARFzpDAtmv95gEQSjQAyx0wjItAtvexusxX12gptxgunCiYuko9H1GYLy2 -TyEzs/HBOO3bFl6bsiFGqiHSjgvMF/x3eGDq8S3ZdYrk/BByeXqWmp35p8mV9rwt ------END RSA PRIVATE KEY----- +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIJtTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQuhvXmfvdzyRgPB8S +cOGFpwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEECm/MFfP4xPaFlgH +b2qKZqIEgglQb/xM/BE+hbUWMUhOAL3hmhSNplxCDrTQtWpE4OB0RYXKxxhbbIFZ +2mSiYFuDiNdaMqVgvRe10Mf1/qG1CgeEBSjE5r/wdcXmg0fOVprRZGVpiDryIWSx +fbJXfIRhm5Jn0Zf7wZnliJMvCRGwYUqI+fZuJ8EaCpNmsrUUK5V8H1+pjO2qcdIt +kDd6TtzdtcdD5AatV3IRH+dm4r9kDBPJUCIdvuxtmlftu0n4bHkwW+x21Vbk63Ij +AMUu+x35kvHe5omQAa3F0gveE0fhUKjP3DS6Jq0iDC6iMCsQnxyPkyvsnrwMOM7F +lX4WvefQg3Cemh6ow5tSnshQQIC09BqJ3QVv0SUyVfNRnEK9fnbGEb8+H9QAy2Eb +cInxSf+m46VH9Nf6L2J0nlansku4uwtmwGTt7lCJ2avxKInuBkbBo6sjE8dlZ6IZ +uCyXdldaOCutO3H4sIbUnQeKXZLIJKgnTLolAcE/c+fdN0XfLCXsXlobP8ZvoNYP +OVcZ/5g7/8GsAgZm7EgKCpY6W1HnbEy5xJBQX3feVg4Z4l3uKheDvdLRTnmcSDNh +diuv/YyvTxd/+hyB5sjbXH3VB5ajHvFIpOTTQtnLJDUd5xGzIsvnDUTK+OeEQSqK +vPngJbwZ35a1bDafQmfoHzExXd8W4FAHEBboBoe8Ah58i04mJDX9faYfpjB3Wej7 +G3X9tmkPxczIhXxKDZez34VPUhoM9Rrw3W/gLvHcR7E2v5meUzP6SSu9ItKY4CM0 +FGdjnPGjkEik/J+cPidhO0CGVfjGR+ipjsGXbJj1w04TfFwDckllajvnMlgID3ct +k+h9uD3h5sShT4fF2SxyGUorJwhOTvmQyUx6C0DMTHOcCJrKhtmQKAbZ3azfGBNw +qwyCSIu0OwQDshRUiQjAdF/rTHVvsOBFU25yjiQTi5cVal5TIGOdfmCP79/MUcn0 +qxdwHCjhBHRTib+k9N0R1zdc/NEMo2IWz6VBx8F/xSsRYfAv4/8XOyX4I2lySTlx +tcTp0mt/TTpDd/MwOz1KW5G3ANdiBYlN6aPnl2snC2Z7um2zlZsYojtYhgdEov0D +O+4rrO7i5+EwVPDRbFQWs2/3mcRvvm3mDci2riFJWhmvJ7pilMxXkixvEWgge4yW +LD4PP/32YcJIyajkaMyiOgvB0lZbVCGd49KKgDIpVXAa7g0LTz6FWngxDjRcWp5z +fO6Lx/C6LcJ7OX/OAukhUW91npeFAGfLWWoW1AYNeKmS/LMmHrkSrcf+M6OM4RIJ +3S708OvXpM1myyh3F7f/Z3UK+ASmwIbbTKjtC3Dp+oyMpzTzSC7n8+IS8mJgn/e7 +0FF9dnrNInHC2delVRFdAMjUJjye8/saqyOjwMVhWhfH7ZsQD88ZOVwk201wtT6K +gTp5xO38Lp37pjxmLWQMrPBWe/ngPrx6XeZ9+mBjcnzoACwcIy5JTQSrKafoLMkI +/ut3OSqUAeGQNKSWoCziXFudY1oRNe+oh847sv0olcvogfoTAp7BKjVLNY+gIFnD +xmzrUGrMKiUp7Nog/Mif9YXoT7nBKdGDIoSuF3081hKk5jqfc3nVXtjOEYnxXGzA +UPjkNqYE9AmMlwMldhHs3aVugMA4Sp3YTZ3JTI5hasJIa3yrxAGsu0EzMtFZLXaX +EF5tAfs3Im9eUkATP3UcLTTRncveXB3CDzKTU066y+Fjb2XgcMW2c7WOqMqBE8AR +ev9vNSvLSRK6WJYxRRM4ENr+u+XGvzBF/Tw9WAEK/UGaN0tHSuhlFtnPBcfyr028 +m/vlZ8OCCK2kUW5/3dB/Hp20pjlUB3Y97XnH9SL6/BlOZSn/Lu94IxbEWmuBPFU+ +KFqqCYU2TpU/RZvrREpthAvDELsG9Tk0Y4gLWrpBttEPbRyz6Jh/2fyFquXd4nqj +WVwxjJMWy8TGODws4zSIjaQLJHRcwTlFac+Rx55JPPaUYuZzFQpWGYq8CHUIL8aa +GVBHE+y39WWMhZtGgqDD7j6+40hHSq92yT/Oya9FfbHlb7kZeY5gzXVAiRhZBKMp +k/iJdPvbwHM6z6T5O/Q9WhxbNChhJOibAEMgPMusqp427VjSHyoRbvXtslsoQJbF +iNj33K0FDSk1anhRJmak8+JEjuhwxLTY+fLQNWBAaH/AKUcntxWjXN9+lqZI8BXm +yG2EFLKXGfS5UB6Frv1yVX3Ywu6lms90V+XUYJjxiHOLz1nYY2Zoze7PmVbZXURl +AIA6SvE7p8YsasX6LOZGqyhRC/uC41Ckp0SZHhAsR0NNtBH90I7mnpQrvBr8RgX0 +p4BexzXZXyuUD/LigGPZHQCpjs6kNFrmMse7QbSYWub6YHpdUHEclGjTX4MOqTVp +zFGBnY6L1umeNnBgdS0/Uh6ENGUrShSIJEddrOUwV9XpiYnQqlcrK512tR3aANT8 +mOXp+86PGeKDInXQVSYOgZy3e7U3h83wWG5ff5nbpT/v5DnLMmEJgFXXRguCbNVm +ITGQOVz3t02Bm9XuatHoKogSsv8MwLPNC99Hv9ow3sR8rKxKvesZcL60+kQ7x1a1 +iW5liamf3629v0yWMNt9b7lELWb/+OoC1HexJXubAFTYIZWsyWftiJmRBgQFylQS +bKFLdtzmzPNFvzbrEGP3DBAuKhi1uLaBMdAp8uvREjTe/zd73LbNEFk4uyOBVlRr +MoeRwIIee+lgoZAc5m/MGnmdiDcIvzBP0u3ddkZgFZKa+z1zkxj3kj8CoDRO4aJU +d9MtFiZwF4LF8KFP0/fYM/odsQTdDPeDcAZXQhk0Zu7DmoYc+0DNYABW+tMP9Wdl +ZljRS1xCAGpBjuFciyGhptPs7XZuu6QKbs5/aeePPfvwbI5xACIoyloPQeT8ftD+ +uVAseex2qtoeG4sv1QAgGxuqpJz4KBbjC74lXJputV5G2yGeC7bTqZ4QJk5ERgNt +ORozS3tfLPTGYtWiZc+Cqr9bIuKqj1HbHpW59N5tZuKVZQG89KBAheo9SVsItCR6 +A6ifbqFcS9gwLMuHJgRErL1pfRWgjSZ8ZZ7JjIUv02CvC8M85v129rHnlORCwB4u +7QIYUySdAzBUPxQ3GYFx59yE6oAJRQVanJzjc6dFqmueU5+7MG4jlV9skpqXW5Oq +5hFj/z7oPZBYK+MxdPCbFDg4mXrBVP4vaIMIXSSpgjXOJmz3L3IS0ro= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/certdir/badroot.srl b/certdir/badroot.srl index 3706e3e..ec2b19e 100644 --- a/certdir/badroot.srl +++ b/certdir/badroot.srl @@ -1 +1 @@ -10599F2C87091E6AE8588703E8E8CB18D55CB412 +47E38F626312686A04EB303490BBD3A874FB2625 diff --git a/certdir/goodclient.crt b/certdir/goodclient.crt index 1757ffe..b443a41 100644 --- a/certdir/goodclient.crt +++ b/certdir/goodclient.crt @@ -1,24 +1,26 @@ -----BEGIN CERTIFICATE----- -MIIEEjCCAfoCFFTo+fMF4VshhRLB9KPR+UJYCMqjMA0GCSqGSIb3DQEBCwUAMEsx -CzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UECgwLUGdKZGJjIHRlc3Qx -GTAXBgNVBAMMEHJvb3QgY2VydGlmaWNhdGUwHhcNMjEwNjI3MTM1NjM1WhcNMzEw -NjI1MTM1NjM1WjBAMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFTATBgNVBAoM -DFBnSmRiYyB0ZXN0czENMAsGA1UEAwwEdGVzdDCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKnLmQi5Trz7ulayCwiGWjQY+w0UwW5w3Tn8dwao+xSqN8BR -51MVmXo3zT15lLSx8KHpIZKWskxzJM9TXsajBru9lChCLEzhWhdwzZbOGTsP5ZPT -vNxx6wzgd54krqbWkvjLY2KMknraXFiJiXZWCAlPj2wkVaVeSkj9Ti9PRQ3BoSzB -8h2OX+VU6VuFP33UZjBN+9yKJJK0b1JQt3HXLYGXctf0GBRW00HDofT/tUkfHiA1 -ILqMi1WjL00oLwH4l/R3kTCsHXldj7WrzYE6egmowr81TrRNLIzoYO+Op+b4C85n -dx8HhNKYCXJbC7VVfsVoPt9QO1Rr+XuyE1ixoXsCAwEAATANBgkqhkiG9w0BAQsF -AAOCAgEAUfOjofRMPgFkZYV0wbMNMqQixc3nNKdEB0AcpTr54YQN5i5d4DuY2ITd -Bf4j8ftKj22IyjI6XqDkU50aACyeTJ5jE6sXNu18AkOyItVUWfuIZy8Y4EnhFzgs -q181AVZzpaWnn+JuEq2jCUk3+LPDlrI+OOSMAEUkhHxDESD/g2Le+53cQrXLHZ2x -zUIua9n/P6GA7Yp/tvdBVaBJhAjmunKx8PsbJycI1vzQqkrd/8vd+jdHfF2JMkIK -uAzrN7xhPolLPsk2dva8S5EoCzKEvFi1T5GrdLcyfWV0AWEJlekfOe2UsHR7MMZB -dsh8knsXzkSCdc8oLHz6/iIHioLTPdr6v3jQopvQOtVwkQLHAJY2plG7jJwhFczt -Ja4sJ7zquKGRM4CFq1inwYTVvK+NKzwM/fIbsP56j+n59i3kyOoyL5wsfyBUWNNt -puMjFgG0o/pzWYHxqqW1WB8uC+qWVfJTKpdl2v1sic9TPml/gBndplqR5tZiU1XU -J5/SyLkmLkjNZPRrrgKb9sq0OrxlkX5nAZoEwfS9QPj/cO1X5KcojlcDa77B1WCj -0X9n38eN5Q5gbCfBRFM7nHeUlTJExJYiaFKe7khfADJ6VOE44QOTrC+pO8FnNspc -Kd0z1axhTs5wVGx5m02u+noYLW79nkk5ePdP2Wj/QF8TG+MroVE= +MIIEWzCCAkOgAwIBAgIUNatI3x5V51YqkMcR8RL5oJ/Ql+8wDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQKDAtQZ0pkYmMg +dGVzdDEZMBcGA1UEAwwQcm9vdCBjZXJ0aWZpY2F0ZTAeFw0yNDA1MjIxMzIyMjNa +Fw0zNDA1MjAxMzIyMjNaMEAxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEVMBMG +A1UECgwMUGdKZGJjIHRlc3RzMQ0wCwYDVQQDDAR0ZXN0MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA1jm3rb8DxnbRzAFQpOP23/Dd60evOG+5UgxrI6Pg +jPSMW1gv/2p2lEnyLCgwL/L0rA/cNOlIvCM1LWWaxcnS9bbVWXg9P15Z3jg+g6AI +IKDuNtRaXMiViiczRXWGZ5N22oB+evpZ3MEqsZTPyay6wgOaVPe3yLnuAsOP/ynk +YH9dbYxAgvWaSSdfxMitg3OvTeLAlvF7iwifTSipxbGkiWVJ4F8YPz67WndfVety +e6UjKqsCN4d/xcOxgTDeiYTnK2KPpvHXzGQfDbW1AJNzB+wYLNcf2H2osMkdp9AX +WrZrj4CWn7pGjlLZMJ484ojLsVgIarvk/fu5UjbTj6plbQIDAQABo0IwQDAdBgNV +HQ4EFgQUFTuc0Lqkt2a1Tuo/rzpsC9ikeWowHwYDVR0jBBgwFoAU3gF40GtF0Ev/ +pMgBWvmUsEsMYikwDQYJKoZIhvcNAQELBQADggIBABSNE3GER7YR5k0I0fxIghVi +NGWNT/4V8I1ZqXpWjAAwbYYyQqdotUOYErp49kg5BawLsOnu2P8vfSXl0K9djQJC +rF5zfzGBfQ7JZJJ+kru99hc7nrr8q3OM8KVdoktvtn/wINCa0CBfspqzzo/xr0iQ +sPoeZQBAX4YLzRAQtwLo8PQKGF4POnwsvtHfo1U6b2CEZPBiP6Ny4d7sD+6s+fXT +8E5NBU61T/6t+RDg530TvEZk1ZRwbgXthfV/+xEHxx25AwAN32gaRUBzjyazihkx +WIv0IrVqjf9UI00ZmFCozmyI5dsfv5zFdlgTbl4uLuK/kq3J+UnGdOjtTkJNtUii +rlGDDdmUuJ+Qga3BaiTD82G2evb9nBxqu6TE8Vb60oVWwNVXH/Pw3YAlYHMvPgKn +/dQI3WVbCQDMPoNZkvJtDeb+fUoMBC1XxsfY2zCrcQIiSYdaxuHw+P9v/7iudevb +h8yUnrIEQyKLIIv0GwoMDd0+QCb1iqH9eVFWkZ20gYbO97jpYd/6E9/3Gpig+suD +sZbBcRxQwQaGRjZRIgoUD/Cy2A0nPbJW6Y6VK2n2Uzqow/HkoeN/4gmCZglx7dr5 +wI0mCgzUuKyi5pC5+Y6L7E/Kex43YVFRuuf4anfCx3qh5TLk51lChziSxdSvJbM6 +ms0XG+kxC5r6JbLHab/u -----END CERTIFICATE----- diff --git a/certdir/goodclient.key b/certdir/goodclient.key index 45e3bd8..249c52d 100644 --- a/certdir/goodclient.key +++ b/certdir/goodclient.key @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAqcuZCLlOvPu6VrILCIZaNBj7DRTBbnDdOfx3Bqj7FKo3wFHn -UxWZejfNPXmUtLHwoekhkpayTHMkz1NexqMGu72UKEIsTOFaF3DNls4ZOw/lk9O8 -3HHrDOB3niSuptaS+MtjYoySetpcWImJdlYICU+PbCRVpV5KSP1OL09FDcGhLMHy -HY5f5VTpW4U/fdRmME373IokkrRvUlC3cdctgZdy1/QYFFbTQcOh9P+1SR8eIDUg -uoyLVaMvTSgvAfiX9HeRMKwdeV2PtavNgTp6CajCvzVOtE0sjOhg746n5vgLzmd3 -HweE0pgJclsLtVV+xWg+31A7VGv5e7ITWLGhewIDAQABAoIBAGWYIaf7oKYpBHSN -MVbXdK+JZuWLzrUzKNtiOSGuCBV0R8l1+DBZUyyyqMYoshZwBagLIwBf1K0zlAnN -O38k2omIxFZWScVybQJrh0e9Z5FUUKxj27QXIWniARCJqErBRs1AfOXhcF+7uddr -6+j3TvWrOgidsPUydsY3cgmSp6GziKU0nyRpGq6U/M965GsMZyXOLMtlmcR/qn3q -bIp59tYQDASwW26xDleEIrObdHIEOIC/g1gcl0oQD0IzcTCXFc8sI2eUvX/ED9tN -hIAyx5VGuLlkMz6/qtnyAR9BZ8mNqYRSxxTLH3CD40+bTcnTEgEfZfzj++utB5ai -0PKsU1ECgYEA3mjLtQMrRBiKJY/LUBj6M6iwlkrOy9N5OgthKmDvh2kXoPRiM6tO -F78ZE58n0nUK3QvCoC10fP7xt8hYxjPabET40CuxWySdeMHac9y7nAAdrnIE0iqe -MR3/NZzF4HVEIS8CDXgz0nOmY7zTi4D5Tv0lP3NPCE1twGYHaec0FMkCgYEAw3CK -JRkHEeem+StZtNw7kWFUdP+wGNo5PJDJPTedgn1HmlcRmCoLYf7BftupkqmdHqYn -1ESTjv2QiQhs9uIvO8AXPd8OCX6OC4gil2j5UPoMneNHZazwDcYwyS313T/a7GsF -5HEGURsceZzcXjdy8HzbAwzBPB38kCMbKAHTeiMCgYA15duH8E6p3/CKjcBNlt/7 -aOPyaAqZZpQ4Ns3DQV6KyDMLtG2f6+Gu64aeNLGn1OlfDByMSLe0GuxlB05MKgDC -wCwz9oKyfbsTqpbQASwN8BFBVyxH6kAP0x8n1Og3LvPlvsiWjwyv8YrfHMF/SzAf -rkU6jS0X6/uu5orhZUA/0QKBgGmvwgSttBUKoFC+EJStQ8kqSPG8Ew5dc/Y8lZZ1 -LMgT11SvIOSYV+92REzFnL2i20RntcIkE2eP10lDNSmMxt4Y0niy1nRr64Rw6cPX -EHupvIjlDwb8rhPEyT0BjaNHlKukdFtEg5X4gz2AheqtMYq6+fR8QUKxNJL9aQIJ -esLhAoGAPR0TKIlF5qX8OF4ugmnRgzTHoCrh8OOWKNo2dBsxhImP6yml67ti12ol -52x3Au3LAT14Lp3ZuLcTHeC+iwds7GsZXi7vMo25V0ktwbiLLhHmzm1YN8I2fSaI -QxICgq/Jq1eicz12oQCjVMhXAZQy2KI8awNcmuh+3zoDjYxiNTQ= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDWObetvwPGdtHM +AVCk4/bf8N3rR684b7lSDGsjo+CM9IxbWC//anaUSfIsKDAv8vSsD9w06Ui8IzUt +ZZrFydL1ttVZeD0/XlneOD6DoAggoO421FpcyJWKJzNFdYZnk3bagH56+lncwSqx +lM/JrLrCA5pU97fIue4Cw4//KeRgf11tjECC9ZpJJ1/EyK2Dc69N4sCW8XuLCJ9N +KKnFsaSJZUngXxg/Prtad19V63J7pSMqqwI3h3/Fw7GBMN6JhOcrYo+m8dfMZB8N +tbUAk3MH7Bgs1x/YfaiwyR2n0BdatmuPgJafukaOUtkwnjziiMuxWAhqu+T9+7lS +NtOPqmVtAgMBAAECgf8LKEu+FjTGzBQhNpr8pjg1catylZ6Kp0b/qaXq3/N8uT/r +ggGooTg2gPjGqVEug3jlfUNQKNZLoN8kKNE8JpwrhVpB+VaMoQgNNYthT58cvppU +QLsexKkFM7AimKcSE0bIGpPRKTjaRoqdX81ys4xb49Ql7hZPCLVcxNK7Ih4YPcbj +y757ncy3z4ofDEWekktLsldGTBqf+ZTwKPGfXi+aQUK1smVXD/0qKMulhPd2bzG0 +jjS49IXtxAPZxDQe8rNURHOvWjOlUh4XXAPD602A7YKlDBgp7bXtmRg7qQJSyofd +ZwSqXcQj7/LFf0vMKlzqEpr49bREckFKu1elTwECgYEA8A7tpXnR4rwIVJg78YW6 +AdkWrDwDx+BcOW3D5wnR+8r5DqN2JLdyVJuSK+PZ/YZ5NsxQTTBbNP9BwYCr9/7c +7Ont4WBv5Sq6uMIP3Q4ZYTOnXDht+5fECrUgJNeHnYawTXjjEn/2hXVwAKyQV6ZW +p8tJWEZLNilVgsTzIpWu2kkCgYEA5HOfPBVrF4fNTHreyMchvM6TDoHgCFxboSRa +OhxJvILCDx26H57/ry8/kMWfSICsHOMpNhdHk2dcncS3Dxrxzoh44mPduRE6vmJ9 +aMCL9oMK3cNOJpnibYju70HKOiSo6xfmKThh3cT6vht/zGAiQl2xgnmW5BHClS2E +UCCvEgUCgYEAn0D4DQAM6kLdr+kyUx7o8BLphKdnxrCj3gFgpiQQpt7XbQup89+z +PEfcp7tHwXX1chIG7741s6nnIIzRCPuN6qmiAxMsCDbZmno7deXFloNi8r4I0JC1 +IHlacrZ9JFbXHP4mff5gN9dZz0irlUonULd+184CVVMEhHVN6tKsJekCgYB5gm4N +nS7K4zZ1GV1p2VC+hfl92+J3T/iEqzs6cVVe6QWi2KLosr84RJ9At3xTKe1hqAmB +dbxiULXYTIs5g5Fd0wTJFWkXSGzAWs5tawyG34hmD71/lpXywMSliY6wglM+SAXp +os2bEWx0tVeyMufF+OOzjEyPnkqq4fzgSaIUqQKBgQCNR/hMpknDsLPATAzy/IxU +iV59WFsgv7Gl1aL2EXIBBdQ2bJO8vLBVWKajUSuA/XkpZKjOKuri7KW78LdqJUrk +kwAaEUDreM59J2rXmWvJWAgwKN6fyd0h6fKsmo94ZgXBHb5Q9QdirpHK+4UaYlQ/ +ICYEA/qnkUs0N+MGfu47+g== +-----END PRIVATE KEY----- diff --git a/certdir/goodclient.p12 b/certdir/goodclient.p12 index 14db04b..485671c 100644 Binary files a/certdir/goodclient.p12 and b/certdir/goodclient.p12 differ diff --git a/certdir/goodclient.pk8 b/certdir/goodclient.pk8 deleted file mode 100644 index 7a75b41..0000000 Binary files a/certdir/goodclient.pk8 and /dev/null differ diff --git a/certdir/goodroot.crt b/certdir/goodroot.crt index ed67a0a..1d33881 100644 --- a/certdir/goodroot.crt +++ b/certdir/goodroot.crt @@ -1,32 +1,32 @@ -----BEGIN CERTIFICATE----- -MIIFdzCCA1+gAwIBAgIUZok1lfYqJerJHIiWp/xDukOP1SYwDQYJKoZIhvcNAQEL +MIIFdzCCA1+gAwIBAgIUCQhnIOUcUpY/AlaU8V3+aJuCXeYwDQYJKoZIhvcNAQEL BQAwSzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQwEgYDVQQKDAtQZ0pkYmMg -dGVzdDEZMBcGA1UEAwwQcm9vdCBjZXJ0aWZpY2F0ZTAeFw0yMTA2MjcxMzU2MzVa -Fw0zMTA2MjUxMzU2MzVaMEsxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIG +dGVzdDEZMBcGA1UEAwwQcm9vdCBjZXJ0aWZpY2F0ZTAeFw0yNDA1MjIxMzIyMjNa +Fw0zNDA1MjAxMzIyMjNaMEsxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIG A1UECgwLUGdKZGJjIHRlc3QxGTAXBgNVBAMMEHJvb3QgY2VydGlmaWNhdGUwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCuXfIqe27Y9YAASUFqPwX9z5jL -Yzs7HoYOsJiXeKlxuiIE8KrDxrdlkDY3Cb/eWgREq0IkbowrznDZ0rCIE88hom+d -HHtqw6V1+sBLfuw1o83Jm2kQRbpa+Lacsfza9DUJj7Zy4m9DY+yKXULi5N0ncvI4 -YBeIceczcb/y0mpATUGsTfbetOkFOv3Zn/Di53/gWzVOftFzSYCyUOJ1ocJYmOjN -2/Fey1Z2qNF/sVoxhusd59TDKtBu81uHuSS9mHlVin9GEjG/eD/zxzDgnamvpFKv -tatGrjS42nZmKS656xpizvL3hPE+aLBZglqmvHFDIe7WMBok08EKleQGH1kazFXD -Q4fJaZTwzRA/pDKVmmycJG6HrgN4nANq0CiUolmCg82ZGkYv/X0sQrLAUyqYD9sQ -mtE83E0y2tSJ6swnQL7PoP6J5wYLVOOvCGdv0E6chb4EKlGaHD5Ylq3Tj7rJdhCh -XADkiHHKFzsxA9YnnGvzNKm9F5IImwGgatdffVNof9RrTpJCuwnuV+7zqH29oqun -XsMzXbbcxXgyOfKTZdAViHY043xB9bpfIv0LFCC4T4UGcq1n/B0Wu576Q/3ySHKh -v1FXdG+MlR/3jdtVWQmig6krLQzBTnivbZJUCDj9BA8K+iRhpSuUj+h9WODMrpjb -JzuxJxaN8wvFS6GuDwIDAQABo1MwUTAdBgNVHQ4EFgQUOoukTRhVzM0yLN1NY6kU -YVVNaqYwHwYDVR0jBBgwFoAUOoukTRhVzM0yLN1NY6kUYVVNaqYwDwYDVR0TAQH/ -BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAV1Rq9UdWvotgJBHETTiz7bssIfbx -C3OYw7aN6KlxtfDHnz/Xre8bn8y3U+Ahmwi5c1DCITHyWqkhZXJdN7POTFMlaUNu -8OqhoNYrwQmxPEy6bItLWW+kYMDi88ufEaP3JXiYogKFI2Vff+tyZC65bJFWYBD+ -uvOviEtSeYoZaO0Z/OFZ5PNCq8LFwmNAcNFiTbhkQfW74BM2rwhdMqSVcXCbjn9E -oieu0iB08ktpnAPsDMZ9MU1pGuPXjsza/N5CTCMfJ9EDlDkD1cVNk9Mms+r3IFXQ -bnb5RxcNWKfHkQ5RTCmW0LusS1EZIKVOWxhkoDK7Kn6CqR6QSmS52hXB03Rsz1zH -/4/X3HrO6DXjivZ04Im4rTzfLR/tHD3RrbMXPbEwWY/hftMgI9JKLsthCnjiD3Xh -QHepbYvGElW5326L1nzBJW7Rbx6eR/+aBPEWTkyr6rW+CMxZEUHFmsD7GLbqXy6M -rizrTdgg5dGrN3Tej9aYQiW5SZwld3TzTlDdBu5c+xUiku3bYJNO6VIttdSVng7b -xzCTrk0vOSAnvr78FAdeRWNLUnD27mPRBEi5O06/NX2A0OhGE/p0UCpPjT24Pdh8 -0+ivlYIk8zceHC6XrthNKdkA9PuA/rmpyG4kwkOpkvO9nUwCNTrEVJGwhPTf2S7J -2arAdVehUudv304= +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDNQ33pC5Iuz0ZxoTm5tgBFe76j +Y6Yi7KZ0+6fdDwXdhAAcjplVcsUzwFjVAVl3qN6RpiKa4tEUdJtoc2hvFENikWbp +2bLuOcNreC0V3I2eW0YfoX2FUH53Dae1TwWWaaufMn8jB+IhKWQ2Ix/SEktS8pWW +/9FCRgzbml0Io5YItUaphnsAmZn3Gkvz/FQSE/GP6ukF+AY4FoZQ/v05KiN+evxR +lFOhSqym67PxyTrEqShCtgEWzPWKIz7g74mXKmJjfjC2QEZ0Cn/9OfO7dA+ThyBs +5ZQp5FP2oCIyv7hDP7Ipvqp11v5m/zxUocB44HUavR9PrZOKVVaIc2rntkD3iP9z +5pmQjsMDm3XcvsTxC4R/ldVbX6WRstwi0wyN/6ZEHMIPlEmhncVpWeMXYwpct249 +3MBW/Xk66sY4FWagRIhp/k7yoVYmBTnZrIOgxfKM78cuB2hmoNVW/emXEMLz/F8o +oYbvDM74kXrG+SoUt8MfiyhYITvWxlBhqwKw8+ljmPSSnJ9FMLWIxz/rlNlhcEfb +D/OIoTdPQLJD+Bmb4HZ7xVPzcMcBjWKg8ZrKL5S+GPdNit01KxnJegQ5x5bw6Eub +A+KQc0unA+5i66GPl8jAE3O9yk+Qmu39FJNe7NsoPGwydozF7wppynG8IFdJ+wZZ +8zQIVGIHN9pVr0FCtQIDAQABo1MwUTAdBgNVHQ4EFgQU3gF40GtF0Ev/pMgBWvmU +sEsMYikwHwYDVR0jBBgwFoAU3gF40GtF0Ev/pMgBWvmUsEsMYikwDwYDVR0TAQH/ +BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAs1wUuctpDqABzXLA06PJaK8oK4UU +WqAXu26CRduumoq8If4IeEER74SI+W8G4UOF2CuTObrkv3E0MQ6EeTgzbMJlsya6 +t8ZkbybiTMmNn4exh+xsKy+zvJ5xw9UVPEm7rFfJ0r1BR/iFxPtvtwk5TJ5JKEKM +YvVso/J75v/b8iJJWwSK0BBJvQOJkE7IYMJkrl/5dkzGGtuMnh+kOiiYIhQqLalU +7u1zjkLYwB/DcVpAIYysw7K8yEuAlXy1cUE6QRp3Sxptfy/ZLA+Vb0nNg9ppJKt/ +O4ALEHhRiW0phU1dc1mYe+UUIlW/FtvvRbOksoTFtcP87M2qjuvhfagKoshJt2OT +KqZmiKhTyDlTinEtHObIJ6hITPkmJ1zibtNBPvVZlw2eNTvV6b//+G4MkWE34pcH +NQalZlFHGfQHW0pNNsdkMeRAPI9lIKVAzrXdlC+HWHfkVp3dXvjklw3l0QNz92n8 +3/wfvegccwiXO3aNposywzGVmPqhncYyb1TixfCjhlU5zp0lbCuEbf5zHBk13iuj +6WNG1+sox2BloABy+kOLi7FUMjVlzpBzb6ajkVMCBs4IZQigH1WvkO76Lbat1IVT +gvvYYcJWKXJWRHkAZLQZO4D18dXg5f/6ssMp0qY/Pn/LOQ3QRN4tPUlrg17YUnTo +KMd8dhP0m+oAbvA= -----END CERTIFICATE----- diff --git a/debian/changelog b/debian/changelog index 9cd450b..55716a0 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,215 @@ +libpgjava (42.7.11-1) unstable; urgency=medium + + * New upstream version 42.7.11. + * Limit SCRAM PBKDF2 iterations accepted from the server. + pgjdbc was vulnerable to a client-side denial of service in SCRAM-SHA-256 + authentication, where a malicious or compromised PostgreSQL server could + specify an extremely large PBKDF2 iteration count, causing the client to + consume unbounded CPU and potentially exhaust connection pools. The fix + introduces a new scramMaxIterations connection property (defaulting to + 100,000) to cap iteration counts before computation begins. + (CVE-2026-42198) + + -- Christoph Berg Wed, 29 Apr 2026 11:08:43 +0200 + +libpgjava (42.7.10-1) unstable; urgency=medium + + * New upstream version 42.7.10. + + -- Christoph Berg Wed, 11 Feb 2026 20:38:12 +0100 + +libpgjava (42.7.9-1) unstable; urgency=medium + + * New upstream version 42.7.9. + + -- Christoph Berg Sat, 17 Jan 2026 14:52:24 +0100 + +libpgjava (42.7.8-2) unstable; urgency=medium + + * Team upload. + * Ignore org.junit:junit-bom to avoid propagating it as a transitive + dependency to reverse build-deps through the pom. (Closes: #1121881) + * Bump Standards-Version to 4.7.2 + * Update d/copyright Format URL + * Update debian/watch to version 5 + * Remove Rules-Requires-Root from debian/control + + -- tony mancill Wed, 03 Dec 2025 21:19:04 -0800 + +libpgjava (42.7.8-1) unstable; urgency=medium + + * New upstream version 42.7.8. + + -- Christoph Berg Tue, 23 Sep 2025 20:18:01 +0000 + +libpgjava (42.7.7-2) unstable; urgency=medium + + * Rebuild against libscram-java 3.2-1. (CVE-2025-59432) + + -- Christoph Berg Tue, 23 Sep 2025 18:14:27 +0000 + +libpgjava (42.7.7-1) unstable; urgency=medium + + * New upstream version 42.7.7. + Fixes CVE-2025-49146: When the PostgreSQL JDBC driver is configured with + channel binding set to required (default value is prefer), the driver + would incorrectly allow connections to proceed with authentication methods + that do not support channel binding (such as password, MD5, GSS, or SSPI + authentication). This could allow a man-in-the-middle attacker to + intercept connections that users believed were protected by channel + binding requirements. + + -- Christoph Berg Fri, 13 Jun 2025 15:26:53 +0200 + +libpgjava (42.7.6-1) experimental; urgency=medium + + * New upstream version 42.7.6. + + -- Christoph Berg Mon, 02 Jun 2025 17:05:52 +0200 + +libpgjava (42.7.5-2) unstable; urgency=medium + + * Fix org.postgresql.util.PSQLException: Unable to convert bytea parameter + at position 1 to literal. (Closes: #1098830) + + -- Christoph Berg Thu, 17 Apr 2025 11:14:38 +0000 + +libpgjava (42.7.5-1) unstable; urgency=medium + + * Team upload. + * New upstream release + - Refreshed the patches + * Removed the unused dependency on libxerces2-java + + -- Emmanuel Bourg Mon, 10 Feb 2025 10:15:21 +0100 + +libpgjava (42.7.3-2) unstable; urgency=medium + + * Team upload. + * Removed the -java-doc package (Closes: #1088125) + * Standards-Version updated to 4.7.0 + + -- Emmanuel Bourg Mon, 09 Dec 2024 14:05:50 +0100 + +libpgjava (42.7.3-1) unstable; urgency=medium + + * New upstream version 42.7.3. + + -- Christoph Berg Fri, 15 Mar 2024 15:13:39 +0100 + +libpgjava (42.7.2-1) unstable; urgency=medium + + * New upstream version 42.7.2. (CVE-2024-1597) + + -- Christoph Berg Thu, 22 Feb 2024 11:46:47 +0100 + +libpgjava (42.7.1-1) unstable; urgency=medium + + * New upstream version 42.7.1. + + -- Christoph Berg Thu, 07 Dec 2023 23:27:15 +0100 + +libpgjava (42.7.0-1) unstable; urgency=medium + + * New upstream version 42.7.0. + + -- Christoph Berg Tue, 21 Nov 2023 10:31:51 +0100 + +libpgjava (42.6.0-2) unstable; urgency=medium + + * Remove ancient Replaces/Conflicts. Thanks Helmut Grohne for the report. + + -- Christoph Berg Wed, 18 Oct 2023 14:05:07 +0200 + +libpgjava (42.6.0-1) experimental; urgency=medium + + * New upstream version 42.6.0. + + -- Christoph Berg Mon, 27 Mar 2023 09:36:55 +0200 + +libpgjava (42.5.4-1) unstable; urgency=medium + + * New upstream version 42.5.4. + + -- Christoph Berg Fri, 17 Feb 2023 18:19:35 +0100 + +libpgjava (42.5.3-1) unstable; urgency=medium + + * New upstream version 42.5.3. + + -- Christoph Berg Thu, 09 Feb 2023 11:26:33 +0100 + +libpgjava (42.5.1-1) unstable; urgency=medium + + * New upstream version 42.5.1, fixes CVE-2022-41946. + + -- Christoph Berg Thu, 24 Nov 2022 12:54:21 +0100 + +libpgjava (42.5.0-1) unstable; urgency=medium + + * New upstream version 42.5.0. + + -- Christoph Berg Fri, 26 Aug 2022 12:06:57 +0200 + +libpgjava (42.4.2-1) unstable; urgency=medium + + * New upstream version 42.4.2. + + -- Christoph Berg Mon, 22 Aug 2022 14:24:18 +0200 + +libpgjava (42.4.1-1) unstable; urgency=medium + + * New upstream version 42.4.1 + + Fixes SQL generated in PgResultSet.refresh() to escape column identifiers + so as to prevent SQL injection. + (Closes: #1016662, CVE-2022-31197, reported by Sho Kato) + + Previously, the column names for both key and data columns in the table + were copied as-is into the generated SQL. This allowed a malicious table + with column names that include statement terminator to be parsed and + executed as multiple separate commands. + + -- Christoph Berg Mon, 08 Aug 2022 14:53:28 +0200 + +libpgjava (42.4.0-1) unstable; urgency=medium + + * New upstream version 42.4.0. + + -- Christoph Berg Tue, 14 Jun 2022 15:18:49 +0200 + +libpgjava (42.3.6-1) unstable; urgency=medium + + * New upstream version 42.3.6. + + -- Christoph Berg Fri, 27 May 2022 14:56:40 +0200 + +libpgjava (42.3.5-1) unstable; urgency=medium + + * New upstream version 42.3.5. + + -- Christoph Berg Fri, 06 May 2022 16:51:03 +0200 + +libpgjava (42.3.4-1) unstable; urgency=medium + + * New upstream version 42.3.4. + + -- Christoph Berg Mon, 02 May 2022 15:56:41 +0200 + +libpgjava (42.3.3-1) unstable; urgency=medium + + * New upstream version 42.3.3. + https://github.com/pgjdbc/pgjdbc/security/advisories/GHSA-673j-qm5f-xpv8 + + -- Christoph Berg Thu, 17 Feb 2022 13:08:38 +0100 + +libpgjava (42.3.2-1) unstable; urgency=medium + + * New upstream version 42.3.2. + https://github.com/pgjdbc/pgjdbc/security/advisories/GHSA-v7wg-cpwc-24m4 + + -- Christoph Berg Fri, 04 Feb 2022 10:58:43 +0100 + libpgjava (42.3.1-1) unstable; urgency=medium * New upstream version 42.3.1. @@ -292,7 +504,7 @@ libpgjava (8.0-312-1) unstable; urgency=low * New upstream release + Supports Postgresql 7.2 up to 8.0 releases - + Upstream supplies its own source packages for the jdbc driver + + Upstream supplies its own source packages for the jdbc driver starting with release 8.0 - no longer extracted from postgresql * Changed library name to comply with java policy (closes: #275417) + Added conflicts, replaces with old binary @@ -332,15 +544,15 @@ libpgjava (7.4.7-2) unstable; urgency=low * Added patch to fix jikes compile error in JDBC2 optional classes (02_jikes_jdbc2-optional_compilefix.patch) * Build JDBC2 with optional classes but without SSL support - Building with SSL support would prevent running the JDBC2 driver on - jdk1.3 runtimes without SSL extensions or using the -Xverify:none flag + Building with SSL support would prevent running the JDBC2 driver on + jdk1.3 runtimes without SSL extensions or using the -Xverify:none flag * Build.xml (03_BuildXml.patch): - Patched to allow explicit selection of JDBC specification with -Djdbc2=true or -Djdbc3=true arguments during build - Patched to allow explicit selection of SSL usage during compile time with -Dssl=true (JDBC2 build has to be built without SSL) - Patched to allow explicit selection of compile target version with - -Dtarget=1.3 for JDBC2 and -Dtarget=1.4 for JDBC3 + -Dtarget=1.3 for JDBC2 and -Dtarget=1.4 for JDBC3 -- Wolfgang Baer Tue, 19 Apr 2005 20:28:25 +0200 @@ -362,7 +574,7 @@ libpgjava (7.4.2-1) unstable; urgency=low (closes: #238961) * Updated debian/copyright * Use libant1.6-java instead of libant1.5-java for building - * + * -- Stefan Gybas Mon, 19 Apr 2004 19:40:03 +0200 diff --git a/debian/control b/debian/control index 302ee25..3f3fda5 100644 --- a/debian/control +++ b/debian/control @@ -12,16 +12,13 @@ Build-Depends: libbuild-helper-maven-plugin-java, libcomment-preprocessor-java, libmaven-bundle-plugin-java, - libmaven-javadoc-plugin-java, libmaven-shade-plugin-java, libproperties-maven-plugin-java, libscram-java (>= 2.1), maven-debian-helper Build-Depends-Indep: - default-jdk, - default-jdk-doc, - libxerces2-java -Standards-Version: 4.5.0 + default-jdk +Standards-Version: 4.7.2 Vcs-Git: https://salsa.debian.org/java-team/libpostgresql-jdbc-java.git Vcs-Browser: https://salsa.debian.org/java-team/libpostgresql-jdbc-java Homepage: https://jdbc.postgresql.org/ @@ -32,29 +29,10 @@ Multi-Arch: foreign Depends: ${maven:Depends}, ${misc:Depends} # No "Recommends: ${maven:OptionalDepends}" here because that would point at # libscram-java which we bundle -Conflicts: libpgjava (<= 7.4.7-3), libpg-java (<= 9.1-901-1) Provides: libpgjava, libpg-java -Replaces: libpgjava, libpg-java (<= 9.1-901-1) Built-Using: ${builtUsing} Description: Java database (JDBC) driver for PostgreSQL PostgreSQL JDBC Driver allows Java programs to connect to a PostgreSQL database (8.4 or later) using standard, database independent Java code. It is an open source JDBC driver written in Pure Java (Type 4), and communicates in the PostgreSQL native network protocol. - -Package: libpostgresql-jdbc-java-doc -Section: doc -Architecture: all -Multi-Arch: foreign -Depends: ${maven:DocDepends}, ${misc:Depends} -Recommends: ${maven:DocOptionalDepends} -Conflicts: libpg-java-doc (<= 8.4-702-1) -Provides: libpg-java-doc -Replaces: libpg-java-doc -Description: Java database (JDBC) driver for PostgreSQL (documentation) - PostgreSQL JDBC Driver allows Java programs to connect to a PostgreSQL - database (8.4 or later) using standard, database independent Java code. - It is an open source JDBC driver written in Pure Java (Type 4), and - communicates in the PostgreSQL native network protocol. - . - This package contains the documentation. diff --git a/debian/copyright b/debian/copyright index 16ddf36..23b37a3 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,4 +1,4 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Postgresql JDBC Driver Source: https://github.com/pgjdbc/pgjdbc/ diff --git a/debian/libpostgresql-jdbc-java-doc.install b/debian/libpostgresql-jdbc-java-doc.install deleted file mode 100644 index 71b6aec..0000000 --- a/debian/libpostgresql-jdbc-java-doc.install +++ /dev/null @@ -1 +0,0 @@ -target/site/apidocs/* usr/share/doc/libpostgresql-jdbc-java/api diff --git a/debian/maven.ignoreRules b/debian/maven.ignoreRules new file mode 100644 index 0000000..507d093 --- /dev/null +++ b/debian/maven.ignoreRules @@ -0,0 +1,2 @@ +* * * * * test +org.junit junit-bom * * * * diff --git a/debian/patches/02-scram-optional.patch b/debian/patches/02-scram-optional.patch index 016f4f9..18fc61e 100644 --- a/debian/patches/02-scram-optional.patch +++ b/debian/patches/02-scram-optional.patch @@ -5,13 +5,13 @@ Bug: #900615 --- a/pom.xml +++ b/pom.xml -@@ -43,7 +43,8 @@ +@@ -57,7 +57,8 @@ com.ongres.scram - client -- 2.1 + scram-client +- 3.2 + debian + true - se.jiderhamn + org.junit.jupiter diff --git a/debian/patches/missing-test-deps b/debian/patches/missing-test-deps deleted file mode 100644 index 755c90c..0000000 --- a/debian/patches/missing-test-deps +++ /dev/null @@ -1,50 +0,0 @@ -Remove missing test dependencies - -classloader-leak-test-framework: Not packaged -junit: Packaged, but mvn doesn't find it - ---- a/pom.xml -+++ b/pom.xml -@@ -46,42 +46,6 @@ - debian - true - -- -- se.jiderhamn -- classloader-leak-test-framework -- 1.1.1 -- test -- -- -- junit -- junit -- 4.13 -- test -- -- -- org.junit.jupiter -- junit-jupiter-api -- 5.6.0 -- test -- -- -- org.junit.jupiter -- junit-jupiter-params -- 5.6.0 -- test -- -- -- org.junit.jupiter -- junit-jupiter-engine -- 5.6.0 -- test -- -- -- org.junit.vintage -- junit-vintage-engine -- 5.6.0 -- test -- - - - diff --git a/debian/patches/series b/debian/patches/series index a747363..f179736 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,2 +1 @@ 02-scram-optional.patch -missing-test-deps diff --git a/debian/rules b/debian/rules index 8f10a21..41d00ea 100755 --- a/debian/rules +++ b/debian/rules @@ -1,14 +1,10 @@ #!/usr/bin/make -f -# force doc build to be in English -export LC_ALL=C.UTF-8 - %: dh $@ override_dh_auto_build: dh_auto_build -- package - dh_auto_build -- javadoc:javadoc # defang package-contains-ancient-file touch README.md diff --git a/debian/watch b/debian/watch index 384bb86..901c47c 100644 --- a/debian/watch +++ b/debian/watch @@ -1,2 +1,4 @@ -version=4 -https://repo1.maven.org/maven2/org/postgresql/postgresql/(\d[\d.]+)/postgresql-([\d.]+)-jdbc-src.tar.gz +Version: 5 + +Source: https://repo1.maven.org/maven2/org/postgresql/postgresql/(\d[\d.]+)/ +Matching-Pattern: postgresql-([\d.]+)-jdbc-src.tar.gz diff --git a/pom.xml b/pom.xml index a956792..c0b0192 100644 --- a/pom.xml +++ b/pom.xml @@ -8,12 +8,17 @@ org.postgresql postgresql + 42.7.11 jar PostgreSQL JDBC Driver - JDBC 4.2 - 42.3.1 Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database https://github.com/pgjdbc/pgjdbc + + PostgreSQL Global Development Group + https://jdbc.postgresql.org/ + + BSD-2-Clause @@ -21,64 +26,68 @@ - - PostgreSQL Global Development Group - https://jdbc.postgresql.org/ - - 1.8 + 8 UTF-8 ${encoding} ${encoding} ${encoding} - 3.8.1 + 3.12.1 2.22.2 - 2.6 + 3.3.0 3.0.1 + true + + + + org.junit + junit-bom + 5.14.3 + pom + import + + + + com.ongres.scram - client - 2.1 - - - se.jiderhamn - classloader-leak-test-framework - 1.1.1 - test - - - junit - junit - 4.13 - test + scram-client + 3.2 org.junit.jupiter junit-jupiter-api - 5.6.0 test org.junit.jupiter junit-jupiter-params - 5.6.0 + test + + + org.junit.platform + junit-platform-launcher test org.junit.jupiter junit-jupiter-engine - 5.6.0 test org.junit.vintage junit-vintage-engine - 5.6.0 + test + + + uk.org.webcompere + system-stubs-jupiter + 2.1.8 test @@ -89,10 +98,6 @@ org.apache.maven.plugins maven-compiler-plugin ${maven-compiler-plugin.version} - - ${javac.target} - ${javac.target} - maven-surefire-plugin @@ -102,6 +107,12 @@ . + + + junit.jupiter.extensions.autodetection.enabled=true + junit.jupiter.execution.timeout.default=5 m + + @@ -118,6 +129,53 @@ + + jdk8 + + 1.8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${javac.target} + ${javac.target} + + + org/postgresql/test/jdbc2/DriverTest.java + org/postgresql/util/OSUtilTest.java + org/postgresql/util/StubEnvironmentAndProperties.java + org/postgresql/jdbcurlresolver/PgPassParserTest.java + org/postgresql/jdbcurlresolver/PgServiceConfParserTest.java + + + + + + + + + jdkge11 + + [11,) + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.target.release} + + + + + + section-one is selected + # in case of duplicate key - first entry counts + # Line: "[service-one]" + # Line: "host=host-one" + # Line: "host=host-two" + # --> host-one is selected + # service name is case sensitive + # Line: "[service-one]" + # Line: "[service-ONE]" + # --> these are unique service names + # whatever is between brackets is considered as service name (including space) + # Line: "[ service-ONE]" + # Line: "[service-ONE ]" + # Line: "[service ONE]" + # --> these are unique service names + */ + @SuppressWarnings("RedundantControlFlow") + private /* @Nullable */ Properties parseInputStream(InputStream inputStream) throws IOException { + // build set of allowed keys + Set allowedServiceKeys = Arrays.stream(PGProperty.values()) + .map(PGProperty::getName) + .map(PGPropertyUtil::translatePGPropertyToPGService) + .collect(Collectors.toSet()); + + // + Properties result = new Properties(); + boolean isFound = false; + try ( + Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); + BufferedReader br = new BufferedReader(reader)) { + // + String originalLine; + String line; + int lineNumber = 0; + while ((originalLine = br.readLine()) != null) { + lineNumber++; + // remove spaces around it + line = originalLine.trim(); + // skip if empty line or starts with comment sign + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + // find first equal sign + int indexOfEqualSign = line.indexOf("="); + // is it section start? + if (line.startsWith("[") && line.endsWith("]")) { + // stop processing if section with correct name was found already + if (isFound) { + break; + } + // get name of section + String sectionName = line.substring(1, line.length() - 1); + // if match then mark it as section is found + if (serviceName.equals(sectionName)) { + isFound = true; + } + } else if (!isFound) { + // skip further processing until section is found + // TODO: avoid continue here to resolve https://errorprone.info/bugpattern/RedundantControlFlow + //noinspection UnnecessaryContinue + continue; + } else if (indexOfEqualSign > 1) { + // get key and value + String key = line.substring(0, indexOfEqualSign); + String value = line.substring(indexOfEqualSign + 1); + // check key against set of allowed keys + if (!allowedServiceKeys.contains(key)) { + // log list of allowed keys + String allowedValuesCommaSeparated = + allowedServiceKeys.stream().sorted().collect(Collectors.joining(",")); + LOGGER.log(Level.SEVERE, "Got invalid key: line number [{0}], value [{1}], allowed " + + "values [{2}]", + new Object[]{lineNumber, originalLine, allowedValuesCommaSeparated}); + // stop processing because of invalid key + return null; + } + // ignore line if value is missing + if (!value.isEmpty()) { + // ignore line having duplicate key, otherwise store key-value pair + result.putIfAbsent(PGPropertyUtil.translatePGServiceToPGProperty(key), value); + } + } else { + // if not equal sign then stop processing because of invalid syntax + LOGGER.log(Level.WARNING, "Not valid line: line number [{0}], value [{1}]", + new Object[]{lineNumber, originalLine}); + return null; + } + } + } + // null means failure - service is not found + return isFound ? result : null; + } + +} diff --git a/src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java b/src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java deleted file mode 100644 index 55d8ff7..0000000 --- a/src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (c) 2017, PostgreSQL Global Development Group - * See the LICENSE file in the project root for more information. - */ - -package org.postgresql.jre7.sasl; - -import static org.postgresql.util.internal.Nullness.castNonNull; - -import org.postgresql.core.PGStream; -import org.postgresql.util.GT; -import org.postgresql.util.PSQLException; -import org.postgresql.util.PSQLState; - -import com.ongres.scram.client.ScramClient; -import com.ongres.scram.client.ScramSession; -import com.ongres.scram.common.exception.ScramException; -import com.ongres.scram.common.exception.ScramInvalidServerSignatureException; -import com.ongres.scram.common.exception.ScramParseException; -import com.ongres.scram.common.exception.ScramServerErrorException; -import com.ongres.scram.common.stringprep.StringPreparations; -// import org.checkerframework.checker.nullness.qual.Nullable; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class ScramAuthenticator { - private static final Logger LOGGER = Logger.getLogger(ScramAuthenticator.class.getName()); - - private final String user; - private final String password; - private final PGStream pgStream; - private /* @Nullable */ ScramClient scramClient; - private /* @Nullable */ ScramSession scramSession; - private /* @Nullable */ ScramSession.ClientFinalProcessor clientFinalProcessor; - - private interface BodySender { - void sendBody(PGStream pgStream) throws IOException; - } - - private void sendAuthenticationMessage(int bodyLength, BodySender bodySender) - throws IOException { - pgStream.sendChar('p'); - pgStream.sendInteger4(Integer.SIZE / Byte.SIZE + bodyLength); - bodySender.sendBody(pgStream); - pgStream.flush(); - } - - public ScramAuthenticator(String user, String password, PGStream pgStream) { - this.user = user; - this.password = password; - this.pgStream = pgStream; - } - - public void processServerMechanismsAndInit() throws IOException, PSQLException { - List mechanisms = new ArrayList<>(); - do { - mechanisms.add(pgStream.receiveString()); - } while (pgStream.peekChar() != 0); - int c = pgStream.receiveChar(); - assert c == 0; - if (mechanisms.size() < 1) { - throw new PSQLException( - GT.tr("No SCRAM mechanism(s) advertised by the server"), - PSQLState.CONNECTION_REJECTED - ); - } - - ScramClient scramClient; - try { - scramClient = ScramClient - .channelBinding(ScramClient.ChannelBinding.NO) - .stringPreparation(StringPreparations.SASL_PREPARATION) - .selectMechanismBasedOnServerAdvertised(mechanisms.toArray(new String[]{})) - .setup(); - } catch (IllegalArgumentException e) { - throw new PSQLException( - GT.tr("Invalid or unsupported by client SCRAM mechanisms", e), - PSQLState.CONNECTION_REJECTED - ); - } - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.log(Level.FINEST, " Using SCRAM mechanism {0}", scramClient.getScramMechanism().getName()); - } - - this.scramClient = scramClient; - scramSession = - scramClient.scramSession("*"); // Real username is ignored by server, uses startup one - } - - public void sendScramClientFirstMessage() throws IOException { - ScramSession scramSession = this.scramSession; - String clientFirstMessage = castNonNull(scramSession).clientFirstMessage(); - LOGGER.log(Level.FINEST, " FE=> SASLInitialResponse( {0} )", clientFirstMessage); - - ScramClient scramClient = this.scramClient; - String scramMechanismName = castNonNull(scramClient).getScramMechanism().getName(); - final byte[] scramMechanismNameBytes = scramMechanismName.getBytes(StandardCharsets.UTF_8); - final byte[] clientFirstMessageBytes = clientFirstMessage.getBytes(StandardCharsets.UTF_8); - sendAuthenticationMessage( - (scramMechanismNameBytes.length + 1) + 4 + clientFirstMessageBytes.length, - new BodySender() { - @Override - public void sendBody(PGStream pgStream) throws IOException { - pgStream.send(scramMechanismNameBytes); - pgStream.sendChar(0); // List terminated in '\0' - pgStream.sendInteger4(clientFirstMessageBytes.length); - pgStream.send(clientFirstMessageBytes); - } - } - ); - } - - public void processServerFirstMessage(int length) throws IOException, PSQLException { - String serverFirstMessage = pgStream.receiveString(length); - LOGGER.log(Level.FINEST, " <=BE AuthenticationSASLContinue( {0} )", serverFirstMessage); - - ScramSession scramSession = this.scramSession; - if (scramSession == null) { - throw new PSQLException( - GT.tr("SCRAM session does not exist"), - PSQLState.UNKNOWN_STATE - ); - } - - ScramSession.ServerFirstProcessor serverFirstProcessor; - try { - serverFirstProcessor = scramSession.receiveServerFirstMessage(serverFirstMessage); - } catch (ScramException e) { - throw new PSQLException( - GT.tr("Invalid server-first-message: {0}", serverFirstMessage), - PSQLState.CONNECTION_REJECTED, - e - ); - } - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.log(Level.FINEST, - " <=BE AuthenticationSASLContinue(salt={0}, iterations={1})", - new Object[] { serverFirstProcessor.getSalt(), serverFirstProcessor.getIteration() } - ); - } - - clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(password); - - String clientFinalMessage = clientFinalProcessor.clientFinalMessage(); - LOGGER.log(Level.FINEST, " FE=> SASLResponse( {0} )", clientFinalMessage); - - final byte[] clientFinalMessageBytes = clientFinalMessage.getBytes(StandardCharsets.UTF_8); - sendAuthenticationMessage( - clientFinalMessageBytes.length, - new BodySender() { - @Override - public void sendBody(PGStream pgStream) throws IOException { - pgStream.send(clientFinalMessageBytes); - } - } - ); - } - - public void verifyServerSignature(int length) throws IOException, PSQLException { - String serverFinalMessage = pgStream.receiveString(length); - LOGGER.log(Level.FINEST, " <=BE AuthenticationSASLFinal( {0} )", serverFinalMessage); - - ScramSession.ClientFinalProcessor clientFinalProcessor = this.clientFinalProcessor; - if (clientFinalProcessor == null) { - throw new PSQLException( - GT.tr("SCRAM client final processor does not exist"), - PSQLState.UNKNOWN_STATE - ); - } - try { - clientFinalProcessor.receiveServerFinalMessage(serverFinalMessage); - } catch (ScramParseException e) { - throw new PSQLException( - GT.tr("Invalid server-final-message: {0}", serverFinalMessage), - PSQLState.CONNECTION_REJECTED, - e - ); - } catch (ScramServerErrorException e) { - throw new PSQLException( - GT.tr("SCRAM authentication failed, server returned error: {0}", - e.getError().getErrorMessage()), - PSQLState.CONNECTION_REJECTED, - e - ); - } catch (ScramInvalidServerSignatureException e) { - throw new PSQLException( - GT.tr("Invalid server SCRAM signature"), - PSQLState.CONNECTION_REJECTED, - e - ); - } - } -} diff --git a/src/main/java/org/postgresql/largeobject/BlobInputStream.java b/src/main/java/org/postgresql/largeobject/BlobInputStream.java index 314da8c..13fe16b 100644 --- a/src/main/java/org/postgresql/largeobject/BlobInputStream.java +++ b/src/main/java/org/postgresql/largeobject/BlobInputStream.java @@ -5,6 +5,9 @@ package org.postgresql.largeobject; +import org.postgresql.jdbc.ResourceLock; +import org.postgresql.util.GT; + // import org.checkerframework.checker.nullness.qual.Nullable; import java.io.IOException; @@ -15,15 +18,19 @@ * This is an implementation of an InputStream from a large object. */ public class BlobInputStream extends InputStream { + static final int DEFAULT_MAX_BUFFER_SIZE = 512 * 1024; + static final int INITIAL_BUFFER_SIZE = 64 * 1024; + /** * The parent LargeObject. */ private /* @Nullable */ LargeObject lo; + private final ResourceLock lock = new ResourceLock(); /** * The absolute position. */ - private long apos; + private long absolutePosition; /** * Buffer used to improve performance. @@ -33,28 +40,34 @@ public class BlobInputStream extends InputStream { /** * Position within buffer. */ - private int bpos; + private int bufferPosition; + + /** + * The amount of bytes to read on the next read. + * Currently, we nullify {@link #buffer}, so we can't use {@code buffer.length}. + */ + private int lastBufferSize; /** * The buffer size. */ - private int bsize; + private final int maxBufferSize; /** * The mark position. */ - private long mpos = 0; + private long markPosition; /** * The limit. */ - private long limit = -1; + private final long limit; /** * @param lo LargeObject to read from */ public BlobInputStream(LargeObject lo) { - this(lo, 1024); + this(lo, DEFAULT_MAX_BUFFER_SIZE); } /** @@ -63,7 +76,7 @@ public BlobInputStream(LargeObject lo) { */ public BlobInputStream(LargeObject lo, int bsize) { - this(lo, bsize, -1); + this(lo, bsize, Long.MAX_VALUE); } /** @@ -73,68 +86,188 @@ public BlobInputStream(LargeObject lo, int bsize) { */ public BlobInputStream(LargeObject lo, int bsize, long limit) { this.lo = lo; - buffer = null; - bpos = 0; - apos = 0; - this.bsize = bsize; - this.limit = limit; + this.maxBufferSize = bsize; + // The very first read multiplies the last buffer size by two, so we divide by two to get + // the first read to be exactly the initial buffer size + this.lastBufferSize = INITIAL_BUFFER_SIZE / 2; + // Treat -1 as no limit for backward compatibility + this.limit = limit == -1 ? Long.MAX_VALUE : limit; } /** * The minimum required to implement input stream. */ - public int read() throws java.io.IOException { - LargeObject lo = getLo(); - try { - if (limit > 0 && apos >= limit) { + @Override + public int read() throws IOException { + try (ResourceLock ignore = lock.obtain()) { + LargeObject lo = getLo(); + if (absolutePosition >= limit) { + buffer = null; + bufferPosition = 0; return -1; } - if (buffer == null || bpos >= buffer.length) { - buffer = lo.read(bsize); - bpos = 0; + // read more in if necessary + if (buffer == null || bufferPosition >= buffer.length) { + // Don't hold the buffer while waiting for DB to respond + // Note: lo.read(...) does not support "fetching the response into the user-provided buffer" + // See https://github.com/pgjdbc/pgjdbc/issues/3043 + int nextBufferSize = getNextBufferSize(1); + buffer = lo.read(nextBufferSize); + bufferPosition = 0; + + if (buffer.length == 0) { + // The lob does not produce any more data, so we are at the end of the stream + return -1; + } } - // Handle EOF - if (buffer == null || bpos >= buffer.length) { - return -1; + int ret = buffer[bufferPosition] & 0xFF; + + bufferPosition++; + absolutePosition++; + if (bufferPosition >= buffer.length) { + // TODO: support buffer reuse in mark/reset + buffer = null; + bufferPosition = 0; } - int ret = (buffer[bpos] & 0x7F); - if ((buffer[bpos] & 0x80) == 0x80) { - ret |= 0x80; + return ret; + } catch (SQLException e) { + long loId = lo == null ? -1 : lo.getLongOID(); + throw new IOException( + GT.tr("Can not read data from large object {0}, position: {1}, buffer size: {2}", + loId, absolutePosition, lastBufferSize), + e); + } + } + + /** + * Computes the next buffer size to use for reading data from the large object. + * The idea is to avoid allocating too much memory, especially if the user will use just a few + * bytes of the data. + * @param len estimated read request + * @return next buffer size or {@link #maxBufferSize} if the buffer should not be increased + */ + private int getNextBufferSize(int len) { + int nextBufferSize = Math.min(maxBufferSize, this.lastBufferSize * 2); + if (len > nextBufferSize) { + nextBufferSize = Math.min(maxBufferSize, Integer.highestOneBit(len * 2)); + } + this.lastBufferSize = nextBufferSize; + return nextBufferSize; + } + + @Override + public int read(byte[] dest, int off, int len) throws IOException { + if (len == 0) { + return 0; + } + try (ResourceLock ignore = lock.obtain()) { + int bytesCopied = 0; + LargeObject lo = getLo(); + + // Check to make sure we aren't at the limit. + if (absolutePosition >= limit) { + return -1; } - bpos++; - apos++; + // Check to make sure we are not going to read past the limit + len = Math.min(len, (int) Math.min(limit - absolutePosition, Integer.MAX_VALUE)); - return ret; - } catch (SQLException se) { - throw new IOException(se.toString()); + // have we read anything into the buffer + if (buffer != null) { + // now figure out how much data is in the buffer + int bytesInBuffer = buffer.length - bufferPosition; + // figure out how many bytes the user wants + int bytesToCopy = Math.min(len, bytesInBuffer); + // copy them in + System.arraycopy(buffer, bufferPosition, dest, off, bytesToCopy); + // move the buffer position + bufferPosition += bytesToCopy; + if (bufferPosition >= buffer.length) { + // TODO: support buffer reuse in mark/reset + buffer = null; + bufferPosition = 0; + } + // position in the blob + absolutePosition += bytesToCopy; + // increment offset + off += bytesToCopy; + // decrement the length + len -= bytesToCopy; + bytesCopied = bytesToCopy; + } + + if (len > 0) { + int nextBufferSize = getNextBufferSize(len); + // We are going to read data past the existing buffer, so we release the memory + // before making a DB call + buffer = null; + bufferPosition = 0; + int bytesRead; + try { + if (len >= nextBufferSize) { + // Read directly into the user's buffer + bytesRead = lo.read(dest, off, len); + } else { + // Refill the buffer and copy from it + buffer = lo.read(nextBufferSize); + // Note that actual number of bytes read may be less than requested + bytesRead = Math.min(len, buffer.length); + System.arraycopy(buffer, 0, dest, off, bytesRead); + // If we at the end of the stream, and we just copied the last bytes, + // we can release the buffer + if (bytesRead == buffer.length) { + // TODO: if we want to reuse the buffer in mark/reset we should not release the + // buffer here + buffer = null; + bufferPosition = 0; + } else { + bufferPosition = bytesRead; + } + } + } catch (SQLException ex) { + throw new IOException( + GT.tr("Can not read data from large object {0}, position: {1}, buffer size: {2}", + lo.getLongOID(), absolutePosition, len), + ex); + } + bytesCopied += bytesRead; + absolutePosition += bytesRead; + } + return bytesCopied == 0 ? -1 : bytesCopied; } } /** - *

Closes this input stream and releases any system resources associated with the stream.

+ * Closes this input stream and releases any system resources associated with the stream. * *

The close method of InputStream does nothing.

* * @throws IOException if an I/O error occurs. */ + @Override public void close() throws IOException { - if (lo != null) { - try { + long loId = 0; + try (ResourceLock ignore = lock.obtain()) { + LargeObject lo = this.lo; + if (lo != null) { + loId = lo.getLongOID(); lo.close(); - lo = null; - } catch (SQLException se) { - throw new IOException(se.toString()); } + this.lo = null; + } catch (SQLException e) { + throw new IOException( + GT.tr("Can not close large object {0}", + loId), + e); } } /** - *

Marks the current position in this input stream. A subsequent call to the reset + * Marks the current position in this input stream. A subsequent call to the reset * method repositions this stream at the last marked position so that subsequent reads re-read the - * same bytes.

+ * same bytes. * *

The readlimit arguments tells this input stream to allow that many bytes to be * read before the mark position gets invalidated.

@@ -152,8 +285,11 @@ public void close() throws IOException { * invalid. * @see java.io.InputStream#reset() */ - public synchronized void mark(int readlimit) { - mpos = apos; + @Override + public void mark(int readlimit) { + try (ResourceLock ignore = lock.obtain()) { + markPosition = absolutePosition; + } } /** @@ -163,18 +299,25 @@ public synchronized void mark(int readlimit) { * @see java.io.InputStream#mark(int) * @see java.io.IOException */ - public synchronized void reset() throws IOException { - LargeObject lo = getLo(); - try { - if (mpos <= Integer.MAX_VALUE) { - lo.seek((int)mpos); - } else { - lo.seek64(mpos, LargeObject.SEEK_SET); + @Override + public void reset() throws IOException { + try (ResourceLock ignore = lock.obtain()) { + LargeObject lo = getLo(); + long loId = lo.getLongOID(); + try { + if (markPosition <= Integer.MAX_VALUE) { + lo.seek((int) markPosition); + } else { + lo.seek64(markPosition, LargeObject.SEEK_SET); + } + buffer = null; + absolutePosition = markPosition; + } catch (SQLException e) { + throw new IOException( + GT.tr("Can not reset stream for large object {0} to position {1}", + loId, markPosition), + e); } - buffer = null; - apos = mpos; - } catch (SQLException se) { - throw new IOException(se.toString()); } } @@ -187,6 +330,7 @@ public synchronized void reset() throws IOException { * @see java.io.InputStream#mark(int) * @see java.io.InputStream#reset() */ + @Override public boolean markSupported() { return true; } diff --git a/src/main/java/org/postgresql/largeobject/BlobOutputStream.java b/src/main/java/org/postgresql/largeobject/BlobOutputStream.java index ad81b43..dcb6f50 100644 --- a/src/main/java/org/postgresql/largeobject/BlobOutputStream.java +++ b/src/main/java/org/postgresql/largeobject/BlobOutputStream.java @@ -5,35 +5,44 @@ package org.postgresql.largeobject; +import org.postgresql.jdbc.ResourceLock; +import org.postgresql.util.ByteStreamWriter; +import org.postgresql.util.GT; + +// import org.checkerframework.checker.index.qual.Positive; // import org.checkerframework.checker.nullness.qual.Nullable; import java.io.IOException; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.sql.SQLException; /** * This implements a basic output stream that writes to a LargeObject. */ public class BlobOutputStream extends OutputStream { + static final int DEFAULT_MAX_BUFFER_SIZE = 512 * 1024; + /** * The parent LargeObject. */ private /* @Nullable */ LargeObject lo; + private final ResourceLock lock = new ResourceLock(); /** * Buffer. */ - private byte[] buf; + private byte /* @Nullable */ [] buf; /** * Size of the buffer (default 1K). */ - private int bsize; + private final /* @Positive */ int maxBufferSize; /** * Position within the buffer. */ - private int bpos; + private int bufferPosition; /** * Create an OutputStream to a large object. @@ -41,50 +50,146 @@ public class BlobOutputStream extends OutputStream { * @param lo LargeObject */ public BlobOutputStream(LargeObject lo) { - this(lo, 1024); + this(lo, DEFAULT_MAX_BUFFER_SIZE); } /** * Create an OutputStream to a large object. * * @param lo LargeObject - * @param bsize The size of the buffer used to improve performance + * @param bufferSize The size of the buffer for single-byte writes */ - public BlobOutputStream(LargeObject lo, int bsize) { + public BlobOutputStream(LargeObject lo, int bufferSize) { this.lo = lo; - this.bsize = bsize; - buf = new byte[bsize]; - bpos = 0; + // Avoid "0" buffer size, and ensure the bufferSize will always be a power of two + this.maxBufferSize = Integer.highestOneBit(Math.max(bufferSize, 1)); + } + + /** + * Grows an internal buffer to ensure the extra bytes fit in the buffer. + * @param extraBytes the number of extra bytes that should fit in the buffer + * @return new buffer + */ + private byte[] growBuffer(int extraBytes) { + byte[] buf = this.buf; + if (buf != null && (buf.length == maxBufferSize || buf.length - bufferPosition >= extraBytes)) { + // Buffer is already large enough + return buf; + } + // We use power-of-two buffers, so they align nicely with PostgreSQL's LargeObject slicing + // By default PostgreSQL slices the data in 2KiB chunks + int newSize = Math.min(maxBufferSize, Integer.highestOneBit(bufferPosition + extraBytes) * 2); + byte[] newBuffer = new byte[newSize]; + if (buf != null && bufferPosition != 0) { + // There was some data in the old buffer, copy it over + System.arraycopy(buf, 0, newBuffer, 0, bufferPosition); + } + this.buf = newBuffer; + return newBuffer; } - public void write(int b) throws java.io.IOException { - LargeObject lo = checkClosed(); - try { - if (bpos >= bsize) { + @Override + public void write(int b) throws IOException { + long loId = 0; + try (ResourceLock ignore = lock.obtain()) { + LargeObject lo = checkClosed(); + loId = lo.getLongOID(); + byte[] buf = growBuffer(16); + if (bufferPosition >= buf.length) { lo.write(buf); - bpos = 0; + bufferPosition = 0; } - buf[bpos++] = (byte) b; - } catch (SQLException se) { - throw new IOException(se.toString()); + buf[bufferPosition++] = (byte) b; + } catch (SQLException e) { + throw new IOException( + GT.tr("Can not write data to large object {0}, requested write length: {1}", + loId, 1), + e); } } - public void write(byte[] buf, int off, int len) throws java.io.IOException { - LargeObject lo = checkClosed(); - try { - // If we have any internally buffered data, send it first - if (bpos > 0) { - flush(); - } + @Override + public void write(byte[] b, int off, int len) throws IOException { + long loId = 0; + try (ResourceLock ignore = lock.obtain()) { + LargeObject lo = checkClosed(); + loId = lo.getLongOID(); + byte[] buf = this.buf; + int totalData = bufferPosition + len; + // We have two parts of the data (it goes sequentially): + // 1) Data in buf at positions [0, bufferPosition) + // 2) Data in b at positions [off, off + len) + // If the new data fits into the buffer, we just copy it there. + // Otherwise, it might sound nice idea to just write them to the database, unfortunately, + // it is not optimal, as PostgreSQL chunks LargeObjects into 2KiB rows. + // That is why we would like to avoid writing a part of 2KiB chunk, and then issue overwrite + // causing DB to load and update the row. + // + // In fact, LOBLKSIZE is BLCKSZ/4, so users might have different values, so we use + // 8KiB write alignment for larger buffer sizes just in case. + // + // | buf[0] ... buf[bufferPosition] | b[off] ... b[off + len] | + // |<----------------- totalData ---------------------------->| + // If the total data does not align with 2048, we might have some remainder that we will + // copy to the beginning of the buffer and write later. + // The remainder can fall into either b (e.g. if the requested len is big enough): + // + // | buf[0] ... buf[bufferPosition] | b[off] ........ b[off + len] | + // |<----------------- totalData --------------------------------->| + // |<-------writeFromBuf----------->|<-writeFromB->|<--tailLength->| + // + // or + // buf (e.g. if the requested write len is small yet it does not fit into the max buffer size): + // | buf[0] .................... buf[bufferPosition] | b[off] .. b[off + len] | + // |<----------------- totalData -------------------------------------------->| + // |<-------writeFromBuf---------------->|<--------tailLength---------------->| + // "writeFromB" will be zero in that case + + // We want aligned writes, so the write requests chunk nicely into large object rows + int tailLength = + maxBufferSize >= 8192 ? totalData % 8192 : ( + maxBufferSize >= 2048 ? totalData % 2048 : 0 + ); - if (off == 0 && len == buf.length) { - lo.write(buf); // save a buffer creation and copy since full buffer written - } else { - lo.write(buf, off, len); + if (totalData >= maxBufferSize) { + // The resulting data won't fit into the buffer, so we flush the data to the database + int writeFromBuffer = Math.min(bufferPosition, totalData - tailLength); + int writeFromB = Math.max(0, totalData - writeFromBuffer - tailLength); + if (buf == null || bufferPosition <= 0) { + // The buffer is empty, so we can write the data directly + lo.write(b, off, writeFromB); + } else { + if (writeFromB == 0) { + lo.write(buf, 0, writeFromBuffer); + } else { + lo.write( + ByteStreamWriter.of( + ByteBuffer.wrap(buf, 0, writeFromBuffer), + ByteBuffer.wrap(b, off, writeFromB))); + } + // There might be some data left in the buffer since we keep the tail + if (writeFromBuffer >= bufferPosition) { + // The buffer was fully written to the database + bufferPosition = 0; + } else { + // Copy the rest to the beginning + System.arraycopy(buf, writeFromBuffer, buf, 0, bufferPosition - writeFromBuffer); + bufferPosition -= writeFromBuffer; + } + } + len -= writeFromB; + off += writeFromB; + } + if (len > 0) { + buf = growBuffer(len); + System.arraycopy(b, off, buf, bufferPosition, len); + bufferPosition += len; } - } catch (SQLException se) { - throw new IOException(se.toString()); + } catch (SQLException e) { + throw new IOException( + GT.tr("Can not write data to large object {0}, requested write length: {1}", + loId, len), + e); } } @@ -96,28 +201,41 @@ public void write(byte[] buf, int off, int len) throws java.io.IOException { * * @throws IOException if an I/O error occurs. */ + @Override public void flush() throws IOException { - LargeObject lo = checkClosed(); - try { - if (bpos > 0) { - lo.write(buf, 0, bpos); + long loId = 0; + try (ResourceLock ignore = lock.obtain()) { + LargeObject lo = checkClosed(); + loId = lo.getLongOID(); + byte[] buf = this.buf; + if (buf != null && bufferPosition > 0) { + lo.write(buf, 0, bufferPosition); } - bpos = 0; - } catch (SQLException se) { - throw new IOException(se.toString()); + bufferPosition = 0; + } catch (SQLException e) { + throw new IOException( + GT.tr("Can not flush large object {0}", + loId), + e); } } + @Override public void close() throws IOException { - LargeObject lo = this.lo; - if (lo != null) { - try { + long loId = 0; + try (ResourceLock ignore = lock.obtain()) { + LargeObject lo = this.lo; + if (lo != null) { + loId = lo.getLongOID(); flush(); lo.close(); this.lo = null; - } catch (SQLException se) { - throw new IOException(se.toString()); } + } catch (SQLException e) { + throw new IOException( + GT.tr("Can not close large object {0}", + loId), + e); } } diff --git a/src/main/java/org/postgresql/largeobject/LargeObject.java b/src/main/java/org/postgresql/largeobject/LargeObject.java index 646da3a..e4b06a1 100644 --- a/src/main/java/org/postgresql/largeobject/LargeObject.java +++ b/src/main/java/org/postgresql/largeobject/LargeObject.java @@ -5,11 +5,11 @@ package org.postgresql.largeobject; -import static org.postgresql.util.internal.Nullness.castNonNull; - import org.postgresql.core.BaseConnection; import org.postgresql.fastpath.Fastpath; import org.postgresql.fastpath.FastpathArg; +import org.postgresql.util.ByteStreamWriter; +import org.postgresql.util.GT; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; @@ -21,8 +21,8 @@ import java.sql.SQLException; /** - *

This class provides the basic methods required to run the interface, plus a pair of methods that - * provide InputStream and OutputStream classes for this object.

+ * This class provides the basic methods required to run the interface, plus a pair of methods that + * provide InputStream and OutputStream classes for this object. * *

Normally, client code would use the getAsciiStream, getBinaryStream, or getUnicodeStream methods * in ResultSet, or setAsciiStream, setBinaryStream, or setUnicodeStream methods in @@ -42,9 +42,10 @@ * @see java.sql.PreparedStatement#setBinaryStream * @see java.sql.PreparedStatement#setUnicodeStream */ +@SuppressWarnings("deprecation") // support for deprecated Fastpath API public class LargeObject - implements AutoCloseable - /* hi, checkstyle */ { + implements AutoCloseable { + /** * Indicates a seek from the beginning of a file. */ @@ -60,6 +61,8 @@ public class LargeObject */ public static final int SEEK_END = 2; + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + private final Fastpath fp; // Fastpath API to use private final long oid; // OID of this object private final int mode; // read/write mode of this object @@ -67,13 +70,25 @@ public class LargeObject private /* @Nullable */ BlobOutputStream os; // The current output stream - private boolean closed = false; // true when we are closed + private boolean closed; // true when we are closed private /* @Nullable */ BaseConnection conn; // Only initialized when open a LOB with CommitOnClose private final boolean commitOnClose; // Only initialized when open a LOB with CommitOnClose /** - *

This opens a large object.

+ * Checks if this LargeObject is closed and throws an exception if it is. + * + * @throws SQLException if this LargeObject has been closed + */ + private void checkClosed() throws SQLException { + if (closed) { + throw new PSQLException(GT.tr("This large object has been closed."), + PSQLState.OBJECT_NOT_IN_STATE); + } + } + + /** + * This opens a large object. * *

If the object does not exist, then an SQLException is thrown.

* @@ -106,7 +121,7 @@ protected LargeObject(Fastpath fp, long oid, int mode, } /** - *

This opens a large object.

+ * This opens a large object. * *

If the object does not exist, then an SQLException is thrown.

* @@ -121,20 +136,10 @@ protected LargeObject(Fastpath fp, long oid, int mode) throws SQLException { } public LargeObject copy() throws SQLException { + checkClosed(); return new LargeObject(fp, oid, mode); } - /* - * Release large object resources during garbage cleanup. - * - * This code used to call close() however that was problematic because the scope of the fd is a - * transaction, thus if commit or rollback was called before garbage collection ran then the call - * to close would error out with an invalid large object handle. So this method now does nothing - * and lets the server handle cleanup when it ends the transaction. - * - * protected void finalize() throws SQLException { } - */ - /** * @return the OID of this LargeObject * @deprecated As of 8.3, replaced by {@link #getLongOID()} @@ -156,29 +161,31 @@ public long getLongOID() { * * @throws SQLException if a database-access error occurs. */ + @Override public void close() throws SQLException { - if (!closed) { - // flush any open output streams - if (os != null) { - try { - // we can't call os.close() otherwise we go into an infinite loop! - os.flush(); - } catch (IOException ioe) { - throw new PSQLException("Exception flushing output stream", PSQLState.DATA_ERROR, ioe); - } finally { - os = null; - } + if (closed) { + return; + } + closed = true; + // flush any open output streams + if (os != null) { + try { + // we can't call os.close() otherwise we go into an infinite loop! + os.flush(); + } catch (IOException ioe) { + throw new PSQLException("Exception flushing output stream", PSQLState.DATA_ERROR, ioe); + } finally { + os = null; } + } - // finally close - FastpathArg[] args = new FastpathArg[1]; - args[0] = new FastpathArg(fd); - fp.fastpath("lo_close", args); // true here as we dont care!! - closed = true; - BaseConnection conn = this.conn; - if (this.commitOnClose && conn != null) { - conn.commit(); - } + // finally close + FastpathArg[] args = new FastpathArg[1]; + args[0] = new FastpathArg(fd); + fp.fastpath("lo_close", args); // true here as we dont care!! + BaseConnection conn = this.conn; + if (this.commitOnClose && conn != null) { + conn.commit(); } } @@ -190,12 +197,17 @@ public void close() throws SQLException { * @throws SQLException if a database-access error occurs. */ public byte[] read(int len) throws SQLException { + checkClosed(); // This is the original method, where the entire block (len bytes) // is retrieved in one go. FastpathArg[] args = new FastpathArg[2]; args[0] = new FastpathArg(fd); args[1] = new FastpathArg(len); - return castNonNull(fp.getData("loread", args)); + byte[] bytes = fp.getData("loread", args); + if (bytes == null) { + return EMPTY_BYTE_ARRAY; + } + return bytes; } /** @@ -208,13 +220,12 @@ public byte[] read(int len) throws SQLException { * @throws SQLException if a database-access error occurs. */ public int read(byte[] buf, int off, int len) throws SQLException { + checkClosed(); byte[] b = read(len); - if (b == null) { + if (b.length == 0) { return 0; } - if (b.length < len) { - len = b.length; - } + len = Math.min(len, b.length); System.arraycopy(b, 0, buf, off, len); return len; } @@ -226,6 +237,7 @@ public int read(byte[] buf, int off, int len) throws SQLException { * @throws SQLException if a database-access error occurs. */ public void write(byte[] buf) throws SQLException { + checkClosed(); FastpathArg[] args = new FastpathArg[2]; args[0] = new FastpathArg(fd); args[1] = new FastpathArg(buf); @@ -241,6 +253,7 @@ public void write(byte[] buf) throws SQLException { * @throws SQLException if a database-access error occurs. */ public void write(byte[] buf, int off, int len) throws SQLException { + checkClosed(); FastpathArg[] args = new FastpathArg[2]; args[0] = new FastpathArg(fd); args[1] = new FastpathArg(buf, off, len); @@ -248,7 +261,21 @@ public void write(byte[] buf, int off, int len) throws SQLException { } /** - *

Sets the current position within the object.

+ * Writes some data from a given writer to the object. + * + * @param writer the source of the data to write + * @throws SQLException if a database-access error occurs. + */ + public void write(ByteStreamWriter writer) throws SQLException { + checkClosed(); + FastpathArg[] args = new FastpathArg[2]; + args[0] = new FastpathArg(fd); + args[1] = FastpathArg.of(writer); + fp.fastpath("lowrite", args); + } + + /** + * Sets the current position within the object. * *

This is similar to the fseek() call in the standard C library. It allows you to have random * access to the large object.

@@ -258,6 +285,7 @@ public void write(byte[] buf, int off, int len) throws SQLException { * @throws SQLException if a database-access error occurs. */ public void seek(int pos, int ref) throws SQLException { + checkClosed(); FastpathArg[] args = new FastpathArg[3]; args[0] = new FastpathArg(fd); args[1] = new FastpathArg(pos); @@ -273,6 +301,7 @@ public void seek(int pos, int ref) throws SQLException { * @throws SQLException if a database-access error occurs. */ public void seek64(long pos, int ref) throws SQLException { + checkClosed(); FastpathArg[] args = new FastpathArg[3]; args[0] = new FastpathArg(fd); args[1] = new FastpathArg(pos); @@ -281,7 +310,7 @@ public void seek64(long pos, int ref) throws SQLException { } /** - *

Sets the current position within the object.

+ * Sets the current position within the object. * *

This is similar to the fseek() call in the standard C library. It allows you to have random * access to the large object.

@@ -290,6 +319,7 @@ public void seek64(long pos, int ref) throws SQLException { * @throws SQLException if a database-access error occurs. */ public void seek(int pos) throws SQLException { + checkClosed(); seek(pos, SEEK_SET); } @@ -298,6 +328,7 @@ public void seek(int pos) throws SQLException { * @throws SQLException if a database-access error occurs. */ public int tell() throws SQLException { + checkClosed(); FastpathArg[] args = new FastpathArg[1]; args[0] = new FastpathArg(fd); return fp.getInteger("lo_tell", args); @@ -308,14 +339,15 @@ public int tell() throws SQLException { * @throws SQLException if a database-access error occurs. */ public long tell64() throws SQLException { + checkClosed(); FastpathArg[] args = new FastpathArg[1]; args[0] = new FastpathArg(fd); return fp.getLong("lo_tell64", args); } /** - *

This method is inefficient, as the only way to find out the size of the object is to seek to - * the end, record the current position, then return to the original position.

+ * This method is inefficient, as the only way to find out the size of the object is to seek to + * the end, record the current position, then return to the original position. * *

A better method will be found in the future.

* @@ -323,6 +355,7 @@ public long tell64() throws SQLException { * @throws SQLException if a database-access error occurs. */ public int size() throws SQLException { + checkClosed(); int cp = tell(); seek(0, SEEK_END); int sz = tell(); @@ -337,6 +370,7 @@ public int size() throws SQLException { * @throws SQLException if a database-access error occurs. */ public long size64() throws SQLException { + checkClosed(); long cp = tell64(); seek64(0, SEEK_END); long sz = tell64(); @@ -353,6 +387,7 @@ public long size64() throws SQLException { * @throws SQLException if something goes wrong */ public void truncate(int len) throws SQLException { + checkClosed(); FastpathArg[] args = new FastpathArg[2]; args[0] = new FastpathArg(fd); args[1] = new FastpathArg(len); @@ -368,6 +403,7 @@ public void truncate(int len) throws SQLException { * @throws SQLException if something goes wrong */ public void truncate64(long len) throws SQLException { + checkClosed(); FastpathArg[] args = new FastpathArg[2]; args[0] = new FastpathArg(fd); args[1] = new FastpathArg(len); @@ -375,7 +411,7 @@ public void truncate64(long len) throws SQLException { } /** - *

Returns an {@link InputStream} from this object.

+ * Returns an {@link InputStream} from this object. * *

This {@link InputStream} can then be used in any method that requires an InputStream.

* @@ -383,7 +419,8 @@ public void truncate64(long len) throws SQLException { * @throws SQLException if a database-access error occurs. */ public InputStream getInputStream() throws SQLException { - return new BlobInputStream(this, 4096); + checkClosed(); + return new BlobInputStream(this); } /** @@ -395,11 +432,27 @@ public InputStream getInputStream() throws SQLException { * @throws SQLException if a database-access error occurs. */ public InputStream getInputStream(long limit) throws SQLException { - return new BlobInputStream(this, 4096, limit); + checkClosed(); + return new BlobInputStream(this, BlobInputStream.DEFAULT_MAX_BUFFER_SIZE, limit); + } + + /** + * Returns an {@link InputStream} from this object, that will limit the amount of data that is + * visible. + * Added mostly for testing + * + * @param bufferSize buffer size for the stream + * @param limit maximum number of bytes the resulting stream will serve + * @return {@link InputStream} from this object + * @throws SQLException if a database-access error occurs. + */ + public InputStream getInputStream(int bufferSize, long limit) throws SQLException { + checkClosed(); + return new BlobInputStream(this, bufferSize, limit); } /** - *

Returns an {@link OutputStream} to this object.

+ * Returns an {@link OutputStream} to this object. * *

This OutputStream can then be used in any method that requires an OutputStream.

* @@ -407,8 +460,9 @@ public InputStream getInputStream(long limit) throws SQLException { * @throws SQLException if a database-access error occurs. */ public OutputStream getOutputStream() throws SQLException { + checkClosed(); if (os == null) { - os = new BlobOutputStream(this, 4096); + os = new BlobOutputStream(this); } return os; } diff --git a/src/main/java/org/postgresql/largeobject/LargeObjectManager.java b/src/main/java/org/postgresql/largeobject/LargeObjectManager.java index 1278096..3fa2cba 100644 --- a/src/main/java/org/postgresql/largeobject/LargeObjectManager.java +++ b/src/main/java/org/postgresql/largeobject/LargeObjectManager.java @@ -56,6 +56,7 @@ * @see java.sql.PreparedStatement#setBinaryStream * @see java.sql.PreparedStatement#setUnicodeStream */ +@SuppressWarnings("deprecation") // support for deprecated Fastpath API public class LargeObjectManager { // the fastpath api for this connection private Fastpath fp; @@ -77,7 +78,7 @@ public class LargeObjectManager { public static final int READWRITE = READ | WRITE; /** - *

Constructs the LargeObject API.

+ * Constructs the LargeObject API. * *

Important Notice
* This method should only be called by {@link BaseConnection}

@@ -144,7 +145,7 @@ public LargeObjectManager(BaseConnection conn) throws SQLException { */ @Deprecated public LargeObject open(int oid) throws SQLException { - return open((long) oid, false); + return open((long) oid); } /** @@ -247,7 +248,7 @@ public LargeObject open(long oid, int mode, boolean commitOnClose) throws SQLExc } /** - *

This creates a large object, returning its OID.

+ * This creates a large object, returning its OID. * *

It defaults to READWRITE for the new object's attributes.

* @@ -261,7 +262,7 @@ public int create() throws SQLException { } /** - *

This creates a large object, returning its OID.

+ * This creates a large object, returning its OID. * *

It defaults to READWRITE for the new object's attributes.

* @@ -316,7 +317,7 @@ public void delete(long oid) throws SQLException { } /** - *

This deletes a large object.

+ * This deletes a large object. * *

It is identical to the delete method, and is supplied as the C API uses unlink.

* @@ -330,7 +331,7 @@ public void unlink(int oid) throws SQLException { } /** - *

This deletes a large object.

+ * This deletes a large object. * *

It is identical to the delete method, and is supplied as the C API uses unlink.

* diff --git a/src/main/java/org/postgresql/plugin/AuthenticationPlugin.java b/src/main/java/org/postgresql/plugin/AuthenticationPlugin.java new file mode 100644 index 0000000..398e56a --- /dev/null +++ b/src/main/java/org/postgresql/plugin/AuthenticationPlugin.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.plugin; + +import org.postgresql.util.PSQLException; + +// import org.checkerframework.checker.nullness.qual.Nullable; + +public interface AuthenticationPlugin { + + /** + * Callback method to provide the password to use for authentication. + * + *

Implementers can also check the authentication type to ensure that the + * authentication handshake is using a specific authentication method (e.g. SASL) + * or avoiding a specific one (e.g. cleartext).

+ * + *

For security reasons, the driver will wipe the contents of the array returned + * by this method after it has been used for authentication.

+ * + *

Implementers must provide a new array each time this method is invoked as + * the previous contents will have been wiped.

+ * + * @param type The authentication method that the server is requesting + * @return The password to use or null if no password is available + * @throws PSQLException if something goes wrong supplying the password + */ + char /* @Nullable */ [] getPassword(AuthenticationRequestType type) throws PSQLException; + +} diff --git a/src/main/java/org/postgresql/plugin/AuthenticationRequestType.java b/src/main/java/org/postgresql/plugin/AuthenticationRequestType.java new file mode 100644 index 0000000..f62bb11 --- /dev/null +++ b/src/main/java/org/postgresql/plugin/AuthenticationRequestType.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.plugin; + +public enum AuthenticationRequestType { + CLEARTEXT_PASSWORD, + GSS, + MD5_PASSWORD, + SASL, +} diff --git a/src/main/java/org/postgresql/replication/LogSequenceNumber.java b/src/main/java/org/postgresql/replication/LogSequenceNumber.java index f1ed18e..459696a 100644 --- a/src/main/java/org/postgresql/replication/LogSequenceNumber.java +++ b/src/main/java/org/postgresql/replication/LogSequenceNumber.java @@ -100,7 +100,7 @@ public boolean equals(/* @Nullable */ Object o) { @Override public int hashCode() { - return (int) (value ^ (value >>> 32)); + return Long.hashCode(value); } @Override @@ -110,11 +110,7 @@ public String toString() { @Override public int compareTo(LogSequenceNumber o) { - if (value == o.value) { - return 0; - } - //Unsigned comparison - return value + Long.MIN_VALUE < o.value + Long.MIN_VALUE ? -1 : 1; + return Long.compareUnsigned(value, o.value); } } diff --git a/src/main/java/org/postgresql/replication/PGReplicationConnection.java b/src/main/java/org/postgresql/replication/PGReplicationConnection.java index 6148f49..c38f47f 100644 --- a/src/main/java/org/postgresql/replication/PGReplicationConnection.java +++ b/src/main/java/org/postgresql/replication/PGReplicationConnection.java @@ -27,7 +27,7 @@ public interface PGReplicationConnection { ChainedStreamBuilder replicationStream(); /** - *

Create replication slot, that can be next use in {@link PGReplicationConnection#replicationStream()}

+ * Create replication slot, that can be next use in {@link PGReplicationConnection#replicationStream()} * *

Replication slots provide an automated way to ensure that the master does not remove WAL * segments until they have been received by all standbys, and that the master does not remove diff --git a/src/main/java/org/postgresql/replication/PGReplicationConnectionImpl.java b/src/main/java/org/postgresql/replication/PGReplicationConnectionImpl.java index 5503391..350526e 100644 --- a/src/main/java/org/postgresql/replication/PGReplicationConnectionImpl.java +++ b/src/main/java/org/postgresql/replication/PGReplicationConnectionImpl.java @@ -15,7 +15,7 @@ import java.sql.Statement; public class PGReplicationConnectionImpl implements PGReplicationConnection { - private BaseConnection connection; + private final BaseConnection connection; public PGReplicationConnectionImpl(BaseConnection connection) { this.connection = connection; diff --git a/src/main/java/org/postgresql/replication/PGReplicationStream.java b/src/main/java/org/postgresql/replication/PGReplicationStream.java index 1fef540..5e3566f 100644 --- a/src/main/java/org/postgresql/replication/PGReplicationStream.java +++ b/src/main/java/org/postgresql/replication/PGReplicationStream.java @@ -14,19 +14,18 @@ import java.sql.SQLException; /** - * Not tread safe replication stream (though certain methods can be safely called by different + * Not thread safe replication stream (though certain methods can be safely called by different * threads). After complete streaming should be close, for free resource on backend. Periodical * status update work only when use {@link PGReplicationStream#read()} method. It means that * process wal record should be fast as possible, because during process wal record lead to * disconnect by timeout from server. */ public interface PGReplicationStream - extends AutoCloseable - /* hi, checkstyle */ { + extends AutoCloseable { /** - *

Read next wal record from backend. It method can be block until new message will not get - * from server.

+ * Read next wal record from backend. It method can be block until new message will not get + * from server. * *

A single WAL record is never split across two XLogData messages. When a WAL record crosses a * WAL page boundary, and is therefore already split using continuation records, it can be split @@ -40,10 +39,10 @@ public interface PGReplicationStream /* @Nullable */ ByteBuffer read() throws SQLException; /** - *

Read next WAL record from backend. This method does not block and in contrast to {@link + * Read next WAL record from backend. This method does not block and in contrast to {@link * PGReplicationStream#read()}. If message from backend absent return null. It allow periodically * check message in stream and if they absent sleep some time, but it time should be less than - * {@link CommonOptions#getStatusInterval()} to avoid disconnect from the server.

+ * {@link CommonOptions#getStatusInterval()} to avoid disconnect from the server. * *

A single WAL record is never split across two XLogData messages. When a WAL record crosses a * WAL page boundary, and is therefore already split using continuation records, it can be split @@ -58,7 +57,7 @@ public interface PGReplicationStream /* @Nullable */ ByteBuffer readPending() throws SQLException; /** - *

Parameter updates by execute {@link PGReplicationStream#read()} method.

+ * Parameter updates by execute {@link PGReplicationStream#read()} method. * *

It is safe to call this method in a thread different than the main thread. However, usually this * method is called in the main thread after a successful {@link PGReplicationStream#read()} or @@ -70,8 +69,8 @@ public interface PGReplicationStream LogSequenceNumber getLastReceiveLSN(); /** - *

Last flushed LSN sent in update message to backend. Parameter updates only via {@link - * PGReplicationStream#setFlushedLSN(LogSequenceNumber)}

+ * Last flushed LSN sent in update message to backend. Parameter updates only via {@link + * PGReplicationStream#setFlushedLSN(LogSequenceNumber)} * *

It is safe to call this method in a thread different than the main thread.

* @@ -80,8 +79,8 @@ public interface PGReplicationStream LogSequenceNumber getLastFlushedLSN(); /** - *

Last applied lsn sent in update message to backed. Parameter updates only via {@link - * PGReplicationStream#setAppliedLSN(LogSequenceNumber)}

+ * Last applied lsn sent in update message to backed. Parameter updates only via {@link + * PGReplicationStream#setAppliedLSN(LogSequenceNumber)} * *

It is safe to call this method in a thread different than the main thread.

* @@ -90,8 +89,8 @@ public interface PGReplicationStream LogSequenceNumber getLastAppliedLSN(); /** - *

Set flushed LSN. This parameter will be sent to backend on next update status iteration. Flushed - * LSN position help backend define which WAL can be recycled.

+ * Set flushed LSN. This parameter will be sent to backend on next update status iteration. Flushed + * LSN position help backend define which WAL can be recycled. * *

It is safe to call this method in a thread different than the main thread. The updated value * will be sent to the backend in the next status update run.

@@ -102,8 +101,8 @@ public interface PGReplicationStream void setFlushedLSN(LogSequenceNumber flushed); /** - *

Inform backend which LSN has been applied on standby. - * Feedback will send to backend on next update status iteration.

+ * Inform backend which LSN has been applied on standby. + * Feedback will send to backend on next update status iteration. * *

It is safe to call this method in a thread different than the main thread. The updated value * will be sent to the backend in the next status update run.

@@ -129,8 +128,8 @@ public interface PGReplicationStream boolean isClosed(); /** - *

Stop replication changes from server and free resources. After that connection can be reuse - * to another queries. Also after close current stream they cannot be used anymore.

+ * Stop replication changes from server and free resources. After that connection can be reuse + * to another queries. Also after close current stream they cannot be used anymore. * *

Note: This method can spend much time for logical replication stream on postgresql * version 9.6 and lower, because postgresql have bug - during decode big transaction to logical @@ -141,5 +140,6 @@ public interface PGReplicationStream * * @throws SQLException when some internal exception occurs during end streaming */ + @Override void close() throws SQLException; } diff --git a/src/main/java/org/postgresql/replication/fluent/AbstractCreateSlotBuilder.java b/src/main/java/org/postgresql/replication/fluent/AbstractCreateSlotBuilder.java index 4d930e6..21e4a19 100644 --- a/src/main/java/org/postgresql/replication/fluent/AbstractCreateSlotBuilder.java +++ b/src/main/java/org/postgresql/replication/fluent/AbstractCreateSlotBuilder.java @@ -17,7 +17,7 @@ public abstract class AbstractCreateSlotBuilder { protected /* @Nullable */ String slotName; - protected boolean temporaryOption = false; + protected boolean temporaryOption; protected BaseConnection connection; protected AbstractCreateSlotBuilder(BaseConnection connection) { diff --git a/src/main/java/org/postgresql/replication/fluent/AbstractStreamBuilder.java b/src/main/java/org/postgresql/replication/fluent/AbstractStreamBuilder.java index 36c2bf5..dfa524d 100644 --- a/src/main/java/org/postgresql/replication/fluent/AbstractStreamBuilder.java +++ b/src/main/java/org/postgresql/replication/fluent/AbstractStreamBuilder.java @@ -17,6 +17,7 @@ public abstract class AbstractStreamBuilderTemporary slots are not saved to disk and are automatically dropped on error or when - * the session has finished.

+ * Temporary slots are not saved to disk and are automatically dropped on error or when + * the session has finished. * *

This feature is only supported by PostgreSQL versions >= 10.

* diff --git a/src/main/java/org/postgresql/replication/fluent/ChainedCommonStreamBuilder.java b/src/main/java/org/postgresql/replication/fluent/ChainedCommonStreamBuilder.java index 2a41246..d2114eb 100644 --- a/src/main/java/org/postgresql/replication/fluent/ChainedCommonStreamBuilder.java +++ b/src/main/java/org/postgresql/replication/fluent/ChainedCommonStreamBuilder.java @@ -45,4 +45,12 @@ public interface ChainedCommonStreamBuilderCreate physical replication stream for process wal logs in binary form.

+ * Create physical replication stream for process wal logs in binary form. * *

Example usage:

*
diff --git a/src/main/java/org/postgresql/replication/fluent/ChainedStreamBuilder.java b/src/main/java/org/postgresql/replication/fluent/ChainedStreamBuilder.java
index 58cbd2e..08a5987 100644
--- a/src/main/java/org/postgresql/replication/fluent/ChainedStreamBuilder.java
+++ b/src/main/java/org/postgresql/replication/fluent/ChainedStreamBuilder.java
@@ -14,12 +14,11 @@
  */
 public interface ChainedStreamBuilder {
   /**
-   * 

Create logical replication stream that decode raw wal logs by output plugin to logical form. + * Create logical replication stream that decode raw wal logs by output plugin to logical form. * Default about logical decoding you can see by following link * * Logical Decoding Concepts * . - *

* *

Example usage:

*
@@ -48,7 +47,7 @@ public interface ChainedStreamBuilder {
   ChainedLogicalStreamBuilder logical();
 
   /**
-   * 

Create physical replication stream for process wal logs in binary form.

+ * Create physical replication stream for process wal logs in binary form. * *

Example usage:

*
@@ -62,6 +61,11 @@ public interface ChainedStreamBuilder {
    *            .replicationStream()
    *            .physical()
    *            .withStartPosition(lsn)
+   *            .withSlotName("test_decoding")
+   *            .withSlotOption("include-xids", false)
+   *            .withSlotOption("skip-empty-xacts", true)
+   *            .withStatusInterval(5, TimeUnit.SECONDS)
+   *            .withAutomaticFlush(true)
    *            .start();
    *
    *    while (true) {
diff --git a/src/main/java/org/postgresql/replication/fluent/CommonOptions.java b/src/main/java/org/postgresql/replication/fluent/CommonOptions.java
index ebd3ef3..653133a 100644
--- a/src/main/java/org/postgresql/replication/fluent/CommonOptions.java
+++ b/src/main/java/org/postgresql/replication/fluent/CommonOptions.java
@@ -36,4 +36,6 @@ public interface CommonOptions {
    * @return the current status interval
    */
   int getStatusInterval();
+
+  boolean getAutomaticFlush();
 }
diff --git a/src/main/java/org/postgresql/replication/fluent/logical/ChainedLogicalCreateSlotBuilder.java b/src/main/java/org/postgresql/replication/fluent/logical/ChainedLogicalCreateSlotBuilder.java
index cae77bb..affd565 100644
--- a/src/main/java/org/postgresql/replication/fluent/logical/ChainedLogicalCreateSlotBuilder.java
+++ b/src/main/java/org/postgresql/replication/fluent/logical/ChainedLogicalCreateSlotBuilder.java
@@ -14,8 +14,8 @@ public interface ChainedLogicalCreateSlotBuilder
     extends ChainedCommonCreateSlotBuilder {
 
   /**
-   * 

Output plugin that should be use for decode physical represent WAL to some logical form. - * Output plugin should be installed on server(exists in shared_preload_libraries).

+ * Output plugin that should be use for decode physical represent WAL to some logical form. + * Output plugin should be installed on server(exists in shared_preload_libraries). * *

Package postgresql-contrib provides sample output plugin test_decoding that can be * use for test logical replication api

diff --git a/src/main/java/org/postgresql/replication/fluent/logical/LogicalReplicationOptions.java b/src/main/java/org/postgresql/replication/fluent/logical/LogicalReplicationOptions.java index 7019e18..8bcf0fe 100644 --- a/src/main/java/org/postgresql/replication/fluent/logical/LogicalReplicationOptions.java +++ b/src/main/java/org/postgresql/replication/fluent/logical/LogicalReplicationOptions.java @@ -17,6 +17,7 @@ public interface LogicalReplicationOptions extends CommonOptions { * * @return not null logical replication slot name that already exists on server and free. */ + @Override /* @Nullable */ String getSlotName(); /** diff --git a/src/main/java/org/postgresql/replication/fluent/logical/LogicalStreamBuilder.java b/src/main/java/org/postgresql/replication/fluent/logical/LogicalStreamBuilder.java index e73d086..e8c104a 100644 --- a/src/main/java/org/postgresql/replication/fluent/logical/LogicalStreamBuilder.java +++ b/src/main/java/org/postgresql/replication/fluent/logical/LogicalStreamBuilder.java @@ -20,7 +20,7 @@ public class LogicalStreamBuilder extends AbstractStreamBuilder 0) { + for (String kt : keyType) { + if (kt.equalsIgnoreCase(certKeyType)) { + keyTypeFound = true; + } + } + } else { + // If no key types were passed in, assume we don't care + // about checking that the cert uses a particular key type. + keyTypeFound = true; + } + if (keyTypeFound) { + for (Principal issuer : principals) { + if (ourissuer.equals(issuer)) { + found = keyTypeFound; + } + } + } + return found ? "user" : null; + } + } + } + + @Override + public String /* @Nullable */ [] getServerAliases(String s, Principal /* @Nullable */ [] principals) { + return new String[]{}; + } + + @Override + public /* @Nullable */ String chooseServerAlias(String s, Principal /* @Nullable */ [] principals, + /* @Nullable */ Socket socket) { + // we are not a server + return null; + } + + /** + * Validates that the private key file has secure permissions, matching libpq behavior. + * On POSIX systems, root-owned files are allowed group-read access (up to 0640), since + * it's common for root to own certs and grant read access via group membership. Files + * owned by anyone else must be 0600 or stricter. + * On Windows, ACLs are checked to ensure only the owner and trusted system accounts have access. + * + * @param keyPath the path to the private key file + * @throws PSQLException if the file has insecure permissions + */ + public static void validateKeyFilePermissions(Path keyPath) throws PSQLException { + // Try POSIX permissions first (Linux, macOS, Unix) + if (validatePosixPermissions(keyPath)) { + return; + } + + // If POSIX is not supported, try Windows ACL permissions + if (validateWindowsAclPermissions(keyPath)) { + return; + } + throw new PSQLException( + GT.tr("Unable to retrieve the permissions of the private key file \"{0}\"", + keyPath.toString()), + PSQLState.CONNECTION_FAILURE); + } + + /** + * Validates POSIX file permissions of key, matching libpq behavior. + * Root-owned files (uid 0) allow GROUP_READ (up to 0640). + * Non-root-owned files require 0600 or less (no group or other permissions). + * + * @param keyPath the path to the private key file + * @return true if validation succeeded (permissions are secure), false if POSIX is not supported + * @throws PSQLException if the file has insecure permissions + */ + private static boolean validatePosixPermissions(Path keyPath) throws PSQLException { + try { + Set permissions = Files.getPosixFilePermissions(keyPath); + boolean isOwnedByRoot = isFileOwnedByRoot(keyPath); + + if (hasInsecurePosixPermissions(permissions, isOwnedByRoot)) { + throw new PSQLException( + GT.tr("private key file \"{0}\" has group or world access; " + + "file must have permissions u=rw (0600) or less if owned by the current user, " + + "or permissions u=rw,g=r (0640) or less if owned by root. " + + "Current permissions: {1}", + keyPath.toString(), + PosixFilePermissions.toString(permissions)), + PSQLState.CONNECTION_FAILURE); + } + return true; + } catch (UnsupportedOperationException e) { + return false; + } catch (IOException e) { + throw new PSQLException( + GT.tr("Could not read permissions for private key file \"{0}\"", keyPath.toString()), + PSQLState.CONNECTION_FAILURE, e); + } + } + + /** + * Checks whether the file is owned by root (uid 0). + * Falls back to false if the unix:uid attribute is not available. + */ + private static boolean isFileOwnedByRoot(Path keyPath) throws IOException { + try { + Object uid = Files.getAttribute(keyPath, "unix:uid"); + return Integer.valueOf(ROOT_UID).equals(uid); + } catch (UnsupportedOperationException | IllegalArgumentException e) { + // unix:uid not available + return false; + } + } + + /** + * Determines whether the given POSIX permissions are insecure for a private key file. + * Matches libpq behavior: root-owned files allow GROUP_READ (0640), + * while non-root-owned files reject all group and other permissions (0600). + */ + private static boolean hasInsecurePosixPermissions( + Set permissions, boolean isOwnedByRoot) { + boolean hasOtherPerms = permissions.contains(PosixFilePermission.OTHERS_READ) + || permissions.contains(PosixFilePermission.OTHERS_WRITE) + || permissions.contains(PosixFilePermission.OTHERS_EXECUTE); + + if (isOwnedByRoot) { + boolean hasGroupWriteOrExecute = permissions.contains(PosixFilePermission.GROUP_WRITE) + || permissions.contains(PosixFilePermission.GROUP_EXECUTE); + return hasGroupWriteOrExecute || hasOtherPerms; + } + + boolean hasGroupPerms = permissions.contains(PosixFilePermission.GROUP_READ) + || permissions.contains(PosixFilePermission.GROUP_WRITE) + || permissions.contains(PosixFilePermission.GROUP_EXECUTE); + return hasGroupPerms || hasOtherPerms; + } + + /** + * Validates Windows ACL permissions of the key file. + * + * @param keyPath the path to the private key file + * @return true if validation succeeded (permissions are secure), false if ACL is not supported + * @throws PSQLException if the file has insecure permissions + */ + private static boolean validateWindowsAclPermissions(Path keyPath) throws PSQLException { + try { + AclFileAttributeView aclView = Files.getFileAttributeView(keyPath, AclFileAttributeView.class); + if (aclView == null) { + return false; + } + + UserPrincipal owner = aclView.getOwner(); + List aclEntries = aclView.getAcl(); + + for (AclEntry entry : aclEntries) { + UserPrincipal principal = entry.principal(); + String principalName = principal.getName(); + + // Allow owner, SYSTEM, and Administrators (these are trusted on Windows) + boolean isTrustedPrincipal = principal.equals(owner) + || principalName.equals("NT AUTHORITY\\SYSTEM") + || principalName.equals("BUILTIN\\Administrators") + || principalName.endsWith("\\Administrators"); + + if (!isTrustedPrincipal) { + // Check if this non-owner principal has READ permission + Set permissions = entry.permissions(); + if (permissions.contains(AclEntryPermission.READ_DATA) + || permissions.contains(AclEntryPermission.READ_ATTRIBUTES) + || permissions.contains(AclEntryPermission.READ_NAMED_ATTRS)) { + throw new PSQLException( + GT.tr("Private key file \"{0}\" has insecure permissions. " + + "Non-owner principal \"{1}\" has read access.", + keyPath.toString(), + principalName), + PSQLState.CONNECTION_FAILURE); + } + } + } + return true; + } catch (UnsupportedOperationException e) { + return false; + } catch (IOException e) { + throw new PSQLException( + GT.tr("Could not read ACL permissions for private key file \"{0}\"", keyPath.toString()), + PSQLState.CONNECTION_FAILURE, e); + } + } +} diff --git a/src/main/java/org/postgresql/ssl/DbKeyStoreSocketFactory.java b/src/main/java/org/postgresql/ssl/DbKeyStoreSocketFactory.java index cb8131d..a81226f 100644 --- a/src/main/java/org/postgresql/ssl/DbKeyStoreSocketFactory.java +++ b/src/main/java/org/postgresql/ssl/DbKeyStoreSocketFactory.java @@ -5,14 +5,17 @@ package org.postgresql.ssl; +import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; +import java.security.GeneralSecurityException; import java.security.KeyStore; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; -public abstract class DbKeyStoreSocketFactory extends org.postgresql.ssl.WrappedFactory { +public abstract class DbKeyStoreSocketFactory extends WrappedFactory { /* * Populate the WrappedFactory member factory with an SSL Socket Factory that uses the JKS * keystore provided by getKeyStorePassword() and getKeyStoreStream(). A subclass only needs to @@ -20,21 +23,21 @@ public abstract class DbKeyStoreSocketFactory extends org.postgresql.ssl.Wrapped * certificate to send to the server, as well as checking the server's certificate against a set * of trusted CAs. */ - @SuppressWarnings("nullness:method.invocation.invalid") + @SuppressWarnings("method.invocation") public DbKeyStoreSocketFactory() throws DbKeyStoreSocketException { KeyStore keys; char[] password; try { keys = KeyStore.getInstance("JKS"); // Call of the sub-class method during object initialization is generally a bad idea - // Currently we suppress it with method.invocation.invalid + // Currently we suppress it with method.invocation password = getKeyStorePassword(); keys.load(getKeyStoreStream(), password); - } catch (java.security.GeneralSecurityException gse) { + } catch (GeneralSecurityException gse) { throw new DbKeyStoreSocketException("Failed to load keystore: " + gse.getMessage()); - } catch (java.io.FileNotFoundException fnfe) { + } catch (FileNotFoundException fnfe) { throw new DbKeyStoreSocketException("Failed to find keystore file." + fnfe.getMessage()); - } catch (java.io.IOException ioe) { + } catch (IOException ioe) { throw new DbKeyStoreSocketException("Failed to read keystore file: " + ioe.getMessage()); } try { @@ -49,7 +52,7 @@ public DbKeyStoreSocketFactory() throws DbKeyStoreSocketException { SSLContext ctx = SSLContext.getInstance("SSL"); ctx.init(keyfact.getKeyManagers(), trustfact.getTrustManagers(), null); factory = ctx.getSocketFactory(); - } catch (java.security.GeneralSecurityException gse) { + } catch (GeneralSecurityException gse) { throw new DbKeyStoreSocketException( "Failed to set up database socket factory: " + gse.getMessage()); } diff --git a/src/main/java/org/postgresql/ssl/LazyKeyManager.java b/src/main/java/org/postgresql/ssl/LazyKeyManager.java index 5ae37c4..7eb2ef6 100644 --- a/src/main/java/org/postgresql/ssl/LazyKeyManager.java +++ b/src/main/java/org/postgresql/ssl/LazyKeyManager.java @@ -8,12 +8,13 @@ import org.postgresql.util.GT; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; +import org.postgresql.util.internal.FileUtils; // import org.checkerframework.checker.nullness.qual.Nullable; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.io.RandomAccessFile; import java.net.Socket; import java.security.AlgorithmParameters; @@ -48,13 +49,13 @@ * A Key manager that only loads the keys, if necessary. */ public class LazyKeyManager implements X509KeyManager { - private X509Certificate /* @Nullable */ [] cert = null; - private /* @Nullable */ PrivateKey key = null; + private X509Certificate /* @Nullable */ [] cert; + private /* @Nullable */ PrivateKey key; private final /* @Nullable */ String certfile; private final /* @Nullable */ String keyfile; private final CallbackHandler cbh; private final boolean defaultfile; - private /* @Nullable */ PSQLException error = null; + private /* @Nullable */ PSQLException error; /** * Constructor. certfile and keyfile can be null, in that case no certificate is presented to the @@ -73,7 +74,7 @@ public LazyKeyManager(/* @Nullable */ String certfile, /* @Nullable */ String ke } /** - * getCertificateChain and getPrivateKey cannot throw exeptions, therefore any exception is stored + * getCertificateChain and getPrivateKey cannot throw exceptions, therefore any exception is stored * in {@link #error} and can be raised by this method. * * @throws PSQLException if any exception is stored in {@link #error} and can be raised @@ -102,14 +103,30 @@ public void throwKeyManagerException() throws PSQLException { if (certchain == null) { return null; } else { - X500Principal ourissuer = certchain[certchain.length - 1].getIssuerX500Principal(); + X509Certificate cert = certchain[certchain.length - 1]; + X500Principal ourissuer = cert.getIssuerX500Principal(); + String certKeyType = cert.getPublicKey().getAlgorithm(); + boolean keyTypeFound = false; boolean found = false; - for (Principal issuer : issuers) { - if (ourissuer.equals(issuer)) { - found = true; + if (keyType != null && keyType.length > 0) { + for (String kt : keyType) { + if (kt.equalsIgnoreCase(certKeyType)) { + keyTypeFound = true; + } } + } else { + // If no key types were passed in, assume we don't care + // about checking that the cert uses a particular key type. + keyTypeFound = true; } - return (found ? "user" : null); + if (keyTypeFound) { + for (Principal issuer : issuers) { + if (ourissuer.equals(issuer)) { + found = keyTypeFound; + } + } + } + return found ? "user" : null; } } } @@ -138,9 +155,9 @@ public void throwKeyManagerException() throws PSQLException { return null; } Collection certs; - FileInputStream certfileStream = null; + InputStream certfileStream = null; try { - certfileStream = new FileInputStream(certfile); + certfileStream = FileUtils.newBufferedInputStream(certfile); certs = cf.generateCertificates(certfileStream); } catch (FileNotFoundException ioex) { if (!defaultfile) { // It is not an error if there is no file at the default location @@ -175,7 +192,7 @@ public void throwKeyManagerException() throws PSQLException { public String /* @Nullable */ [] getClientAliases(String keyType, Principal /* @Nullable */ [] issuers) { String alias = chooseClientAlias(new String[]{keyType}, issuers, (Socket) null); - return (alias == null ? new String[]{} : new String[]{alias}); + return alias == null ? new String[]{} : new String[]{alias}; } private static byte[] readFileFully(String path) throws IOException { @@ -253,7 +270,7 @@ private static byte[] readFileFully(String path) throws IOException { // Extract the iteration count and the salt AlgorithmParameters algParams = ePKInfo.getAlgParameters(); cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams); - // Decrypt the encryped private key into a PKCS8EncodedKeySpec + // Decrypt the encrypted private key into a PKCS8EncodedKeySpec KeySpec pkcs8KeySpec = ePKInfo.getKeySpec(cipher); key = kf.generatePrivate(pkcs8KeySpec); } catch (GeneralSecurityException ikex) { diff --git a/src/main/java/org/postgresql/ssl/LibPQFactory.java b/src/main/java/org/postgresql/ssl/LibPQFactory.java index bea4926..ac48b85 100644 --- a/src/main/java/org/postgresql/ssl/LibPQFactory.java +++ b/src/main/java/org/postgresql/ssl/LibPQFactory.java @@ -14,14 +14,15 @@ import org.postgresql.util.ObjectFactory; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; +import org.postgresql.util.internal.FileUtils; // import org.checkerframework.checker.initialization.qual.UnderInitialization; // import org.checkerframework.checker.nullness.qual.Nullable; import java.io.Console; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.KeyManagementException; import java.security.KeyStore; @@ -29,6 +30,7 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; +import java.util.Locale; import java.util.Properties; import javax.net.ssl.KeyManager; @@ -53,10 +55,10 @@ private CallbackHandler getCallbackHandler( Properties info) throws PSQLException { // Determine the callback handler CallbackHandler cbh; - String sslpasswordcallback = PGProperty.SSL_PASSWORD_CALLBACK.get(info); + String sslpasswordcallback = PGProperty.SSL_PASSWORD_CALLBACK.getOrDefault(info); if (sslpasswordcallback != null) { try { - cbh = (CallbackHandler) ObjectFactory.instantiate(sslpasswordcallback, info, false, null); + cbh = ObjectFactory.instantiate(CallbackHandler.class, sslpasswordcallback, info, false, null); } catch (Exception e) { throw new PSQLException( GT.tr("The password callback class provided {0} could not be instantiated.", @@ -64,21 +66,27 @@ private CallbackHandler getCallbackHandler( PSQLState.CONNECTION_FAILURE, e); } } else { - cbh = new ConsoleCallbackHandler(PGProperty.SSL_PASSWORD.get(info)); + cbh = new ConsoleCallbackHandler(PGProperty.SSL_PASSWORD.getOrDefault(info)); } return cbh; } - private void initPk8( - /* @UnderInitialization(WrappedFactory.class) */ LibPQFactory this, - String sslkeyfile, String defaultdir, Properties info) throws PSQLException { - + private String getCertFilePath( + /* @UnderInitialization(WrappedFactory.class) */LibPQFactory this, String defaultdir, Properties info) { // Load the client's certificate and key - String sslcertfile = PGProperty.SSL_CERT.get(info); + String sslcertfile = PGProperty.SSL_CERT.getOrDefault(info); if (sslcertfile == null) { // Fall back to default defaultfile = true; sslcertfile = defaultdir + "postgresql.crt"; } + return sslcertfile; + } + + private void initPk8( + /* @UnderInitialization(WrappedFactory.class) */LibPQFactory this, + String sslkeyfile, String defaultdir, Properties info) throws PSQLException { + + String sslcertfile = getCertFilePath(defaultdir, info); // If the properties are empty, give null to prevent client key selection km = new LazyKeyManager(("".equals(sslcertfile) ? null : sslcertfile), @@ -91,6 +99,18 @@ private void initP12( km = new PKCS12KeyManager(sslkeyfile, getCallbackHandler(info)); } + private void initPEM( + /* @UnderInitialization(WrappedFactory.class) */LibPQFactory this, + String sslKeyFile, String defaultdir, Properties info) throws PSQLException { + try { + String sslCertFile = getCertFilePath(defaultdir, info); + String algorithm = castNonNull(PGProperty.PEM_KEY_ALGORITHM.getOrDefault(info)); + km = new PEMKeyManager(sslKeyFile, sslCertFile, algorithm); + } catch (Exception ex) { + throw new PSQLException(GT.tr("Could not initialize PEMKeyManager."), PSQLState.CONNECTION_FAILURE, ex); + } + } + /** * @param info the connection parameters The following parameters are used: * sslmode,sslcert,sslkey,sslrootcert,sslhostnameverifier,sslpasswordcallback,sslpassword @@ -104,13 +124,13 @@ public LibPQFactory(Properties info) throws PSQLException { String pathsep = System.getProperty("file.separator"); String defaultdir; - if (System.getProperty("os.name").toLowerCase().contains("windows")) { // It is Windows + if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows")) { // It is Windows defaultdir = System.getenv("APPDATA") + pathsep + "postgresql" + pathsep; } else { defaultdir = System.getProperty("user.home") + pathsep + ".postgresql" + pathsep; } - String sslkeyfile = PGProperty.SSL_KEY.get(info); + String sslkeyfile = PGProperty.SSL_KEY.getOrDefault(info); if (sslkeyfile == null) { // Fall back to default defaultfile = true; sslkeyfile = defaultdir + "postgresql.pk8"; @@ -118,6 +138,8 @@ public LibPQFactory(Properties info) throws PSQLException { if (sslkeyfile.endsWith(".p12") || sslkeyfile.endsWith(".pfx")) { initP12(sslkeyfile, info); + } else if (sslkeyfile.endsWith(".key") || sslkeyfile.endsWith(".pem")) { + initPEM(sslkeyfile, defaultdir, info); } else { initPk8(sslkeyfile, defaultdir, info); } @@ -138,13 +160,13 @@ public LibPQFactory(Properties info) throws PSQLException { // this should never happen throw new NoSuchAlgorithmException("jks KeyStore not available"); } - String sslrootcertfile = PGProperty.SSL_ROOT_CERT.get(info); + String sslrootcertfile = PGProperty.SSL_ROOT_CERT.getOrDefault(info); if (sslrootcertfile == null) { // Fall back to default sslrootcertfile = defaultdir + "root.crt"; } - FileInputStream fis; + InputStream is; try { - fis = new FileInputStream(sslrootcertfile); // NOSONAR + is = FileUtils.newBufferedInputStream(sslrootcertfile); // NOSONAR } catch (FileNotFoundException ex) { throw new PSQLException( GT.tr("Could not open SSL root certificate file {0}.", sslrootcertfile), @@ -152,9 +174,9 @@ public LibPQFactory(Properties info) throws PSQLException { } try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); - // Certificate[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{}); //Does + // Certificate[] certs = cf.generateCertificates(is).toArray(new Certificate[]{}); //Does // not work in java 1.4 - Object[] certs = cf.generateCertificates(fis).toArray(new Certificate[]{}); + Object[] certs = cf.generateCertificates(is).toArray(new Certificate[]{}); ks.load(null, null); for (int i = 0; i < certs.length; i++) { ks.setCertificateEntry("cert" + i, (Certificate) certs[i]); @@ -171,7 +193,7 @@ public LibPQFactory(Properties info) throws PSQLException { PSQLState.CONNECTION_FAILURE, gsex); } finally { try { - fis.close(); + is.close(); } catch (IOException e) { /* ignore */ } @@ -203,10 +225,13 @@ public LibPQFactory(Properties info) throws PSQLException { public void throwKeyManagerException() throws PSQLException { if (km != null) { if (km instanceof LazyKeyManager) { - ((LazyKeyManager)km).throwKeyManagerException(); + ((LazyKeyManager) km).throwKeyManagerException(); } if (km instanceof PKCS12KeyManager) { - ((PKCS12KeyManager)km).throwKeyManagerException(); + ((PKCS12KeyManager) km).throwKeyManagerException(); + } + if (km instanceof PEMKeyManager) { + ((PEMKeyManager) km).throwKeyManagerException(); } } } @@ -217,7 +242,7 @@ public void throwKeyManagerException() throws PSQLException { */ public static class ConsoleCallbackHandler implements CallbackHandler { - private char /* @Nullable */ [] password = null; + private char /* @Nullable */ [] password; ConsoleCallbackHandler(/* @Nullable */ String password) { if (password != null) { @@ -233,6 +258,7 @@ public static class ConsoleCallbackHandler implements CallbackHandler { * PasswordCallback is supplied */ @Override + @SuppressWarnings("SystemConsoleNull") public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { Console cons = System.console(); char[] password = this.password; diff --git a/src/main/java/org/postgresql/ssl/MakeSSL.java b/src/main/java/org/postgresql/ssl/MakeSSL.java index bf64673..cbcc02e 100644 --- a/src/main/java/org/postgresql/ssl/MakeSSL.java +++ b/src/main/java/org/postgresql/ssl/MakeSSL.java @@ -9,10 +9,12 @@ import org.postgresql.core.PGStream; import org.postgresql.core.SocketFactoryFactory; import org.postgresql.jdbc.SslMode; +import org.postgresql.jdbc.SslNegotiation; import org.postgresql.util.GT; import org.postgresql.util.ObjectFactory; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; +import org.postgresql.util.internal.Nullness; import java.io.IOException; import java.util.Properties; @@ -20,6 +22,7 @@ import java.util.logging.Logger; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; @@ -36,6 +39,13 @@ public static void convert(PGStream stream, Properties info) try { newConnection = (SSLSocket) factory.createSocket(stream.getSocket(), stream.getHostSpec().getHost(), stream.getHostSpec().getPort(), true); + int connectTimeoutSeconds = PGProperty.CONNECT_TIMEOUT.getInt(info); + newConnection.setSoTimeout(connectTimeoutSeconds * 1000); + if (SslNegotiation.of(Nullness.castNonNull(PGProperty.SSL_NEGOTIATION.getOrDefault(info))) == SslNegotiation.DIRECT ) { + SSLParameters sslParameters = newConnection.getSSLParameters(); + sslParameters.setApplicationProtocols(new String[]{"postgresql"}); + newConnection.setSSLParameters(sslParameters); + } // We must invoke manually, otherwise the exceptions are hidden newConnection.setUseClientMode(true); newConnection.startHandshake(); @@ -51,20 +61,22 @@ public static void convert(PGStream stream, Properties info) if (sslMode.verifyPeerName()) { verifyPeerName(stream, info, newConnection); } - + // Zero timeout (default) means infinite + int socketTimeout = PGProperty.SOCKET_TIMEOUT.getInt(info); + newConnection.setSoTimeout(socketTimeout * 1000); stream.changeSocket(newConnection); } private static void verifyPeerName(PGStream stream, Properties info, SSLSocket newConnection) throws PSQLException { HostnameVerifier hvn; - String sslhostnameverifier = PGProperty.SSL_HOSTNAME_VERIFIER.get(info); + String sslhostnameverifier = PGProperty.SSL_HOSTNAME_VERIFIER.getOrDefault(info); if (sslhostnameverifier == null) { hvn = PGjdbcHostnameVerifier.INSTANCE; sslhostnameverifier = "PgjdbcHostnameVerifier"; } else { try { - hvn = (HostnameVerifier) instantiate(sslhostnameverifier, info, false, null); + hvn = instantiate(HostnameVerifier.class, sslhostnameverifier, info, false, null); } catch (Exception e) { throw new PSQLException( GT.tr("The HostnameVerifier class provided {0} could not be instantiated.", diff --git a/src/main/java/org/postgresql/ssl/NonValidatingFactory.java b/src/main/java/org/postgresql/ssl/NonValidatingFactory.java index e7d804b..649a54d 100644 --- a/src/main/java/org/postgresql/ssl/NonValidatingFactory.java +++ b/src/main/java/org/postgresql/ssl/NonValidatingFactory.java @@ -37,13 +37,16 @@ public NonValidatingFactory(String arg) throws GeneralSecurityException { public static class NonValidatingTM implements X509TrustManager { + @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } + @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { } + @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { } } diff --git a/src/main/java/org/postgresql/ssl/PEMKeyManager.java b/src/main/java/org/postgresql/ssl/PEMKeyManager.java new file mode 100644 index 0000000..5dc8982 --- /dev/null +++ b/src/main/java/org/postgresql/ssl/PEMKeyManager.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2025, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.ssl; + +import org.postgresql.util.GT; +import org.postgresql.util.PSQLException; +import org.postgresql.util.PSQLState; + +// import org.checkerframework.checker.nullness.qual.Nullable; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; +import java.util.List; + +public class PEMKeyManager extends BaseX509KeyManager { + + private final String keyFilePath; + private final String certFilePath; + private final String keyAlgorithm; + + public PEMKeyManager(String pemKeyPath, String pemCertsPath, String keyAlgorithm) { + this.keyFilePath = pemKeyPath; + this.certFilePath = pemCertsPath; + this.keyAlgorithm = keyAlgorithm; + } + + @Override + public /* @Nullable */ PrivateKey getPrivateKey(String s) { + try { + Path keyPath = Paths.get(keyFilePath); + + // Validate file permissions before reading + validateKeyFilePermissions(keyPath); + + List lines = Files.readAllLines(keyPath); + StringBuilder keyContent = new StringBuilder(); + for (String line : lines) { + // as we are using PKCS#8 format, we just expect "BEGIN PRIVATE KEY" as the start of the key + // ref: https://datatracker.ietf.org/doc/html/rfc5208#section-5 + if (line.contains("BEGIN PRIVATE KEY")) { + // ignore the start of the key + continue; + } + // as we are using PKCS#8 format, we just expect "END PRIVATE KEY" as the end of the key + // ref: https://datatracker.ietf.org/doc/html/rfc5208#section-5 + if (line.contains("END PRIVATE KEY")) { + // stop reading after we encounter end of the key + break; + } + keyContent.append(line.trim()); + } + + byte[] privateKeyDERBytes = Base64.getDecoder().decode(keyContent.toString()); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyDERBytes); + KeyFactory kf = KeyFactory.getInstance(this.keyAlgorithm); + + // to prevent security attacks, lets wipe out the contents of variables holding sensitive data + Arrays.fill(privateKeyDERBytes, (byte) 0); + for (int i = 0; i < keyContent.length(); i++) { + keyContent.setCharAt(i, '\0'); + } + + return kf.generatePrivate(keySpec); + } catch (Exception e) { + error = new PSQLException(GT.tr("Could not load the private key"), PSQLState.CONNECTION_FAILURE, e); + } + return null; + } + + @Override + public X509Certificate /* @Nullable */ [] getCertificateChain(String alias) { + try (InputStream inStream = Files.newInputStream(Paths.get(this.certFilePath))) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + Collection certs = cf.generateCertificates(inStream); + List certChain = new ArrayList<>(); + + for (Certificate cert : certs) { + if (cert instanceof X509Certificate) { + certChain.add((X509Certificate) cert); + } + } + + return certChain.toArray(new X509Certificate[0]); + } catch (Exception e) { + error = new PSQLException(GT.tr("Could not load cert chain"), PSQLState.CONNECTION_FAILURE, e); + } + return null; + } + +} diff --git a/src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java b/src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java index 20de49f..300bca9 100644 --- a/src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java +++ b/src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java @@ -188,7 +188,7 @@ public boolean verify(String hostname, SSLSession session) { return false; } - List commonNames = new ArrayList(1); + List commonNames = new ArrayList<>(1); for (Rdn rdn : dn.getRdns()) { if ("CN".equals(rdn.getType())) { commonNames.add((String) rdn.getValue()); diff --git a/src/main/java/org/postgresql/ssl/PKCS12KeyManager.java b/src/main/java/org/postgresql/ssl/PKCS12KeyManager.java index 4951581..ef3a5f3 100644 --- a/src/main/java/org/postgresql/ssl/PKCS12KeyManager.java +++ b/src/main/java/org/postgresql/ssl/PKCS12KeyManager.java @@ -5,36 +5,32 @@ package org.postgresql.ssl; +import org.postgresql.jdbc.ResourceLock; import org.postgresql.util.GT; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; +import org.postgresql.util.internal.FileUtils; // import org.checkerframework.checker.nullness.qual.Nullable; -import java.io.File; -import java.io.FileInputStream; -import java.net.Socket; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.Principal; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; -import javax.net.ssl.X509KeyManager; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.x500.X500Principal; -public class PKCS12KeyManager implements X509KeyManager { +public class PKCS12KeyManager extends BaseX509KeyManager { private final CallbackHandler cbh; - private /* @Nullable */ PSQLException error = null; private final String keyfile; private final KeyStore keyStore; - boolean keystoreLoaded = false; + boolean keystoreLoaded; + private final ResourceLock lock = new ResourceLock(); public PKCS12KeyManager(String pkcsFile, CallbackHandler cbh) throws PSQLException { try { @@ -48,63 +44,6 @@ public PKCS12KeyManager(String pkcsFile, CallbackHandler cbh) throws PSQLExcepti } } - /** - * getCertificateChain and getPrivateKey cannot throw exeptions, therefore any exception is stored - * in {@link #error} and can be raised by this method. - * - * @throws PSQLException if any exception is stored in {@link #error} and can be raised - */ - public void throwKeyManagerException() throws PSQLException { - if (error != null) { - throw error; - } - } - - @Override - public String /* @Nullable */ [] getClientAliases(String keyType, Principal /* @Nullable */ [] principals) { - String alias = chooseClientAlias(new String[]{keyType}, principals, (Socket) null); - return alias == null ? null : new String[]{alias}; - } - - @Override - public /* @Nullable */ String chooseClientAlias(String[] strings, Principal /* @Nullable */ [] principals, - /* @Nullable */ Socket socket) { - if (principals == null || principals.length == 0) { - // Postgres 8.4 and earlier do not send the list of accepted certificate authorities - // to the client. See BUG #5468. We only hope, that our certificate will be accepted. - return "user"; - } else { - // Sending a wrong certificate makes the connection rejected, even, if clientcert=0 in - // pg_hba.conf. - // therefore we only send our certificate, if the issuer is listed in issuers - X509Certificate[] certchain = getCertificateChain("user"); - if (certchain == null) { - return null; - } else { - X500Principal ourissuer = certchain[certchain.length - 1].getIssuerX500Principal(); - boolean found = false; - for (Principal issuer : principals) { - if (ourissuer.equals(issuer)) { - found = true; - } - } - return (found ? "user" : null); - } - } - } - - @Override - public String /* @Nullable */ [] getServerAliases(String s, Principal /* @Nullable */ [] principals) { - return new String[]{}; - } - - @Override - public /* @Nullable */ String chooseServerAlias(String s, Principal /* @Nullable */ [] principals, - /* @Nullable */ Socket socket) { - // we are not a server - return null; - } - @Override public X509Certificate /* @Nullable */ [] getCertificateChain(String alias) { try { @@ -116,7 +55,7 @@ public void throwKeyManagerException() throws PSQLException { X509Certificate[] x509Certificates = new X509Certificate[certs.length]; int i = 0; for (Certificate cert : certs) { - x509Certificates[i++] = (X509Certificate)cert; + x509Certificates[i++] = (X509Certificate) cert; } return x509Certificates; } catch (Exception kse) { @@ -140,8 +79,7 @@ public void throwKeyManagerException() throws PSQLException { if (pkEntry == null) { return null; } - PrivateKey myPrivateKey = pkEntry.getPrivateKey(); - return myPrivateKey; + return pkEntry.getPrivateKey(); } catch (Exception ioex ) { error = new PSQLException(GT.tr("Could not read SSL key file {0}.", keyfile), PSQLState.CONNECTION_FAILURE, ioex); @@ -149,33 +87,34 @@ public void throwKeyManagerException() throws PSQLException { return null; } - private synchronized void loadKeyStore() throws Exception { - - if (keystoreLoaded) { - return; - } - // We call back for the password - PasswordCallback pwdcb = new PasswordCallback(GT.tr("Enter SSL password: "), false); - try { - cbh.handle(new Callback[]{pwdcb}); - } catch (UnsupportedCallbackException ucex) { - if ((cbh instanceof LibPQFactory.ConsoleCallbackHandler) - && ("Console is not available".equals(ucex.getMessage()))) { - error = new PSQLException(GT - .tr("Could not read password for SSL key file, console is not available."), - PSQLState.CONNECTION_FAILURE, ucex); - } else { - error = - new PSQLException( - GT.tr("Could not read password for SSL key file by callbackhandler {0}.", - cbh.getClass().getName()), + private void loadKeyStore() throws Exception { + try (ResourceLock ignore = lock.obtain()) { + if (keystoreLoaded) { + return; + } + // We call back for the password + PasswordCallback pwdcb = new PasswordCallback(GT.tr("Enter SSL password: "), false); + try { + cbh.handle(new Callback[]{pwdcb}); + } catch (UnsupportedCallbackException ucex) { + if ((cbh instanceof LibPQFactory.ConsoleCallbackHandler) + && ("Console is not available".equals(ucex.getMessage()))) { + error = new PSQLException(GT + .tr("Could not read password for SSL key file, console is not available."), PSQLState.CONNECTION_FAILURE, ucex); + } else { + error = + new PSQLException( + GT.tr("Could not read password for SSL key file by callbackhandler {0}.", + cbh.getClass().getName()), + PSQLState.CONNECTION_FAILURE, ucex); + } + } + keyStore.load(FileUtils.newBufferedInputStream(keyfile), pwdcb.getPassword()); + keystoreLoaded = true; } - - keyStore.load(new FileInputStream(new File(keyfile)), pwdcb.getPassword()); - keystoreLoaded = true; } } diff --git a/src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java b/src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java index 3f1805e..bd47d69 100644 --- a/src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java +++ b/src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java @@ -6,10 +6,10 @@ package org.postgresql.ssl; import org.postgresql.util.GT; +import org.postgresql.util.internal.FileUtils; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -26,10 +26,10 @@ import javax.net.ssl.X509TrustManager; /** - *

Provides a SSLSocketFactory that authenticates the remote server against an explicit pre-shared + * Provides a SSLSocketFactory that authenticates the remote server against an explicit pre-shared * SSL certificate. This is more secure than using the NonValidatingFactory as it prevents "man in * the middle" attacks. It is also more secure than relying on a central CA signing your server's - * certificate as it pins the server's certificate.

+ * certificate as it pins the server's certificate. * *

This class requires a single String parameter specified by setting the connection property * sslfactoryarg. The value of this property is the PEM-encoded remote server's SSL @@ -88,14 +88,14 @@ public class SingleCertValidatingFactory extends WrappedFactory { private static final String SYS_PROP_PREFIX = "sys:"; public SingleCertValidatingFactory(String sslFactoryArg) throws GeneralSecurityException { - if (sslFactoryArg == null || sslFactoryArg.equals("")) { + if (sslFactoryArg == null || "".equals(sslFactoryArg)) { throw new GeneralSecurityException(GT.tr("The sslfactoryarg property may not be empty.")); } InputStream in = null; try { if (sslFactoryArg.startsWith(FILE_PREFIX)) { String path = sslFactoryArg.substring(FILE_PREFIX.length()); - in = new BufferedInputStream(new FileInputStream(path)); + in = FileUtils.newBufferedInputStream(path); } else if (sslFactoryArg.startsWith(CLASSPATH_PREFIX)) { String path = sslFactoryArg.substring(CLASSPATH_PREFIX.length()); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); @@ -187,15 +187,18 @@ public SingleCertTrustManager(InputStream in) throws IOException, GeneralSecurit } } + @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } + @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { trustManager.checkServerTrusted(chain, authType); } + @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{cert}; } diff --git a/src/main/java/org/postgresql/ssl/WrappedFactory.java b/src/main/java/org/postgresql/ssl/WrappedFactory.java index 0a983d6..66e940c 100644 --- a/src/main/java/org/postgresql/ssl/WrappedFactory.java +++ b/src/main/java/org/postgresql/ssl/WrappedFactory.java @@ -19,36 +19,43 @@ public abstract class WrappedFactory extends SSLSocketFactory { // The field is indeed not initialized in this class, however it is a part of public API, // so it is hard to fix. - @SuppressWarnings("initialization.fields.uninitialized") + @SuppressWarnings("initialization.field.uninitialized") protected SSLSocketFactory factory; + @Override public Socket createSocket(InetAddress host, int port) throws IOException { return factory.createSocket(host, port); } + @Override public Socket createSocket(String host, int port) throws IOException { return factory.createSocket(host, port); } + @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { return factory.createSocket(host, port, localHost, localPort); } + @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return factory.createSocket(address, port, localAddress, localPort); } + @Override public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { return factory.createSocket(socket, host, port, autoClose); } + @Override public String[] getDefaultCipherSuites() { return factory.getDefaultCipherSuites(); } + @Override public String[] getSupportedCipherSuites() { return factory.getSupportedCipherSuites(); } diff --git a/src/main/java/org/postgresql/ssl/jdbc4/LibPQFactory.java b/src/main/java/org/postgresql/ssl/jdbc4/LibPQFactory.java index e73588a..40e5139 100644 --- a/src/main/java/org/postgresql/ssl/jdbc4/LibPQFactory.java +++ b/src/main/java/org/postgresql/ssl/jdbc4/LibPQFactory.java @@ -75,6 +75,7 @@ public static boolean verifyHostName(String hostname, String pattern) { * @deprecated use PgjdbcHostnameVerifier */ @Deprecated + @Override public boolean verify(String hostname, SSLSession session) { if (!sslMode.verifyPeerName()) { return true; diff --git a/src/main/java/org/postgresql/sspi/ISSPIClient.java b/src/main/java/org/postgresql/sspi/ISSPIClient.java index fcb14e4..02798be 100644 --- a/src/main/java/org/postgresql/sspi/ISSPIClient.java +++ b/src/main/java/org/postgresql/sspi/ISSPIClient.java @@ -10,8 +10,8 @@ import java.sql.SQLException; /** - *

Use Waffle-JNI to support SSPI authentication when PgJDBC is running on a Windows - * client and talking to a Windows server.

+ * Use Waffle-JNI to support SSPI authentication when PgJDBC is running on a Windows + * client and talking to a Windows server. * *

SSPI is not supported on a non-Windows client.

*/ diff --git a/src/main/java/org/postgresql/translation/bg.po b/src/main/java/org/postgresql/translation/bg.po index 69ddd59..b64ee41 100644 --- a/src/main/java/org/postgresql/translation/bg.po +++ b/src/main/java/org/postgresql/translation/bg.po @@ -888,7 +888,7 @@ msgstr "Невъзможно преобразуване на данни в же #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "Размера за fetch size трябва да бъде по-голям или равен на 0." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/cs.po b/src/main/java/org/postgresql/translation/cs.po index e5af30d..59f5e47 100644 --- a/src/main/java/org/postgresql/translation/cs.po +++ b/src/main/java/org/postgresql/translation/cs.po @@ -846,7 +846,7 @@ msgstr "Nemohu p�elo�it data do po�adovan�ho k�dov�n�." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "Nabran� velikost mus� b�t nez�porn�." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/de.po b/src/main/java/org/postgresql/translation/de.po index 6492344..21a2a51 100644 --- a/src/main/java/org/postgresql/translation/de.po +++ b/src/main/java/org/postgresql/translation/de.po @@ -926,7 +926,7 @@ msgstr "Die Daten konnten nicht in die gew�nschte Kodierung gewandelt werden." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "Die Fetch-Gr��e muss ein Wert gr��er oder gleich Null sein." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/es.po b/src/main/java/org/postgresql/translation/es.po index c8c6ef2..4715a20 100644 --- a/src/main/java/org/postgresql/translation/es.po +++ b/src/main/java/org/postgresql/translation/es.po @@ -848,7 +848,7 @@ msgstr "" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/fr.po b/src/main/java/org/postgresql/translation/fr.po index e015746..0b3e21c 100644 --- a/src/main/java/org/postgresql/translation/fr.po +++ b/src/main/java/org/postgresql/translation/fr.po @@ -910,7 +910,7 @@ msgstr "Impossible de traduire les donn�es dans l''encodage d�sir�." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "Fetch size doit �tre une valeur sup�rieur ou �gal � 0." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/it.po b/src/main/java/org/postgresql/translation/it.po index d226e23..84e8365 100644 --- a/src/main/java/org/postgresql/translation/it.po +++ b/src/main/java/org/postgresql/translation/it.po @@ -916,7 +916,7 @@ msgstr "Impossibile tradurre i dati nella codifica richiesta." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "La dimensione dell''area di �fetch� deve essere maggiore o eguale a 0." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/ja.po b/src/main/java/org/postgresql/translation/ja.po index ae5b7f6..dad60f5 100644 --- a/src/main/java/org/postgresql/translation/ja.po +++ b/src/main/java/org/postgresql/translation/ja.po @@ -887,7 +887,7 @@ msgstr "データを指定されたエンコーディングに変換すること #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "フェッチサイズは、0または、より大きな値でなくてはなりません。" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/ko.po b/src/main/java/org/postgresql/translation/ko.po new file mode 100644 index 0000000..48e8fe7 --- /dev/null +++ b/src/main/java/org/postgresql/translation/ko.po @@ -0,0 +1,2017 @@ +# ko.po +# JDBC Translation into Korean +# Distributed under the same licensing terms as the JDBC driver itself. +# Sheeraz Majeed, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: head-ko\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-05-29 11:10+0500\n" +"Last-Translator: Sheeraz Majeed\n" +"Language-Team: PostgreSQL Translators\n" +"Language: ko\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/main/java/org/postgresql/Driver.java:259 +msgid "Error loading default settings from driverconfig.properties" +msgstr "driverconfig.properties에서 기본 설정을 로드하는 중 오류 발생" + +#: src/main/java/org/postgresql/Driver.java:271 +msgid "Properties for the driver contains a non-string value for the key " +msgstr "드라이버의 속성에 문자열이 아닌 값이 포함되어 있습니다. 키: " + +#: src/main/java/org/postgresql/Driver.java:281 +#, java-format +msgid "Unable to parse URL {0}" +msgstr "URL {0} 을(를) 파싱할 수 없습니다" + +#: src/main/java/org/postgresql/Driver.java:316 +msgid "" +"Your security policy has prevented the connection from being attempted. You " +"probably need to grant the connect java.net.SocketPermission to the database " +"server host and port that you wish to connect to." +msgstr "" +"보안 정책으로 인해 연결을 시도할 수 없습니다. 데이터베이스 서버 호스트와 연결" +"하고자 하는 포트에 java.net.SocketPermission 연결 권한을 부여해야 할 것입니" +"다." + +#: src/main/java/org/postgresql/Driver.java:322 +#: src/main/java/org/postgresql/Driver.java:402 +msgid "" +"Something unusual has occurred to cause the driver to fail. Please report " +"this exception." +msgstr "" +"드라이버가 실패하는 비정상적인 현상이 발생했습니다. 이 예외를 보고해 주세요." + +#: src/main/java/org/postgresql/Driver.java:410 +msgid "Connection attempt timed out." +msgstr "연결 시도가 시간 초과되었습니다." + +#: src/main/java/org/postgresql/Driver.java:423 +msgid "Interrupted while attempting to connect." +msgstr "연결 시도 중 중단되었습니다." + +#: src/main/java/org/postgresql/Driver.java:742 +#, java-format +msgid "Method {0} is not yet implemented." +msgstr "메서드 {0} 이(가) 아직 구현되지 않았습니다." + +#: src/main/java/org/postgresql/PGConnection.java:279 +msgid "Expected a row when reading password_encryption but none was found" +msgstr "" +"password_encryption을 읽을 때 행이 예상되었으나 아무 것도 발견되지 않았습니" +"다." + +#: src/main/java/org/postgresql/PGConnection.java:284 +msgid "SHOW password_encryption returned null value" +msgstr "SHOW password_encryption이 null 값을 반환했습니다." + +#: src/main/java/org/postgresql/PGProperty.java:947 +#: src/main/java/org/postgresql/PGProperty.java:967 +#, java-format +msgid "{0} parameter value must be an integer but was: {1}" +msgstr "{0} 매개 변수 값은 정수여야 하지만 실제로는: {1} 입니다." + +#: src/main/java/org/postgresql/copy/CopyManager.java:50 +#, java-format +msgid "Requested CopyIn but got {0}" +msgstr "요청된 CopyIn 대신 {0} 을(를) 받았습니다." + +#: src/main/java/org/postgresql/copy/CopyManager.java:61 +#, java-format +msgid "Requested CopyOut but got {0}" +msgstr "요청된 CopyOut 대신 {0} 을(를) 받았습니다." + +#: src/main/java/org/postgresql/copy/CopyManager.java:72 +#, java-format +msgid "Requested CopyDual but got {0}" +msgstr "요청된 CopyDual 대신 {0} 을(를) 받았습니다." + +#: src/main/java/org/postgresql/copy/PGCopyInputStream.java:60 +#, java-format +msgid "Copying from database failed: {0}" +msgstr "데이터베이스에서 복사 실패: {0}" + +#: src/main/java/org/postgresql/copy/PGCopyInputStream.java:74 +#: src/main/java/org/postgresql/copy/PGCopyOutputStream.java:104 +msgid "This copy stream is closed." +msgstr "이 복사 스트림은 닫혀 있습니다." + +#: src/main/java/org/postgresql/copy/PGCopyInputStream.java:125 +msgid "Read from copy failed." +msgstr "복사에서 읽기 실패." + +#: src/main/java/org/postgresql/copy/PGCopyOutputStream.java:81 +#, java-format +msgid "Cannot write to copy a byte of value {0}" +msgstr "값 {0} 의 바이트를 복사에 쓸 수 없습니다." + +#: src/main/java/org/postgresql/core/CommandCompleteParser.java:74 +#, java-format +msgid "Unable to parse the count in command completion tag: {0}." +msgstr "명령 완료 태그의 카운트를 파싱할 수 없습니다: {0}." + +#: src/main/java/org/postgresql/core/ConnectionFactory.java:62 +#, java-format +msgid "A connection could not be made using the requested protocol {0}." +msgstr "요청된 프로토콜 {0} 을(를) 사용하여 연결할 수 없습니다." + +#: src/main/java/org/postgresql/core/Oid.java:142 +#, java-format +msgid "oid type {0} not known and not a number" +msgstr "oid 유형 {0} 이(가) 알려지지 않았고 숫자가 아닙니다." + +#: src/main/java/org/postgresql/core/PGStream.java:697 +#: src/main/java/org/postgresql/util/PGbytea.java:199 +#, java-format +msgid "Premature end of input stream, expected {0} bytes, but only read {1}." +msgstr "입력 스트림의 조기 종료, 예상 {0} 바이트 중 {1} 바이트만 읽었습니다." + +#: src/main/java/org/postgresql/core/PGStream.java:738 +#, java-format +msgid "Expected an EOF from server, got: {0}" +msgstr "서버로부터 EOF를 기대했지만, 대신: {0} 을(를) 받았습니다." + +#: src/main/java/org/postgresql/core/PGStream.java:838 +#, java-format +msgid "" +"Result set exceeded maxResultBuffer limit. Received: {0}; Current limit: {1}" +msgstr "" +"결과 집합이 maxResultBuffer 한도를 초과했습니다. 받은 값: {0}; 현재 한도: {1}" + +#: src/main/java/org/postgresql/core/Parser.java:1201 +#, java-format +msgid "Malformed function or procedure escape syntax at offset {0}." +msgstr "오프셋 {0} 에서 잘못된 함수 또는 절차 이스케이프 구문입니다." + +#: src/main/java/org/postgresql/core/SetupQueryRunner.java:68 +msgid "An unexpected result was returned by a query." +msgstr "쿼리가 예상치 못한 결과를 반환했습니다." + +#: src/main/java/org/postgresql/core/SocketFactoryFactory.java:43 +#, java-format +msgid "The SocketFactory class provided {0} could not be instantiated." +msgstr "제공된 SocketFactory 클래스 {0} 을(를) 인스턴스화할 수 없습니다." + +#: src/main/java/org/postgresql/core/SocketFactoryFactory.java:68 +#, java-format +msgid "The SSLSocketFactory class provided {0} could not be instantiated." +msgstr "제공된 SSLSocketFactory 클래스 {0} 을(를) 인스턴스화할 수 없습니다." + +#: src/main/java/org/postgresql/core/Utils.java:72 +#: src/main/java/org/postgresql/core/Utils.java:89 +msgid "Zero bytes may not occur in string parameters." +msgstr "문자열 매개 변수에 0 바이트가 발생할 수 없습니다." + +#: src/main/java/org/postgresql/core/Utils.java:99 +#: src/main/java/org/postgresql/core/Utils.java:149 +msgid "No IOException expected from StringBuffer or StringBuilder" +msgstr "StringBuffer 또는 StringBuilder에서 IOException이 발생하지 않았습니다." + +#: src/main/java/org/postgresql/core/Utils.java:138 +msgid "Zero bytes may not occur in identifiers." +msgstr "식별자에 0 바이트가 발생할 수 없습니다." + +#: src/main/java/org/postgresql/core/v3/AuthenticationPluginManager.java:73 +#, java-format +msgid "Unable to load Authentication Plugin {0}" +msgstr "인증 플러그인 {0} 을(를) 로드할 수 없습니다." + +#: src/main/java/org/postgresql/core/v3/AuthenticationPluginManager.java:111 +#, java-format +msgid "" +"The server requested password-based authentication, but no password was " +"provided by plugin {0}" +msgstr "" +"서버가 비밀번호 기반 인증을 요청했으나, 플러그인 {0} 에서 비밀번호를 제공하" +"지 않았습니다." + +#: src/main/java/org/postgresql/core/v3/CompositeParameterList.java:38 +#: src/main/java/org/postgresql/core/v3/SimpleParameterList.java:60 +#: src/main/java/org/postgresql/core/v3/SimpleParameterList.java:71 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:225 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3258 +#: src/main/java/org/postgresql/jdbc/PgResultSetMetaData.java:420 +#, java-format +msgid "The column index is out of range: {0}, number of columns: {1}." +msgstr "열 인덱스가 범위를 벗어났습니다: {0}, 열 수: {1}." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:130 +msgid "User cannot be null" +msgstr "사용자는 null일 수 없습니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:133 +msgid "Database cannot be null" +msgstr "데이터베이스는 null일 수 없습니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:227 +#, java-format +msgid "Invalid targetServerType value: {0}" +msgstr "잘못된 targetServerType 값: {0}" + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:347 +#, java-format +msgid "" +"Connection to {0} refused. Check that the hostname and port are correct and " +"that the postmaster is accepting TCP/IP connections." +msgstr "" +"{0} 에 대한 연결이 거부되었습니다. 호스트 이름과 포트가 올바른지, 그리고 " +"postmaster가 TCP/IP 연결을 수락하고 있는지 확인하십시오." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:358 +#: src/main/java/org/postgresql/core/v3/replication/V3ReplicationProtocol.java:139 +msgid "The connection attempt failed." +msgstr "연결 시도가 실패했습니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:373 +#, java-format +msgid "Could not find a server with specified targetServerType: {0}" +msgstr "지정된 targetServerType을 가진 서버를 찾을 수 없습니다: {0}" + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:517 +msgid "The server does not support GSS Encoding." +msgstr "서버가 GSS 인코딩을 지원하지 않습니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:530 +msgid "The server does not support GSS Encryption." +msgstr "서버가 GSS 암호화를 지원하지 않습니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:558 +msgid "An error occurred while setting up the GSS Encoded connection." +msgstr "GSS 인코딩된 연결을 설정하는 동안 오류가 발생했습니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:603 +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:615 +msgid "The server does not support SSL." +msgstr "서버가 SSL을 지원하지 않습니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:629 +msgid "An error occurred while setting up the SSL connection." +msgstr "SSL 연결을 설정하는 동안 오류가 발생했습니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:844 +msgid "" +"The server requested SCRAM-based authentication, but no password was " +"provided." +msgstr "서버가 SCRAM 기반 인증을 요청했지만, 비밀번호가 제공되지 않았습니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:850 +msgid "" +"The server requested SCRAM-based authentication, but the password is an " +"empty string." +msgstr "서버가 SCRAM 기반 인증을 요청했지만, 비밀번호가 빈 문자열입니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:862 +msgid "" +"SCRAM authentication is not supported by this driver. You need JDK >= 8 and " +"pgjdbc >= 42.2.0 (not \".jre\" versions)" +msgstr "" +"이 드라이버는 SCRAM 인증을 지원하지 않습니다. JDK >= 8 및 pgjdbc >= " +"42.2.0( \".jre\" 버전이 아님)이 필요합니다." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:883 +#, java-format +msgid "" +"The authentication type {0} is not supported. Check that you have configured " +"the pg_hba.conf file to include the client''s IP address or subnet, and that " +"it is using an authentication scheme supported by the driver." +msgstr "" +"인증 유형 {0} 이(가) 지원되지 않습니다. pg_hba.conf 파일에 클라이언트의 IP 주" +"소 또는 서브넷이 포함되어 있고, 드라이버가 지원하는 인증 방식을 사용하고 있는" +"지 확인하십시오." + +#: src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java:890 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:2821 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:2854 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:2858 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:2934 +#: src/main/java/org/postgresql/gss/GssAction.java:156 +msgid "Protocol error. Session setup failed." +msgstr "프로토콜 오류. 세션 설정 실패." + +#: src/main/java/org/postgresql/core/v3/CopyInImpl.java:58 +msgid "CopyIn copy direction can't receive data" +msgstr "CopyIn 복사 방향은 데이터를 수신할 수 없습니다." + +#: src/main/java/org/postgresql/core/v3/CopyOperationImpl.java:65 +msgid "CommandComplete expected COPY but got: " +msgstr "CommandComplete는 COPY를 예상했으나 대신 다음을 받았습니다: " + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:205 +msgid "Tried to obtain lock while already holding it" +msgstr "이미 잠금을 보유한 상태에서 잠금을 얻으려고 시도했습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:221 +msgid "Tried to break lock on database connection" +msgstr "데이터베이스 연결에서 잠금을 해제하려고 시도했습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:239 +msgid "Interrupted while waiting to obtain lock on database connection" +msgstr "데이터베이스 연결에서 잠금을 얻기 위해 대기하는 동안 중단되었습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:392 +msgid "Unable to bind parameter values for statement." +msgstr "문에 대한 매개 변수 값을 바인딩할 수 없습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:398 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:585 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:671 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:721 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:852 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:2615 +#: src/main/java/org/postgresql/util/StreamWrapper.java:85 +msgid "An I/O error occurred while sending to the backend." +msgstr "백엔드로 전송하는 동안 I/O 오류가 발생했습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:451 +msgid "Error releasing savepoint" +msgstr "세이브포인트 해제 오류" + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:642 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:690 +#, java-format +msgid "Expected command status BEGIN, got {0}." +msgstr "예상한 명령 상태 BEGIN 대신 {0} 을(를) 받았습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:695 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:2200 +#, java-format +msgid "Unexpected command status: {0}." +msgstr "예상치 못한 명령 상태: {0}." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:810 +msgid "An error occurred while trying to get the socket timeout." +msgstr "소켓 시간 초과를 가져오는 동안 오류가 발생했습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:845 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:935 +#, java-format +msgid "Unknown Response Type {0}." +msgstr "알 수 없는 응답 유형 {0}." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:869 +msgid "An error occurred while trying to reset the socket timeout." +msgstr "소켓 시간 초과를 재설정하는 동안 오류가 발생했습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:982 +msgid "Database connection failed when starting copy" +msgstr "복사 시작 시 데이터베이스 연결 실패" + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1020 +msgid "Tried to cancel an inactive copy operation" +msgstr "비활성 복사 작업을 취소하려고 시도했습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1059 +msgid "Database connection failed when canceling copy operation" +msgstr "복사 작업을 취소하는 동안 데이터베이스 연결 실패" + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1075 +msgid "Missing expected error response to copy cancel request" +msgstr "복사 취소 요청에 대한 예상 오류 응답이 누락되었습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1079 +#, java-format +msgid "Got {0} error responses to single copy cancel request" +msgstr "단일 복사 취소 요청에 대해 {0} 오류 응답을 받았습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1095 +msgid "Tried to end inactive copy" +msgstr "비활성 복사를 끝내려고 시도했습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1110 +msgid "Database connection failed when ending copy" +msgstr "복사를 종료하는 동안 데이터베이스 연결 실패" + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1130 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1159 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1180 +msgid "Tried to write to an inactive copy operation" +msgstr "비활성 복사 작업에 쓰기를 시도했습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1141 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1171 +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1187 +msgid "Database connection failed when writing to copy" +msgstr "복사에 쓰는 동안 데이터베이스 연결 실패" + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1204 +msgid "Tried to read from inactive copy" +msgstr "비활성 복사에서 읽기를 시도했습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1211 +msgid "Database connection failed when reading from copy" +msgstr "복사에서 읽는 동안 데이터베이스 연결 실패" + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1236 +msgid "PGStream is closed" +msgstr "PGStream이 닫혔습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1297 +#, java-format +msgid "Received CommandComplete ''{0}'' without an active copy operation" +msgstr "활성 복사 작업 없이 CommandComplete ''{0}'' 을(를) 받았습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1322 +#, java-format +msgid "Got CopyInResponse from server during an active {0}" +msgstr "활성 {0} 중 서버로부터 CopyInResponse를 받았습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1336 +#, java-format +msgid "Got CopyOutResponse from server during an active {0}" +msgstr "활성 {0} 중 서버로부터 CopyOutResponse를 받았습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1350 +#, java-format +msgid "Got CopyBothResponse from server during an active {0}" +msgstr "활성 {0} 중 서버로부터 CopyBothResponse를 받았습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1369 +msgid "Got CopyData without an active copy operation" +msgstr "활성 복사 작업 없이 CopyData를 받았습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1373 +#, java-format +msgid "Unexpected copydata from server for {0}" +msgstr "{0} 에 대한 서버의 예상치 못한 copydata" + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1433 +#, java-format +msgid "Unexpected packet type during copy: {0}" +msgstr "복사 중 예상치 못한 패킷 유형: {0}" + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:1736 +#, java-format +msgid "" +"Bind message length {0} too long. This can be caused by very large or " +"incorrect length specifications on InputStream parameters." +msgstr "" +"바인드 메시지 길이 {0} 이 너무 깁니다. 이는 매우 크거나 잘못된 길이 지정이 " +"InputStream 매개 변수에 포함되어 있을 때 발생할 수 있습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:2383 +msgid "Ran out of memory retrieving query results." +msgstr "쿼리 결과를 검색하는 동안 메모리가 부족했습니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:2549 +msgid "COPY commands are only supported using the CopyManager API." +msgstr "COPY 명령은 CopyManager API를 사용하여서만 지원됩니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:2891 +#, java-format +msgid "" +"The server''s client_encoding parameter was changed to {0}. The JDBC driver " +"requires client_encoding to be UTF8 for correct operation." +msgstr "" +"서버의 client_encoding 매개 변수가 {0} 으로 변경되었습니다. JDBC 드라이버는 " +"올바른 작동을 위해 client_encoding이 UTF8이어야 합니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:2901 +#, java-format +msgid "" +"The server''s DateStyle parameter was changed to {0}. The JDBC driver " +"requires DateStyle to begin with ISO for correct operation." +msgstr "" +"서버의 DateStyle 매개 변수가 {0} 으로 변경되었습니다. JDBC 드라이버는 올바른 " +"작동을 위해 DateStyle이 ISO로 시작해야 합니다." + +#: src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java:2914 +#, java-format +msgid "" +"The server''s standard_conforming_strings parameter was reported as {0}. The " +"JDBC driver expected on or off." +msgstr "" +"서버의 standard_conforming_strings 매개 변수가 {0} 으로 보고되었습니다. JDBC " +"드라이버는 켜짐 또는 꺼짐을 예상했습니다." + +#: src/main/java/org/postgresql/core/v3/SimpleParameterList.java:262 +#, java-format +msgid "Unable to convert bytea parameter at position {0} to literal" +msgstr "위치 {0} 의 bytea 매개 변수를 리터럴로 변환할 수 없습니다." + +#: src/main/java/org/postgresql/core/v3/SimpleParameterList.java:402 +#, java-format +msgid "No value specified for parameter {0}." +msgstr "매개 변수 {0} 에 대해 지정된 값이 없습니다." + +#: src/main/java/org/postgresql/core/v3/SimpleParameterList.java:607 +#, java-format +msgid "Added parameters index out of range: {0}, number of columns: {1}." +msgstr "추가된 매개 변수 인덱스가 범위를 벗어났습니다: {0}, 열 수: {1}." + +#: src/main/java/org/postgresql/core/v3/replication/V3PGReplicationStream.java:152 +#, java-format +msgid "Unexpected packet type during replication: {0}" +msgstr "복제 중 예상치 못한 패킷 유형: {0}" + +#: src/main/java/org/postgresql/core/v3/replication/V3PGReplicationStream.java:283 +msgid "This replication stream has been closed." +msgstr "이 복제 스트림은 닫혔습니다." + +#: src/main/java/org/postgresql/ds/PGPooledConnection.java:129 +msgid "This PooledConnection has already been closed." +msgstr "이 PooledConnection은 이미 닫혔습니다." + +#: src/main/java/org/postgresql/ds/PGPooledConnection.java:331 +msgid "" +"Connection has been closed automatically because a new connection was opened " +"for the same PooledConnection or the PooledConnection has been closed." +msgstr "" +"같은 PooledConnection에 대해 새 연결이 열리거나 PooledConnection이 닫혔기 때" +"문에 연결이 자동으로 닫혔습니다." + +#: src/main/java/org/postgresql/ds/PGPooledConnection.java:332 +msgid "Connection has been closed." +msgstr "연결이 닫혔습니다." + +#: src/main/java/org/postgresql/ds/PGPooledConnection.java:439 +msgid "Statement has been closed." +msgstr "Statement가 닫혔습니다." + +#: src/main/java/org/postgresql/ds/PGPoolingDataSource.java:286 +msgid "Failed to setup DataSource." +msgstr "DataSource 설정 실패." + +#: src/main/java/org/postgresql/ds/PGPoolingDataSource.java:390 +msgid "DataSource has been closed." +msgstr "DataSource가 닫혔습니다." + +#: src/main/java/org/postgresql/ds/common/BaseDataSource.java:1442 +#: src/main/java/org/postgresql/ds/common/BaseDataSource.java:1452 +#, java-format +msgid "Unsupported property name: {0}" +msgstr "지원되지 않는 속성 이름: {0}" + +#: src/main/java/org/postgresql/fastpath/Fastpath.java:89 +#, java-format +msgid "Fastpath call {0} - No result was returned and we expected a numeric." +msgstr "Fastpath 호출 {0} - 결과가 반환되지 않았고 숫자를 예상했습니다." + +#: src/main/java/org/postgresql/fastpath/Fastpath.java:167 +#, java-format +msgid "Fastpath call {0} - No result was returned and we expected an integer." +msgstr "Fastpath 호출 {0} - 결과가 반환되지 않았고 정수를 예상했습니다." + +#: src/main/java/org/postgresql/fastpath/Fastpath.java:175 +#, java-format +msgid "" +"Fastpath call {0} - No result was returned or wrong size while expecting an " +"integer." +msgstr "" +"Fastpath 호출 {0} - 결과가 반환되지 않았거나 정수를 예상하는 동안 잘못된 크기" +"입니다." + +#: src/main/java/org/postgresql/fastpath/Fastpath.java:192 +#, java-format +msgid "Fastpath call {0} - No result was returned and we expected a long." +msgstr "Fastpath 호출 {0} - 결과가 반환되지 않았고 long을 예상했습니다." + +#: src/main/java/org/postgresql/fastpath/Fastpath.java:200 +#, java-format +msgid "" +"Fastpath call {0} - No result was returned or wrong size while expecting a " +"long." +msgstr "" +"Fastpath 호출 {0} - 결과가 반환되지 않았거나 long을 예상하는 동안 잘못된 크기" +"입니다." + +#: src/main/java/org/postgresql/fastpath/Fastpath.java:303 +#, java-format +msgid "The fastpath function {0} is unknown." +msgstr "Fastpath 함수 {0} 은(는) 알려지지 않았습니다." + +#: src/main/java/org/postgresql/geometric/PGbox.java:83 +#: src/main/java/org/postgresql/geometric/PGcircle.java:81 +#: src/main/java/org/postgresql/geometric/PGcircle.java:89 +#: src/main/java/org/postgresql/geometric/PGline.java:142 +#: src/main/java/org/postgresql/geometric/PGline.java:151 +#: src/main/java/org/postgresql/geometric/PGlseg.java:76 +#: src/main/java/org/postgresql/geometric/PGpoint.java:88 +#, java-format +msgid "Conversion to type {0} failed: {1}." +msgstr "유형 {0} 으로 변환 실패: {1}." + +#: src/main/java/org/postgresql/geometric/PGpath.java:78 +#, java-format +msgid "Cannot tell if path is open or closed: {0}." +msgstr "경로가 열려 있는지 닫혀 있는지 알 수 없습니다: {0}." + +#: src/main/java/org/postgresql/gss/GssAction.java:167 +#: src/main/java/org/postgresql/gss/GssEncAction.java:147 +#: src/main/java/org/postgresql/gss/MakeGSS.java:173 +#: src/main/java/org/postgresql/gss/MakeGSS.java:181 +msgid "GSS Authentication failed" +msgstr "GSS 인증 실패" + +#: src/main/java/org/postgresql/gss/MakeGSS.java:169 +msgid "" +"Neither Subject.doAs (Java before 18) nor Subject.callAs (Java 18+) method " +"found" +msgstr "" +"Subject.doAs (Java 18 이전) 또는 Subject.callAs (Java 18+) 메서드가 발견되지 " +"않았습니다." + +#: src/main/java/org/postgresql/jdbc/AbstractBlobClob.java:84 +msgid "" +"Truncation of large objects is only implemented in 8.3 and later servers." +msgstr "대형 객체의 잘림은 8.3 이후의 서버에서만 구현됩니다." + +#: src/main/java/org/postgresql/jdbc/AbstractBlobClob.java:89 +msgid "Cannot truncate LOB to a negative length." +msgstr "LOB를 음수 길이로 잘릴 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/AbstractBlobClob.java:96 +#: src/main/java/org/postgresql/jdbc/AbstractBlobClob.java:249 +#, java-format +msgid "PostgreSQL LOBs can only index to: {0}" +msgstr "PostgreSQL LOB는 다음으로만 인덱싱할 수 있습니다: {0}" + +#: src/main/java/org/postgresql/jdbc/AbstractBlobClob.java:245 +msgid "LOB positioning offsets start at 1." +msgstr "LOB 위치 오프셋은 1부터 시작합니다." + +#: src/main/java/org/postgresql/jdbc/AbstractBlobClob.java:261 +msgid "free() was called on this LOB previously" +msgstr "이 LOB에서 이전에 free()가 호출되었습니다." + +#: src/main/java/org/postgresql/jdbc/ArrayDecoding.java:284 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:2440 +#: src/main/java/org/postgresql/util/HStoreConverter.java:46 +#: src/main/java/org/postgresql/util/HStoreConverter.java:82 +msgid "" +"Invalid character data was found. This is most likely caused by stored data " +"containing characters that are invalid for the character set the database " +"was created in. The most common example of this is storing 8bit data in a " +"SQL_ASCII database." +msgstr "" +"잘못된 문자 데이터가 발견되었습니다. 이는 데이터베이스가 생성된 문자 세트에 " +"대해 잘못된 문자를 포함하는 저장된 데이터로 인해 발생할 가능성이 큽니다. 가" +"장 일반적인 예는 SQL_ASCII 데이터베이스에 8비트 데이터를 저장하는 것입니다." + +#: src/main/java/org/postgresql/jdbc/ArrayEncoding.java:867 +#: src/main/java/org/postgresql/jdbc/ArrayEncoding.java:904 +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1181 +msgid "Unable to translate data into the desired encoding." +msgstr "데이터를 원하는 인코딩으로 변환할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/ArrayEncoding.java:1124 +#: src/main/java/org/postgresql/jdbc/ArrayEncoding.java:1135 +#: src/main/java/org/postgresql/jdbc/ArrayEncoding.java:1161 +#, java-format +msgid "Invalid elements {0}" +msgstr "잘못된 요소 {0}" + +#: src/main/java/org/postgresql/jdbc/BatchResultHandler.java:102 +msgid "Too many update results were returned." +msgstr "너무 많은 업데이트 결과가 반환되었습니다." + +#: src/main/java/org/postgresql/jdbc/BatchResultHandler.java:163 +#, java-format +msgid "" +"Batch entry {0} {1} was aborted: {2} Call getNextException to see other " +"errors in the batch." +msgstr "" +"배치 항목 {0} {1} 이(가) 중단되었습니다: {2} 배치의 다른 오류를 보려면 " +"getNextException을 호출하십시오." + +#: src/main/java/org/postgresql/jdbc/BooleanTypeUtil.java:99 +#, java-format +msgid "Cannot cast to boolean: \"{0}\"" +msgstr "boolean으로 캐스팅할 수 없습니다: \"{0}\"" + +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:240 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:170 +#, java-format +msgid "{0} function takes four and only four argument." +msgstr "{0} 함수는 네 개의 인수만 받습니다." + +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:270 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:337 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:741 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:199 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:266 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:667 +#, java-format +msgid "{0} function takes two and only two arguments." +msgstr "{0} 함수는 두 개의 인수만 받습니다." + +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:288 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:441 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:467 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:526 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:729 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:214 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:358 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:384 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:443 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:656 +#, java-format +msgid "{0} function takes one and only one argument." +msgstr "{0} 함수는 한 개의 인수만 받습니다." + +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:312 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:386 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:241 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:310 +#, java-format +msgid "{0} function takes two or three arguments." +msgstr "{0} 함수는 두 개 또는 세 개의 인수를 받습니다." + +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:411 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:426 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:694 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:720 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:647 +#, java-format +msgid "{0} function doesn''t take any argument." +msgstr "{0} 함수는 인수를 받지 않습니다." + +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:587 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:640 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:503 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:573 +#, java-format +msgid "{0} function takes three and only three arguments." +msgstr "{0} 함수는 세 개의 인수만 받습니다." + +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:600 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:621 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:624 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:657 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:670 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions.java:673 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:513 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:530 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:587 +#: src/main/java/org/postgresql/jdbc/EscapedFunctions2.java:599 +#, java-format +msgid "Interval {0} not yet implemented" +msgstr "구간 {0} 은(는) 아직 구현되지 않았습니다." + +#: src/main/java/org/postgresql/jdbc/GSSEncMode.java:61 +#, java-format +msgid "Invalid gssEncMode value: {0}" +msgstr "잘못된 gssEncMode 값: {0}" + +#: src/main/java/org/postgresql/jdbc/PSQLSavepoint.java:40 +#: src/main/java/org/postgresql/jdbc/PSQLSavepoint.java:55 +#: src/main/java/org/postgresql/jdbc/PSQLSavepoint.java:73 +msgid "Cannot reference a savepoint after it has been released." +msgstr "해제된 후에는 세이브포인트를 참조할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PSQLSavepoint.java:45 +msgid "Cannot retrieve the id of a named savepoint." +msgstr "이름이 지정된 세이브포인트의 ID를 검색할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PSQLSavepoint.java:60 +msgid "Cannot retrieve the name of an unnamed savepoint." +msgstr "이름이 없는 세이브포인트의 이름을 검색할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgArray.java:153 +#: src/main/java/org/postgresql/jdbc/PgArray.java:369 +#, java-format +msgid "The array index is out of range: {0}" +msgstr "배열 인덱스가 범위를 벗어났습니다: {0}" + +#: src/main/java/org/postgresql/jdbc/PgArray.java:174 +#: src/main/java/org/postgresql/jdbc/PgArray.java:386 +#, java-format +msgid "The array index is out of range: {0}, number of elements: {1}." +msgstr "배열 인덱스가 범위를 벗어났습니다: {0}, 요소 수: {1}." + +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:99 +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:105 +msgid "A CallableStatement was executed with nothing returned." +msgstr "CallableStatement가 실행되었으나 반환된 값이 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:116 +msgid "A CallableStatement was executed with an invalid number of parameters" +msgstr "CallableStatement가 잘못된 수의 매개 변수로 실행되었습니다." + +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:154 +#, java-format +msgid "" +"A CallableStatement function was executed and the out parameter {0} was of " +"type {1} however type {2} was registered." +msgstr "" +"CallableStatement 함수가 실행되었고 out 매개 변수 {0} 의 유형이 {1} 이었지만 " +"유형 {2} 가 등록되었습니다." + +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:217 +msgid "" +"This statement does not declare an OUT parameter. Use '{' ?= call ... '}' " +"to declare one." +msgstr "" +"이 문은 OUT 매개 변수를 선언하지 않습니다. OUT 매개 변수를 선언하려면 '{' ?= " +"call ... '}'을 사용하십시오." + +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:240 +msgid "wasNull cannot be call before fetching a result." +msgstr "wasNull은 결과를 가져오기 전에 호출할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:377 +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:399 +#, java-format +msgid "" +"Parameter of type {0} was registered, but call to get{1} (sqltype={2}) was " +"made." +msgstr "" +"유형 {0} 의 매개 변수가 등록되었지만 get{1} (sqltype={2}) 호출이 이루어졌습니" +"다." + +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:413 +msgid "" +"A CallableStatement was declared, but no call to registerOutParameter(1, " +") was made." +msgstr "" +"CallableStatement가 선언되었지만 registerOutParameter(1, ) 호출이 " +"이루어지지 않았습니다." + +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:418 +msgid "No function outputs were registered." +msgstr "등록된 함수 출력이 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:425 +msgid "" +"Results cannot be retrieved from a CallableStatement before it is executed." +msgstr "CallableStatement가 실행되기 전에 결과를 검색할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgCallableStatement.java:735 +#, java-format +msgid "Unsupported type conversion to {1}." +msgstr "{1} 으로의 지원되지 않는 유형 변환." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:331 +#, java-format +msgid "Unsupported value for stringtype parameter: {0}" +msgstr "stringtype 매개 변수에 대한 지원되지 않는 값: {0}" + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:589 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:138 +#: src/main/java/org/postgresql/jdbc/PgStatement.java:267 +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:303 +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:387 +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:529 +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:566 +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:653 +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:658 +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:703 +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:708 +msgid "No results were returned by the query." +msgstr "쿼리에서 반환된 결과가 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:607 +#: src/main/java/org/postgresql/jdbc/PgConnection.java:624 +#: src/main/java/org/postgresql/jdbc/PgStatement.java:302 +msgid "A result was returned when none was expected." +msgstr "예상치 못한 결과가 반환되었습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:733 +msgid "Custom type maps are not supported." +msgstr "사용자 정의 유형 맵은 지원되지 않습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:775 +#, java-format +msgid "Failed to create object for: {0}." +msgstr "{0} 에 대한 개체 생성 실패." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:842 +#, java-format +msgid "Unable to load the class {0} responsible for the datatype {1}" +msgstr "데이터 유형 {1} 을 담당하는 클래스 {0} 을(를) 로드할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:870 +msgid "Unable to close connection properly" +msgstr "연결을 올바르게 닫을 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:913 +msgid "" +"Cannot change transaction read-only property in the middle of a transaction." +msgstr "트랜잭션 중간에 트랜잭션 읽기 전용 속성을 변경할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:998 +msgid "Cannot commit when autoCommit is enabled." +msgstr "autoCommit이 활성화된 상태에서 커밋할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1009 +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1568 +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1612 +msgid "This connection has been closed." +msgstr "이 연결은 닫혔습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1019 +msgid "Cannot rollback when autoCommit is enabled." +msgstr "autoCommit이 활성화된 상태에서 롤백할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1075 +msgid "" +"Cannot change transaction isolation level in the middle of a transaction." +msgstr "트랜잭션 중간에 트랜잭션 격리 수준을 변경할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1081 +#, java-format +msgid "Transaction isolation level {0} not supported." +msgstr "트랜잭션 격리 수준 {0} 이(가) 지원되지 않습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1247 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:2242 +#: src/main/java/org/postgresql/jdbc/PgStatement.java:1013 +msgid "Fetch size must be a value greater than or equal to 0." +msgstr "가져오기 크기는 0보다 크거나 같아야 합니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1493 +#, java-format +msgid "Unable to find server array type for provided name {0}." +msgstr "제공된 이름 {0} 에 대한 서버 배열 유형을 찾을 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1519 +#, java-format +msgid "Invalid timeout ({0}<0)." +msgstr "잘못된 시간 초과 ({0}<0)." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1556 +msgid "Validating connection." +msgstr "연결을 검증 중입니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1589 +#, java-format +msgid "Failed to set ClientInfo property: {0}" +msgstr "ClientInfo 속성 설정 실패: {0}" + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1599 +msgid "ClientInfo property not supported." +msgstr "ClientInfo 속성이 지원되지 않습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1625 +msgid "One or more ClientInfo failed." +msgstr "하나 이상의 ClientInfo가 실패했습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1727 +msgid "Network timeout must be a value greater than or equal to 0." +msgstr "네트워크 시간 초과는 0보다 크거나 같아야 합니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1736 +msgid "Unable to set network timeout." +msgstr "네트워크 시간 초과를 설정할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1761 +msgid "Unable to get network timeout." +msgstr "네트워크 시간 초과를 가져올 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1776 +#, java-format +msgid "Unknown ResultSet holdability setting: {0}." +msgstr "알 수 없는 ResultSet 유지 가능성 설정: {0}." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1794 +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1815 +msgid "Cannot establish a savepoint in auto-commit mode." +msgstr "자동 커밋 모드에서는 세이브포인트를 설정할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1884 +msgid "Returning autogenerated keys is not supported." +msgstr "자동 생성된 키 반환이 지원되지 않습니다." + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1946 +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1960 +#, java-format +msgid "Could not instantiate xmlFactoryFactory: {0}" +msgstr "xmlFactoryFactory를 인스턴스화할 수 없습니다: {0}" + +#: src/main/java/org/postgresql/jdbc/PgConnection.java:1951 +#, java-format +msgid "" +"Connection property xmlFactoryFactory must implement PGXmlFactoryFactory: {0}" +msgstr "" +"연결 속성 xmlFactoryFactory는 PGXmlFactoryFactory를 구현해야 합니다: {0}" + +#: src/main/java/org/postgresql/jdbc/PgConnectionCleaningAction.java:85 +msgid "Leak detected: Connection.close() was not called" +msgstr "누출 감지됨: Connection.close()가 호출되지 않았습니다." + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:73 +msgid "" +"Unable to determine a value for MaxIndexKeys due to missing system catalog " +"data." +msgstr "" +"시스템 카탈로그 데이터가 누락되어 MaxIndexKeys 값을 결정할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:96 +msgid "Unable to find name datatype in the system catalogs." +msgstr "시스템 카탈로그에서 이름 데이터 유형을 찾을 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:356 +msgid "Unable to find keywords in the system catalogs." +msgstr "시스템 카탈로그에서 키워드를 찾을 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1080 +msgid "" +"Unable to determine a value for DefaultTransactionIsolation due to missing " +"entry in pg_catalog.pg_settings WHERE name='default_transaction_isolation'." +msgstr "" +"pg_catalog.pg_settings에 name='default_transaction_isolation' 항목이 누락되" +"어 DefaultTransactionIsolation 값을 결정할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1265 +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:3092 +msgid "oid" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1265 +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:3092 +msgid "proname" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1267 +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1737 +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:3094 +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:272 +msgid "typtype" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1270 +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:3097 +msgid "proargtypes" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1754 +msgid "adsrc" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1765 +msgid "attidentity" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1767 +msgid "attgenerated" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1900 +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1977 +msgid "rolname" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1901 +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1978 +msgid "relacl" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/PgDatabaseMetaData.java:1907 +msgid "attacl" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/PgParameterMetaData.java:96 +#, java-format +msgid "The parameter index is out of range: {0}, number of parameters: {1}." +msgstr "매개 변수 인덱스가 범위를 벗어났습니다: {0}, 매개 변수 수: {1}." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:105 +#, java-format +msgid "" +"PreparedStatement can have at most {0} parameters. Please consider using " +"arrays, or splitting the query in several ones, or using COPY. Given query " +"has {1} parameters" +msgstr "" +"PreparedStatement는 최대 {0} 개의 매개 변수를 가질 수 있습니다. 배열을 사용하" +"거나 쿼리를 여러 개로 나누거나 COPY를 사용하는 것을 고려하십시오. 주어진 쿼리" +"는 {1} 개의 매개 변수를 가집니다." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:123 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:148 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:173 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1128 +msgid "" +"Can''t use query methods that take a query string on a PreparedStatement." +msgstr "" +"PreparedStatement에서 쿼리 문자열을 사용하는 쿼리 메서드를 사용할 수 없습니" +"다." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:297 +msgid "Unknown Types value." +msgstr "알 수 없는 유형 값입니다." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:451 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1295 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1603 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1664 +#, java-format +msgid "Invalid stream length {0}." +msgstr "잘못된 스트림 길이 {0}." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:480 +#, java-format +msgid "The JVM claims not to support the {0} encoding." +msgstr "JVM이 {0} 인코딩을 지원하지 않는다고 주장합니다." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:483 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1323 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1362 +msgid "Provided InputStream failed." +msgstr "제공된 InputStream이 실패했습니다." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:525 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1196 +#, java-format +msgid "Unknown type {0}." +msgstr "알 수 없는 유형 {0}." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:547 +msgid "No hstore extension installed." +msgstr "설치된 hstore 확장이 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:686 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:708 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:718 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:731 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1059 +#, java-format +msgid "Cannot cast an instance of {0} to type {1}" +msgstr "{0} 의 인스턴스를 유형 {1}(으)로 캐스팅할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:749 +#, java-format +msgid "Unsupported Types value: {0}" +msgstr "지원되지 않는 유형 값: {0}" + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:980 +#, java-format +msgid "Cannot convert an instance of {0} to type {1}" +msgstr "{0} 의 인스턴스를 유형 {1} (으)로 변환할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1065 +#, java-format +msgid "" +"Can''t infer the SQL type to use for an instance of {0}. Use setObject() " +"with an explicit Types value to specify the type to use." +msgstr "" +"{0} 의 인스턴스에 사용할 SQL 유형을 추론할 수 없습니다. 명시적 유형 값을 사용" +"하여 setObject()를 사용하여 사용할 유형을 지정하십시오." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1232 +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1342 +msgid "Unexpected error writing large object to database." +msgstr "데이터베이스에 대형 객체를 쓰는 동안 예상치 못한 오류가 발생했습니다." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1280 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1425 +msgid "Provided Reader failed." +msgstr "제공된 Reader가 실패했습니다." + +#: src/main/java/org/postgresql/jdbc/PgPreparedStatement.java:1600 +#: src/main/java/org/postgresql/util/StreamWrapper.java:60 +msgid "Object is too large to send over the protocol." +msgstr "객체가 프로토콜을 통해 보내기에는 너무 큽니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:324 +msgid "" +"Operation requires a scrollable ResultSet, but this ResultSet is " +"FORWARD_ONLY." +msgstr "" +"작업에는 스크롤 가능한 ResultSet이 필요하지만 이 ResultSet은 FORWARD_ONLY입니" +"다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:543 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:586 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:636 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:696 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:719 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:742 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:773 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:796 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3522 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3600 +#, java-format +msgid "Cannot convert the column of type {0} to requested type {1}." +msgstr "유형 {0} 의 열을 요청된 유형 {1} (으)로 변환할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1022 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1043 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:2276 +msgid "Can''t use relative move methods while on the insert row." +msgstr "삽입 행에 있는 동안 상대적 이동 메서드를 사용할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1067 +#: src/main/java/org/postgresql/jdbc/PgStatement.java:1004 +#, java-format +msgid "Invalid fetch direction constant: {0}." +msgstr "잘못된 가져오기 방향 상수: {0}." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1079 +msgid "Cannot call cancelRowUpdates() when on the insert row." +msgstr "삽입 행에 있는 동안 cancelRowUpdates()를 호출할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1097 +msgid "Cannot call deleteRow() when on the insert row." +msgstr "삽입 행에 있는 동안 deleteRow()를 호출할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1104 +msgid "" +"Currently positioned before the start of the ResultSet. You cannot call " +"deleteRow() here." +msgstr "" +"현재 ResultSet의 시작 이전에 위치해 있습니다. 여기에서 deleteRow()를 호출할 " +"수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1110 +msgid "" +"Currently positioned after the end of the ResultSet. You cannot call " +"deleteRow() here." +msgstr "" +"현재 ResultSet의 끝 이후에 위치해 있습니다. 여기에서 deleteRow()를 호출할 수 " +"없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1115 +msgid "There are no rows in this ResultSet." +msgstr "이 ResultSet에는 행이 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1156 +msgid "Not on the insert row." +msgstr "삽입 행에 있지 않습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1160 +msgid "You must specify at least one column value to insert a row." +msgstr "행을 삽입하려면 적어도 하나의 열 값을 지정해야 합니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1495 +msgid "Can''t refresh the insert row." +msgstr "삽입 행을 새로 고칠 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1571 +msgid "Cannot call updateRow() when on the insert row." +msgstr "삽입 행에 있는 동안 updateRow()를 호출할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1579 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3617 +msgid "" +"Cannot update the ResultSet because it is either before the start or after " +"the end of the results." +msgstr "" +"결과의 시작 전이나 끝 후에 있기 때문에 ResultSet을 업데이트할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1842 +msgid "ResultSets with concurrency CONCUR_READ_ONLY cannot be updated." +msgstr "동시성 CONCUR_READ_ONLY를 가진 ResultSet은 업데이트할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:1946 +#, java-format +msgid "No eligible primary or unique key found for table {0}." +msgstr "테이블 {0} 에 대해 적합한 기본 키 또는 고유 키가 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:2146 +#, java-format +msgid "The JVM claims not to support the encoding: {0}" +msgstr "JVM이 인코딩을 지원하지 않는다고 주장합니다: {0}" + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:2537 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:2542 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3315 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3321 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3346 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3352 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3376 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3381 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3397 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3418 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3429 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3442 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3571 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3581 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3592 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3605 +#, java-format +msgid "Bad value for type {0} : {1}" +msgstr "유형 {0} 에 대한 잘못된 값: {1}" + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3086 +#, java-format +msgid "The column name {0} was not found in this ResultSet." +msgstr "이 ResultSet에서 열 이름 {0} 을(를) 찾을 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3227 +msgid "" +"ResultSet is not updateable. The query that generated this result set must " +"select only one table, and must select all primary keys from that table. See " +"the JDBC 2.1 API Specification, section 5.6 for more details." +msgstr "" +"ResultSet은 업데이트할 수 없습니다. 이 결과 집합을 생성한 쿼리는 하나의 테이" +"블만 선택해야 하며 해당 테이블의 모든 기본 키를 선택해야 합니다. 자세한 내용" +"은 JDBC 2.1 API 명세서 섹션 5.6을 참조하십시오." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3243 +msgid "This ResultSet is closed." +msgstr "이 ResultSet은 닫혔습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3277 +msgid "ResultSet not positioned properly, perhaps you need to call next." +msgstr "" +"ResultSet이 올바르게 위치하지 않았습니다. 아마도 next를 호출해야 할 것입니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3639 +msgid "Invalid UUID data." +msgstr "잘못된 UUID 데이터입니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3739 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3746 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3757 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3768 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3779 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3790 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3801 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3812 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3823 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3830 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3837 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3846 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3861 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3868 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3875 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3886 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3893 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3900 +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3937 +#, java-format +msgid "conversion to {0} from {1} not supported" +msgstr "{1} 에서 {0} (으)로의 변환이 지원되지 않습니다." + +#: src/main/java/org/postgresql/jdbc/PgResultSet.java:3914 +msgid "Invalid Inet data." +msgstr "잘못된 Inet 데이터입니다." + +#: src/main/java/org/postgresql/jdbc/PgSQLXML.java:170 +msgid "Unable to decode xml data." +msgstr "xml 데이터를 디코딩할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgSQLXML.java:173 +#, java-format +msgid "Unknown XML Source class: {0}" +msgstr "알 수 없는 XML 소스 클래스: {0}" + +#: src/main/java/org/postgresql/jdbc/PgSQLXML.java:229 +msgid "Unable to create SAXResult for SQLXML." +msgstr "SQLXML에 대한 SAXResult를 생성할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgSQLXML.java:245 +msgid "Unable to create StAXResult for SQLXML" +msgstr "SQLXML에 대한 StAXResult를 생성할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgSQLXML.java:250 +#, java-format +msgid "Unknown XML Result class: {0}" +msgstr "알 수 없는 XML 결과 클래스: {0}" + +#: src/main/java/org/postgresql/jdbc/PgSQLXML.java:266 +msgid "This SQLXML object has already been freed." +msgstr "이 SQLXML 객체는 이미 해제되었습니다." + +#: src/main/java/org/postgresql/jdbc/PgSQLXML.java:275 +msgid "" +"This SQLXML object has not been initialized, so you cannot retrieve data " +"from it." +msgstr "이 SQLXML 객체가 초기화되지 않았으므로 데이터를 검색할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgSQLXML.java:288 +#, java-format +msgid "Failed to convert binary xml data to encoding: {0}." +msgstr "이진 xml 데이터를 인코딩 {0} (으)로 변환하지 못했습니다." + +#: src/main/java/org/postgresql/jdbc/PgSQLXML.java:315 +msgid "Unable to convert DOMResult SQLXML data to a string." +msgstr "DOMResult SQLXML 데이터를 문자열로 변환할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgSQLXML.java:328 +msgid "" +"This SQLXML object has already been initialized, so you cannot manipulate it " +"further." +msgstr "이 SQLXML 객체는 이미 초기화되었으므로 더 이상 조작할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgStatement.java:166 +msgid "Unknown value for ResultSet type" +msgstr "ResultSet 유형에 대한 알 수 없는 값입니다." + +#: src/main/java/org/postgresql/jdbc/PgStatement.java:172 +msgid "Unknown value for ResultSet concurrency" +msgstr "ResultSet 동시성에 대한 알 수 없는 값입니다." + +#: src/main/java/org/postgresql/jdbc/PgStatement.java:181 +msgid "Unknown value for ResultSet holdability" +msgstr "ResultSet 유지 가능성에 대한 알 수 없는 값입니다." + +#: src/main/java/org/postgresql/jdbc/PgStatement.java:279 +msgid "Multiple ResultSets were returned by the query." +msgstr "쿼리에서 여러 ResultSet이 반환되었습니다." + +#: src/main/java/org/postgresql/jdbc/PgStatement.java:366 +msgid "Can''t use executeWithFlags(int) on a Statement." +msgstr "문에서 executeWithFlags(int)를 사용할 수 없습니다." + +#: src/main/java/org/postgresql/jdbc/PgStatement.java:579 +msgid "Maximum number of rows must be a value greater than or equal to 0." +msgstr "최대 행 수는 0보다 크거나 같아야 합니다." + +#: src/main/java/org/postgresql/jdbc/PgStatement.java:628 +msgid "Query timeout must be a value greater than or equals to 0." +msgstr "쿼리 시간 초과는 0보다 크거나 같아야 합니다." + +#: src/main/java/org/postgresql/jdbc/PgStatement.java:671 +msgid "The maximum field size must be a value greater than or equal to 0." +msgstr "최대 필드 크기는 0보다 크거나 같아야 합니다." + +#: src/main/java/org/postgresql/jdbc/PgStatement.java:776 +msgid "This statement has been closed." +msgstr "이 문이 닫혔습니다." + +#: src/main/java/org/postgresql/jdbc/PgStatement.java:1165 +#: src/main/java/org/postgresql/jdbc/PgStatement.java:1303 +#: src/main/java/org/postgresql/jdbc/PgStatement.java:1336 +msgid "Returning autogenerated keys by column index is not supported." +msgstr "열 인덱스별 자동 생성 키 반환이 지원되지 않습니다." + +#: src/main/java/org/postgresql/jdbc/QueryExecutorTimeZoneProvider.java:32 +msgid "" +"Backend timezone is not known. Backend should have returned TimeZone when " +"establishing a connection" +msgstr "" +"백엔드 표준 시간대를 알 수 없습니다. 연결을 설정할 때 백엔드에서 TimeZone을 " +"반환해야 합니다." + +#: src/main/java/org/postgresql/jdbc/SslMode.java:78 +#, java-format +msgid "Invalid sslmode value: {0}" +msgstr "잘못된 sslmode 값: {0}" + +#: src/main/java/org/postgresql/jdbc/TimestampUtils.java:379 +#: src/main/java/org/postgresql/jdbc/TimestampUtils.java:496 +#, java-format +msgid "Bad value for type timestamp/date/time: {0}" +msgstr "유형 타임스탬프/날짜/시간에 대한 잘못된 값: {0}" + +#: src/main/java/org/postgresql/jdbc/TimestampUtils.java:511 +#: src/main/java/org/postgresql/jdbc/TimestampUtils.java:1326 +#: src/main/java/org/postgresql/jdbc/TimestampUtils.java:1383 +#: src/main/java/org/postgresql/jdbc/TimestampUtils.java:1427 +#: src/main/java/org/postgresql/jdbc/TimestampUtils.java:1480 +#: src/main/java/org/postgresql/jdbc/TimestampUtils.java:1607 +#, java-format +msgid "Unsupported binary encoding of {0}." +msgstr "{0} 의 지원되지 않는 이진 인코딩." + +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:307 +msgid "typname" +msgstr "" + +#: src/main/java/org/postgresql/jdbc/TypeInfoCache.java:1087 +#, java-format +msgid "Value is not an OID: {0}" +msgstr "값이 OID가 아닙니다: {0}" + +#: src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java:68 +msgid "No SCRAM mechanism(s) advertised by the server" +msgstr "서버에서 광고한 SCRAM 메커니즘이 없습니다." + +#: src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java:82 +msgid "Invalid or unsupported by client SCRAM mechanisms" +msgstr "클라이언트에서 지원되지 않거나 잘못된 SCRAM 메커니즘" + +#: src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java:125 +msgid "SCRAM session does not exist" +msgstr "SCRAM 세션이 존재하지 않습니다." + +#: src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java:135 +#, java-format +msgid "Invalid server-first-message: {0}" +msgstr "잘못된 서버 첫 번째 메시지: {0}" + +#: src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java:171 +msgid "SCRAM client final processor does not exist" +msgstr "SCRAM 클라이언트 최종 프로세서가 존재하지 않습니다." + +#: src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java:179 +#, java-format +msgid "Invalid server-final-message: {0}" +msgstr "잘못된 서버 최종 메시지: {0}" + +#: src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java:185 +#, java-format +msgid "SCRAM authentication failed, server returned error: {0}" +msgstr "SCRAM 인증 실패, 서버에서 오류를 반환했습니다: {0}" + +#: src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java:192 +msgid "Invalid server SCRAM signature" +msgstr "잘못된 서버 SCRAM 서명" + +#: src/main/java/org/postgresql/largeobject/BlobInputStream.java:138 +#: src/main/java/org/postgresql/largeobject/BlobInputStream.java:231 +#, java-format +msgid "" +"Can not read data from large object {0}, position: {1}, buffer size: {2}" +msgstr "" +"대형 객체 {0} 에서 데이터를 읽을 수 없습니다. 위치: {1}, 버퍼 크기: {2}" + +#: src/main/java/org/postgresql/largeobject/BlobInputStream.java:261 +#: src/main/java/org/postgresql/largeobject/BlobOutputStream.java:236 +#, java-format +msgid "Can not close large object {0}" +msgstr "대형 객체 {0} 을(를) 닫을 수 없습니다." + +#: src/main/java/org/postgresql/largeobject/BlobInputStream.java:317 +#, java-format +msgid "Can not reset stream for large object {0} to position {1}" +msgstr "대형 객체 {0} 에 대한 스트림을 위치 {1} (으)로 재설정할 수 없습니다." + +#: src/main/java/org/postgresql/largeobject/BlobOutputStream.java:105 +#: src/main/java/org/postgresql/largeobject/BlobOutputStream.java:190 +#, java-format +msgid "Can not write data to large object {0}, requested write length: {1}" +msgstr "대형 객체 {0} 에 데이터를 쓸 수 없습니다. 요청된 쓰기 길이: {1}" + +#: src/main/java/org/postgresql/largeobject/BlobOutputStream.java:217 +#, java-format +msgid "Can not flush large object {0}" +msgstr "대형 객체 {0} 을(를) 플러시할 수 없습니다." + +#: src/main/java/org/postgresql/largeobject/LargeObjectManager.java:244 +#: src/main/java/org/postgresql/largeobject/LargeObjectManager.java:285 +msgid "Large Objects may not be used in auto-commit mode." +msgstr "대형 객체는 자동 커밋 모드에서 사용할 수 없습니다." + +#: src/main/java/org/postgresql/osgi/PGDataSourceFactory.java:89 +#, java-format +msgid "Unsupported properties: {0}" +msgstr "지원되지 않는 속성: {0}" + +#: src/main/java/org/postgresql/replication/fluent/AbstractCreateSlotBuilder.java:40 +msgid "Server does not support temporary replication slots" +msgstr "서버가 임시 복제 슬롯을 지원하지 않습니다." + +#: src/main/java/org/postgresql/replication/fluent/logical/LogicalCreateSlotBuilder.java:72 +#: src/main/java/org/postgresql/replication/fluent/physical/PhysicalCreateSlotBuilder.java:55 +msgid "slot_name" +msgstr "" + +#: src/main/java/org/postgresql/replication/fluent/logical/LogicalCreateSlotBuilder.java:74 +#: src/main/java/org/postgresql/replication/fluent/physical/PhysicalCreateSlotBuilder.java:57 +msgid "consistent_point" +msgstr "" + +#: src/main/java/org/postgresql/replication/fluent/logical/LogicalCreateSlotBuilder.java:75 +#: src/main/java/org/postgresql/replication/fluent/physical/PhysicalCreateSlotBuilder.java:58 +msgid "snapshot_name" +msgstr "" + +#: src/main/java/org/postgresql/replication/fluent/logical/LogicalCreateSlotBuilder.java:76 +#: src/main/java/org/postgresql/replication/fluent/physical/PhysicalCreateSlotBuilder.java:59 +msgid "output_plugin" +msgstr "" + +#: src/main/java/org/postgresql/replication/fluent/logical/LogicalCreateSlotBuilder.java:79 +#: src/main/java/org/postgresql/replication/fluent/physical/PhysicalCreateSlotBuilder.java:62 +#, java-format +msgid "{0} returned no results" +msgstr "{0} 반환된 결과가 없습니다." + +#: src/main/java/org/postgresql/ssl/LazyKeyManager.java:152 +#: src/main/java/org/postgresql/ssl/PKCS12KeyManager.java:141 +msgid "" +"Could not find a java cryptographic algorithm: X.509 CertificateFactory not " +"available." +msgstr "" +"Java 암호화 알고리즘을 찾을 수 없습니다: X.509 CertificateFactory를 사용할 " +"수 없습니다." + +#: src/main/java/org/postgresql/ssl/LazyKeyManager.java:164 +#, java-format +msgid "Could not open SSL certificate file {0}." +msgstr "SSL 인증서 파일 {0} 을(를) 열 수 없습니다." + +#: src/main/java/org/postgresql/ssl/LazyKeyManager.java:169 +#, java-format +msgid "Loading the SSL certificate {0} into a KeyManager failed." +msgstr "SSL 인증서 {0} 을(를) KeyManager로 로드하지 못했습니다." + +#: src/main/java/org/postgresql/ssl/LazyKeyManager.java:179 +#, java-format +msgid "Could not close SSL certificate file {0}." +msgstr "SSL 인증서 파일 {0} 을(를) 닫을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/LazyKeyManager.java:245 +#: src/main/java/org/postgresql/ssl/PKCS12KeyManager.java:151 +#: src/main/java/org/postgresql/ssl/PKCS12KeyManager.java:174 +msgid "Enter SSL password: " +msgstr "SSL 비밀번호 입력: " + +#: src/main/java/org/postgresql/ssl/LazyKeyManager.java:252 +#: src/main/java/org/postgresql/ssl/PKCS12KeyManager.java:181 +msgid "Could not read password for SSL key file, console is not available." +msgstr "SSL 키 파일의 비밀번호를 읽을 수 없습니다. 콘솔을 사용할 수 없습니다." + +#: src/main/java/org/postgresql/ssl/LazyKeyManager.java:257 +#: src/main/java/org/postgresql/ssl/PKCS12KeyManager.java:186 +#, java-format +msgid "Could not read password for SSL key file by callbackhandler {0}." +msgstr "" +"콜백 핸들러 {0} 을(를) 사용하여 SSL 키 파일의 비밀번호를 읽을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/LazyKeyManager.java:277 +#, java-format +msgid "Could not decrypt SSL key file {0}." +msgstr "SSL 키 파일 {0} 을(를) 해독할 수 없습니다." + +#: src/main/java/org/postgresql/ssl/LazyKeyManager.java:284 +#: src/main/java/org/postgresql/ssl/PKCS12KeyManager.java:162 +#, java-format +msgid "Could not read SSL key file {0}." +msgstr "SSL 키 파일 {0} 을(를) 읽을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/LazyKeyManager.java:287 +#: src/main/java/org/postgresql/ssl/LibPQFactory.java:194 +#, java-format +msgid "Could not find a java cryptographic algorithm: {0}." +msgstr "Java 암호화 알고리즘을 찾을 수 없습니다: {0}." + +#: src/main/java/org/postgresql/ssl/LibPQFactory.java:63 +#, java-format +msgid "The password callback class provided {0} could not be instantiated." +msgstr "제공된 비밀번호 콜백 클래스 {0} 을(를) 인스턴스화할 수 없습니다." + +#: src/main/java/org/postgresql/ssl/LibPQFactory.java:151 +#, java-format +msgid "Could not open SSL root certificate file {0}." +msgstr "SSL 루트 인증서 파일 {0} 을(를) 열 수 없습니다." + +#: src/main/java/org/postgresql/ssl/LibPQFactory.java:166 +#, java-format +msgid "Could not read SSL root certificate file {0}." +msgstr "SSL 루트 인증서 파일 {0} 을(를) 읽을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/LibPQFactory.java:170 +#, java-format +msgid "Loading the SSL root certificate {0} into a TrustManager failed." +msgstr "SSL 루트 인증서 {0} 을(를) TrustManager로 로드하지 못했습니다." + +#: src/main/java/org/postgresql/ssl/LibPQFactory.java:188 +msgid "Could not initialize SSL context." +msgstr "SSL 컨텍스트를 초기화할 수 없습니다." + +#: src/main/java/org/postgresql/ssl/MakeSSL.java:45 +#, java-format +msgid "SSL error: {0}" +msgstr "SSL 오류: {0}" + +#: src/main/java/org/postgresql/ssl/MakeSSL.java:74 +#, java-format +msgid "The HostnameVerifier class provided {0} could not be instantiated." +msgstr "제공된 HostnameVerifier 클래스 {0} 을(를) 인스턴스화할 수 없습니다." + +#: src/main/java/org/postgresql/ssl/MakeSSL.java:85 +#, java-format +msgid "The hostname {0} could not be verified by hostnameverifier {1}." +msgstr "hostnameverifier {1} 이(가) 호스트 이름 {0}을(를) 확인할 수 없습니다." + +#: src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java:87 +#, java-format +msgid "Unable to parse X509Certificate for hostname {0}" +msgstr "호스트 이름 {0} 에 대한 X509Certificate을(를) 구문 분석할 수 없습니다." + +#: src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java:92 +#, java-format +msgid "No certificates found for hostname {0}" +msgstr "호스트 이름 {0} 에 대한 인증서를 찾을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java:111 +#, java-format +msgid "Hostname {0} is invalid" +msgstr "호스트 이름 {0} 이(가) 유효하지 않습니다." + +#: src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java:128 +#, java-format +msgid "Unable to parse certificates for hostname {0}" +msgstr "호스트 이름 {0} 에 대한 인증서를 구문 분석할 수 없습니다." + +#: src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java:159 +#, java-format +msgid "Server name validation pass for {0}, subjectAltName {1}" +msgstr "서버 이름 유효성 검사가 {0}, subjectAltName {1} 에 대해 통과했습니다." + +#: src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java:175 +#, java-format +msgid "" +"Server name validation failed: certificate for host {0} dNSName entries " +"subjectAltName, but none of them match. Assuming server name validation " +"failed" +msgstr "" +"서버 이름 유효성 검사 실패: 호스트 {0} dNSName 항목 subjectAltName에 대한 인" +"증서가 있지만 일치하지 않습니다. 서버 이름 유효성 검사 실패로 가정" + +#: src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java:186 +#, java-format +msgid "" +"Server name validation failed: unable to extract common name from " +"X509Certificate for hostname {0}" +msgstr "" +"서버 이름 유효성 검사 실패: 호스트 이름 {0} 에 대한 X509Certificate에서 공통 " +"이름을 추출할 수 없습니다." + +#: src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java:199 +#, java-format +msgid "" +"Server name validation failed: certificate for hostname {0} has no DNS " +"subjectAltNames, and it CommonName is missing as well" +msgstr "" +"서버 이름 유효성 검사 실패: 호스트 이름 {0} 에 대한 인증서에 DNS " +"subjectAltNames가 없으며 CommonName도 없습니다." + +#: src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java:219 +#, java-format +msgid "" +"Server name validation failed: hostname {0} does not match common name {1}" +msgstr "" +"서버 이름 유효성 검사 실패: 호스트 이름 {0} 이(가) 공통 이름 {1} 과(와) 일치" +"하지 않습니다." + +#: src/main/java/org/postgresql/ssl/PKCS12KeyManager.java:47 +msgid "Unable to find pkcs12 keystore." +msgstr "pkcs12 키 저장소를 찾을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java:92 +msgid "The sslfactoryarg property may not be empty." +msgstr "sslfactoryarg 속성은 비어 있을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java:107 +#, java-format +msgid "Unable to find resource {0} via Thread contextClassLoader {1}" +msgstr "" +"스레드 contextClassLoader {1} 을(를) 통해 리소스 {0} 을(를) 찾을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java:115 +#, java-format +msgid "Unable to find resource {0} via class {1} ClassLoader {2}" +msgstr "" +"클래스 {1} ClassLoader {2} 을(를) 통해 리소스 {0} 을(를) 찾을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java:126 +msgid "" +"The environment variable containing the server's SSL certificate must not be " +"empty." +msgstr "서버의 SSL 인증서를 포함하는 환경 변수는 비어 있을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java:134 +msgid "" +"The system property containing the server's SSL certificate must not be " +"empty." +msgstr "서버의 SSL 인증서를 포함하는 시스템 속성은 비어 있을 수 없습니다." + +#: src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java:141 +msgid "" +"The sslfactoryarg property must start with the prefix file:, classpath:, " +"env:, sys:, or -----BEGIN CERTIFICATE-----." +msgstr "" +"sslfactoryarg 속성은 file:, classpath:, env:, sys: 또는 -----BEGIN " +"CERTIFICATE----- 접두사로 시작해야 합니다." + +#: src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java:153 +msgid "An error occurred reading the certificate" +msgstr "인증서를 읽는 동안 오류가 발생했습니다." + +#: src/main/java/org/postgresql/ssl/SingleCertValidatingFactory.java:186 +msgid "No X509TrustManager found" +msgstr "X509TrustManager를 찾을 수 없습니다." + +#: src/main/java/org/postgresql/sspi/SSPIClient.java:67 +msgid "Unable to instantiate SecBufferDesc, so SSPI is unavailable" +msgstr "SecBufferDesc을 인스턴스화할 수 없어 SSPI를 사용할 수 없습니다." + +#: src/main/java/org/postgresql/util/HStoreConverter.java:61 +msgid "hstore key must not be null" +msgstr "hstore 키는 null일 수 없습니다." + +#: src/main/java/org/postgresql/util/PGInterval.java:221 +msgid "Conversion of interval failed" +msgstr "간격 변환 실패" + +#: src/main/java/org/postgresql/util/PGPropertyMaxResultBufferParser.java:201 +#, java-format +msgid "" +"WARNING! Required to allocate {0} bytes, which exceeded possible heap memory " +"size. Assigned {1} bytes as limit." +msgstr "" +"경고! {0} 바이트를 할당해야 하는데 이는 가능한 힙 메모리 크기를 초과했습니" +"다. {1} 바이트를 한도로 할당했습니다." + +#: src/main/java/org/postgresql/util/PGbytea.java:233 +msgid "Can't convert {0} to {1} literal" +msgstr "{0} 을(를) {1} 리터럴로 변환할 수 없습니다." + +#: src/main/java/org/postgresql/util/PGmoney.java:74 +msgid "Conversion of money failed." +msgstr "금액 변환 실패." + +#: src/main/java/org/postgresql/util/ServerErrorMessage.java:45 +#, java-format +msgid "" +" (pgjdbc: autodetected server-encoding to be {0}, if the message is not " +"readable, please check database logs and/or host, port, dbname, user, " +"password, pg_hba.conf)" +msgstr "" +"(pgjdbc: 서버 인코딩이 {0} (으)로 자동 감지되었습니다. 메시지를 읽을 수 없는 " +"경우 데이터베이스 로그 및/또는 호스트, 포트, dbname, 사용자, 비밀번호, " +"pg_hba.conf를 확인하십시오.)" + +#: src/main/java/org/postgresql/util/ServerErrorMessage.java:190 +#, java-format +msgid "Detail: {0}" +msgstr "세부 정보: {0}" + +#: src/main/java/org/postgresql/util/ServerErrorMessage.java:195 +#, java-format +msgid "Hint: {0}" +msgstr "힌트: {0}" + +#: src/main/java/org/postgresql/util/ServerErrorMessage.java:199 +#, java-format +msgid "Position: {0}" +msgstr "위치: {0}" + +#: src/main/java/org/postgresql/util/ServerErrorMessage.java:203 +#, java-format +msgid "Where: {0}" +msgstr "위치: {0}" + +#: src/main/java/org/postgresql/util/ServerErrorMessage.java:209 +#, java-format +msgid "Internal Query: {0}" +msgstr "내부 쿼리: {0}" + +#: src/main/java/org/postgresql/util/ServerErrorMessage.java:213 +#, java-format +msgid "Internal Position: {0}" +msgstr "내부 위치: {0}" + +#: src/main/java/org/postgresql/util/ServerErrorMessage.java:220 +#, java-format +msgid "Location: File: {0}, Routine: {1}, Line: {2}" +msgstr "위치: 파일: {0}, 루틴: {1}, 줄: {2}" + +#: src/main/java/org/postgresql/util/ServerErrorMessage.java:225 +#, java-format +msgid "Server SQLState: {0}" +msgstr "서버 SQL 상태: {0}" + +#: src/main/java/org/postgresql/util/TempFileHolder.java:45 +msgid "StreamWrapper leak detected StreamWrapper.close() was not called. " +msgstr "" +"StreamWrapper 누수가 감지되었습니다. StreamWrapper.close()가 호출되지 않았습" +"니다." + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:133 +msgid "" +"Transaction control methods setAutoCommit(true), commit, rollback and " +"setSavePoint not allowed while an XA transaction is active." +msgstr "" +"XA 트랜잭션이 활성 상태인 동안 트랜잭션 제어 메서드 setAutoCommit(true), " +"commit, rollback 및 setSavePoint가 허용되지 않습니다." + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:191 +#: src/main/java/org/postgresql/xa/PGXAConnection.java:277 +#: src/main/java/org/postgresql/xa/PGXAConnection.java:386 +#, java-format +msgid "Invalid flags {0}" +msgstr "잘못된 플래그 {0}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:195 +#: src/main/java/org/postgresql/xa/PGXAConnection.java:281 +#: src/main/java/org/postgresql/xa/PGXAConnection.java:496 +msgid "xid must not be null" +msgstr "xid는 null일 수 없습니다." + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:199 +msgid "Connection is busy with another transaction" +msgstr "연결이 다른 트랜잭션으로 바쁩니다." + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:208 +#: src/main/java/org/postgresql/xa/PGXAConnection.java:291 +msgid "suspend/resume not implemented" +msgstr "일시 중지/다시 시작이 구현되지 않았습니다." + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:216 +#: src/main/java/org/postgresql/xa/PGXAConnection.java:223 +#: src/main/java/org/postgresql/xa/PGXAConnection.java:227 +#, java-format +msgid "" +"Invalid protocol state requested. Attempted transaction interleaving is not " +"supported. xid={0}, currentXid={1}, state={2}, flags={3}" +msgstr "" +"잘못된 프로토콜 상태 요청됨. 트랜잭션 교차 시도는 지원되지 않습니다. " +"xid={0}, currentXid={1}, state={2}, flags={3}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:238 +msgid "Error disabling autocommit" +msgstr "자동 커밋 비활성화 오류" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:285 +#, java-format +msgid "" +"tried to call end without corresponding start call. state={0}, start " +"xid={1}, currentXid={2}, preparedXid={3}" +msgstr "" +"해당 시작 호출 없이 end를 호출하려고 했습니다. state={0}, start xid={1}, " +"currentXid={2}, preparedXid={3}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:331 +#, java-format +msgid "" +"Preparing already prepared transaction, the prepared xid {0}, prepare xid={1}" +msgstr "이미 준비된 트랜잭션 준비, 준비된 xid {0}, prepare xid={1}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:334 +#, java-format +msgid "Current connection does not have an associated xid. prepare xid={0}" +msgstr "현재 연결에는 연결된 xid가 없습니다. prepare xid={0}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:341 +#, java-format +msgid "" +"Not implemented: Prepare must be issued using the same connection that " +"started the transaction. currentXid={0}, prepare xid={1}" +msgstr "" +"구현되지 않음: Prepare는 트랜잭션을 시작한 동일한 연결을 사용하여 발행해야 합" +"니다. currentXid={0}, prepare xid={1}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:345 +#, java-format +msgid "Prepare called before end. prepare xid={0}, state={1}" +msgstr "end 이전에 호출 준비. prepare xid={0}, state={1}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:365 +#, java-format +msgid "Error preparing transaction. prepare xid={0}" +msgstr "트랜잭션 준비 오류. prepare xid={0}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:421 +msgid "Error during recover" +msgstr "복구 중 오류" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:485 +#, java-format +msgid "" +"Error rolling back prepared transaction. rollback xid={0}, preparedXid={1}, " +"currentXid={2}" +msgstr "" +"준비된 트랜잭션을 롤백하는 중 오류가 발생했습니다. 롤백 xid={0}, " +"preparedXid={1}, currentXid={2}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:526 +#, java-format +msgid "" +"One-phase commit called for xid {0} but connection was prepared with xid {1}" +msgstr "" +"1단계 커밋이 xid {0} 에 대해 호출되었으나 연결이 xid {1}(으)로 준비되었습니" +"다." + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:534 +msgid "" +"Not implemented: one-phase commit must be issued using the same connection " +"that was used to start it" +msgstr "" +"구현되지 않음: 1단계 커밋은 시작하는 데 사용된 동일한 연결을 사용하여 발행해" +"야 합니다." + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:538 +#, java-format +msgid "One-phase commit with unknown xid. commit xid={0}, currentXid={1}" +msgstr "알 수 없는 xid와 1단계 커밋. commit xid={0}, currentXid={1}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:542 +#, java-format +msgid "commit called before end. commit xid={0}, state={1}" +msgstr "end 이전에 호출 커밋. commit xid={0}, state={1}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:553 +#, java-format +msgid "Error during one-phase commit. commit xid={0}" +msgstr "1단계 커밋 중 오류 발생. commit xid={0}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:581 +#, java-format +msgid "" +"Not implemented: 2nd phase commit must be issued using an idle connection. " +"commit xid={0}, currentXid={1}, state={2}, transactionState={3}" +msgstr "" +"구현되지 않음: 2단계 커밋은 유휴 연결을 사용하여 발행해야 합니다. commit " +"xid={0}, currentXid={1}, state={2}, transactionState={3}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:614 +#, java-format +msgid "" +"Error committing prepared transaction. commit xid={0}, preparedXid={1}, " +"currentXid={2}" +msgstr "" +"준비된 트랜잭션을 커밋하는 중 오류가 발생했습니다. commit xid={0}, " +"preparedXid={1}, currentXid={2}" + +#: src/main/java/org/postgresql/xa/PGXAConnection.java:631 +#, java-format +msgid "Heuristic commit/rollback not supported. forget xid={0}" +msgstr "휴리스틱 커밋/롤백이 지원되지 않습니다. forget xid={0}" diff --git a/src/main/java/org/postgresql/translation/messages.pot b/src/main/java/org/postgresql/translation/messages.pot index a879b09..52d3822 100644 --- a/src/main/java/org/postgresql/translation/messages.pot +++ b/src/main/java/org/postgresql/translation/messages.pot @@ -827,7 +827,7 @@ msgstr "" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/messages_bg.java b/src/main/java/org/postgresql/translation/messages_bg.java index 21fce2f..dfddefb 100644 --- a/src/main/java/org/postgresql/translation/messages_bg.java +++ b/src/main/java/org/postgresql/translation/messages_bg.java @@ -300,7 +300,7 @@ public class messages_bg extends java.util.ResourceBundle { t[657] = "Отчетен параметър от тип {0}, но обработено като get{1} (sqltype={2}). "; t[662] = "Unsupported value for stringtype parameter: {0}"; t[663] = "Непозволена стойност за StringType параметър: {0}"; - t[664] = "Fetch size must be a value greater to or equal to 0."; + t[664] = "Fetch size must be a value greater than or equal to 0."; t[665] = "Размера за fetch size трябва да бъде по-голям или равен на 0."; t[670] = "Cannot tell if path is open or closed: {0}."; t[671] = "Не може да определи дали адреса е отворен или затворен: {0}."; diff --git a/src/main/java/org/postgresql/translation/messages_cs.java b/src/main/java/org/postgresql/translation/messages_cs.java index e3c63a8..8e5d342 100644 --- a/src/main/java/org/postgresql/translation/messages_cs.java +++ b/src/main/java/org/postgresql/translation/messages_cs.java @@ -118,7 +118,7 @@ public class messages_cs extends java.util.ResourceBundle { t[187] = "Ovladač nyní nepodporuje příkaz COPY."; t[190] = "Invalid character data was found. This is most likely caused by stored data containing characters that are invalid for the character set the database was created in. The most common example of this is storing 8bit data in a SQL_ASCII database."; t[191] = "Nalezena vada ve znakových datech. Toto může být způsobeno uloženými daty obsahujícími znaky, které jsou závadné pro znakovou sadu nastavenou při zakládání databáze. Nejznámejší příklad je ukládání 8bitových dat vSQL_ASCII databázi."; - t[196] = "Fetch size must be a value greater to or equal to 0."; + t[196] = "Fetch size must be a value greater than or equal to 0."; t[197] = "Nabraná velikost musí být nezáporná."; t[204] = "Unsupported Types value: {0}"; t[205] = "Nepodporovaná hodnota typu: {0}"; diff --git a/src/main/java/org/postgresql/translation/messages_de.java b/src/main/java/org/postgresql/translation/messages_de.java index dcac938..8e045a0 100644 --- a/src/main/java/org/postgresql/translation/messages_de.java +++ b/src/main/java/org/postgresql/translation/messages_de.java @@ -118,7 +118,7 @@ public class messages_de extends java.util.ResourceBundle { t[299] = "Diese PooledConnection ist bereits geschlossen worden."; t[302] = "ClientInfo property not supported."; t[303] = "Die ClientInfo-Eigenschaft ist nicht unterstützt."; - t[306] = "Fetch size must be a value greater to or equal to 0."; + t[306] = "Fetch size must be a value greater than or equal to 0."; t[307] = "Die Fetch-Größe muss ein Wert größer oder gleich Null sein."; t[312] = "A connection could not be made using the requested protocol {0}."; t[313] = "Es konnte keine Verbindung unter Verwendung des Protokolls {0} hergestellt werden."; diff --git a/src/main/java/org/postgresql/translation/messages_fr.java b/src/main/java/org/postgresql/translation/messages_fr.java index 71230fa..8a2426b 100644 --- a/src/main/java/org/postgresql/translation/messages_fr.java +++ b/src/main/java/org/postgresql/translation/messages_fr.java @@ -116,7 +116,7 @@ public class messages_fr extends java.util.ResourceBundle { t[297] = "Le troncage des large objects n''est implémenté que dans les serveurs 8.3 et supérieurs."; t[298] = "This PooledConnection has already been closed."; t[299] = "Cette PooledConnection a déjà été fermée."; - t[306] = "Fetch size must be a value greater to or equal to 0."; + t[306] = "Fetch size must be a value greater than or equal to 0."; t[307] = "Fetch size doit être une valeur supérieur ou égal à 0."; t[312] = "A connection could not be made using the requested protocol {0}."; t[313] = "Aucune connexion n''a pu être établie en utilisant le protocole demandé {0}. "; diff --git a/src/main/java/org/postgresql/translation/messages_it.java b/src/main/java/org/postgresql/translation/messages_it.java index 9f412b5..2bf0cb3 100644 --- a/src/main/java/org/postgresql/translation/messages_it.java +++ b/src/main/java/org/postgresql/translation/messages_it.java @@ -108,7 +108,7 @@ public class messages_it extends java.util.ResourceBundle { t[281] = "Il server ha richiesto l''autenticazione con password, ma tale password non è stata fornita."; t[298] = "This PooledConnection has already been closed."; t[299] = "Questo «PooledConnection» è stato chiuso."; - t[306] = "Fetch size must be a value greater to or equal to 0."; + t[306] = "Fetch size must be a value greater than or equal to 0."; t[307] = "La dimensione dell''area di «fetch» deve essere maggiore o eguale a 0."; t[312] = "A connection could not be made using the requested protocol {0}."; t[313] = "Non è stato possibile attivare la connessione utilizzando il protocollo richiesto {0}."; diff --git a/src/main/java/org/postgresql/translation/messages_ja.java b/src/main/java/org/postgresql/translation/messages_ja.java index d1ffd50..93d32e9 100644 --- a/src/main/java/org/postgresql/translation/messages_ja.java +++ b/src/main/java/org/postgresql/translation/messages_ja.java @@ -96,7 +96,7 @@ public class messages_ja extends java.util.ResourceBundle { t[211] = "サーバはパスワード・ベースの認証を要求しましたが、パスワードが渡されませんでした。"; t[214] = "Interrupted while attempting to connect."; t[215] = "接続試行中に割り込みがありました。"; - t[216] = "Fetch size must be a value greater to or equal to 0."; + t[216] = "Fetch size must be a value greater than or equal to 0."; t[217] = "フェッチサイズは、0または、より大きな値でなくてはなりません。"; t[228] = "Added parameters index out of range: {0}, number of columns: {1}."; t[229] = "パラメータ・インデックスは範囲外です: {0} , カラム数: {1}"; diff --git a/src/main/java/org/postgresql/translation/messages_pl.java b/src/main/java/org/postgresql/translation/messages_pl.java index c9705f5..3bcccb6 100644 --- a/src/main/java/org/postgresql/translation/messages_pl.java +++ b/src/main/java/org/postgresql/translation/messages_pl.java @@ -106,7 +106,7 @@ public class messages_pl extends java.util.ResourceBundle { t[255] = "Aktualna pozycja przed początkiem ResultSet. Nie można wywołać deleteRow()."; t[258] = "Failed to create object for: {0}."; t[259] = "Nie powiodło się utworzenie obiektu dla: {0}."; - t[262] = "Fetch size must be a value greater to or equal to 0."; + t[262] = "Fetch size must be a value greater than or equal to 0."; t[263] = "Rozmiar pobierania musi być wartością dodatnią lub 0."; t[270] = "No results were returned by the query."; t[271] = "Zapytanie nie zwróciło żadnych wyników."; diff --git a/src/main/java/org/postgresql/translation/messages_pt_BR.java b/src/main/java/org/postgresql/translation/messages_pt_BR.java index a38e9b1..44548bc 100644 --- a/src/main/java/org/postgresql/translation/messages_pt_BR.java +++ b/src/main/java/org/postgresql/translation/messages_pt_BR.java @@ -134,7 +134,7 @@ public class messages_pt_BR extends java.util.ResourceBundle { t[299] = "Este PooledConnection já foi fechado."; t[302] = "ClientInfo property not supported."; t[303] = "propriedade ClientInfo não é suportada."; - t[306] = "Fetch size must be a value greater to or equal to 0."; + t[306] = "Fetch size must be a value greater than or equal to 0."; t[307] = "Tamanho da busca deve ser um valor maior ou igual a 0."; t[312] = "A connection could not be made using the requested protocol {0}."; t[313] = "A conexão não pode ser feita usando protocolo informado {0}."; diff --git a/src/main/java/org/postgresql/translation/messages_ru.java b/src/main/java/org/postgresql/translation/messages_ru.java index 3a83278..0785b56 100644 --- a/src/main/java/org/postgresql/translation/messages_ru.java +++ b/src/main/java/org/postgresql/translation/messages_ru.java @@ -53,7 +53,7 @@ public class messages_ru extends java.util.ResourceBundle { t[104] = "Connection to {0} refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections."; t[105] = "Подсоединение по адресу {0} отклонено. Проверьте что хост и порт указаны правильно и что postmaster принимает TCP/IP-подсоединения."; t[108] = "This statement has been closed."; - t[109] = "Этот Sstatement был закрыт."; + t[109] = "Этот statement был закрыт."; t[110] = "Error committing prepared transaction. commit xid={0}, preparedXid={1}, currentXid={2}"; t[111] = "Ошибка при фиксации подготовленной транзакции. commit xid={0}, preparedXid={1}, currentXid={2}"; t[114] = "Position: {0}"; diff --git a/src/main/java/org/postgresql/translation/messages_sr.java b/src/main/java/org/postgresql/translation/messages_sr.java index 78cd1ca..9385937 100644 --- a/src/main/java/org/postgresql/translation/messages_sr.java +++ b/src/main/java/org/postgresql/translation/messages_sr.java @@ -136,7 +136,7 @@ public class messages_sr extends java.util.ResourceBundle { t[299] = "PooledConnection je već zatvoren."; t[302] = "ClientInfo property not supported."; t[303] = "ClientInfo property nije podržan."; - t[306] = "Fetch size must be a value greater to or equal to 0."; + t[306] = "Fetch size must be a value greater than or equal to 0."; t[307] = "Doneta veličina mora biti vrednost veća ili jednaka 0."; t[312] = "A connection could not be made using the requested protocol {0}."; t[313] = "Konekciju nije moguće kreirati uz pomoć protokola {0}."; diff --git a/src/main/java/org/postgresql/translation/messages_tr.java b/src/main/java/org/postgresql/translation/messages_tr.java index f6e0e94..cdf1ca1 100644 --- a/src/main/java/org/postgresql/translation/messages_tr.java +++ b/src/main/java/org/postgresql/translation/messages_tr.java @@ -136,7 +136,7 @@ public class messages_tr extends java.util.ResourceBundle { t[299] = "Geçerli PooledConnection zaten önceden kapatıldı."; t[302] = "ClientInfo property not supported."; t[303] = "Clientinfo property'si desteklenememktedir."; - t[306] = "Fetch size must be a value greater to or equal to 0."; + t[306] = "Fetch size must be a value greater than or equal to 0."; t[307] = "Fetch boyutu sıfır veya daha büyük bir değer olmalıdır."; t[312] = "A connection could not be made using the requested protocol {0}."; t[313] = "İstenilen protokol ile bağlantı kurulamadı {0}"; diff --git a/src/main/java/org/postgresql/translation/messages_zh_CN.java b/src/main/java/org/postgresql/translation/messages_zh_CN.java index 81adfb9..688be2e 100644 --- a/src/main/java/org/postgresql/translation/messages_zh_CN.java +++ b/src/main/java/org/postgresql/translation/messages_zh_CN.java @@ -82,7 +82,7 @@ public class messages_zh_CN extends java.util.ResourceBundle { t[205] = "不能在 ResultSet 的第一笔数据之前呼叫 deleteRow()。"; t[214] = "The maximum field size must be a value greater than or equal to 0."; t[215] = "最大栏位容量必须大于或等于 0。"; - t[216] = "Fetch size must be a value greater to or equal to 0."; + t[216] = "Fetch size must be a value greater than or equal to 0."; t[217] = "数据读取笔数(fetch size)必须大于或等于 0。"; t[220] = "PostgreSQL LOBs can only index to: {0}"; t[221] = "PostgreSQL LOBs 仅能索引到:{0}"; diff --git a/src/main/java/org/postgresql/translation/messages_zh_TW.java b/src/main/java/org/postgresql/translation/messages_zh_TW.java index 1d9eb65..d68c803 100644 --- a/src/main/java/org/postgresql/translation/messages_zh_TW.java +++ b/src/main/java/org/postgresql/translation/messages_zh_TW.java @@ -82,7 +82,7 @@ public class messages_zh_TW extends java.util.ResourceBundle { t[205] = "不能在 ResultSet 的第一筆資料之前呼叫 deleteRow()。"; t[214] = "The maximum field size must be a value greater than or equal to 0."; t[215] = "最大欄位容量必須大於或等於 0。"; - t[216] = "Fetch size must be a value greater to or equal to 0."; + t[216] = "Fetch size must be a value greater than or equal to 0."; t[217] = "資料讀取筆數(fetch size)必須大於或等於 0。"; t[220] = "PostgreSQL LOBs can only index to: {0}"; t[221] = "PostgreSQL LOBs 僅能索引到:{0}"; diff --git a/src/main/java/org/postgresql/translation/nl.po b/src/main/java/org/postgresql/translation/nl.po index 5de5f8b..4c12cc4 100644 --- a/src/main/java/org/postgresql/translation/nl.po +++ b/src/main/java/org/postgresql/translation/nl.po @@ -855,7 +855,7 @@ msgstr "" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/pl.po b/src/main/java/org/postgresql/translation/pl.po index 6d6a87c..7fe82cb 100644 --- a/src/main/java/org/postgresql/translation/pl.po +++ b/src/main/java/org/postgresql/translation/pl.po @@ -872,7 +872,7 @@ msgstr "" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "Rozmiar pobierania musi być wartością dodatnią lub 0." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/pt_BR.po b/src/main/java/org/postgresql/translation/pt_BR.po index 9d5450d..73a4f30 100644 --- a/src/main/java/org/postgresql/translation/pt_BR.po +++ b/src/main/java/org/postgresql/translation/pt_BR.po @@ -901,7 +901,7 @@ msgstr "Não foi possível traduzir dado para codificação desejada." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "Tamanho da busca deve ser um valor maior ou igual a 0." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/ru.po b/src/main/java/org/postgresql/translation/ru.po index 5ceadc8..4be692f 100644 --- a/src/main/java/org/postgresql/translation/ru.po +++ b/src/main/java/org/postgresql/translation/ru.po @@ -907,7 +907,7 @@ msgstr "" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/sr.po b/src/main/java/org/postgresql/translation/sr.po index 607ea03..59efc77 100644 --- a/src/main/java/org/postgresql/translation/sr.po +++ b/src/main/java/org/postgresql/translation/sr.po @@ -894,7 +894,7 @@ msgstr "Nije moguće prevesti podatke u odabrani encoding format." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "Doneta veličina mora biti vrednost veća ili jednaka 0." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/tr.po b/src/main/java/org/postgresql/translation/tr.po index 0c19ed0..6e337d1 100644 --- a/src/main/java/org/postgresql/translation/tr.po +++ b/src/main/java/org/postgresql/translation/tr.po @@ -880,7 +880,7 @@ msgstr "Veri, istenilen dil kodlamasına çevrilemiyor." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "Fetch boyutu sıfır veya daha büyük bir değer olmalıdır." #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/zh_CN.po b/src/main/java/org/postgresql/translation/zh_CN.po index e4ed574..a723533 100644 --- a/src/main/java/org/postgresql/translation/zh_CN.po +++ b/src/main/java/org/postgresql/translation/zh_CN.po @@ -852,7 +852,7 @@ msgstr "无法将数据转成目标编码。" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "数据读取笔数(fetch size)必须大于或等于 0。" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/translation/zh_TW.po b/src/main/java/org/postgresql/translation/zh_TW.po index 2c33128..5392ec6 100644 --- a/src/main/java/org/postgresql/translation/zh_TW.po +++ b/src/main/java/org/postgresql/translation/zh_TW.po @@ -852,7 +852,7 @@ msgstr "無法將資料轉成目標編碼。" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1105 #: src/main/java/org/postgresql/jdbc/PgResultSet.java:1783 #: src/main/java/org/postgresql/jdbc/PgStatement.java:929 -msgid "Fetch size must be a value greater to or equal to 0." +msgid "Fetch size must be a value greater than or equal to 0." msgstr "資料讀取筆數(fetch size)必須大於或等於 0。" #: src/main/java/org/postgresql/jdbc/PgConnection.java:1384 diff --git a/src/main/java/org/postgresql/util/ByteBufferByteStreamWriter.java b/src/main/java/org/postgresql/util/ByteBufferByteStreamWriter.java index be25be5..f0a8aa0 100644 --- a/src/main/java/org/postgresql/util/ByteBufferByteStreamWriter.java +++ b/src/main/java/org/postgresql/util/ByteBufferByteStreamWriter.java @@ -36,13 +36,17 @@ public int getLength() { @Override public void writeTo(ByteStreamTarget target) throws IOException { + if (buf.hasArray()) { + // Avoid copying the array if possible + target.getOutputStream() + .write(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); + return; + } + // this _does_ involve some copying to a temporary buffer, but that's unavoidable // as OutputStream itself only accepts single bytes or heap allocated byte arrays - WritableByteChannel c = Channels.newChannel(target.getOutputStream()); - try { + try (WritableByteChannel c = Channels.newChannel(target.getOutputStream())) { c.write(buf); - } finally { - c.close(); } } } diff --git a/src/main/java/org/postgresql/util/ByteBuffersByteStreamWriter.java b/src/main/java/org/postgresql/util/ByteBuffersByteStreamWriter.java new file mode 100644 index 0000000..9edde9f --- /dev/null +++ b/src/main/java/org/postgresql/util/ByteBuffersByteStreamWriter.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.WritableByteChannel; + +/** + * A {@link ByteStreamWriter} that writes a {@link ByteBuffer java.nio.ByteBuffer} to a byte array + * parameter. + */ +class ByteBuffersByteStreamWriter implements ByteStreamWriter { + + private final ByteBuffer[] buffers; + private final int length; + + /** + * Construct the writer with the given {@link ByteBuffer} + * + * @param buffers the buffer to use. + */ + ByteBuffersByteStreamWriter(ByteBuffer... buffers) { + this.buffers = buffers; + int length = 0; + for (ByteBuffer buffer : buffers) { + length += buffer.remaining(); + } + this.length = length; + } + + @Override + public int getLength() { + return length; + } + + @Override + public void writeTo(ByteStreamTarget target) throws IOException { + boolean allArraysAreAccessible = true; + for (ByteBuffer buffer : buffers) { + if (!buffer.hasArray()) { + allArraysAreAccessible = false; + break; + } + } + + OutputStream os = target.getOutputStream(); + if (allArraysAreAccessible) { + for (ByteBuffer buffer : buffers) { + os.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); + } + return; + } + // Channels.newChannel does not buffer writes, so we can mix writes to the channel with writes + // to the OutputStream + try (WritableByteChannel c = Channels.newChannel(os)) { + for (ByteBuffer buffer : buffers) { + if (buffer.hasArray()) { + os.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); + } else { + c.write(buffer); + } + } + } + } +} diff --git a/src/main/java/org/postgresql/util/ByteConverter.java b/src/main/java/org/postgresql/util/ByteConverter.java index d2cc478..2a216e2 100644 --- a/src/main/java/org/postgresql/util/ByteConverter.java +++ b/src/main/java/org/postgresql/util/ByteConverter.java @@ -22,12 +22,12 @@ public class ByteConverter { */ private static final class PositiveShorts { private short[] shorts = new short[8]; - private int idx = 0; + private int idx; PositiveShorts() { } - public void push(short s) { + void push(short s) { if (s < 0) { throw new IllegalArgumentException("only non-negative values accepted: " + s); } @@ -37,15 +37,15 @@ public void push(short s) { shorts[idx++] = s; } - public int size() { + int size() { return idx; } - public boolean isEmpty() { + boolean isEmpty() { return idx == 0; } - public short pop() { + short pop() { return idx > 0 ? shorts[--idx] : -1; } @@ -59,6 +59,8 @@ private void grow() { private static final short NUMERIC_POS = 0x0000; private static final short NUMERIC_NEG = 0x4000; private static final short NUMERIC_NAN = (short) 0xC000; + private static final short NUMERIC_PINF = (short) 0xD000; + private static final short NUMERIC_NINF = (short) 0xF000; private static final int SHORT_BYTES = 2; private static final int LONG_BYTES = 4; private static final int[] INT_TEN_POWERS = new int[6]; @@ -68,13 +70,13 @@ private void grow() { private static final BigInteger BI_MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); static { - for (int i = 0; i < INT_TEN_POWERS.length; ++i) { + for (int i = 0; i < INT_TEN_POWERS.length; i++) { INT_TEN_POWERS[i] = (int) Math.pow(10, i); } - for (int i = 0; i < LONG_TEN_POWERS.length; ++i) { + for (int i = 0; i < LONG_TEN_POWERS.length; i++) { LONG_TEN_POWERS[i] = (long) Math.pow(10, i); } - for (int i = 0; i < BI_TEN_POWERS.length; ++i) { + for (int i = 0; i < BI_TEN_POWERS.length; i++) { BI_TEN_POWERS[i] = BigInteger.TEN.pow(i); } } @@ -90,7 +92,7 @@ private ByteConverter() { */ public static int bytesToInt(byte []bytes) { if ( bytes.length == 1 ) { - return (int)bytes[0]; + return (int) bytes[0]; } if ( bytes.length == SHORT_BYTES ) { return int2(bytes, 0); @@ -113,13 +115,14 @@ public static Number numeric(byte [] bytes) { /** * Convert a variable length array of bytes to a {@link Number}. The result will - * always be a {@link BigDecimal} or {@link Double#NaN}. + * always be a {@link BigDecimal} or a special {@link Double} value. * * @param bytes array of bytes to be decoded from binary numeric representation. * @param pos index of the start position of the bytes array for number * @param numBytes number of bytes to use, length is already encoded * in the binary format but this is used for double checking - * @return BigDecimal representation of numeric or {@link Double#NaN}. + * @return BigDecimal representation of numeric or one of the special double values + * {@link Double#NaN}, {@link Double#NEGATIVE_INFINITY} and {@link Double#POSITIVE_INFINITY}. */ public static Number numeric(byte [] bytes, int pos, int numBytes) { @@ -127,12 +130,12 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { throw new IllegalArgumentException("number of bytes should be at-least 8"); } - //number of 2-byte shorts representing 4 decimal digits - short len = ByteConverter.int2(bytes, pos); + //number of 2-byte shorts representing 4 decimal digits - should be treated as unsigned + int len = ByteConverter.int2(bytes, pos) & 0xFFFF; //0 based number of 4 decimal digits (i.e. 2-byte shorts) before the decimal //a value <= 0 indicates an absolute value < 1. short weight = ByteConverter.int2(bytes, pos + 2); - //indicates positive, negative or NaN + //indicates positive, negative, NaN, Infinity or -Infinity short sign = ByteConverter.int2(bytes, pos + 4); //number of digits after the decimal. This must be >= 0. //a value of 0 indicates a whole number (integer). @@ -157,14 +160,18 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { throw new IllegalArgumentException("invalid length of bytes \"numeric\" value"); } - if (!(sign == NUMERIC_POS - || sign == NUMERIC_NEG - || sign == NUMERIC_NAN)) { - throw new IllegalArgumentException("invalid sign in \"numeric\" value"); - } - - if (sign == NUMERIC_NAN) { - return Double.NaN; + switch (sign) { + case NUMERIC_POS: + case NUMERIC_NEG: + break; + case NUMERIC_NAN: + return Double.NaN; + case NUMERIC_PINF: + return Double.POSITIVE_INFINITY; + case NUMERIC_NINF: + return Double.NEGATIVE_INFINITY; + default: + throw new IllegalArgumentException("invalid sign in \"numeric\" value"); } if ((scale & NUMERIC_DSCALE_MASK) != scale) { @@ -184,35 +191,45 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { if (weight < 0) { assert scale > 0; int effectiveScale = scale; + //adjust weight to determine how many leading 0s after the decimal + //before the provided values/digits actually begin ++weight; if (weight < 0) { - effectiveScale += (4 * weight); + effectiveScale += 4 * weight; } int i = 1; //typically there should not be leading 0 short values, as it is more //efficient to represent that in the weight value - for ( ; i < len && d == 0; ++i) { + for (; i < len && d == 0; i++) { + //each leading 0 value removes 4 from the effective scale effectiveScale -= 4; idx += 2; d = ByteConverter.int2(bytes, idx); } - BigInteger unscaledBI = null; assert effectiveScale > 0; if (effectiveScale >= 4) { effectiveScale -= 4; } else { + //an effective scale of less than four means that the value d + //has trailing 0s which are not significant + //so we divide by the appropriate power of 10 to reduce those d = (short) (d / INT_TEN_POWERS[4 - effectiveScale]); effectiveScale = 0; } + //defer moving to BigInteger as long as possible + //operations on the long are much faster + BigInteger unscaledBI = null; long unscaledInt = d; - for ( ; i < len; ++i) { + for (; i < len; i++) { if (i == 4 && effectiveScale > 2) { unscaledBI = BigInteger.valueOf(unscaledInt); } idx += 2; d = ByteConverter.int2(bytes, idx); + //if effective scale is at least 4, then all 4 digits should be used + //and the existing number needs to be shifted 4 if (effectiveScale >= 4) { if (unscaledBI == null) { unscaledInt *= 10000; @@ -221,11 +238,14 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { } effectiveScale -= 4; } else { + //if effective scale is less than 4, then only shift left based on remaining scale if (unscaledBI == null) { unscaledInt *= INT_TEN_POWERS[effectiveScale]; } else { unscaledBI = unscaledBI.multiply(tenPower(effectiveScale)); } + //and d needs to be shifted to the right to only get correct number of + //significant digits d = (short) (d / INT_TEN_POWERS[4 - effectiveScale]); effectiveScale = 0; } @@ -237,9 +257,11 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { } } } + //now we need BigInteger to create BigDecimal if (unscaledBI == null) { unscaledBI = BigInteger.valueOf(unscaledInt); } + //if there is remaining effective scale, apply it here if (effectiveScale > 0) { unscaledBI = unscaledBI.multiply(tenPower(effectiveScale)); } @@ -252,9 +274,12 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { //if there is no scale, then shorts are the unscaled int if (scale == 0) { + //defer moving to BigInteger as long as possible + //operations on the long are much faster BigInteger unscaledBI = null; long unscaledInt = d; - for (int i = 1; i < len; ++i) { + //loop over all of the len shorts to process as the unscaled int + for (int i = 1; i < len; i++) { if (i == 4) { unscaledBI = BigInteger.valueOf(unscaledInt); } @@ -270,12 +295,14 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { } } } + //now we need BigInteger to create BigDecimal if (unscaledBI == null) { unscaledBI = BigInteger.valueOf(unscaledInt); } if (sign == NUMERIC_NEG) { unscaledBI = unscaledBI.negate(); } + //the difference between len and weight (adjusted from 0 based) becomes the scale for BigDecimal final int bigDecScale = (len - (weight + 1)) * 4; //string representation always results in a BigDecimal with scale of 0 //the binary representation, where weight and len can infer trailing 0s, can result in a negative scale @@ -283,16 +310,21 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { return bigDecScale == 0 ? new BigDecimal(unscaledBI) : new BigDecimal(unscaledBI, bigDecScale).setScale(0); } + //defer moving to BigInteger as long as possible + //operations on the long are much faster BigInteger unscaledBI = null; long unscaledInt = d; + //weight and scale as defined by postgresql are a bit different than how BigDecimal treats scale + //maintain the effective values to massage as we process through values int effectiveWeight = weight; int effectiveScale = scale; - for (int i = 1 ; i < len; ++i) { + for (int i = 1; i < len; i++) { if (i == 4) { unscaledBI = BigInteger.valueOf(unscaledInt); } idx += 2; d = ByteConverter.int2(bytes, idx); + //first process effective weight down to 0 if (effectiveWeight > 0) { --effectiveWeight; if (unscaledBI == null) { @@ -301,6 +333,8 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { unscaledBI = unscaledBI.multiply(BI_TEN_THOUSAND); } } else if (effectiveScale >= 4) { + //if effective scale is at least 4, then all 4 digits should be used + //and the existing number needs to be shifted 4 effectiveScale -= 4; if (unscaledBI == null) { unscaledInt *= 10000; @@ -308,11 +342,14 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { unscaledBI = unscaledBI.multiply(BI_TEN_THOUSAND); } } else { + //if effective scale is less than 4, then only shift left based on remaining scale if (unscaledBI == null) { unscaledInt *= INT_TEN_POWERS[effectiveScale]; } else { unscaledBI = unscaledBI.multiply(tenPower(effectiveScale)); } + //and d needs to be shifted to the right to only get correct number of + //significant digits d = (short) (d / INT_TEN_POWERS[4 - effectiveScale]); effectiveScale = 0; } @@ -325,12 +362,15 @@ public static Number numeric(byte [] bytes, int pos, int numBytes) { } } + //now we need BigInteger to create BigDecimal if (unscaledBI == null) { unscaledBI = BigInteger.valueOf(unscaledInt); } + //if there is remaining weight, apply it here if (effectiveWeight > 0) { unscaledBI = unscaledBI.multiply(tenPower(effectiveWeight * 4)); } + //if there is remaining effective scale, apply it here if (effectiveScale > 0) { unscaledBI = unscaledBI.multiply(tenPower(effectiveScale)); } @@ -351,7 +391,7 @@ public static byte[] numeric(BigDecimal nbr) { BigInteger unscaled = nbr.unscaledValue().abs(); int scale = nbr.scale(); if (unscaled.equals(BigInteger.ZERO)) { - final byte[] bytes = new byte[] {0,0,-1,-1,0,0,0,0}; + final byte[] bytes = new byte[]{0, 0, -1, -1, 0, 0, 0, 0}; ByteConverter.int2(bytes, 6, Math.max(0, scale)); return bytes; } @@ -415,7 +455,7 @@ public static byte[] numeric(BigDecimal nbr) { weight -= segments; } else { //now add leading 0 shorts - for (int i = 0; i < segments; ++i) { + for (int i = 0; i < segments; i++) { shorts.push((short) 0); } } @@ -493,7 +533,7 @@ public static int int4(byte[] bytes, int idx) { ((bytes[idx] & 255) << 24) + ((bytes[idx + 1] & 255) << 16) + ((bytes[idx + 2] & 255) << 8) - + ((bytes[idx + 3] & 255)); + + (bytes[idx + 3] & 255); } /** @@ -504,7 +544,7 @@ public static int int4(byte[] bytes, int idx) { * @return parsed short value. */ public static short int2(byte[] bytes, int idx) { - return (short) (((bytes[idx] & 255) << 8) + ((bytes[idx + 1] & 255))); + return (short) (((bytes[idx] & 255) << 8) + (bytes[idx + 1] & 255)); } /** diff --git a/src/main/java/org/postgresql/util/ByteStreamWriter.java b/src/main/java/org/postgresql/util/ByteStreamWriter.java index 798f938..381a06f 100644 --- a/src/main/java/org/postgresql/util/ByteStreamWriter.java +++ b/src/main/java/org/postgresql/util/ByteStreamWriter.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; /** * A class that can be used to set a byte array parameter by writing to an OutputStream. @@ -15,16 +16,20 @@ *

The intended use case is wanting to write data to a byte array parameter that is stored off * heap in a direct memory pool or in some other form that is inconvenient to assemble into a single * heap-allocated buffer.

- *

Users should write their own implementation depending on the + * + *

Users should write their own implementation depending on the * original data source. The driver provides a built-in implementation supporting the {@link * java.nio.ByteBuffer} class, see {@link ByteBufferByteStreamWriter}.

- *

Intended usage is to simply pass in an instance using + * + *

Intended usage is to simply pass in an instance using * {@link java.sql.PreparedStatement#setObject(int, Object)}:

*
  *     int bufLength = someBufferObject.length();
  *     preparedStatement.setObject(1, new MyByteStreamWriter(bufLength, someBufferObject));
  * 
+ * *

The length must be known ahead of the stream being written to.

+ * *

This provides the application more control over memory management than calling * {@link java.sql.PreparedStatement#setBinaryStream(int, InputStream)} as with the latter the * caller has no control over the buffering strategy.

@@ -34,7 +39,7 @@ public interface ByteStreamWriter { /** * Returns the length of the stream. * - *

This must be known ahead of calling {@link #writeTo(ByteStreamTarget)}.

+ *

This must be known ahead of calling {@link #writeTo(ByteStreamTarget)}.

* * @return the number of bytes in the stream. */ @@ -43,7 +48,7 @@ public interface ByteStreamWriter { /** * Write the data to the provided {@link OutputStream}. * - *

Should not write more than {@link #getLength()} bytes. If attempted, the provided stream + *

Should not write more than {@link #getLength()} bytes. If attempted, the provided stream * will throw an {@link java.io.IOException}.

* * @param target the stream to write the data to @@ -51,6 +56,12 @@ public interface ByteStreamWriter { */ void writeTo(ByteStreamTarget target) throws IOException; + static ByteStreamWriter of(ByteBuffer... buf) { + return buf.length == 1 + ? new ByteBufferByteStreamWriter(buf[0]) + : new ByteBuffersByteStreamWriter(buf); + } + /** * Provides a target to write bytes to. */ diff --git a/src/main/java/org/postgresql/util/ClassUtils.java b/src/main/java/org/postgresql/util/ClassUtils.java new file mode 100644 index 0000000..78cc805 --- /dev/null +++ b/src/main/java/org/postgresql/util/ClassUtils.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2026, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +// import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Utility class for safe class loading operations. + */ +public final class ClassUtils { + + private ClassUtils() { + // Utility class + } + + /** + * Safely loads a class using the three-parameter Class.forName with initialize=false + * and validates it's assignable to the expected type. + * + * @param className the name of the class to load + * @param expectedClass the expected superclass or interface + * @param classLoader the class loader to use + * @param the expected type + * @return the loaded class as a subclass of the expected type + * @throws ClassNotFoundException if the class cannot be found + */ + public static Class forName(String className, Class expectedClass, /* @Nullable */ ClassLoader classLoader) + throws ClassNotFoundException { + return Class.forName(className, false, classLoader).asSubclass(expectedClass); + } +} diff --git a/src/main/java/org/postgresql/util/DriverInfo.java b/src/main/java/org/postgresql/util/DriverInfo.java index 6559234..c003b55 100644 --- a/src/main/java/org/postgresql/util/DriverInfo.java +++ b/src/main/java/org/postgresql/util/DriverInfo.java @@ -16,13 +16,13 @@ private DriverInfo() { // Driver name public static final String DRIVER_NAME = "PostgreSQL JDBC Driver"; public static final String DRIVER_SHORT_NAME = "PgJDBC"; - public static final String DRIVER_VERSION = "42.3.1"; + public static final String DRIVER_VERSION = "42.7.11"; public static final String DRIVER_FULL_NAME = DRIVER_NAME + " " + DRIVER_VERSION; // Driver version public static final int MAJOR_VERSION = 42; - public static final int MINOR_VERSION = 3; - public static final int PATCH_VERSION = 1; + public static final int MINOR_VERSION = 7; + public static final int PATCH_VERSION = 11; // JDBC specification public static final String JDBC_VERSION = "4.2"; diff --git a/src/main/java/org/postgresql/util/ExpressionProperties.java b/src/main/java/org/postgresql/util/ExpressionProperties.java index bf8a7c0..3fe46c6 100644 --- a/src/main/java/org/postgresql/util/ExpressionProperties.java +++ b/src/main/java/org/postgresql/util/ExpressionProperties.java @@ -19,6 +19,7 @@ public class ExpressionProperties extends Properties { private static final /* @Regex(1) */ Pattern EXPRESSION = Pattern.compile("\\$\\{([^}]+)\\}"); + @SuppressWarnings("HidingField") private final Properties[] defaults; /** @@ -31,8 +32,8 @@ public ExpressionProperties(Properties ...defaults) { } /** - *

Returns property value with all {@code ${propKey}} like references replaced with the value of - * the relevant property with recursive resolution.

+ * Returns property value with all {@code ${propKey}} like references replaced with the value of + * the relevant property with recursive resolution. * *

The method returns null if the property is not found.

* diff --git a/src/main/java/org/postgresql/util/HStoreConverter.java b/src/main/java/org/postgresql/util/HStoreConverter.java index 2a112bc..02928d4 100644 --- a/src/main/java/org/postgresql/util/HStoreConverter.java +++ b/src/main/java/org/postgresql/util/HStoreConverter.java @@ -24,7 +24,7 @@ public class HStoreConverter { int numElements = ByteConverter.int4(b, pos); pos += 4; try { - for (int i = 0; i < numElements; ++i) { + for (int i = 0; i < numElements; i++) { int keyLen = ByteConverter.int4(b, pos); pos += 4; String key = encoding.decode(b, pos, keyLen); diff --git a/src/main/java/org/postgresql/util/HostSpec.java b/src/main/java/org/postgresql/util/HostSpec.java index 6858a9f..1b9d877 100644 --- a/src/main/java/org/postgresql/util/HostSpec.java +++ b/src/main/java/org/postgresql/util/HostSpec.java @@ -9,6 +9,7 @@ // import org.checkerframework.checker.nullness.qual.Nullable; +import java.util.Locale; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -41,6 +42,7 @@ public int getPort() { return port; } + @Override public String toString() { return host + ":" + port; } @@ -80,12 +82,12 @@ private Boolean matchesNonProxyHosts() { } @SuppressWarnings("regex") - private /* @Nullable */ Pattern toPattern(String mask) { + private static /* @Nullable */ Pattern toPattern(String mask) { StringBuilder joiner = new StringBuilder(); String separator = ""; for (String disjunct : mask.split("\\|")) { if (!disjunct.isEmpty()) { - String regex = disjunctToRegex(disjunct.toLowerCase()); + String regex = disjunctToRegex(disjunct.toLowerCase(Locale.ROOT)); joiner.append(separator).append(regex); separator = "|"; } @@ -94,7 +96,7 @@ private Boolean matchesNonProxyHosts() { return joiner.length() == 0 ? null : compile(joiner.toString()); } - private String disjunctToRegex(String disjunct) { + private static String disjunctToRegex(String disjunct) { String regex; if (disjunct.startsWith("*")) { diff --git a/src/main/java/org/postgresql/util/IntList.java b/src/main/java/org/postgresql/util/IntList.java new file mode 100644 index 0000000..7e701ed --- /dev/null +++ b/src/main/java/org/postgresql/util/IntList.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import java.util.Arrays; + +/** + * A specialized class to store a list of {@code int} values, so it does not need auto-boxing. Note: + * this is a driver-internal class, and it is not intended to be used outside the driver. + */ +public final class IntList { + private static final int[] EMPTY_INT_ARRAY = new int[0]; + private int[] ints = EMPTY_INT_ARRAY; + private int size; + + public void add(int i) { + int size = this.size; + ensureCapacity(size); + ints[size] = i; + this.size = size + 1; + } + + private void ensureCapacity(int size) { + int length = ints.length; + if (size >= length) { + // double in size until 1024 in size, then grow by 1.5x + final int newLength = length == 0 ? 8 : + length < 1024 ? length << 1 : + (length + (length >> 1)); + ints = Arrays.copyOf(ints, newLength); + } + } + + public int size() { + return size; + } + + public int get(int i) { + if (i < 0 || i >= size) { + throw new ArrayIndexOutOfBoundsException("Index: " + i + ", Size: " + size); + } + return ints[i]; + } + + public void clear() { + size = 0; + } + + /** + * Returns an array containing all the elements in this list. The modifications of the returned + * array will not affect this list. + * + * @return an array containing all the elements in this list + */ + public int[] toArray() { + if (size == 0) { + return EMPTY_INT_ARRAY; + } + return Arrays.copyOf(ints, size); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < size; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append(ints[i]); + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/src/main/java/org/postgresql/util/KerberosTicket.java b/src/main/java/org/postgresql/util/KerberosTicket.java new file mode 100644 index 0000000..096c51e --- /dev/null +++ b/src/main/java/org/postgresql/util/KerberosTicket.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +public class KerberosTicket { + + private static final String CONFIG_ITEM_NAME = "ticketCache"; + private static final String KRBLOGIN_MODULE = "com.sun.security.auth.module.Krb5LoginModule"; + + static class CustomKrbConfig extends Configuration { + + @SuppressWarnings("nullness") + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + if (CONFIG_ITEM_NAME.equals(name)) { + Map options = new HashMap<>(); + options.put("refreshKrb5Config", String.valueOf(false)); + options.put("useTicketCache", String.valueOf(true)); + options.put("doNotPrompt", String.valueOf(true)); + options.put("useKeyTab", String.valueOf(true)); + options.put("isInitiator", String.valueOf(false)); + options.put("renewTGT", String.valueOf(false)); + options.put("debug", String.valueOf(false)); + return new AppConfigurationEntry[]{ + new AppConfigurationEntry(KRBLOGIN_MODULE, + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options)}; + } + return null; + } + + } + + public static boolean credentialCacheExists(Properties info) { + LoginContext lc = null; + + // in the event that the user has specified a jaas.conf file then we want to remember it + Configuration existingConfiguration = Configuration.getConfiguration(); + Configuration.setConfiguration(new CustomKrbConfig()); + + try { + lc = new LoginContext(CONFIG_ITEM_NAME, new CallbackHandler() { + + @Override + public void handle(Callback[] callbacks) + throws IOException, UnsupportedCallbackException { + // if the user has not configured jaasLogin correctly this can happen + throw new RuntimeException("This is an error, you should set doNotPrompt to false in jaas.config"); + } + }); + lc.login(); + } catch (LoginException e) { + // restore saved configuration + if (existingConfiguration != null ) { + Configuration.setConfiguration(existingConfiguration); + } + return false; + } + // restore saved configuration + if (existingConfiguration != null ) { + Configuration.setConfiguration(existingConfiguration); + } + Subject sub = lc.getSubject(); + return sub != null; + } +} diff --git a/src/main/java/org/postgresql/util/LazyCleaner.java b/src/main/java/org/postgresql/util/LazyCleaner.java new file mode 100644 index 0000000..24b9b46 --- /dev/null +++ b/src/main/java/org/postgresql/util/LazyCleaner.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +public interface LazyCleaner { + /** + * Cleanable interface for objects that can be manually cleaned. + * + * @param the type of exception that can be thrown during cleanup + */ + interface Cleanable { + void clean() throws T; + } + + /** + * CleaningAction interface for cleanup actions that are notified whether cleanup + * occurred due to a leak (automatic) or manual cleanup. + * + * @param the type of exception that can be thrown during cleanup + */ + interface CleaningAction { + void onClean(boolean leak) throws T; + } + + /** + * Registers an object for cleanup when it becomes phantom reachable. + * + * @param obj the object to monitor for cleanup (should not be the same as action) + * @param action the action to perform when the object becomes unreachable + * @param the type of exception that can be thrown during cleanup + * @return a Cleanable that can be used to manually trigger cleanup + */ + Cleanable register(Object obj, CleaningAction action); +} diff --git a/src/main/java/org/postgresql/util/LazyCleanerImpl.java b/src/main/java/org/postgresql/util/LazyCleanerImpl.java new file mode 100644 index 0000000..5f5f6a1 --- /dev/null +++ b/src/main/java/org/postgresql/util/LazyCleanerImpl.java @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +/* changes were made to move it into the org.postgresql.util package + * + * Copyright 2022 Juan Lopes + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.postgresql.util; + +// import org.checkerframework.checker.nullness.qual.Nullable; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.time.Duration; +import java.util.concurrent.ForkJoinPool; +import java.util.function.BooleanSupplier; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * LazyCleaner is a utility class that allows to register objects for deferred cleanup. + * + *

This is the Java 8 compatible implementation that uses PhantomReferences + * and ForkJoinPool for deferred cleanup operations.

+ * + *

On Java 11+, this class is replaced by a version that uses the native + * {@link java.lang.ref.Cleaner} API via the multi-release JAR mechanism.

+ * + *

Note: this is a driver-internal class

+ */ +public class LazyCleanerImpl implements LazyCleaner { + private static final Logger LOGGER = Logger.getLogger(LazyCleanerImpl.class.getName()); + private static final LazyCleanerImpl instance = new LazyCleanerImpl( + "PostgreSQL-JDBC-Cleaner", + Duration.ofMillis(Long.getLong("pgjdbc.config.cleanup.thread.ttl", 30000)) + ); + + private final ReferenceQueue queue = new ReferenceQueue<>(); + private final String threadName; + private final Duration threadTtl; + private boolean threadRunning; + private /* @Nullable */ Node first; + + /** + * Creates a LazyCleaner with the specified configuration. + * + * @param threadName the name for the cleanup thread + * @param threadTtl the maximum time the cleanup thread will wait for references + */ + public LazyCleanerImpl(String threadName, Duration threadTtl) { + this.threadName = threadName; + this.threadTtl = threadTtl; + } + + /** + * Returns a default cleaner instance. + * + *

Note: this is driver-internal API.

+ * @return the instance of LazyCleaner + */ + public static LazyCleanerImpl getInstance() { + return instance; + } + + @Override + public Cleanable register(Object obj, CleaningAction action) { + assert obj != action : "object handle should not be the same as cleaning action, otherwise" + + " the object will never become phantom reachable, so the action will never trigger"; + return add(new Node<>(obj, action)); + } + + /** + * Returns whether the cleanup thread is currently running. + * + * @return true if cleanup operations are active + */ + public synchronized boolean isThreadRunning() { + return threadRunning; + } + + private synchronized boolean checkEmpty() { + if (first == null) { + threadRunning = false; + return true; + } + return false; + } + + private synchronized Node add(Node node) { + if (first != null) { + node.next = first; + first.prev = node; + } + first = node; + + if (!threadRunning) { + threadRunning = startThread(); + } + return node; + } + + /** + * RefQueueBlocker retrieves references from the reference queue without blocking ForkJoinPool + * CPU threads. + * + * @param the type of the objects referenced by the {@link Reference}s in the queue + */ + private static class RefQueueBlocker implements ForkJoinPool.ManagedBlocker { + private final ReferenceQueue queue; + private final String threadName; + private /* @Nullable */ Reference ref; + private final long blockTimeoutMillis; + private final BooleanSupplier shouldTerminate; + + RefQueueBlocker(ReferenceQueue queue, String threadName, Duration blockTimeout, BooleanSupplier shouldTerminate) { + this.queue = queue; + this.threadName = threadName; + this.blockTimeoutMillis = blockTimeout.toMillis(); + this.shouldTerminate = shouldTerminate; + } + + @Override + public boolean isReleasable() { + if (ref != null || shouldTerminate.getAsBoolean()) { + return true; // already have a ref from a previous call + } + // non-blocking check + ref = queue.poll(); + // no need to block if we already have a ref from a previous call + return ref != null; + } + + @Override + public boolean block() throws InterruptedException { + if (isReleasable()) { + return true; + } + Thread currentThread = Thread.currentThread(); + // ForkJoinPool reuses threads, so we set the thread name just for the blocking operation + String oldName = currentThread.getName(); + try { + currentThread.setName(threadName); + // Perform blocking operation + ref = queue.remove(blockTimeoutMillis); + } finally { + currentThread.setName(oldName); + } + return false; + } + + /* @Nullable */ Reference drainOne() { + Reference ref = this.ref; + this.ref = null; + return ref; + } + } + + private boolean startThread() { + // We use ForkJoinPool to work around Thread.inheritedAccessControlContext memory leak + // Java creates FJP threads without caller's access control context, thus we reduce + // the surface for the leak. + ForkJoinPool.commonPool().execute( + () -> { + // ForkJoinPool does not inherit contextClassLoader from the submitting thread, + // however custom thread factories might, so we try nullifying it to avoid + // classloader leaks. InnocuousForkJoinWorkerThread forbids setContextClassLoader, + // so we catch SecurityException to avoid crashing the cleanup thread. + // See https://github.com/pgjdbc/pgjdbc/issues/3953 + try { + Thread.currentThread().setContextClassLoader(null); + } catch (SecurityException ignore) { + // InnocuousForkJoinWorkerThread or a SecurityManager forbids setContextClassLoader + } + RefQueueBlocker blocker = + new RefQueueBlocker<>(queue, threadName, threadTtl, this::checkEmpty); + while (!checkEmpty()) { + try { + ForkJoinPool.managedBlock(blocker); + Node ref = (Node) blocker.drainOne(); + if (ref != null) { + ref.onClean(true); + } + } catch (InterruptedException e) { + if (!blocker.isReleasable()) { + LOGGER.log(Level.FINE, "Got interrupt and the cleanup queue is empty, will terminate the cleanup thread"); + break; + } + LOGGER.log(Level.FINE, "Got interrupt and the cleanup queue is NOT empty. Will ignore the interrupt"); + } catch (Throwable e) { + LOGGER.log(Level.WARNING, "Unexpected exception while executing onClean", e); + } + } + } + ); + return true; + } + + private synchronized boolean remove(Node node) { + // If already removed, do nothing + if (node.next == node) { + return false; + } + + // Update list + if (first == node) { + first = node.next; + } + if (node.next != null) { + node.next.prev = node.prev; + } + if (node.prev != null) { + node.prev.next = node.next; + } + + // Indicate removal by pointing the cleaner to itself + node.next = node; + node.prev = node; + + return true; + } + + private class Node extends PhantomReference implements Cleanable, + CleaningAction { + private final CleaningAction action; + private /* @Nullable */ Node prev; + private /* @Nullable */ Node next; + + Node(Object referent, CleaningAction action) { + super(referent, queue); + this.action = action; + //Objects.requireNonNull(referent); // poor man`s reachabilityFence + } + + @Override + public void clean() throws T { + onClean(false); + } + + @Override + public void onClean(boolean leak) throws T { + if (!remove(this)) { + return; + } + action.onClean(leak); + } + } +} diff --git a/src/main/java/org/postgresql/util/LogWriterHandler.java b/src/main/java/org/postgresql/util/LogWriterHandler.java index 181fc5e..2554437 100644 --- a/src/main/java/org/postgresql/util/LogWriterHandler.java +++ b/src/main/java/org/postgresql/util/LogWriterHandler.java @@ -5,6 +5,8 @@ package org.postgresql.util; +import org.postgresql.jdbc.ResourceLock; + // import org.checkerframework.checker.nullness.qual.Nullable; import java.io.Writer; @@ -18,9 +20,9 @@ public class LogWriterHandler extends Handler { private /* @Nullable */ Writer writer; - private final Object lock = new Object(); + private final ResourceLock lock = new ResourceLock(); - @SuppressWarnings("method.invocation.invalid") + @SuppressWarnings("method.invocation") public LogWriterHandler(Writer inWriter) { super(); setLevel(Level.INFO); @@ -45,7 +47,7 @@ public void publish(LogRecord record) { return; } try { - synchronized (lock) { + try (ResourceLock ignore = lock.obtain()) { Writer writer = this.writer; if (writer != null) { writer.write(formatted); @@ -58,7 +60,7 @@ public void publish(LogRecord record) { @Override public void flush() { - try { + try (ResourceLock ignore = lock.obtain()) { Writer writer = this.writer; if (writer != null) { writer.flush(); @@ -70,7 +72,7 @@ public void flush() { @Override public void close() throws SecurityException { - try { + try (ResourceLock ignore = lock.obtain()) { Writer writer = this.writer; if (writer != null) { writer.close(); @@ -81,15 +83,17 @@ public void close() throws SecurityException { } private void setWriter(Writer writer) throws IllegalArgumentException { - if (writer == null) { - throw new IllegalArgumentException("Writer cannot be null"); - } - this.writer = writer; + try (ResourceLock ignore = lock.obtain()) { + if (writer == null) { + throw new IllegalArgumentException("Writer cannot be null"); + } + this.writer = writer; - try { - writer.write(getFormatter().getHead(this)); - } catch ( Exception ex) { - reportError("Error writing head section", ex, ErrorManager.WRITE_FAILURE); + try { + writer.write(getFormatter().getHead(this)); + } catch (Exception ex) { + reportError("Error writing head section", ex, ErrorManager.WRITE_FAILURE); + } } } } diff --git a/src/main/java/org/postgresql/util/LruCache.java b/src/main/java/org/postgresql/util/LruCache.java index 6886afe..dcae769 100644 --- a/src/main/java/org/postgresql/util/LruCache.java +++ b/src/main/java/org/postgresql/util/LruCache.java @@ -15,6 +15,7 @@ /** * Caches values in simple least-recently-accessed order. */ +@SuppressWarnings("ExtendsObject") public class LruCache implements Gettable { /** @@ -103,8 +104,12 @@ public LruCache(int maxSizeEntries, long maxSizeBytes, boolean accessOrder, * @param key cache key * @return entry from cache or null if cache does not contain given key. */ - public synchronized /* @Nullable */ Value get(Key key) { - return cache.get(key); + @Override + public /* @Nullable */ Value get(Key key) { + Map cache = this.cache; + synchronized (cache) { + return cache.get(key); + } } /** @@ -114,16 +119,19 @@ public LruCache(int maxSizeEntries, long maxSizeBytes, boolean accessOrder, * @return entry from cache or newly created entry if cache does not contain given key. * @throws SQLException if entry creation fails */ - public synchronized Value borrow(Key key) throws SQLException { - Value value = cache.remove(key); - if (value == null) { - if (createAction == null) { - throw new UnsupportedOperationException("createAction == null, so can't create object"); + public Value borrow(Key key) throws SQLException { + Map cache = this.cache; + synchronized (cache) { + Value value = cache.remove(key); + if (value == null) { + if (createAction == null) { + throw new UnsupportedOperationException("createAction == null, so can't create object"); + } + return createAction.create(key); } - return createAction.create(key); + currentSize -= value.getSize(); + return value; } - currentSize -= value.getSize(); - return value; } /** @@ -132,23 +140,26 @@ public synchronized Value borrow(Key key) throws SQLException { * @param key key * @param value value */ - public synchronized void put(Key key, Value value) { - long valueSize = value.getSize(); - if (maxSizeBytes == 0 || maxSizeEntries == 0 || valueSize * 2 > maxSizeBytes) { - // Just destroy the value if cache is disabled or if entry would consume more than a half of - // the cache - evictValue(value); - return; - } - currentSize += valueSize; - /* @Nullable */ Value prev = cache.put(key, value); - if (prev == null) { - return; - } - // This should be a rare case - currentSize -= prev.getSize(); - if (prev != value) { - evictValue(prev); + public void put(Key key, Value value) { + Map cache = this.cache; + synchronized (cache) { + long valueSize = value.getSize(); + if (maxSizeBytes == 0 || maxSizeEntries == 0 || valueSize * 2 > maxSizeBytes) { + // Just destroy the value if cache is disabled or if entry would consume more than a half of + // the cache + evictValue(value); + return; + } + currentSize += valueSize; + /* @Nullable */ Value prev = cache.put(key, value); + if (prev == null) { + return; + } + // This should be a rare case + currentSize -= prev.getSize(); + if (prev != value) { + evictValue(prev); + } } } @@ -157,9 +168,12 @@ public synchronized void put(Key key, Value value) { * * @param m The map containing entries to put into the cache */ - public synchronized void putAll(Map m) { - for (Map.Entry entry : m.entrySet()) { - this.put(entry.getKey(), entry.getValue()); + public void putAll(Map m) { + Map cache = this.cache; + synchronized (cache) { + for (Map.Entry entry : m.entrySet()) { + this.put(entry.getKey(), entry.getValue()); + } } } } diff --git a/src/main/java/org/postgresql/util/MD5Digest.java b/src/main/java/org/postgresql/util/MD5Digest.java index 80c30c7..f0e586c 100644 --- a/src/main/java/org/postgresql/util/MD5Digest.java +++ b/src/main/java/org/postgresql/util/MD5Digest.java @@ -57,7 +57,7 @@ public static byte[] encode(byte[] user, byte[] password, byte[] salt) { /* * Turn 16-byte stream into a human-readable 32-byte hex string */ - private static void bytesToHex(byte[] bytes, byte[] hex, int offset) { + public static void bytesToHex(byte[] bytes, byte[] hex, int offset) { int pos = offset; for (int i = 0; i < 16; i++) { //bit twiddling converts to int, so just do it once here for both operations diff --git a/src/main/java/org/postgresql/util/NumberParser.java b/src/main/java/org/postgresql/util/NumberParser.java new file mode 100644 index 0000000..a41e3fb --- /dev/null +++ b/src/main/java/org/postgresql/util/NumberParser.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +/** + * Optimised byte[] to number parser. + */ +public class NumberParser { + @SuppressWarnings("StaticAssignmentOfThrowable") + private static final NumberFormatException FAST_NUMBER_FAILED = new NumberFormatException() { + @Override + public Throwable fillInStackTrace() { + return this; + } + }; + + private static final long MAX_LONG_DIV_TEN = Long.MAX_VALUE / 10; + + /** + * Optimised byte[] to number parser. This code does not handle null values, so the caller must do + * checkResultSet and handle null values prior to calling this function. Fraction part is + * discarded. + * + * @param bytes integer represented as a sequence of ASCII bytes + * @return The parsed number. + * @throws NumberFormatException If the number is invalid or the out of range for fast parsing. + * The value must then be parsed by another (less optimised) method. + */ + public static long getFastLong(byte[] bytes, long minVal, long maxVal) throws NumberFormatException { + int len = bytes.length; + if (len == 0) { + throw FAST_NUMBER_FAILED; + } + + boolean neg = bytes[0] == '-'; + + long val = 0; + int start = neg ? 1 : 0; + while (start < len) { + byte b = bytes[start++]; + if (b < '0' || b > '9') { + if (b == '.') { + if (neg && len == 2 || !neg && len == 1) { + // we have to check that string is not "." or "-." + throw FAST_NUMBER_FAILED; + } + // check that the rest of the buffer contains only digits + while (start < len) { + b = bytes[start++]; + if (b < '0' || b > '9') { + throw FAST_NUMBER_FAILED; + } + } + break; + } else { + throw FAST_NUMBER_FAILED; + } + } + + if (val <= MAX_LONG_DIV_TEN) { + val *= 10; + val += b - '0'; + } else { + throw FAST_NUMBER_FAILED; + } + } + + if (val < 0) { + // It is possible to get overflow in two situations: + // 1. for MIN_VALUE, because abs(MIN_VALUE)=MAX_VALUE+1. In this situation thanks to + // complement arithmetic we got correct result and shouldn't do anything with it. + // 2. for incorrect string, representing a number greater than MAX_VALUE, for example + // "9223372036854775809", it this case we have to throw exception + if (!(neg && val == Long.MIN_VALUE)) { + throw FAST_NUMBER_FAILED; + } + } else if (neg) { + val = -val; + } + + if (val < minVal || val > maxVal) { + throw FAST_NUMBER_FAILED; + } + return val; + } +} diff --git a/src/main/java/org/postgresql/util/OSUtil.java b/src/main/java/org/postgresql/util/OSUtil.java new file mode 100644 index 0000000..6e86c7d --- /dev/null +++ b/src/main/java/org/postgresql/util/OSUtil.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import java.io.File; +import java.util.Locale; + +/** + * Operating system specifics + */ +public class OSUtil { + + /** + * + * @return true if OS is windows + */ + public static boolean isWindows() { + return System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows"); + } + + /** + * + * @return OS specific root directory for user specific configurations + */ + public static String getUserConfigRootDirectory() { + if (isWindows()) { + return System.getenv("APPDATA") + File.separator + "postgresql"; + } else { + return System.getProperty("user.home"); + } + } + +} diff --git a/src/main/java/org/postgresql/util/ObjectFactory.java b/src/main/java/org/postgresql/util/ObjectFactory.java index 055670f..f0ee865 100644 --- a/src/main/java/org/postgresql/util/ObjectFactory.java +++ b/src/main/java/org/postgresql/util/ObjectFactory.java @@ -23,6 +23,9 @@ public class ObjectFactory { * single String argument is searched if it fails, or tryString is true a no argument constructor * is tried. * + * @param type of expected class + * @param expectedClass expected class of type T, if the classname instantiated doesn't match + * the expected type of this class this method will fail * @param classname name of the class to instantiate * @param info parameter to pass as Properties * @param tryString whether to look for a single String argument constructor @@ -36,23 +39,26 @@ public class ObjectFactory { * @throws IllegalAccessException if something goes wrong * @throws InvocationTargetException if something goes wrong */ - public static Object instantiate(String classname, Properties info, boolean tryString, + public static T instantiate(Class expectedClass, String classname, Properties info, + boolean tryString, /* @Nullable */ String stringarg) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { /* @Nullable */ Object[] args = {info}; - Constructor ctor = null; - Class cls = Class.forName(classname); + Constructor ctor = null; + Class cls = ClassUtils.forName(classname, expectedClass, ObjectFactory.class.getClassLoader()); try { ctor = cls.getConstructor(Properties.class); } catch (NoSuchMethodException ignored) { + // Try String-based constructor later } if (tryString && ctor == null) { try { ctor = cls.getConstructor(String.class); args = new String[]{stringarg}; } catch (NoSuchMethodException ignored) { + // Try no-argument constructor below } } if (ctor == null) { diff --git a/src/main/java/org/postgresql/util/PGBinaryObject.java b/src/main/java/org/postgresql/util/PGBinaryObject.java index b90fd78..f41e763 100644 --- a/src/main/java/org/postgresql/util/PGBinaryObject.java +++ b/src/main/java/org/postgresql/util/PGBinaryObject.java @@ -8,8 +8,8 @@ import java.sql.SQLException; /** - * PGBinaryObject is a inteface that classes extending {@link PGobject} can use to take advantage of - * more optimal binary encoding of the data type. + * PGBinaryObject is a interface that classes extending {@link PGobject} can use to take advantage + * of more optimal binary encoding of the data type. */ public interface PGBinaryObject { /** diff --git a/src/main/java/org/postgresql/util/PGInterval.java b/src/main/java/org/postgresql/util/PGInterval.java index 907e114..c8118ee 100644 --- a/src/main/java/org/postgresql/util/PGInterval.java +++ b/src/main/java/org/postgresql/util/PGInterval.java @@ -9,8 +9,6 @@ import java.io.Serializable; import java.sql.SQLException; -import java.text.DecimalFormat; -import java.text.NumberFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; @@ -42,23 +40,23 @@ public PGInterval() { /** * Initialize a interval with a given interval string representation. * - * @param value String representated interval (e.g. '3 years 2 mons') + * @param value String represented interval (e.g. '3 years 2 mons') * @throws SQLException Is thrown if the string representation has an unknown format * @see PGobject#setValue(String) */ - @SuppressWarnings("method.invocation.invalid") + @SuppressWarnings("method.invocation") public PGInterval(String value) throws SQLException { this(); setValue(value); } - private int lookAhead(String value, int position, String find) { + private static int lookAhead(String value, int position, String find) { char [] tokens = find.toCharArray(); int found = -1; - for ( int i = 0; i < tokens.length; i++ ) { + for (int i = 0; i < tokens.length; i++) { found = value.indexOf(tokens[i], position); - if ( found > 0 ) { + if (found > 0) { return found; } } @@ -73,14 +71,14 @@ private void parseISO8601Format(String value) { int hasTime = value.indexOf('T'); if ( hasTime > 0 ) { /* skip over the P */ - dateValue = value.substring(1,hasTime); + dateValue = value.substring(1, hasTime); timeValue = value.substring(hasTime + 1); } else { /* skip over the P */ dateValue = value.substring(1); } - for ( int i = 0; i < dateValue.length(); i++ ) { + for (int i = 0; i < dateValue.length(); i++) { int lookAhead = lookAhead(dateValue, i, "YMD"); if (lookAhead > 0) { number = Integer.parseInt(dateValue.substring(i, lookAhead)); @@ -98,13 +96,12 @@ private void parseISO8601Format(String value) { for (int i = 0; i < timeValue.length(); i++) { int lookAhead = lookAhead(timeValue, i, "HMS"); if (lookAhead > 0) { - number = Integer.parseInt(timeValue.substring(i, lookAhead)); if (timeValue.charAt(lookAhead) == 'H') { - setHours(number); + setHours(Integer.parseInt(timeValue.substring(i, lookAhead))); } else if (timeValue.charAt(lookAhead) == 'M') { - setMinutes(number); + setMinutes(Integer.parseInt(timeValue.substring(i, lookAhead))); } else if (timeValue.charAt(lookAhead) == 'S') { - setSeconds(number); + setSeconds(Double.parseDouble(timeValue.substring(i, lookAhead))); } i = lookAhead; } @@ -123,7 +120,7 @@ private void parseISO8601Format(String value) { * @param seconds seconds * @see #setValue(int, int, int, int, int, double) */ - @SuppressWarnings("method.invocation.invalid") + @SuppressWarnings("method.invocation") public PGInterval(int years, int months, int days, int hours, int minutes, double seconds) { this(); setValue(years, months, days, hours, minutes, seconds); @@ -133,9 +130,10 @@ public PGInterval(int years, int months, int days, int hours, int minutes, doubl * Sets a interval string represented value to this instance. This method only recognize the * format, that Postgres returns - not all input formats are supported (e.g. '1 yr 2 m 3 s'). * - * @param value String representated interval (e.g. '3 years 2 mons') + * @param value String represented interval (e.g. '3 years 2 mons') * @throws SQLException Is thrown if the string representation has an unknown format */ + @Override public void setValue(/* @Nullable */ String value) throws SQLException { isNull = value == null; if (value == null) { @@ -143,13 +141,13 @@ public void setValue(/* @Nullable */ String value) throws SQLException { isNull = true; return; } - final boolean PostgresFormat = !value.startsWith("@"); + final boolean postgresFormat = !value.startsWith("@"); if (value.startsWith("P")) { parseISO8601Format(value); return; } // Just a simple '0' - if (!PostgresFormat && value.length() == 3 && value.charAt(2) == '0') { + if (!postgresFormat && value.length() == 3 && value.charAt(2) == '0') { setValue(0, 0, 0, 0, 0, 0.0); return; } @@ -165,6 +163,7 @@ public void setValue(/* @Nullable */ String value) throws SQLException { String valueToken = null; value = value.replace('+', ' ').replace('@', ' '); + value = value.toLowerCase(Locale.ROOT); final StringTokenizer st = new StringTokenizer(value); for (int i = 1; st.hasMoreTokens(); i++) { String token = st.nextToken(); @@ -178,7 +177,7 @@ public void setValue(/* @Nullable */ String value) throws SQLException { // This handles hours, minutes, seconds and microseconds for // ISO intervals - int offset = (token.charAt(0) == '-') ? 1 : 0; + int offset = token.charAt(0) == '-' ? 1 : 0; hours = nullSafeIntGet(token.substring(offset + 0, endHours)); minutes = nullSafeIntGet(token.substring(endHours + 1, endHours + 3)); @@ -222,7 +221,7 @@ public void setValue(/* @Nullable */ String value) throws SQLException { PSQLState.NUMERIC_CONSTANT_OUT_OF_RANGE, e); } - if (!PostgresFormat && value.endsWith("ago")) { + if (!postgresFormat && value.endsWith("ago")) { // Inverse the leading sign setValue(-years, -months, -days, -hours, -minutes, -seconds); } else { @@ -254,23 +253,60 @@ public void setValue(int years, int months, int days, int hours, int minutes, do * * @return String represented interval */ + @Override public /* @Nullable */ String getValue() { if (isNull) { return null; } - DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(Locale.US); - df.applyPattern("0.0#####"); - return String.format( - Locale.ROOT, - "%d years %d mons %d days %d hours %d mins %s secs", - years, - months, - days, - hours, - minutes, - df.format(getSeconds()) - ); + // See https://github.com/pgjdbc/pgjdbc/pull/3866 for the justification + // It looks like any attempt to estimate the buffer size causes noticeable slowdown + StringBuilder sb = new StringBuilder(64); + appendUnit(sb, years, " years"); + appendUnit(sb, months, " mons"); + appendUnit(sb, days, " days"); + appendUnit(sb, hours, " hours"); + appendUnit(sb, minutes, " mins"); + + if (sb.length() == 0 || wholeSeconds != 0 || microSeconds != 0) { + if (sb.length() > 0) { + sb.append(' '); + } + if (wholeSeconds < 0 || microSeconds < 0) { + // E.g. -0.73 has wholeSeconds==0, so we need to check micros as well + sb.append('-'); + } + sb.append(Math.abs(wholeSeconds)); + + if (microSeconds != 0) { + sb.append('.'); + int microsStart = sb.length(); // including + // Add microseconds + sb.append(Math.abs(microSeconds)); + int microsEnd = sb.length(); // excluding + int prefixZeros = 6 - (microsEnd - microsStart); + // Remove trailing zeros + while (sb.charAt(microsEnd - 1) == '0' && microsEnd > microsStart) { + microsEnd--; + } + sb.setLength(microsEnd); + // Add missing leading zeros + sb.insert(microsStart, "000000", 0, prefixZeros); + } + + sb.append(" secs"); + } + return sb.toString(); + } + + private static void appendUnit(StringBuilder sb, int value, String unit) { + if (value == 0) { + return; + } + if (sb.length() > 0) { + sb.append(' '); + } + sb.append(value).append(unit); } /** @@ -392,8 +428,14 @@ public int getMicroSeconds() { */ public void setSeconds(double seconds) { isNull = false; - wholeSeconds = (int) seconds; - microSeconds = (int) Math.round((seconds - wholeSeconds) * MICROS_IN_SECOND); + + double micros = seconds * MICROS_IN_SECOND; + if (micros > Long.MAX_VALUE || micros < Long.MIN_VALUE) { + throw new IllegalArgumentException("Number of seconds should be within Long.MIN_VALUE/1000000...Long.MAX_VALUE/1000000"); + } + long totalMicros = Math.round(micros); + wholeSeconds = (int) (totalMicros / MICROS_IN_SECOND); + microSeconds = (int) (totalMicros % MICROS_IN_SECOND); } /** @@ -406,7 +448,7 @@ public void add(Calendar cal) { return; } - final int milliseconds = (microSeconds + ((microSeconds < 0) ? -500 : 500)) / 1000 + wholeSeconds * 1000; + final int milliseconds = (microSeconds + (microSeconds < 0 ? -500 : 500)) / 1000 + wholeSeconds * 1000; cal.add(Calendar.MILLISECOND, milliseconds); cal.add(Calendar.MINUTE, getMinutes()); @@ -421,6 +463,7 @@ public void add(Calendar cal) { * * @param date Date instance to add to */ + @SuppressWarnings("JavaUtilDate") public void add(Date date) { if (isNull) { return; @@ -477,7 +520,7 @@ public void scale(int factor) { * @throws NumberFormatException if the string contains invalid chars */ private static int nullSafeIntGet(/* @Nullable */ String value) throws NumberFormatException { - return (value == null) ? 0 : Integer.parseInt(value); + return value == null ? 0 : Integer.parseInt(value); } /** @@ -488,7 +531,7 @@ private static int nullSafeIntGet(/* @Nullable */ String value) throws NumberFor * @throws NumberFormatException if the string contains invalid chars */ private static double nullSafeDoubleGet(/* @Nullable */ String value) throws NumberFormatException { - return (value == null) ? 0 : Double.parseDouble(value); + return value == null ? 0 : Double.parseDouble(value); } /** @@ -497,6 +540,7 @@ private static double nullSafeDoubleGet(/* @Nullable */ String value) throws Num * @param obj Object to compare with * @return true if the two intervals are identical */ + @Override public boolean equals(/* @Nullable */ Object obj) { if (obj == null) { return false; diff --git a/src/main/java/org/postgresql/util/PGJDBCMain.java b/src/main/java/org/postgresql/util/PGJDBCMain.java index 5c06bcc..55353db 100644 --- a/src/main/java/org/postgresql/util/PGJDBCMain.java +++ b/src/main/java/org/postgresql/util/PGJDBCMain.java @@ -7,12 +7,14 @@ import org.postgresql.Driver; +import java.net.URL; + public class PGJDBCMain { public static void main(String[] args) { - java.net.URL url = Driver.class.getResource("/org/postgresql/Driver.class"); - System.out.printf("%n%s%n", org.postgresql.util.DriverInfo.DRIVER_FULL_NAME); + URL url = Driver.class.getResource("/org/postgresql/Driver.class"); + System.out.printf("%n%s%n", DriverInfo.DRIVER_FULL_NAME); System.out.printf("Found in: %s%n%n", url); System.out.printf("The PgJDBC driver is not an executable Java program.%n%n" diff --git a/src/main/java/org/postgresql/util/PGPropertyMaxResultBufferParser.java b/src/main/java/org/postgresql/util/PGPropertyMaxResultBufferParser.java index 8d0be80..240c2c1 100644 --- a/src/main/java/org/postgresql/util/PGPropertyMaxResultBufferParser.java +++ b/src/main/java/org/postgresql/util/PGPropertyMaxResultBufferParser.java @@ -128,8 +128,7 @@ private static int getPhraseLengthIfContains(String valueToCheck, String phrase) private static long calculatePercentOfMemory(String value, int percentPhraseLength) { String realValue = value.substring(0, value.length() - percentPhraseLength); double percent = Double.parseDouble(realValue) / 100; - long result = (long) (percent * ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax()); - return result; + return (long) (percent * ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax()); } /** @@ -152,14 +151,17 @@ private static long parseByteValue(String value) throws PSQLException { case 'T': case 't': multiplier *= mul; + // fall through case 'G': case 'g': multiplier *= mul; + // fall through case 'M': case 'm': multiplier *= mul; + // fall through case 'K': case 'k': diff --git a/src/main/java/org/postgresql/util/PGPropertyUtil.java b/src/main/java/org/postgresql/util/PGPropertyUtil.java new file mode 100644 index 0000000..0e50c71 --- /dev/null +++ b/src/main/java/org/postgresql/util/PGPropertyUtil.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import org.postgresql.PGProperty; + +// import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Locale; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * routines to support PG properties + */ +public class PGPropertyUtil { + + private static final Logger LOGGER = Logger.getLogger(PGPropertyUtil.class.getName()); + + /** + * converts PGPORT String to Integer + * + * @param portStr value of port + * @return value of port or null + */ + private static /* @Nullable */ Integer convertPgPortToInt(String portStr) { + try { + int port = Integer.parseInt(portStr); + if (port < 1 || port > 65535) { + LOGGER.log(Level.WARNING, "JDBC URL port: {0} not valid (1:65535) ", portStr); + return null; + } + return port; + } catch (NumberFormatException ignore) { + LOGGER.log(Level.WARNING, "JDBC URL invalid port number: {0}", portStr); + return null; + } + } + + /** + * Validate properties. Goal is to detect inconsistencies and report understandable messages + * + * @param properties properties + * @return false if errors found + */ + public static boolean propertiesConsistencyCheck(Properties properties) { + // + String hosts = PGProperty.PG_HOST.getOrDefault(properties); + if (hosts == null) { + LOGGER.log(Level.WARNING, "Property [{0}] can not be null", PGProperty.PG_HOST.getName()); + return false; + } + String ports = PGProperty.PG_PORT.getOrDefault(properties); + if (ports == null) { + LOGGER.log(Level.WARNING, "Property [{0}] can not be null", PGProperty.PG_PORT.getName()); + return false; + } + + // check port values + for (String portStr : ports.split(",")) { + if (PGPropertyUtil.convertPgPortToInt(portStr) == null) { + return false; + } + } + + // check count of hosts and count of ports + int hostCount = hosts.split(",").length; + int portCount = ports.split(",").length; + if (hostCount != portCount) { + LOGGER.log(Level.WARNING, "Properties [{0}] [{1}] must have same amount of values", + new Object[]{PGProperty.PG_HOST.getName(), PGProperty.PG_PORT.getName()}); + LOGGER.log(Level.WARNING, "Property [{0}] ; value [{1}] ; count [{2}]", + new Object[]{PGProperty.PG_HOST.getName(), hosts, hostCount}); + LOGGER.log(Level.WARNING, "Property [{0}] ; value [{1}] ; count [{2}]", + new Object[]{PGProperty.PG_PORT.getName(), ports, portCount}); + return false; + } + // + return true; + } + + /** + * translate PGSERVICEFILE keys host, port, dbname + * Example: "host" becomes "PGHOST" + * + * @param serviceKey key in pg_service.conf + * @return translated property or the same value if translation is not needed + */ + // translate PGSERVICEFILE keys host, port, dbname + public static String translatePGServiceToPGProperty(String serviceKey) { + String testKey = "PG" + serviceKey.toUpperCase(Locale.ROOT); + if ( + PGProperty.PG_HOST.getName().equals(testKey) + || (PGProperty.PG_PORT.getName().equals(testKey)) + || (PGProperty.PG_DBNAME.getName().equals(testKey)) + ) { + return testKey; + } else { + return serviceKey; + } + } + + /** + * translate PGSERVICEFILE keys host, port, dbname + * Example: "PGHOST" becomes "host" + * + * @param propertyKey postgres property + * @return translated property or the same value if translation is not needed + */ + public static String translatePGPropertyToPGService(String propertyKey) { + if ( + PGProperty.PG_HOST.getName().equals(propertyKey) + || (PGProperty.PG_PORT.getName().equals(propertyKey)) + || (PGProperty.PG_DBNAME.getName().equals(propertyKey)) + ) { + return propertyKey.substring(2).toLowerCase(Locale.ROOT); + } else { + return propertyKey; + } + } +} diff --git a/src/main/java/org/postgresql/util/PGTime.java b/src/main/java/org/postgresql/util/PGTime.java index 451de12..da5f656 100644 --- a/src/main/java/org/postgresql/util/PGTime.java +++ b/src/main/java/org/postgresql/util/PGTime.java @@ -75,7 +75,7 @@ public void setCalendar(/* @Nullable */ Calendar calendar) { public int hashCode() { final int prime = 31; int result = super.hashCode(); - result = prime * result + ((calendar == null) ? 0 : calendar.hashCode()); + result = prime * result + (calendar == null ? 0 : calendar.hashCode()); return result; } diff --git a/src/main/java/org/postgresql/util/PGTimestamp.java b/src/main/java/org/postgresql/util/PGTimestamp.java index dd1890d..e3d5e3e 100644 --- a/src/main/java/org/postgresql/util/PGTimestamp.java +++ b/src/main/java/org/postgresql/util/PGTimestamp.java @@ -38,9 +38,9 @@ public PGTimestamp(long time) { } /** - *

Constructs a PGTimestamp with the given time zone. The integral seconds are stored + * Constructs a PGTimestamp with the given time zone. The integral seconds are stored * in the underlying date value; the fractional seconds are stored in the nanos field - * of the Timestamp object.

+ * of the Timestamp object. * *

The calendar object is optional. If absent, the driver will treat the timestamp as * timestamp without time zone. When present, the driver will treat the timestamp as @@ -80,7 +80,7 @@ public void setCalendar(/* @Nullable */ Calendar calendar) { public int hashCode() { final int prime = 31; int result = super.hashCode(); - result = prime * result + ((calendar == null) ? 0 : calendar.hashCode()); + result = prime * result + (calendar == null ? 0 : calendar.hashCode()); return result; } diff --git a/src/main/java/org/postgresql/util/PGbytea.java b/src/main/java/org/postgresql/util/PGbytea.java index 2c9abef..c36018a 100644 --- a/src/main/java/org/postgresql/util/PGbytea.java +++ b/src/main/java/org/postgresql/util/PGbytea.java @@ -5,8 +5,15 @@ package org.postgresql.util; +import org.postgresql.core.FixedLengthOutputStream; +import org.postgresql.core.v3.SqlSerializationContext; + // import org.checkerframework.checker.nullness.qual.PolyNull; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.sql.SQLException; /** @@ -22,10 +29,10 @@ public class PGbytea { private static final int[] HEX_VALS = new int['f' + 1 - '0']; static { - for (int i = 0; i < 10; ++i) { + for (int i = 0; i < 10; i++) { HEX_VALS[i] = (byte) i; } - for (int i = 0; i < 6; ++i) { + for (int i = 0; i < 6; i++) { HEX_VALS['A' + i - '0'] = (byte) (10 + i); HEX_VALS['a' + i - '0'] = (byte) (10 + i); } @@ -75,7 +82,7 @@ private static byte[] toBytesOctalEscaped(byte[] s) { // count backslash escapes, they will be either // backslashes or an octal escape \\ or \003 // - for (int i = 0; i < slength; ++i) { + for (int i = 0; i < slength; i++) { byte current = s[i]; if (current == '\\') { byte next = s[++i]; @@ -128,32 +135,124 @@ private static byte[] toBytesOctalEscaped(byte[] s) { if (buf == null) { return null; } - StringBuilder stringBuilder = new StringBuilder(2 * buf.length); - for (byte element : buf) { - int elementAsInt = (int) element; - if (elementAsInt < 0) { - elementAsInt = 256 + elementAsInt; + StringBuilder stringBuilder = new StringBuilder(2 + 2 * buf.length); + stringBuilder.append("\\x"); + appendHexString(stringBuilder, buf, 0, buf.length); + return stringBuilder.toString(); + } + + /** + * Appends given byte array as hex string. + * See HexEncodingBenchmark for the benchmark. + * @param sb output builder + * @param buf buffer to append + * @param offset offset within the buffer + * @param length the length of sequence to append + */ + public static void appendHexString(StringBuilder sb, byte[] buf, int offset, int length) { + for (int i = offset; i < offset + length; i++) { + byte element = buf[i]; + sb.append(Character.forDigit((element >> 4) & 0xf, 16)); + sb.append(Character.forDigit(element & 0xf, 16)); + } + } + + /** + * Formats input object as {@code bytea} literal like {@code '\xcafebabe'::bytea}. + * The following inputs are supported: {@code byte[]}, {@link StreamWrapper}, and + * {@link ByteStreamWriter}. + * @param value input value to format + * @return formatted value + * @throws IOException in case there's underflow in the input value + * @deprecated prefer {@link #toPGLiteral(Object, SqlSerializationContext)} to clarify the behaviour + * regarding {@link InputStream} objects + */ + @Deprecated + public static String toPGLiteral(Object value) throws IOException { + return toPGLiteral(value, SqlSerializationContext.of(true, true)); + } + + /** + * Formats input object as {@code bytea} literal like {@code '\xcafebabe'::bytea}. + * The following inputs are supported: {@code byte[]}, {@link StreamWrapper}, and + * {@link ByteStreamWriter}. + * @param value input value to format + * @param context specifies configuration for converting the parameters to string + * @return formatted value + * @throws IOException in case there's underflow in the input value + */ + public static String toPGLiteral(Object value, SqlSerializationContext context) throws IOException { + if (value instanceof byte[]) { + byte[] bytes = (byte[]) value; + StringBuilder sb = new StringBuilder(bytes.length * 2 + 11); + sb.append("'\\x"); + appendHexString(sb, bytes, 0, bytes.length); + sb.append("'::bytea"); + return sb.toString(); + } + + if (value instanceof StreamWrapper) { + StreamWrapper sw = (StreamWrapper) value; + byte[] bytes = sw.getBytes(); + if (context.getIdempotent() && bytes == null) { + // Note: we skip reading the stream wrapper only in case it wraps a stream + // If StreamWrapper wraps a byte[] instance, then it is fine to serialize it + return "?"; } - // we escape the same non-printable characters as the backend - // we must escape all 8bit characters otherwise when convering - // from java unicode to the db character set we may end up with - // question marks if the character set is SQL_ASCII - if (elementAsInt < 040 || elementAsInt > 0176) { - // escape charcter with the form \000, but need two \\ because of - // the Java parser - stringBuilder.append("\\"); - stringBuilder.append((char) (((elementAsInt >> 6) & 0x3) + 48)); - stringBuilder.append((char) (((elementAsInt >> 3) & 0x7) + 48)); - stringBuilder.append((char) ((elementAsInt & 0x07) + 48)); - } else if (element == (byte) '\\') { - // escape the backslash character as \\, but need four \\\\ because - // of the Java parser - stringBuilder.append("\\\\"); - } else { - // other characters are left alone - stringBuilder.append((char) element); + + int length = sw.getLength(); + StringBuilder sb = new StringBuilder(length * 2 + 11); + sb.append("'\\x"); + if (bytes != null) { + appendHexString(sb, bytes, sw.getOffset(), length); + } else if (length > 0) { + InputStream str = sw.getStream(); + byte[] streamBuffer = new byte[8192]; + int read; + while (length > 0) { + read = str.read(streamBuffer, 0, Math.min(length, streamBuffer.length)); + if (read == -1) { + break; + } + appendHexString(sb, streamBuffer, 0, read); + length -= read; + } + if (length > 0) { + throw new EOFException( + GT.tr("Premature end of input stream, expected {0} bytes, but only read {1}.", + sw.getLength(), sw.getLength() - length)); + } } + sb.append("'::bytea"); + return sb.toString(); } - return stringBuilder.toString(); + + if (value instanceof ByteStreamWriter) { + ByteStreamWriter bsw = (ByteStreamWriter) value; + int len = bsw.getLength(); + StringBuilder sb = new StringBuilder(len * 2 + 11); + sb.append("'\\x"); + FixedLengthOutputStream str = new FixedLengthOutputStream(len, new OutputStream() { + @Override + public void write(int b) { + sb.append(Character.forDigit((b >> 4) & 0xf, 16)); + sb.append(Character.forDigit(b & 0xf, 16)); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + appendHexString(sb, b, off, len); + } + }); + bsw.writeTo(() -> str); + for (int i = 0; i < str.remaining(); i++) { + sb.append("00"); + } + sb.append("'::bytea"); + return sb.toString(); + } + + throw new IllegalArgumentException( + GT.tr("Can't convert {0} to {1} literal", value.getClass(), "bytea")); } } diff --git a/src/main/java/org/postgresql/util/PGmoney.java b/src/main/java/org/postgresql/util/PGmoney.java index 44f297a..bcc8209 100644 --- a/src/main/java/org/postgresql/util/PGmoney.java +++ b/src/main/java/org/postgresql/util/PGmoney.java @@ -32,7 +32,7 @@ public PGmoney(double value) { val = value; } - @SuppressWarnings("method.invocation.invalid") + @SuppressWarnings("method.invocation") public PGmoney(String value) throws SQLException { this(); setValue(value); @@ -45,6 +45,7 @@ public PGmoney() { type = "money"; } + @Override public void setValue(/* @Nullable */ String s) throws SQLException { isNull = s == null; if (s == null) { @@ -54,7 +55,7 @@ public void setValue(/* @Nullable */ String s) throws SQLException { String s1; boolean negative; - negative = (s.charAt(0) == '('); + negative = s.charAt(0) == '('; // Remove any () (for negative) & currency symbol s1 = PGtokenizer.removePara(s).substring(1); @@ -88,6 +89,7 @@ public int hashCode() { return result; } + @Override public boolean equals(/* @Nullable */ Object obj) { if (obj instanceof PGmoney) { PGmoney p = (PGmoney) obj; @@ -101,6 +103,7 @@ public boolean equals(/* @Nullable */ Object obj) { return false; } + @Override public /* @Nullable */ String getValue() { if (isNull) { return null; diff --git a/src/main/java/org/postgresql/util/PGobject.java b/src/main/java/org/postgresql/util/PGobject.java index e136654..677e34c 100644 --- a/src/main/java/org/postgresql/util/PGobject.java +++ b/src/main/java/org/postgresql/util/PGobject.java @@ -27,7 +27,7 @@ public PGobject() { } /** - *

This method sets the type of this object.

+ * This method sets the type of this object. * *

It should not be extended by subclasses, hence it is final

* @@ -57,7 +57,7 @@ public final String getType() { } /** - * This must be overidden, to return the value of the object, in the form required by + * This must be overridden, to return the value of the object, in the form required by * org.postgresql. * * @return the value of this object @@ -73,15 +73,16 @@ public final String getType() { * @return true if the current object wraps `null` value. */ public boolean isNull() { - return getValue() != null; + return getValue() == null; } /** - * This must be overidden to allow comparisons of objects. + * This must be overridden to allow comparisons of objects. * * @param obj Object to compare with * @return true if the two boxes are identical */ + @Override public boolean equals(/* @Nullable */ Object obj) { if (obj instanceof PGobject) { final Object otherValue = ((PGobject) obj).getValue(); @@ -95,17 +96,19 @@ public boolean equals(/* @Nullable */ Object obj) { } /** - * This must be overidden to allow the object to be cloned. + * This must be overridden to allow the object to be cloned. */ + @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } /** - * This is defined here, so user code need not overide it. + * This is defined here, so user code need not override it. * * @return the value of this object, in the syntax expected by org.postgresql */ + @Override @SuppressWarnings("nullness") public String toString() { return getValue(); diff --git a/src/main/java/org/postgresql/util/PGtokenizer.java b/src/main/java/org/postgresql/util/PGtokenizer.java index 04b0117..5f79feb 100644 --- a/src/main/java/org/postgresql/util/PGtokenizer.java +++ b/src/main/java/org/postgresql/util/PGtokenizer.java @@ -28,20 +28,20 @@ public class PGtokenizer { private static final Map CLOSING_TO_OPENING_CHARACTER = new HashMap<>(); static { - CLOSING_TO_OPENING_CHARACTER.put( ')', '('); + CLOSING_TO_OPENING_CHARACTER.put(')', '('); - CLOSING_TO_OPENING_CHARACTER.put( ']', '['); + CLOSING_TO_OPENING_CHARACTER.put(']', '['); - CLOSING_TO_OPENING_CHARACTER.put( '>', '<'); + CLOSING_TO_OPENING_CHARACTER.put('>', '<'); - CLOSING_TO_OPENING_CHARACTER.put( '"', '"'); + CLOSING_TO_OPENING_CHARACTER.put('"', '"'); } // Our tokens - protected List tokens = new ArrayList(); + protected List tokens = new ArrayList<>(); /** - *

Create a tokeniser.

+ * Create a tokeniser. * *

We could have used StringTokenizer to do this, however, we needed to handle nesting of '(' ')' * '[' ']' '<' and '>' as these are used by the geometric data types.

@@ -49,7 +49,7 @@ public class PGtokenizer { * @param string containing tokens * @param delim single character to split the tokens */ - @SuppressWarnings("method.invocation.invalid") + @SuppressWarnings("method.invocation") public PGtokenizer(String string, char delim) { tokenize(string, delim); } @@ -77,7 +77,7 @@ public int tokenize(String string, char delim) { int s; boolean skipChar = false; boolean nestedDoubleQuote = false; - char c = (char)0; + char c = (char) 0; for (p = 0, s = 0; p < string.length(); p++) { c = string.charAt(p); @@ -145,7 +145,7 @@ public String getToken(int n) { } /** - *

This returns a new tokenizer based on one of our tokens.

+ * This returns a new tokenizer based on one of our tokens. * *

The geometric datatypes use this to process nested tokens (usually PGpoint).

* diff --git a/src/main/java/org/postgresql/util/PSQLWarning.java b/src/main/java/org/postgresql/util/PSQLWarning.java index dc0c23e..f133441 100644 --- a/src/main/java/org/postgresql/util/PSQLWarning.java +++ b/src/main/java/org/postgresql/util/PSQLWarning.java @@ -18,6 +18,7 @@ public PSQLWarning(ServerErrorMessage err) { this.serverError = err; } + @Override public /* @Nullable */ String getMessage() { return serverError.getMessage(); } diff --git a/src/main/java/org/postgresql/util/PasswordUtil.java b/src/main/java/org/postgresql/util/PasswordUtil.java new file mode 100644 index 0000000..239a39d --- /dev/null +++ b/src/main/java/org/postgresql/util/PasswordUtil.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import org.postgresql.core.Utils; + +import com.ongres.scram.common.ScramFunctions; +import com.ongres.scram.common.ScramMechanism; +import com.ongres.scram.common.StringPreparation; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Base64; +import java.util.Objects; + +public class PasswordUtil { + private static final int DEFAULT_ITERATIONS = 4096; + private static final int DEFAULT_SALT_LENGTH = 16; + + private static class SecureRandomHolder { + static final SecureRandom INSTANCE = new SecureRandom(); + } + + private static SecureRandom getSecureRandom() { + return SecureRandomHolder.INSTANCE; + } + + /** + * Generate the encoded text representation of the given password for + * SCRAM-SHA-256 authentication. The return value of this method is the literal + * text that may be used when creating or modifying a user with the given + * password without the surrounding single quotes. + * + * @param password The plain text of the user's password. The implementation will zero out + * the array after use + * @param iterations The number of iterations of the hashing algorithm to + * perform + * @param salt The random salt value + * @return The text representation of the password encrypted for SCRAM-SHA-256 + * authentication + */ + public static String encodeScramSha256(char[] password, int iterations, byte[] salt) { + Objects.requireNonNull(password, "password"); + Objects.requireNonNull(salt, "salt"); + if (iterations <= 0) { + throw new IllegalArgumentException("iterations must be greater than zero"); + } + if (salt.length == 0) { + throw new IllegalArgumentException("salt length must be greater than zero"); + } + try { + ScramMechanism scramSha256 = ScramMechanism.SCRAM_SHA_256; + byte[] saltedPassword = ScramFunctions.saltedPassword(scramSha256, + StringPreparation.POSTGRESQL_PREPARATION, password, salt, iterations); + byte[] clientKey = ScramFunctions.clientKey(scramSha256, saltedPassword); + byte[] storedKey = ScramFunctions.storedKey(scramSha256, clientKey); + byte[] serverKey = ScramFunctions.serverKey(scramSha256, saltedPassword); + + return scramSha256.getName() + + "$" + iterations + + ":" + Base64.getEncoder().encodeToString(salt) + + "$" + Base64.getEncoder().encodeToString(storedKey) + + ":" + Base64.getEncoder().encodeToString(serverKey); + } finally { + Arrays.fill(password, (char) 0); + } + } + + /** + * Encode the given password for SCRAM-SHA-256 authentication using the default + * iteration count and a random salt. + * + * @param password The plain text of the user's password. The implementation will zero out the + * array after use + * @return The text representation of the password encrypted for SCRAM-SHA-256 + * authentication + */ + public static String encodeScramSha256(char[] password) { + Objects.requireNonNull(password, "password"); + try { + byte[] salt = ScramFunctions.salt(DEFAULT_SALT_LENGTH, getSecureRandom()); + return encodeScramSha256(password, DEFAULT_ITERATIONS, salt); + } finally { + Arrays.fill(password, (char) 0); + } + } + + /** + * Encode the given password for use with md5 authentication. The PostgreSQL + * server uses the username as the per-user salt so that must also be provided. + * The return value of this method is the literal text that may be used when + * creating or modifying a user with the given password without the surrounding + * single quotes. + * + * @param user The username of the database user + * @param password The plain text of the user's password. The implementation will zero out the + * array after use + * @return The text representation of the password encrypted for md5 + * authentication. + * @deprecated prefer {@link org.postgresql.PGConnection#alterUserPassword(String, char[], String)} + * or {@link #encodeScramSha256(char[])} for better security. + */ + @Deprecated + @SuppressWarnings("DeprecatedIsStillUsed") + public static String encodeMd5(String user, char[] password) { + Objects.requireNonNull(user, "user"); + Objects.requireNonNull(password, "password"); + ByteBuffer passwordBytes = null; + try { + passwordBytes = StandardCharsets.UTF_8.encode(CharBuffer.wrap(password)); + byte[] userBytes = user.getBytes(StandardCharsets.UTF_8); + final MessageDigest md = MessageDigest.getInstance("MD5"); + + md.update(passwordBytes); + md.update(userBytes); + byte[] digest = md.digest(); // 16-byte MD5 + + final byte[] encodedPassword = new byte[35]; // 3 + 2 x 16 + encodedPassword[0] = (byte) 'm'; + encodedPassword[1] = (byte) 'd'; + encodedPassword[2] = (byte) '5'; + MD5Digest.bytesToHex(digest, encodedPassword, 3); + + return new String(encodedPassword, StandardCharsets.UTF_8); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Unable to encode password with MD5", e); + } finally { + Arrays.fill(password, (char) 0); + if (passwordBytes != null) { + if (passwordBytes.hasArray()) { + @SuppressWarnings("ByteBufferBackingArray") + byte[] array = passwordBytes.array(); + Arrays.fill(array, (byte) 0); + } else { + int limit = passwordBytes.limit(); + for (int i = 0; i < limit; i++) { + passwordBytes.put(i, (byte) 0); + } + } + } + } + } + + /** + * Encode the given password for the specified encryption type. + * The word "encryption" is used here to match the verbiage in the PostgreSQL + * server, i.e. the "password_encryption" setting. In reality, a cryptographic + * digest / HMAC operation is being performed. + * The database user is only required for the md5 encryption type. + * + * @param user The username of the database user + * @param password The plain text of the user's password. The implementation will zero + * out the array after use + * @param encryptionType The encryption type for which to encode the user's + * password. This should match the database's supported + * methods and value of the password_encryption setting. + * @return The encoded password + * @throws SQLException If an error occurs encoding the password + */ + public static String encodePassword(String user, char[] password, String encryptionType) + throws SQLException { + Objects.requireNonNull(password, "password"); + Objects.requireNonNull(encryptionType, "encryptionType"); + switch (encryptionType) { + case "md5": + return encodeMd5(user, password); + case "on": + case "off": + case "scram-sha-256": + return encodeScramSha256(password); + } + // If we get here then it's an unhandled encryption type so we must wipe the array ourselves + Arrays.fill(password, (char) 0); + throw new PSQLException("Unable to determine encryption type: " + encryptionType, PSQLState.SYSTEM_ERROR); + } + + /** + * Generate the SQL statement to alter a user's password using the given + * encryption. + * All other encryption settings for the password will use the driver's + * defaults. + * + * @param user The username of the database user + * @param password The plain text of the user's password. The implementation will zero + * out the array after use + * @param encryptionType The encryption type of the password + * @return An SQL statement that may be executed to change the user's password + * @throws SQLException If an error occurs encoding the password + */ + public static String genAlterUserPasswordSQL(String user, char[] password, String encryptionType) + throws SQLException { + try { + String encodedPassword = encodePassword(user, password, encryptionType); + StringBuilder sb = new StringBuilder(); + sb.append("ALTER USER "); + Utils.escapeIdentifier(sb, user); + sb.append(" PASSWORD '"); + // The choice of true / false for standard conforming strings does not matter + // here as the value being escaped is generated by us and known to be hex + // characters for all of the implemented password encryption methods. + Utils.escapeLiteral(sb, encodedPassword, true); + sb.append("'"); + return sb.toString(); + } finally { + Arrays.fill(password, (char) 0); + } + } +} diff --git a/src/main/java/org/postgresql/util/ReaderInputStream.java b/src/main/java/org/postgresql/util/ReaderInputStream.java index e4079b2..3e3d786 100644 --- a/src/main/java/org/postgresql/util/ReaderInputStream.java +++ b/src/main/java/org/postgresql/util/ReaderInputStream.java @@ -16,8 +16,8 @@ import java.nio.charset.StandardCharsets; /** - *

ReaderInputStream accepts a UTF-16 char stream (Reader) as input and - * converts it to a UTF-8 byte stream (InputStream) as output.

+ * ReaderInputStream accepts a UTF-16 char stream (Reader) as input and + * converts it to a UTF-8 byte stream (InputStream) as output. * *

This is the inverse of java.io.InputStreamReader which converts a * binary stream to a character stream.

@@ -99,7 +99,7 @@ private void advance() throws IOException { bbuf.flip(); } - private void checkEncodeResult(CoderResult result) throws CharacterCodingException { + private static void checkEncodeResult(CoderResult result) throws CharacterCodingException { if (result.isError()) { result.throwException(); } @@ -111,7 +111,7 @@ public int read() throws IOException { while (res != -1) { res = read(oneByte); if (res > 0) { - return (oneByte[0] & 0xFF); + return oneByte[0] & 0xFF; } } return -1; diff --git a/src/main/java/org/postgresql/util/ServerErrorMessage.java b/src/main/java/org/postgresql/util/ServerErrorMessage.java index 5c33a46..0489cf7 100644 --- a/src/main/java/org/postgresql/util/ServerErrorMessage.java +++ b/src/main/java/org/postgresql/util/ServerErrorMessage.java @@ -36,7 +36,7 @@ public class ServerErrorMessage implements Serializable { private static final Character DATATYPE = 'd'; private static final Character CONSTRAINT = 'n'; - private final Map mesgParts = new HashMap(); + private final Map mesgParts = new HashMap<>(); public ServerErrorMessage(EncodingPredictor.DecodeResult serverError) { this(serverError.result); @@ -157,6 +157,7 @@ String getNonSensitiveErrorMessage() { return totalMessage.toString(); } + @Override public String toString() { // Now construct the message from what the server sent // The general format is: diff --git a/src/main/java/org/postgresql/util/SharedTimer.java b/src/main/java/org/postgresql/util/SharedTimer.java index fb0720c..e9e2ab7 100644 --- a/src/main/java/org/postgresql/util/SharedTimer.java +++ b/src/main/java/org/postgresql/util/SharedTimer.java @@ -5,6 +5,8 @@ package org.postgresql.util; +import org.postgresql.jdbc.ResourceLock; + // import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Timer; @@ -13,12 +15,27 @@ import java.util.logging.Logger; public class SharedTimer { + static class TimerCleanup implements LazyCleaner.CleaningAction { + private final Timer timer; + + TimerCleanup(Timer timer) { + this.timer = timer; + } + + @Override + public void onClean(boolean leak) throws RuntimeException { + timer.cancel(); + } + } + // Incremented for each Timer created, this allows each to have a unique Timer name private static final AtomicInteger timerCount = new AtomicInteger(0); private static final Logger LOGGER = Logger.getLogger(SharedTimer.class.getName()); private volatile /* @Nullable */ Timer timer; private final AtomicInteger refCount = new AtomicInteger(0); + private final ResourceLock lock = new ResourceLock(); + private LazyCleaner./* @Nullable */ Cleanable timerCleanup; public SharedTimer() { } @@ -27,48 +44,54 @@ public int getRefCount() { return refCount.get(); } - public synchronized Timer getTimer() { - Timer timer = this.timer; - if (timer == null) { - int index = timerCount.incrementAndGet(); + public Timer getTimer() { + try (ResourceLock ignore = lock.obtain()) { + Timer timer = this.timer; + if (timer == null) { + int index = timerCount.incrementAndGet(); - /* - Temporarily switch contextClassLoader to the one that loaded this driver to avoid TimerThread preventing current - contextClassLoader - which may be the ClassLoader of a web application - from being GC:ed. - */ - final ClassLoader prevContextCL = Thread.currentThread().getContextClassLoader(); - try { /* - Scheduled tasks whould not need to use .getContextClassLoader, so we just reset it to null + Temporarily switch contextClassLoader to the one that loaded this driver to avoid TimerThread preventing current + contextClassLoader - which may be the ClassLoader of a web application - from being GC:ed. */ - Thread.currentThread().setContextClassLoader(null); + final ClassLoader prevContextCL = Thread.currentThread().getContextClassLoader(); + try { + /* + Scheduled tasks should not need to use .getContextClassLoader, so we just reset it to null + */ + Thread.currentThread().setContextClassLoader(null); - this.timer = timer = new Timer("PostgreSQL-JDBC-SharedTimer-" + index, true); - } finally { - Thread.currentThread().setContextClassLoader(prevContextCL); + this.timer = timer = new Timer("PostgreSQL-JDBC-SharedTimer-" + index, true); + this.timerCleanup = LazyCleanerImpl.getInstance().register(refCount, new TimerCleanup(timer)); + } finally { + Thread.currentThread().setContextClassLoader(prevContextCL); + } } + refCount.incrementAndGet(); + return timer; } - refCount.incrementAndGet(); - return timer; } - public synchronized void releaseTimer() { - int count = refCount.decrementAndGet(); - if (count > 0) { - // There are outstanding references to the timer so do nothing - LOGGER.log(Level.FINEST, "Outstanding references still exist so not closing shared Timer"); - } else if (count == 0) { - // This is the last usage of the Timer so cancel it so it's resources can be release. - LOGGER.log(Level.FINEST, "No outstanding references to shared Timer, will cancel and close it"); - if (timer != null) { - timer.cancel(); - timer = null; + public void releaseTimer() { + try (ResourceLock ignore = lock.obtain()) { + int count = refCount.decrementAndGet(); + if (count > 0) { + // There are outstanding references to the timer so do nothing + LOGGER.log(Level.FINEST, "Outstanding references still exist so not closing shared Timer"); + } else if (count == 0) { + // This is the last usage of the Timer so cancel it so it's resources can be release. + LOGGER.log(Level.FINEST, "No outstanding references to shared Timer, will cancel and close it"); + if (timerCleanup != null) { + timerCleanup.clean(); + timer = null; + timerCleanup = null; + } + } else { + // Should not get here under normal circumstance, probably a bug in app code. + LOGGER.log(Level.WARNING, + "releaseTimer() called too many times; there is probably a bug in the calling code"); + refCount.set(0); } - } else { - // Should not get here under normal circumstance, probably a bug in app code. - LOGGER.log(Level.WARNING, - "releaseTimer() called too many times; there is probably a bug in the calling code"); - refCount.set(0); } } } diff --git a/src/main/java/org/postgresql/util/StreamWrapper.java b/src/main/java/org/postgresql/util/StreamWrapper.java index f449c5f..28c1ae4 100644 --- a/src/main/java/org/postgresql/util/StreamWrapper.java +++ b/src/main/java/org/postgresql/util/StreamWrapper.java @@ -10,20 +10,21 @@ // import org.checkerframework.checker.nullness.qual.Nullable; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; /** * Wrapper around a length-limited InputStream. * * @author Oliver Jowett (oliver@opencloud.com) */ -public class StreamWrapper { +public final class StreamWrapper implements Closeable { private static final int MAX_MEMORY_BUFFER_BYTES = 51200; @@ -51,90 +52,30 @@ public StreamWrapper(InputStream stream) throws PSQLException { if (memoryLength == -1) { final int diskLength; - final File tempFile = File.createTempFile(TEMP_FILE_PREFIX, null); - FileOutputStream diskOutputStream = new FileOutputStream(tempFile); - diskOutputStream.write(rawData); - try { + final Path tempFile = Files.createTempFile(TEMP_FILE_PREFIX, ".tmp"); + try (OutputStream diskOutputStream = Files.newOutputStream(tempFile)) { + diskOutputStream.write(rawData); diskLength = copyStream(stream, diskOutputStream, Integer.MAX_VALUE - rawData.length); if (diskLength == -1) { throw new PSQLException(GT.tr("Object is too large to send over the protocol."), PSQLState.NUMERIC_CONSTANT_OUT_OF_RANGE); } - diskOutputStream.flush(); - } finally { - diskOutputStream.close(); + } catch (RuntimeException | Error | PSQLException e) { + try { + tempFile.toFile().delete(); + } catch (Throwable ignore) { + // If the temporary file can't be deleted, we can't do much about it + } + throw e; } + // The finalize action is not created if the above code throws this.offset = 0; this.length = rawData.length + diskLength; this.rawData = null; - this.stream = new FileInputStream(tempFile) { - /* - * Usually, closing stream should be done by pgjdbc clients. Here it's an internally - * managed stream so we need to auto-close it and be sure to delete the temporary file - * when doing so. Auto-closing will be done when the first occurs: reaching EOF or Garbage - * Collection - */ - private boolean closed = false; - private int position = 0; - - /** - * Check if we should auto-close this stream - */ - private void checkShouldClose(int readResult) throws IOException { - if (readResult == -1) { - close(); - } else { - position += readResult; - if (position >= length) { - close(); - } - } - } - - public int read(byte[] b) throws IOException { - if (closed) { - return -1; - } - int result = super.read(b); - checkShouldClose(result); - return result; - } - - public int read(byte[] b, int off, int len) throws IOException { - if (closed) { - return -1; - } - int result = super.read(b, off, len); - checkShouldClose(result); - return result; - } - - public void close() throws IOException { - if (!closed) { - super.close(); - tempFile.delete(); - closed = true; - } - } - - protected void finalize() throws IOException { - // forcibly close it because super.finalize() may keep the FD open, which may prevent - // file deletion - close(); - // javac 13 assumes it can throw Throwable - try { - super.finalize(); - } catch (RuntimeException e) { - throw e; - } catch (Error e) { - throw e; - } catch (IOException e) { - throw e; - } catch (Throwable e) { - throw new RuntimeException("Unexpected exception from finalize", e); - } - } - }; + this.stream = null; // The stream is opened on demand + TempFileHolder tempFileHolder = new TempFileHolder(tempFile); + this.tempFileHolder = tempFileHolder; + cleaner = LazyCleanerImpl.getInstance().register(leakHandle, tempFileHolder); } else { this.rawData = rawData; this.stream = null; @@ -147,12 +88,23 @@ protected void finalize() throws IOException { } } - public InputStream getStream() { + public InputStream getStream() throws IOException { if (stream != null) { return stream; } + TempFileHolder finalizeAction = this.tempFileHolder; + if (finalizeAction != null) { + return finalizeAction.getStream(); + } - return new java.io.ByteArrayInputStream(castNonNull(rawData), offset, length); + return new ByteArrayInputStream(castNonNull(rawData), offset, length); + } + + @Override + public void close() throws IOException { + if (cleaner != null) { + cleaner.clean(); + } } public int getLength() { @@ -167,6 +119,7 @@ public int getOffset() { return rawData; } + @Override public String toString() { return ""; } @@ -188,6 +141,9 @@ private static int copyStream(InputStream inputStream, OutputStream outputStream } private final /* @Nullable */ InputStream stream; + private /* @Nullable */ TempFileHolder tempFileHolder; + private final Object leakHandle = new Object(); + private LazyCleaner./* @Nullable */ Cleanable cleaner; private final byte /* @Nullable */ [] rawData; private final int offset; private final int length; diff --git a/src/main/java/org/postgresql/util/TempFileHolder.java b/src/main/java/org/postgresql/util/TempFileHolder.java new file mode 100644 index 0000000..396855e --- /dev/null +++ b/src/main/java/org/postgresql/util/TempFileHolder.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import static org.postgresql.util.internal.Nullness.castNonNull; + +// import org.checkerframework.checker.nullness.qual.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * The action deletes temporary file in case the user submits a large input stream, + * and then abandons the statement. + */ +class TempFileHolder implements LazyCleaner.CleaningAction { + + private static final Logger LOGGER = Logger.getLogger(StreamWrapper.class.getName()); + private /* @Nullable */ InputStream stream; + private /* @Nullable */ Path tempFile; + + TempFileHolder(Path tempFile) { + this.tempFile = tempFile; + } + + public InputStream getStream() throws IOException { + InputStream stream = this.stream; + if (stream == null) { + stream = Files.newInputStream(castNonNull(tempFile)); + this.stream = stream; + } + return stream; + } + + @Override + public void onClean(boolean leak) throws IOException { + if (leak) { + LOGGER.log(Level.WARNING, GT.tr("StreamWrapper leak detected StreamWrapper.close() was not called. ")); + } + Path tempFile = this.tempFile; + if (tempFile != null) { + tempFile.toFile().delete(); + this.tempFile = null; + } + InputStream stream = this.stream; + if (stream != null) { + stream.close(); + this.stream = null; + } + } + +} diff --git a/src/main/java/org/postgresql/util/URLCoder.java b/src/main/java/org/postgresql/util/URLCoder.java index 92fe577..feb45e8 100644 --- a/src/main/java/org/postgresql/util/URLCoder.java +++ b/src/main/java/org/postgresql/util/URLCoder.java @@ -10,9 +10,9 @@ import java.net.URLEncoder; /** - *

This class helps with URL encoding and decoding. UTF-8 encoding is used by default to make + * This class helps with URL encoding and decoding. UTF-8 encoding is used by default to make * encoding consistent across the driver, and encoding might be changed via {@code - * postgresql.url.encoding} property

+ * postgresql.url.encoding} property * *

Note: this should not be used outside of PostgreSQL source, this is not a public API of the * driver.

@@ -28,6 +28,7 @@ public final class URLCoder { * @return decoded value * @see URLDecoder#decode(String, String) */ + @SuppressWarnings("JdkObsolete") public static String decode(String encoded) { try { return URLDecoder.decode(encoded, ENCODING_FOR_URL); @@ -44,9 +45,10 @@ public static String decode(String encoded) { * @return encoded value * @see URLEncoder#encode(String, String) */ + @SuppressWarnings("JdkObsolete") public static String encode(String plain) { try { - return URLEncoder.encode(plain, "UTF-8"); + return URLEncoder.encode(plain, ENCODING_FOR_URL); } catch (UnsupportedEncodingException e) { throw new IllegalStateException( "Unable to encode URL entry via " + ENCODING_FOR_URL + ". This should not happen", e); diff --git a/src/main/java/org/postgresql/util/internal/FileUtils.java b/src/main/java/org/postgresql/util/internal/FileUtils.java new file mode 100644 index 0000000..52745c0 --- /dev/null +++ b/src/main/java/org/postgresql/util/internal/FileUtils.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util.internal; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +public class FileUtils { + + private FileUtils() { + // prevent instantiation of static helper class + } + + public static BufferedInputStream newBufferedInputStream(String path) throws FileNotFoundException { + return new BufferedInputStream(new FileInputStream(path)); + } + + public static BufferedInputStream newBufferedInputStream(File file) throws FileNotFoundException { + return new BufferedInputStream(new FileInputStream(file)); + } +} diff --git a/src/main/java/org/postgresql/util/internal/IntSet.java b/src/main/java/org/postgresql/util/internal/IntSet.java new file mode 100644 index 0000000..20b8b1d --- /dev/null +++ b/src/main/java/org/postgresql/util/internal/IntSet.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util.internal; + +import org.postgresql.core.Oid; + +// import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.BitSet; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Read-optimized {@code Set} for storing {@link Oid} values. + * Note: the set does not support nullable values. + */ +public final class IntSet { + /** + * Maximal Oid that will bs stored in {@link BitSet}. + * If Oid exceeds this value, then it will be stored in {@code Set} only. + * In theory, Oids can be up to 32bit, so we want to limit per-connection memory utilization. + * Allow {@code BitSet} to consume up to 8KiB (one for send and one for receive). + */ + private static final int MAX_OID_TO_STORE_IN_BITSET = 8192 * 8; + + /** + * Contains values outside [0..MAX_OID_TO_STORE_IN_BITSET] range. + * This field is null if bitSet contains all the values. + */ + private /* @Nullable */ Set set; + + /** + * Contains values in range of [0..MAX_OID_TO_STORE_IN_BITSET]. + */ + private final BitSet bitSet = new BitSet(); + + /** + * Clears the contents of the set. + */ + public void clear() { + set = null; + bitSet.clear(); + } + + /** + * Adds all the values to the set. + * @param values set of values to add + */ + public void addAll(Collection values) { + for (Integer value : values) { + add(value); + } + } + + /** + * Adds a single value to the set. + * + * @param value value to add + * @return true if the set did not already contain the specified value + */ + public boolean add(int value) { + if (value >= 0 && value <= MAX_OID_TO_STORE_IN_BITSET) { + boolean contains = bitSet.get(value); + if (!contains) { + bitSet.set(value); + return true; + } + return false; + } + Set set = this.set; + if (set == null) { + this.set = set = new HashSet<>(); + } + return set.add(value); + } + + /** + * Removes a value from the set. + * @param value value to remove + * @return true if the element was + */ + public boolean remove(int value) { + if (value >= 0 && value <= MAX_OID_TO_STORE_IN_BITSET) { + boolean contains = bitSet.get(value); + if (contains) { + bitSet.clear(value); + return true; + } + return false; + } + Set set = this.set; + return set != null && set.remove(value); + } + + /** + * Checks if a given value belongs to the set. + * @param value value to check + * @return true if the value belons to the set + */ + public boolean contains(int value) { + if (value >= 0 && value <= MAX_OID_TO_STORE_IN_BITSET) { + return bitSet.get(value); + } + Set set = this.set; + return set != null && set.contains(value); + } + + /** + * Returns a mutable snapshot of the values stored in the current set. + * @return a mutable snapshot of the values stored in the current set + */ + public Set toMutableSet() { + Set set = this.set; + Set result = new HashSet<>( + (int) ((bitSet.cardinality() + (set != null ? set.size() : 0)) / 0.75f)); + if (set != null) { + result.addAll(set); + } + bitSet.stream().forEach(result::add); + return result; + } +} diff --git a/src/main/java/org/postgresql/util/internal/Nullness.java b/src/main/java/org/postgresql/util/internal/Nullness.java index 952c29c..b915307 100644 --- a/src/main/java/org/postgresql/util/internal/Nullness.java +++ b/src/main/java/org/postgresql/util/internal/Nullness.java @@ -14,7 +14,7 @@ * The methods in this class allow to cast nullable reference to a non-nullable one. * This is an internal class, and it is not meant to be used as a public API. */ -@SuppressWarnings({"cast.unsafe", "NullableProblems", "contracts.postcondition.not.satisfied"}) +@SuppressWarnings({"cast.unsafe", "NullableProblems", "contracts.postcondition"}) public class Nullness { /* @Pure */ public static /* @EnsuresNonNull("#1") */ /* @NonNull */ T castNonNull( diff --git a/src/main/java/org/postgresql/util/internal/PgBufferedOutputStream.java b/src/main/java/org/postgresql/util/internal/PgBufferedOutputStream.java new file mode 100644 index 0000000..c7188c3 --- /dev/null +++ b/src/main/java/org/postgresql/util/internal/PgBufferedOutputStream.java @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util.internal; + +import org.postgresql.util.ByteConverter; +import org.postgresql.util.GT; + +import java.io.EOFException; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; + +/** + * Buffered output stream. The key difference from {@link java.io.BufferedOutputStream} is that + * {@code PgBufferedOutputStream} does not perform synchronization. + * This is an internal class, and it is not meant to be used as a public API. + */ +public class PgBufferedOutputStream extends FilterOutputStream { + /** + * Buffer for the data + */ + protected final byte[] buf; + + /** + * Number of bytes stored in the buffer + */ + protected int count; + + public PgBufferedOutputStream(OutputStream out, int bufferSize) { + super(out); + buf = new byte[bufferSize]; + } + + protected void flushBuffer() throws IOException { + if (count > 0) { + out.write(buf, 0, count); + count = 0; + } + } + + @Override + public void flush() throws IOException { + flushBuffer(); + super.flush(); + } + + public void writeInt2(int val) throws IOException { + byte[] buf = this.buf; + if (buf.length - count < 2) { + flushBuffer(); + } + int count = this.count; + ByteConverter.int2(buf, count, val); + this.count = count + 2; + } + + public void writeInt4(int val) throws IOException { + byte[] buf = this.buf; + if (buf.length - count < 4) { + flushBuffer(); + } + int count = this.count; + ByteConverter.int4(buf, count, val); + this.count = count + 4; + } + + @Override + public void write(int b) throws IOException { + if (count >= buf.length) { + flushBuffer(); + } + buf[count++] = (byte) b; + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + if (len == 0) { + return; + } + + if (count > 0) { + int avail = buf.length - count; + if (avail + buf.length <= len) { + // We have data in the buffer, however, even if we copy as much as it is possible + // the leftover will exceed buffer size, so we issue two write calls + // Sample test to trigger the branch: + // BatchedInsertReWriteEnabledTest.test32767Binds + flushBuffer(); + out.write(b, off, len); + return; + } + int prefixLength = Math.min(len, avail); + System.arraycopy(b, off, buf, count, prefixLength); + count += prefixLength; + off += prefixLength; + len -= prefixLength; + if (count == buf.length) { + flushBuffer(); + } + if (len == 0) { + return; + } + } + + // At this point, the buffer is empty + if (len >= buf.length) { + // Write big chunk + // Sample tests to trigger the branch: + // LargeObjectManagerTest.objectWriteThenRead, + // BlobTransactionTest.concurrentReplace + out.write(b, off, len); + return; + } + // Buffer small one + System.arraycopy(b, off, buf, 0, len); + count = len; + } + + /** + * Writes the given amount of bytes from an input stream to this buffered stream. + * @param inStream input data + * @param remaining the number of bytes to transfer + * @throws IOException in case writing to the output stream fails + * @throws SourceStreamIOException in case reading from the source stream fails + */ + public void write(InputStream inStream, int remaining) throws IOException { + int expectedLength = remaining; + byte[] buf = this.buf; + + while (remaining > 0) { + int readSize = Math.min(remaining, buf.length - count); + int readCount; + + try { + readCount = inStream.read(buf, count, readSize); + } catch (IOException e) { + throw new SourceStreamIOException(remaining, e); + } + + if (readCount < 0) { + throw new SourceStreamIOException( + remaining, + new EOFException( + GT.tr("Premature end of input stream, expected {0} bytes, but only read {1}.", + expectedLength, expectedLength - remaining))); + } + + count += readCount; + remaining -= readCount; + if (count == buf.length) { + flushBuffer(); + } + } + } + + /** + * Writes the required number of zero bytes to the output stream. + * @param len number of bytes to write + * @throws IOException in case writing to the underlying stream fails + */ + public void writeZeros(int len) throws IOException { + int startPos = count; + if (count > 0) { + int avail = buf.length - count; + int prefixLength = Math.min(len, avail); + Arrays.fill(buf, count, count + prefixLength, (byte) 0); + count += prefixLength; + len -= prefixLength; + if (count == buf.length) { + flushBuffer(); + } + if (len == 0) { + return; + } + } + // The buffer is empty at this point, and startPos..buf.length is filled with zeros + // So fill the beginning with zeros as well. + Arrays.fill(buf, 0, Math.min(startPos, len), (byte) 0); + + while (len >= buf.length) { + // Pretend we have the full buffer + count = buf.length; + flushBuffer(); + len -= buf.length; + } + // Pretend we have the remaining zeros in the buffer. + count = len; + } +} diff --git a/src/main/java/org/postgresql/util/internal/SourceStreamIOException.java b/src/main/java/org/postgresql/util/internal/SourceStreamIOException.java new file mode 100644 index 0000000..e0dee03 --- /dev/null +++ b/src/main/java/org/postgresql/util/internal/SourceStreamIOException.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util.internal; + +import static org.postgresql.util.internal.Nullness.castNonNull; + +import java.io.IOException; + +/** + * A marker exception class to distinguish between "IOException when reading the data" and + * "IOException when writing the data" when transferring data from one stream to another. + */ +public class SourceStreamIOException extends IOException { + /** + * The number of bytes remaining to transfer to the destination stream. + */ + private final int bytesRemaining; + + public SourceStreamIOException(int bytesRemaining, IOException cause) { + super(cause); + this.bytesRemaining = bytesRemaining; + } + + public int getBytesRemaining() { + return bytesRemaining; + } + + @Override + public synchronized IOException getCause() { + return (IOException) castNonNull(super.getCause()); + } +} diff --git a/src/main/java/org/postgresql/util/internal/Unsafe.java b/src/main/java/org/postgresql/util/internal/Unsafe.java deleted file mode 100644 index c112986..0000000 --- a/src/main/java/org/postgresql/util/internal/Unsafe.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2021, PostgreSQL Global Development Group - * See the LICENSE file in the project root for more information. - */ - -package org.postgresql.util.internal; - -/** - * This class is excluded from {@code forbidden-apis} check. - * @see Extend - * suppression configuration to be more fine grained - */ -public class Unsafe { - public static boolean credentialCacheExists() { - try { - @SuppressWarnings({"nullness"}) - sun.security.krb5.Credentials credentials = - sun.security.krb5.Credentials.acquireTGTFromCache(null, null); - return credentials != null; - } catch (Exception ex) { - return false; - } - } -} diff --git a/src/main/java/org/postgresql/xa/PGXAConnection.java b/src/main/java/org/postgresql/xa/PGXAConnection.java index 6d13a92..00aa5fa 100644 --- a/src/main/java/org/postgresql/xa/PGXAConnection.java +++ b/src/main/java/org/postgresql/xa/PGXAConnection.java @@ -25,7 +25,8 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -35,7 +36,7 @@ import javax.transaction.xa.Xid; /** - *

The PostgreSQL implementation of {@link XAResource}.

+ * The PostgreSQL implementation of {@link XAResource}. * *

This implementation doesn't support transaction interleaving (see JTA specification, section * 3.4.4) and suspend/resume.

@@ -124,10 +125,10 @@ private class ConnectionHandler implements InvocationHandler { public /* @Nullable */ Object invoke(Object proxy, Method method, /* @Nullable */ Object[] args) throws Throwable { if (state != State.IDLE) { String methodName = method.getName(); - if (methodName.equals("commit") - || methodName.equals("rollback") - || methodName.equals("setSavePoint") - || (methodName.equals("setAutoCommit") && castNonNull((Boolean) args[0]))) { + if ("commit".equals(methodName) + || "rollback".equals(methodName) + || "setSavePoint".equals(methodName) + || ("setAutoCommit".equals(methodName) && castNonNull((Boolean) args[0]))) { throw new PSQLException( GT.tr( "Transaction control methods setAutoCommit(true), commit, rollback and setSavePoint not allowed while an XA transaction is active."), @@ -139,7 +140,7 @@ private class ConnectionHandler implements InvocationHandler { * If the argument to equals-method is also a wrapper, present the original unwrapped * connection to the underlying equals method. */ - if (method.getName().equals("equals") && args.length == 1) { + if ("equals".equals(method.getName()) && args.length == 1) { Object arg = args[0]; if (arg != null && Proxy.isProxyClass(arg.getClass())) { InvocationHandler h = Proxy.getInvocationHandler(arg); @@ -158,7 +159,7 @@ private class ConnectionHandler implements InvocationHandler { } /** - *

Preconditions:

+ * Preconditions: *
    *
  1. Flags must be one of TMNOFLAGS, TMRESUME or TMJOIN
  2. *
  3. xid != null
  4. @@ -200,7 +201,7 @@ public void start(Xid xid, int flags) throws XAException { XAException.XAER_PROTO); } - // We can't check precondition 4 easily, so we don't. Duplicate xid will be catched in prepare + // We can't check precondition 4 easily, so we don't. Duplicate xid will be caught in prepare // phase. // Check implementation deficiency preconditions @@ -247,7 +248,7 @@ public void start(Xid xid, int flags) throws XAException { } /** - *

    Preconditions:

    + * Preconditions: *
      *
    1. Flags is one of TMSUCCESS, TMFAIL, TMSUSPEND
    2. *
    3. xid != null
    4. @@ -299,7 +300,7 @@ public void end(Xid xid, int flags) throws XAException { } /** - *

      Prepares transaction. Preconditions:

      + * Prepares transaction. Preconditions: *
        *
      1. xid != null
      2. *
      3. xid is in ended state
      4. @@ -352,22 +353,22 @@ public int prepare(Xid xid) throws XAException { try { String s = RecoveredXid.xidToString(xid); - Statement stmt = conn.createStatement(); - try { + try (Statement stmt = conn.createStatement()) { stmt.executeUpdate("PREPARE TRANSACTION '" + s + "'"); - } finally { - stmt.close(); } conn.setAutoCommit(localAutoCommitMode); - - return XA_OK; + if (conn.isReadOnly()) { + return XA_RDONLY; + } else { + return XA_OK; + } } catch (SQLException ex) { throw new PGXAException(GT.tr("Error preparing transaction. prepare xid={0}", xid), ex, mapSQLStateToXAErrorCode(ex)); } } /** - *

        Recovers transaction. Preconditions:

        + * Recovers transaction. Preconditions: *
          *
        1. flag must be one of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS or TMSTARTTRSCAN | TMENDRSCAN
        2. *
        3. If flag isn't TMSTARTRSCAN or TMSTARTRSCAN | TMENDRSCAN, a recovery scan must be in progress
        4. @@ -402,9 +403,12 @@ public Xid[] recover(int flag) throws XAException { // except if the transaction is in abort-only state and the // backed refuses to process new queries. Hopefully not a problem // in practise. + // PostgreSQL requires the user to own the transaction in order to successfully execute + // commit prepared or rollback prepared + // See https://github.com/postgres/postgres/blob/15afb7d61c142a9254a6612c6774aff4f358fb69/src/backend/access/transam/twophase.c#L583C32-L599 ResultSet rs = stmt.executeQuery( - "SELECT gid FROM pg_prepared_xacts where database = current_database()"); - LinkedList l = new LinkedList(); + "SELECT gid FROM pg_prepared_xacts where database = current_database() and pg_has_role(current_user, owner, 'member')"); + List l = new ArrayList<>(); while (rs.next()) { Xid recoveredXid = RecoveredXid.stringToXid(castNonNull(rs.getString(1))); if (recoveredXid != null) { @@ -424,7 +428,7 @@ public Xid[] recover(int flag) throws XAException { } /** - *

          Preconditions:

          + * Preconditions: *
            *
          1. xid is known to the RM or it's in prepared state
          2. *
          @@ -482,7 +486,7 @@ public void rollback(Xid xid) throws XAException { } errorCode = XAException.XAER_RMFAIL; } - throw new PGXAException(GT.tr("Error rolling back prepared transaction. rollback xid={0}, preparedXid={1}, currentXid={2}", xid, preparedXid), ex, errorCode); + throw new PGXAException(GT.tr("Error rolling back prepared transaction. rollback xid={0}, preparedXid={1}, currentXid={2}", xid, preparedXid, currentXid), ex, errorCode); } } @@ -504,7 +508,7 @@ public void commit(Xid xid, boolean onePhase) throws XAException { } /** - *

          Preconditions:

          + * Preconditions: *
            *
          1. xid must in ended state.
          2. *
          @@ -555,7 +559,7 @@ private void commitOnePhase(Xid xid) throws XAException { } /** - *

          Commits prepared transaction. Preconditions:

          + * Commits prepared transaction. Preconditions: *
            *
          1. xid must be in prepared state in the server
          2. *
          @@ -648,7 +652,7 @@ public boolean setTransactionTimeout(int seconds) { return false; } - private int mapSQLStateToXAErrorCode(SQLException sqlException) { + private static int mapSQLStateToXAErrorCode(SQLException sqlException) { if (isPostgreSQLIntegrityConstraintViolation(sqlException)) { return XAException.XA_RBINTEGRITY; } @@ -656,7 +660,7 @@ private int mapSQLStateToXAErrorCode(SQLException sqlException) { return XAException.XAER_RMFAIL; } - private boolean isPostgreSQLIntegrityConstraintViolation(SQLException sqlException) { + private static boolean isPostgreSQLIntegrityConstraintViolation(SQLException sqlException) { if (!(sqlException instanceof PSQLException)) { return false; } diff --git a/src/main/java/org/postgresql/xa/PGXADataSource.java b/src/main/java/org/postgresql/xa/PGXADataSource.java index ed067f8..b0e25a1 100644 --- a/src/main/java/org/postgresql/xa/PGXADataSource.java +++ b/src/main/java/org/postgresql/xa/PGXADataSource.java @@ -7,6 +7,7 @@ import org.postgresql.core.BaseConnection; import org.postgresql.ds.common.BaseDataSource; +import org.postgresql.util.DriverInfo; // import org.checkerframework.checker.nullness.qual.Nullable; @@ -31,6 +32,7 @@ public class PGXADataSource extends BaseDataSource implements XADataSource { * @return A valid database connection. * @throws SQLException Occurs when the database connection cannot be established. */ + @Override public XAConnection getXAConnection() throws SQLException { return getXAConnection(getUser(), getPassword()); } @@ -44,19 +46,22 @@ public XAConnection getXAConnection() throws SQLException { * @return A valid database connection. * @throws SQLException Occurs when the database connection cannot be established. */ + @Override public XAConnection getXAConnection(/* @Nullable */ String user, /* @Nullable */ String password) throws SQLException { Connection con = super.getConnection(user, password); return new PGXAConnection((BaseConnection) con); } + @Override public String getDescription() { - return "XA-enabled DataSource from " + org.postgresql.util.DriverInfo.DRIVER_FULL_NAME; + return "XA-enabled DataSource from " + DriverInfo.DRIVER_FULL_NAME; } /** * Generates a reference using the appropriate object factory. */ + @Override protected Reference createReference() { return new Reference(getClass().getName(), PGXADataSourceFactory.class.getName(), null); } diff --git a/src/main/java/org/postgresql/xa/PGXADataSourceFactory.java b/src/main/java/org/postgresql/xa/PGXADataSourceFactory.java index 454f427..83590ce 100644 --- a/src/main/java/org/postgresql/xa/PGXADataSourceFactory.java +++ b/src/main/java/org/postgresql/xa/PGXADataSourceFactory.java @@ -26,11 +26,12 @@ public class PGXADataSourceFactory extends PGObjectFactory { * "JDBC2 Enterprise" edition build which doesn't include PGXADataSource. */ + @Override public /* @Nullable */ Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception { Reference ref = (Reference) obj; String className = ref.getClassName(); - if (className.equals("org.postgresql.xa.PGXADataSource")) { + if ("org.postgresql.xa.PGXADataSource".equals(className)) { return loadXADataSource(ref); } else { return null; diff --git a/src/main/java/org/postgresql/xa/RecoveredXid.java b/src/main/java/org/postgresql/xa/RecoveredXid.java index 3ca5ad4..c5a4bf8 100644 --- a/src/main/java/org/postgresql/xa/RecoveredXid.java +++ b/src/main/java/org/postgresql/xa/RecoveredXid.java @@ -26,14 +26,17 @@ class RecoveredXid implements Xid { this.branchQualifier = branchQualifier; } + @Override public int getFormatId() { return formatId; } + @Override public byte[] getGlobalTransactionId() { return globalTransactionId; } + @Override public byte[] getBranchQualifier() { return branchQualifier; } @@ -48,6 +51,7 @@ public int hashCode() { return result; } + @Override public boolean equals(/* @Nullable */ Object o) { if (o == this) { // optimization for the common case. @@ -67,6 +71,7 @@ public boolean equals(/* @Nullable */ Object o) { /** * This is for debugging purposes only. */ + @Override public String toString() { return xidToString(this); } @@ -105,7 +110,7 @@ static String xidToString(Xid xid) { return new RecoveredXid(formatId, globalTransactionId, branchQualifier); } catch (Exception ex) { final LogRecord logRecord = new LogRecord(Level.FINE, "XID String is invalid: [{0}]"); - logRecord.setParameters(new Object[] {s}); + logRecord.setParameters(new Object[]{s}); logRecord.setThrown(ex); Logger.getLogger(RecoveredXid.class.getName()).log(logRecord); // Doesn't seem to be an xid generated by this driver. diff --git a/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java b/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java index 50b765d..d27e361 100644 --- a/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java +++ b/src/main/java/org/postgresql/xml/DefaultPGXmlFactoryFactory.java @@ -29,7 +29,7 @@ public class DefaultPGXmlFactoryFactory implements PGXmlFactoryFactory { private DefaultPGXmlFactoryFactory() { } - private DocumentBuilderFactory getDocumentBuilderFactory() { + private static DocumentBuilderFactory getDocumentBuilderFactory() { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); setFactoryProperties(factory); factory.setXIncludeAware(false); @@ -69,8 +69,7 @@ public XMLInputFactory newXMLInputFactory() { @Override public XMLOutputFactory newXMLOutputFactory() { - XMLOutputFactory factory = XMLOutputFactory.newInstance(); - return factory; + return XMLOutputFactory.newInstance(); } @Override @@ -95,8 +94,8 @@ private static void setFeatureQuietly(Object factory, String name, boolean value } else { throw new Error("Invalid factory class: " + factory.getClass()); } - return; } catch (Exception ignore) { + // ignore } } @@ -110,6 +109,7 @@ private static void setAttributeQuietly(Object factory, String name, Object valu throw new Error("Invalid factory class: " + factory.getClass()); } } catch (Exception ignore) { + // ignore } } @@ -135,6 +135,7 @@ private static void setPropertyQuietly(Object factory, String name, Object value throw new Error("Invalid factory class: " + factory.getClass()); } } catch (Exception ignore) { + // ignore } } } diff --git a/src/main/java/org/postgresql/xml/NullErrorHandler.java b/src/main/java/org/postgresql/xml/NullErrorHandler.java index d12a4fa..64b1a4d 100644 --- a/src/main/java/org/postgresql/xml/NullErrorHandler.java +++ b/src/main/java/org/postgresql/xml/NullErrorHandler.java @@ -14,12 +14,15 @@ public class NullErrorHandler implements ErrorHandler { public static final NullErrorHandler INSTANCE = new NullErrorHandler(); + @Override public void error(SAXParseException e) { } + @Override public void fatalError(SAXParseException e) { } + @Override public void warning(SAXParseException e) { } } diff --git a/src/main/java11/org/postgresql/util/LazyCleanerImpl.java b/src/main/java11/org/postgresql/util/LazyCleanerImpl.java new file mode 100644 index 0000000..b22fea9 --- /dev/null +++ b/src/main/java11/org/postgresql/util/LazyCleanerImpl.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +// import org.checkerframework.checker.nullness.qual.Nullable; + +import java.lang.ref.Cleaner; +import java.time.Duration; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * LazyCleaner is a utility class that allows to register objects for deferred cleanup. + * + *

          This is the Java 11+ implementation that uses the native {@link Cleaner} API + * introduced in Java 9+ for deferred cleanup operations.

          + * + *

          This class replaces the Java 8 PhantomReference-based implementation via the + * multi-release JAR mechanism when running on Java 11+.

          + * + *

          Note: this is a driver-internal class

          + */ +public class LazyCleanerImpl implements LazyCleaner { + private static final Logger LOGGER = Logger.getLogger(LazyCleanerImpl.class.getName()); + private static final LazyCleanerImpl instance = new LazyCleanerImpl( + "PostgreSQL-JDBC-Cleaner", + Duration.ofMillis(Long.getLong("pgjdbc.config.cleanup.thread.ttl", 30000)) + ); + + private final Cleaner cleaner; + + /** + * Creates a LazyCleaner with the specified configuration. + * + *

          Note: The {@code threadName} and {@code threadTtl} parameters are ignored + * in this implementation since the JVM manages Cleaner threads internally.

          + * + * @param threadName the name for the cleanup thread (ignored, for API compatibility) + * @param threadTtl the maximum time the cleanup thread will wait (ignored, for API compatibility) + */ + public LazyCleanerImpl(String threadName, Duration threadTtl) { + // threadName and threadTtl are ignored in Java 11+ since Cleaner manages its own threads + this.cleaner = Cleaner.create(); + } + + /** + * Returns a default cleaner instance. + * + *

          Note: this is driver-internal API.

          + * @return the instance of LazyCleaner + */ + public static LazyCleanerImpl getInstance() { + return instance; + } + + @Override + public Cleanable register(Object obj, CleaningAction action) { + assert obj != action : "object handle should not be the same as cleaning action, otherwise" + + " the object will never become phantom reachable, so the action will never trigger"; + + // Wrapper is needed to prevent double cleanup (e.g. user-managed cleanups and automatic cleanup) + CleanableWrapper wrapper = new CleanableWrapper<>(action); + + // Register with the native Cleaner, using the wrapper's cleanup method + Cleaner.Cleanable nativeCleanable = cleaner.register(obj, wrapper::leakDetected); + + // Add the reference so the cleanable could be deregistered when user calls clean() + wrapper.setNativeCleanable(nativeCleanable); + return wrapper; + } + + /** + * Returns whether the cleanup thread is currently running. + * + *

          For the Java 11+ implementation, this always returns false since the Cleaner + * manages its own thread pool internally and there is no dedicated cleanup thread.

          + * + * @return false, since the Cleaner manages threads internally + */ + public boolean isThreadRunning() { + // The Cleaner manages its own thread pool, so we return false meaning "there's no extra cleanup thread running" + return false; + } + + /** + * Wrapper that adapts the native Cleaner.Cleanable to our Cleanable interface. + */ + private static class CleanableWrapper implements Cleanable { + private Cleaner./* @Nullable */ Cleanable nativeCleanable; + private volatile /* @Nullable */ CleaningAction action; + + CleanableWrapper(CleaningAction action) { + this.action = action; + } + + void setNativeCleanable(Cleaner.Cleanable nativeCleanable) { + this.nativeCleanable = nativeCleanable; + } + + private synchronized /* @Nullable */ CleaningAction getCleaningAction() { + CleaningAction action = this.action; + this.action = null; + return action; + } + + /** + * Performs the actual cleanup if it hasn't been done yet. + * This method is called both from manual clean() and from the Cleaner thread. + */ + void leakDetected() { + CleaningAction cleaningAction = getCleaningAction(); + if (cleaningAction == null) { + return; + } + try { + cleaningAction.onClean(true); + } catch (Throwable e) { + if (e instanceof InterruptedException) { + LOGGER.log(Level.WARNING, "Unexpected interrupt while executing onClean", e); + } else { + // Should not happen if cleaners are well-behaved + LOGGER.log(Level.WARNING, "Unexpected exception while executing onClean", e); + } + } + } + + @Override + public void clean() throws T { + CleaningAction cleaningAction = getCleaningAction(); + if (cleaningAction == null) { + return; + } + + // Deregister from native Cleaner to prevent automatic cleanup + // This is safe to call even if cleanup already happened + Cleaner.Cleanable nativeCleanable = this.nativeCleanable; + if (nativeCleanable != null) { + // just in case + this.nativeCleanable = null; + nativeCleanable.clean(); + } + + // Call performCleanup with leak=false for manual cleanup + // We rethrow the exceptions so callers can handle them + cleaningAction.onClean(false); + } + } +} diff --git a/src/main/resources/META-INF/LICENSE b/src/main/resources/META-INF/LICENSE index daa7e32..40e549a 100644 --- a/src/main/resources/META-INF/LICENSE +++ b/src/main/resources/META-INF/LICENSE @@ -27,7 +27,7 @@ Additional License files can be found in the 'licenses' folder located in the sa - Software produced outside the ASF which is available under other licenses (not Apache-2.0) BSD-2-Clause -* com.ongres.scram:client:2.1 -* com.ongres.scram:common:2.1 -* com.ongres.stringprep:saslprep:1.1 -* com.ongres.stringprep:stringprep:1.1 +* com.ongres.scram:scram-client:3.2 +* com.ongres.scram:scram-common:3.2 +* com.ongres.stringprep:saslprep:2.2 +* com.ongres.stringprep:stringprep:2.2 diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF index 440dbbb..6e61654 100644 --- a/src/main/resources/META-INF/MANIFEST.MF +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -1,12 +1,13 @@ Manifest-Version: 1.0 -Implementation-Title: PostgreSQL JDBC Driver Bundle-License: BSD-2-Clause -Automatic-Module-Name: org.postgresql.jdbc -Implementation-Version: 42.3.1 +Implementation-Title: PostgreSQL JDBC Driver +Implementation-Version: 42.7.11 Specification-Vendor: Oracle Corporation -Specification-Title: JDBC -Implementation-Vendor-Id: org.postgresql Specification-Version: 4.2 +Specification-Title: JDBC Implementation-Vendor: PostgreSQL Global Development Group +Implementation-Vendor-Id: org.postgresql Main-Class: org.postgresql.util.PGJDBCMain +Automatic-Module-Name: org.postgresql.jdbc +Multi-Release: true diff --git a/src/main/resources/META-INF/licenses/saslprep-1.1.jar/LICENSE b/src/main/resources/META-INF/licenses/com.ongres.scram/scram-client-3.2/META-INF/LICENSE similarity index 97% rename from src/main/resources/META-INF/licenses/saslprep-1.1.jar/LICENSE rename to src/main/resources/META-INF/licenses/com.ongres.scram/scram-client-3.2/META-INF/LICENSE index 84b3fe0..6837c1e 100644 --- a/src/main/resources/META-INF/licenses/saslprep-1.1.jar/LICENSE +++ b/src/main/resources/META-INF/licenses/com.ongres.scram/scram-client-3.2/META-INF/LICENSE @@ -1,4 +1,4 @@ -Copyright 2019, OnGres. +Copyright (c) 2017 OnGres, Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/main/resources/META-INF/licenses/common-2.1.jar/LICENSE b/src/main/resources/META-INF/licenses/com.ongres.scram/scram-common-3.2/META-INF/LICENSE similarity index 97% rename from src/main/resources/META-INF/licenses/common-2.1.jar/LICENSE rename to src/main/resources/META-INF/licenses/com.ongres.scram/scram-common-3.2/META-INF/LICENSE index 13fdb10..6837c1e 100644 --- a/src/main/resources/META-INF/licenses/common-2.1.jar/LICENSE +++ b/src/main/resources/META-INF/licenses/com.ongres.scram/scram-common-3.2/META-INF/LICENSE @@ -1,4 +1,4 @@ -Copyright 2017, OnGres. +Copyright (c) 2017 OnGres, Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/main/resources/META-INF/licenses/stringprep-1.1.jar/LICENSE b/src/main/resources/META-INF/licenses/com.ongres.stringprep/saslprep-2.2/META-INF/LICENSE similarity index 97% rename from src/main/resources/META-INF/licenses/stringprep-1.1.jar/LICENSE rename to src/main/resources/META-INF/licenses/com.ongres.stringprep/saslprep-2.2/META-INF/LICENSE index 84b3fe0..d816533 100644 --- a/src/main/resources/META-INF/licenses/stringprep-1.1.jar/LICENSE +++ b/src/main/resources/META-INF/licenses/com.ongres.stringprep/saslprep-2.2/META-INF/LICENSE @@ -1,4 +1,4 @@ -Copyright 2019, OnGres. +Copyright (c) 2019 OnGres, Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/main/resources/META-INF/licenses/client-2.1.jar/LICENSE b/src/main/resources/META-INF/licenses/com.ongres.stringprep/stringprep-2.2/META-INF/LICENSE similarity index 97% rename from src/main/resources/META-INF/licenses/client-2.1.jar/LICENSE rename to src/main/resources/META-INF/licenses/com.ongres.stringprep/stringprep-2.2/META-INF/LICENSE index 13fdb10..d816533 100644 --- a/src/main/resources/META-INF/licenses/client-2.1.jar/LICENSE +++ b/src/main/resources/META-INF/licenses/com.ongres.stringprep/stringprep-2.2/META-INF/LICENSE @@ -1,4 +1,4 @@ -Copyright 2017, OnGres. +Copyright (c) 2019 OnGres, Inc. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/test/java/org/postgresql/core/AsciiStringInternerTest.java b/src/test/java/org/postgresql/core/AsciiStringInternerTest.java index c19fa74..450941f 100644 --- a/src/test/java/org/postgresql/core/AsciiStringInternerTest.java +++ b/src/test/java/org/postgresql/core/AsciiStringInternerTest.java @@ -5,15 +5,12 @@ package org.postgresql.core; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; -import org.postgresql.test.SlowTests; - -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -28,10 +25,10 @@ * * @author Brett Okken */ -public class AsciiStringInternerTest { +class AsciiStringInternerTest { @Test - public void testCanonicalValue() throws Exception { + void canonicalValue() throws Exception { AsciiStringInterner interner = new AsciiStringInterner(); String s1 = "testCanonicalValue"; byte[] bytes = s1.getBytes(StandardCharsets.US_ASCII); @@ -55,7 +52,7 @@ public void testCanonicalValue() throws Exception { } @Test - public void testStagedValue() throws Exception { + void stagedValue() throws Exception { AsciiStringInterner interner = new AsciiStringInterner(); String s1 = "testStagedValue"; interner.putString(s1); @@ -73,7 +70,7 @@ public void testStagedValue() throws Exception { } @Test - public void testNonAsciiValue() throws Exception { + void nonAsciiValue() throws Exception { final Encoding encoding = Encoding.getJVMEncoding("UTF-8"); AsciiStringInterner interner = new AsciiStringInterner(); String s1 = "testNonAsciiValue" + '\u03C0'; // add multi-byte to string to make invalid for intern @@ -92,26 +89,25 @@ public void testNonAsciiValue() throws Exception { } @Test - public void testToString() throws Exception { + void testToString() throws Exception { AsciiStringInterner interner = new AsciiStringInterner(); - assertEquals("empty", "AsciiStringInterner []", interner.toString()); + assertEquals("AsciiStringInterner []", interner.toString(), "empty"); interner.putString("s1"); - assertEquals("empty", "AsciiStringInterner ['s1']", interner.toString()); + assertEquals("AsciiStringInterner ['s1']", interner.toString(), "empty"); interner.getString("s2".getBytes(StandardCharsets.US_ASCII), 0, 2, null); - assertEquals("empty", "AsciiStringInterner ['s1', 's2']", interner.toString()); + assertEquals("AsciiStringInterner ['s1', 's2']", interner.toString(), "empty"); } @Test - @Category(SlowTests.class) - public void testGarbageCleaning() throws Exception { + void garbageCleaning() throws Exception { final byte[] bytes = new byte[100000]; - for (int i = 0; i < 100000; ++i) { + for (int i = 0; i < 100000; i++) { bytes[i] = (byte) ThreadLocalRandom.current().nextInt(128); } final AsciiStringInterner interner = new AsciiStringInterner(); final LongAdder length = new LongAdder(); final Callable c = () -> { - for (int i = 0; i < 25000; ++i) { + for (int i = 0; i < 25000; i++) { String str; try { str = interner.getString(bytes, 0, ThreadLocalRandom.current().nextInt(1000, bytes.length), null); diff --git a/src/test/java/org/postgresql/core/CommandCompleteParserNegativeTest.java b/src/test/java/org/postgresql/core/CommandCompleteParserNegativeTest.java index 0e6f82d..8f66834 100644 --- a/src/test/java/org/postgresql/core/CommandCompleteParserNegativeTest.java +++ b/src/test/java/org/postgresql/core/CommandCompleteParserNegativeTest.java @@ -5,22 +5,16 @@ package org.postgresql.core; +import static org.junit.jupiter.api.Assertions.fail; + import org.postgresql.util.PSQLException; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Arrays; -@RunWith(Parameterized.class) public class CommandCompleteParserNegativeTest { - - @Parameterized.Parameter(0) - public String input; - - @Parameterized.Parameters(name = "input={0}") public static Iterable data() { return Arrays.asList(new Object[][]{ {"SELECT 0_0 42"}, @@ -29,12 +23,13 @@ public static Iterable data() { }); } - @Test - public void run() throws PSQLException { + @MethodSource("data") + @ParameterizedTest + void run(String input) throws PSQLException { CommandCompleteParser parser = new CommandCompleteParser(); try { parser.parse(input); - Assert.fail("CommandCompleteParser should throw NumberFormatException for " + input); + fail("CommandCompleteParser should throw NumberFormatException for " + input); } catch (PSQLException e) { Throwable cause = e.getCause(); if (cause == null) { @@ -43,7 +38,7 @@ public void run() throws PSQLException { if (!(cause instanceof NumberFormatException)) { throw e; } - // NumerFormatException is expected + // NumberFormatException is expected } } } diff --git a/src/test/java/org/postgresql/core/CommandCompleteParserTest.java b/src/test/java/org/postgresql/core/CommandCompleteParserTest.java index ce3878d..f1e75e1 100644 --- a/src/test/java/org/postgresql/core/CommandCompleteParserTest.java +++ b/src/test/java/org/postgresql/core/CommandCompleteParserTest.java @@ -5,26 +5,16 @@ package org.postgresql.core; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.util.PSQLException; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Arrays; -@RunWith(Parameterized.class) public class CommandCompleteParserTest { - - @Parameterized.Parameter(0) - public String input; - @Parameterized.Parameter(1) - public long oid; - @Parameterized.Parameter(2) - public long rows; - - @Parameterized.Parameters(name = "input={0}, oid={1}, rows={2}") public static Iterable data() { return Arrays.asList(new Object[][]{ {"SELECT 0", 0, 0}, @@ -44,15 +34,17 @@ public static Iterable data() { (Long.MAX_VALUE / 100), (Long.MAX_VALUE / 100)}, {"CREATE TABLE", 0, 0}, {"CREATE OR DROP OR DELETE TABLE 42", 0, 42}, + {"CALL", 0, -1}, }); } - @Test - public void run() throws PSQLException { + @MethodSource("data") + @ParameterizedTest + void run(String input, long oid, long rows) throws PSQLException { CommandCompleteParser expected = new CommandCompleteParser(); CommandCompleteParser actual = new CommandCompleteParser(); expected.set(oid, rows); actual.parse(input); - Assert.assertEquals(input, expected, actual); + assertEquals(expected, actual, input); } } diff --git a/src/test/java/org/postgresql/core/OidToStringTest.java b/src/test/java/org/postgresql/core/OidToStringTest.java index 5f4586f..f1b6c00 100644 --- a/src/test/java/org/postgresql/core/OidToStringTest.java +++ b/src/test/java/org/postgresql/core/OidToStringTest.java @@ -5,23 +5,16 @@ package org.postgresql.core; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.util.PSQLException; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Arrays; -@RunWith(Parameterized.class) public class OidToStringTest { - @Parameterized.Parameter(0) - public int value; - @Parameterized.Parameter(1) - public String expected; - - @Parameterized.Parameters(name = "expected={1}, value={0}") public static Iterable data() { return Arrays.asList(new Object[][]{ {142, "XML"}, @@ -30,8 +23,9 @@ public static Iterable data() { }); } - @Test - public void run() throws PSQLException { - Assert.assertEquals(expected, Oid.toString(value)); + @MethodSource("data") + @ParameterizedTest + void run(int value, String expected) throws PSQLException { + assertEquals(expected, Oid.toString(value)); } } diff --git a/src/test/java/org/postgresql/core/OidValueOfTest.java b/src/test/java/org/postgresql/core/OidValueOfTest.java index 110af68..f13a3ae 100644 --- a/src/test/java/org/postgresql/core/OidValueOfTest.java +++ b/src/test/java/org/postgresql/core/OidValueOfTest.java @@ -5,23 +5,16 @@ package org.postgresql.core; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.util.PSQLException; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Arrays; -@RunWith(Parameterized.class) public class OidValueOfTest { - @Parameterized.Parameter(0) - public int expected; - @Parameterized.Parameter(1) - public String value; - - @Parameterized.Parameters(name = "expected={0}, value={1}") public static Iterable data() { return Arrays.asList(new Object[][]{ {25, "TEXT"}, @@ -31,8 +24,9 @@ public static Iterable data() { }); } - @Test - public void run() throws PSQLException { - Assert.assertEquals(expected, Oid.valueOf(value)); + @MethodSource("data") + @ParameterizedTest + void run(int expected, String value) throws PSQLException { + assertEquals(expected, Oid.valueOf(value)); } } diff --git a/src/test/java/org/postgresql/core/OidValuesCorrectnessTest.java b/src/test/java/org/postgresql/core/OidValuesCorrectnessTest.java index 8d53394..7216d11 100644 --- a/src/test/java/org/postgresql/core/OidValuesCorrectnessTest.java +++ b/src/test/java/org/postgresql/core/OidValuesCorrectnessTest.java @@ -5,14 +5,17 @@ package org.postgresql.core; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.lang.reflect.Field; import java.sql.ResultSet; @@ -22,17 +25,19 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; /** * Test to check if values in Oid class are correct with Oid values in a database. */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class OidValuesCorrectnessTest extends BaseTest4 { - @Parameterized.Parameter(0) + @Parameter(0) public String oidName; - @Parameterized.Parameter(1) + @Parameter(1) public int oidValue; /** @@ -41,61 +46,67 @@ public class OidValuesCorrectnessTest extends BaseTest4 { */ private static List oidsToIgnore = Arrays.asList( "UNSPECIFIED" //UNSPECIFIED isn't an Oid, it's a value to specify that Oid value is unspecified - ); + ); /** * Map to contain Oid names with server version of their support. * Prevents that some Oid values will be tested with a database not supporting given Oid. */ - private static Map oidsMinimumVersions = new HashMap() {{ - put("JSON", ServerVersion.v9_2); - put("JSON_ARRAY", ServerVersion.v9_2); - put("JSONB", ServerVersion.v9_4); - put("JSONB_ARRAY", ServerVersion.v9_4); - put("MACADDR8", ServerVersion.v10); - }}; + private static Map oidsMinimumVersions; + + static { + oidsMinimumVersions = new HashMap<>(); + oidsMinimumVersions.put("JSON", ServerVersion.v9_2); + oidsMinimumVersions.put("JSON_ARRAY", ServerVersion.v9_2); + oidsMinimumVersions.put("JSONB", ServerVersion.v9_4); + oidsMinimumVersions.put("JSONB_ARRAY", ServerVersion.v9_4); + oidsMinimumVersions.put("MACADDR8", ServerVersion.v10); + } /** * Map to contain Oid names with their proper names from pg_type table (typname) if they are different. * Helps in situation when variable name in Oid class isn't the same as typname in pg_type table. */ - private static Map oidTypeNames = new HashMap() {{ - put("INT2_ARRAY", "_INT2"); - put("INT4_ARRAY", "_INT4"); - put("INT8_ARRAY", "_INT8"); - put("TEXT_ARRAY", "_TEXT"); - put("NUMERIC_ARRAY", "_NUMERIC"); - put("FLOAT4_ARRAY", "_FLOAT4"); - put("FLOAT8_ARRAY", "_FLOAT8"); - put("BOOL_ARRAY", "_BOOL"); - put("DATE_ARRAY", "_DATE"); - put("TIME_ARRAY", "_TIME"); - put("TIMETZ_ARRAY", "_TIMETZ"); - put("TIMESTAMP_ARRAY", "_TIMESTAMP"); - put("TIMESTAMPTZ_ARRAY", "_TIMESTAMPTZ"); - put("BYTEA_ARRAY", "_BYTEA"); - put("VARCHAR_ARRAY", "_VARCHAR"); - put("OID_ARRAY", "_OID"); - put("BPCHAR_ARRAY", "_BPCHAR"); - put("MONEY_ARRAY", "_MONEY"); - put("NAME_ARRAY", "_NAME"); - put("BIT_ARRAY", "_BIT"); - put("INTERVAL_ARRAY", "_INTERVAl"); - put("CHAR_ARRAY", "_CHAR"); - put("VARBIT_ARRAY", "_VARBIT"); - put("UUID_ARRAY", "_UUID"); - put("XML_ARRAY", "_XML"); - put("POINT_ARRAY", "_POINT"); - put("JSONB_ARRAY", "_JSONB"); - put("JSON_ARRAY", "_JSON"); - put("REF_CURSOR", "REFCURSOR"); - put("REF_CURSOR_ARRAY", "_REFCURSOR"); - }}; - - @Parameterized.Parameters(name = "oidName={0}, oidValue={1}") + private static Map oidTypeNames; + + static { + oidTypeNames = new HashMap<>(); + oidTypeNames.put("BOX_ARRAY", "_BOX"); + oidTypeNames.put("INT2_ARRAY", "_INT2"); + oidTypeNames.put("INT4_ARRAY", "_INT4"); + oidTypeNames.put("INT8_ARRAY", "_INT8"); + oidTypeNames.put("TEXT_ARRAY", "_TEXT"); + oidTypeNames.put("NUMERIC_ARRAY", "_NUMERIC"); + oidTypeNames.put("FLOAT4_ARRAY", "_FLOAT4"); + oidTypeNames.put("FLOAT8_ARRAY", "_FLOAT8"); + oidTypeNames.put("BOOL_ARRAY", "_BOOL"); + oidTypeNames.put("DATE_ARRAY", "_DATE"); + oidTypeNames.put("TIME_ARRAY", "_TIME"); + oidTypeNames.put("TIMETZ_ARRAY", "_TIMETZ"); + oidTypeNames.put("TIMESTAMP_ARRAY", "_TIMESTAMP"); + oidTypeNames.put("TIMESTAMPTZ_ARRAY", "_TIMESTAMPTZ"); + oidTypeNames.put("BYTEA_ARRAY", "_BYTEA"); + oidTypeNames.put("VARCHAR_ARRAY", "_VARCHAR"); + oidTypeNames.put("OID_ARRAY", "_OID"); + oidTypeNames.put("BPCHAR_ARRAY", "_BPCHAR"); + oidTypeNames.put("MONEY_ARRAY", "_MONEY"); + oidTypeNames.put("NAME_ARRAY", "_NAME"); + oidTypeNames.put("BIT_ARRAY", "_BIT"); + oidTypeNames.put("INTERVAL_ARRAY", "_INTERVAl"); + oidTypeNames.put("CHAR_ARRAY", "_CHAR"); + oidTypeNames.put("VARBIT_ARRAY", "_VARBIT"); + oidTypeNames.put("UUID_ARRAY", "_UUID"); + oidTypeNames.put("XML_ARRAY", "_XML"); + oidTypeNames.put("POINT_ARRAY", "_POINT"); + oidTypeNames.put("JSONB_ARRAY", "_JSONB"); + oidTypeNames.put("JSON_ARRAY", "_JSON"); + oidTypeNames.put("REF_CURSOR", "REFCURSOR"); + oidTypeNames.put("REF_CURSOR_ARRAY", "_REFCURSOR"); + } + public static Iterable data() throws IllegalAccessException { Field[] fields = Oid.class.getFields(); - List data = new ArrayList(); + List data = new ArrayList<>(); for (Field field : fields) { if (!oidsToIgnore.contains(field.getName())) { @@ -114,22 +125,19 @@ public static Iterable data() throws IllegalAccessException { public void testValue() throws SQLException { // check if Oid can be tested with given database by checking version if (oidsMinimumVersions.containsKey(oidName)) { - Assume.assumeTrue(TestUtil.haveMinimumServerVersion(con, oidsMinimumVersions.get(oidName))); + assumeTrue(TestUtil.haveMinimumServerVersion(con, oidsMinimumVersions.get(oidName))); } String typeName = oidTypeNames.getOrDefault(oidName, oidName); Statement stmt = con.createStatement(); ResultSet resultSet; - stmt.execute("select oid from pg_type where typname = '" + typeName.toLowerCase() + "'"); + stmt.execute("select oid from pg_type where typname = '" + typeName.toLowerCase(Locale.ROOT) + "'"); resultSet = stmt.getResultSet(); // resultSet have to have next row - Assert.assertTrue("Oid value doesn't exist for oid " + oidName + ";with used type: " + typeName, - resultSet.next()); + assertTrue(resultSet.next(), () -> "Oid value doesn't exist for oid " + oidName + ";with used type: " + typeName); // check if expected value from a database is the same as value in Oid class - Assert.assertEquals("Wrong value for oid: " + oidName + ";with used type: " + typeName, - resultSet.getInt(1), oidValue); - + assertEquals(resultSet.getInt(1), oidValue, () -> "Wrong value for oid: " + oidName + ";with used type: " + typeName); } } diff --git a/src/test/java/org/postgresql/core/ParserTest.java b/src/test/java/org/postgresql/core/ParserTest.java index a302794..20b2101 100644 --- a/src/test/java/org/postgresql/core/ParserTest.java +++ b/src/test/java/org/postgresql/core/ParserTest.java @@ -5,14 +5,15 @@ package org.postgresql.core; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.jdbc.EscapeSyntaxCallMode; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.sql.SQLException; import java.util.List; @@ -21,101 +22,101 @@ * Test cases for the Parser. * @author Jeremy Whiting jwhiting@redhat.com */ -public class ParserTest { +class ParserTest { /** * Test to make sure delete command is detected by parser and detected via * api. Mix up the case of the command to check detection continues to work. */ @Test - public void testDeleteCommandParsing() { + void deleteCommandParsing() { char[] command = new char[6]; "DELETE".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse upper case command.", Parser.parseDeleteKeyword(command, 0)); + assertTrue(Parser.parseDeleteKeyword(command, 0), "Failed to correctly parse upper case command."); "DelEtE".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseDeleteKeyword(command, 0)); + assertTrue(Parser.parseDeleteKeyword(command, 0), "Failed to correctly parse mixed case command."); "deleteE".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseDeleteKeyword(command, 0)); + assertTrue(Parser.parseDeleteKeyword(command, 0), "Failed to correctly parse mixed case command."); "delete".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse lower case command.", Parser.parseDeleteKeyword(command, 0)); + assertTrue(Parser.parseDeleteKeyword(command, 0), "Failed to correctly parse lower case command."); "Delete".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseDeleteKeyword(command, 0)); + assertTrue(Parser.parseDeleteKeyword(command, 0), "Failed to correctly parse mixed case command."); } /** * Test UPDATE command parsing. */ @Test - public void testUpdateCommandParsing() { + void updateCommandParsing() { char[] command = new char[6]; "UPDATE".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse upper case command.", Parser.parseUpdateKeyword(command, 0)); + assertTrue(Parser.parseUpdateKeyword(command, 0), "Failed to correctly parse upper case command."); "UpDateE".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseUpdateKeyword(command, 0)); + assertTrue(Parser.parseUpdateKeyword(command, 0), "Failed to correctly parse mixed case command."); "updatE".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseUpdateKeyword(command, 0)); + assertTrue(Parser.parseUpdateKeyword(command, 0), "Failed to correctly parse mixed case command."); "Update".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseUpdateKeyword(command, 0)); + assertTrue(Parser.parseUpdateKeyword(command, 0), "Failed to correctly parse mixed case command."); "update".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse lower case command.", Parser.parseUpdateKeyword(command, 0)); + assertTrue(Parser.parseUpdateKeyword(command, 0), "Failed to correctly parse lower case command."); } /** * Test MOVE command parsing. */ @Test - public void testMoveCommandParsing() { + void moveCommandParsing() { char[] command = new char[4]; "MOVE".getChars(0, 4, command, 0); - assertTrue("Failed to correctly parse upper case command.", Parser.parseMoveKeyword(command, 0)); + assertTrue(Parser.parseMoveKeyword(command, 0), "Failed to correctly parse upper case command."); "mOVe".getChars(0, 4, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseMoveKeyword(command, 0)); + assertTrue(Parser.parseMoveKeyword(command, 0), "Failed to correctly parse mixed case command."); "movE".getChars(0, 4, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseMoveKeyword(command, 0)); + assertTrue(Parser.parseMoveKeyword(command, 0), "Failed to correctly parse mixed case command."); "Move".getChars(0, 4, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseMoveKeyword(command, 0)); + assertTrue(Parser.parseMoveKeyword(command, 0), "Failed to correctly parse mixed case command."); "move".getChars(0, 4, command, 0); - assertTrue("Failed to correctly parse lower case command.", Parser.parseMoveKeyword(command, 0)); + assertTrue(Parser.parseMoveKeyword(command, 0), "Failed to correctly parse lower case command."); } /** * Test WITH command parsing. */ @Test - public void testWithCommandParsing() { + void withCommandParsing() { char[] command = new char[4]; "WITH".getChars(0, 4, command, 0); - assertTrue("Failed to correctly parse upper case command.", Parser.parseWithKeyword(command, 0)); + assertTrue(Parser.parseWithKeyword(command, 0), "Failed to correctly parse upper case command."); "wITh".getChars(0, 4, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseWithKeyword(command, 0)); + assertTrue(Parser.parseWithKeyword(command, 0), "Failed to correctly parse mixed case command."); "witH".getChars(0, 4, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseWithKeyword(command, 0)); + assertTrue(Parser.parseWithKeyword(command, 0), "Failed to correctly parse mixed case command."); "With".getChars(0, 4, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseWithKeyword(command, 0)); + assertTrue(Parser.parseWithKeyword(command, 0), "Failed to correctly parse mixed case command."); "with".getChars(0, 4, command, 0); - assertTrue("Failed to correctly parse lower case command.", Parser.parseWithKeyword(command, 0)); + assertTrue(Parser.parseWithKeyword(command, 0), "Failed to correctly parse lower case command."); } /** * Test SELECT command parsing. */ @Test - public void testSelectCommandParsing() { + void selectCommandParsing() { char[] command = new char[6]; "SELECT".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse upper case command.", Parser.parseSelectKeyword(command, 0)); + assertTrue(Parser.parseSelectKeyword(command, 0), "Failed to correctly parse upper case command."); "sELect".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseSelectKeyword(command, 0)); + assertTrue(Parser.parseSelectKeyword(command, 0), "Failed to correctly parse mixed case command."); "selecT".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseSelectKeyword(command, 0)); + assertTrue(Parser.parseSelectKeyword(command, 0), "Failed to correctly parse mixed case command."); "Select".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse mixed case command.", Parser.parseSelectKeyword(command, 0)); + assertTrue(Parser.parseSelectKeyword(command, 0), "Failed to correctly parse mixed case command."); "select".getChars(0, 6, command, 0); - assertTrue("Failed to correctly parse lower case command.", Parser.parseSelectKeyword(command, 0)); + assertTrue(Parser.parseSelectKeyword(command, 0), "Failed to correctly parse lower case command."); } @Test - public void testEscapeProcessing() throws Exception { + void escapeProcessing() throws Exception { assertEquals("DATE '1999-01-09'", Parser.replaceProcessing("{d '1999-01-09'}", true, false)); assertEquals("DATE '1999-01-09'", Parser.replaceProcessing("{D '1999-01-09'}", true, false)); assertEquals("TIME '20:00:03'", Parser.replaceProcessing("{t '20:00:03'}", true, false)); @@ -137,151 +138,182 @@ public void testEscapeProcessing() throws Exception { } @Test - public void testModifyJdbcCall() throws SQLException { - assertEquals("select * from pack_getValue(?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); - assertEquals("select * from pack_getValue(?,?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue(?) }", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); - assertEquals("select * from pack_getValue(?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue()}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); - assertEquals("select * from pack_getValue(?,?,?,?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue(?,?,?) }", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); - assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); - assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); - assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.CALL).getSql()); - assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); - assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); - assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.CALL).getSql()); - assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); - assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); - assertEquals("call lower(?,?)", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.CALL).getSql()); - assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); - assertEquals("call lower(?,?)", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); - assertEquals("call lower(?,?)", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.CALL).getSql()); + void modifyJdbcCall() throws SQLException { + ProtocolVersion protocolVersion = ProtocolVersion.fromMajorMinor(3,0); + assertEquals("select * from pack_getValue(?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue}", true, ServerVersion.v9_6.getVersionNum(), + EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from pack_getValue(?,?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue(?) }", true, ServerVersion.v9_6.getVersionNum(), + EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from pack_getValue(?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue()}", true, ServerVersion.v9_6.getVersionNum(), + EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from pack_getValue(?,?,?,?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue(?,?,?) }", true, ServerVersion.v9_6.getVersionNum(), + EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v9_6.getVersionNum(), + EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v9_6.getVersionNum(), + EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v9_6.getVersionNum(), + EscapeSyntaxCallMode.CALL).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v9_6.getVersionNum(), + EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v9_6.getVersionNum(), + EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v9_6.getVersionNum(), + EscapeSyntaxCallMode.CALL).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v11.getVersionNum(), + EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v11.getVersionNum(), + EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); + assertEquals("call lower(?,?)", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v11.getVersionNum(), + EscapeSyntaxCallMode.CALL).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v11.getVersionNum(), + EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("call lower(?,?)", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v11.getVersionNum(), + EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); + assertEquals("call lower(?,?)", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v11.getVersionNum(), + EscapeSyntaxCallMode.CALL).getSql()); } @Test - public void testUnterminatedEscape() throws Exception { + void unterminatedEscape() throws Exception { assertEquals("{oj ", Parser.replaceProcessing("{oj ", true, false)); } @Test - @Ignore(value = "returning in the select clause is hard to distinguish from insert ... returning *") - public void insertSelectFakeReturning() throws SQLException { + @Disabled(value = "returning in the select clause is hard to distinguish from insert ... returning *") + void insertSelectFakeReturning() throws SQLException { String query = "insert test(id, name) select 1, 'value' as RETURNING from test2"; List qry = Parser.parseJdbcSql( query, true, true, true, true, true); boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent(); - Assert.assertFalse("Query does not have returning clause " + query, returningKeywordPresent); + assertFalse(returningKeywordPresent, "Query does not have returning clause " + query); } @Test - public void insertSelectReturning() throws SQLException { + void insertSelectReturning() throws SQLException { String query = "insert test(id, name) select 1, 'value' from test2 RETURNING id"; List qry = Parser.parseJdbcSql( query, true, true, true, true, true); boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent(); - Assert.assertTrue("Query has a returning clause " + query, returningKeywordPresent); + assertTrue(returningKeywordPresent, "Query has a returning clause " + query); } @Test - public void insertReturningInWith() throws SQLException { + void insertReturningInWith() throws SQLException { String query = "with x as (insert into mytab(x) values(1) returning x) insert test(id, name) select 1, 'value' from test2"; List qry = Parser.parseJdbcSql( query, true, true, true, true, true); boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent(); - Assert.assertFalse("There's no top-level <> clause " + query, returningKeywordPresent); + assertFalse(returningKeywordPresent, "There's no top-level <> clause " + query); } @Test - public void insertBatchedReWriteOnConflict() throws SQLException { + void insertBatchedReWriteOnConflict() throws SQLException { String query = "insert into test(id, name) values (:id,:name) ON CONFLICT (id) DO NOTHING"; List qry = Parser.parseJdbcSql(query, true, true, true, true, true); SqlCommand command = qry.get(0).getCommand(); - Assert.assertEquals(34, command.getBatchRewriteValuesBraceOpenPosition()); - Assert.assertEquals(44, command.getBatchRewriteValuesBraceClosePosition()); + assertEquals(34, command.getBatchRewriteValuesBraceOpenPosition()); + assertEquals(44, command.getBatchRewriteValuesBraceClosePosition()); } @Test - public void insertBatchedReWriteOnConflictUpdateBind() throws SQLException { + void insertBatchedReWriteOnConflictUpdateBind() throws SQLException { String query = "insert into test(id, name) values (?,?) ON CONFLICT (id) UPDATE SET name=?"; List qry = Parser.parseJdbcSql(query, true, true, true, true, true); SqlCommand command = qry.get(0).getCommand(); - Assert.assertFalse("update set name=? is NOT compatible with insert rewrite", command.isBatchedReWriteCompatible()); + assertFalse(command.isBatchedReWriteCompatible(), "update set name=? is NOT compatible with insert rewrite"); } @Test - public void insertBatchedReWriteOnConflictUpdateConstant() throws SQLException { + void insertBatchedReWriteOnConflictUpdateConstant() throws SQLException { String query = "insert into test(id, name) values (?,?) ON CONFLICT (id) UPDATE SET name='default'"; List qry = Parser.parseJdbcSql(query, true, true, true, true, true); SqlCommand command = qry.get(0).getCommand(); - Assert.assertTrue("update set name='default' is compatible with insert rewrite", command.isBatchedReWriteCompatible()); + assertTrue(command.isBatchedReWriteCompatible(), "update set name='default' is compatible with insert rewrite"); } @Test - public void insertMultiInsert() throws SQLException { + void insertMultiInsert() throws SQLException { String query = "insert into test(id, name) values (:id,:name),(:id,:name) ON CONFLICT (id) DO NOTHING"; List qry = Parser.parseJdbcSql(query, true, true, true, true, true); SqlCommand command = qry.get(0).getCommand(); - Assert.assertEquals(34, command.getBatchRewriteValuesBraceOpenPosition()); - Assert.assertEquals(56, command.getBatchRewriteValuesBraceClosePosition()); + assertEquals(34, command.getBatchRewriteValuesBraceOpenPosition()); + assertEquals(56, command.getBatchRewriteValuesBraceClosePosition()); } @Test - public void valuesTableParse() throws SQLException { + void valuesTableParse() throws SQLException { String query = "insert into values_table (id, name) values (?,?)"; List qry = Parser.parseJdbcSql(query, true, true, true, true, true); SqlCommand command = qry.get(0).getCommand(); - Assert.assertEquals(43,command.getBatchRewriteValuesBraceOpenPosition()); - Assert.assertEquals(49,command.getBatchRewriteValuesBraceClosePosition()); + assertEquals(43, command.getBatchRewriteValuesBraceOpenPosition()); + assertEquals(49, command.getBatchRewriteValuesBraceClosePosition()); query = "insert into table_values (id, name) values (?,?)"; qry = Parser.parseJdbcSql(query, true, true, true, true, true); command = qry.get(0).getCommand(); - Assert.assertEquals(43,command.getBatchRewriteValuesBraceOpenPosition()); - Assert.assertEquals(49,command.getBatchRewriteValuesBraceClosePosition()); + assertEquals(43, command.getBatchRewriteValuesBraceOpenPosition()); + assertEquals(49, command.getBatchRewriteValuesBraceClosePosition()); } @Test - public void createTableParseWithOnDeleteClause() throws SQLException { + void createTableParseWithOnDeleteClause() throws SQLException { String[] returningColumns = {"*"}; String query = "create table \"testTable\" (\"id\" INT SERIAL NOT NULL PRIMARY KEY, \"foreignId\" INT REFERENCES \"otherTable\" (\"id\") ON DELETE NO ACTION)"; List qry = Parser.parseJdbcSql(query, true, true, true, true, true, returningColumns); SqlCommand command = qry.get(0).getCommand(); - Assert.assertFalse("No returning keyword should be present", command.isReturningKeywordPresent()); - Assert.assertEquals(SqlCommandType.CREATE, command.getType()); + assertFalse(command.isReturningKeywordPresent(), "No returning keyword should be present"); + assertEquals(SqlCommandType.CREATE, command.getType()); } @Test - public void createTableParseWithOnUpdateClause() throws SQLException { + void createTableParseWithOnUpdateClause() throws SQLException { String[] returningColumns = {"*"}; String query = "create table \"testTable\" (\"id\" INT SERIAL NOT NULL PRIMARY KEY, \"foreignId\" INT REFERENCES \"otherTable\" (\"id\")) ON UPDATE NO ACTION"; List qry = Parser.parseJdbcSql(query, true, true, true, true, true, returningColumns); SqlCommand command = qry.get(0).getCommand(); - Assert.assertFalse("No returning keyword should be present", command.isReturningKeywordPresent()); - Assert.assertEquals(SqlCommandType.CREATE, command.getType()); + assertFalse(command.isReturningKeywordPresent(), "No returning keyword should be present"); + assertEquals(SqlCommandType.CREATE, command.getType()); } @Test - public void alterTableParseWithOnDeleteClause() throws SQLException { + void alterTableParseWithOnDeleteClause() throws SQLException { String[] returningColumns = {"*"}; String query = "alter table \"testTable\" ADD \"foreignId\" INT REFERENCES \"otherTable\" (\"id\") ON DELETE NO ACTION"; List qry = Parser.parseJdbcSql(query, true, true, true, true, true, returningColumns); SqlCommand command = qry.get(0).getCommand(); - Assert.assertFalse("No returning keyword should be present", command.isReturningKeywordPresent()); - Assert.assertEquals(SqlCommandType.ALTER, command.getType()); + assertFalse(command.isReturningKeywordPresent(), "No returning keyword should be present"); + assertEquals(SqlCommandType.ALTER, command.getType()); } @Test - public void alterTableParseWithOnUpdateClause() throws SQLException { + void alterTableParseWithOnUpdateClause() throws SQLException { String[] returningColumns = {"*"}; String query = "alter table \"testTable\" ADD \"foreignId\" INT REFERENCES \"otherTable\" (\"id\") ON UPDATE RESTRICT"; List qry = Parser.parseJdbcSql(query, true, true, true, true, true, returningColumns); SqlCommand command = qry.get(0).getCommand(); - Assert.assertFalse("No returning keyword should be present", command.isReturningKeywordPresent()); - Assert.assertEquals(SqlCommandType.ALTER, command.getType()); + assertFalse(command.isReturningKeywordPresent(), "No returning keyword should be present"); + assertEquals(SqlCommandType.ALTER, command.getType()); + } + + @Test + void parseV14functions() throws SQLException { + String[] returningColumns = {"*"}; + String query = "CREATE OR REPLACE FUNCTION asterisks(n int)\n" + + " RETURNS SETOF text\n" + + " LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE\n" + + "BEGIN ATOMIC\n" + + "SELECT repeat('*', g) FROM generate_series (1, n) g; \n" + + "END;"; + List qry = Parser.parseJdbcSql(query, true, true, true, true, true, returningColumns); + assertNotNull(qry); + assertEquals(1, qry.size(), "There should only be one query returned here"); } } diff --git a/src/test/java/org/postgresql/core/ReturningParserTest.java b/src/test/java/org/postgresql/core/ReturningParserTest.java index 6acd8cd..3b8292e 100644 --- a/src/test/java/org/postgresql/core/ReturningParserTest.java +++ b/src/test/java/org/postgresql/core/ReturningParserTest.java @@ -5,33 +5,19 @@ package org.postgresql.core; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.List; -@RunWith(Parameterized.class) public class ReturningParserTest { - private final String columnName; - private final String returning; - private final String prefix; - private final String suffix; - - public ReturningParserTest(String columnName, String returning, String prefix, String suffix) { - this.columnName = columnName; - this.returning = returning; - this.prefix = prefix; - this.suffix = suffix; - } - - @Parameterized.Parameters(name = "columnName={2} {0} {3}, returning={2} {1} {3}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); String[] delimiters = {"", "_", "3", "*", " "}; @@ -47,21 +33,22 @@ public static Iterable data() { return ids; } - @Test - public void test() throws SQLException { + @MethodSource("data") + @ParameterizedTest(name = "columnName={2} {0} {3}, returning={2} {1} {3}") + void test(String columnName, String returning, String prefix, String suffix) throws SQLException { String query = "insert into\"prep\"(a, " + prefix + columnName + suffix + ")values(1,2)" + prefix + returning + suffix; List qry = Parser.parseJdbcSql(query, true, true, true, true, true); boolean returningKeywordPresent = qry.get(0).command.isReturningKeywordPresent(); - boolean expectedReturning = this.returning.equalsIgnoreCase("returning") + boolean expectedReturning = "returning".equalsIgnoreCase(returning) && (prefix.isEmpty() || !Character.isJavaIdentifierStart(prefix.charAt(0))) && (suffix.isEmpty() || !Character.isJavaIdentifierPart(suffix.charAt(0))); if (expectedReturning != returningKeywordPresent) { - Assert.assertEquals("Wrong detected in SQL " + query, - expectedReturning, - returningKeywordPresent); + assertEquals(expectedReturning, + returningKeywordPresent, + "Wrong detected in SQL " + query); } } diff --git a/src/test/java/org/postgresql/core/UTF8EncodingTest.java b/src/test/java/org/postgresql/core/UTF8EncodingTest.java index f6d4360..9e3e846 100644 --- a/src/test/java/org/postgresql/core/UTF8EncodingTest.java +++ b/src/test/java/org/postgresql/core/UTF8EncodingTest.java @@ -5,34 +5,25 @@ package org.postgresql.core; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.ArrayList; import java.util.List; -@RunWith(Parameterized.class) public class UTF8EncodingTest { private static final int STEP = 8 * 1024; - @Parameterized.Parameter(0) - public Encoding encoding; - - @Parameterized.Parameter(1) - public String string; - - @Parameterized.Parameters(name = "string={1}, encoding={0}") public static Iterable data() { final StringBuilder reallyLongString = new StringBuilder(1024 * 1024); - for (int i = 0; i < 185000; ++i) { + for (int i = 0; i < 185000; i++) { reallyLongString.append(i); } - final List strings = new ArrayList(150); + final List strings = new ArrayList<>(150); strings.add("short simple"); strings.add("longer but still not really all that long"); strings.add(reallyLongString.toString()); @@ -46,7 +37,7 @@ public static Iterable data() { for (int i = 1; i < 0xd800; i += STEP) { int count = (i + STEP) > 0xd800 ? 0xd800 - i : STEP; char[] testChars = new char[count]; - for (int j = 0; j < count; ++j) { + for (int j = 0; j < count; j++) { testChars[j] = (char) (i + j); } @@ -56,7 +47,7 @@ public static Iterable data() { for (int i = 0xe000; i < 0x10000; i += STEP) { int count = (i + STEP) > 0x10000 ? 0x10000 - i : STEP; char[] testChars = new char[count]; - for (int j = 0; j < count; ++j) { + for (int j = 0; j < count; j++) { testChars[j] = (char) (i + j); } @@ -66,7 +57,7 @@ public static Iterable data() { for (int i = 0x10000; i < 0x110000; i += STEP) { int count = (i + STEP) > 0x110000 ? 0x110000 - i : STEP; char[] testChars = new char[count * 2]; - for (int j = 0; j < count; ++j) { + for (int j = 0; j < count; j++) { testChars[j * 2] = (char) (0xd800 + ((i + j - 0x10000) >> 10)); testChars[j * 2 + 1] = (char) (0xdc00 + ((i + j - 0x10000) & 0x3ff)); } @@ -74,15 +65,20 @@ public static Iterable data() { strings.add(new String(testChars)); } - final List data = new ArrayList(strings.size() * 2); + final List data = new ArrayList<>(strings.size() * 2); for (String string : strings) { - data.add(new Object[] { Encoding.getDatabaseEncoding("UNICODE"), string }); + String shortString = string; + if (shortString != null && shortString.length() > 1000) { + shortString = shortString.substring(0, 100) + "...(" + string.length() + " chars)"; + } + data.add(new Object[]{Encoding.getDatabaseEncoding("UNICODE"), string, shortString}); } return data; } - @Test - public void test() throws Exception { + @MethodSource("data") + @ParameterizedTest(name = "string={2}, encoding={0}") + void test(Encoding encoding, String string, String shortString) throws Exception { final byte[] encoded = encoding.encode(string); assertEquals(string, encoding.decode(encoded)); } diff --git a/src/test/java/org/postgresql/core/v3/V3ParameterListTests.java b/src/test/java/org/postgresql/core/v3/V3ParameterListTests.java index 200004f..e26d5e4 100644 --- a/src/test/java/org/postgresql/core/v3/V3ParameterListTests.java +++ b/src/test/java/org/postgresql/core/v3/V3ParameterListTests.java @@ -5,10 +5,10 @@ package org.postgresql.core.v3; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.SQLException; @@ -18,11 +18,11 @@ * @author Jeremy Whiting jwhiting@redhat.com * */ -public class V3ParameterListTests { +class V3ParameterListTests { private TypeTransferModeRegistry transferModeRegistry; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { transferModeRegistry = new TypeTransferModeRegistry() { @Override public boolean useBinaryForSend(int oid) { @@ -44,7 +44,7 @@ public boolean useBinaryForReceive(int oid) { * raised exception if setting parameter fails. */ @Test - public void testMergeOfParameterLists() throws SQLException { + void mergeOfParameterLists() throws SQLException { SimpleParameterList s1SPL = new SimpleParameterList(8, transferModeRegistry); s1SPL.setIntParameter(1, 1); s1SPL.setIntParameter(2, 2); @@ -59,7 +59,6 @@ public void testMergeOfParameterLists() throws SQLException { s1SPL.appendAll(s2SPL); assertEquals( - "Expected string representation of values does not match outcome.", - "<[1 ,2 ,3 ,4 ,5 ,6 ,7 ,8]>", s1SPL.toString()); + "<[('1'::int4) ,('2'::int4) ,('3'::int4) ,('4'::int4) ,('5'::int4) ,('6'::int4) ,('7'::int4) ,('8'::int4)]>", s1SPL.toString(), "Expected string representation of values does not match outcome."); } } diff --git a/src/test/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchCacheTest.java b/src/test/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchCacheTest.java index 4d42f84..315570e 100644 --- a/src/test/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchCacheTest.java +++ b/src/test/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchCacheTest.java @@ -5,17 +5,19 @@ package org.postgresql.core.v3.adaptivefetch; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import org.postgresql.PGProperty; import org.postgresql.core.ParameterList; import org.postgresql.core.Query; import org.postgresql.core.SqlCommand; +import org.postgresql.core.v3.SqlSerializationContext; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.lang.reflect.Field; import java.sql.SQLException; @@ -25,23 +27,23 @@ /** * Unit tests for AdaptiveFetchCache class. */ -public class AdaptiveFetchCacheTest { +class AdaptiveFetchCacheTest { private AdaptiveFetchCache adaptiveFetchCache; private int size; // Strings containing variables names in AdaptiveFetchCache class - private static String infoMapVariableName = "adaptiveFetchInfoMap"; - private static String minimumSizeVariableName = "minimumAdaptiveFetchSize"; - private static String maximumSizeVariableName = "maximumAdaptiveFetchSize"; - private static String adaptiveFetchVariableName = "adaptiveFetch"; - private static String maximumBufferSizeVariableName = "maximumResultBufferSize"; + private static final String infoMapVariableName = "adaptiveFetchInfoMap"; + private static final String minimumSizeVariableName = "minimumAdaptiveFetchSize"; + private static final String maximumSizeVariableName = "maximumAdaptiveFetchSize"; + private static final String adaptiveFetchVariableName = "adaptiveFetch"; + private static final String maximumBufferSizeVariableName = "maximumResultBufferSize"; /** * Simple setup to create new AdaptiveFetchCache with buffer size 1000. */ - @Before - public void setUp() throws SQLException { + @BeforeEach + void setUp() throws SQLException { Properties properties = new Properties(); size = 1000; adaptiveFetchCache = new AdaptiveFetchCache(size, properties); @@ -51,10 +53,10 @@ public void setUp() throws SQLException { * Tests for calling constructor with empty properties (just asserts after setUp). */ @Test - public void testConstructorDefault() throws NoSuchFieldException, IllegalAccessException { + void constructorDefault() throws NoSuchFieldException, IllegalAccessException { assertNotNull(getInfoMapVariable()); assertEquals(size, getMaximumBufferVariable()); - assertEquals(false, getAdaptiveFetchVariable()); + assertFalse(getAdaptiveFetchVariable()); assertEquals(0, getMinimumSizeVariable()); assertEquals(-1, getMaximumSizeVariable()); } @@ -63,7 +65,7 @@ public void testConstructorDefault() throws NoSuchFieldException, IllegalAccessE * Test for calling constructor with information about adaptiveFetch property. */ @Test - public void testConstructorWithAdaptiveFetch() + void constructorWithAdaptiveFetch() throws SQLException, NoSuchFieldException, IllegalAccessException { Properties properties = new Properties(); boolean expectedValue = true; @@ -82,7 +84,7 @@ public void testConstructorWithAdaptiveFetch() * Test for calling constructor with information about adaptiveFetchMinimum property. */ @Test - public void testConstructorWithMinimumSize() + void constructorWithMinimumSize() throws SQLException, NoSuchFieldException, IllegalAccessException { Properties properties = new Properties(); int expectedValue = 100; @@ -92,7 +94,7 @@ public void testConstructorWithMinimumSize() assertNotNull(getInfoMapVariable()); assertEquals(size, getMaximumBufferVariable()); - assertEquals(false, getAdaptiveFetchVariable()); + assertFalse(getAdaptiveFetchVariable()); assertEquals(expectedValue, getMinimumSizeVariable()); assertEquals(-1, getMaximumSizeVariable()); } @@ -101,7 +103,7 @@ public void testConstructorWithMinimumSize() * Test for calling constructor with information about adaptiveFetchMaximum property. */ @Test - public void testConstructorWithMaximumSize() + void constructorWithMaximumSize() throws SQLException, NoSuchFieldException, IllegalAccessException { Properties properties = new Properties(); int expectedValue = 100; @@ -111,7 +113,7 @@ public void testConstructorWithMaximumSize() assertNotNull(getInfoMapVariable()); assertEquals(size, getMaximumBufferVariable()); - assertEquals(false, getAdaptiveFetchVariable()); + assertFalse(getAdaptiveFetchVariable()); assertEquals(0, getMinimumSizeVariable()); assertEquals(expectedValue, getMaximumSizeVariable()); } @@ -121,7 +123,7 @@ public void testConstructorWithMaximumSize() * adaptiveFetchMaximum properties. */ @Test - public void testConstructorWithAllProperties() + void constructorWithAllProperties() throws SQLException, NoSuchFieldException, IllegalAccessException { Properties properties = new Properties(); boolean expectedAdaptiveFetchValue = false; @@ -145,7 +147,7 @@ public void testConstructorWithAllProperties() * Test for calling addNewQuery method. */ @Test - public void testAddingSingleQuery() throws NoSuchFieldException, IllegalAccessException { + void addingSingleQuery() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = true; @@ -161,7 +163,7 @@ public void testAddingSingleQuery() throws NoSuchFieldException, IllegalAccessEx * Test for calling addNewQuery method, but adaptiveFetch is set to false. */ @Test - public void testAddingSingleQueryWithoutAdaptiveFetch() + void addingSingleQueryWithoutAdaptiveFetch() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = false; @@ -179,7 +181,7 @@ public void testAddingSingleQueryWithoutAdaptiveFetch() * once, with counter set as 2. */ @Test - public void testAddingSameQueryTwoTimes() throws NoSuchFieldException, IllegalAccessException { + void addingSameQueryTwoTimes() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = true; @@ -198,7 +200,7 @@ public void testAddingSameQueryTwoTimes() throws NoSuchFieldException, IllegalAc * false. The query shouldn't be added. */ @Test - public void testAddingSameQueryTwoTimesWithoutAdaptiveFetch() + void addingSameQueryTwoTimesWithoutAdaptiveFetch() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = false; @@ -217,7 +219,7 @@ public void testAddingSameQueryTwoTimesWithoutAdaptiveFetch() * added. */ @Test - public void testAddingTwoDifferentQueries() throws NoSuchFieldException, IllegalAccessException { + void addingTwoDifferentQueries() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; String expectedQuery2 = "test-query-2"; boolean adaptiveFetch = true; @@ -239,7 +241,7 @@ public void testAddingTwoDifferentQueries() throws NoSuchFieldException, Illegal * false. Both queries shouldn't be added. */ @Test - public void testAddingTwoDifferentQueriesWithoutAdaptiveFetch() + void addingTwoDifferentQueriesWithoutAdaptiveFetch() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; String expectedQuery2 = "test-query-2"; @@ -258,7 +260,7 @@ public void testAddingTwoDifferentQueriesWithoutAdaptiveFetch() * Test for calling getAdaptiveFetch method with value true. */ @Test - public void testGettingAdaptiveFetchIfTrue() + void gettingAdaptiveFetchIfTrue() throws NoSuchFieldException, IllegalAccessException { boolean expectedResult = true; @@ -271,7 +273,7 @@ public void testGettingAdaptiveFetchIfTrue() * Test for calling getAdaptiveFetch method with value false. */ @Test - public void testGettingAdaptiveFetchIfFalse() + void gettingAdaptiveFetchIfFalse() throws NoSuchFieldException, IllegalAccessException { boolean expectedResult = false; @@ -284,7 +286,7 @@ public void testGettingAdaptiveFetchIfFalse() * Test for calling getFetchSizeForQuery method for not existing query. Should return value -1. */ @Test - public void testGettingFetchSizeForNotExistingQuery() { + void gettingFetchSizeForNotExistingQuery() { String expectedQuery = "test-query"; boolean adaptiveFetch = true; @@ -299,7 +301,7 @@ public void testGettingFetchSizeForNotExistingQuery() { * to false. Should return value -1. */ @Test - public void testGettingFetchSizeForNotExistingQueryIfAdaptiveFetchFalse() { + void gettingFetchSizeForNotExistingQueryIfAdaptiveFetchFalse() { String expectedQuery = "test-query"; boolean adaptiveFetch = false; @@ -314,7 +316,7 @@ public void testGettingFetchSizeForNotExistingQueryIfAdaptiveFetchFalse() { * for the query. */ @Test - public void testGettingFetchSizeForExistingQuery() + void gettingFetchSizeForExistingQuery() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = true; @@ -338,7 +340,7 @@ public void testGettingFetchSizeForExistingQuery() * false. Should return value -1. */ @Test - public void testGettingFetchSizeForExistingQueryIfAdaptiveFetchFalse() + void gettingFetchSizeForExistingQueryIfAdaptiveFetchFalse() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = false; @@ -361,7 +363,7 @@ public void testGettingFetchSizeForExistingQueryIfAdaptiveFetchFalse() * Test for calling removeQuery method for not existing query. Should nothing happen. */ @Test - public void testRemovingNotExistingQuery() + void removingNotExistingQuery() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = true; @@ -378,7 +380,7 @@ public void testRemovingNotExistingQuery() * Should nothing happen. */ @Test - public void testRemovingNotExistingQueryIfAdaptiveFetchFalse() + void removingNotExistingQueryIfAdaptiveFetchFalse() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = false; @@ -395,7 +397,7 @@ public void testRemovingNotExistingQueryIfAdaptiveFetchFalse() * map inside AdaptiveFetchCache. */ @Test - public void testRemovingExistingQuery() + void removingExistingQuery() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = true; @@ -419,7 +421,7 @@ public void testRemovingExistingQuery() * query shouldn't be removed. */ @Test - public void testRemovingExistingQueryIfAdaptiveFetchFalse() + void removingExistingQueryIfAdaptiveFetchFalse() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = false; @@ -444,7 +446,7 @@ public void testRemovingExistingQueryIfAdaptiveFetchFalse() * shouldn't be removed, but counter set to 1. After next call, query should be removed. */ @Test - public void testRemovingExistingQueryWithLargeCounter() + void removingExistingQueryWithLargeCounter() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = true; @@ -473,7 +475,7 @@ public void testRemovingExistingQueryWithLargeCounter() * change. */ @Test - public void testRemovingExistingQueryWithLargeCounterIfAdaptiveFetchFalse() + void removingExistingQueryWithLargeCounterIfAdaptiveFetchFalse() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query"; boolean adaptiveFetch = false; @@ -502,7 +504,7 @@ public void testRemovingExistingQueryWithLargeCounterIfAdaptiveFetchFalse() * query used in method call should be removed, other shouldn't change. */ @Test - public void testRemovingExistingQueryWithMoreQueriesCached() + void removingExistingQueryWithMoreQueriesCached() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; String expectedQuery2 = "test-query-2"; @@ -548,7 +550,7 @@ public void testRemovingExistingQueryWithMoreQueriesCached() * adaptiveFetch is set false. Queries shouldn't change */ @Test - public void testRemovingExistingQueryWithMoreQueriesCachedIfAdaptiveFetchFalse() + void removingExistingQueryWithMoreQueriesCachedIfAdaptiveFetchFalse() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; String expectedQuery2 = "test-query-2"; @@ -595,7 +597,7 @@ public void testRemovingExistingQueryWithMoreQueriesCachedIfAdaptiveFetchFalse() * Test for calling setAdaptiveFetch method with true value. */ @Test - public void testSettingAdaptiveFetchAsTrue() + void settingAdaptiveFetchAsTrue() throws NoSuchFieldException, IllegalAccessException { boolean expectedAdaptiveFetch = true; @@ -610,7 +612,7 @@ public void testSettingAdaptiveFetchAsTrue() * Test for calling setAdaptiveFetch method with false value. */ @Test - public void testSettingAdaptiveFetchAsFalse() + void settingAdaptiveFetchAsFalse() throws NoSuchFieldException, IllegalAccessException { boolean expectedAdaptiveFetch = false; @@ -625,7 +627,7 @@ public void testSettingAdaptiveFetchAsFalse() * Test for calling updateQueryFetchSize method. Method should update a value for a query. */ @Test - public void testUpdatingAdaptiveFetchSize() + void updatingAdaptiveFetchSize() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; boolean adaptiveFetch = true; @@ -653,7 +655,7 @@ public void testUpdatingAdaptiveFetchSize() * update any values. */ @Test - public void testUpdatingAdaptiveFetchSizeIfAdaptiveFetchFalse() + void updatingAdaptiveFetchSizeIfAdaptiveFetchFalse() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; boolean adaptiveFetch = false; @@ -681,7 +683,7 @@ public void testUpdatingAdaptiveFetchSizeIfAdaptiveFetchFalse() * any values. */ @Test - public void testUpdatingAdaptiveFetchSizeForNotExistingQuery() + void updatingAdaptiveFetchSizeForNotExistingQuery() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; String expectedQuery2 = "test-query-2"; @@ -714,7 +716,7 @@ public void testUpdatingAdaptiveFetchSizeForNotExistingQuery() * false. Method shouldn't update any values. */ @Test - public void testUpdatingAdaptiveFetchSizeForNotExistingQueryIfAdaptiveFetchFalse() + void updatingAdaptiveFetchSizeForNotExistingQueryIfAdaptiveFetchFalse() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; String expectedQuery2 = "test-query-2"; @@ -747,7 +749,7 @@ public void testUpdatingAdaptiveFetchSizeForNotExistingQueryIfAdaptiveFetchFalse * in a map. The method should only change value for query used in a call. */ @Test - public void testUpdatingAdaptiveFetchSizeWithMoreQueriesInMap() + void updatingAdaptiveFetchSizeWithMoreQueriesInMap() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; String expectedQuery2 = "test-query-2"; @@ -786,7 +788,7 @@ public void testUpdatingAdaptiveFetchSizeWithMoreQueriesInMap() * in a map, but adaptiveFetch is set false. The method shouldn't change any values. */ @Test - public void testUpdatingAdaptiveFetchSizeWithMoreQueriesInMapIfAdaptiveFetchFalse() + void updatingAdaptiveFetchSizeWithMoreQueriesInMapIfAdaptiveFetchFalse() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; String expectedQuery2 = "test-query-2"; @@ -825,7 +827,7 @@ public void testUpdatingAdaptiveFetchSizeWithMoreQueriesInMapIfAdaptiveFetchFals * value. The method should update a query to have value of minimum. */ @Test - public void testUpdatingAdaptiveFetchSizeWithMinimumSize() + void updatingAdaptiveFetchSizeWithMinimumSize() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; boolean adaptiveFetch = true; @@ -856,7 +858,7 @@ public void testUpdatingAdaptiveFetchSizeWithMinimumSize() * value, but adaptiveFetch is set false. The method shouldn't update size for a query. */ @Test - public void testUpdatingAdaptiveFetchSizeWithMinimumSizeIfAdaptiveFetchFalse() + void updatingAdaptiveFetchSizeWithMinimumSizeIfAdaptiveFetchFalse() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; boolean adaptiveFetch = false; @@ -887,7 +889,7 @@ public void testUpdatingAdaptiveFetchSizeWithMinimumSizeIfAdaptiveFetchFalse() * value. The method should update a query to have value of maximum. */ @Test - public void testUpdatingAdaptiveFetchSizeWithMaximumSize() + void updatingAdaptiveFetchSizeWithMaximumSize() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; boolean adaptiveFetch = true; @@ -918,7 +920,7 @@ public void testUpdatingAdaptiveFetchSizeWithMaximumSize() * value, but adaptiveFetch is set false. The method shouldn't update size for a query. */ @Test - public void testUpdatingAdaptiveFetchSizeWithMaximumSizeIfAdaptiveFetchFalse() + void updatingAdaptiveFetchSizeWithMaximumSizeIfAdaptiveFetchFalse() throws NoSuchFieldException, IllegalAccessException { String expectedQuery = "test-query-1"; boolean adaptiveFetch = false; @@ -1027,6 +1029,11 @@ public ParameterList createParameterList() { throw new WrongMethodCallException("Method shouldn't be called."); } + @Override + public String toString(ParameterList parameters, SqlSerializationContext context) { + throw new WrongMethodCallException("Method shouldn't be called."); + } + @Override public String toString(ParameterList parameters) { throw new WrongMethodCallException("Method shouldn't be called."); diff --git a/src/test/java/org/postgresql/jdbc/AbstractArraysTest.java b/src/test/java/org/postgresql/jdbc/AbstractArraysTest.java index b275b78..f6a8bcd 100644 --- a/src/test/java/org/postgresql/jdbc/AbstractArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/AbstractArraysTest.java @@ -5,7 +5,7 @@ package org.postgresql.jdbc; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.postgresql.PGNotification; import org.postgresql.copy.CopyManager; @@ -21,11 +21,12 @@ import org.postgresql.jdbc.FieldMetadata.Key; import org.postgresql.largeobject.LargeObjectManager; import org.postgresql.replication.PGReplicationConnection; +import org.postgresql.test.annotations.tags.Arrays; import org.postgresql.util.LruCache; import org.postgresql.util.PGobject; import org.postgresql.xml.PGXmlFactoryFactory; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.lang.reflect.Array; import java.sql.Blob; @@ -48,6 +49,7 @@ import java.util.concurrent.Executor; import java.util.logging.Logger; +@Arrays public abstract class AbstractArraysTest { private static final BaseConnection ENCODING_CONNECTION = new EncodingConnection(Encoding.getJVMEncoding("utf-8")); @@ -63,7 +65,7 @@ public abstract class AbstractArraysTest { * @param testData * 3 dimensional array to use for testing. * @param binarySupported - * Indicates if binary support is epxected for the type. + * Indicates if binary support is expected for the type. */ public AbstractArraysTest(A[][] testData, boolean binarySupported, int arrayTypeOid) { super(); @@ -74,14 +76,14 @@ public AbstractArraysTest(A[][] testData, boolean binarySupported, int arrayType protected void assertArraysEquals(String message, A expected, Object actual) { final int expectedLength = Array.getLength(expected); - assertEquals(message + " size", expectedLength, Array.getLength(actual)); - for (int i = 0; i < expectedLength; ++i) { - assertEquals(message + " value at " + i, Array.get(expected, i), Array.get(actual, i)); + assertEquals(expectedLength, Array.getLength(actual), message + " size"); + for (int i = 0; i < expectedLength; i++) { + assertEquals(Array.get(expected, i), Array.get(actual, i), message + " value at " + i); } } @Test - public void testBinary() throws Exception { + public void binary() throws Exception { A data = testData[0][0]; @@ -103,7 +105,7 @@ public void testBinary() throws Exception { } @Test - public void testString() throws Exception { + public void string() throws Exception { A data = testData[0][0]; @@ -138,7 +140,7 @@ public void test2dBinary() throws Exception { assertEquals(data.length, actual.length); - for (int i = 0; i < data.length; ++i) { + for (int i = 0; i < data.length; i++) { assertArraysEquals("array at position " + i, data[i], actual[i]); } } @@ -159,7 +161,7 @@ public void test2dString() throws Exception { assertEquals(data.length, actual.length); - for (int i = 0; i < data.length; ++i) { + for (int i = 0; i < data.length; i++) { assertArraysEquals("array at position " + i, data[i], actual[i]); } } @@ -182,9 +184,9 @@ public void test3dBinary() throws Exception { assertEquals(testData.length, actual.length); - for (int i = 0; i < testData.length; ++i) { - assertEquals("array length at " + i, testData[i].length, actual[i].length); - for (int j = 0; j < testData[i].length; ++j) { + for (int i = 0; i < testData.length; i++) { + assertEquals(testData[i].length, actual[i].length, "array length at " + i); + for (int j = 0; j < testData[i].length; j++) { assertArraysEquals("array at " + i + ',' + j, testData[i][j], actual[i][j]); } } @@ -204,18 +206,18 @@ public void test3dString() throws Exception { assertEquals(testData.length, actual.length); - for (int i = 0; i < testData.length; ++i) { - assertEquals("array length at " + i, testData[i].length, actual[i].length); - for (int j = 0; j < testData[i].length; ++j) { + for (int i = 0; i < testData.length; i++) { + assertEquals(testData[i].length, actual[i].length, "array length at " + i); + for (int j = 0; j < testData[i].length; j++) { assertArraysEquals("array at " + i + ',' + j, testData[i][j], actual[i][j]); } } } @Test - public void testObjectArrayCopy() throws Exception { + public void objectArrayCopy() throws Exception { final Object[] copy = new Object[testData.length]; - for (int i = 0; i < testData.length; ++i) { + for (int i = 0; i < testData.length; i++) { copy[i] = testData[i]; } @@ -229,9 +231,9 @@ public void testObjectArrayCopy() throws Exception { } @Test - public void testObject2dArrayCopy() throws Exception { + public void object2dArrayCopy() throws Exception { final Object[][] copy = new Object[testData.length][]; - for (int i = 0; i < testData.length; ++i) { + for (int i = 0; i < testData.length; i++) { copy[i] = testData[i]; } @@ -245,11 +247,11 @@ public void testObject2dArrayCopy() throws Exception { } @Test - public void testObject3dArrayCopy() throws Exception { + public void object3dArrayCopy() throws Exception { final A[][][] source = (A[][][]) Array.newInstance(testData.getClass(), 2); source[0] = testData; source[1] = testData; - final Object[][][] copy = new Object[][][] { testData, testData }; + final Object[][][] copy = new Object[][][]{testData, testData}; final ArrayEncoding.ArrayEncoder support = ArrayEncoding.getArrayEncoder(source); final String arrayString = support.toArrayString(',', source); @@ -271,6 +273,7 @@ private static final class EncodingConnection implements BaseConnection { /** * {@inheritDoc} */ + @Override public Encoding getEncoding() throws SQLException { return encoding; } @@ -278,6 +281,7 @@ public Encoding getEncoding() throws SQLException { /** * {@inheritDoc} */ + @Override public TypeInfo getTypeInfo() { return typeInfo; } @@ -285,6 +289,7 @@ public TypeInfo getTypeInfo() { /** * {@inheritDoc} */ + @Override public void cancelQuery() throws SQLException { throw new UnsupportedOperationException(); } @@ -292,6 +297,7 @@ public void cancelQuery() throws SQLException { /** * {@inheritDoc} */ + @Override public ResultSet execSQLQuery(String s) throws SQLException { throw new UnsupportedOperationException(); } @@ -299,6 +305,7 @@ public ResultSet execSQLQuery(String s) throws SQLException { /** * {@inheritDoc} */ + @Override public ResultSet execSQLQuery(String s, int resultSetType, int resultSetConcurrency) throws SQLException { throw new UnsupportedOperationException(); } @@ -306,6 +313,7 @@ public ResultSet execSQLQuery(String s, int resultSetType, int resultSetConcurre /** * {@inheritDoc} */ + @Override public void execSQLUpdate(String s) throws SQLException { throw new UnsupportedOperationException(); } @@ -313,6 +321,7 @@ public void execSQLUpdate(String s) throws SQLException { /** * {@inheritDoc} */ + @Override public QueryExecutor getQueryExecutor() { throw new UnsupportedOperationException(); } @@ -320,6 +329,7 @@ public QueryExecutor getQueryExecutor() { /** * {@inheritDoc} */ + @Override public ReplicationProtocol getReplicationProtocol() { throw new UnsupportedOperationException(); } @@ -327,6 +337,7 @@ public ReplicationProtocol getReplicationProtocol() { /** * {@inheritDoc} */ + @Override public Object getObject(String type, String value, byte[] byteValue) throws SQLException { throw new UnsupportedOperationException(); } @@ -334,6 +345,7 @@ public Object getObject(String type, String value, byte[] byteValue) throws SQLE /** * {@inheritDoc} */ + @Override public boolean haveMinimumServerVersion(int ver) { throw new UnsupportedOperationException(); } @@ -341,6 +353,7 @@ public boolean haveMinimumServerVersion(int ver) { /** * {@inheritDoc} */ + @Override public boolean haveMinimumServerVersion(Version ver) { throw new UnsupportedOperationException(); } @@ -348,6 +361,7 @@ public boolean haveMinimumServerVersion(Version ver) { /** * {@inheritDoc} */ + @Override public byte[] encodeString(String str) throws SQLException { throw new UnsupportedOperationException(); } @@ -355,6 +369,7 @@ public byte[] encodeString(String str) throws SQLException { /** * {@inheritDoc} */ + @Override public String escapeString(String str) throws SQLException { throw new UnsupportedOperationException(); } @@ -362,6 +377,7 @@ public String escapeString(String str) throws SQLException { /** * {@inheritDoc} */ + @Override public boolean getStandardConformingStrings() { throw new UnsupportedOperationException(); } @@ -369,6 +385,8 @@ public boolean getStandardConformingStrings() { /** * {@inheritDoc} */ + @Override + @SuppressWarnings("deprecation") public TimestampUtils getTimestampUtils() { throw new UnsupportedOperationException(); } @@ -376,6 +394,7 @@ public TimestampUtils getTimestampUtils() { /** * {@inheritDoc} */ + @Override public Logger getLogger() { throw new UnsupportedOperationException(); } @@ -383,6 +402,7 @@ public Logger getLogger() { /** * {@inheritDoc} */ + @Override public boolean getStringVarcharFlag() { throw new UnsupportedOperationException(); } @@ -390,6 +410,7 @@ public boolean getStringVarcharFlag() { /** * {@inheritDoc} */ + @Override public TransactionState getTransactionState() { throw new UnsupportedOperationException(); } @@ -397,6 +418,7 @@ public TransactionState getTransactionState() { /** * {@inheritDoc} */ + @Override public boolean binaryTransferSend(int oid) { throw new UnsupportedOperationException(); } @@ -404,6 +426,7 @@ public boolean binaryTransferSend(int oid) { /** * {@inheritDoc} */ + @Override public boolean isColumnSanitiserDisabled() { throw new UnsupportedOperationException(); } @@ -411,6 +434,7 @@ public boolean isColumnSanitiserDisabled() { /** * {@inheritDoc} */ + @Override public void addTimerTask(TimerTask timerTask, long milliSeconds) { throw new UnsupportedOperationException(); } @@ -418,6 +442,7 @@ public void addTimerTask(TimerTask timerTask, long milliSeconds) { /** * {@inheritDoc} */ + @Override public void purgeTimerTasks() { throw new UnsupportedOperationException(); } @@ -425,6 +450,7 @@ public void purgeTimerTasks() { /** * {@inheritDoc} */ + @Override public LruCache getFieldMetadataCache() { throw new UnsupportedOperationException(); } @@ -432,6 +458,7 @@ public LruCache getFieldMetadataCache() { /** * {@inheritDoc} */ + @Override public CachedQuery createQuery(String sql, boolean escapeProcessing, boolean isParameterized, String... columnNames) throws SQLException { throw new UnsupportedOperationException(); @@ -440,6 +467,7 @@ public CachedQuery createQuery(String sql, boolean escapeProcessing, boolean isP /** * {@inheritDoc} */ + @Override public void setFlushCacheOnDeallocate(boolean flushCacheOnDeallocate) { throw new UnsupportedOperationException(); } @@ -447,6 +475,7 @@ public void setFlushCacheOnDeallocate(boolean flushCacheOnDeallocate) { /** * {@inheritDoc} */ + @Override public Statement createStatement() throws SQLException { throw new UnsupportedOperationException(); } @@ -454,6 +483,7 @@ public Statement createStatement() throws SQLException { /** * {@inheritDoc} */ + @Override public PreparedStatement prepareStatement(String sql) throws SQLException { throw new UnsupportedOperationException(); } @@ -461,6 +491,7 @@ public PreparedStatement prepareStatement(String sql) throws SQLException { /** * {@inheritDoc} */ + @Override public CallableStatement prepareCall(String sql) throws SQLException { throw new UnsupportedOperationException(); } @@ -468,6 +499,7 @@ public CallableStatement prepareCall(String sql) throws SQLException { /** * {@inheritDoc} */ + @Override public String nativeSQL(String sql) throws SQLException { throw new UnsupportedOperationException(); } @@ -475,6 +507,7 @@ public String nativeSQL(String sql) throws SQLException { /** * {@inheritDoc} */ + @Override public void setAutoCommit(boolean autoCommit) throws SQLException { throw new UnsupportedOperationException(); } @@ -482,6 +515,7 @@ public void setAutoCommit(boolean autoCommit) throws SQLException { /** * {@inheritDoc} */ + @Override public boolean getAutoCommit() throws SQLException { throw new UnsupportedOperationException(); } @@ -489,6 +523,7 @@ public boolean getAutoCommit() throws SQLException { /** * {@inheritDoc} */ + @Override public void commit() throws SQLException { throw new UnsupportedOperationException(); } @@ -496,6 +531,7 @@ public void commit() throws SQLException { /** * {@inheritDoc} */ + @Override public void rollback() throws SQLException { throw new UnsupportedOperationException(); } @@ -503,6 +539,7 @@ public void rollback() throws SQLException { /** * {@inheritDoc} */ + @Override public void close() throws SQLException { throw new UnsupportedOperationException(); } @@ -510,6 +547,7 @@ public void close() throws SQLException { /** * {@inheritDoc} */ + @Override public boolean isClosed() throws SQLException { throw new UnsupportedOperationException(); } @@ -517,6 +555,7 @@ public boolean isClosed() throws SQLException { /** * {@inheritDoc} */ + @Override public DatabaseMetaData getMetaData() throws SQLException { throw new UnsupportedOperationException(); } @@ -524,6 +563,7 @@ public DatabaseMetaData getMetaData() throws SQLException { /** * {@inheritDoc} */ + @Override public void setReadOnly(boolean readOnly) throws SQLException { throw new UnsupportedOperationException(); } @@ -531,6 +571,7 @@ public void setReadOnly(boolean readOnly) throws SQLException { /** * {@inheritDoc} */ + @Override public boolean isReadOnly() throws SQLException { throw new UnsupportedOperationException(); } @@ -538,6 +579,7 @@ public boolean isReadOnly() throws SQLException { /** * {@inheritDoc} */ + @Override public void setCatalog(String catalog) throws SQLException { throw new UnsupportedOperationException(); } @@ -545,6 +587,7 @@ public void setCatalog(String catalog) throws SQLException { /** * {@inheritDoc} */ + @Override public String getCatalog() throws SQLException { throw new UnsupportedOperationException(); } @@ -552,6 +595,7 @@ public String getCatalog() throws SQLException { /** * {@inheritDoc} */ + @Override public void setTransactionIsolation(int level) throws SQLException { throw new UnsupportedOperationException(); } @@ -559,6 +603,7 @@ public void setTransactionIsolation(int level) throws SQLException { /** * {@inheritDoc} */ + @Override public int getTransactionIsolation() throws SQLException { throw new UnsupportedOperationException(); } @@ -566,6 +611,7 @@ public int getTransactionIsolation() throws SQLException { /** * {@inheritDoc} */ + @Override public SQLWarning getWarnings() throws SQLException { throw new UnsupportedOperationException(); } @@ -573,6 +619,7 @@ public SQLWarning getWarnings() throws SQLException { /** * {@inheritDoc} */ + @Override public void clearWarnings() throws SQLException { throw new UnsupportedOperationException(); } @@ -580,6 +627,7 @@ public void clearWarnings() throws SQLException { /** * {@inheritDoc} */ + @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { throw new UnsupportedOperationException(); } @@ -587,6 +635,7 @@ public Statement createStatement(int resultSetType, int resultSetConcurrency) th /** * {@inheritDoc} */ + @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { throw new UnsupportedOperationException(); @@ -595,6 +644,7 @@ public PreparedStatement prepareStatement(String sql, int resultSetType, int res /** * {@inheritDoc} */ + @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { throw new UnsupportedOperationException(); } @@ -602,6 +652,7 @@ public CallableStatement prepareCall(String sql, int resultSetType, int resultSe /** * {@inheritDoc} */ + @Override public Map> getTypeMap() throws SQLException { throw new UnsupportedOperationException(); } @@ -609,6 +660,7 @@ public Map> getTypeMap() throws SQLException { /** * {@inheritDoc} */ + @Override public void setTypeMap(Map> map) throws SQLException { throw new UnsupportedOperationException(); } @@ -616,6 +668,7 @@ public void setTypeMap(Map> map) throws SQLException { /** * {@inheritDoc} */ + @Override public void setHoldability(int holdability) throws SQLException { throw new UnsupportedOperationException(); } @@ -623,6 +676,7 @@ public void setHoldability(int holdability) throws SQLException { /** * {@inheritDoc} */ + @Override public int getHoldability() throws SQLException { throw new UnsupportedOperationException(); } @@ -630,6 +684,7 @@ public int getHoldability() throws SQLException { /** * {@inheritDoc} */ + @Override public Savepoint setSavepoint() throws SQLException { throw new UnsupportedOperationException(); } @@ -637,6 +692,7 @@ public Savepoint setSavepoint() throws SQLException { /** * {@inheritDoc} */ + @Override public Savepoint setSavepoint(String name) throws SQLException { throw new UnsupportedOperationException(); } @@ -644,6 +700,7 @@ public Savepoint setSavepoint(String name) throws SQLException { /** * {@inheritDoc} */ + @Override public void rollback(Savepoint savepoint) throws SQLException { throw new UnsupportedOperationException(); } @@ -651,6 +708,7 @@ public void rollback(Savepoint savepoint) throws SQLException { /** * {@inheritDoc} */ + @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { throw new UnsupportedOperationException(); } @@ -658,6 +716,7 @@ public void releaseSavepoint(Savepoint savepoint) throws SQLException { /** * {@inheritDoc} */ + @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { throw new UnsupportedOperationException(); @@ -666,6 +725,7 @@ public Statement createStatement(int resultSetType, int resultSetConcurrency, in /** * {@inheritDoc} */ + @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { throw new UnsupportedOperationException(); @@ -674,6 +734,7 @@ public PreparedStatement prepareStatement(String sql, int resultSetType, int res /** * {@inheritDoc} */ + @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { throw new UnsupportedOperationException(); @@ -682,6 +743,7 @@ public CallableStatement prepareCall(String sql, int resultSetType, int resultSe /** * {@inheritDoc} */ + @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { throw new UnsupportedOperationException(); } @@ -689,6 +751,7 @@ public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) thr /** * {@inheritDoc} */ + @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { throw new UnsupportedOperationException(); } @@ -696,6 +759,7 @@ public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throw /** * {@inheritDoc} */ + @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { throw new UnsupportedOperationException(); } @@ -703,6 +767,7 @@ public PreparedStatement prepareStatement(String sql, String[] columnNames) thro /** * {@inheritDoc} */ + @Override public Clob createClob() throws SQLException { throw new UnsupportedOperationException(); } @@ -710,6 +775,7 @@ public Clob createClob() throws SQLException { /** * {@inheritDoc} */ + @Override public Blob createBlob() throws SQLException { throw new UnsupportedOperationException(); } @@ -717,6 +783,7 @@ public Blob createBlob() throws SQLException { /** * {@inheritDoc} */ + @Override public NClob createNClob() throws SQLException { throw new UnsupportedOperationException(); } @@ -724,6 +791,7 @@ public NClob createNClob() throws SQLException { /** * {@inheritDoc} */ + @Override public SQLXML createSQLXML() throws SQLException { throw new UnsupportedOperationException(); } @@ -731,6 +799,7 @@ public SQLXML createSQLXML() throws SQLException { /** * {@inheritDoc} */ + @Override public boolean isValid(int timeout) throws SQLException { throw new UnsupportedOperationException(); } @@ -738,6 +807,7 @@ public boolean isValid(int timeout) throws SQLException { /** * {@inheritDoc} */ + @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { throw new UnsupportedOperationException(); } @@ -745,6 +815,7 @@ public void setClientInfo(String name, String value) throws SQLClientInfoExcepti /** * {@inheritDoc} */ + @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { throw new UnsupportedOperationException(); } @@ -752,6 +823,7 @@ public void setClientInfo(Properties properties) throws SQLClientInfoException { /** * {@inheritDoc} */ + @Override public String getClientInfo(String name) throws SQLException { throw new UnsupportedOperationException(); } @@ -759,6 +831,7 @@ public String getClientInfo(String name) throws SQLException { /** * {@inheritDoc} */ + @Override public Properties getClientInfo() throws SQLException { throw new UnsupportedOperationException(); } @@ -766,6 +839,7 @@ public Properties getClientInfo() throws SQLException { /** * {@inheritDoc} */ + @Override public java.sql.Array createArrayOf(String typeName, Object[] elements) throws SQLException { throw new UnsupportedOperationException(); } @@ -773,6 +847,7 @@ public java.sql.Array createArrayOf(String typeName, Object[] elements) throws S /** * {@inheritDoc} */ + @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { throw new UnsupportedOperationException(); } @@ -780,6 +855,7 @@ public Struct createStruct(String typeName, Object[] attributes) throws SQLExcep /** * {@inheritDoc} */ + @Override public void setSchema(String schema) throws SQLException { throw new UnsupportedOperationException(); } @@ -787,6 +863,7 @@ public void setSchema(String schema) throws SQLException { /** * {@inheritDoc} */ + @Override public String getSchema() throws SQLException { throw new UnsupportedOperationException(); } @@ -794,6 +871,7 @@ public String getSchema() throws SQLException { /** * {@inheritDoc} */ + @Override public void abort(Executor executor) throws SQLException { throw new UnsupportedOperationException(); } @@ -801,6 +879,7 @@ public void abort(Executor executor) throws SQLException { /** * {@inheritDoc} */ + @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { throw new UnsupportedOperationException(); } @@ -808,6 +887,7 @@ public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLExc /** * {@inheritDoc} */ + @Override public int getNetworkTimeout() throws SQLException { throw new UnsupportedOperationException(); } @@ -815,6 +895,7 @@ public int getNetworkTimeout() throws SQLException { /** * {@inheritDoc} */ + @Override public T unwrap(Class iface) throws SQLException { throw new UnsupportedOperationException(); } @@ -822,6 +903,7 @@ public T unwrap(Class iface) throws SQLException { /** * {@inheritDoc} */ + @Override public boolean isWrapperFor(Class iface) throws SQLException { throw new UnsupportedOperationException(); } @@ -829,6 +911,7 @@ public boolean isWrapperFor(Class iface) throws SQLException { /** * {@inheritDoc} */ + @Override public java.sql.Array createArrayOf(String typeName, Object elements) throws SQLException { throw new UnsupportedOperationException(); } @@ -836,6 +919,7 @@ public java.sql.Array createArrayOf(String typeName, Object elements) throws SQL /** * {@inheritDoc} */ + @Override public PGNotification[] getNotifications() throws SQLException { throw new UnsupportedOperationException(); } @@ -843,6 +927,7 @@ public PGNotification[] getNotifications() throws SQLException { /** * {@inheritDoc} */ + @Override public PGNotification[] getNotifications(int timeoutMillis) throws SQLException { throw new UnsupportedOperationException(); } @@ -850,6 +935,7 @@ public PGNotification[] getNotifications(int timeoutMillis) throws SQLException /** * {@inheritDoc} */ + @Override public CopyManager getCopyAPI() throws SQLException { throw new UnsupportedOperationException(); } @@ -857,6 +943,7 @@ public CopyManager getCopyAPI() throws SQLException { /** * {@inheritDoc} */ + @Override public LargeObjectManager getLargeObjectAPI() throws SQLException { throw new UnsupportedOperationException(); } @@ -864,6 +951,7 @@ public LargeObjectManager getLargeObjectAPI() throws SQLException { /** * {@inheritDoc} */ + @Override public Fastpath getFastpathAPI() throws SQLException { throw new UnsupportedOperationException(); } @@ -871,6 +959,8 @@ public Fastpath getFastpathAPI() throws SQLException { /** * {@inheritDoc} */ + @Override + @SuppressWarnings("deprecation") public void addDataType(String type, String className) { throw new UnsupportedOperationException(); } @@ -878,6 +968,7 @@ public void addDataType(String type, String className) { /** * {@inheritDoc} */ + @Override public void addDataType(String type, Class klass) throws SQLException { throw new UnsupportedOperationException(); } @@ -885,6 +976,7 @@ public void addDataType(String type, Class klass) throws SQL /** * {@inheritDoc} */ + @Override public void setPrepareThreshold(int threshold) { throw new UnsupportedOperationException(); } @@ -892,6 +984,7 @@ public void setPrepareThreshold(int threshold) { /** * {@inheritDoc} */ + @Override public int getPrepareThreshold() { throw new UnsupportedOperationException(); } @@ -899,6 +992,7 @@ public int getPrepareThreshold() { /** * {@inheritDoc} */ + @Override public void setDefaultFetchSize(int fetchSize) throws SQLException { throw new UnsupportedOperationException(); } @@ -906,6 +1000,7 @@ public void setDefaultFetchSize(int fetchSize) throws SQLException { /** * {@inheritDoc} */ + @Override public int getDefaultFetchSize() { throw new UnsupportedOperationException(); } @@ -913,6 +1008,23 @@ public int getDefaultFetchSize() { /** * {@inheritDoc} */ + @Override + public void setQueryTimeout(int seconds) throws SQLException { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getQueryTimeout() { + throw new UnsupportedOperationException(); + } + + /** + * {@inheritDoc} + */ + @Override public int getBackendPID() { throw new UnsupportedOperationException(); } @@ -920,6 +1032,7 @@ public int getBackendPID() { /** * {@inheritDoc} */ + @Override public String escapeIdentifier(String identifier) throws SQLException { throw new UnsupportedOperationException(); } @@ -927,6 +1040,7 @@ public String escapeIdentifier(String identifier) throws SQLException { /** * {@inheritDoc} */ + @Override public String escapeLiteral(String literal) throws SQLException { throw new UnsupportedOperationException(); } @@ -934,6 +1048,7 @@ public String escapeLiteral(String literal) throws SQLException { /** * {@inheritDoc} */ + @Override public PreferQueryMode getPreferQueryMode() { throw new UnsupportedOperationException(); } @@ -941,6 +1056,7 @@ public PreferQueryMode getPreferQueryMode() { /** * {@inheritDoc} */ + @Override public AutoSave getAutosave() { throw new UnsupportedOperationException(); } @@ -948,6 +1064,7 @@ public AutoSave getAutosave() { /** * {@inheritDoc} */ + @Override public void setAutosave(AutoSave autoSave) { throw new UnsupportedOperationException(); } @@ -955,6 +1072,7 @@ public void setAutosave(AutoSave autoSave) { /** * {@inheritDoc} */ + @Override public PGReplicationConnection getReplicationAPI() { throw new UnsupportedOperationException(); } @@ -1014,5 +1132,10 @@ public boolean getAdaptiveFetch() { public boolean getLogServerErrorDetail() { return false; } + + @Override + public boolean getConvertBooleanToNumeric() { + return false; + } } } diff --git a/src/test/java/org/postgresql/jdbc/ArraysTest.java b/src/test/java/org/postgresql/jdbc/ArraysTest.java index bb93a3f..5f7e0d0 100644 --- a/src/test/java/org/postgresql/jdbc/ArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/ArraysTest.java @@ -5,34 +5,41 @@ package org.postgresql.jdbc; -import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.postgresql.core.Oid; import org.postgresql.util.PSQLException; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.sql.SQLFeatureNotSupportedException; -public class ArraysTest { +class ArraysTest { - @Test(expected = PSQLException.class) - public void testNonArrayNotSupported() throws Exception { - ArrayEncoding.getArrayEncoder("asdflkj"); + @Test + void nonArrayNotSupported() throws Exception { + assertThrows(PSQLException.class, () -> { + ArrayEncoding.getArrayEncoder("asdflkj"); + }); } - @Test(expected = PSQLException.class) - public void testNoByteArray() throws Exception { - ArrayEncoding.getArrayEncoder(new byte[] {}); + @Test + void noByteArray() throws Exception { + assertThrows(PSQLException.class, () -> { + ArrayEncoding.getArrayEncoder(new byte[]{}); + }); } - @Test(expected = SQLFeatureNotSupportedException.class) - public void testBinaryNotSupported() throws Exception { - final ArrayEncoding.ArrayEncoder support = ArrayEncoding.getArrayEncoder(new BigDecimal[] {}); + @Test + void binaryNotSupported() throws Exception { + assertThrows(SQLFeatureNotSupportedException.class, () -> { + final ArrayEncoding.ArrayEncoder support = ArrayEncoding.getArrayEncoder(new BigDecimal[]{}); - assertFalse(support.supportBinaryRepresentation(Oid.FLOAT8_ARRAY)); + assertFalse(support.supportBinaryRepresentation(Oid.FLOAT8_ARRAY)); - support.toBinaryRepresentation(null, new BigDecimal[] { BigDecimal.valueOf(3) }, Oid.FLOAT8_ARRAY); + support.toBinaryRepresentation(null, new BigDecimal[]{BigDecimal.valueOf(3)}, Oid.FLOAT8_ARRAY); + }); } } diff --git a/src/test/java/org/postgresql/jdbc/BigDecimalObjectArraysTest.java b/src/test/java/org/postgresql/jdbc/BigDecimalObjectArraysTest.java index 901da10..61aaf3d 100644 --- a/src/test/java/org/postgresql/jdbc/BigDecimalObjectArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/BigDecimalObjectArraysTest.java @@ -13,12 +13,12 @@ public class BigDecimalObjectArraysTest extends AbstractArraysTest { - private static final BigDecimal[][][] doubles = new BigDecimal[][][] { - { { valueOf(1.3), valueOf(2.4), valueOf(3.1), valueOf(4.2) }, - { valueOf(5D), valueOf(6D), valueOf(7D), valueOf(8D) }, - { valueOf(9D), valueOf(10D), valueOf(11D), valueOf(12D) } }, - { { valueOf(13D), valueOf(14D), valueOf(15D), valueOf(16D) }, { valueOf(17D), valueOf(18D), valueOf(19D), null }, - { valueOf(21D), valueOf(22D), valueOf(23D), valueOf(24D) } } }; + private static final BigDecimal[][][] doubles = new BigDecimal[][][]{ + {{valueOf(1.3), valueOf(2.4), valueOf(3.1), valueOf(4.2)}, + {valueOf(5D), valueOf(6D), valueOf(7D), valueOf(8D)}, + {valueOf(9D), valueOf(10D), valueOf(11D), valueOf(12D)}}, + {{valueOf(13D), valueOf(14D), valueOf(15D), valueOf(16D)}, {valueOf(17D), valueOf(18D), valueOf(19D), null}, + {valueOf(21D), valueOf(22D), valueOf(23D), valueOf(24D)}}}; public BigDecimalObjectArraysTest() { super(doubles, false, Oid.NUMERIC_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/BitFieldTest.java b/src/test/java/org/postgresql/jdbc/BitFieldTest.java index 1b887bf..d87d915 100644 --- a/src/test/java/org/postgresql/jdbc/BitFieldTest.java +++ b/src/test/java/org/postgresql/jdbc/BitFieldTest.java @@ -5,14 +5,15 @@ package org.postgresql.jdbc; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.util.PGobject; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -25,11 +26,13 @@ private static class TestData { private final String bitValue; private final String tableName; private final String tableFields; + private final boolean isVarBit; - TestData(String bitValue, String tableName, String tableFields) { + TestData(String bitValue, String tableName, String tableFields, boolean isVarBit) { this.bitValue = bitValue; this.tableName = tableName; this.tableFields = tableFields; + this.isVarBit = isVarBit; } public String getBitValue() { @@ -43,21 +46,28 @@ public String getTableName() { public String getTableFields() { return tableFields; } + + public boolean getIsVarBit() { + return isVarBit; + } } private static final String fieldName = "field_bit"; public static final String testBitValue = "0101010100101010101010100101"; private static final TestData[] testBitValues = new TestData[]{ - new TestData("0", "test_bit_field_0a", fieldName + " bit"), - new TestData("0", "test_bit_field_0b", fieldName + " bit(1)"), - new TestData("1", "test_bit_field_1a", fieldName + " bit"), - new TestData("1", "test_bit_field_1b", fieldName + " bit(1)"), + new TestData("0", "test_bit_field_0a", fieldName + " bit", false), + new TestData("0", "test_bit_field_0b", fieldName + " bit(1)", false), + new TestData("1", "test_bit_field_1a", fieldName + " bit", false), + new TestData("1", "test_bit_field_1b", fieldName + " bit(1)", false), new TestData(testBitValue, "test_bit_field_gt1_1", String.format("%s bit(%d)", fieldName, - testBitValue.length())) + testBitValue.length()), false), + new TestData(testBitValue, "test_varbit_field_gt1_1", String.format("%s varbit(%d)", fieldName, + testBitValue.length()), true), + new TestData("1", "test_varbit_field_1", String.format("%s varbit(1)", fieldName), true), + new TestData("0", "test_varbit_field_0", String.format("%s varbit(1)", fieldName), true) }; @Override - @Before public void setUp() throws Exception { super.setUp(); con = TestUtil.openDB(); @@ -69,7 +79,7 @@ public void setUp() throws Exception { } } - @After + @Override public void tearDown() throws SQLException { Statement stmt = con.createStatement(); for (TestData testData : testBitValues) { @@ -85,7 +95,7 @@ public void TestGetObjectForBitFields() throws SQLException { for (TestData testData : testBitValues) { PreparedStatement pstmt = con.prepareStatement(String.format("SELECT field_bit FROM %s " + "limit 1", testData.getTableName())); - checkBitFieldValue(pstmt, testData.getBitValue()); + checkBitFieldValue(pstmt, testData.getBitValue(), testData.getIsVarBit()); pstmt.close(); } } @@ -98,27 +108,27 @@ public void TestSetBitParameter() throws SQLException { + "field_bit = ?"); PGobject param = new PGobject(); param.setValue(testData.getBitValue()); - param.setType("bit"); + param.setType(testData.getIsVarBit() ? "varbit" : "bit"); pstmt.setObject(1, param); - checkBitFieldValue(pstmt, testData.getBitValue()); + checkBitFieldValue(pstmt, testData.getBitValue(), testData.getIsVarBit()); pstmt.close(); } } - private void checkBitFieldValue(PreparedStatement pstmt, String bitValue) throws SQLException { + private static void checkBitFieldValue(PreparedStatement pstmt, String bitValue, boolean isVarBit) throws SQLException { ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Object o = rs.getObject(1); - if (bitValue.length() == 1) { - Assert.assertTrue("Failed for " + bitValue, o instanceof java.lang.Boolean); + if (bitValue.length() == 1 && !isVarBit) { + assertInstanceOf(Boolean.class, o, "Failed for " + bitValue); Boolean b = (Boolean) o; - Assert.assertEquals("Failed for " + bitValue, bitValue.charAt(0) == '1', b); + assertEquals(bitValue.charAt(0) == '1', b, "Failed for " + bitValue); } else { - Assert.assertTrue("Failed for " + bitValue, o instanceof PGobject); + assertInstanceOf(PGobject.class, o, "Failed for " + bitValue); PGobject pGobject = (PGobject) o; - Assert.assertEquals("Failed for " + bitValue, bitValue, pGobject.getValue()); + assertEquals(bitValue, pGobject.getValue(), "Failed for " + bitValue); } String s = rs.getString(1); - Assert.assertEquals(bitValue, s); + assertEquals(bitValue, s); } } diff --git a/src/test/java/org/postgresql/jdbc/BooleanArraysTest.java b/src/test/java/org/postgresql/jdbc/BooleanArraysTest.java index c3717b5..cc90451 100644 --- a/src/test/java/org/postgresql/jdbc/BooleanArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/BooleanArraysTest.java @@ -8,9 +8,9 @@ import org.postgresql.core.Oid; public class BooleanArraysTest extends AbstractArraysTest { - private static final boolean[][][] booleans = new boolean[][][] { - { { true, false, false, true }, { false, false, true, true }, { true, true, false, false } }, - { { false, true, true, false }, { true, false, true, false }, { false, true, false, true } } }; + private static final boolean[][][] booleans = new boolean[][][]{ + {{true, false, false, true}, {false, false, true, true}, {true, true, false, false}}, + {{false, true, true, false}, {true, false, true, false}, {false, true, false, true}}}; public BooleanArraysTest() { super(booleans, true, Oid.BOOL_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/BooleanObjectArraysTest.java b/src/test/java/org/postgresql/jdbc/BooleanObjectArraysTest.java index 6e33d1c..9357f09 100644 --- a/src/test/java/org/postgresql/jdbc/BooleanObjectArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/BooleanObjectArraysTest.java @@ -8,9 +8,9 @@ import org.postgresql.core.Oid; public class BooleanObjectArraysTest extends AbstractArraysTest { - private static final Boolean[][][] booleans = new Boolean[][][] { - { { true, false, null, true }, { false, false, true, true }, { true, true, false, false } }, - { { false, true, true, false }, { true, false, true, null }, { false, true, false, true } } }; + private static final Boolean[][][] booleans = new Boolean[][][]{ + {{true, false, null, true}, {false, false, true, true}, {true, true, false, false}}, + {{false, true, true, false}, {true, false, true, null}, {false, true, false, true}}}; public BooleanObjectArraysTest() { super(booleans, true, Oid.BOOL_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/ByteaArraysTest.java b/src/test/java/org/postgresql/jdbc/ByteaArraysTest.java index e1d2c9c..b777a11 100644 --- a/src/test/java/org/postgresql/jdbc/ByteaArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/ByteaArraysTest.java @@ -5,29 +5,29 @@ package org.postgresql.jdbc; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.core.Oid; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.lang.reflect.Array; public class ByteaArraysTest extends AbstractArraysTest { - private static final byte[][][][] longs = new byte[][][][] { - { { { 0x1, 0x23, (byte) 0xDF, 0x43 }, { 0x5, 0x6, 0x7, (byte) 0xFF }, null, { 0x9, 0x10, 0x11, 0x12 } }, - { null, { 0x13, 0x14, 0x15, 0x16 }, { 0x17, 0x18, (byte) 0xFF, 0x20 }, { 0x1, 0x2, (byte) 0xFF, 0x4F } }, - { { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 }, - { 0x1, 0x2, (byte) 0xFF, 0x4 } } }, - { { { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 }, - { 0x1, 0x2, (byte) 0xFE, 0x4 } }, - { { 0x1, 0x2, (byte) 0xCD, 0x4 }, { 0x1, 0x73, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFF, 0x4 }, - { 0x1, 0x2, (byte) 0xFF, 0x4 } }, - { { 0x1, 0x2, (byte) 0xFF, 0x4 }, { 0x1, 0x2, (byte) 0xFE, 0x10 }, { 0x1, 0x2, (byte) 0xFF, 0x4 }, - { 0x1, 0x2, (byte) 0xFF, 0x4 } } } }; + private static final byte[][][][] longs = new byte[][][][]{ + {{{0x1, 0x23, (byte) 0xDF, 0x43}, {0x5, 0x6, 0x7, (byte) 0xFF}, null, {0x9, 0x10, 0x11, 0x12}}, + {null, {0x13, 0x14, 0x15, 0x16}, {0x17, 0x18, (byte) 0xFF, 0x20}, {0x1, 0x2, (byte) 0xFF, 0x4F}}, + {{0x1, 0x2, (byte) 0xFF, 0x4}, {0x1, 0x2, (byte) 0xFF, 0x4}, {0x1, 0x2, (byte) 0xFF, 0x4}, + {0x1, 0x2, (byte) 0xFF, 0x4}}}, + {{{0x1, 0x2, (byte) 0xFF, 0x4}, {0x1, 0x2, (byte) 0xFF, 0x4}, {0x1, 0x2, (byte) 0xFF, 0x4}, + {0x1, 0x2, (byte) 0xFE, 0x4}}, + {{0x1, 0x2, (byte) 0xCD, 0x4}, {0x1, 0x73, (byte) 0xFF, 0x4}, {0x1, 0x2, (byte) 0xFF, 0x4}, + {0x1, 0x2, (byte) 0xFF, 0x4}}, + {{0x1, 0x2, (byte) 0xFF, 0x4}, {0x1, 0x2, (byte) 0xFE, 0x10}, {0x1, 0x2, (byte) 0xFF, 0x4}, + {0x1, 0x2, (byte) 0xFF, 0x4}}}}; public ByteaArraysTest() { super(longs, true, Oid.BYTEA_ARRAY); @@ -39,15 +39,15 @@ public ByteaArraysTest() { @Override protected void assertArraysEquals(String message, byte[][] expected, Object actual) { final int expectedLength = Array.getLength(expected); - assertEquals(message + " size", expectedLength, Array.getLength(actual)); - for (int i = 0; i < expectedLength; ++i) { - Assert.assertArrayEquals(message + " value at " + i, expected[i], (byte[]) Array.get(actual, i)); + assertEquals(expectedLength, Array.getLength(actual), message + " size"); + for (int i = 0; i < expectedLength; i++) { + assertArrayEquals(expected[i], (byte[]) Array.get(actual, i), message + " value at " + i); } } @Test - public void testObjectArrayWrapper() throws Exception { - final Object[] array = new Object[] { new byte[] { 0x1, 0x2, (byte) 0xFF, 0x4 }, new byte[] { 0x5, 0x6, 0x7, (byte) 0xFF }}; + void objectArrayWrapper() throws Exception { + final Object[] array = new Object[]{new byte[]{0x1, 0x2, (byte) 0xFF, 0x4}, new byte[]{0x5, 0x6, 0x7, (byte) 0xFF}}; final ArrayEncoding.ArrayEncoder copySupport = ArrayEncoding.getArrayEncoder(array); try { diff --git a/src/test/java/org/postgresql/jdbc/ConnectionValidTest.java b/src/test/java/org/postgresql/jdbc/ConnectionValidTest.java index 07a7974..874a774 100644 --- a/src/test/java/org/postgresql/jdbc/ConnectionValidTest.java +++ b/src/test/java/org/postgresql/jdbc/ConnectionValidTest.java @@ -8,14 +8,14 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; +import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; -import org.postgresql.test.util.rules.annotation.HaveMinimalServerVersion; +import org.postgresql.test.annotations.EnabledForServerVersionRange; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.Timeout; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import java.io.InputStream; import java.io.OutputStream; @@ -28,33 +28,28 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -@HaveMinimalServerVersion("9.4") -public class ConnectionValidTest { - - @Rule - public Timeout timeout = new Timeout(30, TimeUnit.SECONDS); - - private static final int LOCAL_SHADOW_PORT = 9009; - +@EnabledForServerVersionRange(gte = "9.4") +class ConnectionValidTest { private Connection connection; private ConnectionBreaker connectionBreaker; - @Before - public void setUp() throws Exception { - final Properties shadowProperties = new Properties(); - shadowProperties.setProperty(TestUtil.SERVER_HOST_PORT_PROP, - String.format("%s:%s", "localhost", LOCAL_SHADOW_PORT)); - - connectionBreaker = new ConnectionBreaker(LOCAL_SHADOW_PORT, + @BeforeEach + void setUp() throws Exception { + final Properties props = new Properties(); + connectionBreaker = new ConnectionBreaker( TestUtil.getServer(), TestUtil.getPort()); connectionBreaker.acceptAsyncConnection(); - connection = TestUtil.openDB(shadowProperties); + + TestUtil.setTestUrlProperty(props, PGProperty.PG_HOST, connectionBreaker.getListenHost()); + TestUtil.setTestUrlProperty(props, PGProperty.PG_PORT, String.valueOf(connectionBreaker.getListenPort())); + + connection = TestUtil.openDB(props); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { connectionBreaker.close(); connection.close(); } @@ -64,7 +59,8 @@ public void tearDown() throws Exception { * @throws Exception if a database exception occurs. */ @Test - public void testIsValid() throws Exception { + @Timeout(30) + void isValid() throws Exception { connectionBreaker.breakConnection(); boolean result = connection.isValid(5); @@ -82,24 +78,30 @@ private static final class ConnectionBreaker { private final Socket pgSocket; - private boolean breakConnection; + private volatile boolean breakConnection; /** * Constructor of the forwarder for the PostgreSQL server. * - * @param serverPort The forwarder server port. * @param pgServer The PostgreSQL server address. * @param pgPort The PostgreSQL server port. * @throws Exception if anything goes wrong binding the server. */ - ConnectionBreaker(final int serverPort, final String pgServer, - final int pgPort) throws Exception { + ConnectionBreaker(final String pgServer, final int pgPort) throws Exception { workers = Executors.newCachedThreadPool(); - internalServer = new ServerSocket(serverPort); + internalServer = new ServerSocket(9009); pgSocket = new Socket(pgServer, pgPort); breakConnection = false; } + public String getListenHost() { + return internalServer.getInetAddress().getHostAddress(); + } + + public int getListenPort() { + return internalServer.getLocalPort(); + } + /** * Starts to accept a asynchronous connection. * diff --git a/src/test/java/org/postgresql/jdbc/DeepBatchedInsertStatementTest.java b/src/test/java/org/postgresql/jdbc/DeepBatchedInsertStatementTest.java index e01f261..f7dff2a 100644 --- a/src/test/java/org/postgresql/jdbc/DeepBatchedInsertStatementTest.java +++ b/src/test/java/org/postgresql/jdbc/DeepBatchedInsertStatementTest.java @@ -5,7 +5,7 @@ package org.postgresql.jdbc; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.postgresql.PGProperty; import org.postgresql.core.ParameterList; @@ -15,12 +15,14 @@ import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.test.jdbc2.BatchExecuteTest; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.lang.reflect.Method; +import java.sql.Connection; import java.sql.Date; import java.sql.SQLException; -import java.sql.Statement; import java.util.ArrayList; import java.util.Properties; @@ -31,6 +33,22 @@ */ public class DeepBatchedInsertStatementTest extends BaseTest4 { + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "testbatch", "pk INTEGER, col1 INTEGER"); + TestUtil.createTable(con, "testunspecified", "pk INTEGER, bday TIMESTAMP"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "testbatch"); + TestUtil.dropTable(con, "testunspecified"); + } + } + /* * Set up the fixture for this testcase: a connection to a database with a * table for this test. @@ -38,18 +56,9 @@ public class DeepBatchedInsertStatementTest extends BaseTest4 { @Override public void setUp() throws Exception { super.setUp(); - Statement stmt = con.createStatement(); - - /* - * Drop the test table if it already exists for some reason. It is not an - * error if it doesn't exist. - */ - TestUtil.createTable(con, "testbatch", "pk INTEGER, col1 INTEGER"); - TestUtil.createTable(con, "testunspecified", "pk INTEGER, bday TIMESTAMP"); - - stmt.executeUpdate("INSERT INTO testbatch VALUES (1, 0)"); - stmt.close(); - + TestUtil.execute(con, "TRUNCATE testbatch"); + TestUtil.execute(con, "TRUNCATE testunspecified"); + TestUtil.execute(con, "INSERT INTO testbatch VALUES (1, 0)"); /* * Generally recommended with batch updates. By default we run all tests in * this test case with autoCommit disabled. @@ -57,14 +66,6 @@ public void setUp() throws Exception { con.setAutoCommit(false); } - // Tear down the fixture for this test case. - @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "testbatch"); - TestUtil.dropTable(con, "testunspecified"); - super.tearDown(); - } - @Override protected void updateProperties(Properties props) { PGProperty.REWRITE_BATCHED_INSERTS.set(props, true); @@ -242,7 +243,7 @@ public void testUnspecifiedParameterType() throws Exception { public void testVaryingTypeCounts() throws SQLException { PgPreparedStatement pstmt = null; try { - pstmt = (PgPreparedStatement)con.prepareStatement("INSERT INTO testunspecified VALUES (?,?)"); + pstmt = (PgPreparedStatement) con.prepareStatement("INSERT INTO testunspecified VALUES (?,?)"); pstmt.setInt(1, 1); pstmt.setDate(2, new Date(1)); pstmt.addBatch(); @@ -275,7 +276,7 @@ public void testVaryingTypeCounts() throws SQLException { * @return BatchedQueryDecorator[] queries after conversion * @throws Exception fault raised when the field cannot be accessed */ - private BatchedQuery[] transformBQD(PgPreparedStatement ps) throws Exception { + private static BatchedQuery[] transformBQD(PgPreparedStatement ps) throws Exception { // We store collections that get replace on the statement ArrayList batchStatements = ps.batchStatements; ArrayList batchParameters = ps.batchParameters; @@ -293,7 +294,7 @@ private BatchedQuery[] transformBQD(PgPreparedStatement ps) throws Exception { * @param bqds the converted queries * @return the total batch size */ - private int getBatchSize(BatchedQuery[] bqds) { + private static int getBatchSize(BatchedQuery[] bqds) { int total = 0; for (BatchedQuery bqd : bqds) { total += bqd.getBatchSize(); @@ -309,7 +310,7 @@ private int getBatchSize(BatchedQuery[] bqds) { * when encoded * @throws Exception fault raised if access to field not possible */ - private byte[] getEncodedStatementName(BatchedQuery bqd) + private static byte[] getEncodedStatementName(BatchedQuery bqd) throws Exception { Class clazz = Class.forName("org.postgresql.core.v3.SimpleQuery"); Method mESN = clazz.getDeclaredMethod("getEncodedStatementName"); diff --git a/src/test/java/org/postgresql/jdbc/DoubleArraysTest.java b/src/test/java/org/postgresql/jdbc/DoubleArraysTest.java index 1f21f54..3734d1e 100644 --- a/src/test/java/org/postgresql/jdbc/DoubleArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/DoubleArraysTest.java @@ -9,9 +9,9 @@ public class DoubleArraysTest extends AbstractArraysTest { - private static final double[][][] doubles = new double[][][] { - { { 1.2, 2.3, 3.7, 4.9 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }, - { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } }; + private static final double[][][] doubles = new double[][][]{ + {{1.2, 2.3, 3.7, 4.9}, {5, 6, 7, 8}, {9, 10, 11, 12}}, + {{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}}; public DoubleArraysTest() { super(doubles, true, Oid.FLOAT8_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/DoubleObjectArraysTest.java b/src/test/java/org/postgresql/jdbc/DoubleObjectArraysTest.java index 90bc820..854e7b2 100644 --- a/src/test/java/org/postgresql/jdbc/DoubleObjectArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/DoubleObjectArraysTest.java @@ -9,9 +9,9 @@ public class DoubleObjectArraysTest extends AbstractArraysTest { - private static final Double[][][] doubles = new Double[][][] { - { { 1.3, 2.4, 3.1, 4.2 }, { 5D, 6D, 7D, 8D }, { 9D, 10D, 11D, 12D } }, - { { 13D, 14D, 15D, 16D }, { 17D, 18D, 19D, null }, { 21D, 22D, 23D, 24D } } }; + private static final Double[][][] doubles = new Double[][][]{ + {{1.3, 2.4, 3.1, 4.2}, {5D, 6D, 7D, 8D}, {9D, 10D, 11D, 12D}}, + {{13D, 14D, 15D, 16D}, {17D, 18D, 19D, null}, {21D, 22D, 23D, 24D}}}; public DoubleObjectArraysTest() { super(doubles, true, Oid.FLOAT8_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/FloatArraysTest.java b/src/test/java/org/postgresql/jdbc/FloatArraysTest.java index ef9ed26..fd23a27 100644 --- a/src/test/java/org/postgresql/jdbc/FloatArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/FloatArraysTest.java @@ -9,9 +9,9 @@ public class FloatArraysTest extends AbstractArraysTest { - private static final float[][][] floats = new float[][][] { - { { 1.2f, 2.3f, 3.7f, 4.9f }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }, - { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } }; + private static final float[][][] floats = new float[][][]{ + {{1.2f, 2.3f, 3.7f, 4.9f}, {5, 6, 7, 8}, {9, 10, 11, 12}}, + {{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}}; public FloatArraysTest() { super(floats, true, Oid.FLOAT4_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/FloatObjectArraysTest.java b/src/test/java/org/postgresql/jdbc/FloatObjectArraysTest.java index c9a4a5f..50f08b9 100644 --- a/src/test/java/org/postgresql/jdbc/FloatObjectArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/FloatObjectArraysTest.java @@ -9,9 +9,9 @@ public class FloatObjectArraysTest extends AbstractArraysTest { - private static final Float[][][] floats = new Float[][][] { - { { 1.3f, 2.4f, 3.1f, 4.2f }, { 5f, 6f, 7f, 8f }, { 9f, 10f, 11f, 12f } }, - { { 13f, 14f, 15f, 16f }, { 17f, 18f, 19f, null }, { 21f, 22f, 23f, 24f } } }; + private static final Float[][][] floats = new Float[][][]{ + {{1.3f, 2.4f, 3.1f, 4.2f}, {5f, 6f, 7f, 8f}, {9f, 10f, 11f, 12f}}, + {{13f, 14f, 15f, 16f}, {17f, 18f, 19f, null}, {21f, 22f, 23f, 24f}}}; public FloatObjectArraysTest() { super(floats, true, Oid.FLOAT4_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/IntArraysTest.java b/src/test/java/org/postgresql/jdbc/IntArraysTest.java index 01ce25a..82ab376 100644 --- a/src/test/java/org/postgresql/jdbc/IntArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/IntArraysTest.java @@ -9,8 +9,8 @@ public class IntArraysTest extends AbstractArraysTest { - private static final int[][][] ints = new int[][][] { { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }, - { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } }; + private static final int[][][] ints = new int[][][]{{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, + {{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}}; public IntArraysTest() { super(ints, true, Oid.INT4_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/IntegerObjectArraysTest.java b/src/test/java/org/postgresql/jdbc/IntegerObjectArraysTest.java index 0266595..792e50f 100644 --- a/src/test/java/org/postgresql/jdbc/IntegerObjectArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/IntegerObjectArraysTest.java @@ -9,9 +9,9 @@ public class IntegerObjectArraysTest extends AbstractArraysTest { - private static final Integer[][][] ints = new Integer[][][] { - { { 1, 2, 3, 4 }, { 5, null, 7, 8 }, { 9, 10, 11, 12 } }, - { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } }; + private static final Integer[][][] ints = new Integer[][][]{ + {{1, 2, 3, 4}, {5, null, 7, 8}, {9, 10, 11, 12}}, + {{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}}; public IntegerObjectArraysTest() { super(ints, true, Oid.INT4_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/LargeObjectManagerTest.java b/src/test/java/org/postgresql/jdbc/LargeObjectManagerTest.java index 677baff..8236ade 100644 --- a/src/test/java/org/postgresql/jdbc/LargeObjectManagerTest.java +++ b/src/test/java/org/postgresql/jdbc/LargeObjectManagerTest.java @@ -7,17 +7,30 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import org.postgresql.PGConnection; import org.postgresql.core.ServerVersion; +import org.postgresql.largeobject.LargeObject; import org.postgresql.largeobject.LargeObjectManager; import org.postgresql.test.TestUtil; +import org.postgresql.test.util.StrangeInputStream; +import org.postgresql.test.util.StrangeOutputStream; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; -import org.junit.Assume; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.MessageDigest; +import java.sql.SQLException; import java.sql.Statement; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; class LargeObjectManagerTest { @@ -25,11 +38,20 @@ class LargeObjectManagerTest { * It is possible for PostgreSQL to send a ParameterStatus message after an ErrorResponse * Receiving such a message should not lead to an invalid connection state * See https://github.com/pgjdbc/pgjdbc/issues/2237 + * + * Note: This test is skipped when autosave is enabled because with autosave, a savepoint + * is created before the SET statement. When a subsequent error occurs, PostgreSQL doesn't + * send ParameterStatus to reset the parameter because it expects the client might + * ROLLBACK TO SAVEPOINT to recover. This is expected autosave behavior - protecting + * previous successful statements from being rolled back. */ @Test - public void testOpenWithErrorAndSubsequentParameterStatusMessageShouldLeaveConnectionInUsableStateAndUpdateParameterStatus() throws Exception { + void openWithErrorAndSubsequentParameterStatusMessageShouldLeaveConnectionInUsableStateAndUpdateParameterStatus() throws Exception { try (PgConnection con = (PgConnection) TestUtil.openDB()) { - Assume.assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_0)); + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_0)); + assumeTrue(con.getAutosave() == AutoSave.NEVER, + "Test requires autosave=never because autosave creates savepoints that prevent " + + "PostgreSQL from sending ParameterStatus on error"); con.setAutoCommit(false); String originalApplicationName = con.getParameterStatus("application_name"); try (Statement statement = con.createStatement()) { @@ -42,7 +64,7 @@ public void testOpenWithErrorAndSubsequentParameterStatusMessageShouldLeaveConne LargeObjectManager loManager = con.getLargeObjectAPI(); try { loManager.open(0, false); - fail("Succeeded in opening a non-existent large object"); + fail("Succeeded in opening a nonexistent large object"); } catch (PSQLException e) { assertEquals(PSQLState.UNDEFINED_OBJECT.getState(), e.getSQLState()); } @@ -52,4 +74,183 @@ public void testOpenWithErrorAndSubsequentParameterStatusMessageShouldLeaveConne } } } + + + /** + * Writes data into a large object and reads it back. + * The verifications are: + * 1) input size should match the output size + * 2) input checksum should match the output checksum + */ + @Test + void objectWriteThenRead() throws Throwable { + try (PgConnection con = (PgConnection) TestUtil.openDB()) { + // LO is not supported in auto-commit mode + con.setAutoCommit(false); + LargeObjectManager lom = con.unwrap(PGConnection.class).getLargeObjectAPI(); + MessageDigest md = MessageDigest.getInstance("SHA-256"); + long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10); + for (int i = 0; i < 100000 && System.currentTimeMillis() < deadline; i++) { + long seed = ThreadLocalRandom.current().nextLong(); + objectWriteThenRead(lom, seed, md); + // Creating too many large objects in a single transaction might lead to "ERROR: out of shared memory" + if (i % 1000 == 0) { + con.commit(); + } + } + } + } + + private final byte[][] buffers = new byte[][]{new byte[1024], new byte[8192], new byte[128 * 1024]}; + + private void objectWriteThenRead(LargeObjectManager lom, long seed, MessageDigest md) throws SQLException, IOException { + long loId = lom.createLO(); + try (LargeObject lo = lom.open(loId)) { + Random rnd = new Random(seed); + int expectedLength = rnd.nextInt(1000000); + // Write data to the stream + // We do not use try-with-resources as closing the output stream would close the large object + OutputStream os = lo.getOutputStream(); + { + byte[] buf = new byte[Math.min(256 * 1024, expectedLength)]; + // Do not use try-with-resources to avoid closing the large object + StrangeOutputStream fs = new StrangeOutputStream(os, rnd.nextLong(), 0.1); + { + int len = expectedLength; + while (len > 0) { + int writeSize = Math.min(buf.length, len); + rnd.nextBytes(buf); + md.update(buf, 0, writeSize); + fs.write(buf, 0, writeSize); + len -= writeSize; + } + fs.flush(); + } + } + // Verify the size of the resulting blob + assertEquals(expectedLength, lo.tell(), "Lob position after writing the data"); + + // Rewing the position to the beginning + // Ideally, .getInputStream should start reading from the beginning, however, it is not the + // case yet + lo.seek(0); + + // Read out the data and verify its contents + byte[] expectedChecksum = md.digest(); + md.reset(); + int actualLength = 0; + // Do not use try-with-resources to avoid closing the large object + InputStream is = lo.getInputStream(); + { + try (StrangeInputStream fs = new StrangeInputStream(rnd.nextLong(), is)) { + while (true) { + int bufferIndex = rnd.nextInt(buffers.length); + byte[] buf = buffers[bufferIndex]; + int read = fs.read(buf); + if (read == -1) { + break; + } + actualLength += read; + md.update(buf, 0, read); + } + } + byte[] actualChecksum = md.digest(); + if (!Arrays.equals(expectedChecksum, actualChecksum)) { + fail("Checksum of the input and output streams mismatch." + + " Input actualLength: " + expectedLength + + ", output actualLength: " + actualLength + + ", test seed: " + seed + + ", large object id: " + loId + ); + } + } + } catch (Throwable t) { + String message = "Test seed is " + seed; + t.addSuppressed(new Throwable(message) { + @Override + public Throwable fillInStackTrace() { + return this; + } + }); + throw t; + } finally { + lom.delete(loId); + } + } + + @Test + void closedLargeObjectThrowsException() throws Exception { + try (PgConnection con = (PgConnection) TestUtil.openDB()) { + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_0)); + con.setAutoCommit(false); + + LargeObjectManager lom = con.getLargeObjectAPI(); + long loId = lom.createLO(); + LargeObject lo = lom.open(loId, LargeObjectManager.WRITE); + + // Close the large object + lo.close(); + + // Now try to use methods on the closed object - they should all throw PSQLException + try { + lo.read(10); + fail("Expected PSQLException when calling read() on closed LargeObject"); + } catch (PSQLException e) { + assertEquals(PSQLState.OBJECT_NOT_IN_STATE.getState(), e.getSQLState()); + } + + try { + lo.write(new byte[]{1, 2, 3}); + fail("Expected PSQLException when calling write() on closed LargeObject"); + } catch (PSQLException e) { + assertEquals(PSQLState.OBJECT_NOT_IN_STATE.getState(), e.getSQLState()); + } + + try { + lo.seek(0); + fail("Expected PSQLException when calling seek() on closed LargeObject"); + } catch (PSQLException e) { + assertEquals(PSQLState.OBJECT_NOT_IN_STATE.getState(), e.getSQLState()); + } + + try { + lo.tell(); + fail("Expected PSQLException when calling tell() on closed LargeObject"); + } catch (PSQLException e) { + assertEquals(PSQLState.OBJECT_NOT_IN_STATE.getState(), e.getSQLState()); + } + + try { + lo.size(); + fail("Expected PSQLException when calling size() on closed LargeObject"); + } catch (PSQLException e) { + assertEquals(PSQLState.OBJECT_NOT_IN_STATE.getState(), e.getSQLState()); + } + + try { + lo.truncate(0); + fail("Expected PSQLException when calling truncate() on closed LargeObject"); + } catch (PSQLException e) { + assertEquals(PSQLState.OBJECT_NOT_IN_STATE.getState(), e.getSQLState()); + } + + try { + lo.getInputStream(); + fail("Expected PSQLException when calling getInputStream() on closed LargeObject"); + } catch (PSQLException e) { + assertEquals(PSQLState.OBJECT_NOT_IN_STATE.getState(), e.getSQLState()); + } + + try { + lo.getOutputStream(); + fail("Expected PSQLException when calling getOutputStream() on closed LargeObject"); + } catch (PSQLException e) { + assertEquals(PSQLState.OBJECT_NOT_IN_STATE.getState(), e.getSQLState()); + } + + // Clean up + lom.delete(loId); + con.commit(); + } + } } diff --git a/src/test/java/org/postgresql/jdbc/LongArraysTest.java b/src/test/java/org/postgresql/jdbc/LongArraysTest.java index db2c6c4..dcc39f2 100644 --- a/src/test/java/org/postgresql/jdbc/LongArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/LongArraysTest.java @@ -9,8 +9,8 @@ public class LongArraysTest extends AbstractArraysTest { - private static final long[][][] longs = new long[][][] { { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }, - { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } }; + private static final long[][][] longs = new long[][][]{{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, + {{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}}; public LongArraysTest() { super(longs, true, Oid.INT8_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/LongObjectArraysTest.java b/src/test/java/org/postgresql/jdbc/LongObjectArraysTest.java index 5625fab..421cdd8 100644 --- a/src/test/java/org/postgresql/jdbc/LongObjectArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/LongObjectArraysTest.java @@ -9,9 +9,9 @@ public class LongObjectArraysTest extends AbstractArraysTest { - private static final Long[][][] longs = new Long[][][] { - { { 1L, 2L, null, 4L }, { 5L, 6L, 7L, 8L }, { 9L, 10L, 11L, 12L } }, - { { 13L, 14L, 15L, 16L }, { 17L, 18L, 19L, 20L }, { 21L, 22L, 23L, 24L } } }; + private static final Long[][][] longs = new Long[][][]{ + {{1L, 2L, null, 4L}, {5L, 6L, 7L, 8L}, {9L, 10L, 11L, 12L}}, + {{13L, 14L, 15L, 16L}, {17L, 18L, 19L, 20L}, {21L, 22L, 23L, 24L}}}; public LongObjectArraysTest() { super(longs, true, Oid.INT8_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/NoColumnMetadataIssue1613Test.java b/src/test/java/org/postgresql/jdbc/NoColumnMetadataIssue1613Test.java index 65d3eb9..58ad678 100644 --- a/src/test/java/org/postgresql/jdbc/NoColumnMetadataIssue1613Test.java +++ b/src/test/java/org/postgresql/jdbc/NoColumnMetadataIssue1613Test.java @@ -5,13 +5,12 @@ package org.postgresql.jdbc; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.ResultSet; import java.sql.Statement; @@ -25,10 +24,9 @@ */ public class NoColumnMetadataIssue1613Test extends BaseTest4 { @Override - @Before public void setUp() throws Exception { super.setUp(); - TestUtil.createTempTable(con, "test_no_column_metadata","id int"); + TestUtil.createTempTable(con, "test_no_column_metadata", "id int"); } @Test diff --git a/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java b/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java new file mode 100644 index 0000000..8bf2a02 --- /dev/null +++ b/src/test/java/org/postgresql/jdbc/ParameterInjectionTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.jdbc; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.test.TestUtil; + +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class ParameterInjectionTest { + private interface ParameterBinder { + void bind(PreparedStatement stmt) throws SQLException; + } + + private static void testParamInjection(ParameterBinder bindPositiveOne, ParameterBinder bindNegativeOne) + throws SQLException { + try (Connection conn = TestUtil.openDB()) { + { + PreparedStatement stmt = conn.prepareStatement("SELECT -?"); + bindPositiveOne.bind(stmt); + try (ResultSet rs = stmt.executeQuery()) { + assertTrue(rs.next()); + assertEquals(1, rs.getMetaData().getColumnCount(), + "number of result columns must match"); + int value = rs.getInt(1); + assertEquals(-1, value); + } + bindNegativeOne.bind(stmt); + try (ResultSet rs = stmt.executeQuery()) { + assertTrue(rs.next()); + assertEquals(1, rs.getMetaData().getColumnCount(), + "number of result columns must match"); + int value = rs.getInt(1); + assertEquals(1, value); + } + } + { + PreparedStatement stmt = conn.prepareStatement("SELECT -?, ?"); + bindPositiveOne.bind(stmt); + stmt.setString(2, "\nWHERE false --"); + try (ResultSet rs = stmt.executeQuery()) { + assertTrue(rs.next(), "ResultSet should contain a row"); + assertEquals(2, rs.getMetaData().getColumnCount(), + "rs.getMetaData().getColumnCount("); + int value = rs.getInt(1); + assertEquals(-1, value); + } + + bindNegativeOne.bind(stmt); + stmt.setString(2, "\nWHERE false --"); + try (ResultSet rs = stmt.executeQuery()) { + assertTrue(rs.next(), "ResultSet should contain a row"); + assertEquals(2, rs.getMetaData().getColumnCount(), "rs.getMetaData().getColumnCount("); + int value = rs.getInt(1); + assertEquals(1, value); + } + + } + } + } + + @Test + public void handleInt2() throws SQLException { + testParamInjection( + stmt -> { + stmt.setShort(1, (short) 1); + }, + stmt -> { + stmt.setShort(1, (short) -1); + } + ); + } + + @Test + public void handleInt4() throws SQLException { + testParamInjection( + stmt -> { + stmt.setInt(1, 1); + }, + stmt -> { + stmt.setInt(1, -1); + } + ); + } + + @Test + public void handleBigInt() throws SQLException { + testParamInjection( + stmt -> { + stmt.setLong(1, (long) 1); + }, + stmt -> { + stmt.setLong(1, (long) -1); + } + ); + } + + @Test + public void handleNumeric() throws SQLException { + testParamInjection( + stmt -> { + stmt.setBigDecimal(1, new BigDecimal("1")); + }, + stmt -> { + stmt.setBigDecimal(1, new BigDecimal("-1")); + } + ); + } + + @Test + public void handleFloat() throws SQLException { + testParamInjection( + stmt -> { + stmt.setFloat(1, 1); + }, + stmt -> { + stmt.setFloat(1, -1); + } + ); + } + + @Test + public void handleDouble() throws SQLException { + testParamInjection( + stmt -> { + stmt.setDouble(1, 1); + }, + stmt -> { + stmt.setDouble(1, -1); + } + ); + } +} diff --git a/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java b/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java index 9bf848e..c2ea1af 100644 --- a/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java +++ b/src/test/java/org/postgresql/jdbc/PgSQLXMLTest.java @@ -5,18 +5,22 @@ package org.postgresql.jdbc; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.PGProperty; import org.postgresql.core.BaseConnection; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; +import org.postgresql.xml.NullErrorHandler; +import org.postgresql.xml.PGXmlFactoryFactory; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; import java.io.StringWriter; import java.io.Writer; @@ -28,6 +32,11 @@ import java.sql.Statement; import java.util.Properties; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.transform.Source; @@ -36,13 +45,13 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.stax.StAXSource; import javax.xml.transform.stream.StreamResult; public class PgSQLXMLTest extends BaseTest4 { @Override - @Before public void setUp() throws Exception { super.setUp(); TestUtil.createTempTable(con, "xmltab", "x xml"); @@ -50,10 +59,10 @@ public void setUp() throws Exception { @Test public void setCharacterStream() throws Exception { - String exmplar = "value"; + String example = "value"; SQLXML pgSQLXML = con.createSQLXML(); Writer writer = pgSQLXML.setCharacterStream(); - writer.write(exmplar); + writer.write(example); PreparedStatement preparedStatement = con.prepareStatement("insert into xmltab values (?)"); preparedStatement.setSQLXML(1, pgSQLXML); preparedStatement.execute(); @@ -63,7 +72,7 @@ public void setCharacterStream() throws Exception { assertTrue(rs.next()); SQLXML result = rs.getSQLXML(1); assertNotNull(result); - assertEquals(exmplar, result.getString()); + assertEquals(example, result.getString()); } private static final String LICENSE_URL = @@ -84,6 +93,52 @@ public void testLegacyXxe() throws Exception { } } + @Test + public void testCustomXxe() throws Exception { + Properties props = new Properties(); + props.setProperty(PGProperty.XML_FACTORY_FACTORY.getName(), CustomXmlFactoryFactory.class.getName()); + try (Connection conn = TestUtil.openDB(props)) { + BaseConnection baseConn = conn.unwrap(BaseConnection.class); + PgSQLXML xml = new PgSQLXML(baseConn, XXE_EXAMPLE); + xml.getSource(null); + } + } + + public static class CustomXmlFactoryFactory implements PGXmlFactoryFactory { + + @Override + public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + builder.setErrorHandler(NullErrorHandler.INSTANCE); + return builder; + } + + @Override + public TransformerFactory newTransformerFactory() { + return TransformerFactory.newInstance(); + } + + @Override + public SAXTransformerFactory newSAXTransformerFactory() { + return (SAXTransformerFactory) SAXTransformerFactory.newInstance(); + } + + @Override + public XMLInputFactory newXMLInputFactory() { + return XMLInputFactory.newInstance(); + } + + @Override + public XMLOutputFactory newXMLOutputFactory() { + return XMLOutputFactory.newInstance(); + } + + @Override + public XMLReader createXMLReader() throws SAXException { + return XMLReaderFactory.createXMLReader(); + } + } + private static String sourceToString(Source source) throws TransformerException { StringWriter sw = new StringWriter(); Transformer transformer = TransformerFactory.newInstance().newTransformer(); @@ -91,15 +146,15 @@ private static String sourceToString(Source source) throws TransformerException return sw.toString(); } - private void testGetSourceXxe(Class clazz) { + private static void testGetSourceXxe(Class clazz) { SQLException ex = assertThrows(SQLException.class, () -> { PgSQLXML xml = new PgSQLXML(null, XXE_EXAMPLE); xml.getSource(clazz); }); String message = ex.getCause().getMessage(); assertTrue( - "Expected to get a <> SAXParseException. Actual message is " + message, - message.startsWith("DOCTYPE is disallowed")); + message.contains("DOCTYPE"), + () -> "Expected to get a <> SAXParseException. Actual message is " + message); } @Test @@ -121,8 +176,8 @@ public void testGetSourceXxeSAXSource() throws Exception { }); String message = ex.getCause().getMessage(); assertTrue( - "Expected to get a <> TransformerException. Actual message is " + message, - message.startsWith("DOCTYPE is disallowed")); + message.contains("DOCTYPE"), + () -> "Expected to get a <> TransformerException. Actual message is " + message); } @Test diff --git a/src/test/java/org/postgresql/jdbc/ResourceLockTest.java b/src/test/java/org/postgresql/jdbc/ResourceLockTest.java new file mode 100644 index 0000000..7d70d9d --- /dev/null +++ b/src/test/java/org/postgresql/jdbc/ResourceLockTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2004, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.jdbc; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +class ResourceLockTest { + @Test + void obtainClose() { + final ResourceLock lock = new ResourceLock(); + + assertFalse(lock.isLocked(), + "lock.isLocked(). The newly created resource lock should be unlocked"); + assertFalse(lock.isHeldByCurrentThread(), + "lock.isHeldByCurrentThread(). The newly created resource lock should not be held by the current thread"); + + try (ResourceLock ignore = lock.obtain()) { + assertTrue(lock.isLocked(), + "lock.isLocked(). Obtained lock should be locked"); + assertTrue(lock.isHeldByCurrentThread(), + "lock.isHeldByCurrentThread(). Obtained lock should be held by the current thread"); + } + + assertFalse(lock.isLocked(), "lock.isLocked(). Closed resource lock should be unlocked"); + assertFalse(lock.isHeldByCurrentThread(), + "lock.isHeldByCurrentThread(). Closed resource lock should not be held by the current thread"); + } +} diff --git a/src/test/java/org/postgresql/jdbc/ScramTest.java b/src/test/java/org/postgresql/jdbc/ScramTest.java index 20443cf..c20168e 100644 --- a/src/test/java/org/postgresql/jdbc/ScramTest.java +++ b/src/test/java/org/postgresql/jdbc/ScramTest.java @@ -9,15 +9,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assumptions.assumeTrue; +import org.postgresql.PGProperty; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; +import org.postgresql.test.annotations.EnabledForServerVersionRange; +import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -28,6 +31,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.text.NumberFormat; import java.util.Properties; import java.util.stream.Stream; @@ -37,13 +41,13 @@ class ScramTest { private static final String ROLE_NAME = "testscram"; @BeforeAll - public static void setUp() throws Exception { + static void setUp() throws Exception { con = TestUtil.openPrivilegedDB(); assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v10)); } @AfterAll - public static void tearDown() throws Exception { + static void tearDown() throws Exception { try (Statement stmt = con.createStatement()) { stmt.execute("DROP ROLE IF EXISTS " + ROLE_NAME); } @@ -59,12 +63,12 @@ public static void tearDown() throws Exception { @ParameterizedTest @ValueSource(strings = {"My Space", "$ec ret", " rover june spelling ", "!zj5hs*k5 STj@DaRUy", "q\u00A0w\u2000e\u2003r\u2009t\u3000y"}) - void testPasswordWithSpace(String passwd) throws SQLException { + void passwordWithSpace(String passwd) throws SQLException { createRole(passwd); // Create role password with spaces. Properties props = new Properties(); - props.setProperty("username", ROLE_NAME); - props.setProperty("password", passwd); + PGProperty.USER.set(props, ROLE_NAME); + PGProperty.PASSWORD.set(props, passwd); try (Connection c = assertDoesNotThrow(() -> TestUtil.openDB(props)); Statement stmt = c.createStatement(); @@ -83,7 +87,7 @@ void testPasswordWithSpace(String passwd) throws SQLException { @ParameterizedTest @ValueSource(strings = {"My Space", "$ec ret", "rover june spelling", "!zj5hs*k5 STj@DaRUy", "q\u00A0w\u2000e\u2003r\u2009t\u3000y"}) - void testPasswordWithoutSpace(String passwd) throws SQLException { + void passwordWithoutSpace(String passwd) throws SQLException { String passwdNoSpaces = passwd.codePoints() .filter(i -> !Character.isSpaceChar(i)) .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) @@ -92,8 +96,8 @@ void testPasswordWithoutSpace(String passwd) throws SQLException { createRole(passwdNoSpaces); // Create role password without spaces. Properties props = new Properties(); - props.setProperty("username", ROLE_NAME); - props.setProperty("password", passwd); // Open connection with spaces + PGProperty.USER.set(props, ROLE_NAME); + PGProperty.PASSWORD.set(props, passwd); // Open connection with spaces SQLException ex = assertThrows(SQLException.class, () -> TestUtil.openDB(props)); assertEquals(PSQLState.INVALID_PASSWORD.getState(), ex.getSQLState()); @@ -108,23 +112,90 @@ private static Stream provideArgsForTestInvalid() { @ParameterizedTest @MethodSource("provideArgsForTestInvalid") - void testInvalidPasswords(String password, String expectedMessage) throws SQLException { + void invalidPasswords(String password, String expectedMessage) throws SQLException { // We are testing invalid passwords so that correct one does not matter createRole("anything_goes_here"); + SQLException e = assertThrows( + SQLException.class, + () -> DriverManager.getConnection(TestUtil.getURL(), ROLE_NAME, password), + "SCRAM connection attempt with invalid password should fail"); + assertEquals(expectedMessage, e.getMessage()); + } + + private PSQLException scramAuthExpectingFailure(String scramMaxIterations, int serverScramIterations, String password) throws SQLException { + createRoleWithCustomScramIters(serverScramIterations); Properties props = new Properties(); - props.setProperty("user", ROLE_NAME); - if (password != null) { - props.setProperty("password", password); + PGProperty.USER.set(props, ROLE_NAME); + PGProperty.PASSWORD.set(props, password); + if (scramMaxIterations != null) { + PGProperty.SCRAM_MAX_ITERATIONS.set(props, scramMaxIterations); } - try (Connection conn = DriverManager.getConnection(TestUtil.getURL(), props)) { - fail("SCRAM connection attempt with invalid password should fail"); - } catch (SQLException e) { - assertEquals(expectedMessage, e.getMessage()); + return assertThrows(PSQLException.class, () -> TestUtil.openDB(props)); + } + + @Test + void rejectIterationCountAboveDefaultCap() throws SQLException { + int serverScramIterations = 789_123_456; + PSQLException ex = scramAuthExpectingFailure(null, serverScramIterations, "does-not-matter"); + assertTrue(ex.getMessage().contains("exceeds"), + "expected iteration-cap error, got: " + ex.getMessage()); + assertTrue(ex.getMessage().contains("scramMaxIterations"), + "error should reference the connection property name, got: " + ex.getMessage()); + // The message is formatted through MessageFormat, which applies locale-aware grouping + // to integer arguments; format the expected numbers the same way. + NumberFormat nf = NumberFormat.getNumberInstance(); + assertTrue(ex.getMessage().contains(nf.format(serverScramIterations)), + "error should include the configured cap, got: " + ex.getMessage()); + } + + @Test + void rejectIterationCountAboveCustomCap() throws SQLException { + int scramMaxIterations = 123_456; + int serverScramIterations = 789_123_456; + PSQLException ex = scramAuthExpectingFailure(Integer.toString(scramMaxIterations), serverScramIterations, "does-not-matter"); + // The message is formatted through MessageFormat, which applies locale-aware grouping + // to integer arguments; format the expected numbers the same way. + NumberFormat nf = NumberFormat.getNumberInstance(); + assertTrue(ex.getMessage().contains(nf.format(scramMaxIterations)), + "error should include the configured cap, got: " + ex.getMessage()); + assertTrue(ex.getMessage().contains(nf.format(serverScramIterations)), + "error should include the server-supplied iteration count, got: " + ex.getMessage()); + } + + @Test + void rejectValidCredentialsAboveCustomCap() throws SQLException { + String password = "t0pSecret"; + createRole(password); + Properties props = new Properties(); + PGProperty.USER.set(props, ROLE_NAME); + PGProperty.PASSWORD.set(props, password); + PGProperty.SCRAM_MAX_ITERATIONS.set(props, "1234"); + PSQLException ex = assertThrows(PSQLException.class, () -> TestUtil.openDB(props)); + // The message is formatted through MessageFormat, which applies locale-aware grouping + // to integer arguments; format the expected numbers the same way. + NumberFormat nf = NumberFormat.getNumberInstance(); + assertTrue(ex.getMessage().contains(nf.format(1234)), + "error should include the configured cap, got: " + ex.getMessage()); + } + + @Test + @EnabledForServerVersionRange(gte = "16") + void acceptsValidCredentialsBelowCustomCap() throws SQLException { + int serverScramIterations = Integer.parseInt(TestUtil.queryForString(con, "SHOW scram_iterations")); + String password = "t0pSecret"; + createRole(password); + Properties props = new Properties(); + PGProperty.USER.set(props, ROLE_NAME); + PGProperty.PASSWORD.set(props, password); + PGProperty.SCRAM_MAX_ITERATIONS.set(props, Integer.toString(serverScramIterations)); + try (Connection conn = TestUtil.openDB(props)) { + String username = TestUtil.queryForString(conn, "SELECT USER"); + assertEquals(ROLE_NAME, username); } } - private void createRole(String passwd) throws SQLException { + private static void createRole(String passwd) throws SQLException { try (Statement stmt = con.createStatement()) { stmt.execute("SET password_encryption='scram-sha-256'"); stmt.execute("DROP ROLE IF EXISTS " + ROLE_NAME); @@ -132,4 +203,18 @@ private void createRole(String passwd) throws SQLException { } } + private static void createRoleWithCustomScramIters(int iters) throws SQLException { + TestUtil.execute(con, "DROP ROLE IF EXISTS " + ROLE_NAME); + TestUtil.execute(con, "CREATE ROLE " + ROLE_NAME + " WITH LOGIN"); + // SCRAM-SHA-256$:$: + // salt: 16 zero bytes, StoredKey and ServerKey: 32 zero bytes each. + String encodedPassword = "SCRAM-SHA-256$" + iters + + ":AAAAAAAAAAAAAAAAAAAAAA==" + + "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" + + ":AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + // NOTE: We must directly update the system catalog to prevent the server from trying to + // verify the password at creation time. Otherwise it will try to hash empty string with + // our huge number of iterations to ensure the password is not an empty string. + TestUtil.execute(con, "UPDATE pg_authid SET rolpassword = '" + encodedPassword + "' WHERE rolname = '" + ROLE_NAME + "'"); + } } diff --git a/src/test/java/org/postgresql/jdbc/ShortArraysTest.java b/src/test/java/org/postgresql/jdbc/ShortArraysTest.java index ed27792..3fb46b0 100644 --- a/src/test/java/org/postgresql/jdbc/ShortArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/ShortArraysTest.java @@ -9,8 +9,8 @@ public class ShortArraysTest extends AbstractArraysTest { - private static final short[][][] shorts = new short[][][] { { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }, - { { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } } }; + private static final short[][][] shorts = new short[][][]{{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, + {{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}}; public ShortArraysTest() { super(shorts, true, Oid.INT2_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/ShortObjectArraysTest.java b/src/test/java/org/postgresql/jdbc/ShortObjectArraysTest.java index 856da90..9abc6cb 100644 --- a/src/test/java/org/postgresql/jdbc/ShortObjectArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/ShortObjectArraysTest.java @@ -9,8 +9,8 @@ public class ShortObjectArraysTest extends AbstractArraysTest { - private static final Short[][][] shorts = new Short[][][] { { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }, - { { 13, 14, 15, 16 }, { 17, 18, null, 20 }, { 21, 22, 23, 24 } } }; + private static final Short[][][] shorts = new Short[][][]{{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, + {{13, 14, 15, 16}, {17, 18, null, 20}, {21, 22, 23, 24}}}; public ShortObjectArraysTest() { super(shorts, true, Oid.INT2_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/StringArraysTest.java b/src/test/java/org/postgresql/jdbc/StringArraysTest.java index d9e131a..8fc0ce0 100644 --- a/src/test/java/org/postgresql/jdbc/StringArraysTest.java +++ b/src/test/java/org/postgresql/jdbc/StringArraysTest.java @@ -9,10 +9,10 @@ public class StringArraysTest extends AbstractArraysTest { - private static final String[][][] strings = new String[][][] { - { { "some", "String", "haVE some \u03C0", "another" }, { null, "6L", "7L", "8L" }, //unicode escape for pi character - { "asdf", " asdf ", "11L", null } }, - { { "13L", null, "asasde4wtq", "16L" }, { "17L", "", "19L", "20L" }, { "21L", "22L", "23L", "24L" } } }; + private static final String[][][] strings = new String[][][]{ + {{"some", "String", "haVE some \u03C0", "another"}, {null, "6L", "7L", "8L"}, //unicode escape for pi character + {"asdf", " asdf ", "11L", null}}, + {{"13L", null, "asasde4wtq", "16L"}, {"17L", "", "19L", "20L"}, {"21L", "22L", "23L", "24L"}}}; public StringArraysTest() { super(strings, true, Oid.VARCHAR_ARRAY); diff --git a/src/test/java/org/postgresql/jdbc/UUIDArrayTest.java b/src/test/java/org/postgresql/jdbc/UUIDArrayTest.java new file mode 100644 index 0000000..4246534 --- /dev/null +++ b/src/test/java/org/postgresql/jdbc/UUIDArrayTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2022, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.jdbc; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.postgresql.core.ServerVersion; +import org.postgresql.test.TestUtil; +import org.postgresql.test.annotations.tags.Arrays; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.UUID; + +@Arrays +class UUIDArrayTest { + + private static Connection con; + private static final String TABLE_NAME = "uuid_table"; + private static final String INSERT1 = "INSERT INTO " + TABLE_NAME + + " (id, data1) VALUES (?, ?)"; + private static final String INSERT2 = "INSERT INTO " + TABLE_NAME + + " (id, data2) VALUES (?, ?)"; + private static final String SELECT1 = "SELECT data1 FROM " + TABLE_NAME + + " WHERE id = ?"; + private static final String SELECT2 = "SELECT data2 FROM " + TABLE_NAME + + " WHERE id = ?"; + private static final UUID[] uids1 = new UUID[]{UUID.randomUUID(), UUID.randomUUID()}; + private static final UUID[][] uids2 = new UUID[][]{uids1}; + + @BeforeAll + static void setUp() throws Exception { + con = TestUtil.openDB(); + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_6)); + try (Statement stmt = con.createStatement()) { + stmt.execute("CREATE TABLE " + TABLE_NAME + + " (id int PRIMARY KEY, data1 UUID[], data2 UUID[][])"); + } + } + + @AfterAll + static void tearDown() throws Exception { + try (Statement stmt = con.createStatement()) { + stmt.execute("DROP TABLE IF EXISTS " + TABLE_NAME); + } + TestUtil.closeDB(con); + } + + @Test + void test1DWithCreateArrayOf() throws SQLException { + try (Connection c = assertDoesNotThrow(() -> TestUtil.openDB()); + PreparedStatement stmt1 = c.prepareStatement(INSERT1); + PreparedStatement stmt2 = c.prepareStatement(SELECT1)) { + stmt1.setInt(1, 100); + stmt1.setArray(2, c.createArrayOf("uuid", uids1)); + stmt1.execute(); + + stmt2.setInt(1, 100); + stmt2.execute(); + try (ResultSet rs = stmt2.getResultSet()) { + assertTrue(rs.next()); + UUID[] array = (UUID[]) rs.getArray(1).getArray(); + assertEquals(uids1[0], array[0]); + assertEquals(uids1[1], array[1]); + } + } + } + + @Test + void test1DWithSetObject() throws SQLException { + try (Connection c = assertDoesNotThrow(() -> TestUtil.openDB()); + PreparedStatement stmt1 = c.prepareStatement(INSERT1); + PreparedStatement stmt2 = c.prepareStatement(SELECT1)) { + stmt1.setInt(1, 101); + stmt1.setObject(2, uids1); + stmt1.execute(); + + stmt2.setInt(1, 101); + stmt2.execute(); + try (ResultSet rs = stmt2.getResultSet()) { + assertTrue(rs.next()); + UUID[] array = (UUID[]) rs.getArray(1).getArray(); + assertEquals(uids1[0], array[0]); + assertEquals(uids1[1], array[1]); + } + } + } + + @Test + void test2DWithCreateArrayOf() throws SQLException { + try (Connection c = assertDoesNotThrow(() -> TestUtil.openDB()); + PreparedStatement stmt1 = c.prepareStatement(INSERT2); + PreparedStatement stmt2 = c.prepareStatement(SELECT2)) { + stmt1.setInt(1, 200); + stmt1.setArray(2, c.createArrayOf("uuid", uids2)); + stmt1.execute(); + + stmt2.setInt(1, 200); + stmt2.execute(); + try (ResultSet rs = stmt2.getResultSet()) { + assertTrue(rs.next()); + UUID[][] array = (UUID[][]) rs.getArray(1).getArray(); + assertEquals(uids2[0][0], array[0][0]); + assertEquals(uids2[0][1], array[0][1]); + } + } + } + + @Test + void test2DWithSetObject() throws SQLException { + try (Connection c = assertDoesNotThrow(() -> TestUtil.openDB()); + PreparedStatement stmt1 = c.prepareStatement(INSERT2); + PreparedStatement stmt2 = c.prepareStatement(SELECT2)) { + stmt1.setInt(1, 201); + stmt1.setObject(2, uids2); + stmt1.execute(); + + stmt2.setInt(1, 201); + stmt2.execute(); + try (ResultSet rs = stmt2.getResultSet()) { + assertTrue(rs.next()); + UUID[][] array = (UUID[][]) rs.getArray(1).getArray(); + assertEquals(uids2[0][0], array[0][0]); + assertEquals(uids2[0][1], array[0][1]); + } + } + } +} diff --git a/src/test/java/org/postgresql/jdbcurlresolver/PgPassParserTest.java b/src/test/java/org/postgresql/jdbcurlresolver/PgPassParserTest.java new file mode 100644 index 0000000..718ab3d --- /dev/null +++ b/src/test/java/org/postgresql/jdbcurlresolver/PgPassParserTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.jdbcurlresolver; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.postgresql.PGEnvironment; +import org.postgresql.util.StubEnvironmentAndProperties; + +import org.junit.jupiter.api.Test; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.properties.SystemProperties; +import uk.org.webcompere.systemstubs.resource.Resources; + +import java.net.URL; + +/** + * Password resource location used is decided based on availability of different environment + * variables and file existence in user home directory. Tests verify selection of proper resource. + * Also, resource content (* matching, escape character handling, comments etc) can be written + * creatively. Test verify several cases. + * + * @author Marek Läll + */ +@StubEnvironmentAndProperties +class PgPassParserTest { + + // "org.postgresql.pgpassfile" : missing + // "PGPASSFILE" : missing + // ".pgpass" : missing + @Test + void getPassword11() throws Exception { + Resources.with( + new EnvironmentVariables(PGEnvironment.PGPASSFILE.getName(), "", "APPDATA", "/tmp/dir-nonexistent"), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGPASSFILE.getName(), "", "user.home", "/tmp/dir-nonexistent") + ).execute(() -> { + String result = PgPassParser.getPassword("localhost", "5432", "postgres", "postgres"); + assertNull(result); + }); + } + + // "org.postgresql.pgpassfile" : missing + // "PGPASSFILE" : missing + // ".pgpass" : exist + // : exist + @Test + void getPassword22() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGPASSFILE.getName(), "", "APPDATA", urlPath.getPath() ), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGPASSFILE.getName(), "", "user.home", urlPath.getPath()) + ).execute(() -> { + String result = PgPassParser.getPassword("localhost", "5432", "postgres", + "postgres"); + assertEquals("postgres1", result); + result = PgPassParser.getPassword("localhost2", "5432", "postgres", "postgres"); + assertEquals("postgres\\", result); + result = PgPassParser.getPassword("localhost3", "5432", "postgres", "postgres"); + assertEquals("postgres:", result); + result = PgPassParser.getPassword("localhost4", "5432", "postgres", "postgres"); + assertEquals("postgres1:", result); + result = PgPassParser.getPassword("localhost5", "5432", "postgres", "postgres"); + assertEquals("postgres5", result); + result = PgPassParser.getPassword("localhost6", "5432", "postgres", "postgres"); + assertEquals("post\\gres\\", result); + result = PgPassParser.getPassword("localhost7", "5432", "postgres", "postgres"); + assertEquals(" ab cd", result); + result = PgPassParser.getPassword("localhost8", "5432", "postgres", "postgres"); + assertEquals("", result); + // + result = PgPassParser.getPassword("::1", "1234", "colon:db", "colon:user"); + assertEquals("pass:pass", result); + result = PgPassParser.getPassword("::1", "12345", "colon:db", "colon:user"); + assertEquals("pass:pass1", result); + result = PgPassParser.getPassword("::1", "1234", "slash\\db", "slash\\user"); + assertEquals("pass\\pass", result); + result = PgPassParser.getPassword("::1", "12345", "slash\\db", "slash\\user"); + assertEquals("pass\\pass1", result); + // + result = PgPassParser.getPassword("any", "5432", "postgres", "postgres"); + assertEquals("anyhost5", result); + result = PgPassParser.getPassword("localhost11", "9999", "postgres", "postgres"); + assertEquals("anyport5", result); + result = PgPassParser.getPassword("localhost12", "5432", "anydb", "postgres"); + assertEquals("anydb5", result); + result = PgPassParser.getPassword("localhost13", "5432", "postgres", "anyuser"); + assertEquals("anyuser5", result); + // + result = PgPassParser.getPassword("anyhost", "6544", "anydb", "anyuser"); + assertEquals("absolute-any", result); + }); + } + + // "org.postgresql.pgpassfile" : missing + // "PGPASSFILE" : exist + // ".pgpass" : exist + // : missing + @Test + void getPassword31() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + URL urlFileEnv = getClass().getResource("/pg_service/pgpassfileEnv.conf"); + assertNotNull(urlFileEnv); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGPASSFILE.getName(), urlFileEnv.getFile(), "APPDATA", urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGPASSFILE.getName(), "", "user.home", urlPath.getPath()) + ).execute(() -> { + String result = PgPassParser.getPassword("localhost-missing", "5432", "postgres1", "postgres2"); + assertNull(result); + }); + } + + // "org.postgresql.pgpassfile" : missing + // "PGPASSFILE" : exist + // ".pgpass" : exist + // : exist + @Test + void getPassword32() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + URL urlFileEnv = getClass().getResource("/pg_service/pgpassfileEnv.conf"); + assertNotNull(urlFileEnv); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGPASSFILE.getName(), urlFileEnv.getPath(), "APPDATA", urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGPASSFILE.getName(), "", "user.home", urlPath.getPath()) + ).execute(() -> { + String result = PgPassParser.getPassword("localhost", "5432", "postgres1", + "postgres2"); + assertEquals("postgres3", result); + }); + } + + + // "org.postgresql.pgpassfile" : exist + // "PGPASSFILE" : exist + // ".pgpass" : exist + // : missing + @Test + void getPassword41() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + URL urlFileEnv = getClass().getResource("/pg_service/pgpassfileEnv.conf"); + assertNotNull(urlFileEnv); + URL urlFileProps = getClass().getResource("/pg_service/pgpassfileProps.conf"); + assertNotNull(urlFileProps); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGPASSFILE.getName(), urlFileEnv.getFile(), "APPDATA", urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGPASSFILE.getName(), "", "user.home", urlPath.getPath()) + ).execute(() -> { + String result = PgPassParser.getPassword("localhost-missing", "5432", "postgres1", "postgres2"); + assertNull(result); + }); + } + + // "org.postgresql.pgpassfile" : exist + // "PGPASSFILE" : exist + // ".pgpass" : exist + // : exist + @Test + void getPassword42() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + URL urlFileEnv = getClass().getResource("/pg_service/pgpassfileEnv.conf"); + assertNotNull(urlFileEnv); + URL urlFileProps = getClass().getResource("/pg_service/pgpassfileProps.conf"); + assertNotNull(urlFileProps); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGPASSFILE.getName(), urlFileEnv.getPath(), "APPDATA", urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGPASSFILE.getName(), urlFileProps.getFile(), "user.home", urlPath.getPath()) + ).execute(() -> { + String result = PgPassParser.getPassword("localhost77", "5432", "any", "postgres11"); + assertEquals("postgres22", result); + result = PgPassParser.getPassword("localhost888", "5432", "any", "postgres11"); + assertNull(result); + result = PgPassParser.getPassword("localhost999", "5432", "any", "postgres11"); + assertNull(result); + }); + } + +} diff --git a/src/test/java/org/postgresql/jdbcurlresolver/PgServiceConfParserTest.java b/src/test/java/org/postgresql/jdbcurlresolver/PgServiceConfParserTest.java new file mode 100644 index 0000000..9660b06 --- /dev/null +++ b/src/test/java/org/postgresql/jdbcurlresolver/PgServiceConfParserTest.java @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.jdbcurlresolver; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.PGEnvironment; +import org.postgresql.test.annotations.DisableLogger; +import org.postgresql.util.StubEnvironmentAndProperties; + +import org.junit.jupiter.api.Test; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.properties.SystemProperties; +import uk.org.webcompere.systemstubs.resource.Resources; + +import java.net.URL; +import java.util.Properties; + +/** + * Service resource location used is decided based on availability of different environment + * variables and file existence in user home directory. Tests verify selection of proper resource. + * Also, resource content (section headers, comments, key-value pairs etc) can be written + * creatively. Test verify several cases. + * + * @author Marek Läll + */ +@StubEnvironmentAndProperties +class PgServiceConfParserTest { + + // "org.postgresql.pgservicefile" : missing + // "PGSERVICEFILE" : missing + // ".pg_service.conf" : missing + // "PGSYSCONFDIR" : missing + @Test + void pgService11() throws Exception { + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), "", PGEnvironment.PGSYSCONFDIR.getName(), ""), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", "/tmp/dir-nonexistent") + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("service-nonexistent"); + assertNull(result); + }); + } + + // "org.postgresql.pgservicefile" : missing + // "PGSERVICEFILE" : missing + // ".pg_service.conf" : missing + // "PGSYSCONFDIR" : exist + // : missing + @Test + void pgService21() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), "", PGEnvironment.PGSYSCONFDIR.getName(), urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", "/tmp/dir-nonexistent") + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("service-nonexistent"); + assertNull(result); + result = PgServiceConfParser.getServiceProperties("empty-service1"); + assertNotNull(result); + assertTrue(result.isEmpty()); + }); + } + + // "org.postgresql.pgservicefile" : missing + // "PGSERVICEFILE" : missing + // ".pg_service.conf" : missing + // "PGSYSCONFDIR" : exist + // : exist + @Test + void pgService22() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), "", PGEnvironment.PGSYSCONFDIR.getName(), urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", "/tmp/dir-nonexistent") + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("test-service1"); + assertNotNull(result); + assertEquals("test_dbname", result.get("PGDBNAME")); + assertEquals("global-test-host.test.net", result.get("PGHOST")); + assertEquals("5433", result.get("PGPORT")); + assertEquals("admin", result.get("user")); + assertEquals(4, result.size()); + }); + } + + // "org.postgresql.pgservicefile" : missing + // "PGSERVICEFILE" : missing + // ".pg_service.conf" : missing + // "PGSYSCONFDIR" : exist - but file itself is missing + // : exist + @Test + void pgService23() throws Exception { + String nonExistingDir = "non-existing-dir"; + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), "", PGEnvironment.PGSYSCONFDIR.getName(), nonExistingDir), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", "/tmp/dir-nonexistent") + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("test-service1"); + assertNull(result); + }); + } + + + // "org.postgresql.pgservicefile" : missing + // "PGSERVICEFILE" : missing + // ".pg_service.conf" : exist + // "PGSYSCONFDIR" : exist + // : missing + @Test + void pgService31() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), "", PGEnvironment.PGSYSCONFDIR.getName(), urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", urlPath.getPath()) + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("service-nonexistent"); + assertNull(result); + result = PgServiceConfParser.getServiceProperties("empty-service1"); + assertNotNull(result); + assertTrue(result.isEmpty()); + }); + } + + // "org.postgresql.pgservicefile" : missing + // "PGSERVICEFILE" : missing + // ".pg_service.conf" : exist + // "PGSYSCONFDIR" : exist + // : exist + @Test + void pgService32() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), "", "APPDATA", urlPath.getPath(), PGEnvironment.PGSYSCONFDIR.getName(), urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", urlPath.getPath()) + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("test-service1"); + assertNotNull(result); + assertEquals(" test_dbname", result.get("PGDBNAME")); + assertEquals("local-test-host.test.net", result.get("PGHOST")); + assertEquals("5433", result.get("PGPORT")); + assertEquals("admin", result.get("user")); + assertEquals(4, result.size()); + }); + } + + + // "org.postgresql.pgservicefile" : missing + // "PGSERVICEFILE" : exist + // ".pg_service.conf" : exist + // "PGSYSCONFDIR" : exist + // : missing + @Test + void pgService41() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + URL urlFileEnv = getClass().getResource("/pg_service/pgservicefileEnv.conf"); + assertNotNull(urlFileEnv); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), urlFileEnv.getFile(), PGEnvironment.PGSYSCONFDIR.getName(), urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", urlPath.getPath()) + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("service-nonexistent"); + assertNull(result); + result = PgServiceConfParser.getServiceProperties("empty-service1"); + assertNotNull(result); + assertTrue(result.isEmpty()); + }); + } + + // "org.postgresql.pgservicefile" : missing + // "PGSERVICEFILE" : exist + // ".pg_service.conf" : exist + // "PGSYSCONFDIR" : exist + // : exist + @Test + void pgService42() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + URL urlFileEnv = getClass().getResource("/pg_service/pgservicefileEnv.conf"); + assertNotNull(urlFileEnv); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), urlFileEnv.getFile(), PGEnvironment.PGSYSCONFDIR.getName(), urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", urlPath.getPath()) + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("test-service1"); + assertNotNull(result); + assertEquals("test_dbname", result.get("PGDBNAME")); + assertEquals("pgservicefileEnv-test-host.test.net", result.get("PGHOST")); + assertEquals("5433", result.get("PGPORT")); + assertEquals("admin", result.get("user")); + assertEquals("disable", result.get("sslmode")); + assertEquals(5, result.size()); + }); + } + + // "org.postgresql.pgservicefile" : missing + // "PGSERVICEFILE" : exist - but file itself is missing + // ".pg_service.conf" : exist + // "PGSYSCONFDIR" : exist + // : exist + @Test + @DisableLogger(PgServiceConfParser.class) + void pgService43() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + String nonExistingFile = "non-existing-file.conf"; + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), nonExistingFile, PGEnvironment.PGSYSCONFDIR.getName(), urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", urlPath.getPath()) + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("test-service1"); + assertNull(result); + }); + } + + + // "org.postgresql.pgservicefile" : exist + // "PGSERVICEFILE" : exist + // ".pg_service.conf" : exist + // "PGSYSCONFDIR" : exist + // : missing + @Test + void pgService51() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + URL urlFileEnv = getClass().getResource("/pg_service/pgservicefileEnv.conf"); + assertNotNull(urlFileEnv); + URL urlFileProps = getClass().getResource("/pg_service/pgservicefileProps.conf"); + assertNotNull(urlFileProps); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), urlFileEnv.getFile(), PGEnvironment.PGSYSCONFDIR.getName(), urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), urlFileProps.getFile(), "user.home", urlPath.getPath()) + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("service-nonexistent"); + assertNull(result); + result = PgServiceConfParser.getServiceProperties("empty-service1"); + assertNotNull(result); + assertTrue(result.isEmpty()); + }); + } + + // "org.postgresql.pgservicefile" : exist + // "PGSERVICEFILE" : exist + // ".pg_service.conf" : exist + // "PGSYSCONFDIR" : exist + // : exist + @Test + void pgService52() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + URL urlFileEnv = getClass().getResource("/pg_service/pgservicefileEnv.conf"); + assertNotNull(urlFileEnv); + URL urlFileProps = getClass().getResource("/pg_service/pgservicefileProps.conf"); + assertNotNull(urlFileProps); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), urlFileEnv.getFile(), PGEnvironment.PGSYSCONFDIR.getName(), urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), urlFileProps.getFile(), "user.home", urlPath.getPath()) + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("test-service1"); + assertNotNull(result); + assertEquals("test_dbname", result.get("PGDBNAME")); + assertEquals("pgservicefileProps-test-host.test.net", result.get("PGHOST")); + assertEquals("5433", result.get("PGPORT")); + assertEquals("admin", result.get("user")); + assertEquals(4, result.size()); + }); + } + + // "org.postgresql.pgservicefile" : exist - but file itself is missing + // "PGSERVICEFILE" : exist + // ".pg_service.conf" : exist + // "PGSYSCONFDIR" : exist + // : exist + @Test + @DisableLogger(PgServiceConfParser.class) + void pgService53() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + URL urlFileEnv = getClass().getResource("/pg_service/pgservicefileEnv.conf"); + assertNotNull(urlFileEnv); + String nonExistingFile = "non-existing-file.conf"; + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), urlFileEnv.getFile(), PGEnvironment.PGSYSCONFDIR.getName(), urlPath.getPath()), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), nonExistingFile, "user.home", urlPath.getPath()) + ).execute(() -> { + Properties result = PgServiceConfParser.getServiceProperties("test-service1"); + assertNull(result); + }); + } + + + // resource content read tests + @Test + @DisableLogger(PgServiceConfParser.class) + void pgService61() throws Exception { + URL urlPath = getClass().getResource("/pg_service"); + assertNotNull(urlPath); + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), "", "APPDATA", urlPath.getPath(), PGEnvironment.PGSYSCONFDIR.getName(), ""), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", urlPath.getPath()) + ).execute(() -> { + Properties result; + // fail if there is space between key and equal sign + result = PgServiceConfParser.getServiceProperties("fail-case-1"); + assertNull(result); + // service name is case-sensitive + result = PgServiceConfParser.getServiceProperties("fail-case-2"); + assertNull(result); + // service name is case-sensitive + result = PgServiceConfParser.getServiceProperties("fail-case-2"); + assertNull(result); + // invalid line in the section + result = PgServiceConfParser.getServiceProperties("fail-case-3"); + assertNull(result); + // service name: space before and after name becomes part of name + result = PgServiceConfParser.getServiceProperties(" success-case-3 "); + assertNotNull(result); + assertEquals("local-somehost3", result.get("PGHOST")); + assertEquals(1, result.size()); + // service name: space inside name is part of name + result = PgServiceConfParser.getServiceProperties("success case 4"); + assertNotNull(result); + assertEquals("local-somehost4", result.get("PGHOST")); + assertEquals(1, result.size()); + }); + } + +} diff --git a/src/test/java/org/postgresql/test/Replication.java b/src/test/java/org/postgresql/test/Replication.java deleted file mode 100644 index d50f495..0000000 --- a/src/test/java/org/postgresql/test/Replication.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (c) 2020, PostgreSQL Global Development Group - * See the LICENSE file in the project root for more information. - */ - -package org.postgresql.test; - -/** - * Declares interface to specify tests that use replication feature. - * It enables to include or exclude those tests from the command line. - */ -public interface Replication { -} diff --git a/src/test/java/org/postgresql/test/TestUtil.java b/src/test/java/org/postgresql/test/TestUtil.java index f433b69..c83a52c 100644 --- a/src/test/java/org/postgresql/test/TestUtil.java +++ b/src/test/java/org/postgresql/test/TestUtil.java @@ -5,23 +5,27 @@ package org.postgresql.test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.postgresql.util.internal.Nullness.castNonNull; + import org.postgresql.PGConnection; import org.postgresql.PGProperty; import org.postgresql.core.BaseConnection; import org.postgresql.core.ServerVersion; import org.postgresql.core.TransactionState; import org.postgresql.core.Version; -import org.postgresql.jdbc.GSSEncMode; import org.postgresql.jdbc.PgConnection; +import org.postgresql.jdbc.ResourceLock; import org.postgresql.util.PSQLException; +import org.postgresql.util.URLCoder; +import org.postgresql.util.internal.FileUtils; // import org.checkerframework.checker.nullness.qual.Nullable; -import org.junit.Assert; -import org.junit.Assume; -import java.io.Closeable; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; @@ -34,204 +38,179 @@ import java.util.ArrayList; import java.util.List; import java.util.Properties; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; /** * Utility class for JDBC tests. */ public class TestUtil { - /* - * The case is as follows: - * 1. Typically the database and hostname are taken from System.properties or build.properties or build.local.properties - * That enables to override test DB via system property - * 2. There are tests where different DBs should be used (e.g. SSL tests), so we can't just use DB name from system property - * That is why _test_ properties exist: they overpower System.properties and build.properties + /** + * A constant that defines the prefix used for test URL-related property keys. + * All the properties starting with this prefix would be passed to the JDBC URL. */ - public static final String SERVER_HOST_PORT_PROP = "_test_hostport"; - public static final String DATABASE_PROP = "_test_database"; + public static final String TEST_URL_PROPERTY_PREFIX = "test.url."; - /* - * Returns the Test database JDBC URL - */ - public static String getURL() { - return getURL(getServer(), + getPort()); - } + private static final ResourceLock lock = new ResourceLock(); - public static String getURL(String server, int port) { - return getURL(server + ":" + port, getDatabase()); + static { + initDriver(); } - public static String getURL(String hostport, String database) { - String logLevel = ""; - if (getLogLevel() != null && !getLogLevel().equals("")) { - logLevel = "&loggerLevel=" + getLogLevel(); - } - - String logFile = ""; - if (getLogFile() != null && !getLogFile().equals("")) { - logFile = "&loggerFile=" + getLogFile(); - } - - String protocolVersion = ""; - if (getProtocolVersion() != 0) { - protocolVersion = "&protocolVersion=" + getProtocolVersion(); - } - - String options = ""; - if (getOptions() != null) { - options = "&options=" + getOptions(); - } + private static boolean isTestUrlProperty(String propertyName, PGProperty property) { + return propertyName.startsWith(property.getName(), TEST_URL_PROPERTY_PREFIX.length()); + } - String binaryTransfer = ""; - if (getBinaryTransfer() != null && !getBinaryTransfer().equals("")) { - binaryTransfer = "&binaryTransfer=" + getBinaryTransfer(); - } + /** + * Sets a test URL property in the provided {@link Properties} object. + * The given {@link PGProperty} will be passed with JDBC URL rather than {@link Properties} + * when opening a connection. + * + * @param props the {@link Properties} object where the property will be set + * @param property the {@link PGProperty} object representing the property name + * @param value the value to associate with the property key + */ + public static void setTestUrlProperty(Properties props, PGProperty property, String value) { + props.setProperty(TEST_URL_PROPERTY_PREFIX + property.getName(), value); + } - String receiveBufferSize = ""; - if (getReceiveBufferSize() != -1) { - receiveBufferSize = "&receiveBufferSize=" + getReceiveBufferSize(); - } + /** + * Retrieves the test URL property value based on the specified property name and default value. + * + * @param props the Properties object containing the application configuration + * @param property the PGProperty object representing the specific property to retrieve + * @return the test URL property value if it exists in the Properties object; + * otherwise, the default value of the specified property + */ + public static /* @Nullable */ String getTestUrlProperty(Properties props, PGProperty property) { + return props.getProperty( + TEST_URL_PROPERTY_PREFIX + property.getName(), property.getDefaultValue()); + } - String sendBufferSize = ""; - if (getSendBufferSize() != -1) { - sendBufferSize = "&sendBufferSize=" + getSendBufferSize(); - } + /** + * Returns the Test database JDBC URL + */ + public static String getURL() { + return getURL(System.getProperties()); + } - String ssl = ""; - if (getSSL() != null) { - ssl = "&ssl=" + getSSL(); + /** + * Constructs a JDBC URL using the provided properties. The method builds the + * URL for a PostgreSQL database connection by utilizing specific property values + * for host, port, and database name, and appends additional properties as parameters. + * The URL uses {@link #setTestUrlProperty(Properties, PGProperty, String)} values for + * building the URL. + * + *

          Note: the method uses only exliclitly provided properties, and it does not use + * build.properties and other property sources. + * + * @param props the properties object containing key-value pairs used to construct the URL. + * The values that start with {@link #TEST_URL_PROPERTY_PREFIX} will be included + * into the URL. + * @return the constructed JDBC URL as a String. + */ + public static String getURL(Properties props) { + initDriver(); + String host = getTestUrlProperty(props, PGProperty.PG_HOST); + String port = getTestUrlProperty(props, PGProperty.PG_PORT); + String database = getTestUrlProperty(props, PGProperty.PG_DBNAME); + StringBuilder sb = new StringBuilder("jdbc:postgresql://"); + sb.append(host).append(":").append(port).append("/").append(database); + sb.append("?ApplicationName=Driver Tests"); + for (String propertyName : props.stringPropertyNames()) { + if (!propertyName.startsWith(TEST_URL_PROPERTY_PREFIX)) { + continue; + } + if (isTestUrlProperty(propertyName, PGProperty.PG_HOST) + || isTestUrlProperty(propertyName, PGProperty.PG_PORT) + || isTestUrlProperty(propertyName, PGProperty.PG_DBNAME) + ) { + continue; + } + String value = props.getProperty(propertyName); + if (value == null) { + throw new IllegalArgumentException("Null value for property " + propertyName); + } + String name = propertyName.substring(TEST_URL_PROPERTY_PREFIX.length()); + sb.append("&").append(URLCoder.encode(name)).append("=").append(URLCoder.encode(value)); } - - return "jdbc:postgresql://" - + hostport + "/" - + database - + "?ApplicationName=Driver Tests" - + logLevel - + logFile - + protocolVersion - + options - + binaryTransfer - + receiveBufferSize - + sendBufferSize - + ssl; + return sb.toString(); } /* * Returns the Test server */ public static String getServer() { - return System.getProperty("server", "localhost"); + return castNonNull(getTestUrlProperty(System.getProperties(), PGProperty.PG_HOST)); } /* * Returns the Test port */ public static int getPort() { - return Integer.parseInt(System.getProperty("port", System.getProperty("def_pgport"))); + return Integer.parseInt( + castNonNull(getTestUrlProperty(System.getProperties(), PGProperty.PG_PORT))); } /* * Returns the server side prepared statement threshold. */ - public static int getPrepareThreshold() { - return Integer.parseInt(System.getProperty("preparethreshold", "5")); + public static int getPrepareThreshold() throws PSQLException { + return PGProperty.PREPARE_THRESHOLD.getInt(System.getProperties()); } - public static int getProtocolVersion() { - return Integer.parseInt(System.getProperty("protocolVersion", "0")); - } - - public static String getOptions() { - return System.getProperty("options"); + public static int getProtocolVersion() throws PSQLException { + return PGProperty.PROTOCOL_VERSION.getInt(System.getProperties()); } /* * Returns the Test database */ public static String getDatabase() { - return System.getProperty("database"); + return castNonNull(getTestUrlProperty(System.getProperties(), PGProperty.PG_DBNAME)); } /* * Returns the Postgresql username */ - public static String getUser() { - return System.getProperty("username"); + public static /* @Nullable */ String getUser() { + return PGProperty.USER.getOrDefault(System.getProperties()); } /* * Returns the user's password */ - public static String getPassword() { - return System.getProperty("password"); + public static /* @Nullable */ String getPassword() { + return PGProperty.PASSWORD.getOrDefault(System.getProperties()); } /* * Returns password for default callbackhandler */ - public static String getSslPassword() { + public static /* @Nullable */ String getSslPassword() { return System.getProperty(PGProperty.SSL_PASSWORD.getName()); } - /* - * Return the GSSEncMode for the tests - */ - public static GSSEncMode getGSSEncMode() throws PSQLException { - return GSSEncMode.of(System.getProperties()); - } - /* * Returns the user for SSPI authentication tests */ - public static String getSSPIUser() { + public static /* @Nullable */ String getSSPIUser() { return System.getProperty("sspiusername"); } /* * postgres like user */ - public static String getPrivilegedUser() { + public static /* @Nullable */ String getPrivilegedUser() { return System.getProperty("privilegedUser"); } - public static String getPrivilegedPassword() { + public static /* @Nullable */ String getPrivilegedPassword() { return System.getProperty("privilegedPassword"); } - /* - * Returns the log level to use - */ - public static String getLogLevel() { - return System.getProperty("loggerLevel"); - } - - /* - * Returns the log file to use - */ - public static String getLogFile() { - return System.getProperty("loggerFile"); - } - - /* - * Returns the binary transfer mode to use - */ - public static String getBinaryTransfer() { - return System.getProperty("binaryTransfer"); - } - - public static int getSendBufferSize() { - return Integer.parseInt(System.getProperty("sendBufferSize", "-1")); - } - - public static int getReceiveBufferSize() { - return Integer.parseInt(System.getProperty("receiveBufferSize", "-1")); - } - - public static String getSSL() { - return System.getProperty("ssl"); - } - static { try { initDriver(); @@ -242,7 +221,7 @@ public static String getSSL() { } } - private static boolean initialized = false; + private static boolean initialized; public static Properties loadPropertyFiles(String... names) { Properties p = new Properties(); @@ -259,7 +238,7 @@ public static Properties loadPropertyFiles(String... names) { continue; } try { - p.load(new FileInputStream(f)); + p.load(FileUtils.newBufferedInputStream(f)); } catch (IOException ex) { // ignore } @@ -268,30 +247,36 @@ public static Properties loadPropertyFiles(String... names) { return p; } - private static Properties sslTestProperties = null; + private static /* @Nullable */ Properties sslTestProperties; - private static synchronized void initSslTestProperties() { - if (sslTestProperties == null) { - sslTestProperties = TestUtil.loadPropertyFiles("ssltest.properties"); + private static void initSslTestProperties() { + try (ResourceLock ignore = lock.obtain()) { + if (sslTestProperties == null) { + sslTestProperties = TestUtil.loadPropertyFiles("ssltest.properties"); + } } } - private static String getSslTestProperty(String name) { + private static /* @Nullable */ String getSslTestProperty(String name) { initSslTestProperties(); - return sslTestProperties.getProperty(name); + return castNonNull(sslTestProperties).getProperty(name); } public static void assumeSslTestsEnabled() { - Assume.assumeTrue(Boolean.parseBoolean(getSslTestProperty("enable_ssl_tests"))); + assumeTrue(Boolean.parseBoolean(getSslTestProperty("enable_ssl_tests"))); } public static String getSslTestCertPath(String name) { - File certdir = TestUtil.getFile(getSslTestProperty("certdir")); + String certdirProp = getSslTestProperty("certdir"); + if (certdirProp == null) { + throw new IllegalArgumentException("Missing property certdir in ssltest.properties"); + } + File certdir = TestUtil.getFile(certdirProp); return new File(certdir, name).getAbsolutePath(); } public static void initDriver() { - synchronized (TestUtil.class) { + try (ResourceLock ignore = lock.obtain()) { if (initialized) { return; } @@ -330,15 +315,16 @@ public static File getFile(String name) { * functions now as of 4/14 */ public static Connection openPrivilegedDB() throws SQLException { - initDriver(); - Properties properties = new Properties(); - - PGProperty.GSS_ENC_MODE.set(properties,getGSSEncMode().value); - properties.setProperty("user", getPrivilegedUser()); - properties.setProperty("password", getPrivilegedPassword()); - properties.setProperty("options", "-c synchronous_commit=on"); - return DriverManager.getConnection(getURL(), properties); + return openPrivilegedDB(x -> { }); + } + public static Connection openPrivilegedDB(Consumer props) throws SQLException { + Properties properties = new Properties(); + PGProperty.USER.set(properties, getPrivilegedUser()); + PGProperty.PASSWORD.set(properties, getPrivilegedPassword()); + PGProperty.OPTIONS.set(properties, "-c synchronous_commit=on"); + props.accept(properties); + return openDB(properties); } public static Connection openReplicationConnection() throws Exception { @@ -348,9 +334,9 @@ public static Connection openReplicationConnection() throws Exception { PGProperty.REPLICATION.set(properties, "database"); //Only simple query protocol available for replication connection PGProperty.PREFER_QUERY_MODE.set(properties, "simple"); - properties.setProperty("username", TestUtil.getPrivilegedUser()); - properties.setProperty("password", TestUtil.getPrivilegedPassword()); - properties.setProperty("options", "-c synchronous_commit=on"); + PGProperty.USER.set(properties, TestUtil.getPrivilegedUser()); + PGProperty.PASSWORD.set(properties, TestUtil.getPrivilegedPassword()); + PGProperty.OPTIONS.set(properties, "-c synchronous_commit=on"); return TestUtil.openDB(properties); } @@ -363,55 +349,52 @@ public static Connection openDB() throws SQLException { return openDB(new Properties()); } - /* + /** * Helper - opens a connection with the allowance for passing additional parameters, like * "compatible". */ public static Connection openDB(Properties props) throws SQLException { - initDriver(); - - // Allow properties to override the user name. - String user = props.getProperty("username"); - if (user == null) { - user = getUser(); - } - if (user == null) { - throw new IllegalArgumentException( - "user name is not specified. Please specify 'username' property via -D or build.properties"); - } - props.setProperty("user", user); - - // Allow properties to override the password. - String password = props.getProperty("password"); - if (password == null) { - password = getPassword() != null ? getPassword() : ""; - } - props.setProperty("password", password); - - String sslPassword = getSslPassword(); - if (sslPassword != null) { - PGProperty.SSL_PASSWORD.set(props, sslPassword); - } + Properties propsWithDefaults = mergeDefaultProperties(props); + return DriverManager.getConnection(getURL(propsWithDefaults), propsWithDefaults); + } - if (!props.containsKey(PGProperty.PREPARE_THRESHOLD.getName())) { - PGProperty.PREPARE_THRESHOLD.set(props, getPrepareThreshold()); - } - if (!props.containsKey(PGProperty.PREFER_QUERY_MODE.getName())) { - String value = System.getProperty(PGProperty.PREFER_QUERY_MODE.getName()); - if (value != null) { - props.put(PGProperty.PREFER_QUERY_MODE.getName(), value); + /** + * Merges the default properties with the provided properties. The method prioritizes + * properties in the following order: + * 1. Properties passed within the method argument. + * 2. System properties. + * 3. Properties passed with {@code build.properties} file. + * + * @param props the properties object to merge with the default properties. Can be empty. + * @return a new Properties object containing the merged properties with precedence + * applied as described. + */ + public static Properties mergeDefaultProperties(Properties props) { + // Properties priority: + // 1. The ones that are passed within tests + // 2. System.properties + // 3. build.properties + + Properties propsWithDefaults; + // System.properties should override the given props + // The trivial case is when the input props argument is empty (including props.defaults) + Set propertyNames = props.stringPropertyNames(); + Properties systemProperties = System.getProperties(); + if (propertyNames.isEmpty()) { + // When argument props is empty, default to System.properties + propsWithDefaults = systemProperties; + } else { + // Copy all the properties from the given set and make system ones as a fallback + propsWithDefaults = new Properties(systemProperties); + for (String propertyName : propertyNames) { + String value = props.getProperty(propertyName); + if (value == null) { + throw new IllegalArgumentException("Property " + propertyName + " is null"); + } + propsWithDefaults.setProperty(propertyName, value); } } - // Enable Base4 tests to override host,port,database - String hostport = props.getProperty(SERVER_HOST_PORT_PROP, getServer() + ":" + getPort()); - String database = props.getProperty(DATABASE_PROP, getDatabase()); - - // Set GSSEncMode for tests only in the case the property is already missing - if (PGProperty.GSS_ENC_MODE.getSetString(props) == null) { - PGProperty.GSS_ENC_MODE.set(props, getGSSEncMode().value); - } - - return DriverManager.getConnection(getURL(hostport, database), props); + return propsWithDefaults; } /* @@ -512,16 +495,28 @@ public static void createUnloggedTable(Connection con, String table, String colu */ public static void createView(Connection con, String viewName, String query) throws SQLException { - Statement st = con.createStatement(); - try { + try ( Statement st = con.createStatement() ) { // Drop the view dropView(con, viewName); String sql = "CREATE VIEW " + viewName + " AS " + query; st.executeUpdate(sql); - } finally { - closeQuietly(st); + } + } + + /* + * Helper - creates a materialized view + */ + public static void createMaterializedView(Connection con, String matViewName, String query) + throws SQLException { + try ( Statement st = con.createStatement() ) { + // Drop the view + dropMaterializedView(con, matViewName); + + String sql = "CREATE MATERIALIZED VIEW " + matViewName + " AS " + query; + + st.executeUpdate(sql); } } @@ -628,6 +623,13 @@ public static void dropView(Connection con, String view) throws SQLException { dropObject(con, "VIEW", view); } + /* + * Helper - drops a materialized view + */ + public static void dropMaterializedView(Connection con, String matView) throws SQLException { + dropObject(con, "MATERIALIZED VIEW", matView); + } + /* * Helper - drops a type */ @@ -635,6 +637,13 @@ public static void dropType(Connection con, String type) throws SQLException { dropObject(con, "TYPE", type); } + /* + * Drops a function with a given signature. + */ + public static void dropFunction(Connection con, String name, String arguments) throws SQLException { + dropObject(con, "FUNCTION", name + "(" + arguments + ")"); + } + private static void dropObject(Connection con, String type, String name) throws SQLException { Statement stmt = con.createStatement(); try { @@ -658,7 +667,7 @@ public static void assertNumberOfRows(Connection con, String tableName, int expe ps = con.prepareStatement("select count(*) from " + tableName + " as t"); rs = ps.executeQuery(); rs.next(); - Assert.assertEquals(message, expectedRows, rs.getInt(1)); + assertEquals(expectedRows, rs.getInt(1), message); } finally { closeQuietly(rs); closeQuietly(ps); @@ -667,7 +676,7 @@ public static void assertNumberOfRows(Connection con, String tableName, int expe public static void assertTransactionState(String message, Connection con, TransactionState expected) { TransactionState actual = TestUtil.getTransactionState(con); - Assert.assertEquals(message, expected, actual); + assertEquals(expected, actual, message); } /* @@ -677,7 +686,7 @@ public static String insertSQL(String table, String values) { return insertSQL(table, null, values); } - public static String insertSQL(String table, String columns, String values) { + public static String insertSQL(String table, /* @Nullable */ String columns, String values) { String s = "INSERT INTO " + table; if (columns != null) { @@ -694,11 +703,11 @@ public static String selectSQL(String table, String columns) { return selectSQL(table, columns, null, null); } - public static String selectSQL(String table, String columns, String where) { + public static String selectSQL(String table, String columns, /* @Nullable */ String where) { return selectSQL(table, columns, where, null); } - public static String selectSQL(String table, String columns, String where, String other) { + public static String selectSQL(String table, String columns, /* @Nullable */ String where, /* @Nullable */ String other) { String s = "SELECT " + columns + " FROM " + table; if (where != null) { @@ -711,7 +720,7 @@ public static String selectSQL(String table, String columns, String where, Strin return s; } - /* + /** * Helper to prefix a number with leading zeros - ugly but it works... * * @param v value to prefix @@ -769,9 +778,16 @@ public static boolean haveMinimumServerVersion(Connection con, Version version) return false; } + public static void assumeHaveMinimumServerVersion(Version version) + throws SQLException { + try (Connection conn = openPrivilegedDB()) { + assumeTrue(TestUtil.haveMinimumServerVersion(conn, version)); + } + } + public static boolean haveMinimumJVMVersion(String version) { String jvm = java.lang.System.getProperty("java.version"); - return (jvm.compareTo(version) >= 0); + return jvm.compareTo(version) >= 0; } public static boolean haveIntegerDateTimes(Connection con) { @@ -808,7 +824,7 @@ public static void printResultSet(ResultSet rs) throws SQLException { } public static List resultSetToLines(ResultSet rs) throws SQLException { - List res = new ArrayList(); + List res = new ArrayList<>(); ResultSetMetaData rsmd = rs.getMetaData(); StringBuilder sb = new StringBuilder(); while (rs.next()) { @@ -841,38 +857,15 @@ public static String join(List list) { * the caller to detect if the column lookup was successful. */ public static int findColumn(PreparedStatement query, String label) throws SQLException { - int returnValue = 0; - ResultSet rs = query.executeQuery(); - if (rs.next()) { - try { - returnValue = rs.findColumn(label); - } catch (SQLException sqle) { - } // consume exception to allow cleanup of resource. - } - rs.close(); - return returnValue; - } - - /** - * Close a resource and ignore any errors during closing. - */ - public static void closeQuietly(/* @Nullable */ Closeable resource) { - if (resource != null) { - try { - resource.close(); - } catch (Exception ignore) { + try (ResultSet rs = query.executeQuery()) { + if (!rs.next()) { + return 0; } - } - } - - /** - * Close a Connection and ignore any errors during closing. - */ - public static void closeQuietly(/* @Nullable */ Connection conn) { - if (conn != null) { try { - conn.close(); - } catch (SQLException ignore) { + return rs.findColumn(label); + } catch (SQLException e) { + // Column was not found, return 0 + return 0; } } } @@ -885,6 +878,7 @@ public static void closeQuietly(/* @Nullable */ Statement stmt) { try { stmt.close(); } catch (SQLException ignore) { + // ignore } } } @@ -897,6 +891,7 @@ public static void closeQuietly(/* @Nullable */ ResultSet rs) { try { rs.close(); } catch (SQLException ignore) { + // ignore } } } @@ -995,12 +990,27 @@ public static boolean executeQuery(Connection conn, String sql) throws SQLExcept * Execute a SQL query with a given connection, fetch the first row, and return its * string value. */ - public static String queryForString(Connection conn, String sql) throws SQLException { + public static /* @Nullable */ String queryForString(Connection conn, String sql) throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); - Assert.assertTrue("Query should have returned exactly one row but none was found: " + sql, rs.next()); + assertTrue(rs.next(), () -> "Query should have returned exactly one row but none was found: " + sql); + String value = rs.getString(1); + assertFalse(rs.next(), () -> "Query should have returned exactly one row but more than one found: " + sql); + rs.close(); + stmt.close(); + return value; + } + + /** + * Same as queryForString(...) above but with a single string param. + */ + public static /* @Nullable */ String queryForString(Connection conn, String sql, String param) throws SQLException { + PreparedStatement stmt = conn.prepareStatement(sql); + stmt.setString(1, param); + ResultSet rs = stmt.executeQuery(); + assertTrue(rs.next(), () -> "Query should have returned exactly one row but none was found: " + sql); String value = rs.getString(1); - Assert.assertFalse("Query should have returned exactly one row but more than one found: " + sql, rs.next()); + assertFalse(rs.next(), () -> "Query should have returned exactly one row but more than one found: " + sql); rs.close(); stmt.close(); return value; @@ -1010,15 +1020,15 @@ public static String queryForString(Connection conn, String sql) throws SQLExcep * Execute a SQL query with a given connection, fetch the first row, and return its * boolean value. */ - public static Boolean queryForBoolean(Connection conn, String sql) throws SQLException { + public static /* @Nullable */ Boolean queryForBoolean(Connection conn, String sql) throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql); - Assert.assertTrue("Query should have returned exactly one row but none was found: " + sql, rs.next()); + assertTrue(rs.next(), () -> "Query should have returned exactly one row but none was found: " + sql); Boolean value = rs.getBoolean(1); if (rs.wasNull()) { value = null; } - Assert.assertFalse("Query should have returned exactly one row but more than one found: " + sql, rs.next()); + assertFalse(rs.next(), () -> "Query should have returned exactly one row but more than one found: " + sql); rs.close(); stmt.close(); return value; @@ -1068,9 +1078,12 @@ public static boolean waitForBackendTermination(Connection conn, int pid, Durati */ private static Connection createPrivilegedConnection(Connection conn) throws SQLException { String url = conn.getMetaData().getURL(); + if (url == null) { + throw new IllegalStateException("conn.getMetaData().getURL() returned null, so can't reconstruct the URL"); + } Properties props = new Properties(conn.getClientInfo()); - props.setProperty("user", getPrivilegedUser()); - props.setProperty("password", getPrivilegedPassword()); + PGProperty.USER.set(props, getPrivilegedUser()); + PGProperty.PASSWORD.set(props, getPrivilegedPassword()); return DriverManager.getConnection(url, props); } @@ -1137,7 +1150,10 @@ private static void waitStopReplicationSlot(Connection connection, String slotNa } } - public static void execute(String sql, Connection connection) throws SQLException { + /** + * Executes given SQL via {@link Statement#execute(String)} on a given connection. + */ + public static void execute(Connection connection, String sql) throws SQLException { try (Statement stmt = connection.createStatement()) { stmt.execute(sql); } diff --git a/src/test/java/org/postgresql/test/annotations/DisableLogger.java b/src/test/java/org/postgresql/test/annotations/DisableLogger.java new file mode 100644 index 0000000..cbf68f6 --- /dev/null +++ b/src/test/java/org/postgresql/test/annotations/DisableLogger.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2026, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.annotations; + +import org.postgresql.test.impl.DisableLoggerExtension; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation used to disable specific loggers during the execution of tests. + * This annotation can suppress log output for loggers associated with specified + * classes or categories, making it useful for cleaner and more focused test outputs. + * + *

          This annotation must be used with the {@code DisableLoggerExtension} for it to take effect. + * The extension intercepts the test lifecycle to temporarily modify logging levels for the + * specified loggers and restores them once the test execution is complete. + * + *

          The annotation supports: + *

            + *
          • Specifying classes whose associated loggers are to be disabled using the {@code value} attribute.
          • + *
          • Specifying logger categories by name using the {@code categories} attribute.
          • + *
          + * + *

          Attributes: + *

            + *
          • {@code value(): Class[]}: Specifies the classes whose associated loggers should be disabled.
          • + *
          • {@code categories(): String[]}: Defines the logger categories (by string names) to disable.
          • + *
          + */ +@Repeatable(DisableLogger.List.class) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +@ExtendWith(DisableLoggerExtension.class) +public @interface DisableLogger { + /** + * Specifies the classes whose associated loggers should be disabled during the execution of a test or test class. + * By default, no classes are specified, meaning no loggers will be disabled unless explicitly defined. + * + * @return an array of classes whose associated loggers will be disabled + */ + Class[] value() default {}; + + /** + * Specifies the logger categories (by name) that should have their loggers disabled + * during the execution of a test or test class. This attribute is useful for disabling + * loggers by specific category names rather than by class association. + * + * @return an array of logger category names to disable + */ + String[] categories() default {}; + + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.METHOD, ElementType.TYPE}) + @interface List { + DisableLogger[] value(); + } +} diff --git a/src/test/java/org/postgresql/test/annotations/DisabledForServerVersionRange.java b/src/test/java/org/postgresql/test/annotations/DisabledForServerVersionRange.java new file mode 100644 index 0000000..1207fc3 --- /dev/null +++ b/src/test/java/org/postgresql/test/annotations/DisabledForServerVersionRange.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2026, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.annotations; + +import org.postgresql.test.impl.DisabledForServerVersionRangeCondition; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Disables test if the current server version matches the specified range + * @see org.junit.jupiter.api.Disabled + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(DisabledForServerVersionRangeCondition.class) +public @interface DisabledForServerVersionRange { + /** + * Less than + */ + String lt() default ""; + + /** + * Less than or equal + */ + String lte() default ""; + + /** + * Greater than or equal + */ + String gte() default ""; + + /** + * Greater than + */ + String gt() default ""; +} diff --git a/src/test/java/org/postgresql/test/annotations/EnabledForServerVersionRange.java b/src/test/java/org/postgresql/test/annotations/EnabledForServerVersionRange.java new file mode 100644 index 0000000..f1afc10 --- /dev/null +++ b/src/test/java/org/postgresql/test/annotations/EnabledForServerVersionRange.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2026, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.annotations; + +import org.postgresql.test.impl.EnabledForServerVersionRangeCondition; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only enables a test if the current server version matches the specified range + * @see org.junit.jupiter.api.Disabled + */ +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(EnabledForServerVersionRangeCondition.class) +public @interface EnabledForServerVersionRange { + /** + * Less than + */ + String lt() default ""; + + /** + * Less than or equal + */ + String lte() default ""; + + /** + * Greater than or equal + */ + String gte() default ""; + + /** + * Greater than + */ + String gt() default ""; +} diff --git a/src/test/java/org/postgresql/test/annotations/tags/Arrays.java b/src/test/java/org/postgresql/test/annotations/tags/Arrays.java new file mode 100644 index 0000000..be94208 --- /dev/null +++ b/src/test/java/org/postgresql/test/annotations/tags/Arrays.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.annotations.tags; + +import org.junit.jupiter.api.Tag; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Tag("arrays") +public @interface Arrays { +} diff --git a/src/test/java/org/postgresql/test/annotations/tags/Replication.java b/src/test/java/org/postgresql/test/annotations/tags/Replication.java new file mode 100644 index 0000000..849cb6b --- /dev/null +++ b/src/test/java/org/postgresql/test/annotations/tags/Replication.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.annotations.tags; + +import org.junit.jupiter.api.Tag; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Tag("replication") +public @interface Replication { +} diff --git a/src/test/java/org/postgresql/test/annotations/tags/Slow.java b/src/test/java/org/postgresql/test/annotations/tags/Slow.java new file mode 100644 index 0000000..8ef0b2e --- /dev/null +++ b/src/test/java/org/postgresql/test/annotations/tags/Slow.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.annotations.tags; + +import org.junit.jupiter.api.Tag; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Tag("slow") +public @interface Slow { +} diff --git a/src/test/java/org/postgresql/test/annotations/tags/Xa.java b/src/test/java/org/postgresql/test/annotations/tags/Xa.java new file mode 100644 index 0000000..7ef7c85 --- /dev/null +++ b/src/test/java/org/postgresql/test/annotations/tags/Xa.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.annotations.tags; + +import org.junit.jupiter.api.Tag; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Tag("xa") +public @interface Xa { +} diff --git a/src/test/java/org/postgresql/test/core/FixedLengthOutputStreamTest.java b/src/test/java/org/postgresql/test/core/FixedLengthOutputStreamTest.java index 6058795..07d9bd5 100644 --- a/src/test/java/org/postgresql/test/core/FixedLengthOutputStreamTest.java +++ b/src/test/java/org/postgresql/test/core/FixedLengthOutputStreamTest.java @@ -5,77 +5,61 @@ package org.postgresql.test.core; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.core.FixedLengthOutputStream; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.sql.SQLException; -public class FixedLengthOutputStreamTest { +class FixedLengthOutputStreamTest { - private ByteArrayOutputStream targetStream; - private FixedLengthOutputStream fixedLengthStream; - - @Before - public void setUp() throws Exception { - targetStream = new ByteArrayOutputStream(); - fixedLengthStream = new FixedLengthOutputStream(10, targetStream); - } - - @After - public void tearDown() throws SQLException { - } + private ByteArrayOutputStream targetStream = new ByteArrayOutputStream(); + private FixedLengthOutputStream fixedLengthStream = new FixedLengthOutputStream(10, targetStream); private void verifyExpectedOutput(byte[] expected) { - assertArrayEquals("Incorrect data written to target stream", - expected, targetStream.toByteArray()); + assertArrayEquals(expected, targetStream.toByteArray(), "Incorrect data written to target stream"); } @Test - public void testSingleByteWrites() throws IOException { + void singleByteWrites() throws IOException { fixedLengthStream.write((byte) 1); - assertEquals("Incorrect remaining value", 9, fixedLengthStream.remaining()); + assertEquals(9, fixedLengthStream.remaining(), "Incorrect remaining value"); fixedLengthStream.write((byte) 2); - assertEquals("Incorrect remaining value", 8, fixedLengthStream.remaining()); + assertEquals(8, fixedLengthStream.remaining(), "Incorrect remaining value"); verifyExpectedOutput(new byte[]{1, 2}); } @Test - public void testMultipleByteWrites() throws IOException { + void multipleByteWrites() throws IOException { fixedLengthStream.write(new byte[]{1, 2, 3, 4}); - assertEquals("Incorrect remaining value", 6, fixedLengthStream.remaining()); + assertEquals(6, fixedLengthStream.remaining(), "Incorrect remaining value"); fixedLengthStream.write(new byte[]{5, 6, 7, 8}); - assertEquals("Incorrect remaining value", 2, fixedLengthStream.remaining()); + assertEquals(2, fixedLengthStream.remaining(), "Incorrect remaining value"); verifyExpectedOutput(new byte[]{1, 2, 3, 4, 5, 6, 7, 8}); } @Test - public void testSingleByteOverLimit() throws IOException { + void singleByteOverLimit() throws IOException { byte[] data = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; fixedLengthStream.write(data); - assertEquals("Incorrect remaining value", 0, fixedLengthStream.remaining()); + assertEquals(0, fixedLengthStream.remaining(), "Incorrect remaining value"); try { fixedLengthStream.write((byte) 'a'); fail("Expected exception not thrown"); } catch (IOException e) { - assertEquals("Incorrect exception message", - "Attempt to write more than the specified 10 bytes", e.getMessage()); + assertEquals("Attempt to write more than the specified 10 bytes", e.getMessage(), "Incorrect exception message"); } - assertEquals("Incorrect remaining value after exception", - 0, fixedLengthStream.remaining()); + assertEquals(0, fixedLengthStream.remaining(), "Incorrect remaining value after exception"); verifyExpectedOutput(data); } @Test - public void testMultipleBytesOverLimit() throws IOException { + void multipleBytesOverLimit() throws IOException { byte[] data = new byte[]{1, 2, 3, 4, 5, 6, 7, 8}; fixedLengthStream.write(data); assertEquals(2, fixedLengthStream.remaining()); @@ -83,11 +67,9 @@ public void testMultipleBytesOverLimit() throws IOException { fixedLengthStream.write(new byte[]{'a', 'b', 'c', 'd'}); fail("Expected exception not thrown"); } catch (IOException e) { - assertEquals("Incorrect exception message", - "Attempt to write more than the specified 10 bytes", e.getMessage()); + assertEquals("Attempt to write more than the specified 10 bytes", e.getMessage(), "Incorrect exception message"); } - assertEquals("Incorrect remaining value after exception", - 2, fixedLengthStream.remaining()); + assertEquals(2, fixedLengthStream.remaining(), "Incorrect remaining value after exception"); verifyExpectedOutput(data); } } diff --git a/src/test/java/org/postgresql/test/core/JavaVersionTest.java b/src/test/java/org/postgresql/test/core/JavaVersionTest.java index ebac017..12c8db2 100644 --- a/src/test/java/org/postgresql/test/core/JavaVersionTest.java +++ b/src/test/java/org/postgresql/test/core/JavaVersionTest.java @@ -5,20 +5,20 @@ package org.postgresql.test.core; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.core.JavaVersion; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class JavaVersionTest { +class JavaVersionTest { @Test - public void testGetRuntimeVersion() { + void getRuntimeVersion() { String currentVersion = System.getProperty("java.version"); String msg = "java.version = " + currentVersion + ", JavaVersion.getRuntimeVersion() = " + JavaVersion.getRuntimeVersion(); - System.out.println(msg); if (currentVersion.startsWith("1.8")) { - Assert.assertEquals(msg, JavaVersion.v1_8, JavaVersion.getRuntimeVersion()); + assertEquals(JavaVersion.v1_8, JavaVersion.getRuntimeVersion(), msg); } } } diff --git a/src/test/java/org/postgresql/test/core/LogServerMessagePropertyTest.java b/src/test/java/org/postgresql/test/core/LogServerMessagePropertyTest.java index badf5a4..c46d45d 100644 --- a/src/test/java/org/postgresql/test/core/LogServerMessagePropertyTest.java +++ b/src/test/java/org/postgresql/test/core/LogServerMessagePropertyTest.java @@ -5,21 +5,24 @@ package org.postgresql.test.core; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + import org.postgresql.PGProperty; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; import org.postgresql.util.PSQLState; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Locale; import java.util.Properties; -public class LogServerMessagePropertyTest { +class LogServerMessagePropertyTest { private static final String PRIMARY_KEY_NAME = "lms_test_pk"; private static final String CREATE_TABLE_SQL = "CREATE TABLE pg_temp.lms_test (" @@ -37,9 +40,9 @@ public class LogServerMessagePropertyTest { */ private static String testViolatePrimaryKey(Properties props, boolean batch) throws SQLException { Connection conn = TestUtil.openDB(props); - Assume.assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v9_1)); + assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v9_1)); try { - TestUtil.execute(CREATE_TABLE_SQL, conn); + TestUtil.execute(conn, CREATE_TABLE_SQL); if (batch) { PreparedStatement stmt = conn.prepareStatement(INSERT_SQL); stmt.addBatch(); @@ -47,18 +50,18 @@ private static String testViolatePrimaryKey(Properties props, boolean batch) thr stmt.executeBatch(); } else { // First insert should work - TestUtil.execute(INSERT_SQL, conn); + TestUtil.execute(conn, INSERT_SQL); // Second insert should throw a duplicate key error - TestUtil.execute(INSERT_SQL, conn); + TestUtil.execute(conn, INSERT_SQL); } } catch (SQLException e) { - Assert.assertEquals("SQL state must be for a unique violation", PSQLState.UNIQUE_VIOLATION.getState(), e.getSQLState()); + assertEquals(PSQLState.UNIQUE_VIOLATION.getState(), e.getSQLState(), "SQL state must be for a unique violation"); return e.getMessage(); } finally { conn.close(); } // Should never get here: - Assert.fail("A duplicate key exception should have occurred"); + fail("A duplicate key exception should have occurred"); return null; } @@ -67,23 +70,23 @@ private static String testViolatePrimaryKey(Properties props) throws SQLExceptio } private static void assertMessageContains(String message, String text) { - if (message.toLowerCase().indexOf(text.toLowerCase()) < 0) { - Assert.fail(String.format("Message must contain text '%s': %s", text, message)); + if (!message.toLowerCase(Locale.ROOT).contains(text.toLowerCase(Locale.ROOT))) { + fail(String.format("Message must contain text '%s': %s", text, message)); } } private static void assertMessageDoesNotContain(String message, String text) { - if (message.toLowerCase().indexOf(text.toLowerCase()) >= 0) { - Assert.fail(String.format("Message must not contain text '%s': %s", text, message)); + if (message.toLowerCase(Locale.ROOT).contains(text.toLowerCase(Locale.ROOT))) { + fail(String.format("Message must not contain text '%s': %s", text, message)); } } @Test - public void testWithDefaults() throws SQLException { + void withDefaults() throws SQLException { Properties props = new Properties(); String message = testViolatePrimaryKey(props); assertMessageContains(message, PRIMARY_KEY_NAME); - assertMessageContains(message, "Detail:"); + // TODO: Detail is locale-specific assertMessageContains(message, "Detail:"); assertMessageContains(message, SECRET_VALUE); } @@ -91,17 +94,17 @@ public void testWithDefaults() throws SQLException { * NOTE: This should be the same as the default case as "true" is the default. */ @Test - public void testWithExplicitlyEnabled() throws SQLException { + void withExplicitlyEnabled() throws SQLException { Properties props = new Properties(); props.setProperty(PGProperty.LOG_SERVER_ERROR_DETAIL.getName(), "true"); String message = testViolatePrimaryKey(props); assertMessageContains(message, PRIMARY_KEY_NAME); - assertMessageContains(message, "Detail:"); + // TODO: Detail is locale-specific assertMessageContains(message, "Detail:"); assertMessageContains(message, SECRET_VALUE); } @Test - public void testWithLogServerErrorDetailDisabled() throws SQLException { + void withLogServerErrorDetailDisabled() throws SQLException { Properties props = new Properties(); props.setProperty(PGProperty.LOG_SERVER_ERROR_DETAIL.getName(), "false"); String message = testViolatePrimaryKey(props); @@ -111,11 +114,11 @@ public void testWithLogServerErrorDetailDisabled() throws SQLException { } @Test - public void testBatchWithDefaults() throws SQLException { + void batchWithDefaults() throws SQLException { Properties props = new Properties(); String message = testViolatePrimaryKey(props, true); assertMessageContains(message, PRIMARY_KEY_NAME); - assertMessageContains(message, "Detail:"); + // TODO: Detail is locale-specific assertMessageContains(message, "Detail:"); assertMessageContains(message, SECRET_VALUE); } @@ -123,22 +126,22 @@ public void testBatchWithDefaults() throws SQLException { * NOTE: This should be the same as the default case as "true" is the default. */ @Test - public void testBatchExplicitlyEnabled() throws SQLException { + void batchExplicitlyEnabled() throws SQLException { Properties props = new Properties(); props.setProperty(PGProperty.LOG_SERVER_ERROR_DETAIL.getName(), "true"); String message = testViolatePrimaryKey(props, true); assertMessageContains(message, PRIMARY_KEY_NAME); - assertMessageContains(message, "Detail:"); + // TODO: Detail is locale-specific assertMessageContains(message, "Detail:"); assertMessageContains(message, SECRET_VALUE); } @Test - public void testBatchWithLogServerErrorDetailDisabled() throws SQLException { + void batchWithLogServerErrorDetailDisabled() throws SQLException { Properties props = new Properties(); props.setProperty(PGProperty.LOG_SERVER_ERROR_DETAIL.getName(), "false"); String message = testViolatePrimaryKey(props, true); assertMessageContains(message, PRIMARY_KEY_NAME); - assertMessageDoesNotContain(message, "Detail:"); + // TODO: Detail is locale-specific assertMessageDoesNotContain(message, "Detail:"); assertMessageDoesNotContain(message, SECRET_VALUE); } } diff --git a/src/test/java/org/postgresql/test/core/NativeQueryBindLengthTest.java b/src/test/java/org/postgresql/test/core/NativeQueryBindLengthTest.java index 03a63f5..7448ae1 100644 --- a/src/test/java/org/postgresql/test/core/NativeQueryBindLengthTest.java +++ b/src/test/java/org/postgresql/test/core/NativeQueryBindLengthTest.java @@ -5,35 +5,25 @@ package org.postgresql.test.core; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.core.NativeQuery; -import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.ArrayList; import java.util.List; -@RunWith(Parameterized.class) -public class NativeQueryBindLengthTest extends BaseTest4 { - private final int expected; - private final int bindCount; - - public NativeQueryBindLengthTest(String name, int expected, int bindCount) { - this.expected = expected; - this.bindCount = bindCount; - } - - @Test - public void testBindLengthCalculation() { - Assert.assertEquals(expected, NativeQuery.calculateBindLength(bindCount)); +public class NativeQueryBindLengthTest { + @ParameterizedTest(name = "{0} == {1}") + @MethodSource("data") + public void testBindLengthCalculation(String name, int expected, int bindCount) { + assertEquals(expected, NativeQuery.calculateBindLength(bindCount)); } - @Parameterized.Parameters(name = "{0} == {1}") public static Iterable data() { - List res = new ArrayList(); + List res = new ArrayList<>(); res.add(new Object[]{"'$1'.length = 2", 2, 1}); res.add(new Object[]{"'$1$2...$9'.length = 2*9", 18, 9}); res.add(new Object[]{"'$1$2...$9$10'.length = 2*9+3", 21, 10}); diff --git a/src/test/java/org/postgresql/test/core/OptionsPropertyTest.java b/src/test/java/org/postgresql/test/core/OptionsPropertyTest.java index 20a7830..15d655c 100644 --- a/src/test/java/org/postgresql/test/core/OptionsPropertyTest.java +++ b/src/test/java/org/postgresql/test/core/OptionsPropertyTest.java @@ -5,25 +5,27 @@ package org.postgresql.test.core; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; -public class OptionsPropertyTest { +class OptionsPropertyTest { private static final String schemaName = "options_property_test"; private static final String optionsValue = "-c search_path=" + schemaName; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { Connection con = TestUtil.openDB(); Statement stmt = con.createStatement(); stmt.execute("DROP SCHEMA IF EXISTS " + schemaName + ";"); @@ -33,7 +35,7 @@ public void setUp() throws Exception { } @Test - public void testOptionsInProperties() throws Exception { + void optionsInProperties() throws Exception { Properties props = new Properties(); props.setProperty(PGProperty.OPTIONS.getName(), optionsValue); @@ -43,16 +45,16 @@ public void testOptionsInProperties() throws Exception { ResultSet rs = stmt.getResultSet(); if (!rs.next()) { - Assert.fail("'options' connection initialization parameter should be passed to the database."); + fail("'options' connection initialization parameter should be passed to the database."); } - Assert.assertEquals("'options' connection initialization parameter should be passed to the database.", schemaName, rs.getString(1)); + assertEquals(schemaName, rs.getString(1), "'options' connection initialization parameter should be passed to the database."); stmt.close(); TestUtil.closeDB(con); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { Connection con = TestUtil.openDB(); Statement stmt = con.createStatement(); stmt.execute("DROP SCHEMA " + schemaName + ";"); diff --git a/src/test/java/org/postgresql/test/core/QueryExecutorTest.java b/src/test/java/org/postgresql/test/core/QueryExecutorTest.java new file mode 100644 index 0000000..1211e82 --- /dev/null +++ b/src/test/java/org/postgresql/test/core/QueryExecutorTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.core; + +import org.postgresql.core.BaseConnection; +import org.postgresql.core.QueryExecutor; +import org.postgresql.test.jdbc2.BaseTest4; + +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; +import java.util.Set; + +/** + * TestCase to test handling of binary types. + */ +public class QueryExecutorTest extends BaseTest4 { + /** + * Make sure the functions for adding binary transfer OIDs for custom types are correct. + * + * @throws SQLException if a database error occurs + */ + @Test + public void testBinaryTransferOids() throws SQLException { + QueryExecutor queryExecutor = con.unwrap(BaseConnection.class).getQueryExecutor(); + // get current OIDs (make a copy of them) + @SuppressWarnings("deprecation") + Set oidsReceive = queryExecutor.getBinaryReceiveOids(); + @SuppressWarnings("deprecation") + Set oidsSend = queryExecutor.getBinarySendOids(); + // add a new OID to be transferred as binary data + int customTypeOid = 91716; + assertBinaryForReceive(customTypeOid, false, + () -> "Custom type OID should not be binary for receive by default"); + // first for receiving + queryExecutor.addBinaryReceiveOid(customTypeOid); + // Verify + assertBinaryForReceive(customTypeOid, true, + () -> "Just added oid via addBinaryReceiveOid"); + assertBinaryForSend(customTypeOid, false, + () -> "Just added oid via addBinaryReceiveOid"); + for (int oid : oidsReceive) { + assertBinaryForReceive(oid, true, + () -> "Previously registered BinaryReceiveOids should be intact after " + + "addBinaryReceiveOid(" + customTypeOid + ")"); + } + for (int oid : oidsSend) { + assertBinaryForSend(oid, true, + () -> "Previously registered BinarySendOids should be intact after " + + "addBinaryReceiveOid(" + customTypeOid + ")"); + } + // then for sending + queryExecutor.addBinarySendOid(customTypeOid); + // check new OID + assertBinaryForReceive(customTypeOid, true, () -> "added oid via addBinaryReceiveOid and " + + "addBinarySendOid"); + assertBinaryForSend(customTypeOid, true, () -> "added oid via addBinaryReceiveOid and " + + "addBinarySendOid"); + for (int oid : oidsReceive) { + assertBinaryForReceive(oid, true, () -> "Previously registered BinaryReceiveOids should be " + + "intact after addBinaryReceiveOid(" + customTypeOid + ") and addBinarySendOid(" + customTypeOid + ")"); + } + for (int oid : oidsSend) { + assertBinaryForSend(oid, true, () -> "Previously registered BinarySendOids should be intact" + + " after addBinaryReceiveOid(" + customTypeOid + ")"); + } + } +} diff --git a/src/test/java/org/postgresql/test/core/RequireAuthTest.java b/src/test/java/org/postgresql/test/core/RequireAuthTest.java new file mode 100644 index 0000000..64da414 --- /dev/null +++ b/src/test/java/org/postgresql/test/core/RequireAuthTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.core; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.PGProperty; +import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLException; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; + +class RequireAuthTest { + private static boolean hasScram; + + @BeforeAll + static void beforeAll() throws SQLException { + try (Connection conn = TestUtil.openPrivilegedDB()) { + TestUtil.execute(conn, "create user nobody with password 'none'"); + TestUtil.execute(conn, "create user pword with password 'password'"); + TestUtil.execute(conn, "create user scram with password 'scram'"); + TestUtil.execute(conn, "create user md51 with password 'pword'"); + TestUtil.execute(conn, "create database authtest owner nobody"); + hasScram = TestUtil.queryForString(conn, "show password_encryption").equals("scram-sha-256"); + } + } + + @AfterAll + static void afterAll() throws SQLException { + try (Connection conn = TestUtil.openPrivilegedDB()) { + TestUtil.execute(conn, "drop database authtest"); + TestUtil.execute(conn, "drop user md51"); + TestUtil.execute(conn, "drop user scram"); + TestUtil.execute(conn, "drop user pword"); + TestUtil.execute(conn, "drop user nobody"); + } + } + + @Test + void testRequireAuthAllowPassword() throws SQLException { + Properties props = new Properties(); + props.setProperty("user", "pword"); + props.setProperty("password", "password"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_DBNAME, "authtest"); + PGProperty.REQUIRE_AUTH.set(props, "password"); + + // This should succeed if server uses password auth + try (Connection conn = TestUtil.openDB(props)) { + assertTrue(conn.isValid(5)); + } + } + + @Test + void testRequireAuthRejectPassword() { + Properties props = new Properties(); + props.setProperty("user", "pword"); + props.setProperty("password", "password"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_DBNAME, "authtest"); + + PGProperty.REQUIRE_AUTH.set(props, "!password"); + + // This should fail if server uses password auth + assertThrows(PSQLException.class, () -> { + try (Connection conn = TestUtil.openDB(props)) { + // Should not reach here + } + }); + } + + @Test + void testRequireAuthAllowNone() throws SQLException { + Properties props = new Properties(); + props.setProperty("user", "nobody"); + props.setProperty("password", "none"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_DBNAME, "authtest"); + + PGProperty.REQUIRE_AUTH.set(props, "none"); + + // This should succeed if server uses no auth (trust) + try (Connection conn = TestUtil.openDB(props)) { + assertTrue(conn.isValid(5)); + } + } + + @Test + void testRequireAuthFailNone() throws SQLException { + Properties props = new Properties(); + props.setProperty("user", "pword"); + props.setProperty("password", "none"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_DBNAME, "authtest"); + + PGProperty.REQUIRE_AUTH.set(props, "none"); + + // This should fail if server uses no auth (trust) + assertThrows(PSQLException.class, () -> { + try (Connection conn = TestUtil.openDB(props)) { + // Should not reach here + } + }); + } + + @Test + void testRequireAuthRejectNone() { + Properties props = new Properties(); + props.setProperty("user", "nobody"); + props.setProperty("password", "none"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_DBNAME, "authtest"); + + PGProperty.REQUIRE_AUTH.set(props, "!none"); + + // This should fail if server uses no auth (trust) + assertThrows(PSQLException.class, () -> { + try (Connection conn = TestUtil.openDB(props)) { + // Should not reach here + } + }); + } + + @Test + void testRequireAuthAllowMd5orScram() throws SQLException { + Properties props = new Properties(); + props.setProperty("user", hasScram ? "scram" : "md51"); + props.setProperty("password", hasScram ? "scram" : "pword"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_DBNAME, "authtest"); + + PGProperty.REQUIRE_AUTH.set(props, hasScram ? "scram-sha-256" : "md5"); + + // This should succeed if server uses md5 auth + try (Connection conn = TestUtil.openDB(props)) { + assertTrue(conn.isValid(5)); + } + } + + @Test + void testRequireAuthMultipleAllowed() throws SQLException { + Properties props = new Properties(); + props.setProperty("user", hasScram ? "scram" : "md51"); + props.setProperty("password", hasScram ? "scram" : "pword"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_DBNAME, "authtest"); + PGProperty.REQUIRE_AUTH.set(props, "password,md5,scram-sha-256"); + + // This should succeed if server uses any of the allowed methods + try (Connection conn = TestUtil.openDB(props)) { + assertTrue(conn.isValid(5)); + } + } + + @Test + void testRequireAuthMultipleRejected() { + Properties props = new Properties(); + PGProperty.REQUIRE_AUTH.set(props, "!password,!scram-sha-256"); + props.setProperty("user", "pword"); + props.setProperty("password", "password"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_DBNAME, "authtest"); + // This should fail if server uses password or md5 auth + assertThrows(PSQLException.class, () -> { + try (Connection conn = TestUtil.openDB(props)) { + // Should not reach here + } + }); + } + + @Test + void testRequireAuthMixedAllowRejectThrowsException() { + Properties props = new Properties(); + PGProperty.REQUIRE_AUTH.set(props, "md5,!password"); + + // This should throw exception due to mixing positive and negative options + assertThrows(PSQLException.class, () -> { + try (Connection conn = TestUtil.openDB(props)) { + // Should not reach here + } + }); + } + + @Test + void testRequireAuthDuplicateThrowsException() { + Properties props = new Properties(); + PGProperty.REQUIRE_AUTH.set(props, "md5,password,md5"); + + // This should throw exception due to duplicate authentication method + assertThrows(PSQLException.class, () -> { + try (Connection conn = TestUtil.openDB(props)) { + // Should not reach here + } + }); + } + + @Test + void testRequireAuthInvalidMethodThrowsException() { + Properties props = new Properties(); + PGProperty.REQUIRE_AUTH.set(props, "invalid,md5"); + + // This should throw exception due to invalid authentication method + assertThrows(PSQLException.class, () -> { + try (Connection conn = TestUtil.openDB(props)) { + // Should not reach here + } + }); + } +} diff --git a/src/test/java/org/postgresql/test/extensions/HStoreTest.java b/src/test/java/org/postgresql/test/extensions/HStoreTest.java index ace6dce..5f2f51b 100644 --- a/src/test/java/org/postgresql/test/extensions/HStoreTest.java +++ b/src/test/java/org/postgresql/test/extensions/HStoreTest.java @@ -5,16 +5,17 @@ package org.postgresql.test.extensions; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.ServerVersion; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; @@ -34,9 +35,8 @@ public class HStoreTest extends BaseTest4 { @Override public void setUp() throws Exception { super.setUp(); - Assume.assumeTrue("server has installed hstore", isHStoreEnabled(con)); - Assume.assumeFalse("hstore is not supported in simple protocol only mode", - preferQueryMode == PreferQueryMode.SIMPLE); + assumeTrue(isHStoreEnabled(con), "server has installed hstore"); + assumeFalse(preferQueryMode == PreferQueryMode.SIMPLE, "hstore is not supported in simple protocol only mode"); assumeMinimumServerVersion("hstore requires PostgreSQL 8.3+", ServerVersion.v8_3); } @@ -62,7 +62,7 @@ public void testHStoreSelect() throws SQLException { if (!("\"a\"=>\"1\", \"b\"=>\"2\"".equals(str) || "\"b\"=>\"2\", \"a\"=>\"1\"".equals(str))) { fail("Expected " + "\"a\"=>\"1\", \"b\"=>\"2\"" + " but got " + str); } - Map correct = new HashMap(); + Map correct = new HashMap<>(); correct.put("a", "1"); correct.put("b", "2"); assertEquals(correct, rs.getObject(1)); diff --git a/src/test/java/org/postgresql/test/gss/GSSStreamTest.java b/src/test/java/org/postgresql/test/gss/GSSStreamTest.java new file mode 100644 index 0000000..dddf8da --- /dev/null +++ b/src/test/java/org/postgresql/test/gss/GSSStreamTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2025, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.gss; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.postgresql.gss.GSSInputStream; +import org.postgresql.gss.GSSOutputStream; +import org.postgresql.test.util.StrangeInputStream; +import org.postgresql.test.util.StrangeOutputStream; +import org.postgresql.util.internal.PgBufferedOutputStream; + +import org.ietf.jgss.MessageProp; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Random; + +public class GSSStreamTest { + static final boolean DEBUG = false; + private final MessageProp messageProp = new MessageProp(0, true); + + /** + * The test generates a random message, wraps it with {@link GSSOutputStream} and then unwraps + * with {@link GSSInputStream}. The output should match the input. + * + * @throws Exception in case of error + */ + @Test + public void testGSSMessageBuffer() throws Exception { + ByteArrayOutputStream wrappedContents = new ByteArrayOutputStream(); + Random rnd = new Random(42); + MockGSSContext gssContext = new MockGSSContext(rnd.nextLong(), messageProp); + GSSOutputStream gssOutputStream = new GSSOutputStream( + new PgBufferedOutputStream(wrappedContents, 20), + gssContext, messageProp, 20); + byte[] testMessage = new byte[10240]; + if (DEBUG) { + for (int i = 0; i < testMessage.length; i++) { + testMessage[i] = (byte) i; + } + } else { + rnd.nextBytes(testMessage); + } + try (StrangeOutputStream outputStream = + new StrangeOutputStream(gssOutputStream, rnd.nextLong(), 0.1);) { + outputStream.write(testMessage); + } + + // Unwrap the contents + // We use StrangeInputStream to test how GSSInputStream would react to the input streams + // that produce incomplete reads, and to verify how GSSInputStream would respond to + // reads of varying lengths. + StrangeInputStream inputStream = + new StrangeInputStream( + rnd.nextLong(), + new GSSInputStream( + new StrangeInputStream( + rnd.nextLong(), new ByteArrayInputStream(wrappedContents.toByteArray())), + gssContext, messageProp + )); + + ByteArrayOutputStream unwrapResults = new ByteArrayOutputStream(); + int readBytes; + byte[] tmpBuf = new byte[testMessage.length]; + while ((readBytes = inputStream.read(tmpBuf)) != -1) { + unwrapResults.write(tmpBuf, 0, readBytes); + } + byte[] unwrapResult = unwrapResults.toByteArray(); + assertArrayEquals(testMessage, unwrapResult, + "the message should be intact after wrap and unwrap"); + } +} diff --git a/src/test/java/org/postgresql/test/gss/MockGSSContext.java b/src/test/java/org/postgresql/test/gss/MockGSSContext.java new file mode 100644 index 0000000..044067a --- /dev/null +++ b/src/test/java/org/postgresql/test/gss/MockGSSContext.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2025, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.gss; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.ietf.jgss.ChannelBinding; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.MessageProp; +import org.ietf.jgss.Oid; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; + +/** + * Implements {@link GSSContext} that wraps input packets as follows: + * {@code messageLength (message.length + padding.length), message, padding}. + */ +public class MockGSSContext implements GSSContext { + private final Random rnd; + + public MockGSSContext(long seed, MessageProp messageProp) { + rnd = new Random(seed); + } + + @Override + public byte[] initSecContext(byte[] inputBuf, int offset, int len) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public int initSecContext(InputStream inStream, OutputStream outStream) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public byte[] acceptSecContext(byte[] inToken, int offset, int len) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void acceptSecContext(InputStream inStream, OutputStream outStream) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isEstablished() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void dispose() throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public int getWrapSizeLimit(int qop, boolean confReq, int maxTokenSize) throws GSSException { + return 100; + } + + private static void rangeCheck(byte[] inBuf, int offset, int len) { + assertTrue(offset >= 0, + () -> "offset should be greater or equal than zero" + + ", offset=" + offset + ", len=" + len + ", inBuf.length=" + inBuf.length); + assertTrue(offset < inBuf.length, + () -> "offset should be less than inBuf.length" + + ", offset=" + offset + ", len=" + len + "," + " inBuf.length=" + inBuf.length); + assertTrue(len >= 0, + () -> "len should be greater or equal than zero" + + ", offset=" + offset + ", len=" + len + ", inBuf.length=" + inBuf.length); + assertTrue(offset + len <= inBuf.length, + () -> "offset + len should not exceed buffer length" + + ", offset=" + offset + ", len=" + len + ", inBuf.length=" + inBuf.length); + } + + @Override + public byte[] wrap(byte[] inBuf, int offset, int len, MessageProp msgProp) throws GSSException { + rangeCheck(inBuf, offset, len); + byte[] res = new byte[4 + len + rnd.nextInt(inBuf.length)]; + ByteBuffer.wrap(res).putInt(len).put(inBuf, offset, len); + if (GSSStreamTest.DEBUG) { + System.out.println("wrapping offset=" + offset + ", len=" + len + " into message of length " + res.length); + } + return res; + } + + @Override + public byte[] unwrap(byte[] inBuf, int offset, int len, MessageProp msgProp) throws GSSException { + rangeCheck(inBuf, offset, len); + ByteBuffer bb = ByteBuffer.wrap(inBuf, offset, len); + int msgLen = bb.getInt(); + if (GSSStreamTest.DEBUG) { + System.out.println("unwrapping unwrapping offset=" + offset + ", len=" + len + " into message of length " + msgLen); + } + return Arrays.copyOfRange(inBuf, offset + 4, offset + 4 + msgLen); + } + + @Override + public void wrap(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void unwrap(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public byte[] getMIC(byte[] inMsg, int offset, int len, MessageProp msgProp) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void getMIC(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void verifyMIC(byte[] inToken, int tokOffset, int tokLen, byte[] inMsg, int msgOffset, + int msgLen, MessageProp msgProp) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void verifyMIC(InputStream tokStream, InputStream msgStream, MessageProp msgProp) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public byte[] export() throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void requestMutualAuth(boolean state) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void requestReplayDet(boolean state) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void requestSequenceDet(boolean state) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void requestCredDeleg(boolean state) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void requestAnonymity(boolean state) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void requestConf(boolean state) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void requestInteg(boolean state) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void requestLifetime(int lifetime) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void setChannelBinding(ChannelBinding cb) throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean getCredDelegState() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean getMutualAuthState() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean getReplayDetState() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean getSequenceDetState() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean getAnonymityState() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isTransferable() throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isProtReady() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean getConfState() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean getIntegState() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public int getLifetime() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public GSSName getSrcName() throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public GSSName getTargName() throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Oid getMech() throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public GSSCredential getDelegCred() throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isInitiator() throws GSSException { + throw new UnsupportedOperationException("not implemented"); + } +} diff --git a/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java b/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java index a727864..ac08d47 100644 --- a/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java +++ b/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java @@ -8,18 +8,20 @@ import static java.lang.Integer.parseInt; import static java.util.Arrays.asList; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import static org.postgresql.hostchooser.HostRequirement.any; +import static org.postgresql.hostchooser.HostRequirement.preferPrimary; import static org.postgresql.hostchooser.HostRequirement.preferSecondary; import static org.postgresql.hostchooser.HostRequirement.primary; import static org.postgresql.hostchooser.HostRequirement.secondary; import static org.postgresql.hostchooser.HostStatus.Primary; import static org.postgresql.hostchooser.HostStatus.Secondary; import static org.postgresql.test.TestUtil.closeDB; +import static org.postgresql.test.TestUtil.openDB; import org.postgresql.PGProperty; import org.postgresql.hostchooser.GlobalHostStatusTracker; @@ -27,10 +29,11 @@ import org.postgresql.test.TestUtil; import org.postgresql.util.HostSpec; import org.postgresql.util.PSQLException; +import org.postgresql.util.PSQLState; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.lang.reflect.Field; import java.sql.Connection; @@ -57,13 +60,13 @@ public class MultiHostsConnectionTest { private Connection con; private Map hostStatusMap; - @BeforeClass - public static void setUpClass() { + @BeforeAll + static void setUpClass() { assumeTrue(isReplicationInstanceAvailable()); } - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { Field field = GlobalHostStatusTracker.class.getDeclaredField("hostStatusMap"); field.setAccessible(true); hostStatusMap = (Map) field.get(null); @@ -95,15 +98,17 @@ private static Connection openSecondaryDB() throws Exception { TestUtil.initDriver(); Properties props = userAndPassword(); + TestUtil.setTestUrlProperty(props, PGProperty.PG_HOST, getSecondaryServer1()); + TestUtil.setTestUrlProperty(props, PGProperty.PG_PORT, String.valueOf(getSecondaryPort1())); - return DriverManager.getConnection(TestUtil.getURL(getSecondaryServer1(), getSecondaryPort1()), props); + return openDB(props); } private static Properties userAndPassword() { Properties props = new Properties(); - PGProperty.USER.set(props,TestUtil.getUser()); - PGProperty.PASSWORD.set(props,TestUtil.getPassword()); + PGProperty.USER.set(props, TestUtil.getUser()); + PGProperty.PASSWORD.set(props, TestUtil.getPassword()); return props; } @@ -119,7 +124,9 @@ private static Connection openSecondaryDB2() throws Exception { TestUtil.initDriver(); Properties props = userAndPassword(); - return DriverManager.getConnection(TestUtil.getURL(getSecondaryServer2(), getSecondaryPort2()), props); + TestUtil.setTestUrlProperty(props, PGProperty.PG_HOST, getSecondaryServer2()); + TestUtil.setTestUrlProperty(props, PGProperty.PG_PORT, String.valueOf(getSecondaryPort2())); + return openDB(props); } private static String getSecondaryServer2() { @@ -154,12 +161,12 @@ private Connection getConnection(HostRequirement hostType, boolean reset, boolea } Properties props = new Properties(); - PGProperty.USER.set(props,user); - PGProperty.PASSWORD.set(props,password); - PGProperty.TARGET_SERVER_TYPE.set(props,hostType.name()); + PGProperty.USER.set(props, user); + PGProperty.PASSWORD.set(props, password); + PGProperty.TARGET_SERVER_TYPE.set(props, hostType.name()); PGProperty.HOST_RECHECK_SECONDS.set(props, 2); if (lb) { - PGProperty.LOAD_BALANCE_HOSTS.set(props,"true"); + PGProperty.LOAD_BALANCE_HOSTS.set(props, "true"); } StringBuilder sb = new StringBuilder(); @@ -205,7 +212,7 @@ private void resetGlobalState() { } @Test - public void testConnectToAny() throws SQLException { + void connectToAny() throws SQLException { getConnection(any, fake1, primary1); assertRemote(primaryIp); assertGlobalState(primary1, "ConnectOK"); @@ -222,7 +229,7 @@ public void testConnectToAny() throws SQLException { } @Test - public void testConnectToMaster() throws SQLException { + void connectToMaster() throws SQLException { getConnection(primary, true, fake1, primary1, secondary1); assertRemote(primaryIp); assertGlobalState(fake1, "ConnectFail"); @@ -237,7 +244,51 @@ public void testConnectToMaster() throws SQLException { } @Test - public void testConnectToSecondary() throws SQLException { + void connectToPrimaryFirst() throws SQLException { + getConnection(preferPrimary, true, fake1, primary1, secondary1); + assertRemote(primaryIp); + assertGlobalState(fake1, "ConnectFail"); + assertGlobalState(primary1, "Primary"); + assertGlobalState(secondary1, null); + + getConnection(primary, false, fake1, secondary1, primary1); + assertRemote(primaryIp); + assertGlobalState(fake1, "ConnectFail"); + assertGlobalState(primary1, "Primary"); + assertGlobalState(secondary1, "Secondary"); // tried as it was unknown + + getConnection(preferPrimary, true, fake1, secondary1, primary1); + assertRemote(primaryIp); + assertGlobalState(fake1, "ConnectFail"); + assertGlobalState(primary1, "Primary"); + assertGlobalState(secondary1, "Secondary"); + } + + @Test + void connectToPrimaryWithReadonlyTransactionMode() throws SQLException { + con = TestUtil.openPrivilegedDB(); + con.createStatement().execute("ALTER DATABASE " + TestUtil.getDatabase() + " SET default_transaction_read_only=on;"); + try { + getConnection(primary, true, fake1, primary1, secondary1); + } catch (PSQLException e) { + assertEquals(PSQLState.CONNECTION_UNABLE_TO_CONNECT.getState(), e.getSQLState()); + assertGlobalState(fake1, "ConnectFail"); + assertGlobalState(primary1, "Secondary"); + assertGlobalState(secondary1, "Secondary"); + } finally { + con = TestUtil.openPrivilegedDB(); + con.createStatement().execute( + "BEGIN;" + + "SET TRANSACTION READ WRITE;" + + "ALTER DATABASE " + TestUtil.getDatabase() + " SET default_transaction_read_only=off;" + + "COMMIT;" + ); + TestUtil.closeDB(con); + } + } + + @Test + void connectToSecondary() throws SQLException { getConnection(secondary, true, fake1, secondary1, primary1); assertRemote(secondaryIP); assertGlobalState(fake1, "ConnectFail"); @@ -252,7 +303,7 @@ public void testConnectToSecondary() throws SQLException { } @Test - public void testConnectToSecondaryFirst() throws SQLException { + void connectToSecondaryFirst() throws SQLException { getConnection(preferSecondary, true, fake1, secondary1, primary1); assertRemote(secondaryIP); assertGlobalState(fake1, "ConnectFail"); @@ -273,7 +324,7 @@ public void testConnectToSecondaryFirst() throws SQLException { } @Test - public void testFailedConnection() throws SQLException { + void failedConnection() throws SQLException { try { getConnection(any, true, fake1); fail(); @@ -282,10 +333,10 @@ public void testFailedConnection() throws SQLException { } @Test - public void testLoadBalancing() throws SQLException { - Set connectedHosts = new HashSet(); + void loadBalancing() throws SQLException { + Set connectedHosts = new HashSet<>(); boolean fake1FoundTried = false; - for (int i = 0; i < 20; ++i) { + for (int i = 0; i < 20; i++) { getConnection(any, true, true, fake1, primary1, secondary1); connectedHosts.add(getRemoteHostSpec()); fake1FoundTried |= hostStatusMap.containsKey(hostSpec(fake1)); @@ -293,16 +344,60 @@ public void testLoadBalancing() throws SQLException { break; } } - assertEquals("Never connected to all hosts", new HashSet(asList(primaryIp, secondaryIP)), - connectedHosts); - assertTrue("Never tried to connect to fake node", fake1FoundTried); + assertEquals(new HashSet(asList(primaryIp, secondaryIP)), + connectedHosts, + "Never connected to all hosts"); + assertTrue(fake1FoundTried, "Never tried to connect to fake node"); + } + + @Test + void loadBalancing_preferPrimary() throws SQLException { + Set connectedHosts = new HashSet<>(); + Set tryConnectedHosts = new HashSet<>(); + for (int i = 0; i < 20; i++) { + getConnection(preferPrimary, true, true, fake1, secondary1, secondary2, primary1); + connectedHosts.add(getRemoteHostSpec()); + tryConnectedHosts.addAll(hostStatusMap.keySet()); + if (tryConnectedHosts.size() == 4) { + break; + } + } + + assertRemote(primaryIp); + assertEquals(new HashSet(asList(primaryIp)), + connectedHosts, + "Connected to hosts other than primary"); + assertEquals(4, tryConnectedHosts.size(), "Never tried to connect to fake node"); + + getConnection(preferPrimary, false, true, fake1, secondary1, primary1); + assertRemote(primaryIp); + + // connect to secondaries when there's no primary - with load balancing + connectedHosts.clear(); + for (int i = 0; i < 20; i++) { + getConnection(preferPrimary, false, true, fake1, secondary1, secondary2); + connectedHosts.add(getRemoteHostSpec()); + if (connectedHosts.size() == 2) { + break; + } + } + assertEquals(new HashSet(asList(secondaryIP, secondaryIP2)), + connectedHosts, + "Never connected to all secondary hosts"); + + // connect to secondary when there's no primary + getConnection(preferPrimary, true, true, fake1, secondary1); + assertRemote(secondaryIP); + + getConnection(preferPrimary, false, true, fake1, secondary1); + assertRemote(secondaryIP); } @Test - public void testLoadBalancing_preferSecondary() throws SQLException { - Set connectedHosts = new HashSet(); - Set tryConnectedHosts = new HashSet(); - for (int i = 0; i < 20; ++i) { + void loadBalancing_preferSecondary() throws SQLException { + Set connectedHosts = new HashSet<>(); + Set tryConnectedHosts = new HashSet<>(); + for (int i = 0; i < 20; i++) { getConnection(preferSecondary, true, true, fake1, primary1, secondary1, secondary2); connectedHosts.add(getRemoteHostSpec()); tryConnectedHosts.addAll(hostStatusMap.keySet()); @@ -310,22 +405,24 @@ public void testLoadBalancing_preferSecondary() throws SQLException { break; } } - assertEquals("Never connected to all secondary hosts", new HashSet(asList(secondaryIP, secondaryIP2)), - connectedHosts); - assertEquals("Never tried to connect to fake node",4, tryConnectedHosts.size()); + assertEquals(new HashSet(asList(secondaryIP, secondaryIP2)), + connectedHosts, + "Never connected to all secondary hosts"); + assertEquals(4, tryConnectedHosts.size(), "Never tried to connect to fake node"); getConnection(preferSecondary, false, true, fake1, primary1, secondary1); assertRemote(secondaryIP); connectedHosts.clear(); - for (int i = 0; i < 20; ++i) { + for (int i = 0; i < 20; i++) { getConnection(preferSecondary, false, true, fake1, primary1, secondary1, secondary2); connectedHosts.add(getRemoteHostSpec()); if (connectedHosts.size() == 2) { break; } } - assertEquals("Never connected to all secondary hosts", new HashSet(asList(secondaryIP, secondaryIP2)), - connectedHosts); + assertEquals(new HashSet(asList(secondaryIP, secondaryIP2)), + connectedHosts, + "Never connected to all secondary hosts"); // connect to primary when there's no secondary getConnection(preferSecondary, true, true, fake1, primary1); @@ -336,10 +433,10 @@ public void testLoadBalancing_preferSecondary() throws SQLException { } @Test - public void testLoadBalancing_secondary() throws SQLException { - Set connectedHosts = new HashSet(); - Set tryConnectedHosts = new HashSet(); - for (int i = 0; i < 20; ++i) { + void loadBalancing_secondary() throws SQLException { + Set connectedHosts = new HashSet<>(); + Set tryConnectedHosts = new HashSet<>(); + for (int i = 0; i < 20; i++) { getConnection(secondary, true, true, fake1, primary1, secondary1, secondary2); connectedHosts.add(getRemoteHostSpec()); tryConnectedHosts.addAll(hostStatusMap.keySet()); @@ -347,26 +444,28 @@ public void testLoadBalancing_secondary() throws SQLException { break; } } - assertEquals("Did not attempt to connect to all salve hosts", new HashSet(asList(secondaryIP, secondaryIP2)), - connectedHosts); - assertEquals("Did not attempt to connect to primary and fake node", 4, tryConnectedHosts.size()); + assertEquals(new HashSet(asList(secondaryIP, secondaryIP2)), + connectedHosts, + "Did not attempt to connect to all secondary hosts"); + assertEquals(4, tryConnectedHosts.size(), "Did not attempt to connect to primary and fake node"); getConnection(preferSecondary, false, true, fake1, primary1, secondary1); assertRemote(secondaryIP); connectedHosts.clear(); - for (int i = 0; i < 20; ++i) { + for (int i = 0; i < 20; i++) { getConnection(secondary, false, true, fake1, primary1, secondary1, secondary2); connectedHosts.add(getRemoteHostSpec()); if (connectedHosts.size() == 2) { break; } } - assertEquals("Did not connect to all secondary hosts", new HashSet(asList(secondaryIP, secondaryIP2)), - connectedHosts); + assertEquals(new HashSet(asList(secondaryIP, secondaryIP2)), + connectedHosts, + "Did not connect to all secondary hosts"); } @Test - public void testHostRechecks() throws SQLException, InterruptedException { + void hostRechecks() throws SQLException, InterruptedException { GlobalHostStatusTracker.reportHostStatus(hostSpec(primary1), Secondary); GlobalHostStatusTracker.reportHostStatus(hostSpec(secondary1), Primary); GlobalHostStatusTracker.reportHostStatus(hostSpec(fake1), Secondary); @@ -388,7 +487,7 @@ public void testHostRechecks() throws SQLException, InterruptedException { } @Test - public void testNoGoodHostsRechecksEverything() throws SQLException, InterruptedException { + void noGoodHostsRechecksEverything() throws SQLException, InterruptedException { GlobalHostStatusTracker.reportHostStatus(hostSpec(primary1), Secondary); GlobalHostStatusTracker.reportHostStatus(hostSpec(secondary1), Secondary); GlobalHostStatusTracker.reportHostStatus(hostSpec(fake1), Secondary); diff --git a/src/test/java/org/postgresql/test/impl/BaseServerVersionRangeCondition.java b/src/test/java/org/postgresql/test/impl/BaseServerVersionRangeCondition.java new file mode 100644 index 0000000..b893bf3 --- /dev/null +++ b/src/test/java/org/postgresql/test/impl/BaseServerVersionRangeCondition.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2026, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.impl; + +import org.postgresql.core.ServerVersion; +import org.postgresql.core.Version; +import org.postgresql.test.TestUtil; + +// import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.lang.reflect.AnnotatedElement; +import java.sql.Connection; + +public abstract class BaseServerVersionRangeCondition implements ExecutionCondition { + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(BaseServerVersionRangeCondition.class); + private static final String STORE_KEY = "serverVersionNum"; + + protected static Version getServerVersion(ExtensionContext context) { + // Use the root store so the version is computed once per entire test run + ExtensionContext.Store store = context.getRoot().getStore(NAMESPACE); + return store.getOrComputeIfAbsent( + STORE_KEY, + key -> computeServerVersionNum(), + Version.class + ); + } + + private static Version computeServerVersionNum() { + try (Connection con = TestUtil.openDB()) { + String dbVersionNumber = con.getMetaData().getDatabaseProductVersion(); + return ServerVersion.from(dbVersionNumber); + } catch (Exception e) { + throw new IllegalStateException("No available open connection", e); + } + } + + protected static /* @Nullable */ Version getVersion(AnnotatedElement element, String name, String value) { + if (value == null || value.equals("")) { + return null; + } + Version version = ServerVersion.from(value); + if (version.getVersionNum() <= 0) { + throw new IllegalArgumentException( + "Server " + name + " version " + value + " is not valid for " + element); + } + return version; + } + + protected static boolean matchesVersionRange(Version actualVersion, /* @Nullable */ Version lt, /* @Nullable */ Version lte, /* @Nullable */ Version gte, /* @Nullable */ Version gt) { + if (lt == null && lte == null && gt == null && gte == null) { + throw new IllegalArgumentException("At least one version predicate must be populated"); + } + if (lt != null && lte != null) { + throw new IllegalArgumentException("Both less than and less than or equal cannot be populated"); + } + if (gt != null && gte != null) { + throw new IllegalArgumentException("Both greater than and greater than or equal cannot be populated"); + } + int actualVersionNum = actualVersion.getVersionNum(); + return (lt == null || actualVersionNum < lt.getVersionNum()) + && (lte == null || actualVersionNum <= lte.getVersionNum()) + && (gt == null || actualVersionNum > gt.getVersionNum()) + && (gte == null || actualVersionNum >= gte.getVersionNum()); + } +} diff --git a/src/test/java/org/postgresql/test/impl/DisableLoggerExtension.java b/src/test/java/org/postgresql/test/impl/DisableLoggerExtension.java new file mode 100644 index 0000000..bde2fad --- /dev/null +++ b/src/test/java/org/postgresql/test/impl/DisableLoggerExtension.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2026, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.impl; + +import org.postgresql.test.annotations.DisableLogger; + +// import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.support.AnnotationSupport; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A JUnit extension that disables specific loggers during the execution of tests annotated + * with {@code @DisableLogger}. This can be used to suppress log output for certain categories + * or classes during the test execution. + * + *

          This extension handles the following responsibilities: + *

            + *
          • Before each test, it identifies loggers specified in {@code @DisableLogger} annotations + * on the test method or test class, saves their current logging levels, and disables them + * by setting their levels to {@link Level#OFF}.
          • + *
          • After each test, it restores the original logging levels for the loggers that were disabled.
          • + *
          + * + *

          The extension utilizes the JUnit 5 extension model, implementing {@link BeforeEachCallback} + * and {@link AfterEachCallback}, for pre- and post-test processing. + * + *

          This extension works with the {@code @DisableLogger} annotation, which allows specifying + * loggers by their associated classes or by category names. Annotations can be applied + * at the method or class level, and multiple annotations can be used together. + * + *

          For internal state management, the extension uses a {@link ExtensionContext.Store} + * to retain and retrieve the original logging levels for loggers across the lifecycle of each test. + */ +public class DisableLoggerExtension implements BeforeEachCallback, AfterEachCallback { + static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(DisableLoggerExtension.class); + private static final String SAVED_LEVELS_KEY = "savedLoggerLevels"; + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + List annotations = new ArrayList<>(); + annotations.addAll( + AnnotationSupport.findRepeatableAnnotations(context.getRequiredTestMethod(), DisableLogger.class)); + annotations.addAll( + AnnotationSupport.findRepeatableAnnotations(context.getRequiredTestClass(), DisableLogger.class)); + + if (annotations.isEmpty()) { + return; + } + + // Collect all logger names from annotations + Set loggerNames = new HashSet<>(); + for (DisableLogger annotation : annotations) { + if (annotation.value().length == 0 && annotation.categories().length == 0) { + throw new IllegalArgumentException("At least one logger category or class must be specified for @DisableLogger for " + context.getDisplayName()); + } + for (Class clazz : annotation.value()) { + loggerNames.add(clazz.getName()); + } + loggerNames.addAll(Arrays.asList(annotation.categories())); + } + + // Save current levels and disable + Map savedLevels = new HashMap<>(); + for (String name : loggerNames) { + Logger logger = Logger.getLogger(name); + savedLevels.put(name, logger.getLevel()); + logger.setLevel(Level.OFF); + } + + context.getStore(NAMESPACE).put(SAVED_LEVELS_KEY, savedLevels); + } + + @Override + public void afterEach(ExtensionContext context) throws Exception { + @SuppressWarnings("unchecked") + Map savedLevels = + (Map) context.getStore(NAMESPACE).remove(SAVED_LEVELS_KEY); + if (savedLevels == null) { + return; + } + + for (Map.Entry entry : savedLevels.entrySet()) { + Logger.getLogger(entry.getKey()).setLevel(entry.getValue()); + } + } +} diff --git a/src/test/java/org/postgresql/test/impl/DisabledForServerVersionRangeCondition.java b/src/test/java/org/postgresql/test/impl/DisabledForServerVersionRangeCondition.java new file mode 100644 index 0000000..26d9e64 --- /dev/null +++ b/src/test/java/org/postgresql/test/impl/DisabledForServerVersionRangeCondition.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.impl; + +import org.postgresql.core.Version; +import org.postgresql.test.annotations.DisabledForServerVersionRange; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.util.AnnotationUtils; + +import java.lang.reflect.AnnotatedElement; + +/** + * Evaluates condition for {@link DisabledForServerVersionRange} annotation. + */ +public class DisabledForServerVersionRangeCondition extends BaseServerVersionRangeCondition { + private static final ConditionEvaluationResult ENABLED_NOT_PRESENT = ConditionEvaluationResult.enabled( + "@DisabledForServerVersionRange is not present"); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + AnnotatedElement element = context.getElement().orElse(null); + if (element == null) { + return ENABLED_NOT_PRESENT; + } + DisabledForServerVersionRange annotation = AnnotationUtils.findAnnotation(element, DisabledForServerVersionRange.class).orElse(null); + if (annotation == null) { + return ENABLED_NOT_PRESENT; + } + + // This is the server version from the database + Version actualVersion = getServerVersion(context); + + Version lt = getVersion(element, "lt", annotation.lt()); + Version lte = getVersion(element, "lte", annotation.lte()); + Version gte = getVersion(element, "gte", annotation.gte()); + Version gt = getVersion(element, "gt", annotation.gt()); + + boolean matches = matchesVersionRange(actualVersion, lt, lte, gte, gt); + if (matches) { + return ConditionEvaluationResult.disabled("Test is disabled for this server version " + actualVersion); + } + return ConditionEvaluationResult.enabled("Test is enabled for this server version " + actualVersion); + } +} diff --git a/src/test/java/org/postgresql/test/impl/EnabledForServerVersionRangeCondition.java b/src/test/java/org/postgresql/test/impl/EnabledForServerVersionRangeCondition.java new file mode 100644 index 0000000..3b715b2 --- /dev/null +++ b/src/test/java/org/postgresql/test/impl/EnabledForServerVersionRangeCondition.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.impl; + +import org.postgresql.core.Version; +import org.postgresql.test.annotations.EnabledForServerVersionRange; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.platform.commons.util.AnnotationUtils; + +import java.lang.reflect.AnnotatedElement; + +/** + * Evaluates condition for {@link EnabledForServerVersionRange} annotation. + */ +public class EnabledForServerVersionRangeCondition extends BaseServerVersionRangeCondition { + private static final ConditionEvaluationResult ENABLED_NOT_PRESENT = ConditionEvaluationResult.enabled( + "@EnabledForServerVersionRange is not present"); + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + AnnotatedElement element = context.getElement().orElse(null); + if (element == null) { + return ENABLED_NOT_PRESENT; + } + EnabledForServerVersionRange annotation = AnnotationUtils.findAnnotation(element, EnabledForServerVersionRange.class).orElse(null); + if (annotation == null) { + return ENABLED_NOT_PRESENT; + } + + // This is the server version from the database + Version actualVersion = getServerVersion(context); + + Version lt = getVersion(element, "lt", annotation.lt()); + Version lte = getVersion(element, "lte", annotation.lte()); + Version gte = getVersion(element, "gte", annotation.gte()); + Version gt = getVersion(element, "gt", annotation.gt()); + + boolean matches = matchesVersionRange(actualVersion, lt, lte, gte, gt); + if (matches) { + return ConditionEvaluationResult.enabled("Test is enabled for this server version " + actualVersion); + } + return ConditionEvaluationResult.disabled("Test is disabled for this server version " + actualVersion); + } +} diff --git a/src/test/java/org/postgresql/test/jdbc2/ArrayTest.java b/src/test/java/org/postgresql/test/jdbc2/ArrayTest.java index 4df1e60..adfdfa8 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ArrayTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ArrayTest.java @@ -5,8 +5,11 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGConnection; import org.postgresql.core.BaseConnection; @@ -18,10 +21,11 @@ import org.postgresql.test.TestUtil; import org.postgresql.util.PSQLException; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.math.BigDecimal; import java.sql.Array; @@ -31,11 +35,14 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; import java.sql.Types; import java.util.ArrayList; import java.util.Collection; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class ArrayTest extends BaseTest4 { private Connection conn; @@ -43,26 +50,33 @@ public ArrayTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "arrtest", "intarr int[], decarr decimal(2,1)[], strarr text[]"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "arrtest"); + } + } + @Override public void setUp() throws Exception { super.setUp(); conn = con; - TestUtil.createTable(conn, "arrtest", "intarr int[], decarr decimal(2,1)[], strarr text[]"); - } - - @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(conn, "arrtest"); - super.tearDown(); + TestUtil.execute(con, "TRUNCATE arrtest"); } @Test @@ -98,10 +112,10 @@ public void testSetPrimitiveObjects() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT intarr, decarr, strarr FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); - Assert.assertEquals(Types.INTEGER, arr.getBaseType()); + assertEquals(Types.INTEGER, arr.getBaseType()); Integer[] intarr = (Integer[]) arr.getArray(); assertEquals(3, intarr.length); assertEquals(1, intarr[0].intValue()); @@ -137,9 +151,9 @@ public void testIndexAccess() throws SQLException { final Object[][][] origDblObjArray = new Object[2][2][2]; final Object[][][] origStringObjArray = new Object[2][2][2]; int i = 0; - for (int x = 0; x < 2; ++x) { - for (int y = 0; y < 2; ++y) { - for (int z = 0; z < 2; ++z) { + for (int x = 0; x < 2; x++) { + for (int y = 0; y < 2; y++) { + for (int z = 0; z < 2; z++) { origIntArray[x][y][z] = i; origDblArray[x][y][z] = i / 10; origStringArray[x][y][z] = Integer.toString(i); @@ -159,7 +173,7 @@ public void testIndexAccess() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT intarr[1], decarr[1], strarr[1] FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); assertEquals(origIntArray[0][0][0], rs.getInt(1)); assertEquals(origDblArray[0][0][0], rs.getDouble(2), 0.001); @@ -174,13 +188,13 @@ public void testIndexAccess() throws SQLException { pstmt = conn.prepareStatement("INSERT INTO arrtest VALUES (?,?,?)"); pstmt.setObject(1, conn.createArrayOf("int4", origIntObjArray[0][0]), Types.ARRAY); pstmt.setObject(2, conn.createArrayOf("float8", origDblObjArray[0][0]), Types.ARRAY); - pstmt.setObject(3, conn.createArrayOf("varchar", origStringObjArray[0][0]), Types.ARRAY); + pstmt.setObject(3, conn.createArrayOf("varchar", origStringObjArray[0][0]), Types.ARRAY); pstmt.executeUpdate(); pstmt.close(); stmt = conn.createStatement(); rs = stmt.executeQuery("SELECT intarr[1], decarr[1], strarr[1] FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); assertEquals(origIntArray[0][0][0], rs.getInt(1)); assertEquals(origDblArray[0][0][0], rs.getDouble(2), 0.001); @@ -195,13 +209,13 @@ public void testIndexAccess() throws SQLException { pstmt = conn.prepareStatement("INSERT INTO arrtest VALUES (?,?,?)"); pstmt.setObject(1, conn.createArrayOf("int4", origIntArray[0]), Types.ARRAY); pstmt.setObject(2, conn.createArrayOf("float8", origDblArray[0]), Types.ARRAY); - pstmt.setObject(3, conn.createArrayOf("varchar", origStringArray[0]), Types.ARRAY); + pstmt.setObject(3, conn.createArrayOf("varchar", origStringArray[0]), Types.ARRAY); pstmt.executeUpdate(); pstmt.close(); stmt = conn.createStatement(); rs = stmt.executeQuery("SELECT intarr[1][1], decarr[1][1], strarr[1][1], intarr[2][1], decarr[2][1], strarr[2][1] FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); assertEquals(origIntArray[0][0][0], rs.getInt(1)); assertEquals(origDblArray[0][0][0], rs.getDouble(2), 0.001); @@ -219,13 +233,13 @@ public void testIndexAccess() throws SQLException { pstmt = conn.prepareStatement("INSERT INTO arrtest VALUES (?,?,?)"); pstmt.setObject(1, conn.createArrayOf("int4", origIntObjArray[0]), Types.ARRAY); pstmt.setObject(2, conn.createArrayOf("float8", origDblObjArray[0]), Types.ARRAY); - pstmt.setObject(3, conn.createArrayOf("varchar", origStringObjArray[0]), Types.ARRAY); + pstmt.setObject(3, conn.createArrayOf("varchar", origStringObjArray[0]), Types.ARRAY); pstmt.executeUpdate(); pstmt.close(); stmt = conn.createStatement(); rs = stmt.executeQuery("SELECT intarr[1][1], decarr[1][1], strarr[1][1], intarr[2][1], decarr[2][1], strarr[2][1] FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); assertEquals(origIntArray[0][0][0], rs.getInt(1)); assertEquals(origDblArray[0][0][0], rs.getDouble(2), 0.001); @@ -244,13 +258,13 @@ public void testIndexAccess() throws SQLException { pstmt.setObject(1, conn.createArrayOf("int4", origIntArray), Types.ARRAY); pstmt.setObject(2, conn.createArrayOf("float8", origDblArray), Types.ARRAY); - pstmt.setObject(3, conn.createArrayOf("varchar", origStringArray), Types.ARRAY); + pstmt.setObject(3, conn.createArrayOf("varchar", origStringArray), Types.ARRAY); pstmt.executeUpdate(); pstmt.close(); stmt = conn.createStatement(); rs = stmt.executeQuery("SELECT intarr[1][1][1], decarr[1][1][1], strarr[1][1][1], intarr[2][1][1], decarr[2][1][1], strarr[2][1][1] FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); assertEquals(origIntArray[0][0][0], rs.getInt(1)); assertEquals(origDblArray[0][0][0], rs.getDouble(2), 0.001); @@ -269,13 +283,13 @@ public void testIndexAccess() throws SQLException { pstmt.setObject(1, conn.createArrayOf("int4", origIntObjArray), Types.ARRAY); pstmt.setObject(2, conn.createArrayOf("float8", origDblObjArray), Types.ARRAY); - pstmt.setObject(3, conn.createArrayOf("varchar", origStringObjArray), Types.ARRAY); + pstmt.setObject(3, conn.createArrayOf("varchar", origStringObjArray), Types.ARRAY); pstmt.executeUpdate(); pstmt.close(); stmt = conn.createStatement(); rs = stmt.executeQuery("SELECT intarr[1][1][1], decarr[1][1][1], strarr[1][1][1], intarr[2][1][1], decarr[2][1][1], strarr[2][1][1] FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); assertEquals(origIntArray[0][0][0], rs.getInt(1)); assertEquals(origDblArray[0][0][0], rs.getDouble(2), 0.001); @@ -293,41 +307,41 @@ public void testSetPrimitiveArraysObjects() throws SQLException { final PGConnection arraySupport = conn.unwrap(PGConnection.class); - pstmt.setArray(1, arraySupport.createArrayOf("int4", new int[] { 1, 2, 3 })); - pstmt.setObject(2, arraySupport.createArrayOf("float8", new double[] { 3.1d, 1.4d })); - pstmt.setObject(3, arraySupport.createArrayOf("varchar", new String[] { "abc", "f'a", "fa\"b" })); + pstmt.setArray(1, arraySupport.createArrayOf("int4", new int[]{1, 2, 3})); + pstmt.setObject(2, arraySupport.createArrayOf("float8", new double[]{3.1d, 1.4d})); + pstmt.setObject(3, arraySupport.createArrayOf("varchar", new String[]{"abc", "f'a", "fa\"b"})); pstmt.executeUpdate(); pstmt.close(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT intarr, decarr, strarr FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); - Assert.assertEquals(Types.INTEGER, arr.getBaseType()); + assertEquals(Types.INTEGER, arr.getBaseType()); Integer[] intarr = (Integer[]) arr.getArray(); - Assert.assertEquals(3, intarr.length); - Assert.assertEquals(1, intarr[0].intValue()); - Assert.assertEquals(2, intarr[1].intValue()); - Assert.assertEquals(3, intarr[2].intValue()); + assertEquals(3, intarr.length); + assertEquals(1, intarr[0].intValue()); + assertEquals(2, intarr[1].intValue()); + assertEquals(3, intarr[2].intValue()); arr = rs.getArray(2); - Assert.assertEquals(Types.NUMERIC, arr.getBaseType()); + assertEquals(Types.NUMERIC, arr.getBaseType()); BigDecimal[] decarr = (BigDecimal[]) arr.getArray(); - Assert.assertEquals(2, decarr.length); - Assert.assertEquals(new BigDecimal("3.1"), decarr[0]); - Assert.assertEquals(new BigDecimal("1.4"), decarr[1]); + assertEquals(2, decarr.length); + assertEquals(new BigDecimal("3.1"), decarr[0]); + assertEquals(new BigDecimal("1.4"), decarr[1]); arr = rs.getArray(3); - Assert.assertEquals(Types.VARCHAR, arr.getBaseType()); + assertEquals(Types.VARCHAR, arr.getBaseType()); String[] strarr = (String[]) arr.getArray(2, 2); - Assert.assertEquals(2, strarr.length); - Assert.assertEquals("f'a", strarr[0]); - Assert.assertEquals("fa\"b", strarr[1]); + assertEquals(2, strarr.length); + assertEquals("f'a", strarr[0]); + assertEquals("fa\"b", strarr[1]); try { - arraySupport.createArrayOf("int4", Integer.valueOf(1)); + arraySupport.createArrayOf("int4", 1); fail("not an array"); } catch (PSQLException e) { @@ -336,6 +350,102 @@ public void testSetPrimitiveArraysObjects() throws SQLException { rs.close(); } + @Test + public void testSetArraysWithAnsiTypeNames() throws SQLException { + try { + TestUtil.createTable( + conn, + "ansiarraytest", + "floats double precision[], " + + "reals real[], " + + "varchars character varying(8)[], " + + "times time without time zone[], " + + "timestamps timestamp without time zone[], " + + "timestampstz timestamp with time zone[]"); + + PreparedStatement pstmt = + conn.prepareStatement("INSERT INTO ansiarraytest VALUES (?,?,?,?,?,?)"); + + final PGConnection arraySupport = conn.unwrap(PGConnection.class); + + pstmt.setArray(1, arraySupport.createArrayOf("double precision", new Object[]{1d, 4d})); + pstmt.setArray(2, arraySupport.createArrayOf("real", new Object[]{0f, 3f})); + pstmt.setObject( + 3, arraySupport.createArrayOf("character varying", new String[]{"abc", "f'a", "fa\"b"})); + pstmt.setObject( + 4, + arraySupport.createArrayOf( + "time without time zone", + new Object[]{Time.valueOf("12:34:56"), Time.valueOf("03:30:25")})); + pstmt.setObject( + 5, + arraySupport.createArrayOf( + "timestamp without time zone", + new Object[]{"2023-09-05 16:21:50", "2012-01-01 13:02:03"})); + pstmt.setObject( + 6, + arraySupport.createArrayOf( + "timestamp with time zone", + new Object[]{"1996-01-23 12:00:00-08", "1997-08-16 16:51:00-04"})); + + pstmt.executeUpdate(); + pstmt.close(); + + Statement stmt = conn.createStatement(); + ResultSet rs = + stmt.executeQuery( + "SELECT floats, reals, varchars, times, timestamps, timestampstz FROM ansiarraytest"); + assertTrue(rs.next()); + + Array arr = rs.getArray(1); + assertEquals(Types.DOUBLE, arr.getBaseType()); + Double[] doubles = (Double[]) arr.getArray(); + assertEquals(2, doubles.length); + assertEquals(1d, doubles[0], 0); + assertEquals(4d, doubles[1], 0); + + arr = rs.getArray(2); + assertEquals(Types.REAL, arr.getBaseType()); + Float[] floats = (Float[]) arr.getArray(); + assertEquals(2, floats.length); + assertEquals(0f, floats[0], 0); + assertEquals(3f, floats[1], 0); + + arr = rs.getArray(3); + assertEquals(Types.VARCHAR, arr.getBaseType()); + String[] strings = (String[]) arr.getArray(); + assertEquals(3, strings.length); + assertEquals("abc", strings[0]); + assertEquals("f'a", strings[1]); + assertEquals("fa\"b", strings[2]); + + arr = rs.getArray(4); + assertEquals(Types.TIME, arr.getBaseType()); + Time[] times = (Time[]) arr.getArray(); + assertEquals(2, times.length); + assertEquals(Time.valueOf("12:34:56"), times[0]); + assertEquals(Time.valueOf("03:30:25"), times[1]); + + arr = rs.getArray(5); + assertEquals(Types.TIMESTAMP, arr.getBaseType()); + Timestamp[] tzarr = (Timestamp[]) arr.getArray(); + assertEquals(2, times.length); + assertEquals(Timestamp.valueOf("2023-09-05 16:21:50"), tzarr[0]); + assertEquals(Timestamp.valueOf("2012-01-01 13:02:03"), tzarr[1]); + + arr = rs.getArray(6); + assertEquals(Types.TIMESTAMP, arr.getBaseType()); + tzarr = (Timestamp[]) arr.getArray(); + assertEquals(2, times.length); + assertEquals(822427200000L, tzarr[0].getTime()); + assertEquals(871764660000L, tzarr[1].getTime()); + + rs.close(); + } finally { + TestUtil.dropTable(conn, "ansiarraytest"); + } + } + @Test public void testSetNullArrays() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("INSERT INTO arrtest VALUES (?,?,?)"); @@ -351,16 +461,16 @@ public void testSetNullArrays() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT intarr, decarr, strarr FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); - Assert.assertNull(arr); + assertNull(arr); arr = rs.getArray(2); - Assert.assertNull(arr); + assertNull(arr); arr = rs.getArray(3); - Assert.assertNull(arr); + assertNull(arr); rs.close(); } @@ -374,29 +484,29 @@ public void testRetrieveArrays() throws SQLException { + TestUtil.escapeString(conn, "{abc,f'a,\"fa\\\"b\",def, un quot\u000B \u2001 \r}") + "')"); ResultSet rs = stmt.executeQuery("SELECT intarr, decarr, strarr FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); - Assert.assertEquals(Types.INTEGER, arr.getBaseType()); + assertEquals(Types.INTEGER, arr.getBaseType()); Integer[] intarr = (Integer[]) arr.getArray(); - Assert.assertEquals(3, intarr.length); - Assert.assertEquals(1, intarr[0].intValue()); - Assert.assertEquals(2, intarr[1].intValue()); - Assert.assertEquals(3, intarr[2].intValue()); + assertEquals(3, intarr.length); + assertEquals(1, intarr[0].intValue()); + assertEquals(2, intarr[1].intValue()); + assertEquals(3, intarr[2].intValue()); arr = rs.getArray(2); - Assert.assertEquals(Types.NUMERIC, arr.getBaseType()); + assertEquals(Types.NUMERIC, arr.getBaseType()); BigDecimal[] decarr = (BigDecimal[]) arr.getArray(); - Assert.assertEquals(2, decarr.length); - Assert.assertEquals(new BigDecimal("3.1"), decarr[0]); - Assert.assertEquals(new BigDecimal("1.4"), decarr[1]); + assertEquals(2, decarr.length); + assertEquals(new BigDecimal("3.1"), decarr[0]); + assertEquals(new BigDecimal("1.4"), decarr[1]); arr = rs.getArray(3); - Assert.assertEquals(Types.VARCHAR, arr.getBaseType()); + assertEquals(Types.VARCHAR, arr.getBaseType()); String[] strarr = (String[]) arr.getArray(2, 2); - Assert.assertEquals(2, strarr.length); - Assert.assertEquals("f'a", strarr[0]); - Assert.assertEquals("fa\"b", strarr[1]); + assertEquals(2, strarr.length); + assertEquals("f'a", strarr[0]); + assertEquals("fa\"b", strarr[1]); strarr = (String[]) arr.getArray(); assertEquals(5, strarr.length); @@ -416,53 +526,169 @@ public void testRetrieveResultSets() throws SQLException { + TestUtil.escapeString(conn, "{\"a\u2001b\",f'a,\"fa\\\"b\",def}") + "')"); ResultSet rs = stmt.executeQuery("SELECT intarr, decarr, strarr FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); - Assert.assertEquals(Types.INTEGER, arr.getBaseType()); + assertEquals(Types.INTEGER, arr.getBaseType()); ResultSet arrrs = arr.getResultSet(); - Assert.assertTrue(arrrs.next()); - Assert.assertEquals(1, arrrs.getInt(1)); - Assert.assertEquals(1, arrrs.getInt(2)); - Assert.assertTrue(arrrs.next()); - Assert.assertEquals(2, arrrs.getInt(1)); - Assert.assertEquals(2, arrrs.getInt(2)); - Assert.assertTrue(arrrs.next()); - Assert.assertEquals(3, arrrs.getInt(1)); - Assert.assertEquals(3, arrrs.getInt(2)); - Assert.assertTrue(!arrrs.next()); - Assert.assertTrue(arrrs.previous()); - Assert.assertEquals(3, arrrs.getInt(2)); + assertTrue(arrrs.next()); + assertEquals(1, arrrs.getInt(1)); + assertEquals(1, arrrs.getInt(2)); + assertTrue(arrrs.next()); + assertEquals(2, arrrs.getInt(1)); + assertEquals(2, arrrs.getInt(2)); + assertTrue(arrrs.next()); + assertEquals(3, arrrs.getInt(1)); + assertEquals(3, arrrs.getInt(2)); + assertFalse(arrrs.next()); + assertTrue(arrrs.previous()); + assertEquals(3, arrrs.getInt(2)); arrrs.first(); - Assert.assertEquals(1, arrrs.getInt(2)); + assertEquals(1, arrrs.getInt(2)); arrrs.close(); arr = rs.getArray(2); - Assert.assertEquals(Types.NUMERIC, arr.getBaseType()); + assertEquals(Types.NUMERIC, arr.getBaseType()); arrrs = arr.getResultSet(); - Assert.assertTrue(arrrs.next()); - Assert.assertEquals(new BigDecimal("3.1"), arrrs.getBigDecimal(2)); - Assert.assertTrue(arrrs.next()); - Assert.assertEquals(new BigDecimal("1.4"), arrrs.getBigDecimal(2)); + assertTrue(arrrs.next()); + assertEquals(new BigDecimal("3.1"), arrrs.getBigDecimal(2)); + assertTrue(arrrs.next()); + assertEquals(new BigDecimal("1.4"), arrrs.getBigDecimal(2)); arrrs.close(); arr = rs.getArray(3); - Assert.assertEquals(Types.VARCHAR, arr.getBaseType()); + assertEquals(Types.VARCHAR, arr.getBaseType()); arrrs = arr.getResultSet(2, 2); - Assert.assertTrue(arrrs.next()); - Assert.assertEquals(2, arrrs.getInt(1)); - Assert.assertEquals("f'a", arrrs.getString(2)); - Assert.assertTrue(arrrs.next()); - Assert.assertEquals(3, arrrs.getInt(1)); - Assert.assertEquals("fa\"b", arrrs.getString(2)); - Assert.assertTrue(!arrrs.next()); + assertTrue(arrrs.next()); + assertEquals(2, arrrs.getInt(1)); + assertEquals("f'a", arrrs.getString(2)); + assertTrue(arrrs.next()); + assertEquals(3, arrrs.getInt(1)); + assertEquals("fa\"b", arrrs.getString(2)); + assertFalse(arrrs.next()); arrrs.close(); arrrs = arr.getResultSet(1, 1); - Assert.assertTrue(arrrs.next()); - Assert.assertEquals(1, arrrs.getInt(1)); - Assert.assertEquals(stringWithNonAsciiWhiteSpace, arrrs.getString(2)); - Assert.assertFalse(arrrs.next()); + assertTrue(arrrs.next()); + assertEquals(1, arrrs.getInt(1)); + assertEquals(stringWithNonAsciiWhiteSpace, arrrs.getString(2)); + assertFalse(arrrs.next()); + arrrs.close(); + + rs.close(); + stmt.close(); + } + + @Test + public void testMultiDimensionalArrayRetrieveResultSets() throws SQLException { + final int[][][] origIntArray = new int[3][2][1]; + int i = 0; + for (int x = 0; x < 3; x++) { + for (int y = 0; y < 2; y++) { + for (int z = 0; z < 1; z++) { + origIntArray[x][y][z] = i; + i++; + } + } + } + + PreparedStatement pstmt = conn.prepareStatement("INSERT INTO arrtest(intarr) VALUES (?)"); + pstmt.setObject(1, origIntArray, Types.ARRAY); + pstmt.executeUpdate(); + pstmt.close(); + + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT intarr FROM arrtest"); + assertTrue(rs.next()); + + // {{{0},{1}},{{2},{3}},{{4},{5}}} + Array arr = rs.getArray(1); + assertEquals(Types.INTEGER, arr.getBaseType()); + ResultSet arrrs = arr.getResultSet(); + assertTrue(arrrs.next()); + // {{0},{1}} + assertEquals(1, arrrs.getInt(1)); + Array arr1 = arrrs.getArray(2); + ResultSet arr1rs = arr1.getResultSet(); + assertTrue(arr1rs.next()); + // {0} + assertEquals(1, arr1rs.getInt(1)); + Array arr11 = arr1rs.getArray(2); + ResultSet arr11rs = arr11.getResultSet(); + assertTrue(arr11rs.next()); + assertEquals(1, arr11rs.getInt(1)); + assertEquals(0, arr11rs.getInt(2)); + assertFalse(arr11rs.next()); + arr11rs.close(); + assertTrue(arr1rs.next()); + // {1} + assertEquals(2, arr1rs.getInt(1)); + Array arr12 = arr1rs.getArray(2); + ResultSet arr12rs = arr12.getResultSet(); + assertTrue(arr12rs.next()); + assertEquals(1, arr12rs.getInt(1)); + assertEquals(1, arr12rs.getInt(2)); + assertFalse(arr12rs.next()); + arr12rs.close(); + assertFalse(arr1rs.next()); + arr1rs.close(); + assertTrue(arrrs.next()); + + // {{2},{3}} + assertEquals(2, arrrs.getInt(1)); + Array arr2 = arrrs.getArray(2); + ResultSet arr2rs = arr2.getResultSet(); + assertTrue(arr2rs.next()); + // {2} + assertEquals(1, arr2rs.getInt(1)); + Array arr21 = arr2rs.getArray(2); + ResultSet arr21rs = arr21.getResultSet(); + assertTrue(arr21rs.next()); + assertEquals(1, arr21rs.getInt(1)); + assertEquals(2, arr21rs.getInt(2)); + assertFalse(arr21rs.next()); + arr21rs.close(); + assertTrue(arr2rs.next()); + // {3} + assertEquals(2, arr2rs.getInt(1)); + Array arr22 = arr2rs.getArray(2); + ResultSet arr22rs = arr22.getResultSet(); + assertTrue(arr22rs.next()); + assertEquals(1, arr22rs.getInt(1)); + assertEquals(3, arr22rs.getInt(2)); + assertFalse(arr22rs.next()); + arr22rs.close(); + assertFalse(arr2rs.next()); + arr2rs.close(); + assertTrue(arrrs.next()); + + // {{4},{5}} + assertEquals(3, arrrs.getInt(1)); + Array arr3 = arrrs.getArray(2); + ResultSet arr3rs = arr3.getResultSet(); + assertTrue(arr3rs.next()); + // {4} + assertEquals(1, arr3rs.getInt(1)); + Array arr31 = arr3rs.getArray(2); + ResultSet arr31rs = arr31.getResultSet(); + assertTrue(arr31rs.next()); + assertEquals(1, arr31rs.getInt(1)); + assertEquals(4, arr31rs.getInt(2)); + assertFalse(arr31rs.next()); + arr31rs.close(); + assertTrue(arr3rs.next()); + // {5} + assertEquals(2, arr3rs.getInt(1)); + Array arr32 = arr3rs.getArray(2); + ResultSet arr32rs = arr32.getResultSet(); + assertTrue(arr32rs.next()); + assertEquals(1, arr32rs.getInt(1)); + assertEquals(5, arr32rs.getInt(2)); + assertFalse(arr32rs.next()); + arr32rs.close(); + assertFalse(arr3rs.next()); + arr3rs.close(); + assertFalse(arrrs.next()); arrrs.close(); rs.close(); @@ -473,7 +699,7 @@ public void testRetrieveResultSets() throws SQLException { public void testSetArray() throws SQLException { Statement stmt = conn.createStatement(); ResultSet arrRS = stmt.executeQuery("SELECT '{1,2,3}'::int4[]"); - Assert.assertTrue(arrRS.next()); + assertTrue(arrRS.next()); Array arr = arrRS.getArray(1); arrRS.close(); stmt.close(); @@ -496,16 +722,16 @@ public void testSetArray() throws SQLException { while (rs.next()) { resultCount++; Array result = rs.getArray(1); - Assert.assertEquals(Types.INTEGER, result.getBaseType()); - Assert.assertEquals("int4", result.getBaseTypeName()); + assertEquals(Types.INTEGER, result.getBaseType()); + assertEquals("int4", result.getBaseTypeName()); Integer[] intarr = (Integer[]) result.getArray(); - Assert.assertEquals(3, intarr.length); - Assert.assertEquals(1, intarr[0].intValue()); - Assert.assertEquals(2, intarr[1].intValue()); - Assert.assertEquals(3, intarr[2].intValue()); + assertEquals(3, intarr.length); + assertEquals(1, intarr[0].intValue()); + assertEquals(2, intarr[1].intValue()); + assertEquals(3, intarr[2].intValue()); } - Assert.assertEquals(3, resultCount); + assertEquals(3, resultCount); } /** @@ -519,12 +745,12 @@ public void testNonStandardBounds() throws SQLException { stmt.executeUpdate("INSERT INTO arrtest (intarr) VALUES ('{1,2,3}')"); stmt.executeUpdate("UPDATE arrtest SET intarr[0] = 0"); ResultSet rs = stmt.executeQuery("SELECT intarr FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array result = rs.getArray(1); Integer[] intarr = (Integer[]) result.getArray(); - Assert.assertEquals(4, intarr.length); + assertEquals(4, intarr.length); for (int i = 0; i < intarr.length; i++) { - Assert.assertEquals(i, intarr[i].intValue()); + assertEquals(i, intarr[i].intValue()); } } @@ -532,18 +758,18 @@ public void testNonStandardBounds() throws SQLException { public void testMultiDimensionalArray() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '{{1,2},{3,4}}'::int[]"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); Object[] oa = (Object[]) arr.getArray(); - Assert.assertEquals(2, oa.length); + assertEquals(2, oa.length); Integer[] i0 = (Integer[]) oa[0]; - Assert.assertEquals(2, i0.length); - Assert.assertEquals(1, i0[0].intValue()); - Assert.assertEquals(2, i0[1].intValue()); + assertEquals(2, i0.length); + assertEquals(1, i0[0].intValue()); + assertEquals(2, i0[1].intValue()); Integer[] i1 = (Integer[]) oa[1]; - Assert.assertEquals(2, i1.length); - Assert.assertEquals(3, i1[0].intValue()); - Assert.assertEquals(4, i1[1].intValue()); + assertEquals(2, i1.length); + assertEquals(3, i1[0].intValue()); + assertEquals(4, i1[1].intValue()); rs.close(); stmt.close(); } @@ -552,19 +778,19 @@ public void testMultiDimensionalArray() throws SQLException { public void testNullValues() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT ARRAY[1,NULL,3]"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); Integer[] i = (Integer[]) arr.getArray(); - Assert.assertEquals(3, i.length); - Assert.assertEquals(1, i[0].intValue()); - Assert.assertNull(i[1]); - Assert.assertEquals(3, i[2].intValue()); + assertEquals(3, i.length); + assertEquals(1, i[0].intValue()); + assertNull(i[1]); + assertEquals(3, i[2].intValue()); } @Test public void testNullFieldString() throws SQLException { Array arr = new PgArray((BaseConnection) conn, 1, (String) null); - Assert.assertNull(arr.toString()); + assertNull(arr.toString()); } @Test @@ -592,10 +818,10 @@ public void testStringEscaping() throws SQLException { stmt.executeUpdate("INSERT INTO arrtest VALUES (NULL, NULL, '" + TestUtil.escapeString(conn, stringArray) + "')"); final ResultSet rs = stmt.executeQuery("SELECT strarr FROM arrtest"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); - Assert.assertEquals(Types.VARCHAR, arr.getBaseType()); + assertEquals(Types.VARCHAR, arr.getBaseType()); String[] strarr = (String[]) arr.getArray(); assertEquals(5, strarr.length); assertEquals("f'a", strarr[0]); @@ -630,54 +856,54 @@ public void testUnknownArrayType() throws SQLException { ResultSet rs = stmt.executeQuery("SELECT relacl FROM pg_class WHERE relacl IS NOT NULL LIMIT 1"); ResultSetMetaData rsmd = rs.getMetaData(); - Assert.assertEquals(Types.ARRAY, rsmd.getColumnType(1)); + assertEquals(Types.ARRAY, rsmd.getColumnType(1)); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); - Assert.assertEquals("aclitem", arr.getBaseTypeName()); + assertEquals("aclitem", arr.getBaseTypeName()); ResultSet arrRS = arr.getResultSet(); ResultSetMetaData arrRSMD = arrRS.getMetaData(); - Assert.assertEquals("aclitem", arrRSMD.getColumnTypeName(2)); + assertEquals("aclitem", arrRSMD.getColumnTypeName(2)); } @Test public void testRecursiveResultSets() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '{{1,2},{3,4}}'::int[]"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); ResultSet arrRS = arr.getResultSet(); ResultSetMetaData arrRSMD = arrRS.getMetaData(); - Assert.assertEquals(Types.ARRAY, arrRSMD.getColumnType(2)); - Assert.assertEquals("_int4", arrRSMD.getColumnTypeName(2)); + assertEquals(Types.ARRAY, arrRSMD.getColumnType(2)); + assertEquals("_int4", arrRSMD.getColumnTypeName(2)); - Assert.assertTrue(arrRS.next()); - Assert.assertEquals(1, arrRS.getInt(1)); + assertTrue(arrRS.next()); + assertEquals(1, arrRS.getInt(1)); Array a1 = arrRS.getArray(2); ResultSet a1RS = a1.getResultSet(); ResultSetMetaData a1RSMD = a1RS.getMetaData(); - Assert.assertEquals(Types.INTEGER, a1RSMD.getColumnType(2)); - Assert.assertEquals("int4", a1RSMD.getColumnTypeName(2)); - - Assert.assertTrue(a1RS.next()); - Assert.assertEquals(1, a1RS.getInt(2)); - Assert.assertTrue(a1RS.next()); - Assert.assertEquals(2, a1RS.getInt(2)); - Assert.assertTrue(!a1RS.next()); + assertEquals(Types.INTEGER, a1RSMD.getColumnType(2)); + assertEquals("int4", a1RSMD.getColumnTypeName(2)); + + assertTrue(a1RS.next()); + assertEquals(1, a1RS.getInt(2)); + assertTrue(a1RS.next()); + assertEquals(2, a1RS.getInt(2)); + assertFalse(a1RS.next()); a1RS.close(); - Assert.assertTrue(arrRS.next()); - Assert.assertEquals(2, arrRS.getInt(1)); + assertTrue(arrRS.next()); + assertEquals(2, arrRS.getInt(1)); Array a2 = arrRS.getArray(2); ResultSet a2RS = a2.getResultSet(); - Assert.assertTrue(a2RS.next()); - Assert.assertEquals(3, a2RS.getInt(2)); - Assert.assertTrue(a2RS.next()); - Assert.assertEquals(4, a2RS.getInt(2)); - Assert.assertTrue(!a2RS.next()); + assertTrue(a2RS.next()); + assertEquals(3, a2RS.getInt(2)); + assertTrue(a2RS.next()); + assertEquals(4, a2RS.getInt(2)); + assertFalse(a2RS.next()); a2RS.close(); arrRS.close(); @@ -689,13 +915,13 @@ public void testRecursiveResultSets() throws SQLException { public void testNullString() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '{a,NULL}'::text[]"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); String[] s = (String[]) arr.getArray(); - Assert.assertEquals(2, s.length); - Assert.assertEquals("a", s[0]); - Assert.assertNull(s[1]); + assertEquals(2, s.length); + assertEquals("a", s[0]); + assertNull(s[1]); } @Test @@ -707,41 +933,41 @@ public void testEscaping() throws SQLException { sql += "'{{c\\\\\"d, ''}, {\"\\\\\\\\\",\"''\"}}'::text[]"; ResultSet rs = stmt.executeQuery(sql); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); String[][] s = (String[][]) arr.getArray(); - Assert.assertEquals("c\"d", s[0][0]); - Assert.assertEquals("'", s[0][1]); - Assert.assertEquals("\\", s[1][0]); - Assert.assertEquals("'", s[1][1]); + assertEquals("c\"d", s[0][0]); + assertEquals("'", s[0][1]); + assertEquals("\\", s[1][0]); + assertEquals("'", s[1][1]); ResultSet arrRS = arr.getResultSet(); - Assert.assertTrue(arrRS.next()); + assertTrue(arrRS.next()); Array a1 = arrRS.getArray(2); ResultSet rs1 = a1.getResultSet(); - Assert.assertTrue(rs1.next()); - Assert.assertEquals("c\"d", rs1.getString(2)); - Assert.assertTrue(rs1.next()); - Assert.assertEquals("'", rs1.getString(2)); - Assert.assertTrue(!rs1.next()); + assertTrue(rs1.next()); + assertEquals("c\"d", rs1.getString(2)); + assertTrue(rs1.next()); + assertEquals("'", rs1.getString(2)); + assertFalse(rs1.next()); - Assert.assertTrue(arrRS.next()); + assertTrue(arrRS.next()); Array a2 = arrRS.getArray(2); ResultSet rs2 = a2.getResultSet(); - Assert.assertTrue(rs2.next()); - Assert.assertEquals("\\", rs2.getString(2)); - Assert.assertTrue(rs2.next()); - Assert.assertEquals("'", rs2.getString(2)); - Assert.assertTrue(!rs2.next()); + assertTrue(rs2.next()); + assertEquals("\\", rs2.getString(2)); + assertTrue(rs2.next()); + assertEquals("'", rs2.getString(2)); + assertFalse(rs2.next()); } @Test public void testWriteMultiDimensional() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '{{1,2},{3,4}}'::int[]"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); rs.close(); stmt.close(); @@ -753,14 +979,14 @@ public void testWriteMultiDimensional() throws SQLException { PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setArray(1, arr); rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); arr = rs.getArray(1); Integer[][] i = (Integer[][]) arr.getArray(); - Assert.assertEquals(1, i[0][0].intValue()); - Assert.assertEquals(2, i[0][1].intValue()); - Assert.assertEquals(3, i[1][0].intValue()); - Assert.assertEquals(4, i[1][1].intValue()); + assertEquals(1, i[0][0].intValue()); + assertEquals(2, i[0][1].intValue()); + assertEquals(3, i[1][0].intValue()); + assertEquals(4, i[1][1].intValue()); } /* @@ -771,24 +997,24 @@ public void testWriteMultiDimensional() throws SQLException { public void testNonStandardDelimiter() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '{(3,4),(1,2);(7,8),(5,6)}'::box[]"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); ResultSet arrRS = arr.getResultSet(); - Assert.assertTrue(arrRS.next()); + assertTrue(arrRS.next()); PGbox box1 = (PGbox) arrRS.getObject(2); PGpoint p1 = box1.point[0]; - Assert.assertEquals(3, p1.x, 0.001); - Assert.assertEquals(4, p1.y, 0.001); + assertEquals(3, p1.x, 0.001); + assertEquals(4, p1.y, 0.001); - Assert.assertTrue(arrRS.next()); + assertTrue(arrRS.next()); PGbox box2 = (PGbox) arrRS.getObject(2); PGpoint p2 = box2.point[1]; - Assert.assertEquals(5, p2.x, 0.001); - Assert.assertEquals(6, p2.y, 0.001); + assertEquals(5, p2.x, 0.001); + assertEquals(6, p2.y, 0.001); - Assert.assertTrue(!arrRS.next()); + assertFalse(arrRS.next()); } @Test @@ -800,8 +1026,10 @@ public void testEmptyArray() throws SQLException { Array array = rs.getArray(1); if (!rs.wasNull()) { ResultSet ars = array.getResultSet(); - Assert.assertEquals("get columntype should return Types.INTEGER", java.sql.Types.INTEGER, - ars.getMetaData().getColumnType(1)); + assertEquals( + Types.INTEGER, + ars.getMetaData().getColumnType(1), + "get columntype should return Types.INTEGER"); } } } diff --git a/src/test/java/org/postgresql/test/jdbc2/AutoRollbackTest.java b/src/test/java/org/postgresql/test/jdbc2/AutoRollbackTest.java new file mode 100644 index 0000000..457fc2f --- /dev/null +++ b/src/test/java/org/postgresql/test/jdbc2/AutoRollbackTest.java @@ -0,0 +1,404 @@ +/* + * Copyright (c) 2004, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.postgresql.PGConnection; +import org.postgresql.PGProperty; +import org.postgresql.core.BaseConnection; +import org.postgresql.core.ResultHandler; +import org.postgresql.core.ServerVersion; +import org.postgresql.core.TransactionState; +import org.postgresql.jdbc.AutoSave; +import org.postgresql.jdbc.PgConnection; +import org.postgresql.jdbc.PreferQueryMode; +import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLState; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicInteger; + +@ParameterizedClass +@MethodSource("data") +public class AutoRollbackTest extends BaseTest4 { + private static final AtomicInteger counter = new AtomicInteger(); + + private enum CleanSavePoint { + TRUE, + FALSE + } + + private enum FailMode { + /** + * Executes "select 1/0" and causes transaction failure (if autocommit=no). + * Mitigation: "autosave=always" or "autocommit=true" + */ + SELECT, + /** + * Executes "alter table rollbacktest", thus it breaks a prepared select over that table. + * Mitigation: "autosave in (always, conservative)" + */ + ALTER, + /** + * Executes DEALLOCATE ALL. + * Mitigation: + * 1) QueryExecutor tracks "DEALLOCATE ALL" responses ({@see org.postgresql.core.QueryExecutor#setFlushCacheOnDeallocate(boolean)} + * 2) QueryExecutor tracks "prepared statement name is invalid" and unprepared relevant statements ({@link org.postgresql.core.v3.QueryExecutorImpl#processResults(ResultHandler, int)} + * 3) "autosave in (always, conservative)" + * 4) Non-transactional cases are healed by retry (when no transaction present, just retry is possible) + */ + DEALLOCATE, + /** + * Executes DISCARD ALL. + * Mitigation: the same as for {@link #DEALLOCATE} + */ + DISCARD, + /** + * Executes "insert ... select 1/0" in a batch statement, thus causing the transaction to fail. + */ + INSERT_BATCH, + } + + private enum ReturnColumns { + EXACT("a, str"), + STAR("*"); + + public final String cols; + + ReturnColumns(String cols) { + this.cols = cols; + } + } + + private enum TestStatement { + SELECT("select ${cols} from rollbacktest", 0), + WITH_INSERT_SELECT( + "with x as (insert into rollbacktest(a, str) values(43, 'abc') returning ${cols})" + + "select * from x", 1); + + private final String sql; + private final int rowsInserted; + + TestStatement(String sql, int rowsInserted) { + this.sql = sql; + this.rowsInserted = rowsInserted; + } + + public String getSql(ReturnColumns cols) { + return sql.replace("${cols}", cols.cols); + } + } + + private static final EnumSet DEALLOCATES = + EnumSet.of(FailMode.DEALLOCATE, FailMode.DISCARD); + + private static final EnumSet TRANS_KILLERS = + EnumSet.of(FailMode.SELECT, FailMode.INSERT_BATCH); + + private enum ContinueMode { + COMMIT, + IS_VALID, + SELECT, + } + + private final AutoSave autoSave; + private final CleanSavePoint cleanSavePoint; + private final AutoCommit autoCommit; + private final FailMode failMode; + private final ContinueMode continueMode; + private final boolean flushCacheOnDeallocate; + private final boolean trans; + private final TestStatement testSql; + private final ReturnColumns cols; + + public AutoRollbackTest(AutoSave autoSave, CleanSavePoint cleanSavePoint, AutoCommit autoCommit, + FailMode failMode, ContinueMode continueMode, boolean flushCacheOnDeallocate, + boolean trans, TestStatement testSql, ReturnColumns cols) { + this.autoSave = autoSave; + this.cleanSavePoint = cleanSavePoint; + this.autoCommit = autoCommit; + this.failMode = failMode; + this.continueMode = continueMode; + this.flushCacheOnDeallocate = flushCacheOnDeallocate; + this.trans = trans; + this.testSql = testSql; + this.cols = cols; + } + + @Override + public void setUp() throws Exception { + super.setUp(); + if (testSql == TestStatement.WITH_INSERT_SELECT) { + assumeMinimumServerVersion(ServerVersion.v9_1); + } + + TestUtil.createTable(con, "rollbacktest", "a int, str text"); + con.setAutoCommit(autoCommit == AutoCommit.YES); + BaseConnection baseConnection = con.unwrap(BaseConnection.class); + baseConnection.setFlushCacheOnDeallocate(flushCacheOnDeallocate); + assumeTrue(failMode != FailMode.DEALLOCATE || TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3), "DEALLOCATE ALL requires PostgreSQL 8.3+"); + assumeTrue(failMode != FailMode.DISCARD || TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3), "DISCARD ALL requires PostgreSQL 8.3+"); + assumeTrue(failMode != FailMode.ALTER || TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3), "Plan invalidation on table redefinition requires PostgreSQL 8.3+"); + } + + @Override + public void tearDown() throws SQLException { + try { + con.setAutoCommit(true); + TestUtil.dropTable(con, "rollbacktest"); + } catch (Exception e) { + e.printStackTrace(); + } + super.tearDown(); + } + + @Override + protected void updateProperties(Properties props) { + super.updateProperties(props); + PGProperty.AUTOSAVE.set(props, autoSave.value()); + PGProperty.CLEANUP_SAVEPOINTS.set(props, cleanSavePoint.toString()); + PGProperty.PREPARE_THRESHOLD.set(props, 1); + } + + public static Iterable data() { + Collection ids = new ArrayList<>(); + boolean[] booleans = new boolean[]{true, false}; + for (AutoSave autoSave : AutoSave.values()) { + for (CleanSavePoint cleanSavePoint:CleanSavePoint.values()) { + for (AutoCommit autoCommit : AutoCommit.values()) { + for (FailMode failMode : FailMode.values()) { + // ERROR: DISCARD ALL cannot run inside a transaction block + if (failMode == FailMode.DISCARD && autoCommit == AutoCommit.NO) { + continue; + } + for (ContinueMode continueMode : ContinueMode.values()) { + if (failMode == FailMode.ALTER && continueMode != ContinueMode.SELECT) { + continue; + } + for (boolean flushCacheOnDeallocate : booleans) { + if (!(flushCacheOnDeallocate || DEALLOCATES.contains(failMode))) { + continue; + } + + for (boolean trans : new boolean[]{true, false}) { + // continueMode would commit, and autoCommit=YES would commit, + // so it does not make sense to test trans=true for those cases + if (trans && (continueMode == ContinueMode.COMMIT + || autoCommit != AutoCommit.NO)) { + continue; + } + for (TestStatement statement : TestStatement.values()) { + for (ReturnColumns columns : ReturnColumns.values()) { + ids.add(new Object[]{autoSave, cleanSavePoint, autoCommit, failMode, continueMode, + flushCacheOnDeallocate, trans, statement, columns}); + } + } + } + } + } + } + } + } + } + return ids; + } + + @Test + public void run() throws SQLException { + if (continueMode == ContinueMode.IS_VALID) { + // make "isValid" a server-prepared statement + con.isValid(4); + } else if (continueMode == ContinueMode.COMMIT) { + doCommit(); + } else if (continueMode == ContinueMode.SELECT) { + assertRows("rollbacktest", 0); + } + + Statement statement = con.createStatement(); + statement.executeUpdate("insert into rollbacktest(a, str) values (0, 'test')"); + int rowsExpected = 1; + + PreparedStatement ps = con.prepareStatement(testSql.getSql(cols)); + // Server-prepare the testSql + ps.executeQuery().close(); + rowsExpected += testSql.rowsInserted; + + if (trans) { + statement.executeUpdate("update rollbacktest set a=a"); + } + + switch (failMode) { + case SELECT: + try { + statement.execute("select 1/0"); + fail("select 1/0 should fail"); + } catch (SQLException e) { + assertEquals(PSQLState.DIVISION_BY_ZERO.getState(), e.getSQLState(), "division by zero expected"); + } + break; + case DEALLOCATE: + statement.executeUpdate("DEALLOCATE ALL"); + break; + case DISCARD: + statement.executeUpdate("DISCARD ALL"); + break; + case ALTER: + statement.executeUpdate("alter table rollbacktest add q int"); + break; + case INSERT_BATCH: + try { + statement.addBatch("insert into rollbacktest(a, str) values (1/0, 'test')"); + statement.executeBatch(); + fail("select 1/0 should fail"); + } catch (SQLException e) { + assertEquals(PSQLState.DIVISION_BY_ZERO.getState(), e.getSQLState(), "division by zero expected"); + } + break; + default: + fail("Fail mode " + failMode + " is not implemented"); + } + + PgConnection pgConnection = con.unwrap(PgConnection.class); + if (autoSave == AutoSave.ALWAYS) { + assertNotEquals(TransactionState.FAILED, pgConnection.getTransactionState(), "In AutoSave.ALWAYS, transaction should not fail"); + } + if (autoCommit == AutoCommit.NO) { + assertNotEquals(TransactionState.IDLE, pgConnection.getTransactionState(), "AutoCommit == NO, thus transaction should be active (open or failed)"); + } + statement.close(); + + switch (continueMode) { + case COMMIT: + try { + doCommit(); + // No assert here: commit should always succeed with exception of well known failure cases in catch + } catch (SQLException e) { + if (!flushCacheOnDeallocate && DEALLOCATES.contains(failMode) + && autoSave == AutoSave.NEVER) { + assertEquals( + PSQLState.INVALID_SQL_STATEMENT_NAME.getState(), + e.getSQLState(), + () -> "flushCacheOnDeallocate is disabled, thus " + failMode + " should cause " + + "'prepared statement \"...\" does not exist'" + + " error message is " + e.getMessage()); + return; + } + throw e; + } + return; + case IS_VALID: + assertTrue(con.isValid(4), "Connection.isValid should return true unless the connection is closed as .isValid should use simple queries only which should not fail in face of prepared statement failures"); + return; + default: + break; + } + + try { + // Try execute server-prepared statement again + ps.executeQuery().close(); + rowsExpected += testSql.rowsInserted; + executeSqlSuccess(); + } catch (SQLException e) { + if (autoSave != AutoSave.ALWAYS && TRANS_KILLERS.contains(failMode) && autoCommit == AutoCommit.NO) { + assertEquals(PSQLState.IN_FAILED_SQL_TRANSACTION.getState(), e.getSQLState(), "AutoSave==" + autoSave + ", thus statements should fail with 'current transaction is aborted...', " + + " error message is " + e.getMessage()); + return; + } + + if (autoSave == AutoSave.NEVER && autoCommit == AutoCommit.NO) { + if (DEALLOCATES.contains(failMode) && !flushCacheOnDeallocate) { + assertEquals(PSQLState.INVALID_SQL_STATEMENT_NAME.getState(), e.getSQLState(), "flushCacheOnDeallocate is disabled, thus " + failMode + " should cause 'prepared statement \"...\" does not exist'" + + " error message is " + e.getMessage()); + } else if (failMode == FailMode.ALTER) { + assertEquals(PSQLState.NOT_IMPLEMENTED.getState(), e.getSQLState(), "AutoSave==NEVER, autocommit=NO, thus ALTER TABLE causes SELECT * to fail with " + + "'cached plan must not change result type', " + + " error message is " + e.getMessage()); + } else { + throw e; + } + } else { + throw e; + } + } + + try { + assertRows("rollbacktest", rowsExpected); + executeSqlSuccess(); + } catch (SQLException e) { + if (autoSave == AutoSave.NEVER && autoCommit == AutoCommit.NO) { + if (DEALLOCATES.contains(failMode) && !flushCacheOnDeallocate + || failMode == FailMode.ALTER) { + // The above statement failed with "prepared statement does not exist", thus subsequent one should fail with + // transaction aborted. + assertEquals(PSQLState.IN_FAILED_SQL_TRANSACTION.getState(), e.getSQLState(), "AutoSave==NEVER, thus statements should fail with 'current transaction is aborted...', " + + " error message is " + e.getMessage()); + } + } else { + throw e; + } + } + } + + private void executeSqlSuccess() throws SQLException { + if (autoCommit == AutoCommit.YES) { + // in autocommit everything should just work + } else if (TRANS_KILLERS.contains(failMode)) { + if (autoSave != AutoSave.ALWAYS) { + fail("autosave= " + autoSave + " != ALWAYS, thus the transaction should be killed"); + } + } else if (DEALLOCATES.contains(failMode)) { + if (autoSave == AutoSave.NEVER && !flushCacheOnDeallocate + && con.unwrap(PGConnection.class).getPreferQueryMode() != PreferQueryMode.SIMPLE) { + fail("flushCacheOnDeallocate == false, thus DEALLOCATE ALL should kill the transaction"); + } + } else if (failMode == FailMode.ALTER) { + if (autoSave == AutoSave.NEVER + && con.unwrap(PGConnection.class).getPreferQueryMode() != PreferQueryMode.SIMPLE + && cols == ReturnColumns.STAR) { + fail("autosave=NEVER, thus the transaction should be killed"); + } + } else { + fail("It is not specified why the test should pass, thus marking a failure"); + } + } + + private void assertRows(String tableName, int nrows) throws SQLException { + Statement st = con.createStatement(); + ResultSet rs = st.executeQuery("select count(*) from " + tableName); + rs.next(); + assertEquals(nrows, rs.getInt(1), "Table " + tableName); + } + + private void doCommit() throws SQLException { + // Such a dance is required since "commit" checks "current transaction state", + // so we need some pending changes, so "commit" query would be sent to the database + if (con.getAutoCommit()) { + con.setAutoCommit(false); + Statement st = con.createStatement(); + st.executeUpdate( + "insert into rollbacktest(a, str) values (42, '" + System.currentTimeMillis() + "," + counter.getAndIncrement() + "')"); + st.close(); + } + con.commit(); + con.setAutoCommit(autoCommit == AutoCommit.YES); + } +} diff --git a/src/test/java/org/postgresql/test/jdbc2/BaseTest4.java b/src/test/java/org/postgresql/test/jdbc2/BaseTest4.java index 5640799..7290d45 100644 --- a/src/test/java/org/postgresql/test/jdbc2/BaseTest4.java +++ b/src/test/java/org/postgresql/test/jdbc2/BaseTest4.java @@ -5,19 +5,27 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + import org.postgresql.PGConnection; import org.postgresql.PGProperty; +import org.postgresql.core.BaseConnection; +import org.postgresql.core.Oid; import org.postgresql.core.Version; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.TestUtil; +import org.postgresql.util.internal.Nullness; -import org.junit.After; -import org.junit.Assume; -import org.junit.Before; +// import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import java.sql.Connection; import java.sql.SQLException; +import java.util.Locale; import java.util.Properties; +import java.util.function.Supplier; public class BaseTest4 { @@ -34,36 +42,38 @@ public enum AutoCommit { } public enum StringType { - UNSPECIFIED, VARCHAR; + UNSPECIFIED, VARCHAR } - protected Connection con; - private BinaryMode binaryMode; - private ReWriteBatchedInserts reWriteBatchedInserts; - protected PreferQueryMode preferQueryMode; - private StringType stringType; + protected /* @Nullable */ Connection con; + protected /* @Nullable */ BinaryMode binaryMode; + private /* @Nullable */ ReWriteBatchedInserts reWriteBatchedInserts; + protected /* @Nullable */ PreferQueryMode preferQueryMode; + private /* @Nullable */ StringType stringType; protected void updateProperties(Properties props) { if (binaryMode == BinaryMode.FORCE) { forceBinary(props); } - if (reWriteBatchedInserts == ReWriteBatchedInserts.YES) { - PGProperty.REWRITE_BATCHED_INSERTS.set(props, true); + if (reWriteBatchedInserts != null) { + PGProperty.REWRITE_BATCHED_INSERTS.set(props, + reWriteBatchedInserts == ReWriteBatchedInserts.YES); } if (stringType != null) { - PGProperty.STRING_TYPE.set(props, stringType.name().toLowerCase()); + PGProperty.STRING_TYPE.set(props, stringType.name().toLowerCase(Locale.ROOT)); } } - protected void forceBinary(Properties props) { + protected static void forceBinary(Properties props) { PGProperty.PREPARE_THRESHOLD.set(props, -1); + PGProperty.BINARY_TRANSFER_ENABLE.set(props, Oid.BOOL); } public final void setBinaryMode(BinaryMode binaryMode) { this.binaryMode = binaryMode; } - public StringType getStringType() { + public /* @Nullable */ StringType getStringType() { return stringType; } @@ -76,8 +86,40 @@ public void setReWriteBatchedInserts( this.reWriteBatchedInserts = reWriteBatchedInserts; } - @Before - public void setUp() throws Exception { + /** + * Ensures {@link #setUp()} is called for each subclass. + * Note fore test implementation: override {@code setUp()} instead. + * JUnit 5 requires all overridden methods to be annotated with {@code @BeforeEach} which + * is hard to maintain. If the annotation is missing, then JUnit won't consider the method + * for setup. + * @throws Exception if setup fails + */ + @BeforeEach + final void beforeEach() throws Exception { + setUp(); + } + + /** + * Ensures {@link #tearDown()} is called for each subclass. + * Note fore test implementation: override {@code tearDown()} instead. + * JUnit 5 requires all overridden methods to be annotated with {@code @AfterEach} which + * is hard to maintain. If the annotation is missing, then JUnit won't consider the method + * for tear down. + * @throws Exception if setup fails + */ + @AfterEach + final void afterEach() throws Exception { + tearDown(); + } + + /** + * Prepares the test environment. + * Note: it might be worth moving "create table" statements to {@code @BeforeAll} methods, + * so the test creates the table only once, not once for every test method. + * Dot not add {@code @BeforeEach} annotation when overriding the method. + * @throws Exception if setup fails + */ + protected void setUp() throws Exception { Properties props = new Properties(); updateProperties(props); con = TestUtil.openDB(props); @@ -85,42 +127,66 @@ public void setUp() throws Exception { preferQueryMode = pg == null ? PreferQueryMode.EXTENDED : pg.getPreferQueryMode(); } - @After - public void tearDown() throws SQLException { + /** + * Cleans up the test environment. + * Dot not add {@code @AfterEach} annotation when overriding the method. + * @throws SQLException if teardown fails + */ + protected void tearDown() throws SQLException { TestUtil.closeDB(con); } - public void assumeByteaSupported() { - Assume.assumeTrue("bytea is not supported in simple protocol execution mode", - preferQueryMode.compareTo(PreferQueryMode.EXTENDED) >= 0); + public static void assumeCallableStatementsSupported(Connection con) throws SQLException { + PreferQueryMode preferQueryMode = con.unwrap(PGConnection.class).getPreferQueryMode(); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE, "callable statements are not fully supported in simple protocol execution mode"); } public void assumeCallableStatementsSupported() { - Assume.assumeTrue("callable statements are not fully supported in simple protocol execution mode", - preferQueryMode.compareTo(PreferQueryMode.EXTENDED) >= 0); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE, "callable statements are not fully supported in simple protocol execution mode"); } public void assumeBinaryModeRegular() { - Assume.assumeTrue(binaryMode == BinaryMode.REGULAR); + assumeTrue(binaryMode == BinaryMode.REGULAR); } public void assumeBinaryModeForce() { - Assume.assumeTrue(binaryMode == BinaryMode.FORCE); - Assume.assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE); + assumeTrue(binaryMode == BinaryMode.FORCE); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE); + } + + public void assumeNotSimpleQueryMode() { + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE); } /** * Shorthand for {@code Assume.assumeTrue(TestUtil.haveMinimumServerVersion(conn, version)}. */ public void assumeMinimumServerVersion(String message, Version version) throws SQLException { - Assume.assumeTrue(message, TestUtil.haveMinimumServerVersion(con, version)); + Connection con = Nullness.castNonNull(this.con, "connection"); + assumeTrue(TestUtil.haveMinimumServerVersion(con, version), message); } /** * Shorthand for {@code Assume.assumeTrue(TestUtil.haveMinimumServerVersion(conn, version)}. */ public void assumeMinimumServerVersion(Version version) throws SQLException { - Assume.assumeTrue(TestUtil.haveMinimumServerVersion(con, version)); + Connection con = Nullness.castNonNull(this.con, "connection"); + assumeTrue(TestUtil.haveMinimumServerVersion(con, version)); + } + + protected void assertBinaryForReceive(int oid, boolean expected, Supplier message) throws SQLException { + Connection con = Nullness.castNonNull(this.con, "connection"); + assertEquals( + expected, + con.unwrap(BaseConnection.class).getQueryExecutor().useBinaryForReceive(oid), + () -> message.get() + ", useBinaryForReceive(oid=" + oid + ")"); } + protected void assertBinaryForSend(int oid, boolean expected, Supplier message) throws SQLException { + Connection con = Nullness.castNonNull(this.con, "connection"); + assertEquals( + expected, + con.unwrap(BaseConnection.class).getQueryExecutor().useBinaryForSend(oid), + () -> message.get() + ", useBinaryForSend(oid=" + oid + ")"); + } } diff --git a/src/test/java/org/postgresql/test/jdbc2/BatchExecuteTest.java b/src/test/java/org/postgresql/test/jdbc2/BatchExecuteTest.java index af040d6..aafd997 100644 --- a/src/test/java/org/postgresql/test/jdbc2/BatchExecuteTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/BatchExecuteTest.java @@ -5,14 +5,20 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import org.postgresql.PGProperty; import org.postgresql.PGStatement; import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.BatchUpdateException; import java.sql.DatabaseMetaData; @@ -27,15 +33,14 @@ import java.util.Collection; import java.util.Properties; -/* - * TODO tests that can be added to this test case - SQLExceptions chained to a BatchUpdateException - * - test PreparedStatement as thoroughly as Statement - */ - -/* +/** * Test case for Statement.batchExecute() + * TODO tests that can be added to this test case + * - SQLExceptions chained to a BatchUpdateException + * - test PreparedStatement as thoroughly as Statement */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class BatchExecuteTest extends BaseTest4 { private boolean insertRewrite; @@ -45,9 +50,8 @@ public BatchExecuteTest(BinaryMode binaryMode, boolean insertRewrite) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}, insertRewrite = {1}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { for (boolean insertRewrite : new boolean[]{false, true}) { ids.add(new Object[]{binaryMode, insertRewrite}); @@ -99,7 +103,7 @@ public void tearDown() throws SQLException { @Test public void testSupportsBatchUpdates() throws Exception { DatabaseMetaData dbmd = con.getMetaData(); - Assert.assertTrue("Expected that Batch Updates are supported", dbmd.supportsBatchUpdates()); + assertTrue(dbmd.supportsBatchUpdates(), "Expected that Batch Updates are supported"); } @Test @@ -115,12 +119,12 @@ private void assertCol1HasValue(int expected) throws Exception { Statement getCol1 = con.createStatement(); try { ResultSet rs = getCol1.executeQuery("SELECT col1 FROM testbatch WHERE pk = 1"); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); int actual = rs.getInt("col1"); - Assert.assertEquals(expected, actual); - Assert.assertFalse(rs.next()); + assertEquals(expected, actual); + assertFalse(rs.next()); rs.close(); } finally { @@ -133,12 +137,12 @@ public void testExecuteEmptyBatch() throws Exception { Statement stmt = con.createStatement(); try { int[] updateCount = stmt.executeBatch(); - Assert.assertEquals(0, updateCount.length); + assertEquals(0, updateCount.length); stmt.addBatch("UPDATE testbatch SET col1 = col1 + 1 WHERE pk = 1"); stmt.clearBatch(); updateCount = stmt.executeBatch(); - Assert.assertEquals(0, updateCount.length); + assertEquals(0, updateCount.length); stmt.close(); } finally { TestUtil.closeQuietly(stmt); @@ -150,7 +154,7 @@ public void testExecuteEmptyPreparedBatch() throws Exception { PreparedStatement ps = con.prepareStatement("UPDATE testbatch SET col1 = col1 + 1 WHERE pk = 1"); try { int[] updateCount = ps.executeBatch(); - Assert.assertEquals("Empty batch should update empty result", 0, updateCount.length); + assertEquals(0, updateCount.length, "Empty batch should update empty result"); } finally { TestUtil.closeQuietly(ps); } @@ -199,7 +203,7 @@ public void testClearPreparedNoArgBatch() throws Exception { ps.addBatch(); ps.clearBatch(); int[] updateCount = ps.executeBatch(); - Assert.assertEquals("Empty batch should update empty result", 0, updateCount.length); + assertEquals(0, updateCount.length, "Empty batch should update empty result"); } finally { TestUtil.closeQuietly(ps); } @@ -226,15 +230,12 @@ public void testSelectInBatch() throws Exception { // There's no reason to Assert.fail int[] updateCounts = stmt.executeBatch(); - Assert.assertTrue("First update should succeed, thus updateCount should be 1 or SUCCESS_NO_INFO" - + ", actual value: " + updateCounts[0], - updateCounts[0] == 1 || updateCounts[0] == Statement.SUCCESS_NO_INFO); - Assert.assertTrue("For SELECT, number of modified rows should be either 0 or SUCCESS_NO_INFO" - + ", actual value: " + updateCounts[1], - updateCounts[1] == 0 || updateCounts[1] == Statement.SUCCESS_NO_INFO); - Assert.assertTrue("Second update should succeed, thus updateCount should be 1 or SUCCESS_NO_INFO" - + ", actual value: " + updateCounts[2], - updateCounts[2] == 1 || updateCounts[2] == Statement.SUCCESS_NO_INFO); + assertTrue(updateCounts[0] == 1 || updateCounts[0] == Statement.SUCCESS_NO_INFO, "First update should succeed, thus updateCount should be 1 or SUCCESS_NO_INFO" + + ", actual value: " + updateCounts[0]); + assertTrue(updateCounts[1] == 0 || updateCounts[1] == Statement.SUCCESS_NO_INFO, "For SELECT, number of modified rows should be either 0 or SUCCESS_NO_INFO" + + ", actual value: " + updateCounts[1]); + assertTrue(updateCounts[2] == 1 || updateCounts[2] == Statement.SUCCESS_NO_INFO, "Second update should succeed, thus updateCount should be 1 or SUCCESS_NO_INFO" + + ", actual value: " + updateCounts[2]); } finally { TestUtil.closeQuietly(stmt); } @@ -258,7 +259,7 @@ public void testSelectInBatchThrows() throws Exception { int[] updateCounts; try { updateCounts = stmt.executeBatch(); - Assert.fail("0/0 should throw BatchUpdateException"); + fail("0/0 should throw BatchUpdateException"); } catch (BatchUpdateException be) { updateCounts = be.getUpdateCounts(); } @@ -271,13 +272,10 @@ public void testSelectInBatchThrows() throws Exception { boolean firstOk = updateCounts[0] == 1 || updateCounts[0] == Statement.SUCCESS_NO_INFO; boolean lastOk = updateCounts[2] == 1 || updateCounts[2] == Statement.SUCCESS_NO_INFO; - Assert.assertEquals("testbatch.col1 should account +1 and +2 for the relevant successful rows: " - + Arrays.toString(updateCounts), - oldValue + (firstOk ? 1 : 0) + (lastOk ? 2 : 0), newValue); + assertEquals(oldValue + (firstOk ? 1 : 0) + (lastOk ? 2 : 0), newValue, "testbatch.col1 should account +1 and +2 for the relevant successful rows: " + + Arrays.toString(updateCounts)); - Assert.assertEquals("SELECT 0/0 should be marked as Statement.EXECUTE_FAILED", - Statement.EXECUTE_FAILED, - updateCounts[1]); + assertEquals(Statement.EXECUTE_FAILED, updateCounts[1], "SELECT 0/0 should be marked as Statement.EXECUTE_FAILED"); } finally { TestUtil.closeQuietly(stmt); @@ -305,8 +303,7 @@ public void testStringAddBatchOnPreparedStatement() throws Exception { try { pstmt.addBatch("UPDATE testbatch SET col1 = 3"); - Assert.fail( - "Should have thrown an exception about using the string addBatch method on a prepared statement."); + fail("Should have thrown an exception about using the string addBatch method on a prepared statement."); } catch (SQLException sqle) { } @@ -371,9 +368,9 @@ public void testTransactionalBehaviour() throws Exception { assertCol1HasValue(0); int[] updateCounts = stmt.executeBatch(); - Assert.assertEquals(2, updateCounts.length); - Assert.assertEquals(1, updateCounts[0]); - Assert.assertEquals(1, updateCounts[1]); + assertEquals(2, updateCounts.length); + assertEquals(1, updateCounts[0]); + assertEquals(1, updateCounts[1]); assertCol1HasValue(12); con.commit(); @@ -391,7 +388,7 @@ public void testWarningsAreCleared() throws SQLException { stmt.executeBatch(); // Execute an empty batch to clear warnings. stmt.executeBatch(); - Assert.assertNull(stmt.getWarnings()); + assertNull(stmt.getWarnings()); TestUtil.closeQuietly(stmt); } @@ -410,11 +407,11 @@ public void testBatchEscapeProcessing() throws SQLException { pstmt.close(); ResultSet rs = stmt.executeQuery("SELECT d FROM batchescape"); - Assert.assertTrue(rs.next()); - Assert.assertEquals("2007-11-20", rs.getString(1)); - Assert.assertTrue(rs.next()); - Assert.assertEquals("2007-11-20", rs.getString(1)); - Assert.assertTrue(!rs.next()); + assertTrue(rs.next()); + assertEquals("2007-11-20", rs.getString(1)); + assertTrue(rs.next()); + assertEquals("2007-11-20", rs.getString(1)); + assertTrue(!rs.next()); TestUtil.closeQuietly(stmt); } @@ -435,15 +432,15 @@ public void testBatchWithEmbeddedNulls() throws SQLException { pstmt.setString(1, "b"); pstmt.addBatch(); pstmt.executeBatch(); - Assert.fail("Should have thrown an exception."); + fail("Should have thrown an exception."); } catch (SQLException sqle) { con.rollback(); } pstmt.close(); ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM batchstring"); - Assert.assertTrue(rs.next()); - Assert.assertEquals(0, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(0, rs.getInt(1)); TestUtil.closeQuietly(stmt); } @@ -461,11 +458,11 @@ public void testMixedBatch() throws SQLException { st.addBatch("CREATE TEMPORARY TABLE waffles(sauce text)"); st.addBatch("INSERT INTO waffles(sauce) VALUES ('cream'), ('strawberry jam')"); int[] batchResult = st.executeBatch(); - Assert.assertEquals(1, batchResult[0]); - Assert.assertEquals(1, batchResult[1]); - Assert.assertEquals(1, batchResult[2]); - Assert.assertEquals(0, batchResult[3]); - Assert.assertEquals(2, batchResult[4]); + assertEquals(1, batchResult[0]); + assertEquals(1, batchResult[1]); + assertEquals(1, batchResult[2]); + assertEquals(0, batchResult[3]); + assertEquals(2, batchResult[4]); } catch (SQLException ex) { ex.getNextException().printStackTrace(); throw ex; @@ -500,7 +497,7 @@ public void testMixedBatch() throws SQLException { * The second bind identifies the object class as String so it calls setString internally. This * sets the type to 1043 (varchar). * - * The third and subsequent binds, whether null or non-null, will get type 1043, becaues there's + * The third and subsequent binds, whether null or non-null, will get type 1043, because there's * logic to avoid overwriting a known parameter type with the unknown type oid. This is why the * issue can only occur when null is the first entry. * @@ -549,9 +546,9 @@ public void testBatchReturningMixedNulls() throws SQLException { ResultSet rs = st.getGeneratedKeys(); for (int i = 1; i <= testData.length; i++) { rs.next(); - Assert.assertEquals(i, rs.getInt(1)); + assertEquals(i, rs.getInt(1)); } - Assert.assertTrue(!rs.next()); + assertTrue(!rs.next()); } catch (SQLException ex) { ex.getNextException().printStackTrace(); throw ex; @@ -594,7 +591,7 @@ public void testBatchWithAlternatingAndUnknownTypes6() throws SQLException { } /** - *

          This one is reproduced in regular (non-force binary) mode.

          + * This one is reproduced in regular (non-force binary) mode. * *

          As of 9.4.1208 the following tests fail: * BatchExecuteTest.testBatchWithAlternatingAndUnknownTypes3 @@ -619,7 +616,7 @@ public void testBatchWithAlternatingAndUnknownTypesN(int numPreliminaryInserts) ps.executeBatch(); } - ps.setObject(1, new Double(43)); + ps.setObject(1, 43.0); ps.setObject(2, new Date(43)); ps.addBatch(); ps.setNull(1, Types.SMALLINT); @@ -627,7 +624,7 @@ public void testBatchWithAlternatingAndUnknownTypesN(int numPreliminaryInserts) ps.addBatch(); ps.executeBatch(); - ps.setObject(1, new Double(45)); + ps.setObject(1, 45.0); ps.setObject(2, new Date(45)); // <-- this causes "oid of bind unknown, send Describe" ps.addBatch(); ps.setNull(1, Types.SMALLINT); @@ -642,7 +639,7 @@ public void testBatchWithAlternatingAndUnknownTypesN(int numPreliminaryInserts) // This execution with (double, unknown) passes isPreparedForTypes check, and causes // the failure - ps.setObject(1, new Double(47)); + ps.setObject(1, 47.0); ps.setObject(2, new Date(47)); ps.addBatch(); ps.executeBatch(); @@ -1188,7 +1185,7 @@ public void testSmallBatchUpdateFailureSimple() throws SQLException { int[] batchResult; try { batchResult = batchSt.executeBatch(); - Assert.fail("Expecting BatchUpdateException as key-2 is duplicated in batchUpdCnt.id. " + fail("Expecting BatchUpdateException as key-2 is duplicated in batchUpdCnt.id. " + " executeBatch returned " + Arrays.toString(batchResult)); } catch (BatchUpdateException ex) { batchResult = ex.getUpdateCounts(); @@ -1199,23 +1196,20 @@ public void testSmallBatchUpdateFailureSimple() throws SQLException { int newCount = getBatchUpdCount(); if (newCount == 2) { // key-1 did succeed - Assert.assertTrue("batchResult[0] should be 1 or SUCCESS_NO_INFO since 'key-1' was inserted," - + " actual result is " + Arrays.toString(batchResult), - batchResult[0] == 1 || batchResult[0] == Statement.SUCCESS_NO_INFO); + assertTrue(batchResult[0] == 1 || batchResult[0] == Statement.SUCCESS_NO_INFO, "batchResult[0] should be 1 or SUCCESS_NO_INFO since 'key-1' was inserted," + + " actual result is " + Arrays.toString(batchResult)); } else { - Assert.assertTrue("batchResult[0] should be 0 or EXECUTE_FAILED since 'key-1' was NOT inserted," - + " actual result is " + Arrays.toString(batchResult), - batchResult[0] == 0 || batchResult[0] == Statement.EXECUTE_FAILED); + assertTrue(batchResult[0] == 0 || batchResult[0] == Statement.EXECUTE_FAILED, "batchResult[0] should be 0 or EXECUTE_FAILED since 'key-1' was NOT inserted," + + " actual result is " + Arrays.toString(batchResult)); } - Assert.assertEquals("'key-2' insertion should have Assert.failed", - Statement.EXECUTE_FAILED, batchResult[1]); + assertEquals(Statement.EXECUTE_FAILED, batchResult[1], "'key-2' insertion should have Assert.failed"); } private int getBatchUpdCount() throws SQLException { PreparedStatement ps = con.prepareStatement("select count(*) from batchUpdCnt"); ResultSet rs = ps.executeQuery(); - Assert.assertTrue("count(*) must return 1 row", rs.next()); + assertTrue(rs.next(), "count(*) must return 1 row"); return rs.getInt(1); } @@ -1240,13 +1234,13 @@ public void testBatchWithRepeatedInsertStatement() throws SQLException { pstmt.addBatch();//statement two int[] outcome = pstmt.executeBatch(); - Assert.assertNotNull(outcome); - Assert.assertEquals(2, outcome.length); + assertNotNull(outcome); + assertEquals(2, outcome.length); int rowsInserted = insertRewrite ? Statement.SUCCESS_NO_INFO : 1; - Assert.assertEquals(rowsInserted, outcome[0]); - Assert.assertEquals(rowsInserted, outcome[1]); + assertEquals(rowsInserted, outcome[0]); + assertEquals(rowsInserted, outcome[1]); } catch (SQLException sqle) { - Assert.fail("Failed to execute two statements added to a batch. Reason:" + sqle.getMessage()); + fail("Failed to execute two statements added to a batch. Reason:" + sqle.getMessage()); } finally { TestUtil.closeQuietly(pstmt); } @@ -1269,11 +1263,11 @@ public void testBatchWithMultiInsert() throws SQLException { pstmt.setInt(4, 2); pstmt.addBatch();//statement one int[] outcome = pstmt.executeBatch(); - Assert.assertNotNull(outcome); - Assert.assertEquals(1, outcome.length); - Assert.assertEquals(2, outcome[0]); + assertNotNull(outcome); + assertEquals(1, outcome.length); + assertEquals(2, outcome[0]); } catch (SQLException sqle) { - Assert.fail("Failed to execute two statements added to a batch. Reason:" + sqle.getMessage()); + fail("Failed to execute two statements added to a batch. Reason:" + sqle.getMessage()); } finally { TestUtil.closeQuietly(pstmt); } @@ -1302,12 +1296,9 @@ public void testBatchWithTwoMultiInsertStatements() throws SQLException { pstmt.addBatch(); //statement two int[] outcome = pstmt.executeBatch(); int rowsInserted = insertRewrite ? Statement.SUCCESS_NO_INFO : 2; - Assert.assertEquals( - "Inserting two multi-valued statements with two rows each. Expecting {2, 2} rows inserted (or SUCCESS_NO_INFO)", - Arrays.toString(new int[] { rowsInserted, rowsInserted }), - Arrays.toString(outcome)); + assertEquals(Arrays.toString(new int[]{rowsInserted, rowsInserted}), Arrays.toString(outcome), "Inserting two multi-valued statements with two rows each. Expecting {2, 2} rows inserted (or SUCCESS_NO_INFO)"); } catch (SQLException sqle) { - Assert.fail("Failed to execute two statements added to a batch. Reason:" + sqle.getMessage()); + fail("Failed to execute two statements added to a batch. Reason:" + sqle.getMessage()); } finally { TestUtil.closeQuietly(pstmt); } @@ -1332,10 +1323,7 @@ public static void assertBatchResult(String message, int[] expected, int[] actua if (hasChanges) { message += ", original expectation: " + Arrays.toString(expected); } - Assert.assertEquals( - message, - Arrays.toString(clone), - Arrays.toString(actual)); + assertEquals(Arrays.toString(clone), Arrays.toString(actual), message); } @Test @@ -1349,9 +1337,7 @@ public void testServerPrepareMultipleRows() throws SQLException { ps.addBatch(); } int[] actual = ps.executeBatch(); - Assert.assertTrue( - "More than 1 row is inserted via executeBatch, it should lead to multiple server statements, thus the statements should be server-prepared", - ((PGStatement) ps).isUseServerPrepare()); + assertTrue(((PGStatement) ps).isUseServerPrepare(), "More than 1 row is inserted via executeBatch, it should lead to multiple server statements, thus the statements should be server-prepared"); assertBatchResult("3 rows inserted via batch", new int[]{1, 1, 1}, actual); } finally { TestUtil.closeQuietly(ps); @@ -1368,15 +1354,13 @@ public void testNoServerPrepareOneRow() throws SQLException { int[] actual = ps.executeBatch(); int prepareThreshold = ((PGStatement) ps).getPrepareThreshold(); if (prepareThreshold == 1) { - Assert.assertTrue( - "prepareThreshold=" + prepareThreshold - + " thus the statement should be server-prepared", - ((PGStatement) ps).isUseServerPrepare()); + assertTrue(((PGStatement) ps).isUseServerPrepare(), + () -> "prepareThreshold=" + prepareThreshold + + " thus the statement should be server-prepared"); } else { - Assert.assertFalse( - "Just one row inserted via executeBatch, prepareThreshold=" + prepareThreshold - + " thus the statement should not be server-prepared", - ((PGStatement) ps).isUseServerPrepare()); + assertFalse(((PGStatement) ps).isUseServerPrepare(), + () -> "Just one row inserted via executeBatch, prepareThreshold=" + prepareThreshold + + " thus the statement should not be server-prepared"); } assertBatchResult("1 rows inserted via batch", new int[]{1}, actual); } finally { diff --git a/src/test/java/org/postgresql/test/jdbc2/BatchFailureTest.java b/src/test/java/org/postgresql/test/jdbc2/BatchFailureTest.java index 812bd3a..97bf03e 100644 --- a/src/test/java/org/postgresql/test/jdbc2/BatchFailureTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/BatchFailureTest.java @@ -5,13 +5,16 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.BatchUpdateException; import java.sql.Connection; @@ -27,7 +30,8 @@ import java.util.Properties; import java.util.Set; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class BatchFailureTest extends BaseTest4 { private final BatchType batchType; private final AutoCommit autoCommit; @@ -123,10 +127,9 @@ public BatchFailureTest(BatchType batchType, AutoCommit autoCommit, this.insertRewrite = insertRewrite; } - @Parameterized.Parameters(name = "{index}: batchTest(mode={2}, position={3}, autoCommit={1}, batchType={0}, generateKeys={1}, binary={4}, insertRewrite={5})") public static Iterable data() { - Collection ids = new ArrayList(); - boolean[] booleans = new boolean[] {true, false}; + Collection ids = new ArrayList<>(); + boolean[] booleans = new boolean[]{true, false}; for (BatchType batchType : BatchType.values()) { for (FailMode failMode : FailMode.values()) { if (!failMode.supports(batchType)) { @@ -205,20 +208,18 @@ public void run() throws SQLException { minBatchResults = pos; } - List keys = new ArrayList(); + List keys = new ArrayList<>(); int[] batchResult; int expectedRows = 1; try { batchResult = statement.executeBatch(); - Assert.assertTrue("Expecting BatchUpdateException due to " + failMode - + ", executeBatch returned " + Arrays.toString(batchResult), - failPosition == FailPosition.NONE); + assertTrue(failPosition == FailPosition.NONE, "Expecting BatchUpdateException due to " + failMode + + ", executeBatch returned " + Arrays.toString(batchResult)); expectedRows = pos + 1; // +1 since key-2 is already in the DB } catch (BatchUpdateException ex) { batchResult = ex.getUpdateCounts(); - Assert.assertTrue("Should not fail since fail mode should be " + failMode - + ", executeBatch returned " + Arrays.toString(batchResult), - failPosition != FailPosition.NONE); + assertTrue(failPosition != FailPosition.NONE, "Should not fail since fail mode should be " + failMode + + ", executeBatch returned " + Arrays.toString(batchResult)); for (int i : batchResult) { if (i != Statement.EXECUTE_FAILED) { @@ -226,11 +227,10 @@ public void run() throws SQLException { } } - Assert.assertTrue("Batch should fail at row " + minBatchResults + assertTrue(batchResult.length >= minBatchResults, "Batch should fail at row " + minBatchResults + ", thus at least " + minBatchResults + " items should be returned, actual result is " + batchResult.length + " items, " - + Arrays.toString(batchResult), - batchResult.length >= minBatchResults); + + Arrays.toString(batchResult)); } finally { if (batchType == BatchType.PREPARED_WITH_GENERATED) { ResultSet rs = statement.getGeneratedKeys(); @@ -246,29 +246,37 @@ public void run() throws SQLException { } int finalCount = getBatchUpdCount(); - Assert.assertEquals( - "Number of new rows in batchUpdCnt should match number of non-error batchResult items" - + Arrays.toString(batchResult), - expectedRows - 1, finalCount - 1); + int[] batchResultForAssertion = batchResult; + assertEquals( + expectedRows - 1, + finalCount - 1, + () -> "Number of new rows in batchUpdCnt should match number of non-error batchResult items" + + Arrays.toString(batchResultForAssertion)); if (batchType != BatchType.PREPARED_WITH_GENERATED) { return; } if (finalCount > 1) { - Assert.assertFalse((finalCount - 1) + " rows were inserted, thus expecting generated keys", - keys.isEmpty()); + assertFalse( + keys.isEmpty(), + () -> (finalCount - 1) + " rows were inserted, thus expecting generated keys"); } - Set uniqueKeys = new HashSet(keys); - Assert.assertEquals("Generated keys should be unique: " + keys, keys.size(), uniqueKeys.size()); - Assert.assertEquals("Number of generated keys should match the number of inserted rows" + keys, - keys.size(), finalCount - 1); + Set uniqueKeys = new HashSet<>(keys); + assertEquals( + keys.size(), + uniqueKeys.size(), + () -> "Generated keys should be unique: " + keys); + assertEquals( + keys.size(), + finalCount - 1, + () -> "Number of generated keys should match the number of inserted rows" + keys); } private int getBatchUpdCount() throws SQLException { PreparedStatement ps = con.prepareStatement("select count(*) from batchUpdCnt"); ResultSet rs = ps.executeQuery(); - Assert.assertTrue("count(*) must return 1 row", rs.next()); + assertTrue(rs.next(), "count(*) must return 1 row"); return rs.getInt(1); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/BatchedInsertReWriteEnabledTest.java b/src/test/java/org/postgresql/test/jdbc2/BatchedInsertReWriteEnabledTest.java index 19aa1dd..2db9945 100644 --- a/src/test/java/org/postgresql/test/jdbc2/BatchedInsertReWriteEnabledTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/BatchedInsertReWriteEnabledTest.java @@ -5,15 +5,20 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.PGProperty; +import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.BatchUpdateException; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; @@ -22,7 +27,8 @@ import java.util.Collection; import java.util.Properties; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class BatchedInsertReWriteEnabledTest extends BaseTest4 { private final AutoCommit autoCommit; @@ -32,9 +38,8 @@ public BatchedInsertReWriteEnabledTest(AutoCommit autoCommit, setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "{index}: autoCommit={0}, binary={1}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (AutoCommit autoCommit : AutoCommit.values()) { for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{autoCommit, binaryMode}); @@ -43,20 +48,28 @@ public static Iterable data() { return ids; } + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "testbatch", "pk INTEGER, col1 VARCHAR, col2 INTEGER"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "testbatch"); + } + } + /* Set up the fixture for this testcase: a connection to a database with a table for this test. */ public void setUp() throws Exception { super.setUp(); - TestUtil.createTable(con, "testbatch", "pk INTEGER, col1 VARCHAR, col2 INTEGER"); + TestUtil.execute(con, "TRUNCATE testbatch"); con.setAutoCommit(autoCommit == AutoCommit.YES); } - // Tear down the fixture for this test case. - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "testbatch"); - super.tearDown(); - } - @Override protected void updateProperties(Properties props) { super.updateProperties(props); @@ -195,7 +208,7 @@ private void simpleRewriteBatch(String values, String suffix) clean.execute(); clean.close(); - pstmt = con.prepareStatement("INSERT INTO testbatch " + values + "(?,?,?)" + suffix); + pstmt = con.prepareStatement("INSERT INTO testbatch " + values + "(?,?,?)" + suffix); pstmt.setInt(1, 1); pstmt.setString(2, "a"); pstmt.setInt(3, 2); @@ -376,23 +389,29 @@ public void testReWriteDisabledForPlainBatch() throws Exception { stmt = con.createStatement(); stmt.addBatch("INSERT INTO testbatch VALUES (100,'a',200);"); stmt.addBatch("INSERT INTO testbatch VALUES (300,'b',400);"); - Assert.assertEquals( + assertEquals( + Arrays.toString(new int[]{1, 1}), + Arrays.toString(stmt.executeBatch()), "Expected outcome not returned by batch execution. The driver" - + " allowed re-write in combination with plain statements.", - Arrays.toString(new int[]{1, 1}), Arrays.toString(stmt.executeBatch())); + + " allowed re-write in combination with plain statements."); } finally { TestUtil.closeQuietly(stmt); } } @Test - public void test32000Binds() throws Exception { - testNBinds(32000); + public void test32767Binds() throws Exception { + testNBinds(32767); + } + + @Test + public void test32768Binds() throws Exception { + testNBinds(32768); } @Test - public void test17000Binds() throws Exception { - testNBinds(17000); + public void test65535Binds() throws Exception { + testNBinds(65535); } public void testNBinds(int nBinds) throws Exception { @@ -411,11 +430,20 @@ public void testNBinds(int nBinds) throws Exception { } pstmt.addBatch(); } - Assert.assertEquals( - "Statement with " + nBinds - + " binds should not be batched => two executions with exactly one row inserted each", - Arrays.toString(new int[] { 1, 1 }), - Arrays.toString(pstmt.executeBatch())); + if (nBinds * 2 <= 65535 || preferQueryMode == PreferQueryMode.SIMPLE) { + assertEquals( + Arrays.toString(new int[]{Statement.SUCCESS_NO_INFO, Statement.SUCCESS_NO_INFO}), + Arrays.toString(pstmt.executeBatch()), + () -> "Insert with " + nBinds + " binds should be rewritten into multi-value insert" + + ", so expecting Statement.SUCCESS_NO_INFO == -2"); + } else { + assertEquals( + Arrays.toString(new int[]{1, 1}), + Arrays.toString(pstmt.executeBatch()), + () -> "Insert with " + nBinds + " binds can't be rewritten into multi-value insert" + + " since write format allows 65535 binds maximum" + + ", so expecting batch to be executed as individual statements"); + } } catch (BatchUpdateException be) { SQLException e = be; while (true) { diff --git a/src/test/java/org/postgresql/test/jdbc2/BlobTest.java b/src/test/java/org/postgresql/test/jdbc2/BlobTest.java index 3ce07a9..834563c 100644 --- a/src/test/java/org/postgresql/test/jdbc2/BlobTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/BlobTest.java @@ -5,19 +5,25 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import org.postgresql.PGConnection; import org.postgresql.core.ServerVersion; import org.postgresql.largeobject.LargeObject; import org.postgresql.largeobject.LargeObjectManager; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.io.InputStream; import java.io.OutputStream; @@ -29,237 +35,410 @@ import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; +import java.util.concurrent.ThreadLocalRandom; + +import javax.sql.rowset.serial.SerialBlob; +import javax.sql.rowset.serial.SerialClob; /** * Some simple tests based on problems reported by users. Hopefully these will help prevent previous * problems from re-occurring ;-) */ -public class BlobTest { +class BlobTest { + private static final String TEST_FILE = "/test-file.xml"; + private static final int LOOP = 0; // LargeObject API using loop private static final int NATIVE_STREAM = 1; // LargeObject API using OutputStream private Connection con; - @Before - public void setUp() throws Exception { + /* + Only do this once + */ + @BeforeAll + static void createLargeBlob() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "testblob", "id name,lo oid"); + con.setAutoCommit(false); + LargeObjectManager lom = ((PGConnection) con).getLargeObjectAPI(); + long oid = lom.createLO(LargeObjectManager.READWRITE); + LargeObject blob = lom.open(oid); + + byte[] buf = new byte[256]; + for (int i = 0; i < buf.length; i++) { + buf[i] = (byte) i; + } + // I want to create a large object + int i = 1024 / buf.length; + for (int j = i; j > 0; j--) { + blob.write(buf, 0, buf.length); + } + assertEquals(1024, blob.size()); + blob.close(); + try (PreparedStatement pstmt = con.prepareStatement("INSERT INTO testblob(id, lo) VALUES(?,?)")) { + pstmt.setString(1, "l1"); + pstmt.setLong(2, oid); + pstmt.executeUpdate(); + } + con.commit(); + } + } + + @AfterAll + static void cleanup() throws Exception { + try (Connection con = TestUtil.openDB()) { + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT lo_unlink(lo) FROM testblob where id = 'l1'"); + } finally { + TestUtil.dropTable(con, "testblob"); + } + } + } + + @BeforeEach + void setUp() throws Exception { con = TestUtil.openDB(); - TestUtil.createTable(con, "testblob", "id name,lo oid"); con.setAutoCommit(false); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { con.setAutoCommit(true); - try { - Statement stmt = con.createStatement(); - try { - stmt.execute("SELECT lo_unlink(lo) FROM testblob"); - } finally { - try { - stmt.close(); - } catch (Exception e) { - } - } + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT lo_unlink(lo) FROM testblob where id != 'l1'"); + stmt.execute("delete from testblob where id != 'l1'"); } finally { - TestUtil.dropTable(con, "testblob"); TestUtil.closeDB(con); } } @Test - public void testSetNull() throws Exception { - PreparedStatement pstmt = con.prepareStatement("INSERT INTO testblob(lo) VALUES (?)"); + void setNull() throws Exception { + try (PreparedStatement pstmt = con.prepareStatement("INSERT INTO testblob(lo) VALUES (?)")) { - pstmt.setBlob(1, (Blob) null); - pstmt.executeUpdate(); + pstmt.setBlob(1, (Blob) null); + pstmt.executeUpdate(); - pstmt.setNull(1, Types.BLOB); - pstmt.executeUpdate(); + pstmt.setNull(1, Types.BLOB); + pstmt.executeUpdate(); - pstmt.setObject(1, null, Types.BLOB); - pstmt.executeUpdate(); + pstmt.setObject(1, null, Types.BLOB); + pstmt.executeUpdate(); - pstmt.setClob(1, (Clob) null); - pstmt.executeUpdate(); + pstmt.setClob(1, (Clob) null); + pstmt.executeUpdate(); - pstmt.setNull(1, Types.CLOB); - pstmt.executeUpdate(); + pstmt.setNull(1, Types.CLOB); + pstmt.executeUpdate(); - pstmt.setObject(1, null, Types.CLOB); - pstmt.executeUpdate(); + pstmt.setObject(1, null, Types.CLOB); + pstmt.executeUpdate(); + } } @Test - public void testSet() throws SQLException { - Statement stmt = con.createStatement(); - stmt.execute("INSERT INTO testblob(id,lo) VALUES ('1', lo_creat(-1))"); - ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob"); - assertTrue(rs.next()); - - PreparedStatement pstmt = con.prepareStatement("INSERT INTO testblob(id, lo) VALUES(?,?)"); - - Blob blob = rs.getBlob(1); - pstmt.setString(1, "setObjectTypeBlob"); - pstmt.setObject(2, blob, Types.BLOB); - assertEquals(1, pstmt.executeUpdate()); - - blob = rs.getBlob(1); - pstmt.setString(1, "setObjectBlob"); - pstmt.setObject(2, blob); - assertEquals(1, pstmt.executeUpdate()); - - blob = rs.getBlob(1); - pstmt.setString(1, "setBlob"); - pstmt.setBlob(2, blob); - assertEquals(1, pstmt.executeUpdate()); - - Clob clob = rs.getClob(1); - pstmt.setString(1, "setObjectTypeClob"); - pstmt.setObject(2, clob, Types.CLOB); - assertEquals(1, pstmt.executeUpdate()); - - clob = rs.getClob(1); - pstmt.setString(1, "setObjectClob"); - pstmt.setObject(2, clob); - assertEquals(1, pstmt.executeUpdate()); - - clob = rs.getClob(1); - pstmt.setString(1, "setClob"); - pstmt.setClob(2, clob); - assertEquals(1, pstmt.executeUpdate()); + void set() throws SQLException { + try (Statement stmt = con.createStatement()) { + stmt.execute("INSERT INTO testblob(id,lo) VALUES ('1', lo_creat(-1))"); + ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob where id = '1'"); + assertTrue(rs.next()); + + PreparedStatement pstmt = con.prepareStatement("INSERT INTO testblob(id, lo) VALUES(?,?)"); + + Blob blob = rs.getBlob(1); + pstmt.setString(1, "setObjectTypeBlob"); + pstmt.setObject(2, blob, Types.BLOB); + assertEquals(1, pstmt.executeUpdate()); + + blob = rs.getBlob(1); + pstmt.setString(1, "setObjectBlob"); + pstmt.setObject(2, blob); + assertEquals(1, pstmt.executeUpdate()); + + blob = rs.getBlob(1); + pstmt.setString(1, "setBlob"); + pstmt.setBlob(2, blob); + assertEquals(1, pstmt.executeUpdate()); + + Clob clob = rs.getClob(1); + pstmt.setString(1, "setObjectTypeClob"); + pstmt.setObject(2, clob, Types.CLOB); + assertEquals(1, pstmt.executeUpdate()); + + clob = rs.getClob(1); + pstmt.setString(1, "setObjectClob"); + pstmt.setObject(2, clob); + assertEquals(1, pstmt.executeUpdate()); + + clob = rs.getClob(1); + pstmt.setString(1, "setClob"); + pstmt.setClob(2, clob); + assertEquals(1, pstmt.executeUpdate()); + } + } + + @ValueSource(ints = {0, 1, 13, 123423}) + @ParameterizedTest + void setBlobMinusOneLengthAndGivenByteContents(int length) throws Exception { + byte[] contents = new byte[length]; + ThreadLocalRandom.current().nextBytes(contents); + try (PreparedStatement pstmt = + con.prepareStatement("INSERT INTO testblob(id, lo) VALUES (?, ?)")) { + pstmt.setString(1, "setBlobNegativeLength"); + pstmt.setBlob(2, new SerialBlob(contents) { + @Override + public long length() { + return -1; + } + }); + pstmt.executeUpdate(); + } + // Read the value back and compare with original + try (Statement stmt = con.createStatement()) { + try (ResultSet rs = + stmt.executeQuery("SELECT lo FROM testblob where id = 'setBlobNegativeLength'")) { + assertTrue(rs.next(), "rs.next()"); + Blob blob = rs.getBlob(1); + assertArrayEquals( + contents, + blob.getBytes(1, contents.length), + "blob.getBytes(1, contents.length)" + ); + assertArrayEquals( + contents, + blob.getBytes(1, contents.length * 2), + "blob.getBytes(1, contents.length * 2)" + ); + assertEquals(contents.length, blob.length(), "blob.length()"); + } + } + } + + @ValueSource(ints = {0, 1, 13, 123423}) + @ParameterizedTest + void setClobMinusOneLengthAndGivenByteContents(int length) throws Exception { + char[] contents = new char[length]; + for (int i = 0; i < contents.length; i++) { + contents[i] = (char) ('a' + ThreadLocalRandom.current().nextInt(26)); + } + try (PreparedStatement pstmt = + con.prepareStatement("INSERT INTO testblob(id, lo) VALUES (?, ?)")) { + pstmt.setString(1, "setClobNegativeLength"); + pstmt.setClob(2, new SerialClob(contents) { + @Override + public long length() { + return -1; + } + }); + pstmt.executeUpdate(); + } + // Read the value back and compare with original + try (Statement stmt = con.createStatement()) { + try (ResultSet rs = + stmt.executeQuery("SELECT lo FROM testblob where id = 'setClobNegativeLength'")) { + assertTrue(rs.next(), "rs.next()"); + Clob clob = rs.getClob(1); + assertEquals( + new String(contents), + clob.getSubString(1, contents.length), + "clob.getSubString(1, contents.length)" + ); + assertEquals( + new String(contents), + clob.getSubString(1, contents.length * 2), + "clob.getSubString(1, contents.length * 2)" + ); + assertEquals(contents.length, clob.length(), "clob.length()"); + } + } } /* * Tests one method of uploading a blob to the database */ @Test - public void testUploadBlob_LOOP() throws Exception { - assertTrue(uploadFile("/test-file.xml", LOOP) > 0); + void uploadBlob_LOOP() throws Exception { + assertTrue(uploadFile(TEST_FILE, LOOP) > 0); // Now compare the blob & the file. Note this actually tests the // InputStream implementation! - assertTrue(compareBlobsLOAPI()); - assertTrue(compareBlobs()); - assertTrue(compareClobs()); + assertTrue(compareBlobsLOAPI(TEST_FILE)); + assertTrue(compareBlobs(TEST_FILE)); + assertTrue(compareClobs(TEST_FILE)); } /* * Tests one method of uploading a blob to the database */ @Test - public void testUploadBlob_NATIVE() throws Exception { - assertTrue(uploadFile("/test-file.xml", NATIVE_STREAM) > 0); + void uploadBlob_NATIVE() throws Exception { + assertTrue(uploadFile(TEST_FILE, NATIVE_STREAM) > 0); // Now compare the blob & the file. Note this actually tests the // InputStream implementation! - assertTrue(compareBlobs()); + assertTrue(compareBlobs(TEST_FILE)); } @Test - public void testMarkResetStream() throws Exception { - assertTrue(uploadFile("/test-file.xml", NATIVE_STREAM) > 0); + void markResetStream() throws Exception { + assertTrue(uploadFile(TEST_FILE, NATIVE_STREAM) > 0); + + try (Statement stmt = con.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob where id = '/test-file.xml'")) { + assertTrue(rs.next()); + + LargeObjectManager lom = ((PGConnection) con).getLargeObjectAPI(); + + long oid = rs.getLong(1); + LargeObject blob = lom.open(oid); + InputStream bis = blob.getInputStream(); + + assertEquals('<', bis.read()); + bis.mark(4); + assertEquals('?', bis.read()); + assertEquals('x', bis.read()); + assertEquals('m', bis.read()); + assertEquals('l', bis.read()); + bis.reset(); + assertEquals('?', bis.read()); + } + } + } - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob"); - assertTrue(rs.next()); + @Test + void getBytesOffset() throws Exception { + assertTrue(uploadFile(TEST_FILE, NATIVE_STREAM) > 0); - LargeObjectManager lom = ((org.postgresql.PGConnection) con).getLargeObjectAPI(); + try (Statement stmt = con.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob where id = '/test-file.xml'")) { - long oid = rs.getLong(1); - LargeObject blob = lom.open(oid); - InputStream bis = blob.getInputStream(); - - assertEquals('<', bis.read()); - bis.mark(4); - assertEquals('?', bis.read()); - assertEquals('x', bis.read()); - assertEquals('m', bis.read()); - assertEquals('l', bis.read()); - bis.reset(); - assertEquals('?', bis.read()); - } + assertTrue(rs.next()); - @Test - public void testGetBytesOffset() throws Exception { - assertTrue(uploadFile("/test-file.xml", NATIVE_STREAM) > 0); - - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob"); - assertTrue(rs.next()); - - Blob lob = rs.getBlob(1); - byte[] data = lob.getBytes(2, 4); - assertEquals(data.length, 4); - assertEquals(data[0], '?'); - assertEquals(data[1], 'x'); - assertEquals(data[2], 'm'); - assertEquals(data[3], 'l'); + Blob lob = rs.getBlob(1); + byte[] data = lob.getBytes(2, 4); + assertEquals(4, data.length); + assertEquals('?', data[0]); + assertEquals('x', data[1]); + assertEquals('m', data[2]); + assertEquals('l', data[3]); + } + } } @Test - public void testMultipleStreams() throws Exception { - assertTrue(uploadFile("/test-file.xml", NATIVE_STREAM) > 0); - - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob"); - assertTrue(rs.next()); - - Blob lob = rs.getBlob(1); - byte[] data = new byte[2]; - - InputStream is = lob.getBinaryStream(); - assertEquals(data.length, is.read(data)); - assertEquals(data[0], '<'); - assertEquals(data[1], '?'); - is.close(); - - is = lob.getBinaryStream(); - assertEquals(data.length, is.read(data)); - assertEquals(data[0], '<'); - assertEquals(data[1], '?'); - is.close(); + void multipleStreams() throws Exception { + assertTrue(uploadFile(TEST_FILE, NATIVE_STREAM) > 0); + + try (Statement stmt = con.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob where id = '/test-file.xml'")) { + assertTrue(rs.next()); + + Blob lob = rs.getBlob(1); + byte[] data = new byte[2]; + + InputStream is = lob.getBinaryStream(); + assertEquals(data.length, is.read(data)); + assertEquals('<', data[0]); + assertEquals('?', data[1]); + is.close(); + + is = lob.getBinaryStream(); + assertEquals(data.length, is.read(data)); + assertEquals('<', data[0]); + assertEquals('?', data[1]); + is.close(); + } + } } @Test - public void testParallelStreams() throws Exception { - assertTrue(uploadFile("/test-file.xml", NATIVE_STREAM) > 0); - - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob"); - assertTrue(rs.next()); - - Blob lob = rs.getBlob(1); - InputStream is1 = lob.getBinaryStream(); - InputStream is2 = lob.getBinaryStream(); - - while (true) { - int i1 = is1.read(); - int i2 = is2.read(); - assertEquals(i1, i2); - if (i1 == -1) { - break; + void parallelStreams() throws Exception { + assertTrue(uploadFile(TEST_FILE, NATIVE_STREAM) > 0); + + try (Statement stmt = con.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob where id = '/test-file.xml'")) { + assertTrue(rs.next()); + + Blob lob = rs.getBlob(1); + InputStream is1 = lob.getBinaryStream(); + InputStream is2 = lob.getBinaryStream(); + + while (true) { + int i1 = is1.read(); + int i2 = is2.read(); + assertEquals(i1, i2); + if (i1 == -1) { + break; + } + } + + is1.close(); + is2.close(); } } - - is1.close(); - is2.close(); } @Test - public void testLargeLargeObject() throws Exception { + void largeLargeObject() throws Exception { if (!TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_3)) { return; } - Statement stmt = con.createStatement(); - stmt.execute("INSERT INTO testblob(id,lo) VALUES ('1', lo_creat(-1))"); - ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob"); - assertTrue(rs.next()); + try (Statement stmt = con.createStatement()) { + stmt.execute("INSERT INTO testblob(id,lo) VALUES ('1', lo_creat(-1))"); + try (ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob where id ='1'")) { + assertTrue(rs.next()); - Blob lob = rs.getBlob(1); - long length = ((long) Integer.MAX_VALUE) + 1024; - lob.truncate(length); - assertEquals(length, lob.length()); + Blob lob = rs.getBlob(1); + long length = ((long) Integer.MAX_VALUE) + 1024; + lob.truncate(length); + assertEquals(length, lob.length()); + } + } + } + + @Test + void largeObjectRead() throws Exception { + con.setAutoCommit(false); + LargeObjectManager lom = ((PGConnection) con).getLargeObjectAPI(); + try (Statement stmt = con.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob where id='l1'")) { + assertTrue(rs.next()); + + long oid = rs.getLong(1); + try (InputStream lois = lom.open(oid).getInputStream()) { + // read half of the data with read + for (int j = 0; j < 512; j++) { + lois.read(); + } + byte[] buf2 = new byte[512]; + lois.read(buf2, 0, 512); + } + } + } + con.commit(); + } + + @Test + void largeObjectRead1() throws Exception { + con.setAutoCommit(false); + LargeObjectManager lom = ((PGConnection) con).getLargeObjectAPI(); + try (Statement stmt = con.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob where id='l1'")) { + assertTrue(rs.next()); + + long oid = rs.getLong(1); + try (InputStream lois = lom.open(oid).getInputStream(512, 1024)) { + // read one byte + assertEquals(0, lois.read()); + byte[] buf2 = new byte[1024]; + int bytesRead = lois.read(buf2, 0, buf2.length); + assertEquals(1023, bytesRead); + assertEquals(1, buf2[0]); + } + } + } + con.commit(); } /* @@ -267,7 +446,7 @@ public void testLargeLargeObject() throws Exception { * works, and we can use it as a base to test the new methods. */ private long uploadFile(String file, int method) throws Exception { - LargeObjectManager lom = ((org.postgresql.PGConnection) con).getLargeObjectAPI(); + LargeObjectManager lom = ((PGConnection) con).getLargeObjectAPI(); InputStream fis = getClass().getResourceAsStream(file); @@ -319,125 +498,123 @@ private long uploadFile(String file, int method) throws Exception { * Helper - compares the blobs in a table with a local file. Note this uses the postgresql * specific Large Object API */ - private boolean compareBlobsLOAPI() throws Exception { + private boolean compareBlobsLOAPI(String id) throws Exception { boolean result = true; - LargeObjectManager lom = ((org.postgresql.PGConnection) con).getLargeObjectAPI(); - - Statement st = con.createStatement(); - ResultSet rs = st.executeQuery(TestUtil.selectSQL("testblob", "id,lo")); - assertNotNull(rs); - - while (rs.next()) { - String file = rs.getString(1); - long oid = rs.getLong(2); - - InputStream fis = getClass().getResourceAsStream(file); - LargeObject blob = lom.open(oid); - InputStream bis = blob.getInputStream(); - - int f = fis.read(); - int b = bis.read(); - int c = 0; - while (f >= 0 && b >= 0 & result) { - result = (f == b); - f = fis.read(); - b = bis.read(); - c++; - } - result = result && f == -1 && b == -1; - - if (!result) { - fail("Large Object API Blob compare failed at " + c + " of " + blob.size()); + LargeObjectManager lom = ((PGConnection) con).getLargeObjectAPI(); + + try (Statement st = con.createStatement()) { + try (ResultSet rs = st.executeQuery(TestUtil.selectSQL("testblob", "id,lo", "id = '" + id + "'"))) { + assertNotNull(rs); + + while (rs.next()) { + String file = rs.getString(1); + long oid = rs.getLong(2); + + InputStream fis = getClass().getResourceAsStream(file); + LargeObject blob = lom.open(oid); + InputStream bis = blob.getInputStream(); + + int f = fis.read(); + int b = bis.read(); + int c = 0; + while (f >= 0 && b >= 0 & result) { + result = f == b; + f = fis.read(); + b = bis.read(); + c++; + } + result = result && f == -1 && b == -1; + + if (!result) { + fail("Large Object API Blob compare failed at " + c + " of " + blob.size()); + } + + blob.close(); + fis.close(); + } } - - blob.close(); - fis.close(); } - rs.close(); - st.close(); - return result; } /* * Helper - compares the blobs in a table with a local file. This uses the jdbc java.sql.Blob api */ - private boolean compareBlobs() throws Exception { + private boolean compareBlobs(String id) throws Exception { boolean result = true; - Statement st = con.createStatement(); - ResultSet rs = st.executeQuery(TestUtil.selectSQL("testblob", "id,lo")); - assertNotNull(rs); - - while (rs.next()) { - String file = rs.getString(1); - Blob blob = rs.getBlob(2); - - InputStream fis = getClass().getResourceAsStream(file); - InputStream bis = blob.getBinaryStream(); - - int f = fis.read(); - int b = bis.read(); - int c = 0; - while (f >= 0 && b >= 0 & result) { - result = (f == b); - f = fis.read(); - b = bis.read(); - c++; - } - result = result && f == -1 && b == -1; - - if (!result) { - fail("JDBC API Blob compare failed at " + c + " of " + blob.length()); + try (Statement st = con.createStatement()) { + try (ResultSet rs = st.executeQuery(TestUtil.selectSQL("testblob", "id,lo", "id = '" + id + "'"))) { + assertNotNull(rs); + + while (rs.next()) { + String file = rs.getString(1); + Blob blob = rs.getBlob(2); + + InputStream fis = getClass().getResourceAsStream(file); + InputStream bis = blob.getBinaryStream(); + + int f = fis.read(); + int b = bis.read(); + int c = 0; + while (f >= 0 && b >= 0 & result) { + result = f == b; + f = fis.read(); + b = bis.read(); + c++; + } + result = result && f == -1 && b == -1; + + if (!result) { + fail("JDBC API Blob compare failed at " + c + " of " + blob.length()); + } + + bis.close(); + fis.close(); + } } - - bis.close(); - fis.close(); } - rs.close(); - st.close(); - return result; } /* * Helper - compares the clobs in a table with a local file. */ - private boolean compareClobs() throws Exception { + private boolean compareClobs(String id) throws Exception { boolean result = true; - Statement st = con.createStatement(); - ResultSet rs = st.executeQuery(TestUtil.selectSQL("testblob", "id,lo")); - assertNotNull(rs); - - while (rs.next()) { - String file = rs.getString(1); - Clob clob = rs.getClob(2); - - InputStream fis = getClass().getResourceAsStream(file); - InputStream bis = clob.getAsciiStream(); - - int f = fis.read(); - int b = bis.read(); - int c = 0; - while (f >= 0 && b >= 0 & result) { - result = (f == b); - f = fis.read(); - b = bis.read(); - c++; - } - result = result && f == -1 && b == -1; - - if (!result) { - fail("Clob compare failed at " + c + " of " + clob.length()); + try (Statement st = con.createStatement()) { + try (ResultSet rs = st.executeQuery(TestUtil.selectSQL("testblob", "id,lo", "id = '" + id + "'"))) { + assertNotNull(rs); + + while (rs.next()) { + String file = rs.getString(1); + Clob clob = rs.getClob(2); + + InputStream fis = getClass().getResourceAsStream(file); + InputStream bis = clob.getAsciiStream(); + + int f = fis.read(); + int b = bis.read(); + int c = 0; + while (f >= 0 && b >= 0 & result) { + result = f == b; + f = fis.read(); + b = bis.read(); + c++; + } + result = result && f == -1 && b == -1; + + if (!result) { + fail("Clob compare failed at " + c + " of " + clob.length()); + } + + bis.close(); + fis.close(); + } } - - bis.close(); - fis.close(); } - rs.close(); - st.close(); return result; } diff --git a/src/test/java/org/postgresql/test/jdbc2/BlobTransactionTest.java b/src/test/java/org/postgresql/test/jdbc2/BlobTransactionTest.java index 3d8621c..c27d11e 100644 --- a/src/test/java/org/postgresql/test/jdbc2/BlobTransactionTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/BlobTransactionTest.java @@ -5,14 +5,16 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; @@ -31,51 +33,49 @@ * Test that oid/lob are accessible in concurrent connection, in presence of the lo_manage trigger. * Require the lo module accessible in $libdir */ -public class BlobTransactionTest { +class BlobTransactionTest { private Connection con; private Connection con2; - @Before - public void setUp() throws Exception { - con = TestUtil.openDB(); - con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); - con2 = TestUtil.openDB(); - con2.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); - - TestUtil.createTable(con, "testblob", "id name,lo oid"); - - String sql; + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "testblob", "id name,lo oid"); + } /* * this would have to be executed using the postgres user in order to get access to a C function * */ - Connection privilegedCon = TestUtil.openPrivilegedDB(); - Statement st = privilegedCon.createStatement(); - try { - sql = - "CREATE OR REPLACE FUNCTION lo_manage() RETURNS pg_catalog.trigger AS '$libdir/lo' LANGUAGE C"; - st.executeUpdate(sql); - } finally { - st.close(); + try (Connection privilegedCon = TestUtil.openPrivilegedDB()) { + TestUtil.execute(privilegedCon, + "CREATE OR REPLACE FUNCTION lo_manage() RETURNS pg_catalog.trigger AS '$libdir/lo' LANGUAGE C"); + + TestUtil.execute(privilegedCon, + "CREATE TRIGGER testblob_lomanage BEFORE UPDATE OR DELETE ON testblob FOR EACH ROW EXECUTE PROCEDURE lo_manage(lo)"); } + } - st = privilegedCon.createStatement(); - try { - sql = - "CREATE TRIGGER testblob_lomanage BEFORE UPDATE OR DELETE ON testblob FOR EACH ROW EXECUTE PROCEDURE lo_manage(lo)"; - st.executeUpdate(sql); - } finally { - st.close(); + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "testblob"); } - TestUtil.closeDB(privilegedCon); + } + + @BeforeEach + void setUp() throws Exception { + con = TestUtil.openDB(); + con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + con2 = TestUtil.openDB(); + con2.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); con.setAutoCommit(false); con2.setAutoCommit(false); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(con2); con.setAutoCommit(true); @@ -90,20 +90,20 @@ public void tearDown() throws Exception { } } } finally { - TestUtil.dropTable(con, "testblob"); + TestUtil.execute(con, "TRUNCATE TABLE testblob"); TestUtil.closeDB(con); } } - private byte[] randomData() { + private static byte[] randomData() { byte[] data = new byte[64 * 1024 * 8]; - for (int i = 0; i < data.length; ++i) { + for (int i = 0; i < data.length; i++) { data[i] = (byte) (Math.random() * 256); } return data; } - private byte[] readInputStream(InputStream is) throws IOException { + private static byte[] readInputStream(InputStream is) throws IOException { byte[] result = new byte[1024]; int readPos = 0; int d; @@ -118,7 +118,7 @@ private byte[] readInputStream(InputStream is) throws IOException { } @Test - public void testConcurrentReplace() throws SQLException, IOException { + void concurrentReplace() throws SQLException, IOException { // Statement stmt = con.createStatement(); // stmt.execute("INSERT INTO testblob(id,lo) VALUES ('1', lo_creat(-1))"); // ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob"); @@ -153,7 +153,7 @@ public void testConcurrentReplace() throws SQLException, IOException { Blob initContentBlob = rs2.getBlob(1); byte[] initialContentReRead = readInputStream(initContentBlob.getBinaryStream()); assertEquals(initialContentReRead.length, initialData.length); - for (int i = 0; i < initialContentReRead.length; ++i) { + for (int i = 0; i < initialContentReRead.length; i++) { assertEquals(initialContentReRead[i], initialData[i]); } @@ -169,7 +169,7 @@ public void testConcurrentReplace() throws SQLException, IOException { initContentBlob = rs2.getBlob(1); initialContentReRead = readInputStream(initContentBlob.getBinaryStream()); assertEquals(initialContentReRead.length, initialData.length); - for (int i = 0; i < initialContentReRead.length; ++i) { + for (int i = 0; i < initialContentReRead.length; i++) { assertEquals(initialContentReRead[i], initialData[i]); } diff --git a/src/test/java/org/postgresql/test/jdbc2/CallableStmtTest.java b/src/test/java/org/postgresql/test/jdbc2/CallableStmtTest.java index 2113909..46b5e63 100644 --- a/src/test/java/org/postgresql/test/jdbc2/CallableStmtTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/CallableStmtTest.java @@ -5,93 +5,105 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.test.TestUtil; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import java.math.BigDecimal; import java.sql.Array; import java.sql.CallableStatement; +import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Statement; import java.sql.Types; -/* +/** * CallableStatement tests. * * @author Paul Bethe */ public class CallableStmtTest extends BaseTest4 { + @BeforeAll + public static void beforeClass() throws Exception { + try (Connection con = TestUtil.openDB()) { + assumeCallableStatementsSupported(con); + TestUtil.createTable(con, "int_table", "id int"); + try (Statement stmt = con.createStatement()) { + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getString (varchar) " + + "RETURNS varchar AS ' DECLARE inString alias for $1; begin " + + "return ''bob''; end; ' LANGUAGE plpgsql;"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getDouble (float) " + + "RETURNS float AS ' DECLARE inString alias for $1; begin " + + "return 42.42; end; ' LANGUAGE plpgsql;"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getVoid (float) " + + "RETURNS void AS ' DECLARE inString alias for $1; begin " + + " return; end; ' LANGUAGE plpgsql;"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getInt (int) RETURNS int " + + " AS 'DECLARE inString alias for $1; begin " + + "return 42; end;' LANGUAGE plpgsql;"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getShort (int2) RETURNS int2 " + + " AS 'DECLARE inString alias for $1; begin " + + "return 42; end;' LANGUAGE plpgsql;"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getNumeric (numeric) " + + "RETURNS numeric AS ' DECLARE inString alias for $1; " + + "begin return 42; end; ' LANGUAGE plpgsql;"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getNumericWithoutArg() " + + "RETURNS numeric AS ' " + + "begin return 42; end; ' LANGUAGE plpgsql;"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getarray() RETURNS int[] as " + + "'SELECT ''{1,2}''::int[];' LANGUAGE sql"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__raisenotice() RETURNS int as " + + "'BEGIN RAISE NOTICE ''hello''; RAISE NOTICE ''goodbye''; RETURN 1; END;' LANGUAGE plpgsql"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__insertInt(int) RETURNS int as " + + "'BEGIN INSERT INTO int_table(id) VALUES ($1); RETURN 1; END;' LANGUAGE plpgsql"); + } + } + } - @Override - public void setUp() throws Exception { - super.setUp(); - TestUtil.createTable(con, "int_table", "id int"); - Statement stmt = con.createStatement(); - stmt.execute( - "CREATE OR REPLACE FUNCTION testspg__getString (varchar) " - + "RETURNS varchar AS ' DECLARE inString alias for $1; begin " - + "return ''bob''; end; ' LANGUAGE plpgsql;"); - stmt.execute( - "CREATE OR REPLACE FUNCTION testspg__getDouble (float) " - + "RETURNS float AS ' DECLARE inString alias for $1; begin " - + "return 42.42; end; ' LANGUAGE plpgsql;"); - stmt.execute( - "CREATE OR REPLACE FUNCTION testspg__getVoid (float) " - + "RETURNS void AS ' DECLARE inString alias for $1; begin " - + " return; end; ' LANGUAGE plpgsql;"); - stmt.execute( - "CREATE OR REPLACE FUNCTION testspg__getInt (int) RETURNS int " - + " AS 'DECLARE inString alias for $1; begin " - + "return 42; end;' LANGUAGE plpgsql;"); - stmt.execute( - "CREATE OR REPLACE FUNCTION testspg__getShort (int2) RETURNS int2 " - + " AS 'DECLARE inString alias for $1; begin " - + "return 42; end;' LANGUAGE plpgsql;"); - stmt.execute( - "CREATE OR REPLACE FUNCTION testspg__getNumeric (numeric) " - + "RETURNS numeric AS ' DECLARE inString alias for $1; " - + "begin return 42; end; ' LANGUAGE plpgsql;"); - - stmt.execute( - "CREATE OR REPLACE FUNCTION testspg__getNumericWithoutArg() " - + "RETURNS numeric AS ' " - + "begin return 42; end; ' LANGUAGE plpgsql;"); - stmt.execute( - "CREATE OR REPLACE FUNCTION testspg__getarray() RETURNS int[] as " - + "'SELECT ''{1,2}''::int[];' LANGUAGE sql"); - stmt.execute( - "CREATE OR REPLACE FUNCTION testspg__raisenotice() RETURNS int as " - + "'BEGIN RAISE NOTICE ''hello''; RAISE NOTICE ''goodbye''; RETURN 1; END;' LANGUAGE plpgsql"); - stmt.execute( - "CREATE OR REPLACE FUNCTION testspg__insertInt(int) RETURNS int as " - + "'BEGIN INSERT INTO int_table(id) VALUES ($1); RETURN 1; END;' LANGUAGE plpgsql"); - stmt.close(); + @AfterAll + public static void afterClass() throws Exception { + try (Connection con = TestUtil.openDB(); + Statement stmt = con.createStatement()) { + stmt.execute("drop FUNCTION IF EXISTS testspg__getString (varchar);"); + stmt.execute("drop FUNCTION IF EXISTS testspg__getDouble (float);"); + stmt.execute("drop FUNCTION IF EXISTS testspg__getVoid(float);"); + stmt.execute("drop FUNCTION IF EXISTS testspg__getInt (int);"); + stmt.execute("drop FUNCTION IF EXISTS testspg__getShort(int2)"); + stmt.execute("drop FUNCTION IF EXISTS testspg__getNumeric (numeric);"); + stmt.execute("drop FUNCTION IF EXISTS testspg__getNumericWithoutArg ();"); + stmt.execute("DROP FUNCTION IF EXISTS testspg__getarray();"); + stmt.execute("DROP FUNCTION IF EXISTS testspg__raisenotice();"); + stmt.execute("DROP FUNCTION IF EXISTS testspg__insertInt(int);"); + TestUtil.dropTable(con, "int_table"); + } } @Override - public void tearDown() throws SQLException { - Statement stmt = con.createStatement(); - TestUtil.dropTable(con, "int_table"); - stmt.execute("drop FUNCTION testspg__getString (varchar);"); - stmt.execute("drop FUNCTION testspg__getDouble (float);"); - stmt.execute("drop FUNCTION testspg__getVoid(float);"); - stmt.execute("drop FUNCTION testspg__getInt (int);"); - stmt.execute("drop FUNCTION testspg__getShort(int2)"); - stmt.execute("drop FUNCTION testspg__getNumeric (numeric);"); - - stmt.execute("drop FUNCTION testspg__getNumericWithoutArg ();"); - stmt.execute("DROP FUNCTION testspg__getarray();"); - stmt.execute("DROP FUNCTION testspg__raisenotice();"); - stmt.execute("DROP FUNCTION testspg__insertInt(int);"); - super.tearDown(); + public void setUp() throws Exception { + super.setUp(); + TestUtil.execute(con, "TRUNCATE int_table"); } final String func = "{ ? = call "; @@ -99,7 +111,6 @@ public void tearDown() throws SQLException { @Test public void testGetUpdateCount() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall(func + pkgName + "getDouble (?) }"); call.setDouble(2, 3.04); call.registerOutParameter(1, Types.DOUBLE); @@ -118,17 +129,16 @@ public void testGetUpdateCount() throws SQLException { assertNotNull(rs); assertTrue(rs.next()); assertEquals(42.42, rs.getDouble(1), 0.00001); - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); assertEquals(-1, call.getUpdateCount()); - assertTrue(!call.getMoreResults()); + assertFalse(call.getMoreResults()); call.close(); } @Test public void testGetDouble() throws Throwable { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall(func + pkgName + "getDouble (?) }"); call.setDouble(2, 3.04); call.registerOutParameter(1, Types.DOUBLE); @@ -147,7 +157,6 @@ public void testGetDouble() throws Throwable { @Test public void testGetInt() throws Throwable { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall(func + pkgName + "getInt (?) }"); call.setInt(2, 4); call.registerOutParameter(1, Types.INTEGER); @@ -157,7 +166,6 @@ public void testGetInt() throws Throwable { @Test public void testGetShort() throws Throwable { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall(func + pkgName + "getShort (?) }"); call.setShort(2, (short) 4); call.registerOutParameter(1, Types.SMALLINT); @@ -167,26 +175,23 @@ public void testGetShort() throws Throwable { @Test public void testGetNumeric() throws Throwable { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall(func + pkgName + "getNumeric (?) }"); call.setBigDecimal(2, new java.math.BigDecimal(4)); call.registerOutParameter(1, Types.NUMERIC); call.execute(); - assertEquals(new java.math.BigDecimal(42), call.getBigDecimal(1)); + assertEquals(new BigDecimal(42), call.getBigDecimal(1)); } @Test public void testGetNumericWithoutArg() throws Throwable { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall(func + pkgName + "getNumericWithoutArg () }"); call.registerOutParameter(1, Types.NUMERIC); call.execute(); - assertEquals(new java.math.BigDecimal(42), call.getBigDecimal(1)); + assertEquals(new BigDecimal(42), call.getBigDecimal(1)); } @Test public void testGetString() throws Throwable { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall(func + pkgName + "getString (?) }"); call.setString(2, "foo"); call.registerOutParameter(1, Types.VARCHAR); @@ -197,7 +202,6 @@ public void testGetString() throws Throwable { @Test public void testGetArray() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall(func + pkgName + "getarray()}"); call.registerOutParameter(1, Types.ARRAY); call.execute(); @@ -207,12 +211,11 @@ public void testGetArray() throws SQLException { assertEquals(1, rs.getInt(1)); assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test public void testRaiseNotice() throws SQLException { - assumeCallableStatementsSupported(); Statement statement = con.createStatement(); statement.execute("SET SESSION client_min_messages = 'NOTICE'"); CallableStatement call = con.prepareCall(func + pkgName + "raisenotice()}"); @@ -236,7 +239,7 @@ public void testWasNullBeforeFetch() throws SQLException { cs.wasNull(); fail("expected exception"); } catch (Exception e) { - assertTrue(e instanceof SQLException); + assertInstanceOf(SQLException.class, e); } } @@ -249,7 +252,7 @@ public void testFetchBeforeExecute() throws SQLException { cs.getString(1); fail("expected exception"); } catch (Exception e) { - assertTrue(e instanceof SQLException); + assertInstanceOf(SQLException.class, e); } } @@ -261,7 +264,7 @@ public void testFetchWithNoResults() throws SQLException { cs.getObject(1); fail("expected exception"); } catch (Exception e) { - assertTrue(e instanceof SQLException); + assertInstanceOf(SQLException.class, e); } } @@ -301,7 +304,7 @@ public void testBatchCall() throws SQLException { assertEquals(2, rs.getInt(1)); assertTrue(rs.next()); assertEquals(3, rs.getInt(1)); - assertTrue(!rs.next()); + assertFalse(rs.next()); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/CleanupSavepointsWithFastpathTest.java b/src/test/java/org/postgresql/test/jdbc2/CleanupSavepointsWithFastpathTest.java new file mode 100644 index 0000000..dc3657b --- /dev/null +++ b/src/test/java/org/postgresql/test/jdbc2/CleanupSavepointsWithFastpathTest.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import org.postgresql.PGConnection; +import org.postgresql.PGProperty; +import org.postgresql.largeobject.LargeObject; +import org.postgresql.largeobject.LargeObjectManager; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.nio.charset.StandardCharsets; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Properties; + +/** + * Tests for GitHub issue #3910: cleanupSavepoints=true causes "Unknown Response Type C." + * error when used with Large Object operations (fastpath). + * + *

          The bug occurs because {@code releaseSavePoint()} in {@code QueryExecutorImpl} sends + * the RELEASE SAVEPOINT command but doesn't wait for the response. The CommandComplete ('C') + * response is left in the buffer. When a fastpath operation (used by Large Objects) executes + * next, {@code receiveFastpathResult()} receives this unexpected 'C' message and throws + * "Unknown Response Type C."

          + * + * @see
          Issue #3910 + */ +@ParameterizedClass +@MethodSource("data") +class CleanupSavepointsWithFastpathTest extends BaseTest4 { + CleanupSavepointsWithFastpathTest(BinaryMode binaryMode) { + setBinaryMode(binaryMode); + } + + static Iterable data() { + Collection ids = new ArrayList<>(); + for (BinaryMode binaryMode : BinaryMode.values()) { + ids.add(arguments(binaryMode)); + } + return ids; + } + + @Override + protected void updateProperties(Properties props) { + super.updateProperties(props); + PGProperty.AUTOSAVE.set(props, "always"); + PGProperty.CLEANUP_SAVEPOINTS.set(props, true); + } + + /** + * Tests that Large Object operations work correctly when cleanupSavepoints=true + * and autosave=always are both enabled. + * + *

          This reproduces the bug from issue #3910 where the combination of these + * settings causes fastpath operations to fail with "Unknown Response Type C."

          + */ + @Test + void testLargeObjectWithCleanupSavepoints() throws Exception { + con.setAutoCommit(false); + + // Execute a query to trigger autosave mechanism + // This will set a savepoint before the query and release it after (due to cleanupSavepoints) + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + } + + // Now try Large Object operations - these use fastpath protocol + // The bug: the RELEASE SAVEPOINT response ('C') is still in the buffer + // and receiveFastpathResult() will read it instead of the expected response + LargeObjectManager lom = con.unwrap(PGConnection.class).getLargeObjectAPI(); + + // This should NOT throw "Unknown Response Type C." + long oid = lom.createLO(); + try { + try (LargeObject lo = lom.open(oid)) { + byte[] data = "Test data for issue #3910".getBytes(StandardCharsets.UTF_8); + lo.write(data); + lo.seek(0); + byte[] readBack = lo.read(data.length); + assertArrayEquals(data, readBack, + "Large object data should be readable after write"); + } + } finally { + lom.delete(oid); + } + } + + /** + * Tests that a sequence of queries followed by Large Object operations works correctly. + * This tests multiple rounds of savepoint creation/cleanup followed by fastpath. + */ + @Test + void testMultipleQueriesThenLargeObject() throws Exception { + con.setAutoCommit(false); + + // Execute multiple queries - each will trigger savepoint creation and cleanup + try (Statement stmt = con.createStatement()) { + for (int i = 0; i < 5; i++) { + stmt.execute("SELECT " + i); + } + } + + // Now try Large Object operations + LargeObjectManager lom = con.unwrap(PGConnection.class).getLargeObjectAPI(); + long oid = lom.createLO(); + try { + try (LargeObject lo = lom.open(oid)) { + lo.write(new byte[]{1, 2, 3, 4, 5}); + } + } finally { + lom.delete(oid); + } + } + + /** + * Tests interleaving of regular queries and Large Object operations. + */ + @Test + void testInterleavedQueriesAndLargeObjects() throws Exception { + con.setAutoCommit(false); + + LargeObjectManager lom = con.unwrap(PGConnection.class).getLargeObjectAPI(); + + for (int i = 0; i < 3; i++) { + // Execute a query (triggers savepoint cleanup) + try (Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT " + i)) { + rs.next(); + } + + // Then do Large Object operation (uses fastpath) + long oid = lom.createLO(); + try { + try (LargeObject lo = lom.open(oid)) { + lo.write(("Iteration " + i).getBytes(StandardCharsets.UTF_8)); + } + } finally { + lom.delete(oid); + } + } + } + + /** + * Tests PreparedStatement execution followed by Large Object operations. + * PreparedStatements may use different code paths for savepoint handling. + */ + @Test + void testPreparedStatementThenLargeObject() throws Exception { + con.setAutoCommit(false); + + // Use PreparedStatement to trigger server-side prepare + try (PreparedStatement ps = con.prepareStatement("SELECT ?")) { + ps.setInt(1, 42); + try (ResultSet rs = ps.executeQuery()) { + rs.next(); + } + // Execute again to trigger cached statement path + ps.setInt(1, 43); + try (ResultSet rs = ps.executeQuery()) { + rs.next(); + } + } + + // Large Object operation should still work + LargeObjectManager lom = con.unwrap(PGConnection.class).getLargeObjectAPI(); + long oid = lom.createLO(); + try { + try (LargeObject lo = lom.open(oid)) { + lo.write(new byte[100]); + } + } finally { + lom.delete(oid); + } + } + + /** + * Verifies that Large Objects work without cleanupSavepoints (baseline test). + * This confirms the issue is specifically with cleanupSavepoints=true. + */ + @Test + void testLargeObjectWithoutCleanupSavepoints() throws Exception { + con.setAutoCommit(false); + + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + } + + LargeObjectManager lom = con.unwrap(PGConnection.class).getLargeObjectAPI(); + long oid = lom.createLO(); + try { + try (LargeObject lo = lom.open(oid)) { + byte[] data = "Baseline test".getBytes(StandardCharsets.UTF_8); + lo.write(data); + lo.seek(0); + byte[] readBack = lo.read(data.length); + assertArrayEquals(data, readBack); + } + } finally { + lom.delete(oid); + } + } +} diff --git a/src/test/java/org/postgresql/test/jdbc2/ClientEncodingTest.java b/src/test/java/org/postgresql/test/jdbc2/ClientEncodingTest.java index 117b399..fe29f0e 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ClientEncodingTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ClientEncodingTest.java @@ -5,14 +5,18 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; import org.postgresql.util.PSQLState; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.ResultSet; import java.sql.SQLException; @@ -20,10 +24,11 @@ import java.util.Arrays; import java.util.Properties; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class ClientEncodingTest extends BaseTest4 { - @Parameterized.Parameter(0) + @Parameter public boolean allowEncodingChanges; @Override @@ -32,7 +37,6 @@ protected void updateProperties(Properties props) { PGProperty.ALLOW_ENCODING_CHANGES.set(props, allowEncodingChanges); } - @Parameterized.Parameters(name = "allowEncodingChanges={0}") public static Iterable data() { return Arrays.asList(new Object[][]{ {true}, @@ -53,15 +57,14 @@ public void setEncodingAscii() throws SQLException { try { setEncoding("sql_ascii"); if (!allowEncodingChanges) { - Assert.fail( - "allowEncodingChanges is false, thus set client_encoding=aql_ascii is expected to fail"); + fail("allowEncodingChanges is false, thus set client_encoding=aql_ascii is expected to fail"); } } catch (SQLException e) { if (!allowEncodingChanges && !PSQLState.CONNECTION_FAILURE.getState() .equals(e.getSQLState())) { throw e; } - Assert.assertTrue("Connection should be closed on client_encoding change", con.isClosed()); + assertTrue(con.isClosed(), "Connection should be closed on client_encoding change"); return; } @@ -72,7 +75,7 @@ private void checkConnectionSanity() throws SQLException { Statement st = con.createStatement(); ResultSet rs = st.executeQuery("select 'abc' as x"); rs.next(); - Assert.assertEquals("abc", rs.getString(1)); + assertEquals("abc", rs.getString(1)); TestUtil.closeQuietly(rs); TestUtil.closeQuietly(st); } diff --git a/src/test/java/org/postgresql/test/jdbc2/ColumnSanitiserDisabledTest.java b/src/test/java/org/postgresql/test/jdbc2/ColumnSanitiserDisabledTest.java index 6bae461..598b0cb 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ColumnSanitiserDisabledTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ColumnSanitiserDisabledTest.java @@ -5,52 +5,63 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.core.BaseConnection; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.sql.Statement; import java.util.Properties; /* - * This test suite will check the behaviour of the findColumnIndex method. This is testing the - * behaviour when sanitiser is disabled. - */ -public class ColumnSanitiserDisabledTest { +* This test suite will check the behaviour of the findColumnIndex method. This is testing the +* behaviour when sanitiser is disabled. +*/ +class ColumnSanitiserDisabledTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + /* + * Quoted columns will be stored with case preserved. Driver will receive column names as + * defined in db server. + */ + TestUtil.createTable(conn, "allmixedup", + "id int primary key, \"DESCRIPTION\" varchar(40), \"fOo\" varchar(3)"); + TestUtil.execute(conn, TestUtil.insertSQL("allmixedup", "1,'mixed case test', 'bar'")); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "allmixedup"); + } + } + + @BeforeEach + void setUp() throws Exception { Properties props = new Properties(); props.setProperty("disableColumnSanitiser", Boolean.TRUE.toString()); conn = TestUtil.openDB(props); assertTrue(conn instanceof BaseConnection); BaseConnection bc = (BaseConnection) conn; - assertTrue("Expected state [TRUE] of base connection configuration failed test.", - bc.isColumnSanitiserDisabled()); - /* - * Quoted columns will be stored with case preserved. Driver will receive column names as - * defined in db server. - */ - TestUtil.createTable(conn, "allmixedup", - "id int primary key, \"DESCRIPTION\" varchar(40), \"fOo\" varchar(3)"); - Statement data = conn.createStatement(); - data.execute(TestUtil.insertSQL("allmixedup", "1,'mixed case test', 'bar'")); - data.close(); + assertTrue(bc.isColumnSanitiserDisabled(), + "Expected state [TRUE] of base connection configuration failed test."); } - @After - public void tearDown() throws Exception { - TestUtil.dropTable(conn, "allmixedup"); + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(conn); System.setProperty("disableColumnSanitiser", "false"); } @@ -61,47 +72,47 @@ public void tearDown() throws Exception { */ @Test - public void testTableColumnLowerNowFindFindLowerCaseColumn() throws SQLException { + void tableColumnLowerNowFindFindLowerCaseColumn() throws SQLException { findColumn("id", true); } @Test - public void testTableColumnLowerNowFindFindUpperCaseColumn() throws SQLException { + void tableColumnLowerNowFindFindUpperCaseColumn() throws SQLException { findColumn("ID", true); } @Test - public void testTableColumnLowerNowFindFindMixedCaseColumn() throws SQLException { + void tableColumnLowerNowFindFindMixedCaseColumn() throws SQLException { findColumn("Id", false); } @Test - public void testTableColumnUpperNowFindFindLowerCaseColumn() throws SQLException { + void tableColumnUpperNowFindFindLowerCaseColumn() throws SQLException { findColumn("description", true); } @Test - public void testTableColumnUpperNowFindFindUpperCaseColumn() throws SQLException { + void tableColumnUpperNowFindFindUpperCaseColumn() throws SQLException { findColumn("DESCRIPTION", true); } @Test - public void testTableColumnUpperNowFindFindMixedCaseColumn() throws SQLException { + void tableColumnUpperNowFindFindMixedCaseColumn() throws SQLException { findColumn("Description", false); } @Test - public void testTableColumnMixedNowFindLowerCaseColumn() throws SQLException { + void tableColumnMixedNowFindLowerCaseColumn() throws SQLException { findColumn("foo", false); } @Test - public void testTableColumnMixedNowFindFindUpperCaseColumn() throws SQLException { + void tableColumnMixedNowFindFindUpperCaseColumn() throws SQLException { findColumn("FOO", false); } @Test - public void testTableColumnMixedNowFindFindMixedCaseColumn() throws SQLException { + void tableColumnMixedNowFindFindMixedCaseColumn() throws SQLException { findColumn("fOo", true); } diff --git a/src/test/java/org/postgresql/test/jdbc2/ColumnSanitiserEnabledTest.java b/src/test/java/org/postgresql/test/jdbc2/ColumnSanitiserEnabledTest.java index c4c04a9..f9a05b8 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ColumnSanitiserEnabledTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ColumnSanitiserEnabledTest.java @@ -5,47 +5,60 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.core.BaseConnection; import org.postgresql.test.TestUtil; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.sql.Statement; import java.util.Properties; /* - * This test suite will check the behaviour of the findColumnIndex method. The tests will check the - * behaviour of the method when the sanitiser is enabled. Default behaviour of the driver. - */ -public class ColumnSanitiserEnabledTest { +* This test suite will check the behaviour of the findColumnIndex method. The tests will check the +* behaviour of the method when the sanitiser is enabled. Default behaviour of the driver. +*/ +class ColumnSanitiserEnabledTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "allmixedup", + "id int primary key, \"DESCRIPTION\" varchar(40), \"fOo\" varchar(3)"); + TestUtil.execute(conn, TestUtil.insertSQL("allmixedup", "1,'mixed case test', 'bar'")); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "allmixedup"); + } + } + + @BeforeEach + void setUp() throws Exception { Properties props = new Properties(); props.setProperty("disableColumnSanitiser", Boolean.FALSE.toString()); conn = TestUtil.openDB(props); assertTrue(conn instanceof BaseConnection); BaseConnection bc = (BaseConnection) conn; - assertFalse("Expected state [FALSE] of base connection configuration failed test.", - bc.isColumnSanitiserDisabled()); - TestUtil.createTable(conn, "allmixedup", - "id int primary key, \"DESCRIPTION\" varchar(40), \"fOo\" varchar(3)"); - Statement data = conn.createStatement(); - data.execute(TestUtil.insertSQL("allmixedup", "1,'mixed case test', 'bar'")); - data.close(); + assertFalse(bc.isColumnSanitiserDisabled(), + "Expected state [FALSE] of base connection configuration failed test."); } - protected void tearDown() throws Exception { - TestUtil.dropTable(conn, "allmixedup"); + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(conn); } @@ -55,47 +68,47 @@ protected void tearDown() throws Exception { */ @Test - public void testTableColumnLowerNowFindFindLowerCaseColumn() throws SQLException { + void tableColumnLowerNowFindFindLowerCaseColumn() throws SQLException { findColumn("id", true); } @Test - public void testTableColumnLowerNowFindFindUpperCaseColumn() throws SQLException { + void tableColumnLowerNowFindFindUpperCaseColumn() throws SQLException { findColumn("ID", true); } @Test - public void testTableColumnLowerNowFindFindMixedCaseColumn() throws SQLException { + void tableColumnLowerNowFindFindMixedCaseColumn() throws SQLException { findColumn("Id", true); } @Test - public void testTableColumnUpperNowFindFindLowerCaseColumn() throws SQLException { + void tableColumnUpperNowFindFindLowerCaseColumn() throws SQLException { findColumn("description", true); } @Test - public void testTableColumnUpperNowFindFindUpperCaseColumn() throws SQLException { + void tableColumnUpperNowFindFindUpperCaseColumn() throws SQLException { findColumn("DESCRIPTION", true); } @Test - public void testTableColumnUpperNowFindFindMixedCaseColumn() throws SQLException { + void tableColumnUpperNowFindFindMixedCaseColumn() throws SQLException { findColumn("Description", true); } @Test - public void testTableColumnMixedNowFindLowerCaseColumn() throws SQLException { + void tableColumnMixedNowFindLowerCaseColumn() throws SQLException { findColumn("foo", true); } @Test - public void testTableColumnMixedNowFindFindUpperCaseColumn() throws SQLException { + void tableColumnMixedNowFindFindUpperCaseColumn() throws SQLException { findColumn("FOO", true); } @Test - public void testTableColumnMixedNowFindFindMixedCaseColumn() throws SQLException { + void tableColumnMixedNowFindFindMixedCaseColumn() throws SQLException { findColumn("fOo", true); } diff --git a/src/test/java/org/postgresql/test/jdbc2/ConcurrentStatementFetch.java b/src/test/java/org/postgresql/test/jdbc2/ConcurrentStatementFetchTest.java similarity index 64% rename from src/test/java/org/postgresql/test/jdbc2/ConcurrentStatementFetch.java rename to src/test/java/org/postgresql/test/jdbc2/ConcurrentStatementFetchTest.java index 74c6bc6..860b4e5 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ConcurrentStatementFetch.java +++ b/src/test/java/org/postgresql/test/jdbc2/ConcurrentStatementFetchTest.java @@ -5,35 +5,38 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Collection; -@RunWith(Parameterized.class) -public class ConcurrentStatementFetch extends BaseTest4 { +@ParameterizedClass +@MethodSource("data") +public class ConcurrentStatementFetchTest extends BaseTest4 { private final AutoCommit autoCommit; private final int fetchSize; - public ConcurrentStatementFetch(AutoCommit autoCommit, int fetchSize, BinaryMode binaryMode) { + public ConcurrentStatementFetchTest(AutoCommit autoCommit, int fetchSize, BinaryMode binaryMode) { this.autoCommit = autoCommit; this.fetchSize = fetchSize; setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "{index}: fetch(autoCommit={0}, fetchSize={1}, binaryMode={2})") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (AutoCommit autoCommit : AutoCommit.values()) { for (int fetchSize : new int[]{1, 2, 20}) { for (BinaryMode binaryMode : BinaryMode.values()) { @@ -53,7 +56,7 @@ public void setUp() throws Exception { @Test public void testFetchTwoStatements() throws Exception { // This test definitely fails at 8.2 in autocommit=false, and works with 8.4+ - Assume.assumeTrue(autoCommit == AutoCommit.YES + assumeTrue(autoCommit == AutoCommit.YES || TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)); PreparedStatement ps1 = null; PreparedStatement ps2 = null; @@ -66,13 +69,13 @@ public void testFetchTwoStatements() throws Exception { ResultSet rs2 = ps2.executeQuery(); for (int i = 0; i < 10; i++) { - Assert.assertTrue(rs1.next()); - Assert.assertTrue(rs2.next()); - Assert.assertEquals("Row#" + i + ", resultset 1", i, rs1.getInt(1)); - Assert.assertEquals("Row#" + i + ", resultset 2", i + 10, rs2.getInt(1)); + assertTrue(rs1.next()); + assertTrue(rs2.next()); + assertEquals(i, rs1.getInt(1), "Row#" + i + ", resultset 1"); + assertEquals(i + 10, rs2.getInt(1), "Row#" + i + ", resultset 2"); } - Assert.assertFalse(rs1.next()); - Assert.assertFalse(rs2.next()); + assertFalse(rs1.next()); + assertFalse(rs2.next()); } finally { TestUtil.closeQuietly(ps1); TestUtil.closeQuietly(ps2); diff --git a/src/test/java/org/postgresql/test/jdbc2/ConnectTimeoutTest.java b/src/test/java/org/postgresql/test/jdbc2/ConnectTimeoutTest.java index 5b96739..af02dd6 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ConnectTimeoutTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ConnectTimeoutTest.java @@ -5,14 +5,16 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.net.NoRouteToHostException; import java.net.SocketTimeoutException; @@ -20,24 +22,24 @@ import java.sql.SQLException; import java.util.Properties; -public class ConnectTimeoutTest { +class ConnectTimeoutTest { // The IP below is non-routable (see http://stackoverflow.com/a/904609/1261287) private static final String UNREACHABLE_HOST = "10.255.255.1"; private static final String UNREACHABLE_URL = "jdbc:postgresql://" + UNREACHABLE_HOST + ":5432/test"; private static final int CONNECT_TIMEOUT = 5; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { TestUtil.initDriver(); } @Test - public void testTimeout() { + void timeout() { final Properties props = new Properties(); - props.setProperty("user", "test"); - props.setProperty("password", "test"); + PGProperty.USER.set(props, TestUtil.getUser()); + PGProperty.PASSWORD.set(props, TestUtil.getPassword()); // with 0 (default value) it hangs for about 60 seconds (platform dependent) - props.setProperty("connectTimeout", Integer.toString(CONNECT_TIMEOUT)); + PGProperty.CONNECT_TIMEOUT.set(props, CONNECT_TIMEOUT); final long startTime = System.currentTimeMillis(); try { @@ -56,16 +58,22 @@ public void testTimeout() { * We treat this as a skipped test, as the test didn't really "succeed" * in testing the original behaviour, but it didn't fail either. */ - Assume.assumeTrue("Host fast-failed connection to unreachable address " - + UNREACHABLE_HOST + " after " + interval + " ms, " - + " before timeout should have triggered.", - e.getCause() instanceof NoRouteToHostException - && interval < connectTimeoutMillis ); + assumeFalse( + e.getCause() instanceof NoRouteToHostException + && interval < connectTimeoutMillis, + () -> "Host fast-failed connection to unreachable address " + + UNREACHABLE_HOST + " after " + interval + " ms, " + + " before timeout should have triggered."); - assertTrue("Unexpected " + e.toString() + " with cause " + e.getCause(), - e.getCause() instanceof SocketTimeoutException); + assertInstanceOf( + SocketTimeoutException.class, + e.getCause(), + () -> "Unexpected " + e + " with cause " + e.getCause()); // check that it was not a default system timeout, an approximate value is used - assertTrue(Math.abs(interval - connectTimeoutMillis) < maxDeviation); + assertTrue(Math.abs(interval - connectTimeoutMillis) < maxDeviation, + () -> "Connection timeout should be ~" + connectTimeoutMillis + " ms, but was " + + interval + " ms (deviation " + Math.abs(interval - connectTimeoutMillis) + + " ms exceeds maximum allowed " + maxDeviation + " ms)"); return; } fail("SQLException expected"); diff --git a/src/test/java/org/postgresql/test/jdbc2/ConnectionTest.java b/src/test/java/org/postgresql/test/jdbc2/ConnectionTest.java index 7954085..20befff 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ConnectionTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ConnectionTest.java @@ -5,22 +5,26 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.postgresql.PGConnection; import org.postgresql.PGProperty; import org.postgresql.core.PGStream; import org.postgresql.core.QueryExecutor; import org.postgresql.jdbc.PgConnection; import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLState; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.lang.reflect.Field; import java.sql.Connection; @@ -37,30 +41,35 @@ * TestCase to test the internal functionality of org.postgresql.jdbc2.Connection and it's * superclass. */ -public class ConnectionTest { +class ConnectionTest { private Connection con; - // Set up the fixture for this testcase: the tables for this test. - @Before - public void setUp() throws Exception { - con = TestUtil.openDB(); - - TestUtil.createTable(con, "test_a", "imagename name,image oid,id int4"); - TestUtil.createTable(con, "test_c", "source text,cost money,imageid int4"); - - TestUtil.closeDB(con); + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "test_a", "imagename name,image oid,id int4"); + TestUtil.createTable(conn, "test_c", "source text,cost money,imageid int4"); + } } - // Tear down the fixture for this test case. - @After - public void tearDown() throws Exception { - TestUtil.closeDB(con); + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "test_a"); + TestUtil.dropTable(conn, "test_c"); + } + } + // Set up the fixture for this testcase: the tables for this test. + @BeforeEach + void setUp() throws Exception { con = TestUtil.openDB(); + TestUtil.execute(con, "TRUNCATE test_a, test_c"); + } - TestUtil.dropTable(con, "test_a"); - TestUtil.dropTable(con, "test_c"); - + // Tear down the fixture for this test case. + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(con); } @@ -68,7 +77,7 @@ public void tearDown() throws Exception { * Tests the two forms of createStatement() */ @Test - public void testCreateStatement() throws Exception { + void createStatement() throws Exception { con = TestUtil.openDB(); // A standard Statement @@ -86,7 +95,7 @@ public void testCreateStatement() throws Exception { * Tests the two forms of prepareStatement() */ @Test - public void testPrepareStatement() throws Exception { + void prepareStatement() throws Exception { con = TestUtil.openDB(); String sql = "select source,cost,imageid from test_c"; @@ -106,14 +115,14 @@ public void testPrepareStatement() throws Exception { * Put the test for createPrepareCall here */ @Test - public void testPrepareCall() { + void prepareCall() { } /* * Test nativeSQL */ @Test - public void testNativeSQL() throws Exception { + void nativeSQL() throws Exception { // test a simple escape con = TestUtil.openDB(); assertEquals("DATE '2005-01-24'", con.nativeSQL("{d '2005-01-24'}")); @@ -123,14 +132,14 @@ public void testNativeSQL() throws Exception { * Test autoCommit (both get & set) */ @Test - public void testTransactions() throws Exception { + void transactions() throws Exception { con = TestUtil.openDB(); Statement st; ResultSet rs; // Turn it off con.setAutoCommit(false); - assertTrue(!con.getAutoCommit()); + assertFalse(con.getAutoCommit()); // Turn it back on con.setAutoCommit(true); @@ -165,7 +174,7 @@ public void testTransactions() throws Exception { * Tests for session and transaction read only behavior with "always" read only mode. */ @Test - public void testReadOnly_always() throws Exception { + void readOnly_always() throws Exception { final Properties props = new Properties(); PGProperty.READ_ONLY_MODE.set(props, "always"); con = TestUtil.openDB(props); @@ -200,7 +209,7 @@ public void testReadOnly_always() throws Exception { con.setReadOnly(false); fail("cannot set read only during transaction"); } catch (SQLException e) { - assertStringContains(e.getMessage(), "Cannot change transaction read-only"); + assertEquals(PSQLState.ACTIVE_SQL_TRANSACTION.getState(), e.getSQLState(), "Expecting <>"); } // end the transaction @@ -237,7 +246,7 @@ public void testReadOnly_always() throws Exception { assertEquals(9876, rs.getInt(1)); // Should not change! rs.close(); - // repeat attempt to chagne with auto commit true + // repeat attempt to change with auto commit true con.setAutoCommit(true); try { @@ -260,7 +269,7 @@ public void testReadOnly_always() throws Exception { * Tests for session and transaction read only behavior with "ignore" read only mode. */ @Test - public void testReadOnly_ignore() throws Exception { + void readOnly_ignore() throws Exception { final Properties props = new Properties(); PGProperty.READ_ONLY_MODE.set(props, "ignore"); con = TestUtil.openDB(props); @@ -301,7 +310,7 @@ public void testReadOnly_ignore() throws Exception { * Tests for session and transaction read only behavior with "transaction" read only mode. */ @Test - public void testReadOnly_transaction() throws Exception { + void readOnly_transaction() throws Exception { final Properties props = new Properties(); PGProperty.READ_ONLY_MODE.set(props, "transaction"); con = TestUtil.openDB(props); @@ -355,7 +364,7 @@ public void testReadOnly_transaction() throws Exception { assertEquals(9876, rs.getInt(1)); // Should not change! rs.close(); - // repeat attempt to chagne with auto commit true + // repeat attempt to change with auto commit true con.setAutoCommit(true); assertEquals(1, st.executeUpdate("update test_a set image=1111 where id=5678")); @@ -373,11 +382,11 @@ public void testReadOnly_transaction() throws Exception { * Simple test to see if isClosed works. */ @Test - public void testIsClosed() throws Exception { + void isClosed() throws Exception { con = TestUtil.openDB(); // Should not say closed - assertTrue(!con.isClosed()); + assertFalse(con.isClosed()); TestUtil.closeDB(con); @@ -389,13 +398,13 @@ public void testIsClosed() throws Exception { * Test the warnings system */ @Test - public void testWarnings() throws Exception { + void warnings() throws Exception { con = TestUtil.openDB(); String testStr = "This Is OuR TeSt message"; // The connection must be ours! - assertTrue(con instanceof org.postgresql.PGConnection); + assertTrue(con instanceof PGConnection); // Clear any existing warnings con.clearWarnings(); @@ -419,7 +428,7 @@ public void testWarnings() throws Exception { * Transaction Isolation Levels */ @Test - public void testTransactionIsolation() throws Exception { + void transactionIsolation() throws Exception { con = TestUtil.openDB(); int defaultLevel = con.getTransactionIsolation(); @@ -483,14 +492,14 @@ public void testTransactionIsolation() throws Exception { * JDBC2 Type mappings */ @Test - public void testTypeMaps() throws Exception { + void typeMaps() throws Exception { con = TestUtil.openDB(); // preserve the current map Map> oldmap = con.getTypeMap(); // now change it for an empty one - Map> newmap = new HashMap>(); + Map> newmap = new HashMap<>(); con.setTypeMap(newmap); assertEquals(newmap, con.getTypeMap()); @@ -505,7 +514,7 @@ public void testTypeMaps() throws Exception { * Closing a Connection more than once is not an error. */ @Test - public void testDoubleClose() throws Exception { + void doubleClose() throws Exception { con = TestUtil.openDB(); con.close(); con.close(); @@ -515,22 +524,22 @@ public void testDoubleClose() throws Exception { * Make sure that type map is empty and not null */ @Test - public void testGetTypeMapEmpty() throws Exception { + void getTypeMapEmpty() throws Exception { con = TestUtil.openDB(); Map typeMap = con.getTypeMap(); assertNotNull(typeMap); - assertTrue("TypeMap should be empty", typeMap.isEmpty()); + assertTrue(typeMap.isEmpty(), "TypeMap should be empty"); con.close(); } @Test - public void testPGStreamSettings() throws Exception { + void pGStreamSettings() throws Exception { con = TestUtil.openDB(); - QueryExecutor queryExecutor = ((PgConnection)con).getQueryExecutor(); + QueryExecutor queryExecutor = ((PgConnection) con).getQueryExecutor(); Field f = queryExecutor.getClass().getSuperclass().getDeclaredField("pgStream"); f.setAccessible(true); - PGStream pgStream = (PGStream)f.get(queryExecutor); + PGStream pgStream = (PGStream) f.get(queryExecutor); pgStream.setNetworkTimeout(1000); pgStream.getSocket().setKeepAlive(true); pgStream.getSocket().setSendBufferSize(8192); diff --git a/src/test/java/org/postgresql/test/jdbc2/CopyLargeFileTest.java b/src/test/java/org/postgresql/test/jdbc2/CopyLargeFileTest.java index 60089eb..07c3d78 100644 --- a/src/test/java/org/postgresql/test/jdbc2/CopyLargeFileTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/CopyLargeFileTest.java @@ -5,20 +5,22 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.postgresql.PGConnection; import org.postgresql.copy.CopyManager; import org.postgresql.test.TestUtil; import org.postgresql.test.util.BufferGenerator; import org.postgresql.test.util.StrangeInputStream; +import org.postgresql.util.internal.FileUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.File; -import java.io.FileInputStream; import java.io.InputStream; import java.sql.CallableStatement; import java.sql.Connection; @@ -29,31 +31,46 @@ /** * @author amozhenin on 30.09.2015. */ -public class CopyLargeFileTest { +class CopyLargeFileTest { private static final int FEED_COUNT = 10; private Connection con; private CopyManager copyAPI; - @Before - public void setUp() throws Exception { - con = TestUtil.openDB(); - - TestUtil.createTable(con, "pgjdbc_issue366_test_glossary", - "id SERIAL, text_id VARCHAR(1000) NOT NULL UNIQUE, name VARCHAR(10) NOT NULL UNIQUE"); - TestUtil.createTable(con, "pgjdbc_issue366_test_data", - "id SERIAL,\n" - + "data_text_id VARCHAR(1000) NOT NULL /*UNIQUE <-- it slows down inserts due to additional index */,\n" - + "glossary_text_id VARCHAR(1000) NOT NULL /* REFERENCES pgjdbc_issue366_test_glossary(text_id) */,\n" - + "value DOUBLE PRECISION NOT NULL"); + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "pgjdbc_issue366_test_glossary", + "id SERIAL, text_id VARCHAR(1000) NOT NULL UNIQUE, name VARCHAR(10) NOT NULL UNIQUE"); + TestUtil.createTable(con, "pgjdbc_issue366_test_data", + "id SERIAL,\n" + + "data_text_id VARCHAR(1000) NOT NULL /*UNIQUE <-- it slows down inserts due to additional index */,\n" + + "glossary_text_id VARCHAR(1000) NOT NULL /* REFERENCES pgjdbc_issue366_test_glossary(text_id) */,\n" + + "value DOUBLE PRECISION NOT NULL"); + feedTable(con); + } - feedTable(); BufferGenerator.main(new String[]{}); + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "pgjdbc_issue366_test_data"); + TestUtil.dropTable(con, "pgjdbc_issue366_test_glossary"); + } + + new File("target/buffer.txt").delete(); + } + + @BeforeEach + void setUp() throws Exception { + con = TestUtil.openDB(); copyAPI = ((PGConnection) con).getCopyAPI(); } - private void feedTable() throws Exception { + private static void feedTable(Connection con) throws Exception { PreparedStatement stmt = con.prepareStatement( TestUtil.insertSQL("pgjdbc_issue366_test_glossary", "text_id, name", "?, ?")); for (int i = 0; i < 26; i++) { @@ -63,25 +80,19 @@ private void feedTable() throws Exception { } } - private void insertData(PreparedStatement stmt, String textId, String name) throws SQLException { + private static void insertData(PreparedStatement stmt, String textId, String name) throws SQLException { stmt.setString(1, textId); stmt.setString(2, name); stmt.executeUpdate(); } - @After - public void tearDown() throws Exception { - try { - TestUtil.dropTable(con, "pgjdbc_issue366_test_data"); - TestUtil.dropTable(con, "pgjdbc_issue366_test_glossary"); - new File("target/buffer.txt").delete(); - } finally { - con.close(); - } + @AfterEach + void tearDown() throws Exception { + con.close(); } @Test - public void testFeedTableSeveralTimesTest() throws Throwable { + void feedTableSeveralTimesTest() throws Throwable { for (int i = 1; i <= FEED_COUNT; i++) { feedTableAndCheckTableFeedIsOk(con); cleanupTable(con); @@ -95,32 +106,19 @@ private void feedTableAndCheckTableFeedIsOk(Connection conn) throws Throwable { } InputStream in = null; try { - in = new StrangeInputStream(new FileInputStream("target/buffer.txt"), seed); + in = new StrangeInputStream(seed, FileUtils.newBufferedInputStream("target/buffer.txt")); long size = copyAPI.copyIn( "COPY pgjdbc_issue366_test_data(data_text_id, glossary_text_id, value) FROM STDIN", in); assertEquals(BufferGenerator.ROW_COUNT, size); } catch (Throwable t) { String message = "Using seed = " + seed + " for StrangeInputStream. Set -DStrangeInputStream.seed=" + seed + " to reproduce the test"; - //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1" t.addSuppressed(new Throwable(message) { @Override - public synchronized Throwable fillInStackTrace() { + public Throwable fillInStackTrace() { return this; } }); - //#else - if (t.getCause() != null) { - System.err.println(message); - } else { - t.initCause(new Throwable(message) { - @Override - public synchronized Throwable fillInStackTrace() { - return this; - } - }); - } - //#endif } finally { if (in != null) { in.close(); @@ -128,7 +126,7 @@ public synchronized Throwable fillInStackTrace() { } } - private void cleanupTable(Connection conn) throws Exception { + private static void cleanupTable(Connection conn) throws Exception { CallableStatement stmt = null; try { stmt = conn.prepareCall("TRUNCATE pgjdbc_issue366_test_data;"); @@ -138,6 +136,5 @@ private void cleanupTable(Connection conn) throws Exception { stmt.close(); } } - } } diff --git a/src/test/java/org/postgresql/test/jdbc2/CopyTest.java b/src/test/java/org/postgresql/test/jdbc2/CopyTest.java index ca9d325..3ff95fc 100644 --- a/src/test/java/org/postgresql/test/jdbc2/CopyTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/CopyTest.java @@ -5,25 +5,32 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeout; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGConnection; +import org.postgresql.PGProperty; import org.postgresql.copy.CopyIn; import org.postgresql.copy.CopyManager; import org.postgresql.copy.CopyOut; +import org.postgresql.copy.PGCopyInputStream; import org.postgresql.copy.PGCopyOutputStream; import org.postgresql.core.ServerVersion; +import org.postgresql.jdbc.PgConnection; import org.postgresql.test.TestUtil; +import org.postgresql.test.util.StrangeProxyServer; import org.postgresql.util.ByteBufferByteStreamWriter; import org.postgresql.util.PSQLState; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -33,27 +40,32 @@ import java.io.PrintStream; import java.io.StringReader; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.time.Duration; +import java.util.Locale; +import java.util.Properties; +import java.util.concurrent.TimeUnit; /** * @author kato@iki.fi */ -public class CopyTest { +class CopyTest { private Connection con; private CopyManager copyAPI; private String copyParams; // 0's required to match DB output for numeric(5,2) - private String[] origData = + private final String[] origData = {"First Row\t1\t1.10\n", "Second Row\t2\t-22.20\n", "\\N\t\\N\t\\N\n", "\t4\t444.40\n"}; - private int dataRows = origData.length; + private final int dataRows = origData.length; - private byte[] getData(String[] origData) { + private static byte[] getData(String[] origData) { ByteArrayOutputStream buf = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(buf); for (String anOrigData : origData) { @@ -62,8 +74,8 @@ private byte[] getData(String[] origData) { return buf.toByteArray(); } - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { con = TestUtil.openDB(); TestUtil.createTempTable(con, "copytest", "stringvalue text, intvalue int, numvalue numeric(5,2)"); @@ -76,8 +88,8 @@ public void setUp() throws Exception { } } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(con); // one of the tests will render the existing connection broken, @@ -100,7 +112,7 @@ private int getCount() throws SQLException { } @Test - public void testCopyInByRow() throws SQLException { + void copyInByRow() throws SQLException { String sql = "COPY copytest FROM STDIN"; CopyIn cp = copyAPI.copyIn(sql); for (String anOrigData : origData) { @@ -125,7 +137,7 @@ public void testCopyInByRow() throws SQLException { } @Test - public void testCopyInAsOutputStream() throws SQLException, IOException { + void copyInAsOutputStream() throws SQLException, IOException { String sql = "COPY copytest FROM STDIN"; OutputStream os = new PGCopyOutputStream((PGConnection) con, sql, 1000); for (String anOrigData : origData) { @@ -138,7 +150,7 @@ public void testCopyInAsOutputStream() throws SQLException, IOException { } @Test - public void testCopyInAsOutputStreamClosesAfterEndCopy() throws SQLException, IOException { + void copyInAsOutputStreamClosesAfterEndCopy() throws SQLException, IOException { String sql = "COPY copytest FROM STDIN"; PGCopyOutputStream os = new PGCopyOutputStream((PGConnection) con, sql, 1000); try { @@ -156,7 +168,7 @@ public void testCopyInAsOutputStreamClosesAfterEndCopy() throws SQLException, IO } @Test - public void testCopyInAsOutputStreamFailsOnFlushAfterEndCopy() throws SQLException, IOException { + void copyInAsOutputStreamFailsOnFlushAfterEndCopy() throws SQLException, IOException { String sql = "COPY copytest FROM STDIN"; PGCopyOutputStream os = new PGCopyOutputStream((PGConnection) con, sql, 1000); try { @@ -172,14 +184,16 @@ public void testCopyInAsOutputStreamFailsOnFlushAfterEndCopy() throws SQLExcepti os.flush(); fail("should have failed flushing an inactive copy stream."); } catch (IOException e) { - if (!e.toString().contains("This copy stream is closed.")) { + // We expect "This copy stream is closed", however, the message is locale-dependent + if (Locale.getDefault().getLanguage().equals(new Locale("en").getLanguage()) + && !e.toString().contains("This copy stream is closed.")) { fail("has failed not due to checkClosed(): " + e); } } } @Test - public void testCopyInFromInputStream() throws SQLException, IOException { + void copyInFromInputStream() throws SQLException, IOException { String sql = "COPY copytest FROM STDIN"; copyAPI.copyIn(sql, new ByteArrayInputStream(getData(origData)), 3); int rowCount = getCount(); @@ -187,10 +201,11 @@ public void testCopyInFromInputStream() throws SQLException, IOException { } @Test - public void testCopyInFromStreamFail() throws SQLException { + void copyInFromStreamFail() throws SQLException { String sql = "COPY copytest FROM STDIN"; try { copyAPI.copyIn(sql, new InputStream() { + @Override public int read() { throw new RuntimeException("COPYTEST"); } @@ -205,7 +220,7 @@ public int read() { } @Test - public void testCopyInFromReader() throws SQLException, IOException { + void copyInFromReader() throws SQLException, IOException { String sql = "COPY copytest FROM STDIN"; copyAPI.copyIn(sql, new StringReader(new String(getData(origData))), 3); int rowCount = getCount(); @@ -213,7 +228,7 @@ public void testCopyInFromReader() throws SQLException, IOException { } @Test - public void testCopyInFromByteStreamWriter() throws SQLException, IOException { + void copyInFromByteStreamWriter() throws SQLException, IOException { String sql = "COPY copytest FROM STDIN"; copyAPI.copyIn(sql, new ByteBufferByteStreamWriter(ByteBuffer.wrap(getData(origData)))); int rowCount = getCount(); @@ -225,8 +240,8 @@ public void testCopyInFromByteStreamWriter() throws SQLException, IOException { * write(byte[]) and the driver specific write(ByteStreamWriter) API interleaved. */ @Test - public void testCopyMultiApi() throws SQLException, IOException { - TestUtil.execute("CREATE TABLE pg_temp.copy_api_test (data text)", con); + void copyMultiApi() throws SQLException, IOException { + TestUtil.execute(con, "CREATE TABLE pg_temp.copy_api_test (data text)"); String sql = "COPY pg_temp.copy_api_test (data) FROM STDIN"; PGCopyOutputStream out = new PGCopyOutputStream(copyAPI.copyIn(sql)); try { @@ -239,11 +254,11 @@ public void testCopyMultiApi() throws SQLException, IOException { out.close(); } String data = TestUtil.queryForString(con, "SELECT data FROM pg_temp.copy_api_test"); - assertEquals("The writes to the COPY should be in order", "abcd", data); + assertEquals("abcd", data, "The writes to the COPY should be in order"); } @Test - public void testSkipping() { + void skipping() { String sql = "COPY copytest FROM STDIN"; String at = "init"; int rowCount = -1; @@ -262,7 +277,7 @@ public void testSkipping() { rowCount = getCount(); } } catch (Exception e) { - if (!(skipChar == '\t')) { + if (skipChar != '\t') { // error expected when field separator consumed fail("testSkipping at " + at + " round " + skip + ": " + e.toString()); } @@ -271,8 +286,8 @@ public void testSkipping() { } @Test - public void testCopyOutByRow() throws SQLException, IOException { - testCopyInByRow(); // ensure we have some data. + void copyOutByRow() throws SQLException, IOException { + copyInByRow(); // ensure we have some data. String sql = "COPY copytest TO STDOUT"; CopyOut cp = copyAPI.copyOut(sql); int count = 0; @@ -280,7 +295,7 @@ public void testCopyOutByRow() throws SQLException, IOException { while ((buf = cp.readFromCopy()) != null) { count++; } - assertEquals(false, cp.isActive()); + assertFalse(cp.isActive()); assertEquals(dataRows, count); long rowCount = cp.getHandledRowCount(); @@ -291,8 +306,8 @@ public void testCopyOutByRow() throws SQLException, IOException { } @Test - public void testCopyOut() throws SQLException, IOException { - testCopyInByRow(); // ensure we have some data. + void copyOut() throws SQLException, IOException { + copyInByRow(); // ensure we have some data. String sql = "COPY copytest TO STDOUT"; ByteArrayOutputStream copydata = new ByteArrayOutputStream(); copyAPI.copyOut(sql, copydata); @@ -302,16 +317,15 @@ public void testCopyOut() throws SQLException, IOException { assertNotNull(copybytes); for (int i = 0, l = 0; i < origData.length; i++) { byte[] origBytes = origData[i].getBytes(); - assertTrue("Copy is shorter than original", copybytes.length >= l + origBytes.length); + assertTrue(copybytes.length >= l + origBytes.length, "Copy is shorter than original"); for (int j = 0; j < origBytes.length; j++, l++) { - assertEquals("content changed at byte#" + j + ": " + origBytes[j] + copybytes[l], - origBytes[j], copybytes[l]); + assertEquals(origBytes[j], copybytes[l], "content changed at byte#" + j + ": " + origBytes[j] + copybytes[l]); } } } @Test - public void testNonCopyOut() throws SQLException, IOException { + void nonCopyOut() throws SQLException, IOException { String sql = "SELECT 1"; try { copyAPI.copyOut(sql, new ByteArrayOutputStream()); @@ -323,7 +337,7 @@ public void testNonCopyOut() throws SQLException, IOException { } @Test - public void testNonCopyIn() throws SQLException, IOException { + void nonCopyIn() throws SQLException, IOException { String sql = "SELECT 1"; try { copyAPI.copyIn(sql, new ByteArrayInputStream(new byte[0])); @@ -335,7 +349,7 @@ public void testNonCopyIn() throws SQLException, IOException { } @Test - public void testStatementCopyIn() throws SQLException { + void statementCopyIn() throws SQLException { Statement stmt = con.createStatement(); try { stmt.execute("COPY copytest FROM STDIN"); @@ -348,8 +362,8 @@ public void testStatementCopyIn() throws SQLException { } @Test - public void testStatementCopyOut() throws SQLException { - testCopyInByRow(); // ensure we have some data. + void statementCopyOut() throws SQLException { + copyInByRow(); // ensure we have some data. Statement stmt = con.createStatement(); try { @@ -363,8 +377,8 @@ public void testStatementCopyOut() throws SQLException { } @Test - public void testCopyQuery() throws SQLException, IOException { - testCopyInByRow(); // ensure we have some data. + void copyQuery() throws SQLException, IOException { + copyInByRow(); // ensure we have some data. long count = copyAPI.copyOut("COPY (SELECT generate_series(1,1000)) TO STDOUT", new ByteArrayOutputStream()); @@ -372,15 +386,15 @@ public void testCopyQuery() throws SQLException, IOException { } @Test - public void testCopyRollback() throws SQLException { + void copyRollback() throws SQLException { con.setAutoCommit(false); - testCopyInByRow(); + copyInByRow(); con.rollback(); assertEquals(0, getCount()); } @Test - public void testChangeDateStyle() throws SQLException { + void changeDateStyle() throws SQLException { try { con.setAutoCommit(false); con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); @@ -403,7 +417,7 @@ public void testChangeDateStyle() throws SQLException { con.commit(); } catch (SQLException ex) { - // the with xxx is a syntax error which shoud return a state of 42601 + // the with xxx is a syntax error which should return a state of 42601 // if this fails the 'S' command is not being handled in the copy manager query handler assertEquals("42601", ex.getSQLState()); con.rollback(); @@ -411,7 +425,7 @@ public void testChangeDateStyle() throws SQLException { } @Test - public void testLockReleaseOnCancelFailure() throws SQLException, InterruptedException { + void lockReleaseOnCancelFailure() throws SQLException, InterruptedException { if (!TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)) { // pg_backend_pid() requires PostgreSQL 8.4+ return; @@ -447,7 +461,7 @@ public void testLockReleaseOnCancelFailure() throws SQLException, InterruptedExc } // Now we'll execute rollback on another thread so that if the - // deadlock _does_ occur the testcase doesn't just hange forever. + // deadlock _does_ occur the case doesn't just hange forever. Rollback rollback = new Rollback(con); rollback.start(); rollback.join(1000); @@ -485,10 +499,206 @@ public SQLException exception() { } } - private void acceptIOCause(SQLException e) throws SQLException { + private static void acceptIOCause(SQLException e) throws SQLException { + if (e.getSQLState().equals(PSQLState.CONNECTION_FAILURE.getState()) + || e.getSQLState().equals(PSQLState.CONNECTION_DOES_NOT_EXIST.getState())) { + // The test expects network exception, so CONNECTION_FAILURE looks good + return; + } if (!(e.getCause() instanceof IOException)) { throw e; } } + /** + * Tests that Connection.isValid() does not hang after a network failure + * during a COPY operation using PGCopyOutputStream. + * + * @see Issue 3957 + */ + @Test + @Timeout(value = 15, unit = TimeUnit.SECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) + void isValidShouldNotHangAfterCopyStreamNetworkFailure() throws Exception { + try (StrangeProxyServer proxyServer = new StrangeProxyServer(TestUtil.getServer(), + TestUtil.getPort())) { + Properties props = new Properties(); + TestUtil.setTestUrlProperty(props, PGProperty.PG_HOST, "localhost"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_PORT, + String.valueOf(proxyServer.getServerPort())); + try (Connection proxyCon = TestUtil.openDB(props)) { + TestUtil.createTempTable(proxyCon, "copytest_stream", "data text"); + + String copySql = "COPY copytest_stream (data) FROM STDIN WITH (FORMAT CSV)"; + + // Start COPY via PGCopyOutputStream then break the network + assertThrows(IOException.class, () -> { + try (PGCopyOutputStream copyOut = + new PGCopyOutputStream(proxyCon.unwrap(PgConnection.class), copySql)) { + // Write some data to ensure COPY is active + for (int i = 0; i < 10; i++) { + copyOut.write(("row" + i + "\n").getBytes(StandardCharsets.UTF_8)); + copyOut.flush(); + } + // Close the sockets to trigger an immediate IOException + proxyServer.closeAllClients(); + // Continue writing until the broken connection is detected + for (int i = 0; i < 100_000; i++) { + copyOut.write(("more-data-" + i + "\n").getBytes(StandardCharsets.UTF_8)); + copyOut.flush(); + } + } + }, "Expected IOException from broken connection during COPY"); + + assertTimeout(Duration.ofSeconds(10), () -> { + // This is the actual bug: isValid() should return false promptly, + // not hang indefinitely waiting for the COPY lock to be released. + assertFalse(proxyCon.isValid(5), + "isValid should return false for a broken connection, not hang"); + }, "Test timed out — isValid(5) or connection close hung due to unreleased COPY lock"); + } + } + } + + /** + * Tests that the COPY lock is released after a network failure during + * CopyIn.writeToCopy(), so subsequent operations do not hang. + * + * @see Issue 3957 + */ + @Test + @Timeout(value = 15, unit = TimeUnit.SECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) + void isValidShouldNotHangAfterWriteToCopyNetworkFailure() throws Exception { + try (StrangeProxyServer proxyServer = new StrangeProxyServer(TestUtil.getServer(), + TestUtil.getPort())) { + Properties props = new Properties(); + TestUtil.setTestUrlProperty(props, PGProperty.PG_HOST, "localhost"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_PORT, + String.valueOf(proxyServer.getServerPort())); + try (Connection proxyCon = TestUtil.openDB(props)) { + TestUtil.createTempTable(proxyCon, "copytest_write", "data text"); + + CopyManager manager = proxyCon.unwrap(PGConnection.class).getCopyAPI(); + CopyIn copyIn = manager.copyIn("COPY copytest_write (data) FROM STDIN WITH (FORMAT CSV)"); + + // Write some data to ensure COPY is active + byte[] row = "somedata\n".getBytes(StandardCharsets.UTF_8); + for (int i = 0; i < 10; i++) { + copyIn.writeToCopy(row, 0, row.length); + copyIn.flushCopy(); + } + + // Close the sockets to trigger an immediate IOException + proxyServer.closeAllClients(); + + // writeToCopy or flushCopy should fail with an IOException + assertThrows(SQLException.class, () -> { + for (int i = 0; i < 100_000; i++) { + copyIn.writeToCopy(row, 0, row.length); + copyIn.flushCopy(); + } + }, "Expected SQLException from broken connection during writeToCopy"); + + assertTimeout(Duration.ofSeconds(5), () -> { + assertFalse(proxyCon.isValid(3), + "isValid should return false for a broken connection, not hang"); + }, "isValid() hung — COPY lock was not released after writeToCopy failure"); + } + } + } + + /** + * Tests that the COPY lock is released after a network failure during + * CopyOut.readFromCopy(), so subsequent operations do not hang. + * + * @see Issue 3957 + */ + @Test + @Timeout(value = 15, unit = TimeUnit.SECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) + void isValidShouldNotHangAfterReadFromCopyNetworkFailure() throws Exception { + try (StrangeProxyServer proxyServer = new StrangeProxyServer(TestUtil.getServer(), + TestUtil.getPort())) { + Properties props = new Properties(); + TestUtil.setTestUrlProperty(props, PGProperty.PG_HOST, "localhost"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_PORT, + String.valueOf(proxyServer.getServerPort())); + try (Connection proxyCon = TestUtil.openDB(props)) { + // Insert enough data so the COPY TO STDOUT will have rows to read + try (Statement stmt = proxyCon.createStatement()) { + stmt.execute("CREATE TEMP TABLE copytest_read (data text)"); + stmt.execute( + "INSERT INTO copytest_read SELECT 'row' || g FROM generate_series(1, 10000) g"); + } + + CopyManager manager = proxyCon.unwrap(PGConnection.class).getCopyAPI(); + CopyOut copyOut = manager.copyOut("COPY copytest_read TO STDOUT"); + + // Read a few rows to ensure COPY is active + for (int i = 0; i < 5; i++) { + assertNotNull(copyOut.readFromCopy(), "Expected data row from COPY TO STDOUT"); + } + + // Close the sockets to trigger an immediate IOException on next read + proxyServer.closeAllClients(); + + assertThrows(SQLException.class, () -> { + //noinspection StatementWithEmptyBody + while (copyOut.readFromCopy() != null) { + // drain until failure + } + }, "Expected SQLException from broken connection during readFromCopy"); + + assertTimeout(Duration.ofSeconds(5), () -> { + assertFalse(proxyCon.isValid(3), + "isValid should return false for a broken connection, not hang"); + }, "isValid() hung — COPY lock was not released after readFromCopy failure"); + } + } + } + + /** + * Tests that the COPY lock is released after a network failure during + * PGCopyInputStream.read(), so subsequent operations do not hang. + * + * @see Issue 3957 + */ + @Test + @Timeout(value = 15, unit = TimeUnit.SECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) + void isValidShouldNotHangAfterCopyInputStreamNetworkFailure() throws Exception { + try (StrangeProxyServer proxyServer = new StrangeProxyServer(TestUtil.getServer(), + TestUtil.getPort())) { + Properties props = new Properties(); + TestUtil.setTestUrlProperty(props, PGProperty.PG_HOST, "localhost"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_PORT, + String.valueOf(proxyServer.getServerPort())); + try (Connection proxyCon = TestUtil.openDB(props)) { + try (Statement stmt = proxyCon.createStatement()) { + stmt.execute("CREATE TEMP TABLE copytest_instream (data text)"); + stmt.execute( + "INSERT INTO copytest_instream SELECT 'row' || g FROM generate_series(1, 10000) g"); + } + + CopyManager manager = proxyCon.unwrap(PGConnection.class).getCopyAPI(); + + assertThrows(IOException.class, () -> { + try (PGCopyInputStream copyIn = new PGCopyInputStream( + manager.copyOut("COPY copytest_instream TO STDOUT"))) { + byte[] buf = new byte[1024]; + int bytesRead = 0; + // Read a bit of data, then break the connection + while (copyIn.read(buf) >= 0) { + bytesRead += buf.length; + if (bytesRead > 4096) { + proxyServer.closeAllClients(); + } + } + } + }, "Expected IOException from broken connection during PGCopyInputStream.read()"); + + assertTimeout(Duration.ofSeconds(5), () -> { + assertFalse(proxyCon.isValid(3), + "isValid should return false for a broken connection, not hang"); + }, "isValid() hung — COPY lock was not released after CopyInputStream failure"); + } + } + } } diff --git a/src/test/java/org/postgresql/test/jdbc2/CursorFetchSqlTransactionTest.java b/src/test/java/org/postgresql/test/jdbc2/CursorFetchSqlTransactionTest.java new file mode 100644 index 0000000..4ad9e17 --- /dev/null +++ b/src/test/java/org/postgresql/test/jdbc2/CursorFetchSqlTransactionTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2004, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.postgresql.core.BaseConnection; +import org.postgresql.core.TransactionState; +import org.postgresql.jdbc.PreferQueryMode; +import org.postgresql.test.TestUtil; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.stream.Stream; + +/** + * Tests that cursor-based fetching works when a transaction is started via SQL commands + * ({@code START TRANSACTION}, {@code BEGIN}) instead of {@code setAutoCommit(false)}. + * + * @see issue 3993 + */ +@Isolated("pg_cursors counts are unreliable when tests execute concurrently") +class CursorFetchSqlTransactionTest extends BaseTest4 { + + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + PreferQueryMode mode = con.unwrap(BaseConnection.class).getPreferQueryMode(); + assumeTrue( + mode != PreferQueryMode.SIMPLE && mode != PreferQueryMode.EXTENDED_FOR_PREPARED, + "server-side cursors require extended query protocol for non-prepared statements"); + TestUtil.createTable(con, "test_fetch", "value integer"); + TestUtil.execute(con, "insert into test_fetch(value) select generate_series(0, 99)"); + } + } + + @AfterAll + static void dropTables() throws SQLException { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "test_fetch"); + } + } + + /** + * Counts the number of pgjdbc server-side cursors (named portals with the C_ prefix) + * currently open on the connection. + */ + private int countPgjdbcCursors() throws SQLException { + try (Statement check = con.createStatement(); + ResultSet rs = check.executeQuery( + "SELECT count(*) FROM pg_cursors WHERE name LIKE 'C\\_%'");) { + rs.next(); + return rs.getInt(1); + } + } + + static Stream startTransactionOptions() { + return Stream.of( + "BEGIN", + "START TRANSACTION", + "START TRANSACTION READ ONLY" + ); + } + + @ParameterizedTest + @MethodSource("startTransactionOptions") + void testCursorWithStartTransaction(String startTransaction) throws Exception { + try (Statement stmt = con.createStatement()) { + stmt.execute(startTransaction); + assertTrue(con.getAutoCommit(), + "autoCommit should still report true after START TRANSACTION"); + assertEquals(TransactionState.OPEN, + con.unwrap(BaseConnection.class).getTransactionState(), + "connection should be in OPEN transaction state after START TRANSACTION"); + + int cursorsBefore = countPgjdbcCursors(); + + stmt.setFetchSize(10); + try (ResultSet rs = stmt.executeQuery("SELECT * FROM test_fetch ORDER BY value")) { + assertEquals(cursorsBefore + 1, countPgjdbcCursors(), + () -> "a server-side cursor should be created when using fetchSize with " + startTransaction); + + int count = 0; + while (rs.next()) { + assertEquals(count, rs.getInt(1)); + count++; + } + + assertEquals(100, count, + () -> "cursor-based fetch with " + startTransaction + " should return all rows"); + } + } + } +} diff --git a/src/test/java/org/postgresql/test/jdbc2/CursorFetchTest.java b/src/test/java/org/postgresql/test/jdbc2/CursorFetchTest.java index c4b6d94..28a829b 100644 --- a/src/test/java/org/postgresql/test/jdbc2/CursorFetchTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/CursorFetchTest.java @@ -5,45 +5,65 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.postgresql.PGConnection; import org.postgresql.test.TestUtil; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Collection; +import java.util.function.Supplier; -/* +/** * Tests for using non-zero setFetchSize(). */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class CursorFetchTest extends BaseTest4 { public CursorFetchTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "test_fetch", "value integer"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "test_fetch"); + } + } + @Override public void setUp() throws Exception { super.setUp(); - TestUtil.createTable(con, "test_fetch", "value integer"); + TestUtil.execute(con, "TRUNCATE test_fetch"); con.setAutoCommit(false); } @@ -54,13 +74,12 @@ public void tearDown() throws SQLException { } con.setAutoCommit(true); - TestUtil.dropTable(con, "test_fetch"); super.tearDown(); } protected void createRows(int count) throws Exception { PreparedStatement stmt = con.prepareStatement("insert into test_fetch(value) values(?)"); - for (int i = 0; i < count; ++i) { + for (int i = 0; i < count; i++) { stmt.setInt(1, i); stmt.executeUpdate(); } @@ -78,15 +97,17 @@ public void testBasicFetch() throws Exception { assertEquals(testSize, stmt.getFetchSize()); ResultSet rs = stmt.executeQuery(); - assertEquals(testSize, rs.getFetchSize()); + if (!con.unwrap(PGConnection.class).getAdaptiveFetch()) { + assertEquals(testSize, rs.getFetchSize(), "ResultSet.fetchSize should not change after query execution"); + } int count = 0; while (rs.next()) { - assertEquals("query value error with fetch size " + testSize, count, rs.getInt(1)); + assertEquals(count, rs.getInt(1), () -> "query value error with fetch size " + testSize); ++count; } - assertEquals("total query size error with fetch size " + testSize, 100, count); + assertEquals(100, count, () -> "total query size error with fetch size " + testSize); } } @@ -104,31 +125,29 @@ public void testScrollableFetch() throws Exception { assertEquals(testSize, stmt.getFetchSize()); ResultSet rs = stmt.executeQuery(); - assertEquals(testSize, rs.getFetchSize()); + if (!con.unwrap(PGConnection.class).getAdaptiveFetch()) { + assertEquals(testSize, rs.getFetchSize(), "ResultSet.fetchSize should not change after query execution"); + } - for (int j = 0; j <= 50; ++j) { - assertTrue("ran out of rows at position " + j + " with fetch size " + testSize, rs.next()); - assertEquals("query value error with fetch size " + testSize, j, rs.getInt(1)); + for (int j = 0; j <= 50; j++) { + assertTrue(rs.next(), "ran out of rows at position " + j + " with fetch size " + testSize); + assertEquals(j, rs.getInt(1), () -> "query value error with fetch size " + testSize); } int position = 50; - for (int j = 1; j < 100; ++j) { - for (int k = 0; k < j; ++k) { + for (int j = 1; j < 100; j++) { + for (int k = 0; k < j; k++) { if (j % 2 == 0) { ++position; - assertTrue("ran out of rows doing a forward fetch on iteration " + j + "/" + k - + " at position " + position + " with fetch size " + testSize, rs.next()); + assertTrue(rs.next(), "ran out of rows doing a forward fetch on iteration " + j + "/" + k + + " at position " + position + " with fetch size " + testSize); } else { --position; - assertTrue( - "ran out of rows doing a reverse fetch on iteration " + j + "/" + k - + " at position " + position + " with fetch size " + testSize, - rs.previous()); + assertTrue(rs.previous(), "ran out of rows doing a reverse fetch on iteration " + j + "/" + k + + " at position " + position + " with fetch size " + testSize); } - assertEquals( - "query value error on iteration " + j + "/" + k + " with fetch size " + testSize, - position, rs.getInt(1)); + assertEquals(position, rs.getInt(1), "query value error on iteration " + j + "/" + k + " with fetch size " + testSize); } } } @@ -147,23 +166,25 @@ public void testScrollableAbsoluteFetch() throws Exception { assertEquals(testSize, stmt.getFetchSize()); ResultSet rs = stmt.executeQuery(); - assertEquals(testSize, rs.getFetchSize()); + if (!con.unwrap(PGConnection.class).getAdaptiveFetch()) { + assertEquals(testSize, rs.getFetchSize(), "ResultSet.fetchSize should not change after query execution"); + } int position = 50; - assertTrue("ran out of rows doing an absolute fetch at " + position + " with fetch size " - + testSize, rs.absolute(position + 1)); - assertEquals("query value error with fetch size " + testSize, position, rs.getInt(1)); + assertTrue(rs.absolute(position + 1), "ran out of rows doing an absolute fetch at " + position + " with fetch size " + + testSize); + assertEquals(position, rs.getInt(1), () -> "query value error with fetch size " + testSize); - for (int j = 1; j < 100; ++j) { + for (int j = 1; j < 100; j++) { if (j % 2 == 0) { position += j; } else { position -= j; } - assertTrue("ran out of rows doing an absolute fetch at " + position + " on iteration " + j - + " with fetchsize" + testSize, rs.absolute(position + 1)); - assertEquals("query value error with fetch size " + testSize, position, rs.getInt(1)); + assertTrue(rs.absolute(position + 1), "ran out of rows doing an absolute fetch at " + position + " on iteration " + j + + " with fetchsize" + testSize); + assertEquals(position, rs.getInt(1), () -> "query value error with fetch size " + testSize); } } } @@ -291,27 +312,27 @@ public void testSingleRowResultPositioning() throws Exception { ResultSet rs = stmt.executeQuery("select * from test_fetch order by value"); msg = "before-first row positioning error with fetchsize=" + size; - assertTrue(msg, rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); - assertTrue(msg, !rs.isLast()); + assertTrue(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); + assertFalse(rs.isLast(), msg); msg = "row 1 positioning error with fetchsize=" + size; - assertTrue(msg, rs.next()); + assertTrue(rs.next(), msg); - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); - assertTrue(msg, rs.isFirst()); - assertTrue(msg, rs.isLast()); - assertEquals(msg, 0, rs.getInt(1)); + assertFalse(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); + assertTrue(rs.isFirst(), msg); + assertTrue(rs.isLast(), msg); + assertEquals(0, rs.getInt(1), msg); msg = "after-last row positioning error with fetchsize=" + size; - assertTrue(msg, !rs.next()); + assertFalse(rs.next(), msg); - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); - assertTrue(msg, !rs.isLast()); + assertFalse(rs.isBeforeFirst(), msg); + assertTrue(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); + assertFalse(rs.isLast(), msg); rs.close(); stmt.close(); @@ -331,38 +352,38 @@ public void testMultiRowResultPositioning() throws Exception { ResultSet rs = stmt.executeQuery("select * from test_fetch order by value"); msg = "before-first row positioning error with fetchsize=" + size; - assertTrue(msg, rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); - assertTrue(msg, !rs.isLast()); + assertTrue(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); + assertFalse(rs.isLast(), msg); - for (int j = 0; j < 100; ++j) { + for (int j = 0; j < 100; j++) { msg = "row " + j + " positioning error with fetchsize=" + size; - assertTrue(msg, rs.next()); - assertEquals(msg, j, rs.getInt(1)); + assertTrue(rs.next(), msg); + assertEquals(j, rs.getInt(1), msg); - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); + assertFalse(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); if (j == 0) { - assertTrue(msg, rs.isFirst()); + assertTrue(rs.isFirst(), msg); } else { - assertTrue(msg, !rs.isFirst()); + assertFalse(rs.isFirst(), msg); } if (j == 99) { - assertTrue(msg, rs.isLast()); + assertTrue(rs.isLast(), msg); } else { - assertTrue(msg, !rs.isLast()); + assertFalse(rs.isLast(), msg); } } msg = "after-last row positioning error with fetchsize=" + size; - assertTrue(msg, !rs.next()); + assertFalse(rs.next(), msg); - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); - assertTrue(msg, !rs.isLast()); + assertFalse(rs.isBeforeFirst(), msg); + assertTrue(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); + assertFalse(rs.isLast(), msg); rs.close(); stmt.close(); @@ -387,7 +408,7 @@ public void testMultistatement() throws Exception { "insert into test_fetch(value) values(100); select * from test_fetch order by value"); stmt.setFetchSize(10); - assertTrue(!stmt.execute()); // INSERT + assertFalse(stmt.execute()); // INSERT assertTrue(stmt.getMoreResults()); // SELECT ResultSet rs = stmt.getResultSet(); int count = 0; @@ -448,31 +469,31 @@ public void testRowResultPositioningWithoutIsLast() throws Exception { ResultSet rs = stmt.executeQuery("select * from test_fetch order by value"); msg = "before-first row positioning error with fetchsize=" + size; - assertTrue(msg, rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); + assertTrue(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); - for (int j = 0; j < rowCount; ++j) { + for (int j = 0; j < rowCount; j++) { msg = "row " + j + " positioning error with fetchsize=" + size; - assertTrue(msg, rs.next()); - assertEquals(msg, j, rs.getInt(1)); + assertTrue(rs.next(), msg); + assertEquals(j, rs.getInt(1), msg); - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); + assertFalse(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); if (j == 0) { - assertTrue(msg, rs.isFirst()); + assertTrue(rs.isFirst(), msg); } else { - assertTrue(msg, !rs.isFirst()); + assertFalse(rs.isFirst(), msg); } } msg = "after-last row positioning error with fetchsize=" + size; - assertTrue(msg, !rs.next()); + assertFalse(rs.next(), msg); - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); - assertTrue(msg, !rs.isLast()); + assertFalse(rs.isBeforeFirst(), msg); + assertTrue(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); + assertFalse(rs.isLast(), msg); rs.close(); stmt.close(); @@ -488,17 +509,17 @@ public void testNoRowResultPositioning() throws Exception { stmt.setFetchSize(size); ResultSet rs = stmt.executeQuery("select * from test_fetch order by value"); - String msg = "no row (empty resultset) positioning error with fetchsize=" + size; - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); - assertTrue(msg, !rs.isLast()); - - assertTrue(msg, !rs.next()); - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); - assertTrue(msg, !rs.isLast()); + Supplier msg = () -> "no row (empty resultset) positioning error with fetchsize=" + size; + assertFalse(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); + assertFalse(rs.isLast(), msg); + + assertFalse(rs.next(), msg); + assertFalse(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); + assertFalse(rs.isLast(), msg); rs.close(); stmt.close(); @@ -515,23 +536,23 @@ public void testScrollableNoRowResultPositioning() throws Exception { stmt.setFetchSize(size); ResultSet rs = stmt.executeQuery("select * from test_fetch order by value"); - String msg = "no row (empty resultset) positioning error with fetchsize=" + size; - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); - assertTrue(msg, !rs.isLast()); - - assertTrue(msg, !rs.first()); - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); - assertTrue(msg, !rs.isLast()); - - assertTrue(msg, !rs.next()); - assertTrue(msg, !rs.isBeforeFirst()); - assertTrue(msg, !rs.isAfterLast()); - assertTrue(msg, !rs.isFirst()); - assertTrue(msg, !rs.isLast()); + Supplier msg = () -> "no row (empty resultset) positioning error with fetchsize=" + size; + assertFalse(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); + assertFalse(rs.isLast(), msg); + + assertFalse(rs.first(), msg); + assertFalse(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); + assertFalse(rs.isLast(), msg); + + assertFalse(rs.next(), msg); + assertFalse(rs.isBeforeFirst(), msg); + assertFalse(rs.isAfterLast(), msg); + assertFalse(rs.isFirst(), msg); + assertFalse(rs.isLast(), msg); rs.close(); stmt.close(); diff --git a/src/test/java/org/postgresql/test/jdbc2/CustomTypeWithBinaryTransferTest.java b/src/test/java/org/postgresql/test/jdbc2/CustomTypeWithBinaryTransferTest.java new file mode 100644 index 0000000..fb9c2f5 --- /dev/null +++ b/src/test/java/org/postgresql/test/jdbc2/CustomTypeWithBinaryTransferTest.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.PGConnection; +import org.postgresql.core.BaseConnection; +import org.postgresql.core.Oid; +import org.postgresql.core.QueryExecutor; +import org.postgresql.jdbc.PreferQueryMode; +import org.postgresql.test.TestUtil; +import org.postgresql.util.PGBinaryObject; +import org.postgresql.util.PGobject; + +// import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; + +/** + * TestCase to test handling of binary types for custom objects. + */ +@ParameterizedClass +@MethodSource("data") +public class CustomTypeWithBinaryTransferTest extends BaseTest4 { + // define an oid of a binary type for testing, POINT is used here as it already exists in the + // database and requires no complex own type definition + private static final int CUSTOM_TYPE_OID = Oid.POINT; + + public CustomTypeWithBinaryTransferTest(BinaryMode binaryMode) { + setBinaryMode(binaryMode); + } + + public static Iterable data() { + Collection ids = new ArrayList<>(); + for (BinaryMode binaryMode : BinaryMode.values()) { + ids.add(new Object[]{binaryMode}); + } + return ids; + } + + /** + * Set up the fixture for this testcase: the tables for this test. + * + * @throws SQLException if a database error occurs + */ + @BeforeAll + public static void createTestTable() throws SQLException { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "test_binary_pgobject", "id integer,name text,geom point"); + } + } + + /** + * Tear down the fixture for this test case. + * + * @throws SQLException if a database error occurs + */ + @AfterAll + public static void dropTestTable() throws SQLException { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "test_binary_pgobject"); + } + } + + @Override + public void setUp() throws Exception { + super.setUp(); + QueryExecutor queryExecutor = con.unwrap(BaseConnection.class).getQueryExecutor(); + queryExecutor.removeBinarySendOid(CUSTOM_TYPE_OID); + queryExecutor.removeBinaryReceiveOid(CUSTOM_TYPE_OID); + assertBinaryForReceive(CUSTOM_TYPE_OID, false, + () -> "Binary transfer for point type should be disabled since we've deactivated it in " + + "updateProperties"); + + assertBinaryForSend(CUSTOM_TYPE_OID, false, + () -> "Binary transfer for point type should be disabled since we've deactivated it in " + + "updateProperties"); + try (Statement st = con.createStatement()) { + st.execute("DELETE FROM test_binary_pgobject"); + st.execute("INSERT INTO test_binary_pgobject(id,name,geom) values(1,'Test',Point(1,2))"); + } + } + + /** + * Make sure custom binary types are handled automatically. + * + * @throws SQLException if a database error occurs + */ + @Test + public void testCustomBinaryTypes() throws SQLException { + PGConnection pgconn = con.unwrap(PGConnection.class); + + // make sure the test type implements PGBinaryObject + assertTrue(PGBinaryObject.class.isAssignableFrom(TestCustomType.class), "test type should implement PGBinaryObject"); + + // now define a custom type, which will add it to the binary sent/received OIDs (if the type + // implements PGBinaryObject) + pgconn.addDataType("point", TestCustomType.class); + // check if the type was marked for binary transfer + if (preferQueryMode != PreferQueryMode.SIMPLE) { + assertBinaryForReceive(CUSTOM_TYPE_OID, true, + () -> "Binary transfer for point type should be activated by addDataType(..., " + + "TestCustomType.class)"); + assertBinaryForSend(CUSTOM_TYPE_OID, true, + () -> "Binary transfer for point type should be activated by addDataType(..., " + + "TestCustomType.class)"); + } + + TestCustomType co; + // Try with PreparedStatement + try (PreparedStatement pst = con.prepareStatement("SELECT geom FROM test_binary_pgobject WHERE id=?")) { + pst.setInt(1, 1); + try (ResultSet rs = pst.executeQuery()) { + assertTrue(rs.next(), "rs.next()"); + Object o = rs.getObject(1); + co = (TestCustomType) o; + // now binary transfer should be working + if (preferQueryMode == PreferQueryMode.SIMPLE) { + assertEquals("text", co.wasReadBinary() ? "binary" : "text", "reading via prepared statement: TestCustomType.wasReadBinary() should use text encoding since preferQueryMode=SIMPLE"); + } else { + assertEquals(binaryMode == BinaryMode.FORCE ? "binary" : "text", co.wasReadBinary() ? "binary" : "text", "reading via prepared statement: TestCustomType.wasReadBinary() should use match binary mode requested by the test"); + } + } + } + + // ensure flag is still unset + assertFalse(co.wasWrittenBinary(), "wasWrittenBinary should be false since we have not written the object yet"); + // now try to write it + try (PreparedStatement pst = + con.prepareStatement("INSERT INTO test_binary_pgobject(id,geom) VALUES(?,?)")) { + pst.setInt(1, 2); + pst.setObject(2, co); + pst.executeUpdate(); + // make sure transfer was binary + if (preferQueryMode == PreferQueryMode.SIMPLE) { + assertEquals( + "text", + co.wasWrittenBinary() ? "binary" : "text", + "writing via prepared statement: TestCustomType.wasWrittenBinary() should use text encoding since preferQueryMode=SIMPLE"); + } else { + assertEquals( + binaryMode == BinaryMode.FORCE ? "binary" : "text", + co.wasWrittenBinary() ? "binary" : "text", + "writing via prepared statement: TestCustomType.wasWrittenBinary() should use match binary mode requested by the test"); + } + } + } + + /** + * Custom type that supports binary format. + */ + @SuppressWarnings("serial") + public static class TestCustomType extends PGobject implements PGBinaryObject { + private byte /* @Nullable */ [] byteValue; + private boolean wasReadBinary; + private boolean wasWrittenBinary; + + @Override + public /* @Nullable */ String getValue() { + // set flag + this.wasWrittenBinary = false; + return super.getValue(); + } + + @Override + public int lengthInBytes() { + if (byteValue != null) { + return byteValue.length; + } else { + return 0; + } + } + + @Override + public void setByteValue(byte[] value, int offset) throws SQLException { + this.wasReadBinary = true; + // remember the byte value + byteValue = new byte[value.length - offset]; + System.arraycopy(value, offset, byteValue, 0, byteValue.length); + } + + @Override + public void setValue(/* @Nullable */ String value) throws SQLException { + super.setValue(value); + // set flag + this.wasReadBinary = false; + } + + @Override + public void toBytes(byte[] bytes, int offset) { + if (byteValue != null) { + // make sure array is large enough + if ((bytes.length - offset) <= byteValue.length) { + // copy data + System.arraycopy(byteValue, 0, bytes, offset, byteValue.length); + } else { + throw new IllegalArgumentException( + "byte array is too small, expected: " + byteValue.length + " got: " + + (bytes.length - offset)); + } + } else { + throw new IllegalStateException("no geometry has been set"); + } + // set flag + this.wasWrittenBinary = true; + } + + /** + * Checks, if this type was read in binary mode. + * + * @return true for binary mode, else false + */ + public boolean wasReadBinary() { + return this.wasReadBinary; + } + + /** + * Checks, if this type was written in binary mode. + * + * @return true for binary mode, else false + */ + public boolean wasWrittenBinary() { + return this.wasWrittenBinary; + } + } +} diff --git a/src/test/java/org/postgresql/test/jdbc2/DatabaseEncodingTest.java b/src/test/java/org/postgresql/test/jdbc2/DatabaseEncodingTest.java index 494ddd2..89c7971 100644 --- a/src/test/java/org/postgresql/test/jdbc2/DatabaseEncodingTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/DatabaseEncodingTest.java @@ -5,18 +5,16 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.Encoding; -import org.postgresql.test.SlowTests; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; @@ -25,20 +23,20 @@ import java.util.Arrays; /* - * Test case for various encoding problems. - * - * Ensure that we can do a round-trip of all server-supported unicode values without trashing them, - * and that bad encodings are detected. - */ -public class DatabaseEncodingTest { +* Test case for various encoding problems. +* +* Ensure that we can do a round-trip of all server-supported unicode values without trashing them, +* and that bad encodings are detected. +*/ +class DatabaseEncodingTest { private static final int STEP = 100; private Connection con; // Set up the fixture for this testcase: a connection to a database with // a table for this test. - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { con = TestUtil.openDB(); TestUtil.createTempTable(con, "testdbencoding", "unicode_ordinal integer primary key not null, unicode_string varchar(" + STEP + ")"); @@ -48,15 +46,15 @@ public void setUp() throws Exception { } // Tear down the fixture for this test case. - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { con.setAutoCommit(true); TestUtil.closeDB(con); } private static String dumpString(String s) { StringBuffer sb = new StringBuffer(s.length() * 6); - for (int i = 0; i < s.length(); ++i) { + for (int i = 0; i < s.length(); i++) { sb.append("\\u"); char c = s.charAt(i); sb.append(Integer.toHexString((c >> 12) & 15)); @@ -68,10 +66,9 @@ private static String dumpString(String s) { } @Test - @Category(SlowTests.class) - public void testEncoding() throws Exception { + void encoding() throws Exception { String databaseEncoding = TestUtil.queryForString(con, "SELECT getdatabaseencoding()"); - Assume.assumeTrue("Database encoding must be UTF8", databaseEncoding.equals("UTF8")); + assumeTrue("UTF8".equals(databaseEncoding), "Database encoding must be UTF8"); boolean testHighUnicode = true; @@ -82,7 +79,7 @@ public void testEncoding() throws Exception { for (int i = 1; i < 0xd800; i += STEP) { int count = (i + STEP) > 0xd800 ? 0xd800 - i : STEP; char[] testChars = new char[count]; - for (int j = 0; j < count; ++j) { + for (int j = 0; j < count; j++) { testChars[j] = (char) (i + j); } @@ -96,7 +93,7 @@ public void testEncoding() throws Exception { for (int i = 0xe000; i < 0x10000; i += STEP) { int count = (i + STEP) > 0x10000 ? 0x10000 - i : STEP; char[] testChars = new char[count]; - for (int j = 0; j < count; ++j) { + for (int j = 0; j < count; j++) { testChars[j] = (char) (i + j); } @@ -111,7 +108,7 @@ public void testEncoding() throws Exception { for (int i = 0x10000; i < 0x110000; i += STEP) { int count = (i + STEP) > 0x110000 ? 0x110000 - i : STEP; char[] testChars = new char[count * 2]; - for (int j = 0; j < count; ++j) { + for (int j = 0; j < count; j++) { testChars[j * 2] = (char) (0xd800 + ((i + j - 0x10000) >> 10)); testChars[j * 2 + 1] = (char) (0xdc00 + ((i + j - 0x10000) & 0x3ff)); } @@ -140,14 +137,15 @@ public void testEncoding() throws Exception { int count = (i + STEP) > 0xd800 ? 0xd800 - i : STEP; char[] testChars = new char[count]; - for (int j = 0; j < count; ++j) { + for (int j = 0; j < count; j++) { testChars[j] = (char) (i + j); } String testString = new String(testChars); - assertEquals("Test string: " + dumpString(testString), dumpString(testString), - dumpString(rs.getString(2))); + assertEquals(dumpString(testString), + dumpString(rs.getString(2)), + "Test string: " + dumpString(testString)); } for (int i = 0xe000; i < 0x10000; i += STEP) { @@ -156,14 +154,15 @@ public void testEncoding() throws Exception { int count = (i + STEP) > 0x10000 ? 0x10000 - i : STEP; char[] testChars = new char[count]; - for (int j = 0; j < count; ++j) { + for (int j = 0; j < count; j++) { testChars[j] = (char) (i + j); } String testString = new String(testChars); - assertEquals("Test string: " + dumpString(testString), dumpString(testString), - dumpString(rs.getString(2))); + assertEquals(dumpString(testString), + dumpString(rs.getString(2)), + "Test string: " + dumpString(testString)); } if (testHighUnicode) { @@ -173,26 +172,27 @@ public void testEncoding() throws Exception { int count = (i + STEP) > 0x110000 ? 0x110000 - i : STEP; char[] testChars = new char[count * 2]; - for (int j = 0; j < count; ++j) { + for (int j = 0; j < count; j++) { testChars[j * 2] = (char) (0xd800 + ((i + j - 0x10000) >> 10)); testChars[j * 2 + 1] = (char) (0xdc00 + ((i + j - 0x10000) & 0x3ff)); } String testString = new String(testChars); - assertEquals("Test string: " + dumpString(testString), dumpString(testString), - dumpString(rs.getString(2))); + assertEquals(dumpString(testString), + dumpString(rs.getString(2)), + "Test string: " + dumpString(testString)); } } } @Test - public void testUTF8Decode() throws Exception { + void uTF8Decode() throws Exception { // Tests for our custom UTF-8 decoder. Encoding utf8Encoding = Encoding.getJVMEncoding("UTF-8"); - for (int ch = 0; ch < 0x110000; ++ch) { + for (int ch = 0; ch < 0x110000; ch++) { if (ch >= 0xd800 && ch < 0xe000) { continue; // Surrogate range. } @@ -209,10 +209,12 @@ public void testUTF8Decode() throws Exception { String jvmDecoding = new String(jvmEncoding, 0, jvmEncoding.length, "UTF-8"); String ourDecoding = utf8Encoding.decode(jvmEncoding, 0, jvmEncoding.length); - assertEquals("Test string: " + dumpString(testString), dumpString(testString), - dumpString(jvmDecoding)); - assertEquals("Test string: " + dumpString(testString), dumpString(testString), - dumpString(ourDecoding)); + assertEquals(dumpString(testString), + dumpString(jvmDecoding), + "Test string: " + dumpString(testString)); + assertEquals(dumpString(testString), + dumpString(ourDecoding), + "Test string: " + dumpString(testString)); } } @@ -220,7 +222,7 @@ public void testUTF8Decode() throws Exception { * Tests that invalid utf-8 values are replaced with the unicode replacement chart. */ @Test - public void testTruncatedUTF8Decode() throws Exception { + void truncatedUTF8Decode() throws Exception { Encoding utf8Encoding = Encoding.getJVMEncoding("UTF-8"); byte[][] shortSequences = new byte[][]{{(byte) 0xc0}, // Second byte must be present @@ -234,22 +236,22 @@ public void testTruncatedUTF8Decode() throws Exception { }; byte[] paddedSequence = new byte[32]; - for (int i = 0; i < shortSequences.length; ++i) { + for (int i = 0; i < shortSequences.length; i++) { byte[] sequence = shortSequences[i]; String expected = "\uFFFD"; - for (int j = 1; j < sequence.length; ++j) { + for (int j = 1; j < sequence.length; j++) { expected += "\uFFFD"; } String str = utf8Encoding.decode(sequence, 0, sequence.length); - assertEquals("itr:" + i, expected, str); + assertEquals(expected, str, "itr:" + i); // Try it with padding and a truncated length. Arrays.fill(paddedSequence, (byte) 0); System.arraycopy(sequence, 0, paddedSequence, 0, sequence.length); str = utf8Encoding.decode(paddedSequence, 0, sequence.length); - assertEquals("itr:" + i, expected, str); + assertEquals(expected, str, "itr:" + i); } } } diff --git a/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataCacheTest.java b/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataCacheTest.java index 78e10dc..55814a3 100644 --- a/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataCacheTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataCacheTest.java @@ -5,16 +5,16 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.postgresql.core.TypeInfo; import org.postgresql.jdbc.PgConnection; import org.postgresql.test.TestUtil; import org.postgresql.util.TestLogHandler; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.SQLException; import java.util.List; @@ -25,10 +25,10 @@ import java.util.regex.Pattern; /* - * Tests for caching of DatabaseMetadata - * - */ -public class DatabaseMetaDataCacheTest { +* Tests for caching of DatabaseMetadata +* +*/ +class DatabaseMetaDataCacheTest { private PgConnection con; private TestLogHandler log; private Logger driverLogger; @@ -37,9 +37,9 @@ public class DatabaseMetaDataCacheTest { private static final Pattern SQL_TYPE_QUERY_LOG_FILTER = Pattern.compile("querying SQL typecode for pg type"); private static final Pattern SQL_TYPE_CACHE_LOG_FILTER = Pattern.compile("caching all SQL typecodes"); - @Before - public void setUp() throws Exception { - con = (PgConnection)TestUtil.openDB(); + @BeforeEach + void setUp() throws Exception { + con = (PgConnection) TestUtil.openDB(); log = new TestLogHandler(); driverLogger = LogManager.getLogManager().getLogger("org.postgresql"); driverLogger.addHandler(log); @@ -47,8 +47,8 @@ public void setUp() throws Exception { driverLogger.setLevel(Level.ALL); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(con); driverLogger.removeHandler(log); driverLogger.setLevel(driverLogLevel); @@ -56,34 +56,34 @@ public void tearDown() throws Exception { } @Test - public void testGetSQLTypeQueryCache() throws SQLException { + void getSQLTypeQueryCache() throws SQLException { TypeInfo ti = con.getTypeInfo(); List typeQueries = log.getRecordsMatching(SQL_TYPE_QUERY_LOG_FILTER); assertEquals(0, typeQueries.size()); - ti.getSQLType("box"); // this must be a type not in the hardcoded 'types' list + ti.getSQLType("xid"); // this must be a type not in the hardcoded 'types' list typeQueries = log.getRecordsMatching(SQL_TYPE_QUERY_LOG_FILTER); assertEquals(1, typeQueries.size()); - ti.getSQLType("box"); // this time it should be retrieved from the cache + ti.getSQLType("xid"); // this time it should be retrieved from the cache typeQueries = log.getRecordsMatching(SQL_TYPE_QUERY_LOG_FILTER); assertEquals(1, typeQueries.size()); } @Test - public void testGetTypeInfoUsesCache() throws SQLException { + void getTypeInfoUsesCache() throws SQLException { con.getMetaData().getTypeInfo(); List typeCacheQuery = log.getRecordsMatching(SQL_TYPE_CACHE_LOG_FILTER); - assertEquals("PgDatabaseMetadata.getTypeInfo() did not cache SQL typecodes", 1, typeCacheQuery.size()); + assertEquals(1, typeCacheQuery.size(), "PgDatabaseMetadata.getTypeInfo() did not cache SQL typecodes"); List typeQueries = log.getRecordsMatching(SQL_TYPE_QUERY_LOG_FILTER); - assertEquals("PgDatabaseMetadata.getTypeInfo() resulted in individual queries for SQL typecodes", 0, typeQueries.size()); + assertEquals(0, typeQueries.size(), "PgDatabaseMetadata.getTypeInfo() resulted in individual queries for SQL typecodes"); } @Test - public void testTypeForAlias() { + void typeForAlias() { TypeInfo ti = con.getTypeInfo(); assertEquals("bool", ti.getTypeForAlias("boolean")); assertEquals("bool", ti.getTypeForAlias("Boolean")); diff --git a/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataPropertiesTest.java b/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataPropertiesTest.java index d313ec9..ebb44a8 100644 --- a/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataPropertiesTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataPropertiesTest.java @@ -5,36 +5,39 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.postgresql.Driver; +import org.postgresql.PGConnection; import org.postgresql.test.TestUtil; +import org.postgresql.util.DriverInfo; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; /* - * TestCase to test the internal functionality of org.postgresql.jdbc2.DatabaseMetaData's various - * properties. Methods which return a ResultSet are tested elsewhere. This avoids a complicated - * setUp/tearDown for something like assertTrue(dbmd.nullPlusNonNullIsNull()); - */ -public class DatabaseMetaDataPropertiesTest { +* TestCase to test the internal functionality of org.postgresql.jdbc2.DatabaseMetaData's various +* properties. Methods which return a ResultSet are tested elsewhere. This avoids a complicated +* setUp/tearDown for something like assertTrue(dbmd.nullPlusNonNullIsNull()); +*/ +class DatabaseMetaDataPropertiesTest { private Connection con; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { con = TestUtil.openDB(); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(con); } @@ -42,7 +45,7 @@ public void tearDown() throws Exception { * The spec says this may return null, but we always do! */ @Test - public void testGetMetaData() throws SQLException { + void getMetaData() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); } @@ -51,7 +54,7 @@ public void testGetMetaData() throws SQLException { * Test default capabilities */ @Test - public void testCapabilities() throws SQLException { + void capabilities() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -59,7 +62,7 @@ public void testCapabilities() throws SQLException { assertTrue(dbmd.allTablesAreSelectable()); // not true all the time // This should always be false for postgresql (at least for 7.x) - assertTrue(!dbmd.isReadOnly()); + assertFalse(dbmd.isReadOnly()); // we support multiple resultsets via multiple statements in one execute() now assertTrue(dbmd.supportsMultipleResultSets()); @@ -68,18 +71,18 @@ public void testCapabilities() throws SQLException { assertTrue(dbmd.supportsMultipleTransactions()); assertTrue(dbmd.supportsMinimumSQLGrammar()); - assertTrue(!dbmd.supportsCoreSQLGrammar()); - assertTrue(!dbmd.supportsExtendedSQLGrammar()); + assertFalse(dbmd.supportsCoreSQLGrammar()); + assertFalse(dbmd.supportsExtendedSQLGrammar()); assertTrue(dbmd.supportsANSI92EntryLevelSQL()); - assertTrue(!dbmd.supportsANSI92IntermediateSQL()); - assertTrue(!dbmd.supportsANSI92FullSQL()); + assertFalse(dbmd.supportsANSI92IntermediateSQL()); + assertFalse(dbmd.supportsANSI92FullSQL()); assertTrue(dbmd.supportsIntegrityEnhancementFacility()); } @Test - public void testJoins() throws SQLException { + void joins() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -89,16 +92,16 @@ public void testJoins() throws SQLException { } @Test - public void testCursors() throws SQLException { + void cursors() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); - assertTrue(!dbmd.supportsPositionedDelete()); - assertTrue(!dbmd.supportsPositionedUpdate()); + assertFalse(dbmd.supportsPositionedDelete()); + assertFalse(dbmd.supportsPositionedUpdate()); } @Test - public void testValues() throws SQLException { + void values() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); int indexMaxKeys = dbmd.getMaxColumnsInIndex(); @@ -106,7 +109,7 @@ public void testValues() throws SQLException { } @Test - public void testNulls() throws SQLException { + void nulls() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -121,16 +124,16 @@ public void testNulls() throws SQLException { } @Test - public void testLocalFiles() throws SQLException { + void localFiles() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); - assertTrue(!dbmd.usesLocalFilePerTable()); - assertTrue(!dbmd.usesLocalFiles()); + assertFalse(dbmd.usesLocalFilePerTable()); + assertFalse(dbmd.usesLocalFiles()); } @Test - public void testIdentifiers() throws SQLException { + void identifiers() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -143,12 +146,12 @@ public void testIdentifiers() throws SQLException { assertFalse(dbmd.storesLowerCaseQuotedIdentifiers()); assertFalse(dbmd.storesMixedCaseQuotedIdentifiers()); - assertEquals( "\"", dbmd.getIdentifierQuoteString()); + assertEquals("\"", dbmd.getIdentifierQuoteString()); } @Test - public void testTables() throws SQLException { + void tables() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -160,7 +163,7 @@ public void testTables() throws SQLException { } @Test - public void testSelect() throws SQLException { + void select() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -180,7 +183,7 @@ public void testSelect() throws SQLException { } @Test - public void testDBParams() throws SQLException { + void dBParams() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -189,8 +192,8 @@ public void testDBParams() throws SQLException { } @Test - public void testDbProductDetails() throws SQLException { - assertTrue(con instanceof org.postgresql.PGConnection); + void dbProductDetails() throws SQLException { + assertTrue(con instanceof PGConnection); DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -202,14 +205,14 @@ public void testDbProductDetails() throws SQLException { } @Test - public void testDriverVersioning() throws SQLException { + void driverVersioning() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); assertEquals("PostgreSQL JDBC Driver", dbmd.getDriverName()); - assertEquals(org.postgresql.util.DriverInfo.DRIVER_VERSION, dbmd.getDriverVersion()); - assertEquals(new org.postgresql.Driver().getMajorVersion(), dbmd.getDriverMajorVersion()); - assertEquals(new org.postgresql.Driver().getMinorVersion(), dbmd.getDriverMinorVersion()); + assertEquals(DriverInfo.DRIVER_VERSION, dbmd.getDriverVersion()); + assertEquals(new Driver().getMajorVersion(), dbmd.getDriverMajorVersion()); + assertEquals(new Driver().getMinorVersion(), dbmd.getDriverMinorVersion()); assertTrue(dbmd.getJDBCMajorVersion() >= 4); assertTrue(dbmd.getJDBCMinorVersion() >= 0); } diff --git a/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java b/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java index bff7e93..762f8da 100644 --- a/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTest.java @@ -5,23 +5,30 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.PGProperty; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; +import org.postgresql.test.annotations.EnabledForServerVersionRange; import org.postgresql.test.jdbc2.BaseTest4.BinaryMode; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -40,30 +47,147 @@ import java.util.Properties; import java.util.Set; -/* +/** * TestCase to test the internal functionality of org.postgresql.jdbc2.DatabaseMetaData - * */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class DatabaseMetaDataTest { private Connection con; + private final BinaryMode binaryMode; public DatabaseMetaDataTest(BinaryMode binaryMode) { this.binaryMode = binaryMode; } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "bestrowid", "id int4 primary key"); + TestUtil.createTable(con, "precision_test", "implicit_precision numeric"); + TestUtil.dropSequence(con, "sercoltest_b_seq"); + TestUtil.dropSequence(con, "sercoltest_c_seq"); + TestUtil.createTable(con, "sercoltest", "a int, b serial, c bigserial"); + TestUtil.createTable(con, "\"a\\\"", "a int4"); + TestUtil.createTable(con, "\"a'\"", "a int4"); + TestUtil.createTable(con, "arraytable", "a numeric(5,2)[], b varchar(100)[]"); + TestUtil.createTable(con, "intarraytable", "a int4[], b int4[][]"); + TestUtil.dropType(con, "custom"); + TestUtil.dropType(con, "_custom"); + TestUtil.createCompositeType(con, "custom", "i int", false); + TestUtil.createCompositeType(con, "_custom", "f float", false); + + // create a table and multiple comments on it + TestUtil.createTable(con, "duplicate", "x text"); + TestUtil.execute(con, "comment on table duplicate is 'duplicate table'"); + TestUtil.execute(con, "create or replace function bar() returns integer language sql as $$ select 1 $$"); + TestUtil.execute(con, "comment on function bar() is 'bar function'"); + try (Connection conPriv = TestUtil.openPrivilegedDB()) { + TestUtil.execute(conPriv, "update pg_description set objoid = 'duplicate'::regclass where objoid = 'bar'::regproc"); + } + + Statement stmt = con.createStatement(); + stmt.execute( + "CREATE OR REPLACE FUNCTION f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); + stmt.execute( + "CREATE OR REPLACE FUNCTION f2(a int, b varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); + stmt.execute( + "CREATE OR REPLACE FUNCTION f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql"); + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)) { + // RETURNS TABLE requires PostgreSQL 8.4+ + stmt.execute( + "CREATE OR REPLACE FUNCTION f5() RETURNS TABLE (i int) LANGUAGE sql AS 'SELECT 1'"); + } + + // create a custom `&` operator, which caused failure with `&` usage in getIndexInfo() + stmt.execute( + "CREATE OR REPLACE FUNCTION f6(numeric, integer) returns integer as 'BEGIN return $1::integer & $2;END;' language plpgsql immutable;"); + stmt.execute("DROP OPERATOR IF EXISTS & (numeric, integer)"); + stmt.execute("CREATE OPERATOR & (LEFTARG = numeric, RIGHTARG = integer, PROCEDURE = f6)"); + + TestUtil.createDomain(con, "nndom", "int not null"); + TestUtil.createDomain(con, "varbit2", "varbit(3)"); + TestUtil.createDomain(con, "float83", "numeric(8,3)"); + + TestUtil.createTable(con, "domaintable", "id nndom, v varbit2, f float83"); + stmt.close(); + + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + String tableName = "pk_include_column"; + String indexName = tableName + "_pkey"; + TestUtil.createTable(con, tableName, "a INT, b INT, c INT, d INT"); + + String createIndexStmt = "CREATE UNIQUE INDEX IF NOT EXISTS " + indexName + + " ON " + tableName + " (b,d) INCLUDE (a)"; + TestUtil.execute(con, createIndexStmt); + + String addPrimaryKeyStmt = "ALTER TABLE " + tableName + " ADD PRIMARY KEY USING INDEX " + indexName; + TestUtil.execute(con, addPrimaryKeyStmt); + } + + if ( TestUtil.haveMinimumServerVersion(con, ServerVersion.v12) ) { + TestUtil.createTable(con, "employee", "id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, hours_per_week decimal(3,2), rate_per_hour decimal(3,2), gross_pay decimal GENERATED ALWAYS AS (hours_per_week * rate_per_hour) STORED"); + } + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.execute(con, "drop function bar()"); + TestUtil.dropTable(con, "duplicate"); + TestUtil.dropTable(con, "bestrowid"); + TestUtil.dropTable(con, "sercoltest"); + TestUtil.dropSequence(con, "sercoltest_b_seq"); + TestUtil.dropSequence(con, "sercoltest_c_seq"); + TestUtil.dropTable(con, "precision_test"); + TestUtil.dropTable(con, "\"a\\\""); + TestUtil.dropTable(con, "\"a'\""); + TestUtil.dropTable(con, "arraytable"); + TestUtil.dropTable(con, "intarraytable"); + TestUtil.dropTable(con, "customtable"); + TestUtil.dropType(con, "custom"); + TestUtil.dropType(con, "_custom"); + + Statement stmt = con.createStatement(); + stmt.execute("DROP FUNCTION f1(int, varchar)"); + stmt.execute("DROP FUNCTION f2(int, varchar)"); + stmt.execute("DROP FUNCTION f3(int, varchar)"); + stmt.execute("DROP OPERATOR IF EXISTS & (numeric, integer)"); + stmt.execute("DROP FUNCTION f6(numeric, integer)"); + TestUtil.dropTable(con, "domaintable"); + TestUtil.dropDomain(con, "nndom"); + TestUtil.dropDomain(con, "varbit2"); + TestUtil.dropDomain(con, "float83"); + + if ( TestUtil.haveMinimumServerVersion(con, ServerVersion.v11) ) { + TestUtil.dropTable(con, "pk_include_column"); + } + + if ( TestUtil.haveMinimumServerVersion(con, ServerVersion.v12) ) { + TestUtil.dropTable(con, "employee"); + } + + // Drop metadatatest last (also dropped in @AfterEach, but ensure cleanup) + TestUtil.execute(con, "DROP FUNCTION IF EXISTS f4(int)"); + TestUtil.dropView(con, "viewtest"); + TestUtil.dropTable(con, "metadatatest"); + + stmt.close(); + } + } + + @BeforeEach + void setUp() throws Exception { if (binaryMode == BinaryMode.FORCE) { final Properties props = new Properties(); PGProperty.PREPARE_THRESHOLD.set(props, -1); @@ -71,112 +195,39 @@ public void setUp() throws Exception { } else { con = TestUtil.openDB(); } + + // metadatatest is recreated per test because tests mutate it + // (drop columns, create indexes, revoke permissions) TestUtil.createTable(con, "metadatatest", "id int4, name text, updated timestamptz, colour text, quest text"); - TestUtil.createTable(con, "precision_test", "implicit_precision numeric"); - TestUtil.dropSequence(con, "sercoltest_b_seq"); - TestUtil.dropSequence(con, "sercoltest_c_seq"); - TestUtil.createTable(con, "sercoltest", "a int, b serial, c bigserial"); - TestUtil.createTable(con, "\"a\\\"", "a int4"); - TestUtil.createTable(con, "\"a'\"", "a int4"); - TestUtil.createTable(con, "arraytable", "a numeric(5,2)[], b varchar(100)[]"); - TestUtil.createTable(con, "intarraytable", "a int4[], b int4[][]"); TestUtil.createView(con, "viewtest", "SELECT id, quest FROM metadatatest"); - TestUtil.dropType(con, "custom"); - TestUtil.dropType(con, "_custom"); - TestUtil.createCompositeType(con, "custom", "i int", false); - TestUtil.createCompositeType(con, "_custom", "f float", false); - - // create a table and multiple comments on it - TestUtil.createTable(con, "duplicate", "x text"); - TestUtil.execute("comment on table duplicate is 'duplicate table'", con); - TestUtil.execute("create or replace function bar() returns integer language sql as $$ select 1 $$", con); - TestUtil.execute("comment on function bar() is 'bar function'", con); - try (Connection conPriv = TestUtil.openPrivilegedDB()) { - TestUtil.execute("update pg_description set objoid = 'duplicate'::regclass where objoid = 'bar'::regproc", conPriv); - } - - // 8.2 does not support arrays of composite types - TestUtil.createTable(con, "customtable", "c1 custom, c2 _custom" - + (TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3) ? ", c3 custom[], c4 _custom[]" : "")); - Statement stmt = con.createStatement(); - // we add the following comments to ensure the joins to the comments - // are done correctly. This ensures we correctly test that case. stmt.execute("comment on table metadatatest is 'this is a table comment'"); stmt.execute("comment on column metadatatest.id is 'this is a column comment'"); - - stmt.execute( - "CREATE OR REPLACE FUNCTION f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); - stmt.execute( - "CREATE OR REPLACE FUNCTION f2(a int, b varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); - stmt.execute( - "CREATE OR REPLACE FUNCTION f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql"); stmt.execute( "CREATE OR REPLACE FUNCTION f4(int) RETURNS metadatatest AS 'SELECT 1, ''a''::text, now(), ''c''::text, ''q''::text' LANGUAGE SQL"); - if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)) { - // RETURNS TABLE requires PostgreSQL 8.4+ - stmt.execute( - "CREATE OR REPLACE FUNCTION f5() RETURNS TABLE (i int) LANGUAGE sql AS 'SELECT 1'"); - } - - // create a custom `&` operator, which caused failure with `&` usage in getIndexInfo() - stmt.execute( - "CREATE OR REPLACE FUNCTION f6(numeric, integer) returns integer as 'BEGIN return $1::integer & $2;END;' language plpgsql immutable;"); - stmt.execute("DROP OPERATOR IF EXISTS & (numeric, integer)"); - stmt.execute("CREATE OPERATOR & (LEFTARG = numeric, RIGHTARG = integer, PROCEDURE = f6)"); - - TestUtil.createDomain(con, "nndom", "int not null"); - TestUtil.createDomain(con, "varbit2", "varbit(3)"); - TestUtil.createDomain(con, "float83", "numeric(8,3)"); - - TestUtil.createTable(con, "domaintable", "id nndom, v varbit2, f float83"); + // 8.2 does not support arrays of composite types + TestUtil.createTable(con, "customtable", "c1 custom, c2 _custom" + + (TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3) ? ", c3 custom[], c4 _custom[]" : "")); stmt.close(); } - @After - public void tearDown() throws Exception { - // Drop function first because it depends on the - // metadatatest table's type - Statement stmt = con.createStatement(); - stmt.execute("DROP FUNCTION f4(int)"); - TestUtil.execute("drop function bar()", con); - TestUtil.dropTable(con, "duplicate"); - + @AfterEach + void tearDown() throws Exception { + TestUtil.execute(con, "DROP FUNCTION IF EXISTS f4(int)"); + TestUtil.dropTable(con, "customtable"); TestUtil.dropView(con, "viewtest"); TestUtil.dropTable(con, "metadatatest"); - TestUtil.dropTable(con, "sercoltest"); - TestUtil.dropSequence(con, "sercoltest_b_seq"); - TestUtil.dropSequence(con, "sercoltest_c_seq"); - TestUtil.dropTable(con, "precision_test"); - TestUtil.dropTable(con, "\"a\\\""); - TestUtil.dropTable(con, "\"a'\""); - TestUtil.dropTable(con, "arraytable"); - TestUtil.dropTable(con, "intarraytable"); - TestUtil.dropTable(con, "customtable"); - TestUtil.dropType(con, "custom"); - TestUtil.dropType(con, "_custom"); - - stmt.execute("DROP FUNCTION f1(int, varchar)"); - stmt.execute("DROP FUNCTION f2(int, varchar)"); - stmt.execute("DROP FUNCTION f3(int, varchar)"); - stmt.execute("DROP OPERATOR IF EXISTS & (numeric, integer)"); - stmt.execute("DROP FUNCTION f6(numeric, integer)"); - TestUtil.dropTable(con, "domaintable"); - TestUtil.dropDomain(con, "nndom"); - TestUtil.dropDomain(con, "varbit2"); - TestUtil.dropDomain(con, "float83"); - TestUtil.closeDB(con); } @Test - public void testArrayTypeInfo() throws SQLException { + void arrayTypeInfo() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getColumns(null, null, "intarraytable", "a"); assertTrue(rs.next()); assertEquals("_int4", rs.getString("TYPE_NAME")); - con.createArrayOf("integer", new Integer[] {}); + con.createArrayOf("integer", new Integer[]{}); TestUtil.closeQuietly(rs); rs = dbmd.getColumns(null, null, "intarraytable", "a"); assertTrue(rs.next()); @@ -185,7 +236,7 @@ public void testArrayTypeInfo() throws SQLException { } @Test - public void testArrayInt4DoubleDim() throws SQLException { + void arrayInt4DoubleDim() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getColumns(null, null, "intarraytable", "b"); assertTrue(rs.next()); @@ -197,7 +248,7 @@ public void testArrayInt4DoubleDim() throws SQLException { } @Test - public void testCustomArrayTypeInfo() throws SQLException { + void customArrayTypeInfo() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet res = dbmd.getColumns(null, null, "customtable", null); assertTrue(res.next()); @@ -208,7 +259,11 @@ public void testCustomArrayTypeInfo() throws SQLException { assertTrue(res.next()); assertEquals("__custom", res.getString("TYPE_NAME")); assertTrue(res.next()); - assertEquals("___custom", res.getString("TYPE_NAME")); + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v16)) { + assertEquals("__custom_1", res.getString("TYPE_NAME")); + } else { + assertEquals("___custom", res.getString("TYPE_NAME")); + } } if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3)) { con.createArrayOf("custom", new Object[]{}); @@ -220,12 +275,46 @@ public void testCustomArrayTypeInfo() throws SQLException { assertTrue(res.next()); assertEquals("__custom", res.getString("TYPE_NAME")); assertTrue(res.next()); - assertEquals("___custom", res.getString("TYPE_NAME")); + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v16)) { + assertEquals("__custom_1", res.getString("TYPE_NAME")); + } else { + assertEquals("___custom", res.getString("TYPE_NAME")); + } } } @Test - public void testTables() throws Exception { + void tables_whenCatalogAndSchemaArgsEmpty_expectNoResults() throws Exception { + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + + ResultSet rs = dbmd.getTables("", "", "metadatates%", new String[]{"TABLE"}); + assertFalse(rs.next()); + rs.close(); + + rs = dbmd.getColumns("", "", "meta%", "%"); + assertFalse(rs.next()); + rs.close(); + } + + @Test + void tables_whenWrongCatalogGetMetaData_expectNoException() throws Exception { + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + + ResultSet rs = dbmd.getTables("FakeCatalog", "", null, new String[]{"TABLE"}); + assertFalse(rs.next()); + + ResultSetMetaData resultSetMetaData = rs.getMetaData(); + for (int col = 1; col <= resultSetMetaData.getColumnCount(); col++) { + resultSetMetaData.getColumnLabel(col); + } + + rs.close(); + } + + @Test + void tables() throws Exception { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -235,304 +324,434 @@ public void testTables() throws Exception { assertEquals("metadatatest", tableName); String tableType = rs.getString("TABLE_TYPE"); assertEquals("TABLE", tableType); - assertEquals(rs.findColumn("REMARKS"), 5); - assertEquals(rs.findColumn("TYPE_CAT"), 6); - assertEquals(rs.findColumn("TYPE_SCHEM"),7); - assertEquals(rs.findColumn("TYPE_NAME"), 8); - assertEquals(rs.findColumn("SELF_REFERENCING_COL_NAME"), 9); - assertEquals(rs.findColumn("REF_GENERATION"), 10); + assertEquals(5, rs.findColumn("REMARKS")); + assertEquals(6, rs.findColumn("TYPE_CAT")); + assertEquals(7, rs.findColumn("TYPE_SCHEM")); + assertEquals(8, rs.findColumn("TYPE_NAME")); + assertEquals(9, rs.findColumn("SELF_REFERENCING_COL_NAME")); + assertEquals(10, rs.findColumn("REF_GENERATION")); // There should only be one row returned - assertTrue("getTables() returned too many rows", rs.next() == false); + assertFalse(rs.next(), "getTables() returned too many rows"); rs.close(); - rs = dbmd.getColumns("", "", "meta%", "%"); + rs = dbmd.getColumns(null, null, "meta%", "%"); assertTrue(rs.next()); assertEquals("metadatatest", rs.getString("TABLE_NAME")); assertEquals("id", rs.getString("COLUMN_NAME")); - assertEquals(java.sql.Types.INTEGER, rs.getInt("DATA_TYPE")); + assertEquals(Types.INTEGER, rs.getInt("DATA_TYPE")); assertTrue(rs.next()); assertEquals("metadatatest", rs.getString("TABLE_NAME")); assertEquals("name", rs.getString("COLUMN_NAME")); - assertEquals(java.sql.Types.VARCHAR, rs.getInt("DATA_TYPE")); + assertEquals(Types.VARCHAR, rs.getInt("DATA_TYPE")); assertTrue(rs.next()); assertEquals("metadatatest", rs.getString("TABLE_NAME")); assertEquals("updated", rs.getString("COLUMN_NAME")); - assertEquals(java.sql.Types.TIMESTAMP, rs.getInt("DATA_TYPE")); + assertEquals(Types.TIMESTAMP, rs.getInt("DATA_TYPE")); } @Test - public void testCrossReference() throws Exception { - Connection con1 = TestUtil.openDB(); + void crossReference_whenCatalogAndSchemaArgsEmpty_expectNoResults() throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "vv", "a int not null, b int not null, constraint vv_pkey primary key ( a, b )"); - TestUtil.createTable(con1, "vv", "a int not null, b int not null, constraint vv_pkey primary key ( a, b )"); + TestUtil.createTable(con1, "ww", + "m int not null, n int not null, constraint m_pkey primary key ( m, n ), constraint ww_m_fkey foreign key ( m, n ) references vv ( a, b )"); - TestUtil.createTable(con1, "ww", - "m int not null, n int not null, constraint m_pkey primary key ( m, n ), constraint ww_m_fkey foreign key ( m, n ) references vv ( a, b )"); + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); - DatabaseMetaData dbmd = con.getMetaData(); - assertNotNull(dbmd); + ResultSet rs = dbmd.getCrossReference("", "", "vv", null, null, "ww"); + assertFalse(rs.next()); - ResultSet rs = dbmd.getCrossReference(null, "", "vv", null, "", "ww"); - String[] expectedPkColumnNames = new String[]{"a", "b"}; - String[] expectedFkColumnNames = new String[]{"m", "n"}; - int numRows = 0; + TestUtil.dropTable(con1, "vv"); + TestUtil.dropTable(con1, "ww"); + } + } + + @Test + void crossReference() throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "vv", "a int not null, b int not null, constraint vv_pkey primary key ( a, b )"); - for (int j = 1; rs.next(); j++) { + TestUtil.createTable(con1, "ww", + "m int not null, n int not null, constraint m_pkey primary key ( m, n ), constraint ww_m_fkey foreign key ( m, n ) references vv ( a, b )"); - String pkTableName = rs.getString("PKTABLE_NAME"); - assertEquals("vv", pkTableName); + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); - String pkColumnName = rs.getString("PKCOLUMN_NAME"); - assertEquals(expectedPkColumnNames[j - 1], pkColumnName); + ResultSet rs = dbmd.getCrossReference(null, null, "vv", null, null, "ww"); + String[] expectedPkColumnNames = new String[]{"a", "b"}; + String[] expectedFkColumnNames = new String[]{"m", "n"}; + int numRows = 0; - String fkTableName = rs.getString("FKTABLE_NAME"); - assertEquals("ww", fkTableName); + for (int j = 1; rs.next(); j++) { - String fkColumnName = rs.getString("FKCOLUMN_NAME"); - assertEquals(expectedFkColumnNames[j - 1], fkColumnName); + String pkTableName = rs.getString("PKTABLE_NAME"); + assertEquals("vv", pkTableName); - String fkName = rs.getString("FK_NAME"); - assertEquals("ww_m_fkey", fkName); + String pkColumnName = rs.getString("PKCOLUMN_NAME"); + assertEquals(expectedPkColumnNames[j - 1], pkColumnName); - String pkName = rs.getString("PK_NAME"); - assertEquals("vv_pkey", pkName); + String fkTableName = rs.getString("FKTABLE_NAME"); + assertEquals("ww", fkTableName); - int keySeq = rs.getInt("KEY_SEQ"); - assertEquals(j, keySeq); - numRows += 1; - } - assertEquals(2, numRows); + String fkColumnName = rs.getString("FKCOLUMN_NAME"); + assertEquals(expectedFkColumnNames[j - 1], fkColumnName); + + String fkName = rs.getString("FK_NAME"); + assertEquals("ww_m_fkey", fkName); - TestUtil.dropTable(con1, "vv"); - TestUtil.dropTable(con1, "ww"); - TestUtil.closeDB(con1); + String pkName = rs.getString("PK_NAME"); + assertEquals("vv_pkey", pkName); + + int keySeq = rs.getInt("KEY_SEQ"); + assertEquals(j, keySeq); + numRows += 1; + } + assertEquals(2, numRows); + + TestUtil.dropTable(con1, "vv"); + TestUtil.dropTable(con1, "ww"); + } } @Test - public void testForeignKeyActions() throws Exception { - Connection conn = TestUtil.openDB(); - TestUtil.createTable(conn, "pkt", "id int primary key"); - TestUtil.createTable(conn, "fkt1", - "id int references pkt on update restrict on delete cascade"); - TestUtil.createTable(conn, "fkt2", - "id int references pkt on update set null on delete set default"); - DatabaseMetaData dbmd = conn.getMetaData(); + void foreignKeyActions_whenSchemaArgEmpty_expectNoResults() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "pkt", "id int primary key"); + TestUtil.createTable(conn, "fkt1", + "id int references pkt on update restrict on delete cascade"); + TestUtil.createTable(conn, "fkt2", + "id int references pkt on update set null on delete set default"); + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getImportedKeys(null, "", "fkt1"); + assertFalse(rs.next()); + rs.close(); + + rs = dbmd.getImportedKeys(null, "", "fkt2"); + assertFalse(rs.next()); + rs.close(); + + TestUtil.dropTable(conn, "fkt2"); + TestUtil.dropTable(conn, "fkt1"); + TestUtil.dropTable(conn, "pkt"); + } + } - ResultSet rs = dbmd.getImportedKeys(null, "", "fkt1"); - assertTrue(rs.next()); - assertEquals(DatabaseMetaData.importedKeyRestrict, rs.getInt("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeyCascade, rs.getInt("DELETE_RULE")); - rs.close(); + @Test + void foreignKeyActions() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "pkt", "id int primary key"); + TestUtil.createTable(conn, "fkt1", + "id int references pkt on update restrict on delete cascade"); + TestUtil.createTable(conn, "fkt2", + "id int references pkt on update set null on delete set default"); + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getImportedKeys(null, null, "fkt1"); + assertTrue(rs.next()); + assertEquals(DatabaseMetaData.importedKeyRestrict, rs.getInt("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeyCascade, rs.getInt("DELETE_RULE")); + rs.close(); - rs = dbmd.getImportedKeys(null, "", "fkt2"); - assertTrue(rs.next()); - assertEquals(DatabaseMetaData.importedKeySetNull, rs.getInt("UPDATE_RULE")); - assertEquals(DatabaseMetaData.importedKeySetDefault, rs.getInt("DELETE_RULE")); - rs.close(); + rs = dbmd.getImportedKeys(null, null, "fkt2"); + assertTrue(rs.next()); + assertEquals(DatabaseMetaData.importedKeySetNull, rs.getInt("UPDATE_RULE")); + assertEquals(DatabaseMetaData.importedKeySetDefault, rs.getInt("DELETE_RULE")); + rs.close(); - TestUtil.dropTable(conn, "fkt2"); - TestUtil.dropTable(conn, "fkt1"); - TestUtil.dropTable(conn, "pkt"); - TestUtil.closeDB(conn); + TestUtil.dropTable(conn, "fkt2"); + TestUtil.dropTable(conn, "fkt1"); + TestUtil.dropTable(conn, "pkt"); + } } @Test - public void testForeignKeysToUniqueIndexes() throws Exception { - Connection con1 = TestUtil.openDB(); - TestUtil.createTable(con1, "pkt", - "a int not null, b int not null, CONSTRAINT pkt_pk_a PRIMARY KEY (a), CONSTRAINT pkt_un_b UNIQUE (b)"); - TestUtil.createTable(con1, "fkt", - "c int, d int, CONSTRAINT fkt_fk_c FOREIGN KEY (c) REFERENCES pkt(b)"); + void foreignKeysToUniqueIndexes_whenCatalogAndSchemaArgsEmpty_expect() throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "pkt", + "a int not null, b int not null, CONSTRAINT pkt_pk_a PRIMARY KEY (a), CONSTRAINT pkt_un_b UNIQUE (b)"); + TestUtil.createTable(con1, "fkt", + "c int, d int, CONSTRAINT fkt_fk_c FOREIGN KEY (c) REFERENCES pkt(b)"); - DatabaseMetaData dbmd = con.getMetaData(); - ResultSet rs = dbmd.getImportedKeys("", "", "fkt"); - int j = 0; - for (; rs.next(); j++) { - assertEquals("pkt", rs.getString("PKTABLE_NAME")); - assertEquals("fkt", rs.getString("FKTABLE_NAME")); - assertEquals("pkt_un_b", rs.getString("PK_NAME")); - assertEquals("b", rs.getString("PKCOLUMN_NAME")); - } - assertEquals(1, j); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getImportedKeys("", "", "fkt"); + assertFalse(rs.next()); - TestUtil.dropTable(con1, "fkt"); - TestUtil.dropTable(con1, "pkt"); - con1.close(); + TestUtil.dropTable(con1, "fkt"); + TestUtil.dropTable(con1, "pkt"); + } } @Test - public void testMultiColumnForeignKeys() throws Exception { - Connection con1 = TestUtil.openDB(); - TestUtil.createTable(con1, "pkt", - "a int not null, b int not null, CONSTRAINT pkt_pk PRIMARY KEY (a,b)"); - TestUtil.createTable(con1, "fkt", - "c int, d int, CONSTRAINT fkt_fk_pkt FOREIGN KEY (c,d) REFERENCES pkt(b,a)"); + void foreignKeysToUniqueIndexes() throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "pkt", + "a int not null, b int not null, CONSTRAINT pkt_pk_a PRIMARY KEY (a), CONSTRAINT pkt_un_b UNIQUE (b)"); + TestUtil.createTable(con1, "fkt", + "c int, d int, CONSTRAINT fkt_fk_c FOREIGN KEY (c) REFERENCES pkt(b)"); - DatabaseMetaData dbmd = con.getMetaData(); - ResultSet rs = dbmd.getImportedKeys("", "", "fkt"); - int j = 0; - for (; rs.next(); j++) { - assertEquals("pkt", rs.getString("PKTABLE_NAME")); - assertEquals("fkt", rs.getString("FKTABLE_NAME")); - assertEquals(j + 1, rs.getInt("KEY_SEQ")); - if (j == 0) { + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getImportedKeys(null, null, "fkt"); + int j = 0; + for (; rs.next(); j++) { + assertEquals("pkt", rs.getString("PKTABLE_NAME")); + assertEquals("fkt", rs.getString("FKTABLE_NAME")); + assertEquals("pkt_un_b", rs.getString("PK_NAME")); assertEquals("b", rs.getString("PKCOLUMN_NAME")); - assertEquals("c", rs.getString("FKCOLUMN_NAME")); - } else { - assertEquals("a", rs.getString("PKCOLUMN_NAME")); - assertEquals("d", rs.getString("FKCOLUMN_NAME")); } + assertEquals(1, j); + + TestUtil.dropTable(con1, "fkt"); + TestUtil.dropTable(con1, "pkt"); } - assertEquals(2, j); + } - TestUtil.dropTable(con1, "fkt"); - TestUtil.dropTable(con1, "pkt"); - con1.close(); + @Test + void multiColumnForeignKeys_whenCatalogAndSchemaArgsEmpty_expectNoResults() throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "pkt", + "a int not null, b int not null, CONSTRAINT pkt_pk PRIMARY KEY (a,b)"); + TestUtil.createTable(con1, "fkt", + "c int, d int, CONSTRAINT fkt_fk_pkt FOREIGN KEY (c,d) REFERENCES pkt(b,a)"); + + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getImportedKeys("", "", "fkt"); + assertFalse(rs.next()); + + TestUtil.dropTable(con1, "fkt"); + TestUtil.dropTable(con1, "pkt"); + } } @Test - public void testSameTableForeignKeys() throws Exception { - Connection con1 = TestUtil.openDB(); + void multiColumnForeignKeys() throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "pkt", + "a int not null, b int not null, CONSTRAINT pkt_pk PRIMARY KEY (a,b)"); + TestUtil.createTable(con1, "fkt", + "c int, d int, CONSTRAINT fkt_fk_pkt FOREIGN KEY (c,d) REFERENCES pkt(b,a)"); - TestUtil.createTable(con1, "person", - "FIRST_NAME character varying(100) NOT NULL," + "LAST_NAME character varying(100) NOT NULL," - + "FIRST_NAME_PARENT_1 character varying(100)," - + "LAST_NAME_PARENT_1 character varying(100)," - + "FIRST_NAME_PARENT_2 character varying(100)," - + "LAST_NAME_PARENT_2 character varying(100)," - + "CONSTRAINT PERSON_pkey PRIMARY KEY (FIRST_NAME , LAST_NAME )," - + "CONSTRAINT PARENT_1_fkey FOREIGN KEY (FIRST_NAME_PARENT_1, LAST_NAME_PARENT_1)" - + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " - + "ON UPDATE CASCADE ON DELETE CASCADE," - + "CONSTRAINT PARENT_2_fkey FOREIGN KEY (FIRST_NAME_PARENT_2, LAST_NAME_PARENT_2)" - + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " - + "ON UPDATE CASCADE ON DELETE CASCADE"); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getImportedKeys(null, null, "fkt"); + int j = 0; + for (; rs.next(); j++) { + assertEquals("pkt", rs.getString("PKTABLE_NAME")); + assertEquals("fkt", rs.getString("FKTABLE_NAME")); + assertEquals(j + 1, rs.getInt("KEY_SEQ")); + if (j == 0) { + assertEquals("b", rs.getString("PKCOLUMN_NAME")); + assertEquals("c", rs.getString("FKCOLUMN_NAME")); + } else { + assertEquals("a", rs.getString("PKCOLUMN_NAME")); + assertEquals("d", rs.getString("FKCOLUMN_NAME")); + } + } + assertEquals(2, j); - DatabaseMetaData dbmd = con.getMetaData(); - assertNotNull(dbmd); - ResultSet rs = dbmd.getImportedKeys(null, "", "person"); + TestUtil.dropTable(con1, "fkt"); + TestUtil.dropTable(con1, "pkt"); + } + } - final List fkNames = new ArrayList(); + @Test + void sameTableForeignKeys_whenSchemaArgEmpty_expectNoResults() throws Exception { + try (Connection con1 = TestUtil.openDB()) { + + TestUtil.createTable(con1, "person", + "FIRST_NAME character varying(100) NOT NULL," + "LAST_NAME character varying(100) NOT NULL," + + "FIRST_NAME_PARENT_1 character varying(100)," + + "LAST_NAME_PARENT_1 character varying(100)," + + "FIRST_NAME_PARENT_2 character varying(100)," + + "LAST_NAME_PARENT_2 character varying(100)," + + "CONSTRAINT PERSON_pkey PRIMARY KEY (FIRST_NAME , LAST_NAME )," + + "CONSTRAINT PARENT_1_fkey FOREIGN KEY (FIRST_NAME_PARENT_1, LAST_NAME_PARENT_1)" + + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " + + "ON UPDATE CASCADE ON DELETE CASCADE," + + "CONSTRAINT PARENT_2_fkey FOREIGN KEY (FIRST_NAME_PARENT_2, LAST_NAME_PARENT_2)" + + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " + + "ON UPDATE CASCADE ON DELETE CASCADE"); - int lastFieldCount = -1; - while (rs.next()) { - // destination table (all foreign keys point to the same) - String pkTableName = rs.getString("PKTABLE_NAME"); - assertEquals("person", pkTableName); - - // destination fields - String pkColumnName = rs.getString("PKCOLUMN_NAME"); - assertTrue("first_name".equals(pkColumnName) || "last_name".equals(pkColumnName)); - - // source table (all foreign keys are in the same) - String fkTableName = rs.getString("FKTABLE_NAME"); - assertEquals("person", fkTableName); - - // foreign key name - String fkName = rs.getString("FK_NAME"); - // sequence number within the foreign key - int seq = rs.getInt("KEY_SEQ"); - if (seq == 1) { - // begin new foreign key - assertFalse(fkNames.contains(fkName)); - fkNames.add(fkName); - // all foreign keys have 2 fields - assertTrue(lastFieldCount < 0 || lastFieldCount == 2); - } else { - // continue foreign key, i.e. fkName matches the last foreign key - assertEquals(fkNames.get(fkNames.size() - 1), fkName); - // see always increases by 1 - assertEquals(seq, lastFieldCount + 1); + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + ResultSet rs = dbmd.getImportedKeys(null, "", "person"); + assertFalse(rs.next()); + + TestUtil.dropTable(con1, "person"); + } + } + + @Test + void sameTableForeignKeys() throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "person", + "FIRST_NAME character varying(100) NOT NULL," + "LAST_NAME character varying(100) NOT NULL," + + "FIRST_NAME_PARENT_1 character varying(100)," + + "LAST_NAME_PARENT_1 character varying(100)," + + "FIRST_NAME_PARENT_2 character varying(100)," + + "LAST_NAME_PARENT_2 character varying(100)," + + "CONSTRAINT PERSON_pkey PRIMARY KEY (FIRST_NAME , LAST_NAME )," + + "CONSTRAINT PARENT_1_fkey FOREIGN KEY (FIRST_NAME_PARENT_1, LAST_NAME_PARENT_1)" + + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " + + "ON UPDATE CASCADE ON DELETE CASCADE," + + "CONSTRAINT PARENT_2_fkey FOREIGN KEY (FIRST_NAME_PARENT_2, LAST_NAME_PARENT_2)" + + "REFERENCES PERSON (FIRST_NAME, LAST_NAME) MATCH SIMPLE " + + "ON UPDATE CASCADE ON DELETE CASCADE"); + + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + ResultSet rs = dbmd.getImportedKeys(null, null, "person"); + + final List fkNames = new ArrayList<>(); + + int lastFieldCount = -1; + while (rs.next()) { + // destination table (all foreign keys point to the same) + String pkTableName = rs.getString("PKTABLE_NAME"); + assertEquals("person", pkTableName); + + // destination fields + String pkColumnName = rs.getString("PKCOLUMN_NAME"); + assertTrue("first_name".equals(pkColumnName) || "last_name".equals(pkColumnName)); + + // source table (all foreign keys are in the same) + String fkTableName = rs.getString("FKTABLE_NAME"); + assertEquals("person", fkTableName); + + // foreign key name + String fkName = rs.getString("FK_NAME"); + // sequence number within the foreign key + int seq = rs.getInt("KEY_SEQ"); + if (seq == 1) { + // begin new foreign key + assertFalse(fkNames.contains(fkName)); + fkNames.add(fkName); + // all foreign keys have 2 fields + assertTrue(lastFieldCount < 0 || lastFieldCount == 2); + } else { + // continue foreign key, i.e. fkName matches the last foreign key + assertEquals(fkNames.get(fkNames.size() - 1), fkName); + // see always increases by 1 + assertEquals(seq, lastFieldCount + 1); + } + lastFieldCount = seq; } - lastFieldCount = seq; + // there's more than one foreign key from a table to another + assertEquals(2, fkNames.size()); + + TestUtil.dropTable(con1, "person"); } - // there's more than one foreign key from a table to another - assertEquals(2, fkNames.size()); + } + + @Test + void foreignKeys_whenSchemaArgNull_expectNoResults() throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "people", "id int4 primary key, name text"); + TestUtil.createTable(con1, "policy", "id int4 primary key, name text"); + + TestUtil.createTable(con1, "users", + "id int4 primary key, people_id int4, policy_id int4," + + "CONSTRAINT people FOREIGN KEY (people_id) references people(id)," + + "constraint policy FOREIGN KEY (policy_id) references policy(id)"); - TestUtil.dropTable(con1, "person"); - TestUtil.closeDB(con1); + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + + ResultSet rs = dbmd.getImportedKeys(null, "", "users"); + assertFalse(rs.next()); + rs.close(); + + rs = dbmd.getExportedKeys(null, "", "people"); + assertFalse(rs.next()); + rs.close(); + + TestUtil.dropTable(con1, "users"); + TestUtil.dropTable(con1, "people"); + TestUtil.dropTable(con1, "policy"); + } } @Test - public void testForeignKeys() throws Exception { - Connection con1 = TestUtil.openDB(); - TestUtil.createTable(con1, "people", "id int4 primary key, name text"); - TestUtil.createTable(con1, "policy", "id int4 primary key, name text"); + void foreignKeys() throws Exception { + try (Connection con1 = TestUtil.openDB()) { + TestUtil.createTable(con1, "people", "id int4 primary key, name text"); + TestUtil.createTable(con1, "policy", "id int4 primary key, name text"); - TestUtil.createTable(con1, "users", - "id int4 primary key, people_id int4, policy_id int4," - + "CONSTRAINT people FOREIGN KEY (people_id) references people(id)," - + "constraint policy FOREIGN KEY (policy_id) references policy(id)"); + TestUtil.createTable(con1, "users", + "id int4 primary key, people_id int4, policy_id int4," + + "CONSTRAINT people FOREIGN KEY (people_id) references people(id)," + + "constraint policy FOREIGN KEY (policy_id) references policy(id)"); - DatabaseMetaData dbmd = con.getMetaData(); - assertNotNull(dbmd); + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); - ResultSet rs = dbmd.getImportedKeys(null, "", "users"); - int j = 0; - for (; rs.next(); j++) { + ResultSet rs = dbmd.getImportedKeys(null, null, "users"); + int j = 0; + for (; rs.next(); j++) { - String pkTableName = rs.getString("PKTABLE_NAME"); - assertTrue(pkTableName.equals("people") || pkTableName.equals("policy")); + String pkTableName = rs.getString("PKTABLE_NAME"); + assertTrue("people".equals(pkTableName) || "policy".equals(pkTableName)); - String pkColumnName = rs.getString("PKCOLUMN_NAME"); - assertEquals("id", pkColumnName); + String pkColumnName = rs.getString("PKCOLUMN_NAME"); + assertEquals("id", pkColumnName); - String fkTableName = rs.getString("FKTABLE_NAME"); - assertEquals("users", fkTableName); + String fkTableName = rs.getString("FKTABLE_NAME"); + assertEquals("users", fkTableName); - String fkColumnName = rs.getString("FKCOLUMN_NAME"); - assertTrue(fkColumnName.equals("people_id") || fkColumnName.equals("policy_id")); + String fkColumnName = rs.getString("FKCOLUMN_NAME"); + assertTrue("people_id".equals(fkColumnName) || "policy_id".equals(fkColumnName)); - String fkName = rs.getString("FK_NAME"); - assertTrue(fkName.startsWith("people") || fkName.startsWith("policy")); + String fkName = rs.getString("FK_NAME"); + assertTrue(fkName.startsWith("people") || fkName.startsWith("policy")); - String pkName = rs.getString("PK_NAME"); - assertTrue(pkName.equals("people_pkey") || pkName.equals("policy_pkey")); + String pkName = rs.getString("PK_NAME"); + assertTrue("people_pkey".equals(pkName) || "policy_pkey".equals(pkName)); - } + } - assertEquals(2, j); + assertEquals(2, j); - rs = dbmd.getExportedKeys(null, "", "people"); + rs = dbmd.getExportedKeys(null, null, "people"); - // this is hacky, but it will serve the purpose - assertTrue(rs.next()); + // this is hacky, but it will serve the purpose + assertTrue(rs.next()); - assertEquals("people", rs.getString("PKTABLE_NAME")); - assertEquals("id", rs.getString("PKCOLUMN_NAME")); + assertEquals("people", rs.getString("PKTABLE_NAME")); + assertEquals("id", rs.getString("PKCOLUMN_NAME")); - assertEquals("users", rs.getString("FKTABLE_NAME")); - assertEquals("people_id", rs.getString("FKCOLUMN_NAME")); + assertEquals("users", rs.getString("FKTABLE_NAME")); + assertEquals("people_id", rs.getString("FKCOLUMN_NAME")); - assertTrue(rs.getString("FK_NAME").startsWith("people")); + assertTrue(rs.getString("FK_NAME").startsWith("people")); - TestUtil.dropTable(con1, "users"); - TestUtil.dropTable(con1, "people"); - TestUtil.dropTable(con1, "policy"); - TestUtil.closeDB(con1); + TestUtil.dropTable(con1, "users"); + TestUtil.dropTable(con1, "people"); + TestUtil.dropTable(con1, "policy"); + } } @Test - public void testNumericPrecision() throws SQLException { + void numericPrecision() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); ResultSet rs = dbmd.getColumns(null, "public", "precision_test", "%"); - assertTrue("It should have a row for the first column", rs.next()); - assertEquals("The column size should be zero", 0, rs.getInt("COLUMN_SIZE")); - assertFalse("It should have a single column", rs.next()); + assertTrue(rs.next(), "It should have a row for the first column"); + assertEquals(0, rs.getInt("COLUMN_SIZE"), "The column size should be zero"); + assertFalse(rs.next(), "It should have a single column"); } @Test - public void testColumns() throws SQLException { + void columns() throws SQLException { // At the moment just test that no exceptions are thrown KJ String [] metadataColumns = {"TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "DATA_TYPE", "TYPE_NAME", "COLUMN_SIZE", "BUFFER_LENGTH", "DECIMAL_DIGITS", "NUM_PREC_RADIX", "NULLABLE", "REMARKS", - "COLUMN_DEF","SQL_DATA_TYPE","SQL_DATETIME_SUB","CHAR_OCTET_LENGTH", + "COLUMN_DEF", "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH", "ORDINAL_POSITION", "IS_NULLABLE", "SCOPE_CATALOG", "SCOPE_SCHEMA", "SCOPE_TABLE", "SOURCE_DATA_TYPE", "IS_AUTOINCREMENT", "IS_GENERATEDCOLUMN"}; @@ -541,14 +760,14 @@ public void testColumns() throws SQLException { ResultSet rs = dbmd.getColumns(null, null, "pg_class", null); if ( rs.next() ) { for (int i = 0; i < metadataColumns.length; i++) { - assertEquals(i + 1, rs.findColumn(metadataColumns[i])); + assertEquals(i + 1, rs.findColumn(metadataColumns[i])); } } rs.close(); } @Test - public void testDroppedColumns() throws SQLException { + void droppedColumns() throws SQLException { if (!TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)) { return; } @@ -559,126 +778,146 @@ public void testDroppedColumns() throws SQLException { stmt.close(); DatabaseMetaData dbmd = con.getMetaData(); - ResultSet rs = dbmd.getColumns(null, null, "metadatatest", null); + try (ResultSet rs = dbmd.getColumns(null, null, "metadatatest", null)) { - assertTrue(rs.next()); - assertEquals("id", rs.getString("COLUMN_NAME")); - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - - assertTrue(rs.next()); - assertEquals("updated", rs.getString("COLUMN_NAME")); - assertEquals(2, rs.getInt("ORDINAL_POSITION")); + assertTrue(rs.next()); + assertEquals("id", rs.getString("COLUMN_NAME")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); - assertTrue(rs.next()); - assertEquals("quest", rs.getString("COLUMN_NAME")); - assertEquals(3, rs.getInt("ORDINAL_POSITION")); + assertTrue(rs.next()); + assertEquals("updated", rs.getString("COLUMN_NAME")); + assertEquals(2, rs.getInt("ORDINAL_POSITION")); - rs.close(); + assertTrue(rs.next()); + assertEquals("quest", rs.getString("COLUMN_NAME")); + assertEquals(3, rs.getInt("ORDINAL_POSITION")); + } - rs = dbmd.getColumns(null, null, "metadatatest", "quest"); - assertTrue(rs.next()); - assertEquals("quest", rs.getString("COLUMN_NAME")); - assertEquals(3, rs.getInt("ORDINAL_POSITION")); - assertFalse(rs.next()); - rs.close(); + try (ResultSet rs = dbmd.getColumns(null, null, "metadatatest", "quest")) { + assertTrue(rs.next()); + assertEquals("quest", rs.getString("COLUMN_NAME")); + assertEquals(3, rs.getInt("ORDINAL_POSITION")); + assertFalse(rs.next()); + } /* getFunctionColumns also has to be aware of dropped columns add this in here to make sure it can deal with them */ - rs = dbmd.getFunctionColumns(null, null, "f4", null); - assertTrue(rs.next()); + try (ResultSet rs = dbmd.getFunctionColumns(null, null, "f4", null)) { + assertTrue(rs.next()); - assertTrue(rs.next()); - assertEquals("id", rs.getString(4)); + assertTrue(rs.next()); + assertEquals("id", rs.getString(4)); - assertTrue(rs.next()); - assertEquals("updated", rs.getString(4)); + assertTrue(rs.next()); + assertEquals("updated", rs.getString(4)); + } + } - rs.close(); + @Test + void testGetFunctionColumnsBadCatalog() throws SQLException { + DatabaseMetaData dbmd = con.getMetaData(); + try (ResultSet rs = dbmd.getColumns("nonsensecatalog", null, "sercoltest", null)) { + assertFalse(rs.next()); + } } @Test - public void testSerialColumns() throws SQLException { + void serialColumns() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); - ResultSet rs = dbmd.getColumns(null, null, "sercoltest", null); - int rownum = 0; - while (rs.next()) { - assertEquals("sercoltest", rs.getString("TABLE_NAME")); - assertEquals(rownum + 1, rs.getInt("ORDINAL_POSITION")); - if (rownum == 0) { - assertEquals("int4", rs.getString("TYPE_NAME")); + try (ResultSet rs = dbmd.getColumns(null, null, "sercoltest", null)) { + int rownum = 0; + while (rs.next()) { + assertEquals("sercoltest", rs.getString("TABLE_NAME")); + assertEquals(rownum + 1, rs.getInt("ORDINAL_POSITION")); + if (rownum == 0) { + assertEquals("int4", rs.getString("TYPE_NAME")); + + } else if (rownum == 1) { + assertEquals("serial", rs.getString("TYPE_NAME")); + assertTrue(rs.getBoolean("IS_AUTOINCREMENT")); + } else if (rownum == 2) { + assertEquals("bigserial", rs.getString("TYPE_NAME")); + assertTrue(rs.getBoolean("IS_AUTOINCREMENT")); + } - } else if (rownum == 1) { - assertEquals("serial", rs.getString("TYPE_NAME")); - assertTrue(rs.getBoolean("IS_AUTOINCREMENT")); - } else if (rownum == 2) { - assertEquals("bigserial", rs.getString("TYPE_NAME")); - assertTrue(rs.getBoolean("IS_AUTOINCREMENT")); + rownum++; } - - rownum++; + assertEquals(3, rownum); } - assertEquals(3, rownum); - rs.close(); } @Test - public void testColumnPrivileges() throws SQLException { + void columnPrivileges() throws SQLException { // At the moment just test that no exceptions are thrown KJ DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); - ResultSet rs = dbmd.getColumnPrivileges(null, null, "pg_statistic", null); - rs.close(); + try (ResultSet rs = dbmd.getColumnPrivileges(null, null, "pg_statistic", null)) { + assertTrue(rs.next()); + } + try (ResultSet rs = dbmd.getColumnPrivileges("nonsensecatalog", null, "pg_statistic", null)) { + assertFalse(rs.next()); + } } - @Test - public void testTablePrivileges() throws SQLException { + /* + * Helper function - this logic is used several times to test relation privileges + */ + public void relationPrivilegesHelper(String relationName) throws SQLException { + // Query PG catalog for privileges DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); - ResultSet rs = dbmd.getTablePrivileges(null, null, "metadatatest"); + ResultSet rs = dbmd.getTablePrivileges(null, null, relationName); + + // Parse result to check if table/view owner has select privileges boolean foundSelect = false; while (rs.next()) { if (rs.getString("GRANTEE").equals(TestUtil.getUser()) - && rs.getString("PRIVILEGE").equals("SELECT")) { + && "SELECT".equals(rs.getString("PRIVILEGE"))) { foundSelect = true; } } rs.close(); - // Test that the table owner has select priv - assertTrue("Couldn't find SELECT priv on table metadatatest for " + TestUtil.getUser(), - foundSelect); + + // Check test condition + assertTrue(foundSelect, + "Couldn't find SELECT priv on relation " + + relationName + " for " + TestUtil.getUser()); + } + + @Test + void tablePrivileges() throws SQLException { + relationPrivilegesHelper("metadatatest"); + } + + @Test + void viewPrivileges() throws SQLException { + relationPrivilegesHelper("viewtest"); + } + + @Test + void materializedViewPrivileges() throws SQLException { + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_3)); + TestUtil.createMaterializedView(con, "matviewtest", "SELECT id, quest FROM metadatatest"); + try { + relationPrivilegesHelper("matviewtest"); + } finally { + TestUtil.dropMaterializedView(con, "matviewtest"); + } } @Test - public void testNoTablePrivileges() throws SQLException { + void noTablePrivileges() throws SQLException { Statement stmt = con.createStatement(); stmt.execute("REVOKE ALL ON metadatatest FROM PUBLIC"); stmt.execute("REVOKE ALL ON metadatatest FROM " + TestUtil.getUser()); DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getTablePrivileges(null, null, "metadatatest"); - assertTrue(!rs.next()); - } - - @Test - public void testViewPrivileges() throws SQLException { - DatabaseMetaData dbmd = con.getMetaData(); - assertNotNull(dbmd); - ResultSet rs = dbmd.getTablePrivileges(null, null, "viewtest"); - boolean foundSelect = false; - while (rs.next()) { - if (rs.getString("GRANTEE").equals(TestUtil.getUser()) - && rs.getString("PRIVILEGE").equals("SELECT")) { - foundSelect = true; - } - } - rs.close(); - // Test that the view owner has select priv - assertTrue("Couldn't find SELECT priv on table metadatatest for " + TestUtil.getUser(), - foundSelect); + assertFalse(rs.next()); } @Test - public void testPrimaryKeys() throws SQLException { + void primaryKeys() throws SQLException { // At the moment just test that no exceptions are thrown KJ DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -687,7 +926,7 @@ public void testPrimaryKeys() throws SQLException { } @Test - public void testIndexInfo() throws SQLException { + void indexInfo() throws SQLException { Statement stmt = con.createStatement(); stmt.execute("create index idx_id on metadatatest (id)"); stmt.execute("create index idx_func_single on metadatatest (upper(colour))"); @@ -697,48 +936,48 @@ public void testIndexInfo() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); - ResultSet rs = dbmd.getIndexInfo(null, null, "metadatatest", false, false); + try (ResultSet rs = dbmd.getIndexInfo(null, null, "metadatatest", false, false)) { - assertTrue(rs.next()); - assertEquals("idx_un_id", rs.getString("INDEX_NAME")); - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - assertEquals("id", rs.getString("COLUMN_NAME")); - assertTrue(!rs.getBoolean("NON_UNIQUE")); + assertTrue(rs.next()); + assertEquals("idx_un_id", rs.getString("INDEX_NAME")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertEquals("id", rs.getString("COLUMN_NAME")); + assertFalse(rs.getBoolean("NON_UNIQUE")); - assertTrue(rs.next()); - assertEquals("idx_func_mixed", rs.getString("INDEX_NAME")); - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - assertEquals("colour", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("idx_func_mixed", rs.getString("INDEX_NAME")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertEquals("colour", rs.getString("COLUMN_NAME")); - assertTrue(rs.next()); - assertEquals("idx_func_mixed", rs.getString("INDEX_NAME")); - assertEquals(2, rs.getInt("ORDINAL_POSITION")); - assertEquals("upper(quest)", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("idx_func_mixed", rs.getString("INDEX_NAME")); + assertEquals(2, rs.getInt("ORDINAL_POSITION")); + assertEquals("upper(quest)", rs.getString("COLUMN_NAME")); - assertTrue(rs.next()); - assertEquals("idx_func_multi", rs.getString("INDEX_NAME")); - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - assertEquals("upper(colour)", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("idx_func_multi", rs.getString("INDEX_NAME")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertEquals("upper(colour)", rs.getString("COLUMN_NAME")); - assertTrue(rs.next()); - assertEquals("idx_func_multi", rs.getString("INDEX_NAME")); - assertEquals(2, rs.getInt("ORDINAL_POSITION")); - assertEquals("upper(quest)", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("idx_func_multi", rs.getString("INDEX_NAME")); + assertEquals(2, rs.getInt("ORDINAL_POSITION")); + assertEquals("upper(quest)", rs.getString("COLUMN_NAME")); - assertTrue(rs.next()); - assertEquals("idx_func_single", rs.getString("INDEX_NAME")); - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - assertEquals("upper(colour)", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("idx_func_single", rs.getString("INDEX_NAME")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertEquals("upper(colour)", rs.getString("COLUMN_NAME")); - assertTrue(rs.next()); - assertEquals("idx_id", rs.getString("INDEX_NAME")); - assertEquals(1, rs.getInt("ORDINAL_POSITION")); - assertEquals("id", rs.getString("COLUMN_NAME")); - assertTrue(rs.getBoolean("NON_UNIQUE")); + assertTrue(rs.next()); + assertEquals("idx_id", rs.getString("INDEX_NAME")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertEquals("id", rs.getString("COLUMN_NAME")); + assertTrue(rs.getBoolean("NON_UNIQUE")); - assertTrue(!rs.next()); + assertFalse(rs.next()); - rs.close(); + } } /** @@ -746,43 +985,95 @@ public void testIndexInfo() throws SQLException { * https://docs.oracle.com/javase/8/docs/api/java/sql/DatabaseMetaData.html#getIndexInfo-java.lang.String-java.lang.String-java.lang.String-boolean-boolean- */ @Test - public void testIndexInfoColumnOrder() throws SQLException { + void indexInfoColumnOrder() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); - ResultSet rs = dbmd.getIndexInfo(null, null, "metadatatest", false, false); - assertEquals(rs.findColumn("TABLE_CAT"), 1); - assertEquals(rs.findColumn("TABLE_SCHEM"), 2); - assertEquals(rs.findColumn("TABLE_NAME"), 3); - assertEquals(rs.findColumn("NON_UNIQUE"), 4); - assertEquals(rs.findColumn("INDEX_QUALIFIER"), 5); - assertEquals(rs.findColumn("INDEX_NAME"), 6); - assertEquals(rs.findColumn("TYPE"), 7); - assertEquals(rs.findColumn("ORDINAL_POSITION"), 8); - assertEquals(rs.findColumn("COLUMN_NAME"), 9); - assertEquals(rs.findColumn("ASC_OR_DESC"), 10); - assertEquals(rs.findColumn("CARDINALITY"), 11); - assertEquals(rs.findColumn("PAGES"), 12); - assertEquals(rs.findColumn("FILTER_CONDITION"), 13); + /* + two tests here + first is that we should not return any reults for a nonsensecatalog + second is ordering of fields + */ + try (ResultSet rs = dbmd.getIndexInfo("nonsensecatalog", null, "metadatatest", false, false)) { + assertFalse(rs.next()); + assertEquals(1, rs.findColumn("TABLE_CAT")); + assertEquals(2, rs.findColumn("TABLE_SCHEM")); + assertEquals(3, rs.findColumn("TABLE_NAME")); + assertEquals(4, rs.findColumn("NON_UNIQUE")); + assertEquals(5, rs.findColumn("INDEX_QUALIFIER")); + assertEquals(6, rs.findColumn("INDEX_NAME")); + assertEquals(7, rs.findColumn("TYPE")); + assertEquals(8, rs.findColumn("ORDINAL_POSITION")); + assertEquals(9, rs.findColumn("COLUMN_NAME")); + assertEquals(10, rs.findColumn("ASC_OR_DESC")); + assertEquals(11, rs.findColumn("CARDINALITY")); + assertEquals(12, rs.findColumn("PAGES")); + assertEquals(13, rs.findColumn("FILTER_CONDITION")); + } + try (ResultSet rs = dbmd.getIndexInfo(null, null, "metadatatest", false, false)) { + assertEquals(1, rs.findColumn("TABLE_CAT")); + assertEquals(2, rs.findColumn("TABLE_SCHEM")); + assertEquals(3, rs.findColumn("TABLE_NAME")); + assertEquals(4, rs.findColumn("NON_UNIQUE")); + assertEquals(5, rs.findColumn("INDEX_QUALIFIER")); + assertEquals(6, rs.findColumn("INDEX_NAME")); + assertEquals(7, rs.findColumn("TYPE")); + assertEquals(8, rs.findColumn("ORDINAL_POSITION")); + assertEquals(9, rs.findColumn("COLUMN_NAME")); + assertEquals(10, rs.findColumn("ASC_OR_DESC")); + assertEquals(11, rs.findColumn("CARDINALITY")); + assertEquals(12, rs.findColumn("PAGES")); + assertEquals(13, rs.findColumn("FILTER_CONDITION")); + } + } - rs.close(); + @Test + void indexInfoColumnCase() throws SQLException { + DatabaseMetaData dbmd = con.getMetaData(); + assertNotNull(dbmd); + + try (ResultSet rs = dbmd.getIndexInfo(null, null, "metadatatest", false, false)) { + ResultSetMetaData rsmd = rs.getMetaData(); + for (int i = 1; i < rsmd.getColumnCount() + 1; i++) { + char[] chars = rsmd.getColumnName(i).toCharArray(); + for (int j = 0; j < chars.length; j++) { + if (Character.isAlphabetic(chars[j])) { + assertTrue(Character.isUpperCase(chars[j]), "Column: " + rsmd.getColumnName(i) + " is not UPPER CASE"); + } + } + } + } } @Test - public void testNotNullDomainColumn() throws SQLException { + void notNullDomainColumn_whenCatalogAndSchemaAndColumnNameArgsEmpty_expectNoResults() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getColumns("", "", "domaintable", ""); + assertFalse(rs.next()); + } + + @Test + void notNullDomainColumn() throws SQLException { + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getColumns(null, null, "domaintable", null); assertTrue(rs.next()); assertEquals("id", rs.getString("COLUMN_NAME")); assertEquals("NO", rs.getString("IS_NULLABLE")); assertTrue(rs.next()); assertTrue(rs.next()); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test - public void testDomainColumnSize() throws SQLException { + void domainColumnSize_whenCatalogAndSchemaAndColumnNameArgsEmpty_expectNoResults() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getColumns("", "", "domaintable", ""); + assertFalse(rs.next()); + } + + @Test + void domainColumnSize() throws SQLException { + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getColumns(null, null, "domaintable", null); assertTrue(rs.next()); assertEquals("id", rs.getString("COLUMN_NAME")); assertEquals(10, rs.getInt("COLUMN_SIZE")); @@ -792,12 +1083,12 @@ public void testDomainColumnSize() throws SQLException { assertTrue(rs.next()); assertEquals("f", rs.getString("COLUMN_NAME")); assertEquals(8, rs.getInt("COLUMN_SIZE")); - assertEquals( 3, rs.getInt("DECIMAL_DIGITS")); + assertEquals(3, rs.getInt("DECIMAL_DIGITS")); } @Test - public void testAscDescIndexInfo() throws SQLException { + void ascDescIndexInfo() throws SQLException { if (!TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3)) { return; } @@ -821,7 +1112,7 @@ public void testAscDescIndexInfo() throws SQLException { } @Test - public void testPartialIndexInfo() throws SQLException { + void partialIndexInfo() throws SQLException { Statement stmt = con.createStatement(); stmt.execute("create index idx_p_name_id on metadatatest (name) where id > 5"); stmt.close(); @@ -840,12 +1131,31 @@ public void testPartialIndexInfo() throws SQLException { } @Test - public void testTableTypes() throws SQLException { - final List expectedTableTypes = new ArrayList(Arrays.asList("FOREIGN TABLE", "INDEX", "PARTITIONED INDEX", + void remarkIndexInfo() throws SQLException { + Statement stmt = con.createStatement(); + stmt.execute("create index idx_name on metadatatest (name)"); + stmt.execute("comment on index idx_name is 'index_comment'"); + stmt.close(); + + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getIndexInfo(null, null, "metadatatest", false, false); + + assertTrue(rs.next()); + assertEquals("idx_name", rs.getString("INDEX_NAME")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertEquals("name", rs.getString("COLUMN_NAME")); + assertEquals("index_comment", rs.getString("REMARKS")); + + rs.close(); + } + + @Test + void tableTypes() throws SQLException { + final List expectedTableTypes = new ArrayList<>(Arrays.asList("FOREIGN TABLE", "INDEX", "PARTITIONED INDEX", "MATERIALIZED VIEW", "PARTITIONED TABLE", "SEQUENCE", "SYSTEM INDEX", "SYSTEM TABLE", "SYSTEM TOAST INDEX", "SYSTEM TOAST TABLE", "SYSTEM VIEW", "TABLE", "TEMPORARY INDEX", "TEMPORARY SEQUENCE", "TEMPORARY TABLE", "TEMPORARY VIEW", "TYPE", "VIEW")); - final List foundTableTypes = new ArrayList(); + final List foundTableTypes = new ArrayList<>(); // Test that no exceptions are thrown DatabaseMetaData dbmd = con.getMetaData(); @@ -860,12 +1170,11 @@ public void testTableTypes() throws SQLException { rs.close(); Collections.sort(expectedTableTypes); Collections.sort(foundTableTypes); - Assert.assertEquals("The table types received from DatabaseMetaData should match the 18 expected types", - true, foundTableTypes.equals(expectedTableTypes)); + assertEquals(foundTableTypes, expectedTableTypes, "The table types received from DatabaseMetaData should match the 18 expected types"); } @Test - public void testFuncWithoutNames() throws SQLException { + void funcWithoutNames() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); ResultSet rs = dbmd.getProcedureColumns(null, null, "f1", null); @@ -884,13 +1193,13 @@ public void testFuncWithoutNames() throws SQLException { assertEquals(DatabaseMetaData.procedureColumnIn, rs.getInt(5)); assertEquals(Types.VARCHAR, rs.getInt(6)); - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); } @Test - public void testFuncWithNames() throws SQLException { + void funcWithNames() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getProcedureColumns(null, null, "f2", null); @@ -902,13 +1211,13 @@ public void testFuncWithNames() throws SQLException { assertTrue(rs.next()); assertEquals("b", rs.getString(4)); - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); } @Test - public void testFuncWithDirection() throws SQLException { + void funcWithDirection() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getProcedureColumns(null, null, "f3", null); @@ -931,7 +1240,7 @@ public void testFuncWithDirection() throws SQLException { } @Test - public void testFuncReturningComposite() throws SQLException { + void funcReturningComposite() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getProcedureColumns(null, null, "f4", null); @@ -965,12 +1274,12 @@ public void testFuncReturningComposite() throws SQLException { assertEquals(DatabaseMetaData.procedureColumnResult, rs.getInt(5)); assertEquals(Types.VARCHAR, rs.getInt(6)); - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); } @Test - public void testFuncReturningTable() throws Exception { + void funcReturningTable() throws Exception { if (!TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)) { return; } @@ -984,12 +1293,12 @@ public void testFuncReturningTable() throws Exception { assertEquals("i", rs.getString(4)); assertEquals(DatabaseMetaData.procedureColumnReturn, rs.getInt(5)); assertEquals(Types.INTEGER, rs.getInt(6)); - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); } @Test - public void testVersionColumns() throws SQLException { + void versionColumns() throws SQLException { // At the moment just test that no exceptions are thrown KJ DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -998,17 +1307,22 @@ public void testVersionColumns() throws SQLException { } @Test - public void testBestRowIdentifier() throws SQLException { + void bestRowIdentifier() throws SQLException { // At the moment just test that no exceptions are thrown KJ DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); - ResultSet rs = - dbmd.getBestRowIdentifier(null, null, "pg_type", DatabaseMetaData.bestRowSession, false); - rs.close(); + try (ResultSet rs = + dbmd.getBestRowIdentifier(null, null, "bestrowid", DatabaseMetaData.bestRowSession, false)) { + assertTrue(rs.next()); + } + try (ResultSet rs = + dbmd.getBestRowIdentifier("nonsensecatalog", null, "bestrowid", DatabaseMetaData.bestRowSession, false)) { + assertFalse(rs.next()); + } } @Test - public void testProcedures() throws SQLException { + void procedures() throws SQLException { // At the moment just test that no exceptions are thrown KJ DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -1017,16 +1331,29 @@ public void testProcedures() throws SQLException { } @Test - public void testCatalogs() throws SQLException { + void catalogs() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); - ResultSet rs = dbmd.getCatalogs(); - assertTrue(rs.next()); - assertEquals(con.getCatalog(), rs.getString(1)); - assertTrue(!rs.next()); + try (ResultSet rs = dbmd.getCatalogs()) { + List catalogs = new ArrayList<>(); + while (rs.next()) { + catalogs.add(rs.getString("TABLE_CAT")); + } + List sortedCatalogs = new ArrayList<>(catalogs); + Collections.sort(sortedCatalogs); + + assertThat( + catalogs, + allOf( + hasItem("test"), + hasItem("postgres"), + equalTo(sortedCatalogs) + ) + ); + } } @Test - public void testSchemas() throws Exception { + void schemas() throws Exception { DatabaseMetaData dbmd = con.getMetaData(); assertNotNull(dbmd); @@ -1050,22 +1377,25 @@ public void testSchemas() throws Exception { assertTrue(count >= 2); assertTrue(foundPublic); assertTrue(foundPGCatalog); - assertTrue(!foundEmpty); + assertFalse(foundEmpty); } @Test - public void testEscaping() throws SQLException { + @EnabledForServerVersionRange(gte = "9.2") + void escaping() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getTables(null, null, "a'", new String[]{"TABLE"}); assertTrue(rs.next()); rs = dbmd.getTables(null, null, "a\\\\", new String[]{"TABLE"}); assertTrue(rs.next()); + // PostgreSQL 9.1 fails LIKE pattern must not end with escape character even though + // we pass the pattern as a bind variable, so it should not be a subject to escaping rs = dbmd.getTables(null, null, "a\\", new String[]{"TABLE"}); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test - public void testSearchStringEscape() throws Exception { + void searchStringEscape() throws Exception { DatabaseMetaData dbmd = con.getMetaData(); String pattern = dbmd.getSearchStringEscape() + "_"; PreparedStatement pstmt = con.prepareStatement("SELECT 'a' LIKE ?, '_' LIKE ?"); @@ -1073,14 +1403,14 @@ public void testSearchStringEscape() throws Exception { pstmt.setString(2, pattern); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue(!rs.getBoolean(1)); + assertFalse(rs.getBoolean(1)); assertTrue(rs.getBoolean(2)); rs.close(); pstmt.close(); } @Test - public void testGetUDTQualified() throws Exception { + void getUDTQualified() throws Exception { Statement stmt = null; try { stmt = con.createStatement(); @@ -1097,18 +1427,18 @@ public void testGetUDTQualified() throws Exception { int dataType; int baseType; - cat = rs.getString("type_cat"); - schema = rs.getString("type_schem"); - typeName = rs.getString("type_name"); - className = rs.getString("class_name"); - dataType = rs.getInt("data_type"); - remarks = rs.getString("remarks"); - baseType = rs.getInt("base_type"); - assertEquals("type name ", "testint8", typeName); - assertEquals("schema name ", "jdbc", schema); + cat = rs.getString("TYPE_CAT"); + schema = rs.getString("TYPE_SCHEM"); + typeName = rs.getString("TYPE_NAME"); + className = rs.getString("CLASS_NAME"); + dataType = rs.getInt("DATA_TYPE"); + remarks = rs.getString("REMARKS"); + baseType = rs.getInt("BASE_TYPE"); + assertEquals("testint8", typeName, "type name "); + assertEquals("jdbc", schema, "schema name "); // now test to see if the fully qualified stuff works as planned - rs = dbmd.getUDTs("catalog", "public", "catalog.jdbc.testint8", null); + rs = dbmd.getUDTs(TestUtil.getDatabase(), "public", "catalog.jdbc.testint8", null); assertTrue(rs.next()); cat = rs.getString("type_cat"); schema = rs.getString("type_schem"); @@ -1117,8 +1447,8 @@ public void testGetUDTQualified() throws Exception { dataType = rs.getInt("data_type"); remarks = rs.getString("remarks"); baseType = rs.getInt("base_type"); - assertEquals("type name ", "testint8", typeName); - assertEquals("schema name ", "jdbc", schema); + assertEquals("testint8", typeName, "type name "); + assertEquals("jdbc", schema, "schema name "); } finally { try { if (stmt != null) { @@ -1130,11 +1460,10 @@ public void testGetUDTQualified() throws Exception { } catch (Exception ex) { } } - } @Test - public void testGetUDT1() throws Exception { + void getUDT1() throws Exception { try { Statement stmt = con.createStatement(); stmt.execute("create domain testint8 as int8"); @@ -1151,10 +1480,10 @@ public void testGetUDT1() throws Exception { String remarks = rs.getString("remarks"); int baseType = rs.getInt("base_type"); - assertTrue("base type", !rs.wasNull()); - assertEquals("data type", Types.DISTINCT, dataType); - assertEquals("type name ", "testint8", typeName); - assertEquals("remarks", "jdbc123", remarks); + assertEquals(Types.BIGINT, baseType, "base type"); + assertEquals(Types.DISTINCT, dataType, "data type"); + assertEquals("testint8", typeName, "type name "); + assertEquals("jdbc123", remarks, "remarks"); } finally { try { Statement stmt = con.createStatement(); @@ -1165,7 +1494,7 @@ public void testGetUDT1() throws Exception { } @Test - public void testGetUDT2() throws Exception { + void getUDT2() throws Exception { try { Statement stmt = con.createStatement(); stmt.execute("create domain testint8 as int8"); @@ -1183,10 +1512,10 @@ public void testGetUDT2() throws Exception { String remarks = rs.getString("remarks"); int baseType = rs.getInt("base_type"); - assertTrue("base type", !rs.wasNull()); - assertEquals("data type", Types.DISTINCT, dataType); - assertEquals("type name ", "testint8", typeName); - assertEquals("remarks", "jdbc123", remarks); + assertEquals(Types.BIGINT, baseType, "base type"); + assertEquals(Types.DISTINCT, dataType, "data type"); + assertEquals("testint8", typeName, "type name "); + assertEquals("jdbc123", remarks, "remarks"); } finally { try { Statement stmt = con.createStatement(); @@ -1197,7 +1526,7 @@ public void testGetUDT2() throws Exception { } @Test - public void testGetUDT3() throws Exception { + void getUDT3() throws Exception { try { Statement stmt = con.createStatement(); stmt.execute("create domain testint8 as int8"); @@ -1214,10 +1543,10 @@ public void testGetUDT3() throws Exception { String remarks = rs.getString("remarks"); int baseType = rs.getInt("base_type"); - assertTrue("base type", !rs.wasNull()); - assertEquals("data type", Types.DISTINCT, dataType); - assertEquals("type name ", "testint8", typeName); - assertEquals("remarks", "jdbc123", remarks); + assertEquals(Types.BIGINT, baseType, "base type"); + assertEquals(Types.DISTINCT, dataType, "data type"); + assertEquals("testint8", typeName, "type name "); + assertEquals("jdbc123", remarks, "remarks"); } finally { try { Statement stmt = con.createStatement(); @@ -1228,7 +1557,7 @@ public void testGetUDT3() throws Exception { } @Test - public void testGetUDT4() throws Exception { + void getUDT4() throws Exception { try { Statement stmt = con.createStatement(); stmt.execute("create type testint8 as (i int8)"); @@ -1244,9 +1573,34 @@ public void testGetUDT4() throws Exception { String remarks = rs.getString("remarks"); int baseType = rs.getInt("base_type"); - assertTrue("base type", rs.wasNull()); - assertEquals("data type", Types.STRUCT, dataType); - assertEquals("type name ", "testint8", typeName); + assertTrue(rs.wasNull(), "base type"); + assertEquals(Types.STRUCT, dataType, "data type"); + assertEquals("testint8", typeName, "type name "); + } finally { + try { + Statement stmt = con.createStatement(); + stmt.execute("drop type testint8"); + } catch (Exception ex) { + } + } + } + + @Test + void getUDT5() throws Exception { + try { + Statement stmt = con.createStatement(); + stmt.execute("create type testint8 as (i int8)"); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getUDTs("nonsensecatalog", null, "testint8", null); + assertFalse(rs.next()); + + assertEquals(1, rs.findColumn("type_cat")); + assertEquals(2, rs.findColumn("type_schem")); + assertEquals(3, rs.findColumn("type_name")); + assertEquals(4, rs.findColumn("class_name")); + assertEquals(5, rs.findColumn("data_type")); + assertEquals(6, rs.findColumn("remarks")); + assertEquals(7, rs.findColumn("base_type")); } finally { try { Statement stmt = con.createStatement(); @@ -1257,9 +1611,9 @@ public void testGetUDT4() throws Exception { } @Test - public void testTypes() throws SQLException { + void types() throws SQLException { // https://www.postgresql.org/docs/8.2/static/datatype.html - List stringTypeList = new ArrayList(); + List stringTypeList = new ArrayList<>(); stringTypeList.addAll(Arrays.asList("bit", "bool", "box", @@ -1307,7 +1661,7 @@ public void testTypes() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getTypeInfo(); - List types = new ArrayList(); + List types = new ArrayList<>(); while (rs.next()) { types.add(rs.getString("TYPE_NAME")); @@ -1318,22 +1672,22 @@ public void testTypes() throws SQLException { } @Test - public void testTypeInfoSigned() throws SQLException { + void typeInfoSigned() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getTypeInfo(); while (rs.next()) { if ("int4".equals(rs.getString("TYPE_NAME"))) { - assertEquals(false, rs.getBoolean("UNSIGNED_ATTRIBUTE")); + assertFalse(rs.getBoolean("UNSIGNED_ATTRIBUTE")); } else if ("float8".equals(rs.getString("TYPE_NAME"))) { - assertEquals(false, rs.getBoolean("UNSIGNED_ATTRIBUTE")); + assertFalse(rs.getBoolean("UNSIGNED_ATTRIBUTE")); } else if ("text".equals(rs.getString("TYPE_NAME"))) { - assertEquals(true, rs.getBoolean("UNSIGNED_ATTRIBUTE")); + assertTrue(rs.getBoolean("UNSIGNED_ATTRIBUTE")); } } } @Test - public void testTypeInfoQuoting() throws SQLException { + void typeInfoQuoting() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getTypeInfo(); while (rs.next()) { @@ -1347,9 +1701,16 @@ public void testTypeInfoQuoting() throws SQLException { } @Test - public void testInformationAboutArrayTypes() throws SQLException { + void informationAboutArrayTypes_whenCatalogSchemaColumnNamePatternArgsEmpty_expectNoResults() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getColumns("", "", "arraytable", ""); + assertFalse(rs.next()); + } + + @Test + void informationAboutArrayTypes() throws SQLException { + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getColumns(null, null, "arraytable", null); assertTrue(rs.next()); assertEquals("a", rs.getString("COLUMN_NAME")); assertEquals(5, rs.getInt("COLUMN_SIZE")); @@ -1357,84 +1718,160 @@ public void testInformationAboutArrayTypes() throws SQLException { assertTrue(rs.next()); assertEquals("b", rs.getString("COLUMN_NAME")); assertEquals(100, rs.getInt("COLUMN_SIZE")); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test - public void testPartionedTablesIndex() throws SQLException { + void primaryKeysWithIncludeColumns_whenCatalogAndSchemaArgsEmpty_expectNoResults() throws SQLException { + String tableName = "pk_include_column"; if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { - Statement stmt = null; - try { - stmt = con.createStatement(); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getPrimaryKeys("", "", tableName); + assertFalse(rs.next()); + } + } + + @Test + void primaryKeysWithIncludeColumns() throws SQLException { + String tableName = "pk_include_column"; + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getPrimaryKeys(null, null, tableName); + + // getPrimaryKeys should return only the key columns and not the included column + assertTrue(rs.next()); + assertEquals(tableName, rs.getString("TABLE_NAME")); + assertEquals("b", rs.getString("COLUMN_NAME")); + + assertTrue(rs.next()); + assertEquals("d", rs.getString("COLUMN_NAME")); + + assertFalse(rs.next()); + } + } + + @Test + void partitionedTablesIndex_whenCatalogAndSchemaArgsEmpty_expectNoResults() throws SQLException { + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + try (Statement stmt = con.createStatement()) { + stmt.execute("drop table if exists measurement"); stmt.execute( "CREATE TABLE measurement (logdate date not null primary key,peaktemp int,unitsales int ) PARTITION BY RANGE (logdate);"); DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getPrimaryKeys("", "", "measurement"); + assertFalse(rs.next()); + + stmt.execute("drop table if exists measurement"); + } + } + } + + @Test + void partitionedTablesIndex() throws SQLException { + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + try (Statement stmt = con.createStatement()) { + stmt.execute("drop table if exists measurement"); + stmt.execute( + "CREATE TABLE measurement (logdate date not null primary key,peaktemp int,unitsales int ) PARTITION BY RANGE (logdate);"); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getPrimaryKeys(null, null, "measurement"); assertTrue(rs.next()); assertEquals("measurement_pkey", rs.getString(6)); - } finally { - if (stmt != null) { - stmt.execute("drop table if exists measurement"); - stmt.close(); - } + stmt.execute("drop table if exists measurement"); } } - } @Test - public void testPartitionedTables() throws SQLException { + void partitionedTables_whenCatalogAndSchemaArgsEmpty_expectNoResults() throws SQLException { if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { - Statement stmt = null; - try { - stmt = con.createStatement(); + try (Statement stmt = con.createStatement()) { + stmt.execute("drop table if exists measurement"); stmt.execute( "CREATE TABLE measurement (logdate date not null primary key,peaktemp int,unitsales int ) PARTITION BY RANGE (logdate);"); DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getTables("", "", "measurement", new String[]{"PARTITIONED TABLE"}); + assertFalse(rs.next()); + rs.close(); + rs = dbmd.getPrimaryKeys("", "", "measurement"); + assertFalse(rs.next()); + + stmt.execute("drop table if exists measurement"); + } + } + } + + @Test + void partitionedTables() throws SQLException { + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + try (Statement stmt = con.createStatement()) { + stmt.execute("drop table if exists measurement"); + stmt.execute( + "CREATE TABLE measurement (logdate date not null primary key,peaktemp int,unitsales int ) PARTITION BY RANGE (logdate);"); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getTables(null, null, "measurement", new String[]{"PARTITIONED TABLE"}); assertTrue(rs.next()); assertEquals("measurement", rs.getString("table_name")); rs.close(); - rs = dbmd.getPrimaryKeys("", "", "measurement"); + rs = dbmd.getPrimaryKeys(null, null, "measurement"); assertTrue(rs.next()); assertEquals("measurement_pkey", rs.getString(6)); - } finally { - if (stmt != null) { - stmt.execute("drop table if exists measurement"); - stmt.close(); - } + stmt.execute("drop table if exists measurement"); } } } @Test - public void testIdentityColumns() throws SQLException { + void identityColumns_whenCatalogAndSchemaArgsEmpty_expectNoResults() throws SQLException { if ( TestUtil.haveMinimumServerVersion(con, ServerVersion.v10) ) { - Statement stmt = null; - try { - stmt = con.createStatement(); + try (Statement stmt = con.createStatement()) { + stmt.execute("drop table if exists test_new"); stmt.execute("CREATE TABLE test_new (" + "id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY," + "payload text)"); DatabaseMetaData dbmd = con.getMetaData(); ResultSet rs = dbmd.getColumns("", "", "test_new", "id"); + assertFalse(rs.next()); + + stmt.execute("drop table test_new"); + } + } + } + + @Test + void identityColumns() throws SQLException { + if ( TestUtil.haveMinimumServerVersion(con, ServerVersion.v10) ) { + try (Statement stmt = con.createStatement()) { + stmt.execute("drop table if exists test_new"); + stmt.execute("CREATE TABLE test_new (" + + "id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY," + + "payload text)"); + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getColumns(null, null, "test_new", "id"); assertTrue(rs.next()); - assertEquals(rs.getString("COLUMN_NAME"), "id"); + assertEquals("id", rs.getString("COLUMN_NAME")); assertTrue(rs.getBoolean("IS_AUTOINCREMENT")); - } finally { - if ( stmt != null ) { - stmt.execute( "drop table test_new"); - stmt.close(); - } + stmt.execute("drop table test_new"); } } } @Test - public void testGetSQLKeywords() throws SQLException { + void generatedColumns() throws SQLException { + if ( TestUtil.haveMinimumServerVersion(con, ServerVersion.v12) ) { + DatabaseMetaData dbmd = con.getMetaData(); + ResultSet rs = dbmd.getColumns(null, null, "employee", "gross_pay"); + assertTrue(rs.next()); + assertEquals("gross_pay", rs.getString("COLUMN_NAME")); + assertTrue(rs.getBoolean("IS_GENERATEDCOLUMN")); + } + } + + @Test + void getSQLKeywords() throws SQLException { DatabaseMetaData dbmd = con.getMetaData(); String keywords = dbmd.getSQLKeywords(); @@ -1491,20 +1928,20 @@ public void testGetSQLKeywords() throws SQLException { String[] excludeSQL2003 = sql2003.split(","); String[] returned = keywords.split(","); - Set returnedSet = new HashSet(Arrays.asList(returned)); - Assert.assertEquals("Returned keywords should be unique", returnedSet.size(), returned.length); + Set returnedSet = new HashSet<>(Arrays.asList(returned)); + assertEquals(returnedSet.size(), returned.length, "Returned keywords should be unique"); for (String s : excludeSQL2003) { - assertFalse("Keyword from SQL:2003 \"" + s + "\" found", returnedSet.contains(s)); + assertFalse(returnedSet.contains(s), "Keyword from SQL:2003 \"" + s + "\" found"); } if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_0)) { - Assert.assertTrue("reindex should be in keywords", returnedSet.contains("reindex")); + assertTrue(returnedSet.contains("reindex"), "reindex should be in keywords"); } } @Test - public void testFunctionColumns() throws SQLException { + void functionColumns() throws SQLException { if (!TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)) { return; } @@ -1533,7 +1970,7 @@ public void testFunctionColumns() throws SQLException { assertEquals("SPECIFIC_NAME", rsmd.getColumnName(17)); assertTrue(rs.next()); - assertEquals(null, rs.getString(1)); + assertNotNull(rs.getString(1)); assertEquals("public", rs.getString(2)); assertEquals("f1", rs.getString(3)); assertEquals("returnValue", rs.getString(4)); @@ -1543,7 +1980,7 @@ public void testFunctionColumns() throws SQLException { assertEquals(0, rs.getInt(15)); assertTrue(rs.next()); - assertEquals(null, rs.getString(1)); + assertNotNull(rs.getString(1)); assertEquals("public", rs.getString(2)); assertEquals("f1", rs.getString(3)); assertEquals("$1", rs.getString(4)); @@ -1553,7 +1990,7 @@ public void testFunctionColumns() throws SQLException { assertEquals(1, rs.getInt(15)); assertTrue(rs.next()); - assertEquals(null, rs.getString(1)); + assertNotNull(rs.getString(1)); assertEquals("public", rs.getString(2)); assertEquals("f1", rs.getString(3)); assertEquals("$2", rs.getString(4)); @@ -1562,14 +1999,14 @@ public void testFunctionColumns() throws SQLException { assertEquals("varchar", rs.getString(7)); assertEquals(2, rs.getInt(15)); - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); } @Test - public void testSmallSerialColumns() throws SQLException { - org.junit.Assume.assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_2)); + void smallSerialColumns() throws SQLException { + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_2)); TestUtil.createTable(con, "smallserial_test", "a smallserial"); DatabaseMetaData dbmd = con.getMetaData(); @@ -1588,7 +2025,7 @@ public void testSmallSerialColumns() throws SQLException { } @Test - public void testSmallSerialSequenceLikeColumns() throws SQLException { + void smallSerialSequenceLikeColumns() throws SQLException { Statement stmt = con.createStatement(); // This is the equivalent of the smallserial, not the actual smallserial stmt.execute("CREATE SEQUENCE smallserial_test_a_seq;\n" @@ -1619,7 +2056,7 @@ public void testSmallSerialSequenceLikeColumns() throws SQLException { } @Test - public void testUpperCaseMetaDataLabels() throws SQLException { + void upperCaseMetaDataLabels() throws SQLException { ResultSet rs = con.getMetaData().getTables(null, null, null, null); ResultSetMetaData rsmd = rs.getMetaData(); diff --git a/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTransactionIsolationTest.java b/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTransactionIsolationTest.java new file mode 100644 index 0000000..38627ce --- /dev/null +++ b/src/test/java/org/postgresql/test/jdbc2/DatabaseMetaDataTransactionIsolationTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.postgresql.test.TestUtil; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.function.Supplier; + +class DatabaseMetaDataTransactionIsolationTest { + static Connection con; + + @BeforeAll + static void setup() throws SQLException { + con = TestUtil.openDB(); + } + + @AfterAll + static void teardown() throws SQLException { + TestUtil.closeDB(con); + } + + @BeforeEach + void resetTransactionIsolation() throws SQLException { + // Restore to defaults + con.setAutoCommit(true); + try (Statement st = con.createStatement()) { + st.execute("alter database " + TestUtil.getDatabase() + " set default_transaction_isolation to DEFAULT"); + } + } + + @Test + void connectionTransactionIsolation() throws SQLException { + // We use a new connection to avoid any side effects from other tests as we need to test + // the default transaction isolation level. + try (Connection con = TestUtil.openDB()) { + assertIsolationEquals( + "read committed", + con.getTransactionIsolation(), + () -> "Default connection transaction isolation in PostgreSQL is read committed"); + } + } + + @Test + void metadataDefaultTransactionIsolation() throws SQLException { + assertIsolationEquals( + "read committed", + getDefaultTransactionIsolation(), + () -> "Default database transaction isolation in PostgreSQL is read committed"); + } + + @ParameterizedTest + @ValueSource(strings = {"read committed", "read uncommitted", "repeatable read", "serializable"}) + void alterDatabaseDefaultTransactionIsolation(String isolationLevel) throws SQLException { + try (Statement st = con.createStatement()) { + st.execute( + "alter database " + TestUtil.getDatabase() + " set default_transaction_isolation to '" + isolationLevel + "'"); + } + + assertIsolationEquals( + isolationLevel, + getDefaultTransactionIsolation(), + () -> "Default transaction isolation should be " + isolationLevel); + } + + /** + * PostgreSQL does not seem to update the value in + * pg_catalog.pg_settings WHERE name='default_transaction_isolation' + * when changing default_transaction_isolation, so we reconnect to get the new value. + */ + static int getDefaultTransactionIsolation() throws SQLException { + try (Connection con = TestUtil.openDB()) { + return con.getMetaData().getDefaultTransactionIsolation(); + } + } + + @ParameterizedTest + @ValueSource(strings = {"read committed", "read uncommitted", "repeatable read", "serializable"}) + void alterConnectionTransactionIsolation(String isolationLevel) throws SQLException { + con.setAutoCommit(false); + try (Statement st = con.createStatement()) { + st.execute("set transaction ISOLATION LEVEL " + isolationLevel); + } + + assertIsolationEquals( + isolationLevel, + con.getTransactionIsolation(), + () -> "Connection transaction isolation should be " + isolationLevel); + } + + @ParameterizedTest + @ValueSource(ints = { + Connection.TRANSACTION_SERIALIZABLE, + Connection.TRANSACTION_REPEATABLE_READ, + Connection.TRANSACTION_READ_COMMITTED, + Connection.TRANSACTION_READ_UNCOMMITTED}) + void setConnectionTransactionIsolation(int isolationLevel) throws SQLException { + con.setAutoCommit(false); + con.setTransactionIsolation(isolationLevel); + + assertIsolationEquals( + mapJdbcIsolationToPg(isolationLevel), + con.getTransactionIsolation(), + () -> "Connection transaction isolation should be " + isolationLevel); + } + + private static void assertIsolationEquals(String expected, int actual, Supplier message) { + assertEquals( + expected, + mapJdbcIsolationToPg(actual), + message); + } + + private static String mapJdbcIsolationToPg(int isolationLevel) { + switch (isolationLevel) { + case Connection.TRANSACTION_READ_COMMITTED: + return "read committed"; + case Connection.TRANSACTION_READ_UNCOMMITTED: + return "read uncommitted"; + case Connection.TRANSACTION_REPEATABLE_READ: + return "repeatable read"; + case Connection.TRANSACTION_SERIALIZABLE: + return "serializable"; + case Connection.TRANSACTION_NONE: + return "none"; + default: + return "Unknown isolation level " + isolationLevel; + } + } +} diff --git a/src/test/java/org/postgresql/test/jdbc2/DateStyleTest.java b/src/test/java/org/postgresql/test/jdbc2/DateStyleTest.java index 97850e9..50697fb 100644 --- a/src/test/java/org/postgresql/test/jdbc2/DateStyleTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/DateStyleTest.java @@ -5,28 +5,30 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.fail; + import org.postgresql.test.TestUtil; import org.postgresql.util.PSQLState; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class DateStyleTest extends BaseTest4 { - @Parameterized.Parameter(0) + @Parameter(0) public String dateStyle; - @Parameterized.Parameter(1) + @Parameter(1) public boolean shouldPass; - @Parameterized.Parameters(name = "dateStyle={0}, shouldPass={1}") public static Iterable data() { return Arrays.asList(new Object[][]{ {"iso, mdy", true}, @@ -37,12 +39,12 @@ public static Iterable data() { } @Test - public void conenct() throws SQLException { + public void connect() throws SQLException { Statement st = con.createStatement(); try { st.execute("set DateStyle='" + dateStyle + "'"); if (!shouldPass) { - Assert.fail("Set DateStyle=" + dateStyle + " should not be allowed"); + fail("Set DateStyle=" + dateStyle + " should not be allowed"); } } catch (SQLException e) { if (shouldPass) { diff --git a/src/test/java/org/postgresql/test/jdbc2/DateTest.java b/src/test/java/org/postgresql/test/jdbc2/DateTest.java index 83fb195..75d990f 100644 --- a/src/test/java/org/postgresql/test/jdbc2/DateTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/DateTest.java @@ -5,147 +5,200 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; -import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; - -/* +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.TimeZone; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** * Some simple tests based on problems reported by users. Hopefully these will help prevent previous * problems from re-occurring ;-) - * */ -public class DateTest { - private Connection con; +@ParameterizedClass +@MethodSource("data") +@Isolated("Uses TimeZone.setDefault") +public class DateTest extends BaseTest4 { + private static final TimeZone saveTZ = TimeZone.getDefault(); + + private final String type; + private final String zoneId; + + public DateTest(String type, String zoneId, BinaryMode binaryMode) { + this.type = type; + this.zoneId = zoneId; + TimeZone.setDefault(TimeZone.getTimeZone(zoneId)); + setBinaryMode(binaryMode); + } + + public static Iterable data() { + final List data = new ArrayList<>(); + for (String type : Arrays.asList("date", "timestamp", "timestamptz")) { + Stream tzIds = Stream.of("Africa/Casablanca", "America/New_York", "America/Toronto", + "Europe/Berlin", "Europe/Moscow", "Pacific/Apia", "America/Los_Angeles"); + // some selection of static GMT offsets (not all, as this takes too long): + tzIds = Stream.concat(tzIds, IntStream.of(-12, -11, -5, -1, 0, 1, 3, 12, 13) + .mapToObj(i -> String.format(Locale.ROOT, "GMT%+02d", i))); + for (String tzId : (Iterable) tzIds::iterator) { + for (BinaryMode binaryMode : BinaryMode.values()) { + data.add(new Object[]{type, tzId, binaryMode}); + } + } + } + return data; + } - @Before + @Override public void setUp() throws Exception { - con = TestUtil.openDB(); - TestUtil.createTable(con, "testdate", "dt date"); + super.setUp(); + TestUtil.createTable(con, "test", "dt ".concat(type)); } - @After - public void tearDown() throws Exception { - TestUtil.dropTable(con, "testdate"); - TestUtil.closeDB(con); + @Override + public void tearDown() throws SQLException { + TimeZone.setDefault(saveTZ); + TestUtil.dropTable(con, "test"); + super.tearDown(); } - /* + /** * Tests the time methods in ResultSet */ @Test public void testGetDate() throws SQLException { - Statement stmt = con.createStatement(); - - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1950-02-07'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1970-06-02'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1999-08-11'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2001-02-13'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1950-04-02'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1970-11-30'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1988-01-01'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2003-07-09'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1934-02-28'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1969-04-03'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1982-08-03'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2012-03-15'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1912-05-01'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1971-12-15'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'1984-12-03'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2000-01-01'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'3456-01-01'"))); - assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testdate", "'0101-01-01 BC'"))); - - /* dateTest() contains all of the tests */ - dateTest(); - - assertEquals(18, stmt.executeUpdate("DELETE FROM " + "testdate")); - stmt.close(); + assumeTrue(!Objects.equals(type, "timestamptz") || zoneId.startsWith("GMT"), "TODO: Test fails on some server versions with local time zones (not GMT based)"); + try (Statement stmt = con.createStatement()) { + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1950-02-07'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1970-06-02'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1999-08-11'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2001-02-13'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1950-04-02'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1970-11-30'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1988-01-01'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2003-07-09'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1934-02-28'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1969-04-03'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1982-08-03'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2012-03-15'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1912-05-01'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1971-12-15'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'1984-12-03'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'2000-01-01'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'3456-01-01'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0101-01-01 BC'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0001-01-01'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0001-01-01 BC'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("test", "'0001-12-31 BC'"))); + + /* dateTest() contains all of the tests */ + dateTest(); + + assertEquals(21, stmt.executeUpdate("DELETE FROM test")); + } } - /* + /** * Tests the time methods in PreparedStatement */ @Test public void testSetDate() throws SQLException { - Statement stmt = con.createStatement(); - PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("testdate", "?")); + try (Statement stmt = con.createStatement()) { + PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("test", "?")); - ps.setDate(1, makeDate(1950, 2, 7)); - assertEquals(1, ps.executeUpdate()); + ps.setDate(1, makeDate(1950, 2, 7)); + assertEquals(1, ps.executeUpdate()); - ps.setDate(1, makeDate(1970, 6, 2)); - assertEquals(1, ps.executeUpdate()); + ps.setDate(1, makeDate(1970, 6, 2)); + assertEquals(1, ps.executeUpdate()); - ps.setDate(1, makeDate(1999, 8, 11)); - assertEquals(1, ps.executeUpdate()); + ps.setDate(1, makeDate(1999, 8, 11)); + assertEquals(1, ps.executeUpdate()); - ps.setDate(1, makeDate(2001, 2, 13)); - assertEquals(1, ps.executeUpdate()); + ps.setDate(1, makeDate(2001, 2, 13)); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, java.sql.Timestamp.valueOf("1950-04-02 12:00:00"), java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, java.sql.Timestamp.valueOf("1950-04-02 12:00:00"), java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, java.sql.Timestamp.valueOf("1970-11-30 3:00:00"), java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, java.sql.Timestamp.valueOf("1970-11-30 3:00:00"), java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, java.sql.Timestamp.valueOf("1988-01-01 13:00:00"), java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, java.sql.Timestamp.valueOf("1988-01-01 13:00:00"), java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, java.sql.Timestamp.valueOf("2003-07-09 12:00:00"), java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, java.sql.Timestamp.valueOf("2003-07-09 12:00:00"), java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, "1934-02-28", java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, "1934-02-28", java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, "1969-04-03", java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, "1969-04-03", java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, "1982-08-03", java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, "1982-08-03", java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, "2012-03-15", java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, "2012-03-15", java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, java.sql.Date.valueOf("1912-05-01"), java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, java.sql.Date.valueOf("1912-05-01"), java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, java.sql.Date.valueOf("1971-12-15"), java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, java.sql.Date.valueOf("1971-12-15"), java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, java.sql.Date.valueOf("1984-12-03"), java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, java.sql.Date.valueOf("1984-12-03"), java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, java.sql.Date.valueOf("2000-01-01"), java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, java.sql.Date.valueOf("2000-01-01"), java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - ps.setObject(1, java.sql.Date.valueOf("3456-01-01"), java.sql.Types.DATE); - assertEquals(1, ps.executeUpdate()); + ps.setObject(1, java.sql.Date.valueOf("3456-01-01"), java.sql.Types.DATE); + assertEquals(1, ps.executeUpdate()); - // We can't use valueOf on BC dates. - ps.setObject(1, makeDate(-100, 1, 1)); - assertEquals(1, ps.executeUpdate()); + // We can't use valueOf on BC dates. + ps.setObject(1, makeDate(-100, 1, 1)); + assertEquals(1, ps.executeUpdate()); - ps.close(); + ps.setObject(1, makeDate(1, 1, 1)); + assertEquals(1, ps.executeUpdate()); - dateTest(); + // Note: Year 0 in Java is year '0001-01-01 BC' in PostgreSQL. + ps.setObject(1, makeDate(0, 1, 1)); + assertEquals(1, ps.executeUpdate()); - assertEquals(18, stmt.executeUpdate("DELETE FROM testdate")); - stmt.close(); + ps.setObject(1, makeDate(0, 12, 31)); + assertEquals(1, ps.executeUpdate()); + + ps.close(); + + dateTest(); + + assertEquals(21, stmt.executeUpdate("DELETE FROM test")); + } } - /* + /** * Helper for the date tests. It tests what should be in the db */ private void dateTest() throws SQLException { @@ -153,7 +206,7 @@ private void dateTest() throws SQLException { ResultSet rs; java.sql.Date d; - rs = st.executeQuery(TestUtil.selectSQL("testdate", "dt")); + rs = st.executeQuery(TestUtil.selectSQL("test", "dt")); assertNotNull(rs); assertTrue(rs.next()); @@ -246,13 +299,29 @@ private void dateTest() throws SQLException { assertNotNull(d); assertEquals(makeDate(-100, 1, 1), d); - assertTrue(!rs.next()); + assertTrue(rs.next()); + d = rs.getDate(1); + assertNotNull(d); + assertEquals(makeDate(1, 1, 1), d); + + assertTrue(rs.next()); + d = rs.getDate(1); + assertNotNull(d); + assertEquals(makeDate(0, 1, 1), d); + + assertTrue(rs.next()); + d = rs.getDate(1); + assertNotNull(d); + assertEquals(makeDate(0, 12, 31), d); + + assertFalse(rs.next()); rs.close(); st.close(); } - private java.sql.Date makeDate(int y, int m, int d) { + @SuppressWarnings("deprecation") + private static java.sql.Date makeDate(int y, int m, int d) { return new java.sql.Date(y - 1900, m - 1, d); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/DriverTest.java b/src/test/java/org/postgresql/test/jdbc2/DriverTest.java index fe63435..a0855e6 100644 --- a/src/test/java/org/postgresql/test/jdbc2/DriverTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/DriverTest.java @@ -5,43 +5,51 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.Driver; +import org.postgresql.PGEnvironment; import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; -import org.postgresql.util.LogWriterHandler; -import org.postgresql.util.NullOutputStream; +import org.postgresql.test.annotations.DisableLogger; +import org.postgresql.util.PGPropertyUtil; +import org.postgresql.util.StubEnvironmentAndProperties; import org.postgresql.util.URLCoder; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.properties.SystemProperties; +import uk.org.webcompere.systemstubs.resource.Resources; import java.io.ByteArrayOutputStream; import java.io.PrintStream; -import java.io.PrintWriter; import java.lang.reflect.Method; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Properties; -import java.util.logging.Handler; -import java.util.logging.Logger; /* * Tests the dynamically created class org.postgresql.Driver * */ -public class DriverTest { +@StubEnvironmentAndProperties +class DriverTest { @Test - public void urlIsNotForPostgreSQL() throws SQLException { + void urlIsNotForPostgreSQL() throws SQLException { Driver driver = new Driver(); assertNull(driver.connect("jdbc:otherdb:database", new Properties())); @@ -51,40 +59,57 @@ public void urlIsNotForPostgreSQL() throws SQLException { * According to the javadoc of java.sql.Driver.connect(...), calling abort when the {@code executor} is {@code null} * results in SQLException */ - @Test(expected = SQLException.class) - public void urlIsNull() throws SQLException { + @Test + void urlIsNull() throws SQLException { Driver driver = new Driver(); - driver.connect(null, new Properties()); + assertThrows(SQLException.class, () -> driver.connect(null, new Properties())); } /* * This tests the acceptsURL() method with a couple of well and poorly formed jdbc urls. */ @Test - public void testAcceptsURL() throws Exception { + void acceptsURL() throws Exception { TestUtil.initDriver(); // Set up log levels, etc. // Load the driver (note clients should never do it this way!) - org.postgresql.Driver drv = new org.postgresql.Driver(); + Driver drv = new Driver(); assertNotNull(drv); // These are always correct verifyUrl(drv, "jdbc:postgresql:test", "localhost", "5432", "test"); verifyUrl(drv, "jdbc:postgresql://localhost/test", "localhost", "5432", "test"); + verifyUrl(drv, "jdbc:postgresql://localhost,locahost2/test", "localhost,locahost2", "5432,5432", "test"); + verifyUrl(drv, "jdbc:postgresql://localhost:5433,locahost2:5434/test", "localhost,locahost2", "5433,5434", "test"); + verifyUrl(drv, "jdbc:postgresql://[::1]:5433,:5434,[::1]/test", "[::1],localhost,[::1]", "5433,5434,5432", "test"); + verifyUrl(drv, "jdbc:postgresql://localhost/test?port=8888", "localhost", "8888", "test"); verifyUrl(drv, "jdbc:postgresql://localhost:5432/test", "localhost", "5432", "test"); + verifyUrl(drv, "jdbc:postgresql://localhost:5432/test?dbname=test2", "localhost", "5432", "test2"); verifyUrl(drv, "jdbc:postgresql://127.0.0.1/anydbname", "127.0.0.1", "5432", "anydbname"); verifyUrl(drv, "jdbc:postgresql://127.0.0.1:5433/hidden", "127.0.0.1", "5433", "hidden"); + verifyUrl(drv, "jdbc:postgresql://127.0.0.1:5433/hidden?port=7777", "127.0.0.1", "7777", "hidden"); verifyUrl(drv, "jdbc:postgresql://[::1]:5740/db", "[::1]", "5740", "db"); - - // Badly formatted url's - assertFalse(drv.acceptsURL("jdbc:postgres:test")); - assertFalse(drv.acceptsURL("postgresql:test")); - assertFalse(drv.acceptsURL("db")); - assertFalse(drv.acceptsURL("jdbc:postgresql://localhost:5432a/test")); - assertFalse(drv.acceptsURL("jdbc:postgresql://localhost:500000/test")); - assertFalse(drv.acceptsURL("jdbc:postgresql://localhost:0/test")); - assertFalse(drv.acceptsURL("jdbc:postgresql://localhost:-2/test")); + verifyUrl(drv, "jdbc:postgresql://[::1]:5740/my%20data%23base%251?loggerFile=C%3A%5Cdir%5Cfile.log", "[::1]", "5740", "my data#base%1"); + + // tests for service syntax + URL urlFileProps = getClass().getResource("/pg_service/pgservicefileProps.conf"); + assertNotNull(urlFileProps); + Resources.with( + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), urlFileProps.getFile()) + ).execute(() -> { + // correct cases + verifyUrl(drv, "jdbc:postgresql://?service=driverTestService1", "test-host1", "5444", "testdb1"); + verifyUrl(drv, "jdbc:postgresql://?service=driverTestService1&host=other-host", "other-host", "5444", "testdb1"); + verifyUrl(drv, "jdbc:postgresql:///?service=driverTestService1", "test-host1", "5444", "testdb1"); + verifyUrl(drv, "jdbc:postgresql:///?service=driverTestService1&port=3333&dbname=other-db", "test-host1", "3333", "other-db"); + verifyUrl(drv, "jdbc:postgresql://localhost:5432/test?service=driverTestService1", "localhost", "5432", "test"); + verifyUrl(drv, "jdbc:postgresql://localhost:5432/test?port=7777&dbname=other-db&service=driverTestService1", "localhost", "7777", "other-db"); + verifyUrl(drv, "jdbc:postgresql://[::1]:5740/?service=driverTestService1", "[::1]", "5740", "testdb1"); + verifyUrl(drv, "jdbc:postgresql://:5740/?service=driverTestService1", "localhost", "5740", "testdb1"); + verifyUrl(drv, "jdbc:postgresql://[::1]/?service=driverTestService1", "[::1]", "5432", "testdb1"); + verifyUrl(drv, "jdbc:postgresql://localhost/?service=driverTestService2", "localhost", "5432", "testdb1"); + }); // failover urls verifyUrl(drv, "jdbc:postgresql://localhost,127.0.0.1:5432/test", "localhost,127.0.0.1", @@ -96,40 +121,322 @@ public void testAcceptsURL() throws Exception { "db"); } - private void verifyUrl(Driver drv, String url, String hosts, String ports, String dbName) + @Test + @DisableLogger(PGPropertyUtil.class) + void badUrlsTest() throws Exception { + TestUtil.initDriver(); // Set up log levels, etc. + + // Load the driver (note clients should never do it this way!) + Driver drv = new Driver(); + + // tests for service syntax + URL urlFileProps = getClass().getResource("/pg_service/pgservicefileProps.conf"); + assertNotNull(urlFileProps); + Resources.with( + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), urlFileProps.getFile()) + ).execute(() -> { + // fail cases + assertFalse(drv.acceptsURL("jdbc:postgresql://?service=driverTestService2")); + }); + } + + @Test + @DisableLogger({Driver.class, PGPropertyUtil.class}) + void rejectsBadUrls() { + TestUtil.initDriver(); // Set up log levels, etc. + // Load the driver (note clients should never do it this way!) + Driver drv = new Driver(); + + // Badly formatted url's + assertFalse(drv.acceptsURL("jdbc:postgres:test")); + assertFalse(drv.acceptsURL("jdbc:postgresql:/test")); + assertFalse(drv.acceptsURL("jdbc:postgresql:////")); + assertFalse(drv.acceptsURL("jdbc:postgresql:///?service=my data#base%1")); + assertFalse(drv.acceptsURL("jdbc:postgresql://[::1]:5740/my data#base%1")); + assertFalse(drv.acceptsURL("jdbc:postgresql://localhost/dbname?loggerFile=C%3A%5Cdir%5Cfile.%log")); + assertFalse(drv.acceptsURL("postgresql:test")); + assertFalse(drv.acceptsURL("db")); + assertFalse(drv.acceptsURL("jdbc:postgresql://localhost:5432a/test")); + assertFalse(drv.acceptsURL("jdbc:postgresql://localhost:500000/test")); + assertFalse(drv.acceptsURL("jdbc:postgresql://localhost:0/test")); + assertFalse(drv.acceptsURL("jdbc:postgresql://localhost:-2/test")); + } + + private static void verifyUrl(Driver drv, String url, String hosts, String ports, String dbName) throws Exception { - assertTrue(url, drv.acceptsURL(url)); + assertTrue(drv.acceptsURL(url), url); Method parseMethod = drv.getClass().getDeclaredMethod("parseURL", String.class, Properties.class); parseMethod.setAccessible(true); Properties p = (Properties) parseMethod.invoke(drv, url, null); - assertEquals(url, dbName, p.getProperty(PGProperty.PG_DBNAME.getName())); - assertEquals(url, hosts, p.getProperty(PGProperty.PG_HOST.getName())); - assertEquals(url, ports, p.getProperty(PGProperty.PG_PORT.getName())); + assertEquals(dbName, p.getProperty(PGProperty.PG_DBNAME.getName()), url); + assertEquals(hosts, p.getProperty(PGProperty.PG_HOST.getName()), url); + assertEquals(ports, p.getProperty(PGProperty.PG_PORT.getName()), url); } /** * Tests the connect method by connecting to the test database. */ @Test - public void testConnect() throws Exception { - TestUtil.initDriver(); // Set up log levels, etc. - + void connect() throws Exception { // Test with the url, username & password - Connection con = - DriverManager.getConnection(TestUtil.getURL(), TestUtil.getUser(), TestUtil.getPassword()); - assertNotNull(con); - con.close(); + String url = TestUtil.getURL(); + String user = TestUtil.getUser(); + String password = TestUtil.getPassword(); + + try (Connection con = DriverManager.getConnection(url, user, password);) { + assertNotNull(con, "DriverManager.getConnection(url, user, password) should succeed"); + } // Test with the username in the url - con = DriverManager.getConnection( - TestUtil.getURL() - + "&user=" + URLCoder.encode(TestUtil.getUser()) - + "&password=" + URLCoder.encode(TestUtil.getPassword())); - assertNotNull(con); - con.close(); + try (Connection con = DriverManager.getConnection( + url + + "&user=" + URLCoder.encode(user) + + "&password=" + URLCoder.encode(password));) { + assertNotNull(con, + "DriverManager.getConnection(url + \"&user=...&password=...\") should succeed"); + } + } + + /** + * Tests the connect method by connecting to the test database. + */ + @Test + @DisableLogger(PGPropertyUtil.class) + @Disabled + void connectService() throws Exception { + TestUtil.initDriver(); // Set up log levels, etc. + String wrongPort = "65536"; + + // Create temporary pg_service.conf file + Path tempDirWithPrefix = Files.createTempDirectory("junit"); + Path tempFile = Files.createTempFile(tempDirWithPrefix, "pg_service", "conf"); + try { + // Write service section + String testService1 = "testService1"; // with correct port + String testService2 = "testService2"; // with wrong port + try (PrintStream ps = new PrintStream(Files.newOutputStream(tempFile))) { + ps.printf("[%s]%nhost=%s%nport=%s%ndbname=%s%nuser=%s%npassword=%s%n", testService1, TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getUser(), TestUtil.getPassword()); + ps.printf("[%s]%nhost=%s%nport=%s%ndbname=%s%nuser=%s%npassword=%s%n", testService2, TestUtil.getServer(), wrongPort, TestUtil.getDatabase(), TestUtil.getUser(), TestUtil.getPassword()); + } + // consume service + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), tempFile.toString(), PGEnvironment.PGSYSCONFDIR.getName(), ""), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", "/tmp/dir-nonexistent") + ).execute(() -> { + // + // testing that properties overriding priority is correct (POSITIVE cases) + // + // service=correct port + Connection con = DriverManager.getConnection(String.format("jdbc:postgresql://?service=%s", testService1)); + assertNotNull(con); + con.close(); + // service=wrong port; Properties=correct port + Properties info = new Properties(); + info.setProperty("PGPORT", String.valueOf(TestUtil.getPort())); + con = DriverManager.getConnection(String.format("jdbc:postgresql://?service=%s", testService2), info); + assertNotNull(con); + con.close(); + // service=wrong port; Properties=wrong port; URL port=correct + info.setProperty("PGPORT", wrongPort); + con = DriverManager.getConnection(String.format("jdbc:postgresql://:%s/?service=%s", TestUtil.getPort(), testService2), info); + assertNotNull(con); + con.close(); + // service=wrong port; Properties=wrong port; URL port=wrong; URL argument=correct port + con = DriverManager.getConnection(String.format("jdbc:postgresql://:%s/?service=%s&port=%s", wrongPort, testService2, TestUtil.getPort()), info); + assertNotNull(con); + con.close(); - // Test with failover url + // + // testing that properties overriding priority is correct (NEGATIVE cases) + // + // service=wrong port + try { + con = DriverManager.getConnection(String.format("jdbc:postgresql://?service=%s", testService2)); + fail("Expected an SQLException because port is out of range"); + } catch (SQLException e) { + // Expected exception. + } + // service=correct port; Properties=wrong port + info.setProperty("PGPORT", wrongPort); + try { + con = DriverManager.getConnection(String.format("jdbc:postgresql://?service=%s", testService1), info); + fail("Expected an SQLException because port is out of range"); + } catch (SQLException e) { + // Expected exception. + } + // service=correct port; Properties=correct port; URL port=wrong + info.setProperty("PGPORT", String.valueOf(TestUtil.getPort())); + try { + con = DriverManager.getConnection(String.format("jdbc:postgresql://:%s/?service=%s", wrongPort, testService1), info); + fail("Expected an SQLException because port is out of range"); + } catch (SQLException e) { + // Expected exception. + } + // service=correct port; Properties=correct port; URL port=correct; URL argument=wrong port + try { + con = DriverManager.getConnection(String.format("jdbc:postgresql://:%s/?service=%s&port=%s", TestUtil.getPort(), testService1, wrongPort), info); + fail("Expected an SQLException because port is out of range"); + } catch (SQLException e) { + // Expected exception. + } + }); + } finally { + // cleanup + Files.delete(tempFile); + Files.delete(tempDirWithPrefix); + } + } + + /** + * Tests the password by connecting to the test database. + * password from .pgpass (correct) + */ + @Test + void connectPassword01() throws Exception { + TestUtil.initDriver(); // Set up log levels, etc. + + // Create temporary .pgpass file + Path tempDirWithPrefix = Files.createTempDirectory("junit"); + Path tempPgPassFile = Files.createTempFile(tempDirWithPrefix, "pgpass", "conf"); + try { + try (PrintStream psPass = new PrintStream(Files.newOutputStream(tempPgPassFile))) { + psPass.printf("%s:%s:%s:%s:%s%n", TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getUser(), TestUtil.getPassword()); + } + // ignore pg_service.conf, use .pgpass + Resources.with( + new EnvironmentVariables(PGEnvironment.PGSERVICEFILE.getName(), "", PGEnvironment.PGSYSCONFDIR.getName(), ""), + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), "", "user.home", "/tmp/dir-nonexistent", + PGEnvironment.ORG_POSTGRESQL_PGPASSFILE.getName(), tempPgPassFile.toString()) + ).execute(() -> { + // password from .pgpass (correct) + Connection con = DriverManager.getConnection(String.format("jdbc:postgresql://%s:%s/%s?user=%s", TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getUser())); + assertNotNull(con); + con.close(); + }); + } finally { + // cleanup + Files.delete(tempPgPassFile); + Files.delete(tempDirWithPrefix); + } + } + + /** + * Tests the password by connecting to the test database. + * password from service (correct) and .pgpass (wrong) + */ + @Test + void connectPassword02() throws Exception { + TestUtil.initDriver(); // Set up log levels, etc. + String wrongPassword = "random wrong"; + + // Create temporary pg_service.conf and .pgpass file + Path tempDirWithPrefix = Files.createTempDirectory("junit"); + Path tempPgServiceFile = Files.createTempFile(tempDirWithPrefix, "pg_service", "conf"); + Path tempPgPassFile = Files.createTempFile(tempDirWithPrefix, "pgpass", "conf"); + try { + // Write service section + String testService1 = "testService1"; + try (PrintStream psService = new PrintStream(Files.newOutputStream(tempPgServiceFile)); + PrintStream psPass = new PrintStream(Files.newOutputStream(tempPgPassFile))) { + psService.printf("[%s]%nhost=%s%nport=%s%ndbname=%s%nuser=%s%npassword=%s%n", testService1, TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getUser(), TestUtil.getPassword()); + psPass.printf("%s:%s:%s:%s:%s%n", TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getUser(), wrongPassword); + } + // ignore pg_service.conf, use .pgpass + Resources.with( + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), tempPgServiceFile.toString(), PGEnvironment.ORG_POSTGRESQL_PGPASSFILE.getName(), tempPgPassFile.toString()) + ).execute(() -> { + // password from service (correct) and .pgpass (wrong) + Connection con = DriverManager.getConnection(String.format("jdbc:postgresql://?service=%s", testService1)); + assertNotNull(con); + con.close(); + }); + } finally { + // cleanup + Files.delete(tempPgPassFile); + Files.delete(tempPgServiceFile); + Files.delete(tempDirWithPrefix); + } + } + + /** + * Tests the password by connecting to the test database. + * password from java property (correct) and service (wrong) and .pgpass (wrong) + */ + @Test + void connectPassword03() throws Exception { + TestUtil.initDriver(); // Set up log levels, etc. + String wrongPassword = "random wrong"; + + // Create temporary pg_service.conf and .pgpass file + Path tempDirWithPrefix = Files.createTempDirectory("junit"); + Path tempPgServiceFile = Files.createTempFile(tempDirWithPrefix, "pg_service", "conf"); + Path tempPgPassFile = Files.createTempFile(tempDirWithPrefix, "pgpass", "conf"); + try { + // Write service section + String testService1 = "testService1"; + try (PrintStream psService = new PrintStream(Files.newOutputStream(tempPgServiceFile)); + PrintStream psPass = new PrintStream(Files.newOutputStream(tempPgPassFile))) { + psService.printf("[%s]%nhost=%s%nport=%s%ndbname=%s%nuser=%s%npassword=%s%n", testService1, TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getUser(), wrongPassword); + psPass.printf("%s:%s:%s:%s:%s%n", TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getUser(), wrongPassword); + } + // ignore pg_service.conf, use .pgpass + Resources.with( + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), tempPgServiceFile.toString(), PGEnvironment.ORG_POSTGRESQL_PGPASSFILE.getName(), tempPgPassFile.toString()) + ).execute(() -> { + // password from java property (correct) and service (wrong) and .pgpass (wrong) + Properties info = new Properties(); + PGProperty.PASSWORD.set(info, TestUtil.getPassword()); + Connection con = DriverManager.getConnection(String.format("jdbc:postgresql://?service=%s", testService1), info); + assertNotNull(con); + con.close(); + }); + } finally { + // cleanup + Files.delete(tempPgPassFile); + Files.delete(tempPgServiceFile); + Files.delete(tempDirWithPrefix); + } + } + + /** + * Tests the password by connecting to the test database. + * password from URL parameter (correct) and java property (wrong) and service (wrong) and .pgpass (wrong) + */ + @Test + void connectPassword04() throws Exception { + TestUtil.initDriver(); // Set up log levels, etc. + String wrongPassword = "random wrong"; + + // Create temporary pg_service.conf and .pgpass file + Path tempDirWithPrefix = Files.createTempDirectory("junit"); + Path tempPgServiceFile = Files.createTempFile(tempDirWithPrefix, "pg_service", "conf"); + Path tempPgPassFile = Files.createTempFile(tempDirWithPrefix, "pgpass", "conf"); + try { + // Write service section + String testService1 = "testService1"; + try (PrintStream psService = new PrintStream(Files.newOutputStream(tempPgServiceFile)); + PrintStream psPass = new PrintStream(Files.newOutputStream(tempPgPassFile))) { + psService.printf("[%s]%nhost=%s%nport=%s%ndbname=%s%nuser=%s%npassword=%s%n", testService1, TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getUser(), wrongPassword); + psPass.printf("%s:%s:%s:%s:%s%n", TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getUser(), wrongPassword); + } + // ignore pg_service.conf, use .pgpass + Resources.with( + new SystemProperties(PGEnvironment.ORG_POSTGRESQL_PGSERVICEFILE.getName(), tempPgServiceFile.toString(), PGEnvironment.ORG_POSTGRESQL_PGPASSFILE.getName(), tempPgPassFile.toString()) + ).execute(() -> { + // + Properties info = new Properties(); + PGProperty.PASSWORD.set(info, wrongPassword); + Connection con = DriverManager.getConnection(String.format("jdbc:postgresql://?service=%s&password=%s", testService1, TestUtil.getPassword()), info); + assertNotNull(con); + con.close(); + }); + } finally { + // cleanup + Files.delete(tempPgPassFile); + Files.delete(tempPgServiceFile); + Files.delete(tempDirWithPrefix); + } } /** @@ -139,7 +446,7 @@ public void testConnect() throws Exception { * @throws Exception if something wrong happens */ @Test - public void testConnectFailover() throws Exception { + void connectFailover() throws Exception { String url = "jdbc:postgresql://invalidhost.not.here," + TestUtil.getServer() + ":" + TestUtil.getPort() + "/" + TestUtil.getDatabase() + "?connectTimeout=5"; Connection con = DriverManager.getConnection(url, TestUtil.getUser(), TestUtil.getPassword()); @@ -151,7 +458,7 @@ public void testConnectFailover() throws Exception { * Test that the readOnly property works. */ @Test - public void testReadOnly() throws Exception { + void readOnly() throws Exception { TestUtil.initDriver(); // Set up log levels, etc. Connection con = DriverManager.getConnection(TestUtil.getURL() + "&readOnly=true", @@ -174,17 +481,17 @@ public void testReadOnly() throws Exception { } @Test - public void testRegistration() throws Exception { + void registration() throws Exception { TestUtil.initDriver(); // Driver is initially registered because it is automatically done when class is loaded - assertTrue(org.postgresql.Driver.isRegistered()); + assertTrue(Driver.isRegistered()); ArrayList drivers = Collections.list(DriverManager.getDrivers()); searchInstanceOf: { for (java.sql.Driver driver : drivers) { - if (driver instanceof org.postgresql.Driver) { + if (driver instanceof Driver) { break searchInstanceOf; } } @@ -197,7 +504,7 @@ public void testRegistration() throws Exception { drivers = Collections.list(DriverManager.getDrivers()); for (java.sql.Driver driver : drivers) { - if (driver instanceof org.postgresql.Driver) { + if (driver instanceof Driver) { fail("Driver should be deregistered but it is still present in DriverManager's list"); } } @@ -208,7 +515,7 @@ public void testRegistration() throws Exception { drivers = Collections.list(DriverManager.getDrivers()); for (java.sql.Driver driver : drivers) { - if (driver instanceof org.postgresql.Driver) { + if (driver instanceof Driver) { return; } } @@ -216,77 +523,9 @@ public void testRegistration() throws Exception { } @Test - public void testSetLogWriter() throws Exception { - - // this is a dummy to make sure TestUtil is initialized - Connection con = DriverManager.getConnection(TestUtil.getURL(), TestUtil.getUser(), TestUtil.getPassword()); - con.close(); - String loggerLevel = System.getProperty("loggerLevel"); - String loggerFile = System.getProperty("loggerFile"); - - PrintWriter prevLog = DriverManager.getLogWriter(); - try { - PrintWriter printWriter = new PrintWriter(new NullOutputStream(System.err)); - DriverManager.setLogWriter(printWriter); - assertEquals(DriverManager.getLogWriter(), printWriter); - System.clearProperty("loggerFile"); - System.clearProperty("loggerLevel"); - Properties props = new Properties(); - props.setProperty("user", TestUtil.getUser()); - props.setProperty("password", TestUtil.getPassword()); - props.setProperty("loggerLevel", "DEBUG"); - con = DriverManager.getConnection(TestUtil.getURL(), props); - - Logger logger = Logger.getLogger("org.postgresql"); - Handler[] handlers = logger.getHandlers(); - assertTrue(handlers[0] instanceof LogWriterHandler ); - con.close(); - } finally { - DriverManager.setLogWriter(prevLog); - setProperty("loggerLevel", loggerLevel); - setProperty("loggerFile", loggerFile); - } - } - - @Test - public void testSetLogStream() throws Exception { - // this is a dummy to make sure TestUtil is initialized - Connection con = DriverManager.getConnection(TestUtil.getURL(), TestUtil.getUser(), TestUtil.getPassword()); - con.close(); - String loggerLevel = System.getProperty("loggerLevel"); - String loggerFile = System.getProperty("loggerFile"); - - try { - DriverManager.setLogStream(new NullOutputStream(System.err)); - System.clearProperty("loggerFile"); - System.clearProperty("loggerLevel"); - Properties props = new Properties(); - props.setProperty("user", TestUtil.getUser()); - props.setProperty("password", TestUtil.getPassword()); - props.setProperty("loggerLevel", "DEBUG"); - con = DriverManager.getConnection(TestUtil.getURL(), props); - - Logger logger = Logger.getLogger("org.postgresql"); - Handler []handlers = logger.getHandlers(); - assertTrue( handlers[0] instanceof LogWriterHandler ); - con.close(); - } finally { - DriverManager.setLogStream(null); - setProperty("loggerLevel", loggerLevel); - setProperty("loggerFile", loggerFile); - } - } - - @Test - public void testSystemErrIsNotClosedWhenCreatedMultipleConnections() throws Exception { + void systemErrIsNotClosedWhenCreatedMultipleConnections() throws Exception { TestUtil.initDriver(); PrintStream err = System.err; - String loggerLevel = System.getProperty("loggerLevel"); - String loggerFile = System.getProperty("loggerFile"); - - System.clearProperty("loggerLevel"); - System.clearProperty("loggerFile"); - System.setProperty("loggerLevel", "INFO"); PrintStream buffer = new PrintStream(new ByteArrayOutputStream()); System.setErr(buffer); try { @@ -300,18 +539,16 @@ public void testSystemErrIsNotClosedWhenCreatedMultipleConnections() throws Exce try { assertNotNull(con); System.err.println(); - assertFalse("The System.err should not be closed.", System.err.checkError()); + assertFalse(System.err.checkError(), "The System.err should not be closed."); } finally { con.close(); } } finally { - System.setProperty("loggerLevel", loggerLevel); - System.setProperty("loggerFile", loggerFile); System.setErr(err); } } - private void setProperty(String key, String value) { + private static void setProperty(String key, String value) { if (value == null) { System.clearProperty(key); } else { diff --git a/src/test/java/org/postgresql/test/jdbc2/EncodingTest.java b/src/test/java/org/postgresql/test/jdbc2/EncodingTest.java index 8b43a9a..5ad1ffb 100644 --- a/src/test/java/org/postgresql/test/jdbc2/EncodingTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/EncodingTest.java @@ -5,12 +5,12 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.core.Encoding; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -20,20 +20,19 @@ /** * Tests for the Encoding class. */ -public class EncodingTest { +class EncodingTest { @Test - public void testCreation() throws Exception { + void creation() throws Exception { Encoding encoding = Encoding.getDatabaseEncoding("UTF8"); assertEquals("UTF", encoding.name().substring(0, 3).toUpperCase(Locale.US)); encoding = Encoding.getDatabaseEncoding("SQL_ASCII"); assertTrue(encoding.name().toUpperCase(Locale.US).contains("ASCII")); - assertEquals("When encoding is unknown the default encoding should be used", - Encoding.defaultEncoding(), Encoding.getDatabaseEncoding("UNKNOWN")); + assertEquals(Encoding.defaultEncoding(), Encoding.getDatabaseEncoding("UNKNOWN"), "When encoding is unknown the default encoding should be used"); } @Test - public void testTransformations() throws Exception { + void transformations() throws Exception { Encoding encoding = Encoding.getDatabaseEncoding("UTF8"); assertEquals("ab", encoding.decode(new byte[]{97, 98})); @@ -47,7 +46,7 @@ public void testTransformations() throws Exception { } @Test - public void testReader() throws Exception { + void reader() throws Exception { Encoding encoding = Encoding.getDatabaseEncoding("SQL_ASCII"); InputStream stream = new ByteArrayInputStream(new byte[]{97, 98}); Reader reader = encoding.getDecodingReader(stream); diff --git a/src/test/java/org/postgresql/test/jdbc2/EnumTest.java b/src/test/java/org/postgresql/test/jdbc2/EnumTest.java index 4ea5199..08f198b 100644 --- a/src/test/java/org/postgresql/test/jdbc2/EnumTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/EnumTest.java @@ -5,12 +5,14 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.Array; import java.sql.PreparedStatement; @@ -20,15 +22,15 @@ import java.util.Arrays; import java.util.Collection; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class EnumTest extends BaseTest4 { public EnumTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } @@ -53,19 +55,17 @@ public void enumArray() throws SQLException { ResultSet rs = pstmt.executeQuery(); rs.next(); Array array = rs.getArray(1); - Assert.assertNotNull("{duplicate,new} should come up as a non-null array", array); + assertNotNull(array, "{duplicate,new} should come up as a non-null array"); Object[] objectArray = (Object[]) array.getArray(); - Assert.assertEquals( - "{duplicate,new} should come up as Java array with two entries", + assertEquals( "[duplicate, new]", - Arrays.deepToString(objectArray) - ); + Arrays.deepToString(objectArray), + "{duplicate,new} should come up as Java array with two entries"); - Assert.assertEquals( - "Enum array entries should come up as strings", + assertEquals( "java.lang.String, java.lang.String", - objectArray[0].getClass().getName() + ", " + objectArray[1].getClass().getName() - ); + objectArray[0].getClass().getName() + ", " + objectArray[1].getClass().getName(), + "Enum array entries should come up as strings"); rs.close(); pstmt.close(); } @@ -77,19 +77,17 @@ public void enumArrayArray() throws SQLException { ResultSet rs = pstmt.executeQuery(); rs.next(); Array array = rs.getArray(1); - Assert.assertNotNull(value + " should come up as a non-null array", array); + assertNotNull(array, value + " should come up as a non-null array"); Object[] objectArray = (Object[]) array.getArray(); - Assert.assertEquals( - value + " should come up as Java array with two entries", + assertEquals( "[[duplicate, new], [spike, spike]]", - Arrays.deepToString(objectArray) - ); + Arrays.deepToString(objectArray), + () -> value + " should come up as Java array with two entries"); - Assert.assertEquals( - "Enum array entries should come up as strings", + assertEquals( "[Ljava.lang.String;, [Ljava.lang.String;", - objectArray[0].getClass().getName() + ", " + objectArray[1].getClass().getName() - ); + objectArray[0].getClass().getName() + ", " + objectArray[1].getClass().getName(), + "Enum array entries should come up as strings"); rs.close(); pstmt.close(); } diff --git a/src/test/java/org/postgresql/test/jdbc2/GeometricTest.java b/src/test/java/org/postgresql/test/jdbc2/GeometricTest.java index e2c8710..64729f8 100644 --- a/src/test/java/org/postgresql/test/jdbc2/GeometricTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/GeometricTest.java @@ -5,9 +5,9 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.core.ServerVersion; import org.postgresql.geometric.PGbox; @@ -21,46 +21,57 @@ import org.postgresql.util.PGobject; import org.postgresql.util.PSQLException; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Collection; import java.util.List; -/* +/** * Test case for geometric type I/O */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class GeometricTest extends BaseTest4 { public GeometricTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } - public void setUp() throws Exception { - super.setUp(); - TestUtil.createTable(con, "testgeometric", - "boxval box, circleval circle, lsegval lseg, pathval path, polygonval polygon, pointval point, lineval line"); + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "testgeometric", + "boxval box, circleval circle, lsegval lseg, pathval path, polygonval polygon, pointval point, lineval line"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "testgeometric"); + } } - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "testgeometric"); - super.tearDown(); + public void setUp() throws Exception { + super.setUp(); + TestUtil.execute(con, "TRUNCATE testgeometric"); } private void checkReadWrite(PGobject obj, String column) throws Exception { @@ -73,10 +84,10 @@ private void checkReadWrite(PGobject obj, String column) throws Exception { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT " + column + " FROM testgeometric"); assertTrue(rs.next()); - assertEquals("PGObject#equals(rs.getObject)", obj, rs.getObject(1)); + assertEquals(obj, rs.getObject(1), "PGObject#equals(rs.getObject)"); PGobject obj2 = (PGobject) obj.clone(); obj2.setValue(rs.getString(1)); - assertEquals("PGobject.toString vs rs.getString", obj, obj2); + assertEquals(obj, obj2, "PGobject.toString vs rs.getString"); rs.close(); stmt.executeUpdate("DELETE FROM testgeometric"); @@ -151,7 +162,7 @@ public void testPGline() throws Exception { } // Generate a dataset for testing. - List linesToTest = new ArrayList(); + List linesToTest = new ArrayList<>(); for (double i = 1; i <= 3; i += 0.25) { // Test the 3-arg constructor (coefficients+constant) linesToTest.add(new PGline(i, (0 - i), (1 / i))); @@ -178,7 +189,6 @@ public void testPGline() throws Exception { checkReadWrite(testLine, columnName); } } - } } diff --git a/src/test/java/org/postgresql/test/jdbc2/GetXXXTest.java b/src/test/java/org/postgresql/test/jdbc2/GetXXXTest.java index cb717c9..31e8cd7 100644 --- a/src/test/java/org/postgresql/test/jdbc2/GetXXXTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/GetXXXTest.java @@ -5,15 +5,16 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.test.TestUtil; +import org.postgresql.util.PGInterval; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; @@ -25,13 +26,13 @@ import java.util.HashMap; /* - * Test for getObject - */ -public class GetXXXTest { - private Connection con = null; +* Test for getObject +*/ +class GetXXXTest { + private Connection con; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { con = TestUtil.openDB(); TestUtil.createTempTable(con, "test_interval", "initial timestamp with time zone, final timestamp with time zone"); @@ -45,14 +46,14 @@ public void setUp() throws Exception { pstmt.close(); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { TestUtil.dropTable(con, "test_interval"); con.close(); } @Test - public void testGetObject() throws SQLException { + void getObject() throws SQLException { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select (final-initial) as diff from test_interval"); while (rs.next()) { @@ -65,16 +66,16 @@ public void testGetObject() throws SQLException { } @Test - public void testGetUDT() throws SQLException { + void getUDT() throws SQLException { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select (final-initial) as diff from test_interval"); while (rs.next()) { // make this return a PGobject - Object obj = rs.getObject(1, new HashMap>()); + Object obj = rs.getObject(1, new HashMap<>()); // it should not be an instance of PGInterval - assertTrue(obj instanceof org.postgresql.util.PGInterval); + assertTrue(obj instanceof PGInterval); } diff --git a/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java b/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java index 78c2794..790c2f8 100644 --- a/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/IntervalTest.java @@ -5,17 +5,22 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.test.TestUtil; import org.postgresql.util.PGInterval; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.sql.Connection; import java.sql.PreparedStatement; @@ -26,27 +31,42 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; +import java.util.concurrent.ThreadLocalRandom; -public class IntervalTest { +@Isolated("Uses Locale.setDefault") +class IntervalTest { private Connection conn; - @Before - public void setUp() throws Exception { - conn = TestUtil.openDB(); - TestUtil.createTable(conn, "testinterval", "v interval"); - TestUtil.createTable(conn, "testdate", "v date"); + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "testinterval", "v interval"); + TestUtil.createTable(conn, "testdate", "v date"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "testinterval"); + TestUtil.dropTable(conn, "testdate"); + } } - @After - public void tearDown() throws Exception { - TestUtil.dropTable(conn, "testinterval"); - TestUtil.dropTable(conn, "testdate"); + @BeforeEach + void setUp() throws Exception { + conn = TestUtil.openDB(); + TestUtil.execute(conn, "TRUNCATE testinterval"); + TestUtil.execute(conn, "TRUNCATE testdate"); + } + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(conn); } @Test - public void testOnlineTests() throws SQLException { + void onlineTests() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("INSERT INTO testinterval VALUES (?)"); pstmt.setObject(1, new PGInterval(2004, 13, 28, 0, 0, 43000.9013)); pstmt.executeUpdate(); @@ -62,13 +82,13 @@ public void testOnlineTests() throws SQLException { assertEquals(11, pgi.getHours()); assertEquals(56, pgi.getMinutes()); assertEquals(40.9013, pgi.getSeconds(), 0.000001); - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); stmt.close(); } @Test - public void testStringToIntervalCoercion() throws SQLException { + void stringToIntervalCoercion() throws SQLException { Statement stmt = conn.createStatement(); stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2010-01-01'")); stmt.executeUpdate(TestUtil.insertSQL("testdate", "'2010-01-02'")); @@ -79,7 +99,7 @@ public void testStringToIntervalCoercion() throws SQLException { PreparedStatement pstmt = conn.prepareStatement( "SELECT v FROM testdate WHERE v < (?::timestamp with time zone + ? * ?::interval) ORDER BY v"); pstmt.setObject(1, makeDate(2010, 1, 1)); - pstmt.setObject(2, Integer.valueOf(2)); + pstmt.setObject(2, 2); pstmt.setObject(3, "1 day"); ResultSet rs = pstmt.executeQuery(); @@ -104,15 +124,31 @@ public void testStringToIntervalCoercion() throws SQLException { } @Test - public void testIntervalToStringCoercion() throws SQLException { + void intervalToStringCoercion() throws SQLException { PGInterval interval = new PGInterval("1 year 3 months"); String coercedStringValue = interval.toString(); - assertEquals("1 years 3 mons 0 days 0 hours 0 mins 0.0 secs", coercedStringValue); + assertEquals("1 years 3 mons", coercedStringValue); } @Test - public void testDaysHours() throws SQLException { + void checkCapitalization() throws Exception { + PGInterval pgi = new PGInterval("1 year 3 months 4 days 5 hours 6 minutes"); + PGInterval yCapital = new PGInterval("1 Year 3 months 4 days 5 hours 6 minutes"); + PGInterval mCapital = new PGInterval("1 year 3 Months 4 days 5 hours 6 minutes"); + PGInterval dCapital = new PGInterval("1 year 3 months 4 Days 5 hours 6 minutes"); + PGInterval hCapital = new PGInterval("1 year 3 months 4 days 5 Hours 6 minutes"); + PGInterval minCapital = new PGInterval("1 year 3 months 4 days 5 hours 6 Minutes"); + + assertEquals(pgi, yCapital); + assertEquals(pgi, mCapital); + assertEquals(pgi, dCapital); + assertEquals(pgi, hCapital); + assertEquals(pgi, minCapital); + } + + @Test + void daysHours() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '101:12:00'::interval"); assertTrue(rs.next()); @@ -125,7 +161,7 @@ public void testDaysHours() throws SQLException { } @Test - public void testAddRounding() { + void addRounding() { PGInterval pgi = new PGInterval(0, 0, 0, 0, 0, 0.6006); Calendar cal = Calendar.getInstance(); long origTime = cal.getTime().getTime(); @@ -138,7 +174,7 @@ public void testAddRounding() { } @Test - public void testOfflineTests() throws Exception { + void offlineTests() throws Exception { PGInterval pgi = new PGInterval(2004, 4, 20, 15, 57, 12.1); assertEquals(2004, pgi.getYears()); @@ -186,7 +222,7 @@ public void testOfflineTests() throws Exception { assertEquals(0, pgi.getSeconds(), 0); } - private Calendar getStartCalendar() { + private static Calendar getStartCalendar() { Calendar cal = new GregorianCalendar(); cal.set(Calendar.YEAR, 2005); cal.set(Calendar.MONTH, 4); @@ -200,7 +236,7 @@ private Calendar getStartCalendar() { } @Test - public void testCalendar() throws Exception { + void calendar() throws Exception { Calendar cal = getStartCalendar(); PGInterval pgi = new PGInterval("@ 1 year 1 mon 1 day 1 hour 1 minute 1 secs"); @@ -251,7 +287,7 @@ public void testCalendar() throws Exception { } @Test - public void testDate() throws Exception { + void date() throws Exception { Date date = getStartCalendar().getTime(); Date date2 = getStartCalendar().getTime(); @@ -266,7 +302,7 @@ public void testDate() throws Exception { } @Test - public void testPostgresDate() throws Exception { + void postgresDate() throws Exception { Date date = getStartCalendar().getTime(); Date date2 = getStartCalendar().getTime(); @@ -280,34 +316,44 @@ public void testPostgresDate() throws Exception { } @Test - public void testISO8601() throws Exception { + void iSO8601() throws Exception { PGInterval pgi = new PGInterval("P1Y2M3DT4H5M6S"); - assertEquals(1, pgi.getYears() ); - assertEquals(2, pgi.getMonths() ); - assertEquals(3, pgi.getDays() ); - assertEquals(4, pgi.getHours() ); - assertEquals( 5, pgi.getMinutes() ); - assertEquals( 6, pgi.getSeconds(), .1 ); + assertEquals(1, pgi.getYears()); + assertEquals(2, pgi.getMonths()); + assertEquals(3, pgi.getDays()); + assertEquals(4, pgi.getHours()); + assertEquals(5, pgi.getMinutes()); + assertEquals(6, pgi.getSeconds(), .1); pgi = new PGInterval("P-1Y2M3DT4H5M6S"); assertEquals(-1, pgi.getYears()); pgi = new PGInterval("P1Y2M"); - assertEquals(1,pgi.getYears()); + assertEquals(1, pgi.getYears()); assertEquals(2, pgi.getMonths()); assertEquals(0, pgi.getDays()); pgi = new PGInterval("P3DT4H5M6S"); - assertEquals(0,pgi.getYears()); + assertEquals(0, pgi.getYears()); pgi = new PGInterval("P-1Y-2M3DT-4H-5M-6S"); assertEquals(-1, pgi.getYears()); assertEquals(-2, pgi.getMonths()); assertEquals(-4, pgi.getHours()); + + pgi = new PGInterval("PT6.123456S"); + assertEquals(6.123456, pgi.getSeconds(), .0); + assertEquals(6, pgi.getWholeSeconds()); + assertEquals(123456, pgi.getMicroSeconds()); + + pgi = new PGInterval("PT-6.123456S"); + assertEquals(-6.123456, pgi.getSeconds(), .0); + assertEquals(-6, pgi.getWholeSeconds()); + assertEquals(-123456, pgi.getMicroSeconds()); } @Test - public void testSmallValue() throws SQLException { + void smallValue() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("INSERT INTO testinterval VALUES (?)"); pstmt.setObject(1, new PGInterval("0.0001 seconds")); pstmt.executeUpdate(); @@ -330,7 +376,7 @@ public void testSmallValue() throws SQLException { } @Test - public void testGetValueForSmallValue() throws SQLException { + void getValueForSmallValue() throws SQLException { PGInterval orig = new PGInterval("0.0001 seconds"); PGInterval copy = new PGInterval(orig.getValue()); @@ -338,7 +384,7 @@ public void testGetValueForSmallValue() throws SQLException { } @Test - public void testGetValueForSmallValueWithCommaAsDecimalSeparatorInDefaultLocale() throws SQLException { + void getValueForSmallValueWithCommaAsDecimalSeparatorInDefaultLocale() throws SQLException { Locale originalLocale = Locale.getDefault(); Locale.setDefault(Locale.GERMANY); try { @@ -352,20 +398,97 @@ public void testGetValueForSmallValueWithCommaAsDecimalSeparatorInDefaultLocale( } @Test - public void testGetSecondsForSmallValue() throws SQLException { + void getSecondsForSmallValue() throws SQLException { PGInterval pgi = new PGInterval("0.000001 seconds"); assertEquals(0.000001, pgi.getSeconds(), 0.000000001); } @Test - public void testMicroSecondsAreRoundedToNearest() throws SQLException { + void randomIntervalsRoundtripTest() throws SQLException { + ThreadLocalRandom random = ThreadLocalRandom.current(); + for (int i = 0; i < 1000000; i++) { + // PostgreSQL interval limits are -178000000..178000000 years + int years = random.nextInt(-177000000, 177000000); + int months = random.nextInt(-10000, 10000); + int days = random.nextInt(-10000, 10000); + int hours = random.nextInt(-10000, 10000); + int minutes = random.nextInt(-10000, 10000); + double seconds = random.nextDouble(-100000, 100000); + try { + assertIntervalGetValue(years, months, days, hours, minutes, seconds); + } catch (AssertionError e) { + throw e; + } catch (Throwable t) { + throw new AssertionError( + "Failed to test interval " + years + " years " + months + " months " + days + " days " + + hours + " hours " + minutes + " minutes " + seconds + " seconds", + t); + } + } + } + + @ParameterizedTest + @ValueSource( + doubles = { + 1.9999998, + 1.9999997, + 1.9999996, + 1.9999995, + 1.9999994 + } + ) + void edgeCaseSecondsTest(double seconds) throws SQLException { + assertIntervalGetValue(0, 0, 0, 0, 0, seconds); + assertIntervalGetValue(0, 0, 0, 0, 0, -seconds); + } + + private static void assertIntervalGetValue(int years, int months, int days, int hours, int minutes, double seconds) throws SQLException { + PGInterval original = new PGInterval(years, months, days, hours, minutes, seconds); + assertPGIntervalSeconds(original, seconds); + PGInterval copy = new PGInterval(original.getValue()); + assertEquals(original, copy, + () -> "years: " + years + ", months: " + months + ", days: " + days + + ", hours: " + hours + ", minutes: " + minutes + ", seconds: " + seconds + + "; Copy: years: " + copy.getYears() + ", months: " + copy.getMonths() + ", days: " + copy.getDays() + + ", hours: " + copy.getHours() + ", minutes: " + copy.getMinutes() + ", seconds: " + copy.getSeconds()); + } + + private static void assertPGIntervalSeconds(PGInterval original, double seconds) { + assertEquals(original.getSeconds(), seconds, 0.00000051, () -> "PGInterval(seconds= " + seconds + ").getSeconds()"); + } + + @Test + void secondEdgeCasesTest() { + for (int prefix = 0; prefix < 6; prefix++) { + for (int suffix = 0; suffix < 6 - prefix; suffix++) { + for (int wholeSeconds = 0; wholeSeconds < 2; wholeSeconds++) { + for (int sign = -1; sign <= 1; sign += 2) { + String microsPart = "123456".substring(0, 6 - prefix - suffix); + int micros = Integer.parseInt(microsPart + "000000".substring(0, suffix)); + double seconds = (wholeSeconds + micros / 1_000_000.0) * sign; + PGInterval interval = new PGInterval(0, 0, 0, 0, 0, seconds); + assertPGIntervalSeconds(interval, seconds); + String result = interval.getValue(); + + String expectedValue = + (sign == -1 ? "-" : "") + wholeSeconds + "." + "000000".substring(0, prefix) + microsPart + " secs"; + assertEquals(expectedValue, result, () -> "Input seconds: " + seconds); + } + } + } + } + } + + @Test + void microSecondsAreRoundedToNearest() throws SQLException { PGInterval pgi = new PGInterval("0.0000007 seconds"); assertEquals(1, pgi.getMicroSeconds()); } - private java.sql.Date makeDate(int y, int m, int d) { + @SuppressWarnings("deprecation") + private static java.sql.Date makeDate(int y, int m, int d) { return new java.sql.Date(y - 1900, m - 1, d); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/JBuilderTest.java b/src/test/java/org/postgresql/test/jdbc2/JBuilderTest.java index 872144e..3f69244 100644 --- a/src/test/java/org/postgresql/test/jdbc2/JBuilderTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/JBuilderTest.java @@ -5,47 +5,43 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; /* - * Some simple tests to check that the required components needed for JBuilder stay working - * - */ -public class JBuilderTest { - - // Set up the fixture for this testcase: the tables for this test. - @Before - public void setUp() throws Exception { - Connection con = TestUtil.openDB(); - - TestUtil.createTable(con, "test_c", "source text,cost money,imageid int4"); - - TestUtil.closeDB(con); +* Some simple tests to check that the required components needed for JBuilder stay working +* +*/ +class JBuilderTest { + + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "test_c", "source text,cost money,imageid int4"); + } } - // Tear down the fixture for this test case. - @After - public void tearDown() throws Exception { - Connection con = TestUtil.openDB(); - TestUtil.dropTable(con, "test_c"); - TestUtil.closeDB(con); + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "test_c"); + } } /* * This tests that Money types work. JDBCExplorer barfs if this fails. */ @Test - public void testMoney() throws Exception { + void money() throws Exception { Connection con = TestUtil.openDB(); Statement st = con.createStatement(); diff --git a/src/test/java/org/postgresql/test/jdbc2/LoginTimeoutTest.java b/src/test/java/org/postgresql/test/jdbc2/LoginTimeoutTest.java index 531eeb9..0075f8a 100644 --- a/src/test/java/org/postgresql/test/jdbc2/LoginTimeoutTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/LoginTimeoutTest.java @@ -5,13 +5,15 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import org.postgresql.Driver; +import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; +import org.postgresql.test.annotations.DisableLogger; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.net.InetAddress; @@ -23,66 +25,41 @@ import java.sql.SQLException; import java.util.Properties; -public class LoginTimeoutTest { - - @Before - public void setUp() throws Exception { - TestUtil.initDriver(); // Set up log levels, etc. +class LoginTimeoutTest { + @Test + void intTimeout() throws Exception { + testWithLoginTimeout("10"); } @Test - public void testIntTimeout() throws Exception { - Properties props = new Properties(); - props.setProperty("user", TestUtil.getUser()); - props.setProperty("password", TestUtil.getPassword()); - props.setProperty("loginTimeout", "10"); - - Connection conn = DriverManager.getConnection(TestUtil.getURL(), props); - conn.close(); + void floatTimeout() throws Exception { + testWithLoginTimeout("10.0"); } @Test - public void testFloatTimeout() throws Exception { - Properties props = new Properties(); - props.setProperty("user", TestUtil.getUser()); - props.setProperty("password", TestUtil.getPassword()); - props.setProperty("loginTimeout", "10.0"); - - Connection conn = DriverManager.getConnection(TestUtil.getURL(), props); - conn.close(); + void zeroTimeout() throws Exception { + testWithLoginTimeout("0"); } @Test - public void testZeroTimeout() throws Exception { - Properties props = new Properties(); - props.setProperty("user", TestUtil.getUser()); - props.setProperty("password", TestUtil.getPassword()); - props.setProperty("loginTimeout", "0"); - - Connection conn = DriverManager.getConnection(TestUtil.getURL(), props); - conn.close(); + void negativeTimeout() throws Exception { + testWithLoginTimeout("-1"); } @Test - public void testNegativeTimeout() throws Exception { - Properties props = new Properties(); - props.setProperty("user", TestUtil.getUser()); - props.setProperty("password", TestUtil.getPassword()); - props.setProperty("loginTimeout", "-1"); - - Connection conn = DriverManager.getConnection(TestUtil.getURL(), props); - conn.close(); + @DisableLogger(Driver.class) + void badTimeout() throws Exception { + testWithLoginTimeout("zzzz"); } - @Test - public void testBadTimeout() throws Exception { + private static void testWithLoginTimeout(String value) throws SQLException { Properties props = new Properties(); - props.setProperty("user", TestUtil.getUser()); - props.setProperty("password", TestUtil.getPassword()); - props.setProperty("loginTimeout", "zzzz"); + PGProperty.LOGIN_TIMEOUT.set(props, value); - Connection conn = DriverManager.getConnection(TestUtil.getURL(), props); - conn.close(); + try (Connection conn = TestUtil.openDB(props); ) { + assertTrue(conn.isValid(1), () -> "Connection should be valid when connecting with " + + PGProperty.LOGIN_TIMEOUT.name() + " = " + value); + } } private static class TimeoutHelper implements Runnable { @@ -131,7 +108,7 @@ void kill() { } @Test - public void testTimeoutOccurs() throws Exception { + void timeoutOccurs() throws Exception { // Spawn a helper thread to accept a connection and do nothing with it; // this should trigger a timeout. TimeoutHelper helper = new TimeoutHelper(); @@ -159,7 +136,7 @@ public void testTimeoutOccurs() throws Exception { } long endTime = System.nanoTime(); - assertTrue("Connection timed before 2500ms",endTime > startTime + (2500L * 1E6)); + assertTrue(endTime > startTime + (2500L * 1E6), "Connection timed before 2500ms"); } finally { helper.kill(); } diff --git a/src/test/java/org/postgresql/test/jdbc2/MiscTest.java b/src/test/java/org/postgresql/test/jdbc2/MiscTest.java index 990e1bf..5cceb39 100644 --- a/src/test/java/org/postgresql/test/jdbc2/MiscTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/MiscTest.java @@ -5,13 +5,13 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.test.TestUtil; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; @@ -22,11 +22,11 @@ import java.sql.Statement; /* - * Some simple tests based on problems reported by users. Hopefully these will help prevent previous - * problems from re-occurring ;-) - * - */ -public class MiscTest { +* Some simple tests based on problems reported by users. Hopefully these will help prevent previous +* problems from re-occurring ;-) +* +*/ +class MiscTest { /* * Some versions of the driver would return rs as a null? @@ -36,7 +36,7 @@ public class MiscTest { * Added Feb 13 2001 */ @Test - public void testDatabaseSelectNullBug() throws Exception { + void databaseSelectNullBug() throws Exception { Connection con = TestUtil.openDB(); Statement st = con.createStatement(); @@ -58,7 +58,7 @@ public void testDatabaseSelectNullBug() throws Exception { * cancelled future queries. */ @Test - public void testSingleThreadCancel() throws Exception { + void singleThreadCancel() throws Exception { Connection con = TestUtil.openDB(); Statement stmt = con.createStatement(); for (int i = 0; i < 100; i++) { @@ -70,7 +70,7 @@ public void testSingleThreadCancel() throws Exception { } @Test - public void testError() throws Exception { + void error() throws Exception { Connection con = TestUtil.openDB(); try { @@ -93,7 +93,7 @@ public void testError() throws Exception { } @Test - public void testWarning() throws Exception { + void warning() throws Exception { Connection con = TestUtil.openDB(); Statement stmt = con.createStatement(); stmt.execute("CREATE TEMP TABLE t(a int primary key)"); @@ -114,9 +114,9 @@ public void testWarning() throws Exception { con.close(); } - @Ignore + @Disabled @Test - public void xtestLocking() throws Exception { + void xtestLocking() throws Exception { Connection con = TestUtil.openDB(); Connection con2 = TestUtil.openDB(); diff --git a/src/test/java/org/postgresql/test/jdbc2/NotifyTest.java b/src/test/java/org/postgresql/test/jdbc2/NotifyTest.java index f9fb2a3..29d0468 100644 --- a/src/test/java/org/postgresql/test/jdbc2/NotifyTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/NotifyTest.java @@ -5,39 +5,50 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGConnection; import org.postgresql.PGNotification; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; -public class NotifyTest { +class NotifyTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); } - @After - public void tearDown() throws SQLException { + @AfterEach + void tearDown() throws SQLException { TestUtil.closeDB(conn); } - @Test(timeout = 60000) - public void testNotify() throws SQLException { + @Test + @Timeout(60) + void testNoNotifications() throws SQLException { + PGNotification[] notifications = conn.unwrap(PGConnection.class).getNotifications(); + assertNotNull(notifications); + assertEquals(0, notifications.length); + } + + @Test + @Timeout(60) + void testNotify() throws SQLException { Statement stmt = conn.createStatement(); stmt.executeUpdate("LISTEN mynotification"); stmt.executeUpdate("NOTIFY mynotification"); @@ -51,8 +62,9 @@ public void testNotify() throws SQLException { stmt.close(); } - @Test(timeout = 60000) - public void testNotifyArgument() throws Exception { + @Test + @Timeout(60) + void notifyArgument() throws Exception { if (!TestUtil.haveMinimumServerVersion(conn, ServerVersion.v9_0)) { return; } @@ -70,8 +82,9 @@ public void testNotifyArgument() throws Exception { stmt.close(); } - @Test(timeout = 60000) - public void testAsyncNotify() throws Exception { + @Test + @Timeout(60) + void asyncNotify() throws Exception { Statement stmt = conn.createStatement(); stmt.executeUpdate("LISTEN mynotification"); @@ -81,17 +94,17 @@ public void testAsyncNotify() throws Exception { // Wait a bit to let the notify come through... Changed this so the test takes ~2 seconds // less to run and is still as effective. PGNotification[] notifications = null; - try { - int retries = 20; - while (retries-- > 0 - && (notifications = conn.unwrap(PGConnection.class).getNotifications()) == null ) { - Thread.sleep(100); + PGConnection connection = conn.unwrap(PGConnection.class); + for (int i = 0; i < 3000; i++) { + notifications = connection.getNotifications(); + if (notifications.length > 0) { + break; } - } catch (InterruptedException ie) { + Thread.sleep(10); } - assertNotNull("Notification is expected to be delivered when subscription was created" - + " before sending notification", notifications); + assertNotNull(notifications, "Notification is expected to be delivered when subscription was created" + + " before sending notification"); assertEquals(1, notifications.length); assertEquals("mynotification", notifications[0].getName()); assertEquals("", notifications[0].getParameter()); @@ -103,8 +116,9 @@ public void testAsyncNotify() throws Exception { * To test timeouts we have to send the notification from another thread, because we * listener is blocking. */ - @Test(timeout = 60000) - public void testAsyncNotifyWithTimeout() throws Exception { + @Test + @Timeout(60) + void asyncNotifyWithTimeout() throws Exception { Statement stmt = conn.createStatement(); stmt.executeUpdate("LISTEN mynotification"); @@ -113,15 +127,15 @@ public void testAsyncNotifyWithTimeout() throws Exception { PGNotification[] notifications = conn.unwrap(PGConnection.class).getNotifications(500); long endMillis = System.currentTimeMillis(); long runtime = endMillis - startMillis; - assertEquals("There have been notifications, although none have been expected.", - "[]", Arrays.asList(notifications).toString()); - Assert.assertTrue("We didn't wait long enough! runtime=" + runtime, runtime > 450); + assertEquals("[]", Arrays.asList(notifications).toString(), "There have been notifications, although none have been expected."); + assertTrue(runtime > 450, "We didn't wait long enough! runtime=" + runtime); stmt.close(); } - @Test(timeout = 60000) - public void testAsyncNotifyWithTimeoutAndMessagesAvailableWhenStartingListening() throws Exception { + @Test + @Timeout(60) + void asyncNotifyWithTimeoutAndMessagesAvailableWhenStartingListening() throws Exception { Statement stmt = conn.createStatement(); stmt.executeUpdate("LISTEN mynotification"); @@ -138,8 +152,9 @@ public void testAsyncNotifyWithTimeoutAndMessagesAvailableWhenStartingListening( stmt.close(); } - @Test(timeout = 60000) - public void testAsyncNotifyWithEndlessTimeoutAndMessagesAvailableWhenStartingListening() throws Exception { + @Test + @Timeout(60) + void asyncNotifyWithEndlessTimeoutAndMessagesAvailableWhenStartingListening() throws Exception { Statement stmt = conn.createStatement(); stmt.executeUpdate("LISTEN mynotification"); @@ -155,14 +170,16 @@ public void testAsyncNotifyWithEndlessTimeoutAndMessagesAvailableWhenStartingLis stmt.close(); } - @Test(timeout = 60000) - public void testAsyncNotifyWithTimeoutAndMessagesSendAfter() throws Exception { + @Test + @Timeout(60) + void asyncNotifyWithTimeoutAndMessagesSendAfter() throws Exception { Statement stmt = conn.createStatement(); stmt.executeUpdate("LISTEN mynotification"); // Now we check the case where notifications are send after we have started to listen for // notifications new Thread( new Runnable() { + @Override public void run() { try { Thread.sleep(200); @@ -181,14 +198,16 @@ public void run() { stmt.close(); } - @Test(timeout = 60000) - public void testAsyncNotifyWithEndlessTimeoutAndMessagesSendAfter() throws Exception { + @Test + @Timeout(60) + void asyncNotifyWithEndlessTimeoutAndMessagesSendAfter() throws Exception { Statement stmt = conn.createStatement(); stmt.executeUpdate("LISTEN mynotification"); // Now we check the case where notifications are send after we have started to listen for // notifications forever new Thread( new Runnable() { + @Override public void run() { try { Thread.sleep(200); @@ -207,8 +226,9 @@ public void run() { stmt.close(); } - @Test(timeout = 60000) - public void testAsyncNotifyWithTimeoutAndSocketThatBecomesClosed() throws Exception { + @Test + @Timeout(60) + void asyncNotifyWithTimeoutAndSocketThatBecomesClosed() throws Exception { Statement stmt = conn.createStatement(); stmt.executeUpdate("LISTEN mynotification"); @@ -216,6 +236,7 @@ public void testAsyncNotifyWithTimeoutAndSocketThatBecomesClosed() throws Except // should be able, and this test ensures that no synchronized statements will stop the // connection from becoming closed. new Thread( new Runnable() { + @Override public void run() { try { Thread.sleep(500); @@ -230,7 +251,7 @@ public void run() { try { conn.unwrap(PGConnection.class).getNotifications(40000); - Assert.fail("The getNotifications(...) call didn't return when the socket closed."); + fail("The getNotifications(...) call didn't return when the socket closed."); } catch (SQLException e) { // We expected that } @@ -246,7 +267,7 @@ private static void connectAndNotify(String channel) { stmt2.executeUpdate("NOTIFY " + channel); stmt2.close(); } catch (Exception e) { - throw new RuntimeException("Couldn't notify '" + channel + "'.",e); + throw new RuntimeException("Couldn't notify '" + channel + "'.", e); } finally { try { conn2.close(); diff --git a/src/test/java/org/postgresql/test/jdbc2/NumericTransfer2Test.java b/src/test/java/org/postgresql/test/jdbc2/NumericTransfer2Test.java new file mode 100644 index 0000000..561e146 --- /dev/null +++ b/src/test/java/org/postgresql/test/jdbc2/NumericTransfer2Test.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2020, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.PGProperty; +import org.postgresql.core.Oid; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Properties; + +@ParameterizedClass(name = "binary = {0}, value = {1,number,#,###.##################################################}") +@MethodSource("data") +public class NumericTransfer2Test extends BaseTest4 { + + final BigDecimal value; + + public NumericTransfer2Test(BinaryMode binaryMode, BigDecimal value) { + setBinaryMode(binaryMode); + this.value = value; + } + + @Override + protected void updateProperties(Properties props) { + super.updateProperties(props); + PGProperty.BINARY_TRANSFER_ENABLE.set(props, Oid.NUMERIC); + } + + public static Iterable data() { + Collection numbers = new ArrayList<>(); + for (BinaryMode binaryMode : BinaryMode.values()) { + numbers.add(new Object[]{binaryMode, new BigDecimal("1.0")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("0.000000000000000000000000000000000000000000000000000")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("0.100000000000000000000000000000000000000000000009900")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-1.0")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-1")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("1.2")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-2.05")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("0.000000000000000000000000000990")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-0.000000000000000000000000000990")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("10.0000000000099")}); + numbers.add(new Object[]{binaryMode, new BigDecimal(".10000000000000")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("1.10000000000000")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("99999.2")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("99999")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-99999.2")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-99999")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("2147483647")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-2147483648")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("2147483648")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-2147483649")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("9223372036854775807")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-9223372036854775808")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("9223372036854775808")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-9223372036854775809")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("10223372036850000000")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("19223372036854775807")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("19223372036854775807.300")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("-19223372036854775807.300")}); + numbers.add(new Object[]{binaryMode, new BigDecimal(BigInteger.valueOf(1234567890987654321L), -1)}); + numbers.add(new Object[]{binaryMode, new BigDecimal(BigInteger.valueOf(1234567890987654321L), -5)}); + numbers.add(new Object[]{binaryMode, new BigDecimal(BigInteger.valueOf(-1234567890987654321L), -3)}); + numbers.add(new Object[]{binaryMode, new BigDecimal(BigInteger.valueOf(6), -8)}); + numbers.add(new Object[]{binaryMode, new BigDecimal("30000")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("40000").setScale(15)}); + numbers.add(new Object[]{binaryMode, new BigDecimal("20000.00000000000000000000")}); + numbers.add(new Object[]{binaryMode, new BigDecimal("9990000").setScale(10)}); + numbers.add(new Object[]{binaryMode, new BigDecimal("1000000").setScale(20)}); + numbers.add(new Object[]{binaryMode, new BigDecimal("10000000000000000000000000000000000000").setScale(20)}); + numbers.add(new Object[]{binaryMode, new BigDecimal("90000000000000000000000000000000000000")}); + } + return numbers; + } + + @Test + public void receiveValue() throws SQLException { + final String valString = value.toPlainString(); + try (Statement statement = con.createStatement()) { + final String sql = "SELECT " + valString + "::numeric"; + try (ResultSet rs = statement.executeQuery(sql)) { + assertTrue(rs.next()); + assertEquals(valString, rs.getBigDecimal(1).toPlainString(), "getBigDecimal for " + sql); + } + } + } + + @Test + public void sendReceiveValue() throws SQLException { + final String valString = value.toPlainString(); + try (PreparedStatement statement = con.prepareStatement("select ?::numeric")) { + statement.setBigDecimal(1, value); + try (ResultSet rs = statement.executeQuery()) { + rs.next(); + assertEquals(valString, rs.getBigDecimal(1).toPlainString(), "getBigDecimal for " + valString); + } + } + } +} diff --git a/src/test/java/org/postgresql/test/jdbc2/NumericTransferTest.java b/src/test/java/org/postgresql/test/jdbc2/NumericTransferTest.java index d8b46a9..52bff2c 100644 --- a/src/test/java/org/postgresql/test/jdbc2/NumericTransferTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/NumericTransferTest.java @@ -5,14 +5,14 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.postgresql.PGProperty; import org.postgresql.core.Oid; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.math.BigDecimal; import java.sql.PreparedStatement; @@ -23,7 +23,8 @@ import java.util.Collection; import java.util.Properties; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class NumericTransferTest extends BaseTest4 { public NumericTransferTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); @@ -35,9 +36,8 @@ protected void updateProperties(Properties props) { PGProperty.BINARY_TRANSFER_ENABLE.set(props, Oid.NUMERIC); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } @@ -54,12 +54,12 @@ public void receive100000() throws SQLException { rs.next(); if (i == 0) { final String expected = sign + "1"; - assertEquals("getString for " + sql, expected, rs.getString(1)); - assertEquals("getBigDecimal for " + sql, expected, rs.getBigDecimal(1).toString()); + assertEquals(expected, rs.getString(1), () -> "getString for " + sql); + assertEquals(expected, rs.getBigDecimal(1).toString(), () -> "getBigDecimal for " + sql); } else { final String expected = sign + String.format("1%0" + i + "d", 0); - assertEquals("getString for " + sql, expected, rs.getString(1)); - assertEquals("getBigDecimal for " + sql, expected, rs.getBigDecimal(1).toString()); + assertEquals(expected, rs.getString(1), () -> "getString for " + sql); + assertEquals(expected, rs.getBigDecimal(1).toString(), () -> "getBigDecimal for " + sql); } rs.close(); } @@ -76,8 +76,14 @@ public void sendReceive100000() throws SQLException { statement.setBigDecimal(1, new BigDecimal(expected)); ResultSet rs = statement.executeQuery(); rs.next(); - assertEquals("getString for " + expected, expected, rs.getString(1)); - assertEquals("getBigDecimal for " + expected, expected, rs.getBigDecimal(1).toString()); + assertEquals( + expected, + rs.getString(1), + () -> "getString for " + expected); + assertEquals( + expected, + rs.getBigDecimal(1).toString(), + () -> "getBigDecimal for " + expected); rs.close(); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/NumericTransferTest2.java b/src/test/java/org/postgresql/test/jdbc2/NumericTransferTest2.java deleted file mode 100644 index bf29493..0000000 --- a/src/test/java/org/postgresql/test/jdbc2/NumericTransferTest2.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2020, PostgreSQL Global Development Group - * See the LICENSE file in the project root for more information. - */ - -package org.postgresql.test.jdbc2; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import org.postgresql.PGProperty; -import org.postgresql.core.Oid; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Properties; - -@RunWith(Parameterized.class) -public class NumericTransferTest2 extends BaseTest4 { - - final BigDecimal value; - - public NumericTransferTest2(BinaryMode binaryMode, BigDecimal value) { - setBinaryMode(binaryMode); - this.value = value; - } - - @Override - protected void updateProperties(Properties props) { - super.updateProperties(props); - PGProperty.BINARY_TRANSFER_ENABLE.set(props, Oid.NUMERIC); - } - - @Parameterized.Parameters(name = "binary = {0}, value = {1,number,#,###.##################################################}") - public static Iterable data() { - Collection numbers = new ArrayList(); - for (BinaryMode binaryMode : BinaryMode.values()) { - numbers.add(new Object[] {binaryMode, new BigDecimal("1.0")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("0.000000000000000000000000000000000000000000000000000")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("0.100000000000000000000000000000000000000000000009900")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-1.0")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-1")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("1.2")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-2.05")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("0.000000000000000000000000000990")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-0.000000000000000000000000000990")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("10.0000000000099")}); - numbers.add(new Object[] {binaryMode, new BigDecimal(".10000000000000")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("1.10000000000000")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("99999.2")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("99999")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-99999.2")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-99999")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("2147483647")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-2147483648")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("2147483648")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-2147483649")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("9223372036854775807")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-9223372036854775808")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("9223372036854775808")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-9223372036854775809")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("10223372036850000000")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("19223372036854775807")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("19223372036854775807.300")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("-19223372036854775807.300")}); - numbers.add(new Object[] {binaryMode, new BigDecimal(BigInteger.valueOf(1234567890987654321L), -1)}); - numbers.add(new Object[] {binaryMode, new BigDecimal(BigInteger.valueOf(1234567890987654321L), -5)}); - numbers.add(new Object[] {binaryMode, new BigDecimal(BigInteger.valueOf(-1234567890987654321L), -3)}); - numbers.add(new Object[] {binaryMode, new BigDecimal(BigInteger.valueOf(6), -8)}); - numbers.add(new Object[] {binaryMode, new BigDecimal("30000")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("40000").setScale(15)}); - numbers.add(new Object[] {binaryMode, new BigDecimal("20000.00000000000000000000")}); - numbers.add(new Object[] {binaryMode, new BigDecimal("9990000").setScale(10)}); - numbers.add(new Object[] {binaryMode, new BigDecimal("1000000").setScale(20)}); - numbers.add(new Object[] {binaryMode, new BigDecimal("10000000000000000000000000000000000000").setScale(20)}); - numbers.add(new Object[] {binaryMode, new BigDecimal("90000000000000000000000000000000000000")}); - } - return numbers; - } - - @Test - public void receiveValue() throws SQLException { - final String valString = value.toPlainString(); - try (Statement statement = con.createStatement()) { - final String sql = "SELECT " + valString + "::numeric"; - try (ResultSet rs = statement.executeQuery(sql)) { - assertTrue(rs.next()); - assertEquals("getBigDecimal for " + sql, valString, rs.getBigDecimal(1).toPlainString()); - } - } - } - - @Test - public void sendReceiveValue() throws SQLException { - final String valString = value.toPlainString(); - try (PreparedStatement statement = con.prepareStatement("select ?::numeric")) { - statement.setBigDecimal(1, value); - try (ResultSet rs = statement.executeQuery()) { - rs.next(); - assertEquals("getBigDecimal for " + valString, valString, rs.getBigDecimal(1).toPlainString()); - } - } - } -} diff --git a/src/test/java/org/postgresql/test/jdbc2/OuterJoinSyntaxTest.java b/src/test/java/org/postgresql/test/jdbc2/OuterJoinSyntaxTest.java index 1511545..a440cc9 100644 --- a/src/test/java/org/postgresql/test/jdbc2/OuterJoinSyntaxTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/OuterJoinSyntaxTest.java @@ -5,10 +5,11 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.ResultSet; import java.sql.Statement; @@ -104,7 +105,7 @@ private void testOuterJoinSyntax(String theQuery, List expectedResult) t try { final ResultSet rs = st.executeQuery(theQuery); try { - Assert.assertEquals("SQL " + theQuery, TestUtil.join(TestUtil.resultSetToLines(rs)), TestUtil.join(expectedResult)); + assertEquals(TestUtil.join(TestUtil.resultSetToLines(rs)), TestUtil.join(expectedResult), "SQL " + theQuery); } finally { TestUtil.closeQuietly(rs); } diff --git a/src/test/java/org/postgresql/test/jdbc2/PGObjectGetTest.java b/src/test/java/org/postgresql/test/jdbc2/PGObjectGetTest.java index e9e7376..14e727d 100644 --- a/src/test/java/org/postgresql/test/jdbc2/PGObjectGetTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/PGObjectGetTest.java @@ -5,8 +5,8 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import org.postgresql.geometric.PGbox; import org.postgresql.geometric.PGcircle; @@ -20,9 +20,9 @@ import org.postgresql.util.PGobject; // import org.checkerframework.checker.nullness.qual.Nullable; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -30,7 +30,8 @@ import java.util.ArrayList; import java.util.Collection; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class PGObjectGetTest extends BaseTest4 { private final String sqlExpression; private final Class type; @@ -46,9 +47,8 @@ public PGObjectGetTest(BinaryMode binaryMode, String sqlExpression, this.stringValue = stringValue; } - @Parameterized.Parameters(name = "binary = {0}, sql = {1}, type = {2}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode, "null::inet", PGobject.class, "PGobject(type=inet, value=null)", null}); @@ -90,10 +90,9 @@ public void getAsString() throws SQLException { ResultSet rs = ps.executeQuery(); rs.next(); assertEquals( - "'" + sqlExpression + "'.getString(1)", stringValue, - rs.getString(1) - ); + rs.getString(1), + () -> "'" + sqlExpression + "'.getString(1)"); } private void testGet(final String s, String expected, Class type) throws SQLException { @@ -101,22 +100,19 @@ private void testGet(final String s, String expected, Class ResultSet rs = ps.executeQuery(); rs.next(); assertEquals( - "'" + s + "'.getObject(1, " + type.getSimpleName() + ".class)", expected, - printObject(rs.getObject(1, type)) - ); + printObject(rs.getObject(1, type)), + () -> "'" + s + "'.getObject(1, " + type.getSimpleName() + ".class)"); if (expected.contains("value=null)")) { // For some reason we return objects as nulls assertNull( - "'select " + s + "'.getObject(1)", - rs.getObject(1) - ); + rs.getObject(1), + () -> "'select " + s + "'.getObject(1)"); } else { assertEquals( - "'select " + s + "'.getObject(1)", expected, - printObject(rs.getObject(1)) - ); + printObject(rs.getObject(1)), + () -> "'select " + s + "'.getObject(1)"); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/PGObjectSetTest.java b/src/test/java/org/postgresql/test/jdbc2/PGObjectSetTest.java index 9266f0d..61a4e2e 100644 --- a/src/test/java/org/postgresql/test/jdbc2/PGObjectSetTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/PGObjectSetTest.java @@ -5,8 +5,9 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.geometric.PGbox; import org.postgresql.geometric.PGcircle; @@ -20,9 +21,9 @@ import org.postgresql.util.PGobject; // import org.checkerframework.checker.nullness.qual.Nullable; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.lang.reflect.InvocationTargetException; import java.sql.PreparedStatement; @@ -31,7 +32,8 @@ import java.util.ArrayList; import java.util.Collection; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class PGObjectSetTest extends BaseTest4 { private final String typeName; private final String expected; @@ -45,9 +47,8 @@ public PGObjectSetTest(BinaryMode binaryMode, Class type, this.typeName = typeName; } - @Parameterized.Parameters(name = "binary = {0}, sql = {2}, type = {1}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode, PGobject.class, "inet", "PGobject(type=inet, value=null)"}); @@ -78,6 +79,7 @@ public void setNullAsPGobject() throws SQLException { PGobject object = new PGobject(); object.setType(typeName); object.setValue(null); + assertTrue(object.isNull(), "IsNull should return true"); testSet(object, expected, PGobject.class); } @@ -99,20 +101,20 @@ private void testSet(PGobject value, String expected, Class ResultSet rs = ps.executeQuery(); rs.next(); assertEquals( - "'select ?::" + value.getType() + "'.withParam(" + printObject(value) + ").getObject(1, " + type.getSimpleName() + ".class)", expected, - printObject(rs.getObject(1, type)) + printObject(rs.getObject(1, type)), + () -> "'select ?::" + value.getType() + "'.withParam(" + printObject(value) + ").getObject(1, " + type.getSimpleName() + ".class)" ); if (expected.contains("value=null)")) { assertNull( - "'select ?::" + value.getType() + "'.withParam(" + printObject(value) + ").getObject(1)", - rs.getObject(1) + rs.getObject(1), + () -> "'select ?::" + value.getType() + "'.withParam(" + printObject(value) + ").getObject(1)" ); } else { assertEquals( - "'select ?::" + value.getType() + "'.withParam(" + printObject(value) + ").getObject(1)", expected, - printObject(rs.getObject(1)) + printObject(rs.getObject(1)), + () -> "'select ?::" + value.getType() + "'.withParam(" + printObject(value) + ").getObject(1)" ); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java b/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java index 544f823..1d727fe 100644 --- a/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/PGPropertyTest.java @@ -5,12 +5,11 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.Driver; import org.postgresql.PGProperty; @@ -20,20 +19,21 @@ import org.postgresql.test.TestUtil; import org.postgresql.util.URLCoder; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.sql.DriverPropertyInfo; import java.util.ArrayList; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.TreeMap; -public class PGPropertyTest { +class PGPropertyTest { /** * Some tests modify the "ssl" system property. To not disturb other test cases in the suite store @@ -41,13 +41,13 @@ public class PGPropertyTest { */ private String bootSSLPropertyValue; - @Before - public void setUp() { + @BeforeEach + void setUp() { bootSSLPropertyValue = System.getProperty("ssl"); } - @After - public void tearDown() { + @AfterEach + void tearDown() { if (bootSSLPropertyValue == null) { System.getProperties().remove("ssl"); } else { @@ -59,31 +59,31 @@ public void tearDown() { * Test that we can get and set all default values and all choices (if any). */ @Test - public void testGetSetAllProperties() { + void getSetAllProperties() { Properties properties = new Properties(); for (PGProperty property : PGProperty.values()) { - String value = property.get(properties); + String value = property.getOrDefault(properties); assertEquals(property.getDefaultValue(), value); property.set(properties, value); - assertEquals(value, property.get(properties)); + assertEquals(value, property.getOrDefault(properties)); if (property.getChoices() != null && property.getChoices().length > 0) { for (String choice : property.getChoices()) { property.set(properties, choice); - assertEquals(choice, property.get(properties)); + assertEquals(choice, property.getOrDefault(properties)); } } } } @Test - public void testSortOrder() { + void sortOrder() { String prevName = null; for (PGProperty property : PGProperty.values()) { String name = property.name(); if (prevName != null) { - assertTrue("PGProperty names should be sorted in ascending order: " + name + " < " + prevName, name.compareTo(prevName) > 0); + assertTrue(name.compareTo(prevName) > 0, "PGProperty names should be sorted in ascending order: " + name + " < " + prevName); } prevName = name; } @@ -93,17 +93,17 @@ public void testSortOrder() { * Test that the enum constant is common with the underlying property name. */ @Test - public void testEnumConstantNaming() { + void enumConstantNaming() { for (PGProperty property : PGProperty.values()) { String enumName = property.name().replaceAll("_", ""); - assertEquals("Naming of the enum constant [" + property.name() + assertEquals(property.getName().toLowerCase(Locale.ROOT), enumName.toLowerCase(Locale.ROOT), "Naming of the enum constant [" + property.name() + "] should follow the naming of its underlying property [" + property.getName() - + "] in PGProperty", property.getName().toLowerCase(), enumName.toLowerCase()); + + "] in PGProperty"); } } @Test - public void testDriverGetPropertyInfo() { + void driverGetPropertyInfo() { Driver driver = new Driver(); DriverPropertyInfo[] infos = driver.getPropertyInfo( "jdbc:postgresql://localhost/test?user=fred&password=secret&ssl=true", @@ -124,13 +124,13 @@ public void testDriverGetPropertyInfo() { * Test if the datasource has getter and setter for all properties. */ @Test - public void testDataSourceProperties() throws Exception { + void dataSourceProperties() throws Exception { PGSimpleDataSource dataSource = new PGSimpleDataSource(); BeanInfo info = Introspector.getBeanInfo(dataSource.getClass()); // index PropertyDescriptors by name Map propertyDescriptors = - new TreeMap(String.CASE_INSENSITIVE_ORDER); + new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (PropertyDescriptor propertyDescriptor : info.getPropertyDescriptors()) { propertyDescriptors.put(propertyDescriptor.getName(), propertyDescriptor); } @@ -138,23 +138,23 @@ public void testDataSourceProperties() throws Exception { // test for the existence of all read methods (getXXX/isXXX) and write methods (setXXX) for all // known properties for (PGProperty property : PGProperty.values()) { - if (!property.getName().startsWith("PG")) { - assertTrue("Missing getter/setter for property [" + property.getName() + "] in [" - + BaseDataSource.class + "]", propertyDescriptors.containsKey(property.getName())); + if (!property.getName().startsWith("PG") && property != PGProperty.SERVICE) { + assertTrue(propertyDescriptors.containsKey(property.getName()), "Missing getter/setter for property [" + property.getName() + "] in [" + + BaseDataSource.class + "]"); - assertNotNull("No getter for property [" + property.getName() + "] in [" - + BaseDataSource.class + "]", - propertyDescriptors.get(property.getName()).getReadMethod()); + assertNotNull(propertyDescriptors.get(property.getName()).getReadMethod(), + "No getter for property [" + property.getName() + "] in [" + + BaseDataSource.class + "]"); - assertNotNull("No setter for property [" + property.getName() + "] in [" - + BaseDataSource.class + "]", - propertyDescriptors.get(property.getName()).getWriteMethod()); + assertNotNull(propertyDescriptors.get(property.getName()).getWriteMethod(), + "No setter for property [" + property.getName() + "] in [" + + BaseDataSource.class + "]"); } } // test readability/writability of default value for (PGProperty property : PGProperty.values()) { - if (!property.getName().startsWith("PG")) { + if (!property.getName().startsWith("PG") && property != PGProperty.SERVICE) { Object propertyValue = propertyDescriptors.get(property.getName()).getReadMethod().invoke(dataSource); propertyDescriptors.get(property.getName()).getWriteMethod().invoke(dataSource, @@ -168,18 +168,18 @@ public void testDataSourceProperties() throws Exception { * more should be put in but this scratches the current itch */ @Test - public void testOverWriteDSProperties() throws Exception { + void overWriteDSProperties() throws Exception { PGSimpleDataSource dataSource = new PGSimpleDataSource(); dataSource.setAutosave(AutoSave.CONSERVATIVE); dataSource.setURL("jdbc:postgresql://localhost:5432/postgres"); - assertSame(dataSource.getAutosave(),AutoSave.CONSERVATIVE); + assertSame(AutoSave.CONSERVATIVE, dataSource.getAutosave()); } /** * Test that {@link PGProperty#isPresent(Properties)} returns a correct result in all cases. */ @Test - public void testIsPresentWithParseURLResult() throws Exception { + void isPresentWithParseURLResult() throws Exception { Properties givenProperties = new Properties(); givenProperties.setProperty("user", TestUtil.getUser()); givenProperties.setProperty("password", TestUtil.getPassword()); @@ -188,44 +188,35 @@ public void testIsPresentWithParseURLResult() throws Exception { sysProperties.remove("ssl"); System.setProperties(sysProperties); Properties parsedProperties = Driver.parseURL(TestUtil.getURL(), givenProperties); - assertFalse("SSL property should not be present", - PGProperty.SSL.isPresent(parsedProperties)); + assertFalse(PGProperty.SSL.isPresent(parsedProperties), + "SSL property should not be present"); System.setProperty("ssl", "true"); givenProperties.setProperty("ssl", "true"); parsedProperties = Driver.parseURL(TestUtil.getURL(), givenProperties); - assertTrue("SSL property should be present", PGProperty.SSL.isPresent(parsedProperties)); + assertTrue(PGProperty.SSL.isPresent(parsedProperties), "SSL property should be present"); givenProperties.setProperty("ssl", "anotherValue"); parsedProperties = Driver.parseURL(TestUtil.getURL(), givenProperties); - assertTrue("SSL property should be present", PGProperty.SSL.isPresent(parsedProperties)); + assertTrue(PGProperty.SSL.isPresent(parsedProperties), "SSL property should be present"); parsedProperties = Driver.parseURL(TestUtil.getURL() + "&ssl=true", null); - assertTrue("SSL property should be present", PGProperty.SSL.isPresent(parsedProperties)); + assertTrue(PGProperty.SSL.isPresent(parsedProperties), "SSL property should be present"); } /** * Check whether the isPresent method really works. */ @Test - public void testPresenceCheck() { + void presenceCheck() { Properties empty = new Properties(); - Object value = PGProperty.READ_ONLY.get(empty); + Object value = PGProperty.READ_ONLY.getOrDefault(empty); assertNotNull(value); assertFalse(PGProperty.READ_ONLY.isPresent(empty)); } @Test - public void testNullValue() { - Properties empty = new Properties(); - assertNull(PGProperty.LOGGER_LEVEL.getSetString(empty)); - Properties withLogging = new Properties(); - withLogging.setProperty(PGProperty.LOGGER_LEVEL.getName(), "OFF"); - assertNotNull(PGProperty.LOGGER_LEVEL.getSetString(withLogging)); - } - - @Test - public void testEncodedUrlValues() { + void encodedUrlValues() { String databaseName = "d&a%ta+base"; String userName = "&u%ser"; String password = "p%a&s^s#w!o@r*"; @@ -235,15 +226,15 @@ public void testEncodedUrlValues() { + "?user=" + URLCoder.encode(userName) + "&password=" + URLCoder.encode(password); Properties parsed = Driver.parseURL(url, new Properties()); - assertEquals("database", databaseName, PGProperty.PG_DBNAME.get(parsed)); - assertEquals("user", userName, PGProperty.USER.get(parsed)); - assertEquals("password", password, PGProperty.PASSWORD.get(parsed)); + assertEquals(databaseName, PGProperty.PG_DBNAME.getOrDefault(parsed), "database"); + assertEquals(userName, PGProperty.USER.getOrDefault(parsed), "user"); + assertEquals(password, PGProperty.PASSWORD.getOrDefault(parsed), "password"); } @Test - public void testLowerCamelCase() { + void lowerCamelCase() { // These are legacy properties excluded for backward compatibility. - ArrayList excluded = new ArrayList(); + ArrayList excluded = new ArrayList<>(); excluded.add("LOG_LEVEL"); // Remove with PR #722 excluded.add("PREPARED_STATEMENT_CACHE_SIZE_MIB"); // preparedStatementCacheSizeMi[B] excluded.add("DATABASE_METADATA_CACHE_FIELDS_MIB"); // databaseMetadataCacheFieldsMi[B] @@ -265,15 +256,15 @@ public void testLowerCamelCase() { if (!property.name().startsWith("PG")) { // Ignore all properties that start with PG String[] words = property.name().split("_"); if (words.length == 1) { - assertEquals(words[0].toLowerCase(), property.getName()); + assertEquals(words[0].toLowerCase(Locale.ROOT), property.getName()); } else { if (!excluded.contains(property.name())) { String word = ""; for (int i = 0; i < words.length; i++) { if (i == 0) { - word = words[i].toLowerCase(); + word = words[i].toLowerCase(Locale.ROOT); } else { - word += words[i].substring(0, 1).toUpperCase() + words[i].substring(1).toLowerCase(); + word += words[i].substring(0, 1).toUpperCase(Locale.ROOT) + words[i].substring(1).toLowerCase(Locale.ROOT); } } assertEquals(word, property.getName()); @@ -284,7 +275,7 @@ public void testLowerCamelCase() { } @Test - public void testEncodedUrlValuesFromDataSource() { + void encodedUrlValuesFromDataSource() { String databaseName = "d&a%ta+base"; String userName = "&u%ser"; String password = "p%a&s^s#w!o@r*"; @@ -297,10 +288,10 @@ public void testEncodedUrlValuesFromDataSource() { dataSource.setApplicationName(applicationName); Properties parsed = Driver.parseURL(dataSource.getURL(), new Properties()); - assertEquals("database", databaseName, PGProperty.PG_DBNAME.get(parsed)); + assertEquals(databaseName, PGProperty.PG_DBNAME.getOrDefault(parsed), "database"); // datasources do not pass username and password as URL parameters - assertFalse("user", PGProperty.USER.isPresent(parsed)); - assertFalse("password", PGProperty.PASSWORD.isPresent(parsed)); - assertEquals("APPLICATION_NAME", applicationName, PGProperty.APPLICATION_NAME.get(parsed)); + assertFalse(PGProperty.USER.isPresent(parsed), "user"); + assertFalse(PGProperty.PASSWORD.isPresent(parsed), "password"); + assertEquals(applicationName, PGProperty.APPLICATION_NAME.getOrDefault(parsed), "APPLICATION_NAME"); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/PGTimeTest.java b/src/test/java/org/postgresql/test/jdbc2/PGTimeTest.java index 3463f21..8978060 100644 --- a/src/test/java/org/postgresql/test/jdbc2/PGTimeTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/PGTimeTest.java @@ -5,16 +5,16 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.test.TestUtil; import org.postgresql.util.PGInterval; import org.postgresql.util.PGTime; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -231,7 +231,7 @@ private void verifyInsertAndSelect(PGTime time, boolean useSetObject) throws SQL * @param time the time object. * @return the new format instance. */ - private SimpleDateFormat createSimpleDateFormat(PGTime time) { + private static SimpleDateFormat createSimpleDateFormat(PGTime time) { String pattern = "HH:mm:ss.SSS"; if (time.getCalendar() != null) { pattern += " Z"; diff --git a/src/test/java/org/postgresql/test/jdbc2/PGTimestampTest.java b/src/test/java/org/postgresql/test/jdbc2/PGTimestampTest.java index 286eefb..e8fc523 100644 --- a/src/test/java/org/postgresql/test/jdbc2/PGTimestampTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/PGTimestampTest.java @@ -5,18 +5,20 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.test.TestUtil; import org.postgresql.util.PGInterval; import org.postgresql.util.PGTimestamp; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; @@ -32,7 +34,7 @@ * Tests {@link PGTimestamp} in various scenarios including setTimestamp, setObject for both * {@code timestamp with time zone} and {@code timestamp without time zone} data types. */ -public class PGTimestampTest { +class PGTimestampTest { /** * The name of the test table. */ @@ -40,15 +42,28 @@ public class PGTimestampTest { private Connection con; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, TEST_TABLE, "ts timestamp, tz timestamp with time zone"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, TEST_TABLE); + } + } + + @BeforeEach + void setUp() throws Exception { con = TestUtil.openDB(); - TestUtil.createTable(con, TEST_TABLE, "ts timestamp, tz timestamp with time zone"); + TestUtil.execute(con, "TRUNCATE " + TEST_TABLE); } - @After - public void tearDown() throws Exception { - TestUtil.dropTable(con, TEST_TABLE); + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(con); } @@ -58,7 +73,7 @@ public void tearDown() throws Exception { * @throws SQLException if a JDBC or database problem occurs. */ @Test - public void testTimestampWithInterval() throws SQLException { + void timestampWithInterval() throws SQLException { assumeTrue(TestUtil.haveIntegerDateTimes(con)); PGTimestamp timestamp = new PGTimestamp(System.currentTimeMillis()); PGInterval interval = new PGInterval(0, 0, 0, 1, 2, 3.14); @@ -134,7 +149,7 @@ private void verifyTimestampWithInterval(PGTimestamp timestamp, PGInterval inter * @throws SQLException if a JDBC or database problem occurs. */ @Test - public void testTimeInsertAndSelect() throws SQLException { + void timeInsertAndSelect() throws SQLException { final long now = System.currentTimeMillis(); verifyInsertAndSelect(new PGTimestamp(now), true); verifyInsertAndSelect(new PGTimestamp(now), false); @@ -230,7 +245,7 @@ private void verifyInsertAndSelect(PGTimestamp timestamp, boolean useSetObject) * @param timestamp the timestamp object. * @return the new format instance. */ - private SimpleDateFormat createSimpleDateFormat(PGTimestamp timestamp) { + private static SimpleDateFormat createSimpleDateFormat(PGTimestamp timestamp) { String pattern = "yyyy-MM-dd HH:mm:ss.SSS"; if (timestamp.getCalendar() != null) { pattern += " Z"; diff --git a/src/test/java/org/postgresql/test/jdbc2/ParameterStatusTest.java b/src/test/java/org/postgresql/test/jdbc2/ParameterStatusTest.java index 7f2a94b..5452bff 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ParameterStatusTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ParameterStatusTest.java @@ -5,16 +5,24 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.postgresql.PGConnection; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; +import org.postgresql.test.annotations.EnabledForServerVersionRange; +import org.hamcrest.MatcherAssert; import org.hamcrest.core.StringStartsWith; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.sql.Statement; import java.util.Map; +import java.util.Properties; import java.util.TimeZone; import java.util.logging.Logger; @@ -25,6 +33,7 @@ * (GUC_REPORT) parameters via PGConnection.getParameterStatuses() and * PGConnection.getParameterStatus().

          */ +@Isolated("Uses TimeZone.setDefault") public class ParameterStatusTest extends BaseTest4 { private final TimeZone tzPlus0800 = TimeZone.getTimeZone("GMT+8:00"); @@ -40,43 +49,69 @@ public void expectedInitialParameters() throws Exception { TimeZone.setDefault(tzPlus0800); con = TestUtil.openDB(); - Map params = ((PGConnection)con).getParameterStatuses(); + Map params = ((PGConnection) con).getParameterStatuses(); // PgJDBC forces the following parameters - Assert.assertEquals("UTF8", params.get("client_encoding")); - Assert.assertNotNull(params.get("DateStyle")); - Assert.assertThat(params.get("DateStyle"), StringStartsWith.startsWith("ISO")); + assertEquals("UTF8", params.get("client_encoding")); + assertNotNull(params.get("DateStyle")); + MatcherAssert.assertThat(params.get("DateStyle"), StringStartsWith.startsWith("ISO")); // PgJDBC sets TimeZone via Java's TimeZone.getDefault() // Pg reports POSIX timezones which are negated, so: - Assert.assertEquals("GMT-08:00", params.get("TimeZone")); + assertEquals("GMT-08:00", params.get("TimeZone")); // Must be reported. All these exist in 8.2 or above, and we don't bother // with test coverage older than that. - Assert.assertNotNull(params.get("integer_datetimes")); - Assert.assertNotNull(params.get("is_superuser")); - Assert.assertNotNull(params.get("server_encoding")); - Assert.assertNotNull(params.get("server_version")); - Assert.assertNotNull(params.get("session_authorization")); - Assert.assertNotNull(params.get("standard_conforming_strings")); + assertNotNull(params.get("integer_datetimes")); + assertNotNull(params.get("is_superuser")); + assertNotNull(params.get("server_encoding")); + assertNotNull(params.get("server_version")); + assertNotNull(params.get("session_authorization")); + assertNotNull(params.get("standard_conforming_strings")); if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)) { - Assert.assertNotNull(params.get("IntervalStyle")); + assertNotNull(params.get("IntervalStyle")); } else { - Assert.assertNull(params.get("IntervalStyle")); + assertNull(params.get("IntervalStyle")); } // TestUtil forces "ApplicationName=Driver Tests" // if application_name is supported (9.0 or newer) if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_0)) { - Assert.assertEquals("Driver Tests", params.get("application_name")); + assertEquals("Driver Tests", params.get("application_name")); } else { - Assert.assertNull(params.get("application_name")); + assertNull(params.get("application_name")); } // Not reported - Assert.assertNull(params.get("nonexistent")); - Assert.assertNull(params.get("enable_hashjoin")); + assertNull(params.get("nonexistent")); + assertNull(params.get("enable_hashjoin")); + + TestUtil.closeDB(con); + } + + @Test + @EnabledForServerVersionRange(gte = "9.0") + public void expectedApplicationNameWithMinVersion() throws Exception { + Properties properties = new Properties(); + properties.put("assumeMinServerVersion", "9.0"); + con = TestUtil.openDB(properties); + + Map params = ((PGConnection) con).getParameterStatuses(); + assertEquals("Driver Tests", params.get("application_name")); + + TestUtil.closeDB(con); + } + + @Test + @EnabledForServerVersionRange(gte = "9.0") + public void expectedApplicationNameWithNullMinVersion() throws Exception { + Properties properties = new Properties(); + properties.remove("assumeMinServerVersion"); + con = TestUtil.openDB(properties); + + Map params = ((PGConnection) con).getParameterStatuses(); + assertEquals("Driver Tests", params.get("application_name")); TestUtil.closeDB(con); } @@ -98,7 +133,7 @@ public void reportUpdatedParameters() throws Exception { // Parameter status should be reported before the ReadyForQuery so we will // have already processed it - Assert.assertEquals("pgjdbc_ParameterStatusTest2", ((PGConnection)con).getParameterStatus("application_name")); + assertEquals("pgjdbc_ParameterStatusTest2", ((PGConnection) con).getParameterStatus("application_name")); TestUtil.closeDB(con); } @@ -109,17 +144,17 @@ private void transactionalParametersCommon() throws Exception { Statement stmt = con.createStatement(); // Initial value assigned by TestUtil - Assert.assertEquals("Driver Tests", ((PGConnection)con).getParameterStatus("application_name")); + assertEquals("Driver Tests", ((PGConnection) con).getParameterStatus("application_name")); // PgJDBC begins an explicit txn here due to autocommit=off so the effect // should be lost on rollback but retained on commit per the docs. stmt.executeUpdate("SET application_name = 'pgjdbc_ParameterStatusTestTxn';"); - Assert.assertEquals("pgjdbc_ParameterStatusTestTxn", ((PGConnection)con).getParameterStatus("application_name")); + assertEquals("pgjdbc_ParameterStatusTestTxn", ((PGConnection) con).getParameterStatus("application_name")); // SET LOCAL is always txn scoped so the effect here will always be // unwound on txn end. stmt.executeUpdate("SET LOCAL application_name = 'pgjdbc_ParameterStatusTestLocal';"); - Assert.assertEquals("pgjdbc_ParameterStatusTestLocal", ((PGConnection)con).getParameterStatus("application_name")); + assertEquals("pgjdbc_ParameterStatusTestLocal", ((PGConnection) con).getParameterStatus("application_name")); stmt.close(); } @@ -140,7 +175,7 @@ public void transactionalParametersRollback() throws Exception { // SET unwinds on ROLLBACK con.rollback(); - Assert.assertEquals("Driver Tests", ((PGConnection)con).getParameterStatus("application_name")); + assertEquals("Driver Tests", ((PGConnection) con).getParameterStatus("application_name")); TestUtil.closeDB(con); } @@ -161,7 +196,7 @@ public void transactionalParametersCommit() throws Exception { // SET is retained on commit but SET LOCAL is unwound con.commit(); - Assert.assertEquals("pgjdbc_ParameterStatusTestTxn", ((PGConnection)con).getParameterStatus("application_name")); + assertEquals("pgjdbc_ParameterStatusTestTxn", ((PGConnection) con).getParameterStatus("application_name")); TestUtil.closeDB(con); } @@ -179,21 +214,23 @@ public void transactionalParametersAutocommit() throws Exception { Statement stmt = con.createStatement(); // A SET LOCAL in autocommit should have no visible effect as we report the reset value too - Assert.assertEquals("Driver Tests", ((PGConnection)con).getParameterStatus("application_name")); + assertEquals("Driver Tests", ((PGConnection) con).getParameterStatus("application_name")); stmt.executeUpdate("SET LOCAL application_name = 'pgjdbc_ParameterStatusTestLocal';"); - Assert.assertEquals("Driver Tests", ((PGConnection)con).getParameterStatus("application_name")); + assertEquals("Driver Tests", ((PGConnection) con).getParameterStatus("application_name")); stmt.close(); TestUtil.closeDB(con); } - @Test(expected = UnsupportedOperationException.class) + @Test public void parameterMapReadOnly() throws Exception { try { con = TestUtil.openDB(); - Map params = ((PGConnection)con).getParameterStatuses(); - params.put("DateStyle", "invalid"); - Assert.fail("Attempt to write to exposed parameters map must throw"); + Map params = ((PGConnection) con).getParameterStatuses(); + assertThrows( + UnsupportedOperationException.class, + () -> params.put("DateStyle", "invalid"), + "con..getParameterStatuses().put(...) should fail as the map should be read-only"); } finally { TestUtil.closeDB(con); } @@ -208,13 +245,13 @@ public void parameterMapIsView() throws Exception { return; } - Map params = ((PGConnection)con).getParameterStatuses(); + Map params = ((PGConnection) con).getParameterStatuses(); Statement stmt = con.createStatement(); - Assert.assertEquals("Driver Tests", params.get("application_name")); + assertEquals("Driver Tests", params.get("application_name")); stmt.executeUpdate("SET application_name = 'pgjdbc_paramstatus_view';"); - Assert.assertEquals("pgjdbc_paramstatus_view", params.get("application_name")); + assertEquals("pgjdbc_paramstatus_view", params.get("application_name")); stmt.close(); TestUtil.closeDB(con); diff --git a/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java b/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java index 2ac51e7..7d67114 100644 --- a/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java @@ -5,33 +5,46 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.PGStatement; import org.postgresql.core.ServerVersion; +import org.postgresql.jdbc.PgConnection; import org.postgresql.jdbc.PgStatement; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.TestUtil; +import org.postgresql.test.annotations.EnabledForServerVersionRange; import org.postgresql.test.util.BrokenInputStream; +import org.postgresql.util.GT; +import org.postgresql.util.PSQLState; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; +import java.sql.Connection; +import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -48,7 +61,8 @@ import java.util.logging.LogRecord; import java.util.logging.Logger; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class PreparedStatementTest extends BaseTest4 { private static final int NUMERIC_MAX_PRECISION = 1000; @@ -58,35 +72,46 @@ public PreparedStatementTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } - @Override - public void setUp() throws Exception { - super.setUp(); - TestUtil.createTable(con, "streamtable", "bin bytea, str text"); - TestUtil.createTable(con, "texttable", "ch char(3), te text, vc varchar(3)"); - TestUtil.createTable(con, "intervaltable", "i interval"); - TestUtil.createTable(con, "inttable", "a int"); - TestUtil.createTable(con, "bool_tab", "bool_val boolean, null_val boolean, tf_val boolean, " - + "truefalse_val boolean, yn_val boolean, yesno_val boolean, " - + "onoff_val boolean, onezero_val boolean"); + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "streamtable", "bin bytea, str text"); + TestUtil.createTable(con, "texttable", "ch char(3), te text, vc varchar(3)"); + TestUtil.createTable(con, "intervaltable", "i interval"); + TestUtil.createTable(con, "inttable", "a int"); + TestUtil.createTable(con, "bool_tab", "bool_val boolean, null_val boolean, tf_val boolean, " + + "truefalse_val boolean, yn_val boolean, yesno_val boolean, " + + "onoff_val boolean, onezero_val boolean"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "streamtable"); + TestUtil.dropTable(con, "texttable"); + TestUtil.dropTable(con, "intervaltable"); + TestUtil.dropTable(con, "inttable"); + TestUtil.dropTable(con, "bool_tab"); + } } @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "streamtable"); - TestUtil.dropTable(con, "texttable"); - TestUtil.dropTable(con, "intervaltable"); - TestUtil.dropTable(con, "inttable"); - TestUtil.dropTable(con, "bool_tab"); - super.tearDown(); + public void setUp() throws Exception { + super.setUp(); + TestUtil.execute(con, "TRUNCATE streamtable"); + TestUtil.execute(con, "TRUNCATE texttable"); + TestUtil.execute(con, "TRUNCATE intervaltable"); + TestUtil.execute(con, "TRUNCATE inttable"); + TestUtil.execute(con, "TRUNCATE bool_tab"); } private int getNumberOfServerPreparedStatements(String sql) @@ -108,7 +133,6 @@ private int getNumberOfServerPreparedStatements(String sql) @Test public void testSetBinaryStream() throws SQLException { - assumeByteaSupported(); ByteArrayInputStream bais; byte[] buf = new byte[10]; for (int i = 0; i < buf.length; i++) { @@ -171,7 +195,7 @@ public void testExecuteStringOnPreparedStatement() throws Exception { } @Test - public void testBinaryStreamErrorsRestartable() throws SQLException { + public void testBinaryStreamErrorsRestartable() throws SQLException, IOException { byte[] buf = new byte[10]; for (int i = 0; i < buf.length; i++) { buf[i] = (byte) i; @@ -194,25 +218,34 @@ public void testBinaryStreamErrorsRestartable() throws SQLException { runBrokenStream(is, Integer.MAX_VALUE); } - private void runBrokenStream(InputStream is, int length) throws SQLException { - PreparedStatement pstmt = null; - try { - pstmt = con.prepareStatement("INSERT INTO streamtable (bin,str) VALUES (?,?)"); - pstmt.setBinaryStream(1, is, length); - pstmt.setString(2, "Other"); - pstmt.executeUpdate(); - fail("This isn't supposed to work."); - } catch (SQLException sqle) { - // don't need to rollback because we're in autocommit mode - pstmt.close(); - - // verify the connection is still valid and the row didn't go in. - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM streamtable"); + private void runBrokenStream(InputStream is, int length) throws SQLException, IOException { + assertThrows( + SQLException.class, + () -> { + try (PreparedStatement pstmt = + con.prepareStatement("INSERT INTO streamtable (bin,str) VALUES (?,?)");) { + pstmt.setBinaryStream(1, is, length); + pstmt.setString(2, "Other"); + pstmt.executeUpdate(); + } + }, + () -> { + String available; + try { + available = String.valueOf(is.available()); + } catch (IOException e) { + available = "exception from .available(): " + e.getMessage(); + } + return "the provided stream length is " + length + + ", and the number of available bytes on stream length is " + available + + ", so the bind should fail"; + } + ); + // verify the connection is still valid and the row didn't go in. + try (Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM streamtable");) { assertTrue(rs.next()); assertEquals(0, rs.getInt(1)); - rs.close(); - stmt.close(); } } @@ -234,6 +267,131 @@ private void doSetAsciiStream(InputStream is, int length) throws SQLException { pstmt.close(); } + @Test + public void ByteArrayInputStream_setBinaryStream_toString() throws SQLException { + try (PreparedStatement pstmt = + con.prepareStatement("INSERT INTO streamtable VALUES (?,?)")) { + + byte[] buf = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + ByteArrayInputStream byteStream = new ByteArrayInputStream(buf); + + pstmt.setBinaryStream(1, byteStream, buf.length); + assertEquals("INSERT INTO streamtable VALUES (?,?)", assertDoesNotThrow( + pstmt::toString, + "PreparedStatement#toString call should succeed with half-set parameters"), "InputStream parameter should come as ?, and the second parameter is unset, so it should be ? as well"); + + pstmt.setString(2, "test"); + + String expected = "INSERT INTO streamtable VALUES (?,('test'))"; + assertEquals(expected, assertDoesNotThrow( + pstmt::toString, + "PreparedStatement#toString call should succeed after setting parameters"), "InputStream parameter should come as ? when calling PreparedStatement#toString as we can't process input stream twice yet"); + + assertEquals(expected, assertDoesNotThrow( + pstmt::toString, + "Second PreparedStatement#toString call should succeed as well"), "InputStream parameter should come as ? when calling PreparedStatement#toString as we can't process input stream twice yet"); + + pstmt.execute(); + + assertEquals(expected, assertDoesNotThrow( + pstmt::toString, + "PreparedStatement#toString call should succeed even after execute()"), "PreparedStatement#toString after .execute()"); + } + } + + @Test + public void ByteArrayInputStream_setBinaryStream_addBatch_toString() throws SQLException { + try (PreparedStatement pstmt = + con.prepareStatement("INSERT INTO streamtable VALUES (?,?)")) { + + pstmt.setBinaryStream(1, new ByteArrayInputStream(new byte[]{0, 1}), 2); + pstmt.setString(2, "line1"); + pstmt.addBatch(); + pstmt.setBinaryStream(1, new ByteArrayInputStream(new byte[]{0, 1, 2}), 3); + pstmt.setString(2, "line2"); + pstmt.addBatch(); + + String expected = "INSERT INTO streamtable VALUES (?,('line1'));\n" + + "INSERT INTO streamtable VALUES (?,('line2'))"; + assertEquals(expected, assertDoesNotThrow( + pstmt::toString, + "PreparedStatement#toString call should succeed after addBatch"), "InputStream parameter should come as ? when calling PreparedStatement#toString as we can't process input stream twice yet"); + + assertEquals(expected, assertDoesNotThrow( + pstmt::toString, + "Second PreparedStatement#toString call should succeed as well"), "InputStream parameter should come as ? when calling PreparedStatement#toString as we can't process input stream twice yet"); + + pstmt.executeBatch(); + + assertEquals("INSERT INTO streamtable VALUES (?,('line2'))", assertDoesNotThrow( + pstmt::toString, + "PreparedStatement#toString call should succeed even after executeBatch()"), "PreparedStatement#toString after executeBatch() seem to equal to the latest parameter row"); + } + } + + @Test + public void ByteArray_setBytes_toString() throws SQLException { + try (PreparedStatement pstmt = + con.prepareStatement("INSERT INTO streamtable VALUES (?,?)")) { + + byte[] buf = new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + pstmt.setBytes(1, buf); + + assertEquals("INSERT INTO streamtable VALUES ('\\x00010203040506070809'::bytea,?)", assertDoesNotThrow( + pstmt::toString, + "PreparedStatement#toString call should succeed with half-set parameters"), "byte[] parameter could be rendered, and the second parameter is unset, so it should be ? as well"); + + pstmt.setString(2, "test"); + + String expected = "INSERT INTO streamtable VALUES ('\\x00010203040506070809'::bytea,('test'))"; + assertEquals(expected, assertDoesNotThrow( + pstmt::toString, + "PreparedStatement#toString call should succeed after setting parameters"), "byte[] should be rendered when calling PreparedStatement#toString"); + + assertEquals(expected, assertDoesNotThrow( + pstmt::toString, + "Second PreparedStatement#toString call should succeed as well"), "byte[] should be rendered when calling PreparedStatement#toString"); + + pstmt.execute(); + + assertEquals(expected, assertDoesNotThrow( + pstmt::toString, + "PreparedStatement#toString call should succeed even after execute()"), "PreparedStatement#toString after .execute()"); + } + } + + @Test + public void ByteArray_setBytes_addBatch_toString() throws SQLException { + try (PreparedStatement pstmt = + con.prepareStatement("INSERT INTO streamtable VALUES (?,?)")) { + + pstmt.setBytes(1, new byte[]{0, 1}); + pstmt.setString(2, "line1"); + pstmt.addBatch(); + + pstmt.setBytes(1, new byte[]{0, 1, 2}); + pstmt.setString(2, "line2"); + pstmt.addBatch(); + + String expected = "INSERT INTO streamtable VALUES ('\\x0001'::bytea,('line1'));\n" + + "INSERT INTO streamtable VALUES ('\\x000102'::bytea,('line2'))"; + assertEquals(expected, assertDoesNotThrow( + pstmt::toString, + "PreparedStatement#toString call should succeed after addBatch"), "byte[] should be rendered when calling PreparedStatement#toString"); + + assertEquals(expected, assertDoesNotThrow( + pstmt::toString, + "Second PreparedStatement#toString call should succeed as well"), "byte[] should be rendered when calling PreparedStatement#toString"); + + pstmt.executeBatch(); + + assertEquals("INSERT INTO streamtable VALUES ('\\x000102'::bytea,('line2'))", assertDoesNotThrow( + pstmt::toString, + "PreparedStatement#toString call should succeed even after executeBatch()"), "PreparedStatement#toString after executeBatch() seem to equal to the latest parameter row"); + } + } + @Test public void testTrailingSpaces() throws SQLException { PreparedStatement pstmt = @@ -266,12 +424,12 @@ public void testBinds() throws SQLException { ps.setInt(1, 100500); ps.execute(); ResultSet rs = ps.getResultSet(); - Assert.assertNull("insert produces no results ==> getResultSet should be null", rs); - Assert.assertTrue("There are two statements => getMoreResults should be true", ps.getMoreResults()); + assertNull(rs, "insert produces no results ==> getResultSet should be null"); + assertTrue(ps.getMoreResults(), "There are two statements => getMoreResults should be true"); rs = ps.getResultSet(); - Assert.assertNotNull("select produces results ==> getResultSet should be not null", rs); - Assert.assertTrue("select produces 1 row ==> rs.next should be true", rs.next()); - Assert.assertEquals("second result of query " + query, 42, rs.getInt(1)); + assertNotNull(rs, "select produces results ==> getResultSet should be not null"); + assertTrue(rs.next(), "select produces 1 row ==> rs.next should be true"); + assertEquals(42, rs.getInt(1), "second result of query " + query); TestUtil.closeQuietly(rs); TestUtil.closeQuietly(ps); @@ -317,15 +475,19 @@ public void testSetNull() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertEquals("ok",rs.getObject(1)); + assertEquals("ok", rs.getObject(1)); rs.close(); pstmt.close(); } + @EnabledForServerVersionRange(lt = "19") @Test public void testSingleQuotes() throws SQLException { + // This test is only relevant for PostgreSQL 18 and below + // as of 4576208 in postgres non-standard strings now throw an error. + String[] testStrings = new String[]{ "bare ? question mark", "quoted \\' single quote", @@ -358,7 +520,7 @@ public void testSingleQuotes() throws SQLException { // Test with standard_conforming_strings turned off. stmt.execute("SET standard_conforming_strings TO off"); - for (int i = 0; i < testStrings.length; ++i) { + for (int i = 0; i < testStrings.length; i++) { PreparedStatement pstmt = con.prepareStatement("SELECT '" + testStrings[i] + "'"); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); @@ -370,7 +532,7 @@ public void testSingleQuotes() throws SQLException { // Test with standard_conforming_strings turned off... // ... using the escape string syntax (E''). stmt.execute("SET standard_conforming_strings TO on"); - for (int i = 0; i < testStrings.length; ++i) { + for (int i = 0; i < testStrings.length; i++) { PreparedStatement pstmt = con.prepareStatement("SELECT E'" + testStrings[i] + "'"); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); @@ -379,7 +541,7 @@ public void testSingleQuotes() throws SQLException { pstmt.close(); } // ... using standard conforming input strings. - for (int i = 0; i < testStrings.length; ++i) { + for (int i = 0; i < testStrings.length; i++) { PreparedStatement pstmt = con.prepareStatement("SELECT '" + testStringsStdConf[i] + "'"); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); @@ -504,14 +666,17 @@ public void testDoubleQuestionMark() throws SQLException { st = con.prepareStatement("select ??- lseg '((-1,0),(1,0))';"); rs = st.executeQuery(); assertTrue(rs.next()); - assertEquals("t", rs.getString(1)); + // Bool values in binary mode are first converted to their Java type (Boolean), and then + // converted to String, which means that we receive 'true'. Bool values in text mode are + // returned as the same text value that was returned by the server, i.e. 't'. + assertEquals(binaryMode == BinaryMode.FORCE && preferQueryMode != PreferQueryMode.SIMPLE ? "true" : "t", rs.getString(1)); assertFalse(rs.next()); st.close(); st = con.prepareStatement("select lseg '((-1,0),(1,0))' ??# box '((-2,-2),(2,2))';"); rs = st.executeQuery(); assertTrue(rs.next()); - assertEquals("t", rs.getString(1)); + assertEquals(binaryMode == BinaryMode.FORCE && preferQueryMode != PreferQueryMode.SIMPLE ? "true" : "t", rs.getString(1)); assertFalse(rs.next()); st.close(); } @@ -542,7 +707,7 @@ public void testNumeric() throws SQLException { values[3] = new BigDecimal("-" + minValueString); pstmt = con.prepareStatement("insert into numeric_tab values (?,?,?,?,?)"); - for (int i = 1; i < 5 ; i++) { + for (int i = 1; i < 5; i++) { pstmt.setBigDecimal(i, values[i - 1]); } @@ -553,8 +718,8 @@ public void testNumeric() throws SQLException { pstmt = con.prepareStatement("select * from numeric_tab"); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - for (int i = 1; i < 5 ; i++) { - assertTrue(rs.getBigDecimal(i).compareTo(values[i - 1]) == 0); + for (int i = 1; i < 5; i++) { + assertEquals(0, rs.getBigDecimal(i).compareTo(values[i - 1])); } rs.getDouble(5); assertTrue(rs.wasNull()); @@ -581,8 +746,8 @@ public void testDouble() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); double d = rs.getDouble(1); - assertTrue(rs.getDouble(1) == 1.0E125); - assertTrue(rs.getDouble(2) == 1.0E-130); + assertEquals(1.0E125, rs.getDouble(1)); + assertEquals(1.0E-130, rs.getDouble(2)); rs.getDouble(3); assertTrue(rs.wasNull()); rs.close(); @@ -608,8 +773,8 @@ public void testFloat() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); float f = rs.getFloat(1); - assertTrue("expected 1.0E37,received " + rs.getFloat(1), rs.getFloat(1) == (float) 1.0E37); - assertTrue("expected 1.0E-37,received " + rs.getFloat(2), rs.getFloat(2) == (float) 1.0E-37); + assertEquals((float) 1.0E37, rs.getFloat(1), "expected 1.0E37,received " + rs.getFloat(1)); + assertEquals((float) 1.0E-37, rs.getFloat(2), "expected 1.0E-37,received " + rs.getFloat(2)); rs.getDouble(3); assertTrue(rs.wasNull()); rs.close(); @@ -630,44 +795,125 @@ public void testNaNLiteralsPreparedStatement() throws SQLException { checkNaNLiterals(stmt, stmt.executeQuery()); } - private void checkNaNLiterals(Statement stmt, ResultSet rs) throws SQLException { + private static void checkNaNLiterals(Statement stmt, ResultSet rs) throws SQLException { rs.next(); - assertTrue("Double.isNaN((Double) rs.getObject", Double.isNaN((Double) rs.getObject(3))); - assertTrue("Double.isNaN(rs.getDouble", Double.isNaN(rs.getDouble(3))); - assertTrue("Float.isNaN((Float) rs.getObject", Float.isNaN((Float) rs.getObject(2))); - assertTrue("Float.isNaN(rs.getFloat", Float.isNaN(rs.getFloat(2))); - assertTrue("Double.isNaN((Double) rs.getObject", Double.isNaN((Double) rs.getObject(1))); - assertTrue("Double.isNaN(rs.getDouble", Double.isNaN(rs.getDouble(1))); + assertTrue(Double.isNaN((Double) rs.getObject(3)), "Double.isNaN((Double) rs.getObject"); + assertTrue(Double.isNaN(rs.getDouble(3)), "Double.isNaN(rs.getDouble"); + assertTrue(Float.isNaN((Float) rs.getObject(2)), "Float.isNaN((Float) rs.getObject"); + assertTrue(Float.isNaN(rs.getFloat(2)), "Float.isNaN(rs.getFloat"); + assertTrue(Double.isNaN((Double) rs.getObject(1)), "Double.isNaN((Double) rs.getObject"); + assertTrue(Double.isNaN(rs.getDouble(1)), "Double.isNaN(rs.getDouble"); + try { + rs.getBigDecimal(1); + fail("NaN::numeric rs.getBigDecimal"); + } catch (SQLException e) { + assertEquals(PSQLState.NUMERIC_VALUE_OUT_OF_RANGE.getState(), e.getSQLState()); + assertEquals(GT.tr("Bad value for type {0} : {1}", "BigDecimal", "NaN"), e.getMessage()); + } + rs.close(); stmt.close(); } @Test - public void testNaNSetDoubleFloat() throws SQLException { - PreparedStatement ps = con.prepareStatement("select ?, ?"); + public void testInfinityLiteralsSimpleStatement() throws SQLException { + assumeMinimumServerVersion("v14 introduced 'Infinity'::numeric", ServerVersion.v14); + + String query = "SELECT 'Infinity'::numeric, 'Infinity'::real, 'Infinity'::double precision, " + + "'-Infinity'::numeric, '-Infinity'::real, '-Infinity'::double precision"; + try (Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(query)) { + checkInfinityLiterals(rs); + } + } + + @Test + public void testInfinityLiteralsPreparedStatement() throws SQLException { + assumeMinimumServerVersion("v14 introduced 'Infinity'::numeric", ServerVersion.v14); + + String query = "SELECT 'Infinity'::numeric, 'Infinity'::real, 'Infinity'::double precision, " + + "'-Infinity'::numeric, '-Infinity'::real, '-Infinity'::double precision"; + try (PreparedStatement stmt = con.prepareStatement(query); + ResultSet rs = stmt.executeQuery()) { + checkInfinityLiterals(rs); + } + } + + private static void checkInfinityLiterals(ResultSet rs) throws SQLException { + rs.next(); + assertEquals(Double.POSITIVE_INFINITY, rs.getObject(1), "inf numeric rs.getObject"); + assertEquals(Double.POSITIVE_INFINITY, rs.getDouble(1), 0.0, "inf numeric rs.getDouble"); + assertEquals(Float.POSITIVE_INFINITY, rs.getObject(2), "inf real rs.getObject"); + assertEquals(Float.POSITIVE_INFINITY, rs.getFloat(2), 0.0, "inf real rs.getFloat"); + assertEquals(Double.POSITIVE_INFINITY, rs.getObject(3), "inf double precision rs.getObject"); + assertEquals(Double.POSITIVE_INFINITY, rs.getDouble(3), 0.0, "inf double precision rs.getDouble"); + + assertEquals(Double.NEGATIVE_INFINITY, rs.getObject(4), "-inf numeric rs.getObject"); + assertEquals(Double.NEGATIVE_INFINITY, rs.getDouble(4), 0.0, "-inf numeric rs.getDouble"); + assertEquals(Float.NEGATIVE_INFINITY, rs.getObject(5), "-inf real rs.getObject"); + assertEquals(Float.NEGATIVE_INFINITY, rs.getFloat(5), 0.0, "-inf real rs.getFloat"); + assertEquals(Double.NEGATIVE_INFINITY, rs.getObject(6), "-inf double precision rs.getObject"); + assertEquals(Double.NEGATIVE_INFINITY, rs.getDouble(6), 0.0, "-inf double precision rs.getDouble"); + + try { + rs.getBigDecimal(1); + fail("inf numeric rs.getBigDecimal"); + } catch (SQLException e) { + assertEquals(PSQLState.NUMERIC_VALUE_OUT_OF_RANGE.getState(), e.getSQLState()); + assertEquals(GT.tr("Bad value for type {0} : {1}", "BigDecimal", "Infinity"), e.getMessage()); + } + + try { + rs.getBigDecimal(4); + fail("-inf numeric rs.getBigDecimal"); + } catch (SQLException e) { + assertEquals(PSQLState.NUMERIC_VALUE_OUT_OF_RANGE.getState(), e.getSQLState()); + assertEquals(GT.tr("Bad value for type {0} : {1}", "BigDecimal", "-Infinity"), e.getMessage()); + } + } + + @Test + public void testSpecialSetDoubleFloat() throws SQLException { + PreparedStatement ps = con.prepareStatement("select ?, ?, ?, ?, ?, ?"); ps.setFloat(1, Float.NaN); ps.setDouble(2, Double.NaN); + ps.setFloat(3, Float.POSITIVE_INFINITY); + ps.setDouble(4, Double.POSITIVE_INFINITY); + ps.setFloat(5, Float.NEGATIVE_INFINITY); + ps.setDouble(6, Double.NEGATIVE_INFINITY); checkNaNParams(ps); } @Test - public void testNaNSetObject() throws SQLException { - PreparedStatement ps = con.prepareStatement("select ?, ?"); + public void testSpecialSetObject() throws SQLException { + PreparedStatement ps = con.prepareStatement("select ?, ?, ?, ?, ?, ?"); ps.setObject(1, Float.NaN); ps.setObject(2, Double.NaN); + ps.setObject(3, Float.POSITIVE_INFINITY); + ps.setObject(4, Double.POSITIVE_INFINITY); + ps.setObject(5, Float.NEGATIVE_INFINITY); + ps.setObject(6, Double.NEGATIVE_INFINITY); checkNaNParams(ps); } - private void checkNaNParams(PreparedStatement ps) throws SQLException { + private static void checkNaNParams(PreparedStatement ps) throws SQLException { ResultSet rs = ps.executeQuery(); rs.next(); - assertTrue("Float.isNaN((Float) rs.getObject", Float.isNaN((Float) rs.getObject(1))); - assertTrue("Float.isNaN(rs.getFloat", Float.isNaN(rs.getFloat(1))); - assertTrue("Double.isNaN(rs.getDouble", Double.isNaN(rs.getDouble(2))); - assertTrue("Double.isNaN(rs.getDouble", Double.isNaN(rs.getDouble(2))); + assertTrue(Float.isNaN((Float) rs.getObject(1)), "Float.isNaN((Float) rs.getObject"); + assertTrue(Float.isNaN(rs.getFloat(1)), "Float.isNaN(rs.getFloat"); + assertTrue(Double.isNaN((Double) rs.getObject(2)), "Double.isNaN((Double) rs.getObject"); + assertTrue(Double.isNaN(rs.getDouble(2)), "Double.isNaN(rs.getDouble"); + assertEquals(Float.POSITIVE_INFINITY, rs.getObject(3), "Float.POSITIVE_INFINITY rs.getObject"); + assertEquals(Float.POSITIVE_INFINITY, rs.getFloat(3), 0, "Float.POSITIVE_INFINITY rs.getFloat"); + assertEquals(Double.POSITIVE_INFINITY, rs.getObject(4), "Double.POSITIVE_INFINITY rs.getObject"); + assertEquals(Double.POSITIVE_INFINITY, rs.getDouble(4), 0, "Double.POSITIVE_INFINITY rs.getDouble"); + assertEquals(Float.NEGATIVE_INFINITY, rs.getObject(5), "Float.NEGATIVE_INFINITY rs.getObject"); + assertEquals(Float.NEGATIVE_INFINITY, rs.getFloat(5), 0, "Float.NEGATIVE_INFINITY rs.getFloat"); + assertEquals(Double.NEGATIVE_INFINITY, rs.getObject(6), "Double.NEGATIVE_INFINITY rs.getObject"); + assertEquals(Double.NEGATIVE_INFINITY, rs.getDouble(6), 0, "Double.NEGATIVE_INFINITY rs.getDouble"); TestUtil.closeQuietly(rs); TestUtil.closeQuietly(ps); @@ -700,7 +946,7 @@ public void testBoolean(int prepareThreshold) throws SQLException { pstmt.setObject(7, "On", Types.BIT); pstmt.setObject(8, '1', Types.BIT); pstmt.setObject(8, "1", Types.BIT); - assertEquals("one row inserted, true values", 1, pstmt.executeUpdate()); + assertEquals(1, pstmt.executeUpdate(), "one row inserted, true values"); // Test FALSE values pstmt.setBoolean(1, false); pstmt.setObject(1, Boolean.FALSE); @@ -716,13 +962,13 @@ public void testBoolean(int prepareThreshold) throws SQLException { pstmt.setObject(7, "Off", Types.BOOLEAN); pstmt.setObject(8, "0", Types.BOOLEAN); pstmt.setObject(8, '0', Types.BOOLEAN); - assertEquals("one row inserted, false values", 1, pstmt.executeUpdate()); + assertEquals(1, pstmt.executeUpdate(), "one row inserted, false values"); // Test weird values pstmt.setObject(1, (byte) 0, Types.BOOLEAN); pstmt.setObject(2, BigDecimal.ONE, Types.BOOLEAN); pstmt.setObject(3, 0L, Types.BOOLEAN); pstmt.setObject(4, 0x1, Types.BOOLEAN); - pstmt.setObject(5, new Float(0), Types.BOOLEAN); + pstmt.setObject(5, (float) 0, Types.BOOLEAN); pstmt.setObject(5, 1.0d, Types.BOOLEAN); pstmt.setObject(5, 0.0f, Types.BOOLEAN); pstmt.setObject(6, Integer.valueOf("1"), Types.BOOLEAN); @@ -735,26 +981,26 @@ public void testBoolean(int prepareThreshold) throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue("expected true, received " + rs.getBoolean(1), rs.getBoolean(1)); + assertTrue(rs.getBoolean(1), "expected true, received " + rs.getBoolean(1)); rs.getFloat(2); assertTrue(rs.wasNull()); - assertTrue("expected true, received " + rs.getBoolean(3), rs.getBoolean(3)); - assertTrue("expected true, received " + rs.getBoolean(4), rs.getBoolean(4)); - assertTrue("expected true, received " + rs.getBoolean(5), rs.getBoolean(5)); - assertTrue("expected true, received " + rs.getBoolean(6), rs.getBoolean(6)); - assertTrue("expected true, received " + rs.getBoolean(7), rs.getBoolean(7)); - assertTrue("expected true, received " + rs.getBoolean(8), rs.getBoolean(8)); + assertTrue(rs.getBoolean(3), "expected true, received " + rs.getBoolean(3)); + assertTrue(rs.getBoolean(4), "expected true, received " + rs.getBoolean(4)); + assertTrue(rs.getBoolean(5), "expected true, received " + rs.getBoolean(5)); + assertTrue(rs.getBoolean(6), "expected true, received " + rs.getBoolean(6)); + assertTrue(rs.getBoolean(7), "expected true, received " + rs.getBoolean(7)); + assertTrue(rs.getBoolean(8), "expected true, received " + rs.getBoolean(8)); assertTrue(rs.next()); - assertFalse("expected false, received " + rs.getBoolean(1), rs.getBoolean(1)); + assertFalse(rs.getBoolean(1), "expected false, received " + rs.getBoolean(1)); rs.getBoolean(2); assertTrue(rs.wasNull()); - assertFalse("expected false, received " + rs.getBoolean(3), rs.getBoolean(3)); - assertFalse("expected false, received " + rs.getBoolean(4), rs.getBoolean(4)); - assertFalse("expected false, received " + rs.getBoolean(5), rs.getBoolean(5)); - assertFalse("expected false, received " + rs.getBoolean(6), rs.getBoolean(6)); - assertFalse("expected false, received " + rs.getBoolean(7), rs.getBoolean(7)); - assertFalse("expected false, received " + rs.getBoolean(8), rs.getBoolean(8)); + assertFalse(rs.getBoolean(3), "expected false, received " + rs.getBoolean(3)); + assertFalse(rs.getBoolean(4), "expected false, received " + rs.getBoolean(4)); + assertFalse(rs.getBoolean(5), "expected false, received " + rs.getBoolean(5)); + assertFalse(rs.getBoolean(6), "expected false, received " + rs.getBoolean(6)); + assertFalse(rs.getBoolean(7), "expected false, received " + rs.getBoolean(7)); + assertFalse(rs.getBoolean(8), "expected false, received " + rs.getBoolean(8)); rs.close(); pstmt.close(); @@ -771,14 +1017,14 @@ public void testBadBoolean() throws SQLException { pstmt.setObject(1, "this is not boolean", Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean: \"this is not boolean\"", e.getMessage()); } try { pstmt.setObject(1, 'X', Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean: \"X\"", e.getMessage()); } try { @@ -786,63 +1032,63 @@ public void testBadBoolean() throws SQLException { pstmt.setObject(1, obj, Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean", e.getMessage()); } try { pstmt.setObject(1, "1.0", Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean: \"1.0\"", e.getMessage()); } try { pstmt.setObject(1, "-1", Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean: \"-1\"", e.getMessage()); } try { pstmt.setObject(1, "ok", Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean: \"ok\"", e.getMessage()); } try { pstmt.setObject(1, 0.99f, Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean: \"0.99\"", e.getMessage()); } try { pstmt.setObject(1, -0.01d, Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean: \"-0.01\"", e.getMessage()); } try { pstmt.setObject(1, new java.sql.Date(0), Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean", e.getMessage()); } try { pstmt.setObject(1, new java.math.BigInteger("1000"), Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean: \"1000\"", e.getMessage()); } try { pstmt.setObject(1, Math.PI, Types.BOOLEAN); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean: \"3.141592653589793\"", e.getMessage()); } pstmt.close(); @@ -855,11 +1101,11 @@ public void testSetFloatInteger() throws SQLException { pstmt.executeUpdate(); pstmt.close(); - Integer maxInteger = new Integer(2147483647); - Integer minInteger = new Integer(-2147483648); + Integer maxInteger = 2147483647; + Integer minInteger = -2147483648; - Double maxFloat = new Double(2147483647); - Double minFloat = new Double(-2147483648); + Double maxFloat = 2147483647.0; + Double minFloat = (double) -2147483648; pstmt = con.prepareStatement("insert into float_tab values (?,?,?)"); pstmt.setObject(1, maxInteger, Types.FLOAT); @@ -872,10 +1118,8 @@ public void testSetFloatInteger() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue("expected " + maxFloat + " ,received " + rs.getObject(1), - rs.getObject(1).equals(maxFloat)); - assertTrue("expected " + minFloat + " ,received " + rs.getObject(2), - rs.getObject(2).equals(minFloat)); + assertEquals(maxFloat, rs.getObject(1)); + assertEquals(minFloat, rs.getObject(2)); rs.getFloat(3); assertTrue(rs.wasNull()); rs.close(); @@ -892,8 +1136,8 @@ public void testSetFloatString() throws SQLException { String maxStringFloat = "1.0E37"; String minStringFloat = "1.0E-37"; - Double maxFloat = new Double(1.0E37); - Double minFloat = new Double(1.0E-37); + Double maxFloat = 1.0E37; + Double minFloat = 1.0E-37; pstmt = con.prepareStatement("insert into float_tab values (?,?,?)"); pstmt.setObject(1, maxStringFloat, Types.FLOAT); @@ -910,16 +1154,16 @@ public void testSetFloatString() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue(((Double) rs.getObject(1)).equals(maxFloat)); - assertTrue(((Double) rs.getObject(2)).equals(minFloat)); - assertTrue(rs.getDouble(1) == maxFloat); - assertTrue(rs.getDouble(2) == minFloat); + assertEquals(maxFloat, ((Double) rs.getObject(1))); + assertEquals(minFloat, ((Double) rs.getObject(2))); + assertEquals(maxFloat, rs.getDouble(1)); + assertEquals(minFloat, rs.getDouble(2)); rs.getFloat(3); assertTrue(rs.wasNull()); assertTrue(rs.next()); - assertTrue("expected true, received " + rs.getBoolean(1), rs.getBoolean(1)); - assertFalse("expected false,received " + rs.getBoolean(2), rs.getBoolean(2)); + assertTrue(rs.getBoolean(1)); + assertFalse(rs.getBoolean(2)); rs.close(); pstmt.close(); @@ -935,8 +1179,8 @@ public void testSetFloatBigDecimal() throws SQLException { BigDecimal maxBigDecimalFloat = new BigDecimal("1.0E37"); BigDecimal minBigDecimalFloat = new BigDecimal("1.0E-37"); - Double maxFloat = new Double(1.0E37); - Double minFloat = new Double(1.0E-37); + Double maxFloat = 1.0E37; + Double minFloat = 1.0E-37; pstmt = con.prepareStatement("insert into float_tab values (?,?,?)"); pstmt.setObject(1, maxBigDecimalFloat, Types.FLOAT); @@ -949,10 +1193,8 @@ public void testSetFloatBigDecimal() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue("expected " + maxFloat + " ,received " + rs.getObject(1), - ((Double) rs.getObject(1)).equals(maxFloat)); - assertTrue("expected " + minFloat + " ,received " + rs.getObject(2), - ((Double) rs.getObject(2)).equals(minFloat)); + assertEquals(maxFloat, ((Double) rs.getObject(1))); + assertEquals(minFloat, ((Double) rs.getObject(2))); rs.getFloat(3); assertTrue(rs.wasNull()); rs.close(); @@ -961,16 +1203,17 @@ public void testSetFloatBigDecimal() throws SQLException { } @Test + @SuppressWarnings("deprecation") public void testSetTinyIntFloat() throws SQLException { PreparedStatement pstmt = con .prepareStatement("CREATE temp TABLE tiny_int (max_val int4, min_val int4, null_val int4)"); pstmt.executeUpdate(); pstmt.close(); - Integer maxInt = new Integer(127); - Integer minInt = new Integer(-127); - Float maxIntFloat = new Float(127); - Float minIntFloat = new Float(-127); + Integer maxInt = 127; + Integer minInt = -127; + Float maxIntFloat = 127F; + Float minIntFloat = (float) -127; pstmt = con.prepareStatement("insert into tiny_int values (?,?,?)"); pstmt.setObject(1, maxIntFloat, Types.TINYINT); @@ -983,32 +1226,28 @@ public void testSetTinyIntFloat() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertEquals("maxInt as rs.getObject", maxInt, rs.getObject(1)); - assertEquals("minInt as rs.getObject", minInt, rs.getObject(2)); + assertEquals(maxInt, rs.getObject(1), "maxInt as rs.getObject"); + assertEquals(minInt, rs.getObject(2), "minInt as rs.getObject"); rs.getObject(3); - assertTrue("rs.wasNull after rs.getObject", rs.wasNull()); - assertEquals("maxInt as rs.getInt", maxInt, (Integer) rs.getInt(1)); - assertEquals("minInt as rs.getInt", minInt, (Integer) rs.getInt(2)); + assertTrue(rs.wasNull(), "rs.wasNull after rs.getObject"); + assertEquals(maxInt, (Integer) rs.getInt(1), "maxInt as rs.getInt"); + assertEquals(minInt, (Integer) rs.getInt(2), "minInt as rs.getInt"); rs.getInt(3); - assertTrue("rs.wasNull after rs.getInt", rs.wasNull()); - assertEquals("maxInt as rs.getLong", Long.valueOf(maxInt), (Long) rs.getLong(1)); - assertEquals("minInt as rs.getLong", Long.valueOf(minInt), (Long) rs.getLong(2)); + assertTrue(rs.wasNull(), "rs.wasNull after rs.getInt"); + assertEquals(Long.valueOf(maxInt), (Long) rs.getLong(1), "maxInt as rs.getLong"); + assertEquals(Long.valueOf(minInt), (Long) rs.getLong(2), "minInt as rs.getLong"); rs.getLong(3); - assertTrue("rs.wasNull after rs.getLong", rs.wasNull()); - assertEquals("maxInt as rs.getBigDecimal", BigDecimal.valueOf(maxInt), rs.getBigDecimal(1)); - assertEquals("minInt as rs.getBigDecimal", BigDecimal.valueOf(minInt), rs.getBigDecimal(2)); - assertNull("rs.getBigDecimal", rs.getBigDecimal(3)); - assertTrue("rs.getBigDecimal after rs.getLong", rs.wasNull()); - assertEquals("maxInt as rs.getBigDecimal(scale=0)", BigDecimal.valueOf(maxInt), - rs.getBigDecimal(1, 0)); - assertEquals("minInt as rs.getBigDecimal(scale=0)", BigDecimal.valueOf(minInt), - rs.getBigDecimal(2, 0)); - assertNull("rs.getBigDecimal(scale=0)", rs.getBigDecimal(3, 0)); - assertTrue("rs.getBigDecimal after rs.getLong", rs.wasNull()); - assertEquals("maxInt as rs.getBigDecimal(scale=1)", - BigDecimal.valueOf(maxInt).setScale(1, BigDecimal.ROUND_HALF_EVEN), rs.getBigDecimal(1, 1)); - assertEquals("minInt as rs.getBigDecimal(scale=1)", - BigDecimal.valueOf(minInt).setScale(1, BigDecimal.ROUND_HALF_EVEN), rs.getBigDecimal(2, 1)); + assertTrue(rs.wasNull(), "rs.wasNull after rs.getLong"); + assertEquals(BigDecimal.valueOf(maxInt), rs.getBigDecimal(1), "maxInt as rs.getBigDecimal"); + assertEquals(BigDecimal.valueOf(minInt), rs.getBigDecimal(2), "minInt as rs.getBigDecimal"); + assertNull(rs.getBigDecimal(3), "rs.getBigDecimal"); + assertTrue(rs.wasNull(), "rs.getBigDecimal after rs.getLong"); + assertEquals(BigDecimal.valueOf(maxInt), rs.getBigDecimal(1, 0), "maxInt as rs.getBigDecimal(scale=0)"); + assertEquals(BigDecimal.valueOf(minInt), rs.getBigDecimal(2, 0), "minInt as rs.getBigDecimal(scale=0)"); + assertNull(rs.getBigDecimal(3, 0), "rs.getBigDecimal(scale=0)"); + assertTrue(rs.wasNull(), "rs.getBigDecimal after rs.getLong"); + assertEquals(BigDecimal.valueOf(maxInt).setScale(1, RoundingMode.HALF_EVEN), rs.getBigDecimal(1, 1), "maxInt as rs.getBigDecimal(scale=1)"); + assertEquals(BigDecimal.valueOf(minInt).setScale(1, RoundingMode.HALF_EVEN), rs.getBigDecimal(2, 1), "minInt as rs.getBigDecimal(scale=1)"); rs.getFloat(3); assertTrue(rs.wasNull()); rs.close(); @@ -1023,10 +1262,10 @@ public void testSetSmallIntFloat() throws SQLException { pstmt.executeUpdate(); pstmt.close(); - Integer maxInt = new Integer(32767); - Integer minInt = new Integer(-32768); - Float maxIntFloat = new Float(32767); - Float minIntFloat = new Float(-32768); + Integer maxInt = 32767; + Integer minInt = -32768; + Float maxIntFloat = 32767F; + Float minIntFloat = (float) -32768; pstmt = con.prepareStatement("insert into small_int values (?,?,?)"); pstmt.setObject(1, maxIntFloat, Types.SMALLINT); @@ -1039,10 +1278,8 @@ public void testSetSmallIntFloat() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue("expected " + maxInt + " ,received " + rs.getObject(1), - rs.getObject(1).equals(maxInt)); - assertTrue("expected " + minInt + " ,received " + rs.getObject(2), - rs.getObject(2).equals(minInt)); + assertEquals(maxInt, rs.getObject(1)); + assertEquals(minInt, rs.getObject(2)); rs.getFloat(3); assertTrue(rs.wasNull()); rs.close(); @@ -1056,10 +1293,10 @@ public void testSetIntFloat() throws SQLException { pstmt.executeUpdate(); pstmt.close(); - Integer maxInt = new Integer(1000); - Integer minInt = new Integer(-1000); - Float maxIntFloat = new Float(1000); - Float minIntFloat = new Float(-1000); + Integer maxInt = 1000; + Integer minInt = -1000; + Float maxIntFloat = 1000F; + Float minIntFloat = (float) -1000; pstmt = con.prepareStatement("insert into int_tab values (?,?,?)"); pstmt.setObject(1, maxIntFloat, Types.INTEGER); @@ -1072,10 +1309,8 @@ public void testSetIntFloat() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue("expected " + maxInt + " ,received " + rs.getObject(1), - ((Integer) rs.getObject(1)).equals(maxInt)); - assertTrue("expected " + minInt + " ,received " + rs.getObject(2), - ((Integer) rs.getObject(2)).equals(minInt)); + assertEquals(maxInt, ((Integer) rs.getObject(1))); + assertEquals(minInt, ((Integer) rs.getObject(2))); rs.getFloat(3); assertTrue(rs.wasNull()); rs.close(); @@ -1090,8 +1325,8 @@ public void testSetBooleanDouble() throws SQLException { pstmt.executeUpdate(); pstmt.close(); - Double dBooleanTrue = new Double(1); - Double dBooleanFalse = new Double(0); + Double dBooleanTrue = 1.0; + Double dBooleanFalse = (double) 0; pstmt = con.prepareStatement("insert into double_tab values (?,?,?)"); pstmt.setObject(1, Boolean.TRUE, Types.DOUBLE); @@ -1104,10 +1339,8 @@ public void testSetBooleanDouble() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue("expected " + dBooleanTrue + " ,received " + rs.getObject(1), - rs.getObject(1).equals(dBooleanTrue)); - assertTrue("expected " + dBooleanFalse + " ,received " + rs.getObject(2), - rs.getObject(2).equals(dBooleanFalse)); + assertEquals(dBooleanTrue, rs.getObject(1)); + assertEquals(dBooleanFalse, rs.getObject(2)); rs.getFloat(3); assertTrue(rs.wasNull()); rs.close(); @@ -1136,10 +1369,8 @@ public void testSetBooleanNumeric() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue("expected " + dBooleanTrue + " ,received " + rs.getObject(1), - ((BigDecimal) rs.getObject(1)).compareTo(dBooleanTrue) == 0); - assertTrue("expected " + dBooleanFalse + " ,received " + rs.getObject(2), - ((BigDecimal) rs.getObject(2)).compareTo(dBooleanFalse) == 0); + assertEquals(0, ((BigDecimal) rs.getObject(1)).compareTo(dBooleanTrue)); + assertEquals(0, ((BigDecimal) rs.getObject(2)).compareTo(dBooleanFalse)); rs.getFloat(3); assertTrue(rs.wasNull()); rs.close(); @@ -1168,10 +1399,8 @@ public void testSetBooleanDecimal() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue("expected " + dBooleanTrue + " ,received " + rs.getObject(1), - ((BigDecimal) rs.getObject(1)).compareTo(dBooleanTrue) == 0); - assertTrue("expected " + dBooleanFalse + " ,received " + rs.getObject(2), - ((BigDecimal) rs.getObject(2)).compareTo(dBooleanFalse) == 0); + assertEquals(0, ((BigDecimal) rs.getObject(1)).compareTo(dBooleanTrue)); + assertEquals(0, ((BigDecimal) rs.getObject(2)).compareTo(dBooleanFalse)); rs.getFloat(3); assertTrue(rs.wasNull()); rs.close(); @@ -1202,15 +1431,11 @@ public void testSetObjectBigDecimalUnscaled() throws SQLException { pstmt = con.prepareStatement("select n1,n2,n3,n4 from decimal_scale"); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue("expected numeric set via BigDecimal " + v + " stored as " + rs.getBigDecimal(1), - v.compareTo(rs.getBigDecimal(1)) == 0); - assertTrue("expected numeric set via String" + vs + " stored as " + rs.getBigDecimal(2), - v.compareTo(rs.getBigDecimal(2)) == 0); + assertEquals(0, v.compareTo(rs.getBigDecimal(1)), "expected numeric set via BigDecimal " + v + " stored as " + rs.getBigDecimal(1)); + assertEquals(0, v.compareTo(rs.getBigDecimal(2)), "expected numeric set via String" + vs + " stored as " + rs.getBigDecimal(2)); // float is really bad... - assertTrue("expected numeric set via Float" + vf + " stored as " + rs.getBigDecimal(3), - v.compareTo(rs.getBigDecimal(3).setScale(6, RoundingMode.HALF_UP)) == 0); - assertTrue("expected numeric set via Double" + vd + " stored as " + rs.getBigDecimal(4), - v.compareTo(rs.getBigDecimal(4)) == 0); + assertEquals(0, v.compareTo(rs.getBigDecimal(3).setScale(6, RoundingMode.HALF_UP)), "expected numeric set via Float" + vf + " stored as " + rs.getBigDecimal(3)); + assertEquals(0, v.compareTo(rs.getBigDecimal(4)), "expected numeric set via Double" + vd + " stored as " + rs.getBigDecimal(4)); rs.close(); pstmt.close(); @@ -1240,18 +1465,10 @@ public void testSetObjectBigDecimalWithScale() throws SQLException { ResultSet rs = psselect.executeQuery(); assertTrue(rs.next()); BigDecimal vscaled = v.setScale(s, RoundingMode.HALF_UP); - assertTrue( - "expected numeric set via BigDecimal " + v + " with scale " + s + " stored as " + vscaled, - vscaled.compareTo(rs.getBigDecimal(1)) == 0); - assertTrue( - "expected numeric set via String" + vs + " with scale " + s + " stored as " + vscaled, - vscaled.compareTo(rs.getBigDecimal(2)) == 0); - assertTrue( - "expected numeric set via Float" + vf + " with scale " + s + " stored as " + vscaled, - vscaled.compareTo(rs.getBigDecimal(3)) == 0); - assertTrue( - "expected numeric set via Double" + vd + " with scale " + s + " stored as " + vscaled, - vscaled.compareTo(rs.getBigDecimal(4)) == 0); + assertEquals(0, vscaled.compareTo(rs.getBigDecimal(1)), "expected numeric set via BigDecimal " + v + " with scale " + s + " stored as " + vscaled); + assertEquals(0, vscaled.compareTo(rs.getBigDecimal(2)), "expected numeric set via String" + vs + " with scale " + s + " stored as " + vscaled); + assertEquals(0, vscaled.compareTo(rs.getBigDecimal(3)), "expected numeric set via Float" + vf + " with scale " + s + " stored as " + vscaled); + assertEquals(0, vscaled.compareTo(rs.getBigDecimal(4)), "expected numeric set via Double" + vd + " with scale " + s + " stored as " + vscaled); rs.close(); pstruncate.executeUpdate(); } @@ -1273,9 +1490,7 @@ public void testSetObjectWithBigDecimal() throws SQLException { ResultSet rs = psselect.executeQuery(); assertTrue(rs.next()); - assertTrue( - "expected 733, but received " + rs.getBigDecimal(1), - new BigDecimal("733").compareTo(rs.getBigDecimal(1)) == 0); + assertEquals(0, new BigDecimal("733").compareTo(rs.getBigDecimal(1)), "expected 733, but received " + rs.getBigDecimal(1)); psinsert.close(); psselect.close(); @@ -1293,9 +1508,7 @@ public void testSetObjectNumberFallbackWithBigInteger() throws SQLException { ResultSet rs = psselect.executeQuery(); assertTrue(rs.next()); - assertTrue( - "expected 733, but received " + rs.getBigDecimal(1), - new BigDecimal("733").compareTo(rs.getBigDecimal(1)) == 0); + assertEquals(0, new BigDecimal("733").compareTo(rs.getBigDecimal(1)), "expected 733, but received " + rs.getBigDecimal(1)); psinsert.close(); psselect.close(); @@ -1313,9 +1526,7 @@ public void testSetObjectNumberFallbackWithAtomicLong() throws SQLException { ResultSet rs = psselect.executeQuery(); assertTrue(rs.next()); - assertTrue( - "expected 733, but received " + rs.getBigDecimal(1), - new BigDecimal("733").compareTo(rs.getBigDecimal(1)) == 0); + assertEquals(0, new BigDecimal("733").compareTo(rs.getBigDecimal(1)), "expected 733, but received " + rs.getBigDecimal(1)); psinsert.close(); psselect.close(); @@ -1328,8 +1539,7 @@ public void testUnknownSetObject() throws SQLException { pstmt.setString(1, "1 week"); try { pstmt.executeUpdate(); - assertTrue("When using extended protocol, interval vs character varying type mismatch error is expected", - preferQueryMode == PreferQueryMode.SIMPLE); + assertSame(PreferQueryMode.SIMPLE, preferQueryMode, "When using extended protocol, interval vs character varying type mismatch error is expected"); } catch (SQLException sqle) { // ERROR: column "i" is of type interval but expression is of type character varying } @@ -1345,7 +1555,7 @@ public void testUnknownSetObject() throws SQLException { @Test public void testSetObjectCharacter() throws SQLException { PreparedStatement ps = con.prepareStatement("INSERT INTO texttable(te) VALUES (?)"); - ps.setObject(1, new Character('z')); + ps.setObject(1, 'z'); ps.executeUpdate(); ps.close(); } @@ -1358,7 +1568,7 @@ public void testSetObjectCharacter() throws SQLException { @Test public void testStatementDescribe() throws SQLException { PreparedStatement pstmt = con.prepareStatement("SELECT ?::int"); - pstmt.setObject(1, new Integer(2), Types.OTHER); + pstmt.setObject(1, 2, Types.OTHER); for (int i = 0; i < 10; i++) { ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); @@ -1371,8 +1581,7 @@ public void testStatementDescribe() throws SQLException { @Test public void testBatchWithPrepareThreshold5() throws SQLException { assumeBinaryModeRegular(); - Assume.assumeTrue("simple protocol only does not support prepared statement requests", - preferQueryMode != PreferQueryMode.SIMPLE); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE, "simple protocol only does not support prepared statement requests"); PreparedStatement pstmt = con.prepareStatement("CREATE temp TABLE batch_tab_threshold5 (id bigint, val bigint)"); pstmt.executeUpdate(); @@ -1390,17 +1599,15 @@ public void testBatchWithPrepareThreshold5() throws SQLException { pstmt.executeBatch(); } pstmt.close(); - assertTrue("prepareThreshold=5, so the statement should be server-prepared", - ((PGStatement) pstmt).isUseServerPrepare()); - assertEquals("prepareThreshold=5, so the statement should be server-prepared", 1, - getNumberOfServerPreparedStatements("INSERT INTO batch_tab_threshold5 (id, val) VALUES ($1,$2)")); + assumeFalse(con.unwrap(PgConnection.class).getQueryExecutor().isReWriteBatchedInsertsEnabled(), "Test assertions below support only non-rewritten insert statements"); + assertTrue(((PGStatement) pstmt).isUseServerPrepare(), "prepareThreshold=5, so the statement should be server-prepared"); + assertEquals(1, getNumberOfServerPreparedStatements("INSERT INTO batch_tab_threshold5 (id, val) VALUES ($1,$2)"), "prepareThreshold=5, so the statement should be server-prepared"); } @Test public void testBatchWithPrepareThreshold0() throws SQLException { assumeBinaryModeRegular(); - Assume.assumeTrue("simple protocol only does not support prepared statement requests", - preferQueryMode != PreferQueryMode.SIMPLE); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE, "simple protocol only does not support prepared statement requests"); PreparedStatement pstmt = con.prepareStatement("CREATE temp TABLE batch_tab_threshold0 (id bigint, val bigint)"); pstmt.executeUpdate(); @@ -1419,17 +1626,14 @@ public void testBatchWithPrepareThreshold0() throws SQLException { } pstmt.close(); - assertFalse("prepareThreshold=0, so the statement should not be server-prepared", - ((PGStatement) pstmt).isUseServerPrepare()); - assertEquals("prepareThreshold=0, so the statement should not be server-prepared", 0, - getNumberOfServerPreparedStatements("INSERT INTO batch_tab_threshold0 (id, val) VALUES ($1,$2)")); + assertFalse(((PGStatement) pstmt).isUseServerPrepare(), "prepareThreshold=0, so the statement should not be server-prepared"); + assertEquals(0, getNumberOfServerPreparedStatements("INSERT INTO batch_tab_threshold0 (id, val) VALUES ($1,$2)"), "prepareThreshold=0, so the statement should not be server-prepared"); } @Test public void testSelectPrepareThreshold0AutoCommitFalseFetchSizeNonZero() throws SQLException { assumeBinaryModeRegular(); - Assume.assumeTrue("simple protocol only does not support prepared statement requests", - preferQueryMode != PreferQueryMode.SIMPLE); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE, "simple protocol only does not support prepared statement requests"); con.setAutoCommit(false); PreparedStatement pstmt = null; @@ -1446,41 +1650,43 @@ public void testSelectPrepareThreshold0AutoCommitFalseFetchSizeNonZero() throws TestUtil.closeQuietly(pstmt); } - assertFalse("prepareThreshold=0, so the statement should not be server-prepared", - ((PGStatement) pstmt).isUseServerPrepare()); + assertFalse(((PGStatement) pstmt).isUseServerPrepare(), "prepareThreshold=0, so the statement should not be server-prepared"); - assertEquals("prepareThreshold=0, so the statement should not be server-prepared", 0, - getNumberOfServerPreparedStatements("SELECT 42")); + assertEquals(0, getNumberOfServerPreparedStatements("SELECT 42"), "prepareThreshold=0, so the statement should not be server-prepared"); } @Test public void testInappropriateStatementSharing() throws SQLException { PreparedStatement ps = con.prepareStatement("SELECT ?::timestamp"); + assertFirstParameterTypeName("after prepare ?::timestamp bind type should be timestamp", "timestamp", ps); try { Timestamp ts = new Timestamp(1474997614836L); // Since PreparedStatement isn't cached immediately, we need to some warm up - for (int i = 0; i < 3; ++i) { + for (int i = 0; i < 3; i++) { ResultSet rs; // Flip statement to use Oid.DATE ps.setNull(1, Types.DATE); + assertFirstParameterTypeName("set parameter to DATE", "date", ps); rs = ps.executeQuery(); + assertFirstParameterTypeName("set parameter to DATE (executeQuery should not affect parameterMetadata)", + "date", ps); try { assertTrue(rs.next()); - assertNull("NULL DATE converted to TIMESTAMP should return NULL value on getObject", - rs.getObject(1)); + assertNull(rs.getObject(1), "NULL DATE converted to TIMESTAMP should return NULL value on getObject"); } finally { rs.close(); } // Flop statement to use Oid.UNSPECIFIED ps.setTimestamp(1, ts); + assertFirstParameterTypeName("set parameter to Timestamp", "timestamp", ps); rs = ps.executeQuery(); + assertFirstParameterTypeName("set parameter to Timestamp (executeQuery should not affect parameterMetadata)", + "timestamp", ps); try { assertTrue(rs.next()); - assertEquals( - "Looks like we got a narrowing of the data (TIMESTAMP -> DATE). It might caused by inappropriate caching of the statement.", - ts, rs.getObject(1)); + assertEquals(ts, rs.getObject(1), "Looks like we got a narrowing of the data (TIMESTAMP -> DATE). It might caused by inappropriate caching of the statement."); } finally { rs.close(); } @@ -1490,6 +1696,14 @@ public void testInappropriateStatementSharing() throws SQLException { } } + private void assertFirstParameterTypeName(String msg, String expected, PreparedStatement ps) throws SQLException { + if (preferQueryMode == PreferQueryMode.SIMPLE) { + return; + } + ParameterMetaData pmd = ps.getParameterMetaData(); + assertEquals(expected, pmd.getParameterTypeName(1), () -> "getParameterMetaData().getParameterTypeName(1) " + msg); + } + @Test public void testAlternatingBindType() throws SQLException { assumeBinaryModeForce(); @@ -1522,25 +1736,23 @@ public void close() throws SecurityException { ps.setString(1, "42"); rs = ps.executeQuery(); rs.next(); - Assert.assertEquals("setString(1, \"42\") -> \"42\" expected", "42", rs.getObject(1)); + assertEquals("42", rs.getObject(1), "setString(1, \"42\") -> \"42\" expected"); rs.close(); // The bind type is flipped from VARCHAR to INTEGER, and it causes the driver to prepare statement again ps.setNull(1, Types.INTEGER); rs = ps.executeQuery(); rs.next(); - Assert.assertNull("setNull(1, Types.INTEGER) -> null expected", rs.getObject(1)); - Assert.assertEquals("A re-parse was expected, so the number of parses should be 1", - 1, numOfReParses.get()); + assertNull(rs.getObject(1), "setNull(1, Types.INTEGER) -> null expected"); + assertEquals(1, numOfReParses.get(), "A re-parse was expected, so the number of parses should be 1"); rs.close(); // The bind type is flipped from INTEGER to VARCHAR, and it causes the driver to prepare statement again ps.setString(1, "42"); rs = ps.executeQuery(); rs.next(); - Assert.assertEquals("setString(1, \"42\") -> \"42\" expected", "42", rs.getObject(1)); - Assert.assertEquals("One more re-parse is expected, so the number of parses should be 2", - 2, numOfReParses.get()); + assertEquals("42", rs.getObject(1), "setString(1, \"42\") -> \"42\" expected"); + assertEquals(2, numOfReParses.get(), "One more re-parse is expected, so the number of parses should be 2"); rs.close(); // Types.OTHER null is sent as UNSPECIFIED, and pgjdbc does not re-parse on UNSPECIFIED nulls @@ -1548,17 +1760,15 @@ public void close() throws SecurityException { ps.setNull(1, Types.OTHER); rs = ps.executeQuery(); rs.next(); - Assert.assertNull("setNull(1, Types.OTHER) -> null expected", rs.getObject(1)); - Assert.assertEquals("setNull(, Types.OTHER) should not cause re-parse", - 2, numOfReParses.get()); + assertNull(rs.getObject(1), "setNull(1, Types.OTHER) -> null expected"); + assertEquals(2, numOfReParses.get(), "setNull(, Types.OTHER) should not cause re-parse"); // Types.INTEGER null is sent as int4 null, and it leads to re-parse ps.setNull(1, Types.INTEGER); rs = ps.executeQuery(); rs.next(); - Assert.assertNull("setNull(1, Types.INTEGER) -> null expected", rs.getObject(1)); - Assert.assertEquals("setNull(, Types.INTEGER) causes re-parse", - 3, numOfReParses.get()); + assertNull(rs.getObject(1), "setNull(1, Types.INTEGER) -> null expected"); + assertEquals(3, numOfReParses.get(), "setNull(, Types.INTEGER) causes re-parse"); rs.close(); } finally { TestUtil.closeQuietly(ps); diff --git a/src/test/java/org/postgresql/test/jdbc2/QuotationTest.java b/src/test/java/org/postgresql/test/jdbc2/QuotationTest.java index 879e616..2a3ad0c 100644 --- a/src/test/java/org/postgresql/test/jdbc2/QuotationTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/QuotationTest.java @@ -5,14 +5,14 @@ package org.postgresql.test.jdbc2; -import org.postgresql.test.SlowTests; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.test.TestUtil; +import org.postgresql.test.annotations.tags.Slow; -import org.junit.Assert; -import org.junit.Test; -import org.junit.experimental.categories.Category; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -20,7 +20,8 @@ import java.util.ArrayList; import java.util.Collection; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class QuotationTest extends BaseTest4 { private enum QuoteStyle { SIMPLE("'"), DOLLAR_NOTAG("$$"), DOLLAR_A("$a$"), DOLLAR_DEF("$DEF$"), @@ -47,17 +48,16 @@ public QuotationTest(QuoteStyle quoteStyle, String expected, String expr) { this.expr = expr; } - @Parameterized.Parameters(name = "{index}: quotes(style={0}, src={1}, quoted={2})") public static Iterable data() { - Collection prefix = new ArrayList(); + Collection prefix = new ArrayList<>(); // Too many prefixes make test run long prefix.add(""); prefix.add("/*\n$\n*//* ? *//*{fn *//* now} */"); prefix.add("-- $\n"); prefix.add("--\n/* $ */"); - Collection ids = new ArrayList(); - Collection garbageValues = new ArrayList(); + Collection ids = new ArrayList<>(); + Collection garbageValues = new ArrayList<>(); garbageValues.add("{fn now}"); garbageValues.add("{extract}"); garbageValues.add("{select}"); @@ -114,21 +114,21 @@ public static Iterable data() { } @Test - @Category(SlowTests.class) + @Slow public void quotedString() throws SQLException { PreparedStatement ps = con.prepareStatement("select " + expr); try { ResultSet rs = ps.executeQuery(); rs.next(); String val = rs.getString(1); - Assert.assertEquals(expected, val); + assertEquals(expected, val); } catch (SQLException e) { TestUtil.closeQuietly(ps); } } @Test - @Category(SlowTests.class) + @Slow public void bindInTheMiddle() throws SQLException { PreparedStatement ps = con.prepareStatement("select " + expr + ", ?, " + expr); try { @@ -137,8 +137,8 @@ public void bindInTheMiddle() throws SQLException { rs.next(); String val1 = rs.getString(1); String val3 = rs.getString(3); - Assert.assertEquals(expected, val1); - Assert.assertEquals(expected, val3); + assertEquals(expected, val1); + assertEquals(expected, val3); } catch (SQLException e) { TestUtil.closeQuietly(ps); } diff --git a/src/test/java/org/postgresql/test/jdbc2/RefCursorFetchTest.java b/src/test/java/org/postgresql/test/jdbc2/RefCursorFetchTest.java new file mode 100644 index 0000000..776da65 --- /dev/null +++ b/src/test/java/org/postgresql/test/jdbc2/RefCursorFetchTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.postgresql.PGProperty; +import org.postgresql.core.ServerVersion; +import org.postgresql.test.TestUtil; + +// import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Properties; + +@ParameterizedClass +@MethodSource("data") +public class RefCursorFetchTest extends BaseTest4 { + private final int numRows; + private final /* @Nullable */ Integer defaultFetchSize; + private final /* @Nullable */ Integer statementFetchSize; + private final /* @Nullable */ Integer resultSetFetchSize; + private final AutoCommit autoCommit; + private final boolean commitAfterExecute; + + public RefCursorFetchTest(BinaryMode binaryMode, int numRows, + /* @Nullable */ Integer defaultFetchSize, + /* @Nullable */ Integer statementFetchSize, + /* @Nullable */ Integer resultSetFetchSize, + AutoCommit autoCommit, boolean commitAfterExecute) { + this.numRows = numRows; + this.defaultFetchSize = defaultFetchSize; + this.statementFetchSize = statementFetchSize; + this.resultSetFetchSize = resultSetFetchSize; + this.autoCommit = autoCommit; + this.commitAfterExecute = commitAfterExecute; + setBinaryMode(binaryMode); + } + + public static Iterable data() { + Collection ids = new ArrayList<>(); + for (BinaryMode binaryMode : BinaryMode.values()) { + for (int numRows : new int[]{0, 10, 101}) { + for (Integer defaultFetchSize : new Integer[]{null, 0, 9, 50}) { + for (AutoCommit autoCommit : AutoCommit.values()) { + for (boolean commitAfterExecute : new boolean[]{true, false}) { + for (Integer resultSetFetchSize : new Integer[]{null, 0, 9, 50}) { + for (Integer statementFetchSize : new Integer[]{null, 0, 9, 50}) { + ids.add(new Object[]{binaryMode, numRows, defaultFetchSize, statementFetchSize, resultSetFetchSize, autoCommit, commitAfterExecute}); + } + } + } + } + } + } + } + return ids; + } + + @Override + protected void updateProperties(Properties props) { + super.updateProperties(props); + if (defaultFetchSize != null) { + PGProperty.DEFAULT_ROW_FETCH_SIZE.set(props, defaultFetchSize); + } + } + + @BeforeAll + public static void beforeClass() throws Exception { + TestUtil.assumeHaveMinimumServerVersion(ServerVersion.v9_0); + try (Connection con = TestUtil.openDB()) { + assumeCallableStatementsSupported(con); + TestUtil.createTable(con, "test_blob", "content bytea"); + TestUtil.execute(con, ""); + TestUtil.execute(con, "--create function to read data\n" + + "CREATE OR REPLACE FUNCTION test_blob(p_cur OUT REFCURSOR, p_limit int4) AS $body$\n" + + "BEGIN\n" + + "OPEN p_cur FOR SELECT content FROM test_blob LIMIT p_limit;\n" + + "END;\n" + + "$body$ LANGUAGE plpgsql STABLE"); + + TestUtil.execute(con, "--generate 101 rows with 4096 bytes:\n" + + "insert into test_blob\n" + + "select(select decode(string_agg(lpad(to_hex(width_bucket(random(), 0, 1, 256) - 1), 2, '0'), ''), 'hex')" + + " FROM generate_series(1, 4096))\n" + + "from generate_series (1, 200)"); + } + } + + @AfterAll + public static void afterClass() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "test_blob"); + TestUtil.dropFunction(con, "test_blob", "REFCURSOR, int4"); + } + } + + @Override + public void setUp() throws Exception { + super.setUp(); + con.setAutoCommit(autoCommit == AutoCommit.YES); + } + + @Test + public void fetchAllRows() throws SQLException { + int cnt = 0; + try (CallableStatement call = con.prepareCall("{? = call test_blob(?)}")) { + con.setAutoCommit(false); // ref cursors only work if auto commit is off + if (statementFetchSize != null) { + call.setFetchSize(statementFetchSize); + } + call.registerOutParameter(1, Types.REF_CURSOR); + call.setInt(2, numRows); + call.execute(); + if (commitAfterExecute) { + if (autoCommit == AutoCommit.NO) { + con.commit(); + } else { + con.setAutoCommit(false); + con.setAutoCommit(true); + } + } + try (ResultSet rs = (ResultSet) call.getObject(1)) { + if (resultSetFetchSize != null) { + rs.setFetchSize(resultSetFetchSize); + } + while (rs.next()) { + cnt++; + } + assertEquals(numRows, cnt, "number of rows from test_blob(...) call"); + } catch (SQLException e) { + if (commitAfterExecute && "34000".equals(e.getSQLState())) { + // Transaction commit closes refcursor, so the fetch call is expected to fail + // File: postgres.c, Routine: exec_execute_message, Line: 2070 + // Server SQLState: 34000 + // TODO: add statementFetchSize, resultSetFetchSize when implemented + Integer fetchSize = defaultFetchSize; + int expectedRows = + fetchSize != null && fetchSize != 0 ? Math.min(fetchSize, numRows) : numRows; + assertEquals( + expectedRows, + cnt, + () -> "The transaction was committed before processing the results," + + " so expecting ResultSet to buffer fetchSize=" + fetchSize + " rows out of " + + numRows); + return; + } + throw e; + } + } + } +} diff --git a/src/test/java/org/postgresql/test/jdbc2/RefCursorTest.java b/src/test/java/org/postgresql/test/jdbc2/RefCursorTest.java index e920b95..3729fb4 100644 --- a/src/test/java/org/postgresql/test/jdbc2/RefCursorTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/RefCursorTest.java @@ -5,18 +5,21 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.test.TestUtil; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.CallableStatement; +import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; @@ -32,7 +35,8 @@ * * @author Nic Ferrier (nferrier@tapsellferrier.co.uk) */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class RefCursorTest extends BaseTest4 { private final int cursorType; @@ -41,7 +45,6 @@ public RefCursorTest(String typeName, int cursorType) { this.cursorType = cursorType; } - @Parameterized.Parameters(name = "typeName = {0}, cursorType = {1}") public static Iterable data() { return Arrays.asList(new Object[][]{ {"OTHER", Types.OTHER}, @@ -49,45 +52,48 @@ public static Iterable data() { }); } + @BeforeAll + public static void beforeClass() throws Exception { + try (Connection con = TestUtil.openDB()) { + assumeCallableStatementsSupported(con); + TestUtil.createTable(con, "testrs", "id integer primary key"); + Statement stmt = con.createStatement(); + stmt.execute("CREATE OR REPLACE FUNCTION testspg__getRefcursor () RETURNS refcursor AS '" + + "declare v_resset refcursor; begin open v_resset for select id from testrs order by id; " + + "return v_resset; end;' LANGUAGE plpgsql;"); + stmt.execute("CREATE OR REPLACE FUNCTION testspg__getEmptyRefcursor () RETURNS refcursor AS '" + + "declare v_resset refcursor; begin open v_resset for select id from testrs where id < 1 order by id; " + + "return v_resset; end;' LANGUAGE plpgsql;"); + stmt.close(); + } + } + + @AfterAll + public static void afterClass() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropFunction(con, "testspg__getRefcursor", ""); + TestUtil.dropFunction(con, "testspg__getEmptyRefcursor", ""); + TestUtil.dropTable(con, "testrs"); + } + } + @Override public void setUp() throws Exception { // this is the same as the ResultSet setup. super.setUp(); - Statement stmt = con.createStatement(); - - TestUtil.createTable(con, "testrs", "id integer primary key"); - - stmt.executeUpdate("INSERT INTO testrs VALUES (1)"); - stmt.executeUpdate("INSERT INTO testrs VALUES (2)"); - stmt.executeUpdate("INSERT INTO testrs VALUES (3)"); - stmt.executeUpdate("INSERT INTO testrs VALUES (4)"); - stmt.executeUpdate("INSERT INTO testrs VALUES (6)"); - stmt.executeUpdate("INSERT INTO testrs VALUES (9)"); - - // Create the functions. - stmt.execute("CREATE OR REPLACE FUNCTION testspg__getRefcursor () RETURNS refcursor AS '" - + "declare v_resset refcursor; begin open v_resset for select id from testrs order by id; " - + "return v_resset; end;' LANGUAGE plpgsql;"); - stmt.execute("CREATE OR REPLACE FUNCTION testspg__getEmptyRefcursor () RETURNS refcursor AS '" - + "declare v_resset refcursor; begin open v_resset for select id from testrs where id < 1 order by id; " - + "return v_resset; end;' LANGUAGE plpgsql;"); - stmt.close(); + TestUtil.execute(con, "TRUNCATE testrs"); + TestUtil.execute(con, "INSERT INTO testrs VALUES (1),(2),(3),(4),(6),(9)"); con.setAutoCommit(false); } @Override public void tearDown() throws SQLException { con.setAutoCommit(true); - Statement stmt = con.createStatement(); - stmt.execute("drop FUNCTION testspg__getRefcursor ();"); - stmt.execute("drop FUNCTION testspg__getEmptyRefcursor ();"); - TestUtil.dropTable(con, "testrs"); super.tearDown(); } @Test public void testResult() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall("{ ? = call testspg__getRefcursor () }"); call.registerOutParameter(1, cursorType); call.execute(); @@ -119,13 +125,12 @@ public void testResult() throws SQLException { @Test public void testEmptyResult() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall("{ ? = call testspg__getEmptyRefcursor () }"); call.registerOutParameter(1, cursorType); call.execute(); ResultSet rs = (ResultSet) call.getObject(1); - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); call.close(); @@ -133,8 +138,6 @@ public void testEmptyResult() throws SQLException { @Test public void testMetaData() throws SQLException { - assumeCallableStatementsSupported(); - CallableStatement call = con.prepareCall("{ ? = call testspg__getRefcursor () }"); call.registerOutParameter(1, cursorType); call.execute(); @@ -152,15 +155,14 @@ public void testMetaData() throws SQLException { @Test public void testResultType() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall("{ ? = call testspg__getRefcursor () }", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); call.registerOutParameter(1, cursorType); call.execute(); ResultSet rs = (ResultSet) call.getObject(1); - assertEquals(rs.getType(), ResultSet.TYPE_SCROLL_INSENSITIVE); - assertEquals(rs.getConcurrency(), ResultSet.CONCUR_READ_ONLY); + assertEquals(ResultSet.TYPE_SCROLL_INSENSITIVE, rs.getType()); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); assertTrue(rs.last()); assertEquals(6, rs.getRow()); diff --git a/src/test/java/org/postgresql/test/jdbc2/ReplaceProcessingTest.java b/src/test/java/org/postgresql/test/jdbc2/ReplaceProcessingTest.java index dfa6c56..bba0915 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ReplaceProcessingTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ReplaceProcessingTest.java @@ -5,23 +5,25 @@ package org.postgresql.test.jdbc2; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.SQLException; import java.util.Arrays; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class ReplaceProcessingTest extends BaseTest4 { - @Parameterized.Parameter(0) + @Parameter(0) public String input; - @Parameterized.Parameter(1) + @Parameter(1) public String expected; - @Parameterized.Parameters(name = "input={0}, expected={1}") public static Iterable data() { return Arrays.asList(new Object[][]{ {"{fn timestampadd(SQL_TSI_YEAR, ?, {fn now()})}", "(CAST( $1||' year' as interval)+ now())"}, @@ -34,13 +36,13 @@ public static Iterable data() { {"{fn ifnull(?,?)}", "coalesce($1,$2)"}, {"{fn database()}", "current_database()"}, // Not yet supported - // {"{fn timestampadd(SQL_TSI_QUATER, ?, {fn now()})}", "(CAST( $1||' quater' as interval)+ now())"}, + // {"{fn timestampadd(SQL_TSI_QUARTER, ?, {fn now()})}", "(CAST( $1||' quarter' as interval)+ now())"}, // {"{fn timestampadd(SQL_TSI_FRAC_SECOND, ?, {fn now()})}", "(CAST( $1||' second' as interval)+ now())"}, }); } @Test public void run() throws SQLException { - Assert.assertEquals(input, expected, con.nativeSQL(input)); + assertEquals(expected, con.nativeSQL(input), input); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java b/src/test/java/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java index 0dde340..a6b8ad7 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java @@ -5,9 +5,11 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.PGProperty; import org.postgresql.PGResultSetMetaData; @@ -15,11 +17,11 @@ import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -33,7 +35,8 @@ import java.util.Collection; import java.util.Properties; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class ResultSetMetaDataTest extends BaseTest4 { Connection conn; private final Integer databaseMetadataCacheFields; @@ -44,9 +47,8 @@ public ResultSetMetaDataTest(Integer databaseMetadataCacheFields, Integer databa this.databaseMetadataCacheFieldsMib = databaseMetadataCacheFieldsMib; } - @Parameterized.Parameters(name = "databaseMetadataCacheFields = {0}, databaseMetadataCacheFieldsMib = {1}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (Integer fields : new Integer[]{null, 0}) { for (Integer fieldsMib : new Integer[]{null, 0}) { ids.add(new Object[]{fields, fieldsMib}); @@ -66,45 +68,55 @@ protected void updateProperties(Properties props) { } } - @Override - public void setUp() throws Exception { - super.setUp(); - conn = con; - TestUtil.createTable(conn, "rsmd1", "a int primary key, b text, c decimal(10,2)"); - TestUtil.createTable(conn, "rsmd_cache", "a int primary key"); - TestUtil.createTable(conn, "timetest", - "tm time(3), tmtz timetz, ts timestamp without time zone, tstz timestamp(6) with time zone"); + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "rsmd1", "a int primary key, b text, c decimal(10,2)"); + TestUtil.createTable(con, "rsmd_cache", "a int primary key"); + TestUtil.createTable(con, "timetest", + "tm time(3), tmtz timetz, ts timestamp without time zone, tstz timestamp(6) with time zone"); + + TestUtil.dropSequence(con, "serialtest_a_seq"); + TestUtil.dropSequence(con, "serialtest_b_seq"); - TestUtil.dropSequence(conn, "serialtest_a_seq"); - TestUtil.dropSequence(conn, "serialtest_b_seq"); + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v10)) { + TestUtil.createTable(con, "identitytest", "id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY"); + } - if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v10)) { - TestUtil.createTable(conn, "identitytest", "id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY"); + TestUtil.createTable(con, "serialtest", "a serial, b bigserial, c int"); + TestUtil.createTable(con, "alltypes", + "bool boolean, i2 int2, i4 int4, i8 int8, num numeric(10,2), re real, fl float, ch char(3), vc varchar(3), tx text, d date, t time without time zone, tz time with time zone, ts timestamp without time zone, tsz timestamp with time zone, bt bytea"); + TestUtil.createTable(con, "sizetest", + "fixedchar char(5), fixedvarchar varchar(5), unfixedvarchar varchar, txt text, bytearr bytea, num64 numeric(6,4), num60 numeric(6,0), num numeric, ip inet"); + TestUtil.createTable(con, "compositetest", "col rsmd1"); } + } - TestUtil.createTable(conn, "serialtest", "a serial, b bigserial, c int"); - TestUtil.createTable(conn, "alltypes", - "bool boolean, i2 int2, i4 int4, i8 int8, num numeric(10,2), re real, fl float, ch char(3), vc varchar(3), tx text, d date, t time without time zone, tz time with time zone, ts timestamp without time zone, tsz timestamp with time zone, bt bytea"); - TestUtil.createTable(conn, "sizetest", - "fixedchar char(5), fixedvarchar varchar(5), unfixedvarchar varchar, txt text, bytearr bytea, num64 numeric(6,4), num60 numeric(6,0), num numeric, ip inet"); - TestUtil.createTable(conn, "compositetest", "col rsmd1"); + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "compositetest"); + TestUtil.dropTable(con, "rsmd1"); + TestUtil.dropTable(con, "rsmd_cache"); + TestUtil.dropTable(con, "timetest"); + TestUtil.dropTable(con, "serialtest"); + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v10)) { + TestUtil.dropTable(con, "identitytest"); + } + TestUtil.dropTable(con, "alltypes"); + TestUtil.dropTable(con, "sizetest"); + TestUtil.dropSequence(con, "serialtest_a_seq"); + TestUtil.dropSequence(con, "serialtest_b_seq"); + } } @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(conn, "compositetest"); - TestUtil.dropTable(conn, "rsmd1"); + public void setUp() throws Exception { + super.setUp(); + conn = con; + // Recreate rsmd_cache before each test since testCache renames its column TestUtil.dropTable(conn, "rsmd_cache"); - TestUtil.dropTable(conn, "timetest"); - TestUtil.dropTable(conn, "serialtest"); - if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v10)) { - TestUtil.dropTable(conn, "identitytest"); - } - TestUtil.dropTable(conn, "alltypes"); - TestUtil.dropTable(conn, "sizetest"); - TestUtil.dropSequence(conn, "serialtest_a_seq"); - TestUtil.dropSequence(conn, "serialtest_b_seq"); - super.tearDown(); + TestUtil.createTable(conn, "rsmd_cache", "a int primary key"); } @Test @@ -126,7 +138,7 @@ public void testPreparedResultSet() throws SQLException { pstmt.close(); } - private void runStandardTests(ResultSetMetaData rsmd) throws SQLException { + private static void runStandardTests(ResultSetMetaData rsmd) throws SQLException { PGResultSetMetaData pgrsmd = (PGResultSetMetaData) rsmd; assertEquals(5, rsmd.getColumnCount()); @@ -240,7 +252,7 @@ public void testIsAutoIncrement() throws SQLException { ResultSet rs = stmt.executeQuery("SELECT c,b,a FROM serialtest"); ResultSetMetaData rsmd = rs.getMetaData(); - assertTrue(!rsmd.isAutoIncrement(1)); + assertFalse(rsmd.isAutoIncrement(1)); assertTrue(rsmd.isAutoIncrement(2)); assertTrue(rsmd.isAutoIncrement(3)); assertEquals("bigserial", rsmd.getColumnTypeName(2)); @@ -301,15 +313,15 @@ public void testIdentityColumn() throws Exception { PreparedStatement pstmt = conn.prepareStatement("SELECT id FROM identitytest"); ResultSet rs = pstmt.executeQuery(); ResultSetMetaData rsmd = pstmt.getMetaData(); - Assert.assertTrue(rsmd.isAutoIncrement(1)); + assertTrue(rsmd.isAutoIncrement(1)); } // Verifies that the field metadatacache will cache when enabled and also functions properly // when disabled. @Test public void testCache() throws Exception { - boolean isCacheDisabled = new Integer(0).equals(databaseMetadataCacheFields) - || new Integer(0).equals(databaseMetadataCacheFieldsMib); + boolean isCacheDisabled = Integer.valueOf(0).equals(databaseMetadataCacheFields) + || Integer.valueOf(0).equals(databaseMetadataCacheFieldsMib); { PreparedStatement pstmt = conn.prepareStatement("SELECT a FROM rsmd_cache"); @@ -336,13 +348,12 @@ public void testCache() throws Exception { } private void assumePreparedStatementMetadataSupported() { - Assume.assumeTrue("prepared statement metadata is not supported for simple protocol", - preferQueryMode.compareTo(PreferQueryMode.EXTENDED_FOR_PREPARED) >= 0); + assumeTrue(preferQueryMode.compareTo(PreferQueryMode.EXTENDED_FOR_PREPARED) >= 0, "prepared statement metadata is not supported for simple protocol"); } @Test public void testSmallSerialColumns() throws SQLException { - org.junit.Assume.assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_2)); + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_2)); TestUtil.createTable(con, "smallserial_test", "a smallserial"); Statement stmt = conn.createStatement(); diff --git a/src/test/java/org/postgresql/test/jdbc2/ResultSetRefreshTest.java b/src/test/java/org/postgresql/test/jdbc2/ResultSetRefreshTest.java new file mode 100644 index 0000000..23cb820 --- /dev/null +++ b/src/test/java/org/postgresql/test/jdbc2/ResultSetRefreshTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.test.TestUtil; + +import org.junit.jupiter.api.Test; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class ResultSetRefreshTest extends BaseTest4 { + @Test + public void testWithDataColumnThatRequiresEscaping() throws Exception { + TestUtil.dropTable(con, "refresh_row_bad_ident"); + TestUtil.execute(con, "CREATE TABLE refresh_row_bad_ident (id int PRIMARY KEY, \"1 FROM refresh_row_bad_ident; SELECT 2; SELECT *\" int)"); + TestUtil.execute(con, "INSERT INTO refresh_row_bad_ident (id) VALUES (1), (2), (3)"); + + Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); + ResultSet rs = stmt.executeQuery("SELECT * FROM refresh_row_bad_ident"); + assertTrue(rs.next()); + try { + rs.refreshRow(); + } catch (SQLException ex) { + throw new RuntimeException("ResultSet.refreshRow() did not handle escaping data column identifiers", ex); + } + rs.close(); + stmt.close(); + } + + @Test + public void testWithKeyColumnThatRequiresEscaping() throws Exception { + TestUtil.dropTable(con, "refresh_row_bad_ident"); + TestUtil.execute(con, "CREATE TABLE refresh_row_bad_ident (\"my key\" int PRIMARY KEY)"); + TestUtil.execute(con, "INSERT INTO refresh_row_bad_ident VALUES (1), (2), (3)"); + + Statement stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); + ResultSet rs = stmt.executeQuery("SELECT * FROM refresh_row_bad_ident"); + assertTrue(rs.next()); + try { + rs.refreshRow(); + } catch (SQLException ex) { + throw new RuntimeException("ResultSet.refreshRow() did not handle escaping key column identifiers", ex); + } + rs.close(); + stmt.close(); + } +} diff --git a/src/test/java/org/postgresql/test/jdbc2/ResultSetTest.java b/src/test/java/org/postgresql/test/jdbc2/ResultSetTest.java index aa5ed46..f9dd6dc 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ResultSetTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ResultSetTest.java @@ -5,58 +5,117 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeTrue; - +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.postgresql.PGConnection; import org.postgresql.core.ServerVersion; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.TestUtil; import org.postgresql.util.PGobject; +import org.postgresql.util.PSQLException; +import org.postgresql.util.PSQLState; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.lang.reflect.Field; import java.math.BigDecimal; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.Locale; import java.util.Map; - -/* +import java.util.Properties; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** * ResultSet tests. */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") +// TODO: add @Isolated("Uses Locale.setDefault") since the test modifies the Locale. +// see https://github.com/junit-team/junit-framework/discussions/5154 public class ResultSetTest extends BaseTest4 { public ResultSetTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "testrs", "id integer"); + TestUtil.createTable(con, "teststring", "a text"); + TestUtil.createTable(con, "testint", "a int"); + TestUtil.createTable(con, "testbool", "a boolean, b int"); + TestUtil.createTable(con, "testboolstring", "a varchar(30), b boolean"); + TestUtil.createTable(con, "testboolfloat", "i int, a float4, b boolean"); + TestUtil.createTable(con, "testboolint", "a bigint, b boolean"); + TestUtil.createTable(con, "testnumeric", "t text, a numeric"); + TestUtil.createTable(con, "testpgobject", "id integer NOT NULL, d date, PRIMARY KEY (id)"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "testrs"); + TestUtil.dropTable(con, "teststring"); + TestUtil.dropTable(con, "testint"); + TestUtil.dropTable(con, "testbool"); + TestUtil.dropTable(con, "testboolstring"); + TestUtil.dropTable(con, "testboolfloat"); + TestUtil.dropTable(con, "testboolint"); + TestUtil.dropTable(con, "testnumeric"); + TestUtil.dropTable(con, "testpgobject"); + } + } + @Override public void setUp() throws Exception { super.setUp(); - Statement stmt = con.createStatement(); - TestUtil.createTable(con, "testrs", "id integer"); + TestUtil.execute(con, "TRUNCATE testrs"); + TestUtil.execute(con, "TRUNCATE teststring"); + TestUtil.execute(con, "TRUNCATE testint"); + TestUtil.execute(con, "TRUNCATE testbool"); + TestUtil.execute(con, "TRUNCATE testboolstring"); + TestUtil.execute(con, "TRUNCATE testboolfloat"); + TestUtil.execute(con, "TRUNCATE testboolint"); + TestUtil.execute(con, "TRUNCATE testnumeric"); + TestUtil.execute(con, "TRUNCATE testpgobject"); + + Statement stmt = con.createStatement(); stmt.executeUpdate("INSERT INTO testrs VALUES (1)"); stmt.executeUpdate("INSERT INTO testrs VALUES (2)"); @@ -65,14 +124,14 @@ public void setUp() throws Exception { stmt.executeUpdate("INSERT INTO testrs VALUES (6)"); stmt.executeUpdate("INSERT INTO testrs VALUES (9)"); - TestUtil.createTable(con, "teststring", "a text"); stmt.executeUpdate("INSERT INTO teststring VALUES ('12345')"); - TestUtil.createTable(con, "testint", "a int"); stmt.executeUpdate("INSERT INTO testint VALUES (12345)"); // Boolean Tests - TestUtil.createTable(con, "testboolstring", "a varchar(30), b boolean"); + stmt.executeUpdate("INSERT INTO testbool VALUES(true, 1)"); + stmt.executeUpdate("INSERT INTO testbool VALUES(false, 0)"); + stmt.executeUpdate("INSERT INTO testboolstring VALUES('1 ', true)"); stmt.executeUpdate("INSERT INTO testboolstring VALUES('0', false)"); stmt.executeUpdate("INSERT INTO testboolstring VALUES(' t', true)"); @@ -91,83 +150,78 @@ public void setUp() throws Exception { stmt.executeUpdate("INSERT INTO testboolstring VALUES('1.0', null)"); stmt.executeUpdate("INSERT INTO testboolstring VALUES('0.0', null)"); - TestUtil.createTable(con, "testboolfloat", "a float4, b boolean"); - stmt.executeUpdate("INSERT INTO testboolfloat VALUES('1.0'::real, true)"); - stmt.executeUpdate("INSERT INTO testboolfloat VALUES('0.0'::real, false)"); - stmt.executeUpdate("INSERT INTO testboolfloat VALUES(1.000::real, true)"); - stmt.executeUpdate("INSERT INTO testboolfloat VALUES(0.000::real, false)"); - stmt.executeUpdate("INSERT INTO testboolfloat VALUES('1.001'::real, null)"); - stmt.executeUpdate("INSERT INTO testboolfloat VALUES('-1.001'::real, null)"); - stmt.executeUpdate("INSERT INTO testboolfloat VALUES(123.4::real, null)"); - stmt.executeUpdate("INSERT INTO testboolfloat VALUES(1.234e2::real, null)"); - stmt.executeUpdate("INSERT INTO testboolfloat VALUES(100.00e-2::real, true)"); - - TestUtil.createTable(con, "testboolint", "a bigint, b boolean"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(1, '1.0'::real, true)"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(2, '0.0'::real, false)"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(3, 1.000::real, true)"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(4, 0.000::real, false)"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(5, '1.001'::real, null)"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(6, '-1.001'::real, null)"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(7, 123.4::real, null)"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(8, 1.234e2::real, null)"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(9, 100.00e-2::real, true)"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(10, '9223371487098961921', null)"); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(11, '10223372036850000000', null)"); + String floatVal = Float.toString(StrictMath.nextDown(Long.MAX_VALUE - 1)); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(12, " + floatVal + ", null)"); + floatVal = Float.toString(StrictMath.nextDown(Long.MAX_VALUE + 1)); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(13, " + floatVal + ", null)"); + floatVal = Float.toString(StrictMath.nextUp(Long.MIN_VALUE - 1)); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(14, " + floatVal + ", null)"); + floatVal = Float.toString(StrictMath.nextUp(Long.MIN_VALUE + 1)); + stmt.executeUpdate("INSERT INTO testboolfloat VALUES(15, " + floatVal + ", null)"); + stmt.executeUpdate("INSERT INTO testboolint VALUES(1, true)"); stmt.executeUpdate("INSERT INTO testboolint VALUES(0, false)"); stmt.executeUpdate("INSERT INTO testboolint VALUES(-1, null)"); stmt.executeUpdate("INSERT INTO testboolint VALUES(9223372036854775807, null)"); stmt.executeUpdate("INSERT INTO testboolint VALUES(-9223372036854775808, null)"); + // End Boolean Tests - // TestUtil.createTable(con, "testbit", "a bit"); - - TestUtil.createTable(con, "testnumeric", "a numeric"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('1.0')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('0.0')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('-1.0')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('1.2')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('-2.5')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('0.000000000000000000000000000990')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('10.0000000000099')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('.10000000000000')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('.10')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('1.10000000000000')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('99999.2')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('99999')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('-99999.2')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('-99999')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('1.0', '1.0')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('0.0', '0.0')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('-1.0', '-1.0')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('1.2', '1.2')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('-2.5', '-2.5')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('0.000000000000000000000000000990', '0.000000000000000000000000000990')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('10.0000000000099', '10.0000000000099')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('.10000000000000', '.10000000000000')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('.10', '.10')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('1.10000000000000', '1.10000000000000')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('99999.2', '99999.2')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('99999', '99999')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('-99999.2', '-99999.2')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('-99999', '-99999')"); // Integer.MaxValue - stmt.execute("INSERT INTO testnumeric VALUES('2147483647')"); + stmt.execute("INSERT INTO testnumeric VALUES('2147483647', '2147483647')"); // Integer.MinValue - stmt.execute("INSERT INTO testnumeric VALUES('-2147483648')"); + stmt.execute("INSERT INTO testnumeric VALUES( '-2147483648', '-2147483648')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('2147483648')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('-2147483649')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('2147483648', '2147483648')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('-2147483649', '-2147483649')"); // Long.MaxValue - stmt.executeUpdate("INSERT INTO testnumeric VALUES('9223372036854775807')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('9223372036854775807','9223372036854775807')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('9223372036854775807.9', '9223372036854775807.9')"); // Long.MinValue - stmt.executeUpdate("INSERT INTO testnumeric VALUES('-9223372036854775808')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('-9223372036854775808', '-9223372036854775808')"); + + // Long.MaxValue +1 + stmt.executeUpdate("INSERT INTO testnumeric VALUES('9223372036854775808', '9223372036854775808')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('9223372036854775808')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('-9223372036854775809')"); + // Long.Minvalue -1 + stmt.executeUpdate("INSERT INTO testnumeric VALUES('-9223372036854775809', '-9223372036854775809')"); - stmt.executeUpdate("INSERT INTO testnumeric VALUES('10223372036850000000')"); + stmt.executeUpdate("INSERT INTO testnumeric VALUES('10223372036850000000', '10223372036850000000')"); - TestUtil.createTable(con, "testpgobject", "id integer NOT NULL, d date, PRIMARY KEY (id)"); stmt.execute("INSERT INTO testpgobject VALUES(1, '2010-11-3')"); stmt.close(); } - @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "testrs"); - TestUtil.dropTable(con, "teststring"); - TestUtil.dropTable(con, "testint"); - // TestUtil.dropTable(con, "testbit"); - TestUtil.dropTable(con, "testboolstring"); - TestUtil.dropTable(con, "testboolfloat"); - TestUtil.dropTable(con, "testboolint"); - TestUtil.dropTable(con, "testnumeric"); - TestUtil.dropTable(con, "testpgobject"); - super.tearDown(); - } - @Test public void testBackward() throws SQLException { Statement stmt = @@ -185,7 +239,7 @@ public void testAbsolute() throws SQLException { con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet rs = stmt.executeQuery("SELECT * FROM testrs"); - assertTrue(!rs.absolute(0)); + assertFalse(rs.absolute(0)); assertEquals(0, rs.getRow()); assertTrue(rs.absolute(-1)); @@ -194,12 +248,12 @@ public void testAbsolute() throws SQLException { assertTrue(rs.absolute(1)); assertEquals(1, rs.getRow()); - assertTrue(!rs.absolute(-10)); + assertFalse(rs.absolute(-10)); assertEquals(0, rs.getRow()); assertTrue(rs.next()); assertEquals(1, rs.getRow()); - assertTrue(!rs.absolute(10)); + assertFalse(rs.absolute(10)); assertEquals(0, rs.getRow()); assertTrue(rs.previous()); assertEquals(6, rs.getRow()); @@ -213,7 +267,7 @@ public void testRelative() throws SQLException { con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet rs = stmt.executeQuery("SELECT * FROM testrs"); - assertTrue(!rs.relative(0)); + assertFalse(rs.relative(0)); assertEquals(0, rs.getRow()); assertTrue(rs.isBeforeFirst()); @@ -226,7 +280,7 @@ public void testRelative() throws SQLException { assertTrue(rs.relative(0)); assertEquals(3, rs.getRow()); - assertTrue(!rs.relative(-3)); + assertFalse(rs.relative(-3)); assertEquals(0, rs.getRow()); assertTrue(rs.isBeforeFirst()); @@ -236,14 +290,14 @@ public void testRelative() throws SQLException { assertTrue(rs.relative(-1)); assertEquals(3, rs.getRow()); - assertTrue(!rs.relative(6)); + assertFalse(rs.relative(6)); assertEquals(0, rs.getRow()); assertTrue(rs.isAfterLast()); assertTrue(rs.relative(-4)); assertEquals(3, rs.getRow()); - assertTrue(!rs.relative(-6)); + assertFalse(rs.relative(-6)); assertEquals(0, rs.getRow()); assertTrue(rs.isBeforeFirst()); @@ -257,9 +311,9 @@ public void testEmptyResult() throws SQLException { ResultSet rs = stmt.executeQuery("SELECT * FROM testrs where id=100"); rs.beforeFirst(); rs.afterLast(); - assertTrue(!rs.first()); - assertTrue(!rs.last()); - assertTrue(!rs.next()); + assertFalse(rs.first()); + assertFalse(rs.last()); + assertFalse(rs.next()); } @Test @@ -284,6 +338,14 @@ public void testMaxFieldSize() throws SQLException { assertEquals("12", new String(rs.getBytes(1))); } + @Test + public void testBooleanBool() throws SQLException { + testBoolean("testbool", 0); + testBoolean("testbool", 1); + testBoolean("testbool", 5); + testBoolean("testbool", -1); + } + @Test public void testBooleanString() throws SQLException { testBoolean("testboolstring", 0); @@ -323,7 +385,7 @@ public void testBoolean(String table, int prepareThreshold) throws SQLException rs.getBoolean(1); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); } } } @@ -332,10 +394,10 @@ public void testBoolean(String table, int prepareThreshold) throws SQLException } @Test - public void testgetBooleanJDBCCompliance() throws SQLException { + public void testGetBooleanJDBCCompliance() throws SQLException { // The JDBC specification in Table B-6 "Use of ResultSet getter Methods to Retrieve JDBC Data Types" // the getBoolean have this Supported JDBC Type: TINYINT, SMALLINT, INTEGER, BIGINT, REAL, FLOAT, - // DOUBLE, DECIAML, NUMERIC, BIT, BOOLEAN, CHAR, VARCHAR, LONGVARCHAR + // DOUBLE, DECIMAL, NUMERIC, BIT, BOOLEAN, CHAR, VARCHAR, LONGVARCHAR // There is no TINYINT in PostgreSQL testgetBoolean("int2"); // SMALLINT @@ -353,15 +415,15 @@ public void testgetBoolean(String dataType) throws SQLException { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select 1::" + dataType + ", 0::" + dataType + ", 2::" + dataType); assertTrue(rs.next()); - assertEquals(true, rs.getBoolean(1)); - assertEquals(false, rs.getBoolean(2)); + assertTrue(rs.getBoolean(1)); + assertFalse(rs.getBoolean(2)); try { // The JDBC ResultSet JavaDoc states that only 1 and 0 are valid values, so 2 should return error. rs.getBoolean(3); fail(); } catch (SQLException e) { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); // message can be 2 or 2.0 depending on whether binary or text final String message = e.getMessage(); if (!"Cannot cast to boolean: \"2.0\"".equals(message)) { @@ -405,7 +467,7 @@ public void testBadBoolean(String select, String value) throws SQLException { fail(message); } } else { - assertEquals(org.postgresql.util.PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); assertEquals("Cannot cast to boolean: \"" + value + "\"", e.getMessage()); } } @@ -415,7 +477,7 @@ public void testBadBoolean(String select, String value) throws SQLException { @Test public void testgetByte() throws SQLException { - ResultSet rs = con.createStatement().executeQuery("select * from testnumeric"); + ResultSet rs = con.createStatement().executeQuery("select a from testnumeric"); assertTrue(rs.next()); assertEquals(1, rs.getByte(1)); @@ -451,7 +513,8 @@ public void testgetByte() throws SQLException { try { rs.getByte(1); fail("Exception expected."); - } catch (Exception e) { + } catch (SQLException e) { + assertEquals(e.getSQLState(), "22003"); } } rs.close(); @@ -459,7 +522,7 @@ public void testgetByte() throws SQLException { @Test public void testgetShort() throws SQLException { - ResultSet rs = con.createStatement().executeQuery("select * from testnumeric"); + ResultSet rs = con.createStatement().executeQuery("select a from testnumeric"); assertTrue(rs.next()); assertEquals(1, rs.getShort(1)); @@ -495,7 +558,7 @@ public void testgetShort() throws SQLException { try { rs.getShort(1); fail("Exception expected."); - } catch (Exception e) { + } catch (SQLException e) { } } rs.close(); @@ -503,7 +566,7 @@ public void testgetShort() throws SQLException { @Test public void testgetInt() throws SQLException { - ResultSet rs = con.createStatement().executeQuery("select * from testnumeric"); + ResultSet rs = con.createStatement().executeQuery("select a from testnumeric"); assertTrue(rs.next()); assertEquals(1, rs.getInt(1)); @@ -557,158 +620,330 @@ public void testgetInt() throws SQLException { try { rs.getInt(1); fail("Exception expected." + rs.getString(1)); - } catch (Exception e) { + } catch (SQLException e) { } } rs.close(); + // test for Issue #2748 + rs = con.createStatement().executeQuery("select 2.0 :: double precision"); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + rs.close(); + } @Test public void testgetLong() throws SQLException { - ResultSet rs = con.createStatement().executeQuery("select * from testnumeric"); + ResultSet rs = null; + rs = con.createStatement().executeQuery("select a from testnumeric where t = '1.0'"); assertTrue(rs.next()); assertEquals(1, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '0.0'"); assertTrue(rs.next()); assertEquals(0, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-1.0'"); assertTrue(rs.next()); assertEquals(-1, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '1.2'"); assertTrue(rs.next()); assertEquals(1, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-2.5'"); assertTrue(rs.next()); assertEquals(-2, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '0.000000000000000000000000000990'"); assertTrue(rs.next()); assertEquals(0, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '10.0000000000099'"); assertTrue(rs.next()); assertEquals(10, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '.10000000000000'"); assertTrue(rs.next()); assertEquals(0, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '.10'"); assertTrue(rs.next()); assertEquals(0, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '1.10000000000000'"); assertTrue(rs.next()); assertEquals(1, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '99999.2'"); assertTrue(rs.next()); assertEquals(99999, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '99999'"); assertTrue(rs.next()); assertEquals(99999, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-99999.2'"); assertTrue(rs.next()); assertEquals(-99999, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-99999'"); assertTrue(rs.next()); assertEquals(-99999, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '2147483647'"); assertTrue(rs.next()); assertEquals((Integer.MAX_VALUE), rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-2147483648'"); assertTrue(rs.next()); assertEquals((Integer.MIN_VALUE), rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '2147483648'"); assertTrue(rs.next()); assertEquals(((long) Integer.MAX_VALUE) + 1, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-2147483649'"); assertTrue(rs.next()); assertEquals(((long) Integer.MIN_VALUE) - 1, rs.getLong(1)); + rs.close(); + + rs = con.createStatement().executeQuery("select a from testnumeric where t = '9223372036854775807'"); + assertTrue(rs.next()); + assertEquals(Long.MAX_VALUE, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '9223372036854775807.9'"); assertTrue(rs.next()); assertEquals(Long.MAX_VALUE, rs.getLong(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-9223372036854775808'"); assertTrue(rs.next()); assertEquals(Long.MIN_VALUE, rs.getLong(1)); + rs.close(); - while (rs.next()) { + rs = con.createStatement().executeQuery("select a from testnumeric where t = '9223372036854775808'"); + assertTrue(rs.next()); + try { + rs.getLong(1); + fail("Exception expected. " + rs.getString(1)); + } catch (SQLException e) { + } + rs.close(); + + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-9223372036854775809'"); + assertTrue(rs.next()); + try { + rs.getLong(1); + fail("Exception expected. " + rs.getString(1)); + } catch (SQLException e) { + } + rs.close(); + + rs = con.createStatement().executeQuery("select a from testnumeric where t = '10223372036850000000'"); + assertTrue(rs.next()); + try { + rs.getLong(1); + fail("Exception expected. " + rs.getString(1)); + } catch (SQLException e) { + } + rs.close(); + + rs = con.createStatement().executeQuery("select i, a from testboolfloat order by i"); + + assertTrue(rs.next()); + assertEquals(1, rs.getLong(2)); + + assertTrue(rs.next()); + assertEquals(0, rs.getLong(2)); + + assertTrue(rs.next()); + assertEquals(1, rs.getLong(2)); + + assertTrue(rs.next()); + assertEquals(0, rs.getLong(2)); + + assertTrue(rs.next()); + assertEquals(1, rs.getLong(2)); + + assertTrue(rs.next()); + assertEquals(-1, rs.getLong(2)); + + assertTrue(rs.next()); + assertEquals(123, rs.getLong(2)); + + assertTrue(rs.next()); + assertEquals(123, rs.getLong(2)); + + assertTrue(rs.next()); + assertEquals(1, rs.getLong(2)); + + assertTrue(rs.next()); + // the string value from database trims the significant digits, leading to larger variance than binary + // the liberica jdk gets similar variance, even in forced binary mode + assertEquals(9223371487098961921.0, rs.getLong(2), 1.0e11); + + assertTrue(rs.next()); + do { try { - rs.getLong(1); - fail("Exception expected." + rs.getString(1)); - } catch (Exception e) { + int row = rs.getInt(1); + long l = rs.getLong(2); + if ( row == 12 ) { + assertEquals(9223371487098961920.0, l, 1.0e11); + } else if ( row == 15 ) { + assertEquals(-9223371487098961920.0, l, 1.0e11); + } else { + fail("Exception expected." + rs.getString(2)); + } + } catch (SQLException e) { } - } + } while (rs.next()); + rs.close(); } @Test public void testgetBigDecimal() throws SQLException { - ResultSet rs = con.createStatement().executeQuery("select * from testnumeric"); + ResultSet rs = null; + rs = con.createStatement().executeQuery("select a from testnumeric where t = '1.0'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(1.0), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '0.0'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(0.0), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-1.0'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(-1.0), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '1.2'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(1.2), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-2.5'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(-2.5), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '0.000000000000000000000000000990'"); assertTrue(rs.next()); assertEquals(new BigDecimal("0.000000000000000000000000000990"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '10.0000000000099'"); assertTrue(rs.next()); assertEquals(new BigDecimal("10.0000000000099"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '.10000000000000'"); assertTrue(rs.next()); assertEquals(new BigDecimal("0.10000000000000"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '.10'"); assertTrue(rs.next()); assertEquals(new BigDecimal("0.10"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '1.10000000000000'"); assertTrue(rs.next()); assertEquals(new BigDecimal("1.10000000000000"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '99999.2'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(99999.2), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '99999'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(99999), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-99999.2'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(-99999.2), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-99999'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(-99999), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '2147483647'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(2147483647), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-2147483648'"); assertTrue(rs.next()); assertEquals(BigDecimal.valueOf(-2147483648), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '2147483648'"); assertTrue(rs.next()); assertEquals(new BigDecimal("2147483648"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-2147483649'"); assertTrue(rs.next()); assertEquals(new BigDecimal("-2147483649"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '9223372036854775807'"); assertTrue(rs.next()); assertEquals(new BigDecimal("9223372036854775807"), rs.getBigDecimal(1)); + rs.close(); + + rs = con.createStatement().executeQuery("select a from testnumeric where t = '9223372036854775807.9'"); + assertTrue(rs.next()); + assertEquals(new BigDecimal("9223372036854775807.9"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-9223372036854775808'"); assertTrue(rs.next()); assertEquals(new BigDecimal("-9223372036854775808"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '9223372036854775808'"); assertTrue(rs.next()); assertEquals(new BigDecimal("9223372036854775808"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '-9223372036854775809'"); assertTrue(rs.next()); assertEquals(new BigDecimal("-9223372036854775809"), rs.getBigDecimal(1)); + rs.close(); + rs = con.createStatement().executeQuery("select a from testnumeric where t = '10223372036850000000'"); assertTrue(rs.next()); assertEquals(new BigDecimal("10223372036850000000"), rs.getBigDecimal(1)); + rs.close(); } @Test @@ -727,36 +962,83 @@ public void testParameters() throws SQLException { assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); assertEquals(ResultSet.TYPE_SCROLL_SENSITIVE, rs.getType()); - assertEquals(100, rs.getFetchSize()); + if (!con.unwrap(PGConnection.class).getAdaptiveFetch()) { + assertEquals(100, rs.getFetchSize(), "ResultSet.fetchSize should not change after query execution"); + } assertEquals(ResultSet.FETCH_UNKNOWN, rs.getFetchDirection()); rs.close(); stmt.close(); } + @Test + public void testCreateStatementWithInvalidResultSetParams() throws SQLException { + assertThrows(PSQLException.class, () -> con.createStatement(-1, -1,-1)); + } + + @Test + public void testCreateStatementWithInvalidResultSetConcurrency() throws SQLException { + assertThrows(PSQLException.class, () -> con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, -1) ); + } + + @Test + public void testCreateStatementWithInvalidResultSetHoldability() throws SQLException { + assertThrows(PSQLException.class, () -> con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE, -1) ); + } + + @Test + public void testPrepareStatementWithInvalidResultSetParams() throws SQLException { + assertThrows(PSQLException.class, () -> con.prepareStatement("SELECT id FROM testrs", -1, -1,-1)); + } + + @Test + public void testPrepareStatementWithInvalidResultSetConcurrency() throws SQLException { + assertThrows(PSQLException.class, () -> con.prepareStatement("SELECT id FROM testrs", ResultSet.TYPE_SCROLL_INSENSITIVE, -1) ); + } + + @Test + public void testPrepareStatementWithInvalidResultSetHoldability() throws SQLException { + assertThrows(PSQLException.class, () -> con.prepareStatement("SELECT id FROM testrs", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE, -1) ); + } + + @Test + public void testPrepareCallWithInvalidResultSetParams() throws SQLException { + assertThrows(PSQLException.class, () -> con.prepareCall("SELECT id FROM testrs", -1, -1,-1)); + } + + @Test + public void testPrepareCallWithInvalidResultSetConcurrency() throws SQLException { + assertThrows(PSQLException.class, () -> con.prepareCall("SELECT id FROM testrs", ResultSet.TYPE_SCROLL_INSENSITIVE, -1) ); + } + + @Test + public void testPrepareCallWithInvalidResultSetHoldability() throws SQLException { + assertThrows(PSQLException.class, () -> con.prepareCall("SELECT id FROM testrs", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE, -1) ); + } + @Test public void testZeroRowResultPositioning() throws SQLException { Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = - stmt.executeQuery("SELECT * FROM pg_database WHERE datname='nonexistantdatabase'"); - assertTrue(!rs.previous()); - assertTrue(!rs.previous()); - assertTrue(!rs.next()); - assertTrue(!rs.next()); - assertTrue(!rs.next()); - assertTrue(!rs.next()); - assertTrue(!rs.next()); - assertTrue(!rs.previous()); - assertTrue(!rs.first()); - assertTrue(!rs.last()); + stmt.executeQuery("SELECT * FROM pg_database WHERE datname='nonexistentdatabase'"); + assertFalse(rs.previous()); + assertFalse(rs.previous()); + assertFalse(rs.next()); + assertFalse(rs.next()); + assertFalse(rs.next()); + assertFalse(rs.next()); + assertFalse(rs.next()); + assertFalse(rs.previous()); + assertFalse(rs.first()); + assertFalse(rs.last()); assertEquals(0, rs.getRow()); - assertTrue(!rs.absolute(1)); - assertTrue(!rs.relative(1)); - assertTrue(!rs.isBeforeFirst()); - assertTrue(!rs.isAfterLast()); - assertTrue(!rs.isFirst()); - assertTrue(!rs.isLast()); + assertFalse(rs.absolute(1)); + assertFalse(rs.relative(1)); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertFalse(rs.isFirst()); + assertFalse(rs.isLast()); rs.close(); stmt.close(); } @@ -769,51 +1051,51 @@ public void testRowResultPositioning() throws SQLException { ResultSet rs = stmt.executeQuery("SELECT * FROM pg_database WHERE datname='template1'"); assertTrue(rs.isBeforeFirst()); - assertTrue(!rs.isAfterLast()); - assertTrue(!rs.isFirst()); - assertTrue(!rs.isLast()); + assertFalse(rs.isAfterLast()); + assertFalse(rs.isFirst()); + assertFalse(rs.isLast()); assertTrue(rs.next()); - assertTrue(!rs.isBeforeFirst()); - assertTrue(!rs.isAfterLast()); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); assertTrue(rs.isFirst()); assertTrue(rs.isLast()); - assertTrue(!rs.next()); + assertFalse(rs.next()); - assertTrue(!rs.isBeforeFirst()); + assertFalse(rs.isBeforeFirst()); assertTrue(rs.isAfterLast()); - assertTrue(!rs.isFirst()); - assertTrue(!rs.isLast()); + assertFalse(rs.isFirst()); + assertFalse(rs.isLast()); assertTrue(rs.previous()); - assertTrue(!rs.isBeforeFirst()); - assertTrue(!rs.isAfterLast()); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); assertTrue(rs.isFirst()); assertTrue(rs.isLast()); assertTrue(rs.absolute(1)); - assertTrue(!rs.isBeforeFirst()); - assertTrue(!rs.isAfterLast()); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); assertTrue(rs.isFirst()); assertTrue(rs.isLast()); - assertTrue(!rs.absolute(0)); + assertFalse(rs.absolute(0)); assertTrue(rs.isBeforeFirst()); - assertTrue(!rs.isAfterLast()); - assertTrue(!rs.isFirst()); - assertTrue(!rs.isLast()); + assertFalse(rs.isAfterLast()); + assertFalse(rs.isFirst()); + assertFalse(rs.isLast()); - assertTrue(!rs.absolute(2)); + assertFalse(rs.absolute(2)); - assertTrue(!rs.isBeforeFirst()); + assertFalse(rs.isBeforeFirst()); assertTrue(rs.isAfterLast()); - assertTrue(!rs.isFirst()); - assertTrue(!rs.isLast()); + assertFalse(rs.isFirst()); + assertFalse(rs.isLast()); rs.close(); stmt.close(); @@ -833,8 +1115,7 @@ public void testForwardOnlyExceptions() throws SQLException { } try { rs.afterLast(); - fail( - "afterLast() on a TYPE_FORWARD_ONLY resultset did not throw an exception on a TYPE_FORWARD_ONLY resultset"); + fail("afterLast() on a TYPE_FORWARD_ONLY resultset did not throw an exception on a TYPE_FORWARD_ONLY resultset"); } catch (SQLException e) { } try { @@ -865,15 +1146,13 @@ public void testForwardOnlyExceptions() throws SQLException { try { rs.setFetchDirection(ResultSet.FETCH_REVERSE); - fail( - "setFetchDirection(FETCH_REVERSE) on a TYPE_FORWARD_ONLY resultset did not throw an exception"); + fail("setFetchDirection(FETCH_REVERSE) on a TYPE_FORWARD_ONLY resultset did not throw an exception"); } catch (SQLException e) { } try { rs.setFetchDirection(ResultSet.FETCH_UNKNOWN); - fail( - "setFetchDirection(FETCH_UNKNOWN) on a TYPE_FORWARD_ONLY resultset did not throw an exception"); + fail("setFetchDirection(FETCH_UNKNOWN) on a TYPE_FORWARD_ONLY resultset did not throw an exception"); } catch (SQLException e) { } @@ -1047,10 +1326,10 @@ public void testStatementResultSetColumnMappingCache() throws SQLException { ResultSet rs = stmt.executeQuery("select * from testrs"); Map columnNameIndexMap; columnNameIndexMap = getResultSetColumnNameIndexMap(rs); - assertEquals(null, columnNameIndexMap); + assertNull(columnNameIndexMap); assertTrue(rs.next()); columnNameIndexMap = getResultSetColumnNameIndexMap(rs); - assertEquals(null, columnNameIndexMap); + assertNull(columnNameIndexMap); rs.getInt("ID"); columnNameIndexMap = getResultSetColumnNameIndexMap(rs); assertNotNull(columnNameIndexMap); @@ -1059,7 +1338,7 @@ public void testStatementResultSetColumnMappingCache() throws SQLException { rs.close(); rs = stmt.executeQuery("select * from testrs"); columnNameIndexMap = getResultSetColumnNameIndexMap(rs); - assertEquals(null, columnNameIndexMap); + assertNull(columnNameIndexMap); assertTrue(rs.next()); rs.getInt("Id"); columnNameIndexMap = getResultSetColumnNameIndexMap(rs); @@ -1077,10 +1356,10 @@ public void testPreparedStatementResultSetColumnMappingCache() throws SQLExcepti ResultSet rs = pstmt.executeQuery(); Map columnNameIndexMap; columnNameIndexMap = getResultSetColumnNameIndexMap(rs); - assertEquals(null, columnNameIndexMap); + assertNull(columnNameIndexMap); assertTrue(rs.next()); columnNameIndexMap = getResultSetColumnNameIndexMap(rs); - assertEquals(null, columnNameIndexMap); + assertNull(columnNameIndexMap); rs.getInt("id"); columnNameIndexMap = getResultSetColumnNameIndexMap(rs); assertNotNull(columnNameIndexMap); @@ -1088,7 +1367,7 @@ public void testPreparedStatementResultSetColumnMappingCache() throws SQLExcepti rs = pstmt.executeQuery(); assertTrue(rs.next()); columnNameIndexMap = getResultSetColumnNameIndexMap(rs); - assertEquals(null, columnNameIndexMap); + assertNull(columnNameIndexMap); rs.getInt("id"); columnNameIndexMap = getResultSetColumnNameIndexMap(rs); assertNotNull(columnNameIndexMap); @@ -1102,8 +1381,7 @@ public void testPreparedStatementResultSetColumnMappingCache() throws SQLExcepti */ @Test public void testNamedPreparedStatementResultSetColumnMappingCache() throws SQLException { - assumeTrue("Simple protocol only mode does not support server-prepared statements", - preferQueryMode != PreferQueryMode.SIMPLE); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE, "Simple protocol only mode does not support server-prepared statements"); PreparedStatement pstmt = con.prepareStatement("SELECT id FROM testrs"); ResultSet rs; // Make sure the prepared statement is named. @@ -1122,15 +1400,13 @@ public void testNamedPreparedStatementResultSetColumnMappingCache() throws SQLEx rs = pstmt.executeQuery(); assertTrue(rs.next()); rs.getInt("id"); - assertSame( - "Cached mapping should be same between different result sets of same named prepared statement", - columnNameIndexMap, getResultSetColumnNameIndexMap(rs)); + assertSame(columnNameIndexMap, getResultSetColumnNameIndexMap(rs), "Cached mapping should be same between different result sets of same named prepared statement"); rs.close(); pstmt.close(); } @SuppressWarnings("unchecked") - private Map getResultSetColumnNameIndexMap(ResultSet stmt) { + private static Map getResultSetColumnNameIndexMap(ResultSet stmt) { try { Field columnNameIndexMapField = stmt.getClass().getDeclaredField("columnNameIndexMap"); columnNameIndexMapField.setAccessible(true); @@ -1140,4 +1416,119 @@ private Map getResultSetColumnNameIndexMap(ResultSet stmt) { return null; } + private static class SelectTimestampManyTimes implements Callable { + + private final Connection connection; + private final int expectedYear; + + protected SelectTimestampManyTimes(Connection connection, int expectedYear) { + this.connection = connection; + this.expectedYear = expectedYear; + } + + @Override + @SuppressWarnings("deprecation") + public Integer call() throws SQLException { + int year = expectedYear; + try (Statement statement = connection.createStatement()) { + for (int i = 0; i < 10; i++) { + try (ResultSet resultSet = statement.executeQuery( + String.format("SELECT unnest(array_fill('8/10/%d'::timestamp, ARRAY[%d]))", + expectedYear, 500))) { + while (resultSet.next()) { + Timestamp d = resultSet.getTimestamp(1); + year = 1900 + d.getYear(); + if (year != expectedYear) { + return year; + } + } + } + } + } + return year; + } + } + + @Test + public void testTimestamp() throws InterruptedException, ExecutionException, TimeoutException { + ExecutorService e = Executors.newFixedThreadPool(2); + Integer year1 = 7777; + Future future1 = e.submit(new SelectTimestampManyTimes(con, year1)); + Integer year2 = 2017; + Future future2 = e.submit(new SelectTimestampManyTimes(con, year2)); + assertEquals(year1, future1.get(1, TimeUnit.MINUTES), "Year was changed in another thread"); + assertEquals(year2, future2.get(1, TimeUnit.MINUTES), "Year was changed in another thread"); + e.shutdown(); + e.awaitTermination(1, TimeUnit.MINUTES); + } + + @Test + public void testBooleanToNumericConversionDisabled() throws SQLException { + // Test that numeric getters throw exception when conversion is disabled (default) + try (Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT true::boolean AS bool_col")) { + assertTrue(rs.next()); + + // These should throw PSQLException with "Bad value for type" message + assertThrows(PSQLException.class, () -> rs.getByte("bool_col")); + assertThrows(PSQLException.class, () -> rs.getInt("bool_col")); + assertThrows(PSQLException.class, () -> rs.getLong("bool_col")); + assertThrows(PSQLException.class, () -> rs.getShort("bool_col")); + assertThrows(PSQLException.class, () -> rs.getFloat("bool_col")); + assertThrows(PSQLException.class, () -> rs.getDouble("bool_col")); + assertThrows(PSQLException.class, () -> rs.getBigDecimal("bool_col")); + + // getBoolean should still work + assertTrue(rs.getBoolean("bool_col")); + } + } + + @Test + public void testBooleanToNumericConversionEnabled() throws SQLException { + // Create a connection with boolean-to-numeric conversion enabled + Properties props = new Properties(); + props.setProperty("convertBooleanToNumeric", "true"); + + try (Connection conn = TestUtil.openDB(props); + Statement stmt = conn.createStatement()) { + + // Test TRUE conversion + try (ResultSet rs = stmt.executeQuery("SELECT true::boolean AS bool_col")) { + assertTrue(rs.next()); + assertEquals(1, rs.getByte("bool_col")); + assertEquals(1, rs.getInt("bool_col")); + assertEquals(1L, rs.getLong("bool_col")); + assertEquals((short) 1, rs.getShort("bool_col")); + assertEquals(1.0f, rs.getFloat("bool_col")); + assertEquals(1.0, rs.getDouble("bool_col")); + assertEquals(new BigDecimal("1"), rs.getBigDecimal("bool_col")); + assertTrue(rs.getBoolean("bool_col")); + } + + // Test FALSE conversion + try (ResultSet rs = stmt.executeQuery("SELECT false::boolean AS bool_col")) { + assertTrue(rs.next()); + assertEquals(0, rs.getByte("bool_col")); + assertEquals(0, rs.getInt("bool_col")); + assertEquals(0L, rs.getLong("bool_col")); + assertEquals((short) 0, rs.getShort("bool_col")); + assertEquals(0.0f, rs.getFloat("bool_col")); + assertEquals(0.0, rs.getDouble("bool_col")); + assertEquals(new BigDecimal("0"), rs.getBigDecimal("bool_col")); + assertFalse(rs.getBoolean("bool_col")); + } + + // Test text column with 't'/'f' values - should NOT be converted (not boolean column) + try (ResultSet rs = stmt.executeQuery("SELECT 't'::text AS text_col")) { + assertTrue(rs.next()); + assertThrows(PSQLException.class, () -> rs.getInt("text_col")); + } + + try (ResultSet rs = stmt.executeQuery("SELECT 'f'::text AS text_col")) { + assertTrue(rs.next()); + assertThrows(PSQLException.class, () -> rs.getInt("text_col")); + } + } + } + } diff --git a/src/test/java/org/postgresql/test/jdbc2/SearchPathLookupTest.java b/src/test/java/org/postgresql/test/jdbc2/SearchPathLookupTest.java index c1b7777..e87b803 100644 --- a/src/test/java/org/postgresql/test/jdbc2/SearchPathLookupTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/SearchPathLookupTest.java @@ -5,28 +5,29 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.core.BaseConnection; import org.postgresql.core.TypeInfo; import org.postgresql.test.TestUtil; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.ResultSet; import java.sql.Statement; /* - * TestCase to test the internal functionality of org.postgresql.jdbc2.DatabaseMetaData - * - */ -public class SearchPathLookupTest { +* TestCase to test the internal functionality of org.postgresql.jdbc2.DatabaseMetaData +* +*/ +class SearchPathLookupTest { private BaseConnection con; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { con = (BaseConnection) TestUtil.openDB(); } @@ -37,7 +38,7 @@ public void setUp() throws Exception { * first schema in the search_path). */ @Test - public void testSearchPathNormalLookup() throws Exception { + void searchPathNormalLookup() throws Exception { Statement stmt = con.createStatement(); try { TestUtil.createSchema(con, "first_schema"); @@ -54,7 +55,7 @@ public void testSearchPathNormalLookup() throws Exception { ResultSet rs = stmt.executeQuery("SELECT 'third_schema.x'::regtype::oid"); assertTrue(rs.next()); assertEquals(oid, rs.getInt(1)); - assertTrue(!rs.next()); + assertFalse(rs.next()); TestUtil.dropSchema(con, "first_schema"); TestUtil.dropSchema(con, "second_schema"); TestUtil.dropSchema(con, "third_schema"); @@ -73,7 +74,7 @@ public void testSearchPathNormalLookup() throws Exception { * that is used for keeping utility objects. */ @Test - public void testSearchPathHiddenLookup() throws Exception { + void searchPathHiddenLookup() throws Exception { Statement stmt = con.createStatement(); try { TestUtil.createSchema(con, "first_schema"); @@ -90,7 +91,7 @@ public void testSearchPathHiddenLookup() throws Exception { ResultSet rs = stmt.executeQuery("SELECT 'second_schema.y'::regtype::oid"); assertTrue(rs.next()); assertEquals(oid, rs.getInt(1)); - assertTrue(!rs.next()); + assertFalse(rs.next()); TestUtil.dropSchema(con, "first_schema"); TestUtil.dropSchema(con, "second_schema"); TestUtil.dropSchema(con, "third_schema"); @@ -104,7 +105,7 @@ public void testSearchPathHiddenLookup() throws Exception { } @Test - public void testSearchPathBackwardsCompatibleLookup() throws Exception { + void searchPathBackwardsCompatibleLookup() throws Exception { Statement stmt = con.createStatement(); try { TestUtil.createSchema(con, "first_schema"); @@ -117,7 +118,7 @@ public void testSearchPathBackwardsCompatibleLookup() throws Exception { .executeQuery("SELECT oid FROM pg_type WHERE typname = 'x' ORDER BY oid DESC LIMIT 1"); assertTrue(rs.next()); assertEquals(oid, rs.getInt(1)); - assertTrue(!rs.next()); + assertFalse(rs.next()); TestUtil.dropSchema(con, "first_schema"); TestUtil.dropSchema(con, "second_schema"); } finally { diff --git a/src/test/java/org/postgresql/test/jdbc2/ServerCursorTest.java b/src/test/java/org/postgresql/test/jdbc2/ServerCursorTest.java index 3409b1b..6c8d65c 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ServerCursorTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ServerCursorTest.java @@ -5,12 +5,15 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.postgresql.test.TestUtil; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -20,10 +23,24 @@ */ public class ServerCursorTest extends BaseTest4 { + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "test_fetch", "value integer,data bytea"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "test_fetch"); + } + } + @Override public void setUp() throws Exception { super.setUp(); - TestUtil.createTable(con, "test_fetch", "value integer,data bytea"); + TestUtil.execute(con, "TRUNCATE test_fetch"); con.setAutoCommit(false); } @@ -31,13 +48,12 @@ public void setUp() throws Exception { public void tearDown() throws SQLException { con.rollback(); con.setAutoCommit(true); - TestUtil.dropTable(con, "test_fetch"); super.tearDown(); } protected void createRows(int count) throws Exception { PreparedStatement stmt = con.prepareStatement("insert into test_fetch(value,data) values(?,?)"); - for (int i = 0; i < count; ++i) { + for (int i = 0; i < count; i++) { stmt.setInt(1, i + 1); stmt.setBytes(2, DATA_STRING.getBytes("UTF8")); stmt.executeUpdate(); @@ -48,7 +64,6 @@ protected void createRows(int count) throws Exception { // Test regular cursor fetching @Test public void testBasicFetch() throws Exception { - assumeByteaSupported(); createRows(1); PreparedStatement stmt = @@ -59,17 +74,15 @@ public void testBasicFetch() throws Exception { ResultSet rs = stmt.executeQuery(); while (rs.next()) { // there should only be one row returned - assertEquals("query value error", 1, rs.getInt(1)); + assertEquals(1, rs.getInt(1), "query value error"); byte[] dataBytes = rs.getBytes(2); - assertEquals("binary data got munged", DATA_STRING, new String(dataBytes, "UTF8")); + assertEquals(DATA_STRING, new String(dataBytes, "UTF8"), "binary data got munged"); } - } // Test binary cursor fetching @Test public void testBinaryFetch() throws Exception { - assumeByteaSupported(); createRows(1); PreparedStatement stmt = @@ -81,9 +94,8 @@ public void testBinaryFetch() throws Exception { while (rs.next()) { // there should only be one row returned byte[] dataBytes = rs.getBytes(2); - assertEquals("binary data got munged", DATA_STRING, new String(dataBytes, "UTF8")); + assertEquals(DATA_STRING, new String(dataBytes, "UTF8"), "binary data got munged"); } - } //CHECKSTYLE: OFF diff --git a/src/test/java/org/postgresql/test/jdbc2/ServerErrorTest.java b/src/test/java/org/postgresql/test/jdbc2/ServerErrorTest.java index 9f06d46..af139c1 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ServerErrorTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ServerErrorTest.java @@ -5,9 +5,9 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; @@ -15,8 +15,11 @@ import org.postgresql.util.PSQLState; import org.postgresql.util.ServerErrorMessage; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; @@ -25,25 +28,30 @@ */ public class ServerErrorTest extends BaseTest4 { - @Override - public void setUp() throws Exception { - super.setUp(); - assumeMinimumServerVersion(ServerVersion.v9_3); - Statement stmt = con.createStatement(); + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + Statement stmt = con.createStatement(); + stmt.execute("CREATE DOMAIN testdom AS int4 CHECK (value < 10)"); + TestUtil.createTable(con, "testerr", "id int not null, val testdom not null"); + stmt.execute("ALTER TABLE testerr ADD CONSTRAINT testerr_pk PRIMARY KEY (id)"); + stmt.close(); + } + } - stmt.execute("CREATE DOMAIN testdom AS int4 CHECK (value < 10)"); - TestUtil.createTable(con, "testerr", "id int not null, val testdom not null"); - stmt.execute("ALTER TABLE testerr ADD CONSTRAINT testerr_pk PRIMARY KEY (id)"); - stmt.close(); + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "testerr"); + TestUtil.dropDomain(con, "testdom"); + } } @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "testerr"); - Statement stmt = con.createStatement(); - stmt.execute("DROP DOMAIN IF EXISTS testdom"); - stmt.close(); - super.tearDown(); + public void setUp() throws Exception { + super.setUp(); + assumeMinimumServerVersion(ServerVersion.v9_3); + TestUtil.execute(con, "TRUNCATE testerr"); } @Test diff --git a/src/test/java/org/postgresql/test/jdbc2/ServerPreparedStmtTest.java b/src/test/java/org/postgresql/test/jdbc2/ServerPreparedStmtTest.java index 127ee15..feda4be 100644 --- a/src/test/java/org/postgresql/test/jdbc2/ServerPreparedStmtTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/ServerPreparedStmtTest.java @@ -5,59 +5,63 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.PGStatement; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.TestUtil; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; /* * Tests for using server side prepared statements */ public class ServerPreparedStmtTest extends BaseTest4 { + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "testsps", "id integer, value boolean"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "testsps"); + } + } + @Override public void setUp() throws Exception { super.setUp(); - Assume.assumeTrue("Server-prepared statements are not supported in simple protocol, thus ignoring the tests", - preferQueryMode != PreferQueryMode.SIMPLE); - - Statement stmt = con.createStatement(); - - TestUtil.createTable(con, "testsps", "id integer, value boolean"); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE, "Server-prepared statements are not supported in simple protocol, thus ignoring the tests"); - stmt.executeUpdate("INSERT INTO testsps VALUES (1,'t')"); - stmt.executeUpdate("INSERT INTO testsps VALUES (2,'t')"); - stmt.executeUpdate("INSERT INTO testsps VALUES (3,'t')"); - stmt.executeUpdate("INSERT INTO testsps VALUES (4,'t')"); - stmt.executeUpdate("INSERT INTO testsps VALUES (6,'t')"); - stmt.executeUpdate("INSERT INTO testsps VALUES (9,'f')"); - - stmt.close(); + TestUtil.execute(con, "TRUNCATE testsps"); + TestUtil.execute(con, "INSERT INTO testsps VALUES (1,'t'), (2,'t'), (3,'t'), (4,'t'), (6,'t'), (9,'f')"); } - @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "testsps"); - super.tearDown(); + @SuppressWarnings("deprecation") + private static void setUseServerPrepare(PreparedStatement pstmt, boolean flag) throws SQLException { + pstmt.unwrap(PGStatement.class).setUseServerPrepare(flag); } @Test public void testEmptyResults() throws Exception { PreparedStatement pstmt = con.prepareStatement("SELECT * FROM testsps WHERE id = ?"); - ((PGStatement) pstmt).setUseServerPrepare(true); - for (int i = 0; i < 10; ++i) { + setUseServerPrepare(pstmt, true); + for (int i = 0; i < 10; i++) { pstmt.setInt(1, -1); ResultSet rs = pstmt.executeQuery(); assertFalse(rs.next()); @@ -69,7 +73,7 @@ public void testEmptyResults() throws Exception { @Test public void testPreparedExecuteCount() throws Exception { PreparedStatement pstmt = con.prepareStatement("UPDATE testsps SET id = id + 44"); - ((PGStatement) pstmt).setUseServerPrepare(true); + setUseServerPrepare(pstmt, true); int count = pstmt.executeUpdate(); assertEquals(6, count); pstmt.close(); @@ -78,8 +82,8 @@ public void testPreparedExecuteCount() throws Exception { @Test public void testPreparedStatementsNoBinds() throws Exception { PreparedStatement pstmt = con.prepareStatement("SELECT * FROM testsps WHERE id = 2"); - ((PGStatement) pstmt).setUseServerPrepare(true); - assertTrue(((PGStatement) pstmt).isUseServerPrepare()); + setUseServerPrepare(pstmt, true); + assertTrue(pstmt.unwrap(PGStatement.class).isUseServerPrepare()); // Test that basic functionality works ResultSet rs = pstmt.executeQuery(); @@ -97,8 +101,8 @@ public void testPreparedStatementsNoBinds() throws Exception { if (Boolean.getBoolean("org.postgresql.forceBinary")) { return; } - ((PGStatement) pstmt).setUseServerPrepare(false); - assertTrue(!((PGStatement) pstmt).isUseServerPrepare()); + setUseServerPrepare(pstmt, false); + assertFalse(pstmt.unwrap(PGStatement.class).isUseServerPrepare()); rs = pstmt.executeQuery(); assertTrue(rs.next()); @@ -111,8 +115,8 @@ public void testPreparedStatementsNoBinds() throws Exception { @Test public void testPreparedStatementsWithOneBind() throws Exception { PreparedStatement pstmt = con.prepareStatement("SELECT * FROM testsps WHERE id = ?"); - ((PGStatement) pstmt).setUseServerPrepare(true); - assertTrue(((PGStatement) pstmt).isUseServerPrepare()); + setUseServerPrepare(pstmt, true); + assertTrue(pstmt.unwrap(PGStatement.class).isUseServerPrepare()); // Test that basic functionality works pstmt.setInt(1, 2); @@ -132,8 +136,8 @@ public void testPreparedStatementsWithOneBind() throws Exception { return; } - ((PGStatement) pstmt).setUseServerPrepare(false); - assertTrue(!((PGStatement) pstmt).isUseServerPrepare()); + setUseServerPrepare(pstmt, false); + assertFalse(pstmt.unwrap(PGStatement.class).isUseServerPrepare()); pstmt.setInt(1, 9); rs = pstmt.executeQuery(); @@ -148,8 +152,8 @@ public void testPreparedStatementsWithOneBind() throws Exception { @Test public void testBooleanObjectBind() throws Exception { PreparedStatement pstmt = con.prepareStatement("SELECT * FROM testsps WHERE value = ?"); - ((PGStatement) pstmt).setUseServerPrepare(true); - assertTrue(((PGStatement) pstmt).isUseServerPrepare()); + setUseServerPrepare(pstmt, true); + assertTrue(pstmt.unwrap(PGStatement.class).isUseServerPrepare()); pstmt.setObject(1, Boolean.FALSE, java.sql.Types.BIT); ResultSet rs = pstmt.executeQuery(); @@ -162,8 +166,8 @@ public void testBooleanObjectBind() throws Exception { @Test public void testBooleanIntegerBind() throws Exception { PreparedStatement pstmt = con.prepareStatement("SELECT * FROM testsps WHERE id = ?"); - ((PGStatement) pstmt).setUseServerPrepare(true); - assertTrue(((PGStatement) pstmt).isUseServerPrepare()); + setUseServerPrepare(pstmt, true); + assertTrue(pstmt.unwrap(PGStatement.class).isUseServerPrepare()); pstmt.setObject(1, Boolean.TRUE, java.sql.Types.INTEGER); ResultSet rs = pstmt.executeQuery(); @@ -176,8 +180,8 @@ public void testBooleanIntegerBind() throws Exception { @Test public void testBooleanBind() throws Exception { PreparedStatement pstmt = con.prepareStatement("SELECT * FROM testsps WHERE value = ?"); - ((PGStatement) pstmt).setUseServerPrepare(true); - assertTrue(((PGStatement) pstmt).isUseServerPrepare()); + setUseServerPrepare(pstmt, true); + assertTrue(pstmt.unwrap(PGStatement.class).isUseServerPrepare()); pstmt.setBoolean(1, false); ResultSet rs = pstmt.executeQuery(); @@ -189,8 +193,8 @@ public void testBooleanBind() throws Exception { @Test public void testPreparedStatementsWithBinds() throws Exception { PreparedStatement pstmt = con.prepareStatement("SELECT * FROM testsps WHERE id = ? or id = ?"); - ((PGStatement) pstmt).setUseServerPrepare(true); - assertTrue(((PGStatement) pstmt).isUseServerPrepare()); + setUseServerPrepare(pstmt, true); + assertTrue(pstmt.unwrap(PGStatement.class).isUseServerPrepare()); // Test that basic functionality works // bind different datatypes @@ -214,8 +218,8 @@ public void testPreparedStatementsWithBinds() throws Exception { public void testSPSToggle() throws Exception { // Verify we can toggle UseServerPrepare safely before a query is executed. PreparedStatement pstmt = con.prepareStatement("SELECT * FROM testsps WHERE id = 2"); - ((PGStatement) pstmt).setUseServerPrepare(true); - ((PGStatement) pstmt).setUseServerPrepare(false); + setUseServerPrepare(pstmt, true); + setUseServerPrepare(pstmt, false); } @Test @@ -225,7 +229,7 @@ public void testBytea() throws Exception { TestUtil.createTable(con, "testsps_bytea", "data bytea"); PreparedStatement pstmt = con.prepareStatement("INSERT INTO testsps_bytea(data) VALUES (?)"); - ((PGStatement) pstmt).setUseServerPrepare(true); + setUseServerPrepare(pstmt, true); pstmt.setBytes(1, new byte[100]); pstmt.executeUpdate(); } finally { @@ -239,7 +243,7 @@ public void testCreateTable() throws Exception { // CREATE TABLE isn't supported by PREPARE; the driver should realize this and // still complete without error. PreparedStatement pstmt = con.prepareStatement("CREATE TABLE testsps_bad(data int)"); - ((PGStatement) pstmt).setUseServerPrepare(true); + setUseServerPrepare(pstmt, true); pstmt.executeUpdate(); TestUtil.dropTable(con, "testsps_bad"); } @@ -252,7 +256,7 @@ public void testMultistatement() throws Exception { TestUtil.createTable(con, "testsps_multiple", "data int"); PreparedStatement pstmt = con.prepareStatement( "INSERT INTO testsps_multiple(data) VALUES (?); INSERT INTO testsps_multiple(data) VALUES (?)"); - ((PGStatement) pstmt).setUseServerPrepare(true); + setUseServerPrepare(pstmt, true); pstmt.setInt(1, 1); pstmt.setInt(2, 2); pstmt.executeUpdate(); // Two inserts. @@ -272,20 +276,20 @@ public void testMultistatement() throws Exception { @Test public void testTypeChange() throws Exception { PreparedStatement pstmt = con.prepareStatement("SELECT CAST (? AS TEXT)"); - ((PGStatement) pstmt).setUseServerPrepare(true); + setUseServerPrepare(pstmt, true); // Prepare with int parameter. pstmt.setInt(1, 1); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); assertEquals(1, rs.getInt(1)); - assertTrue(!rs.next()); + assertFalse(rs.next()); // Change to text parameter, check it still works. pstmt.setString(1, "test string"); rs = pstmt.executeQuery(); assertTrue(rs.next()); assertEquals("test string", rs.getString(1)); - assertTrue(!rs.next()); + assertFalse(rs.next()); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/SocketTimeoutTest.java b/src/test/java/org/postgresql/test/jdbc2/SocketTimeoutTest.java index f02600e..253aae7 100644 --- a/src/test/java/org/postgresql/test/jdbc2/SocketTimeoutTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/SocketTimeoutTest.java @@ -5,23 +5,23 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; -public class SocketTimeoutTest { +class SocketTimeoutTest { @Test - public void testSocketTimeoutEnforcement() throws Exception { + void socketTimeoutEnforcement() throws Exception { Properties properties = new Properties(); PGProperty.SOCKET_TIMEOUT.set(properties, 1); diff --git a/src/test/java/org/postgresql/test/jdbc2/StatementTest.java b/src/test/java/org/postgresql/test/jdbc2/StatementTest.java index 29122ff..ba10ef7 100644 --- a/src/test/java/org/postgresql/test/jdbc2/StatementTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/StatementTest.java @@ -5,27 +5,32 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.postgresql.Driver; import org.postgresql.PGProperty; import org.postgresql.core.ServerVersion; import org.postgresql.jdbc.PgStatement; -import org.postgresql.test.SlowTests; import org.postgresql.test.TestUtil; import org.postgresql.test.util.StrangeProxyServer; +import org.postgresql.util.LazyCleaner; +import org.postgresql.util.LazyCleanerImpl; import org.postgresql.util.PSQLState; +import org.postgresql.util.SharedTimer; -import org.junit.After; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import java.io.IOException; import java.sql.Connection; @@ -34,7 +39,10 @@ import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Statement; +import java.time.Duration; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; @@ -43,53 +51,52 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -/* - * Test for getObject - */ -public class StatementTest { +class StatementTest { private Connection con; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "test_lock", "name text"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "test_lock"); + } + } + + @BeforeEach + void setUp() throws Exception { con = TestUtil.openDB(); TestUtil.createTempTable(con, "test_statement", "i int"); TestUtil.createTempTable(con, "escapetest", "ts timestamp, d date, t time, \")\" varchar(5), \"\"\"){a}'\" text "); TestUtil.createTempTable(con, "comparisontest", "str1 varchar(5), str2 varchar(15)"); - TestUtil.createTable(con, "test_lock", "name text"); - Statement stmt = con.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("comparisontest", "str1,str2", "'_abcd','_found'")); - stmt.executeUpdate(TestUtil.insertSQL("comparisontest", "str1,str2", "'%abcd','%found'")); - stmt.close(); + TestUtil.execute(con, TestUtil.insertSQL("comparisontest", "str1,str2", "'_abcd','_found'")); + TestUtil.execute(con, TestUtil.insertSQL("comparisontest", "str1,str2", "'%abcd','%found'")); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { TestUtil.dropTable(con, "test_statement"); TestUtil.dropTable(con, "escapetest"); TestUtil.dropTable(con, "comparisontest"); - TestUtil.dropTable(con, "test_lock"); - TestUtil.execute("DROP FUNCTION IF EXISTS notify_loop()",con); - TestUtil.execute("DROP FUNCTION IF EXISTS notify_then_sleep()",con); + TestUtil.execute(con, "DROP FUNCTION IF EXISTS notify_loop()"); + TestUtil.execute(con, "DROP FUNCTION IF EXISTS notify_then_sleep()"); con.close(); } - private void assumeLongTest() { - // Run the test: - // Travis: in PG_VERSION=HEAD - // Other: always - if ("true".equals(System.getenv("TRAVIS"))) { - Assume.assumeTrue("HEAD".equals(System.getenv("PG_VERSION"))); - } - } - @Test - public void testClose() throws SQLException { + void close() throws SQLException { Statement stmt = con.createStatement(); stmt.close(); @@ -101,7 +108,7 @@ public void testClose() throws SQLException { } @Test - public void testResultSetClosed() throws SQLException { + void resultSetClosed() throws SQLException { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select 1"); stmt.close(); @@ -112,14 +119,14 @@ public void testResultSetClosed() throws SQLException { * Closing a Statement twice is not an error. */ @Test - public void testDoubleClose() throws SQLException { + void doubleClose() throws SQLException { Statement stmt = con.createStatement(); stmt.close(); stmt.close(); } @Test - public void testMultiExecute() throws SQLException { + void multiExecute() throws SQLException { Statement stmt = con.createStatement(); assertTrue(stmt.execute("SELECT 1 as a; UPDATE test_statement SET i=1; SELECT 2 as b, 3 as c")); @@ -128,7 +135,7 @@ public void testMultiExecute() throws SQLException { assertEquals(1, rs.getInt(1)); rs.close(); - assertTrue(!stmt.getMoreResults()); + assertFalse(stmt.getMoreResults()); assertEquals(0, stmt.getUpdateCount()); assertTrue(stmt.getMoreResults()); @@ -137,21 +144,21 @@ public void testMultiExecute() throws SQLException { assertEquals(2, rs.getInt(1)); rs.close(); - assertTrue(!stmt.getMoreResults()); + assertFalse(stmt.getMoreResults()); assertEquals(-1, stmt.getUpdateCount()); stmt.close(); } @Test - public void testEmptyQuery() throws SQLException { + void emptyQuery() throws SQLException { Statement stmt = con.createStatement(); stmt.execute(""); assertNull(stmt.getResultSet()); - assertTrue(!stmt.getMoreResults()); + assertFalse(stmt.getMoreResults()); } @Test - public void testUpdateCount() throws SQLException { + void updateCount() throws SQLException { Statement stmt = con.createStatement(); int count; @@ -173,7 +180,7 @@ public void testUpdateCount() throws SQLException { } @Test - public void testEscapeProcessing() throws SQLException { + void escapeProcessing() throws SQLException { Statement stmt = con.createStatement(); int count; @@ -212,8 +219,8 @@ public void testEscapeProcessing() throws SQLException { assertEquals(0, count); rs = stmt.executeQuery("select * from {oj test_statement a left outer join b on (a.i=b.i)} "); - assertTrue(!rs.next()); - // test escape escape character + assertFalse(rs.next()); + // test escape character rs = stmt .executeQuery("select str2 from comparisontest where str1 like '|_abcd' {escape '|'} "); assertTrue(rs.next()); @@ -225,7 +232,7 @@ public void testEscapeProcessing() throws SQLException { } @Test - public void testPreparedFunction() throws SQLException { + void preparedFunction() throws SQLException { PreparedStatement pstmt = con.prepareStatement("SELECT {fn concat('a', ?)}"); pstmt.setInt(1, 5); ResultSet rs = pstmt.executeQuery(); @@ -234,23 +241,23 @@ public void testPreparedFunction() throws SQLException { } @Test - public void testDollarInComment() throws SQLException { + void dollarInComment() throws SQLException { PreparedStatement pstmt = con.prepareStatement("SELECT /* $ */ {fn curdate()}"); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertNotNull("{fn curdate()} should be not null", rs.getString(1)); + assertNotNull(rs.getString(1), "{fn curdate()} should be not null"); } @Test - public void testDollarInCommentTwoComments() throws SQLException { + void dollarInCommentTwoComments() throws SQLException { PreparedStatement pstmt = con.prepareStatement("SELECT /* $ *//* $ */ {fn curdate()}"); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertNotNull("{fn curdate()} should be not null", rs.getString(1)); + assertNotNull(rs.getString(1), "{fn curdate()} should be not null"); } @Test - public void testNumericFunctions() throws SQLException { + void numericFunctions() throws SQLException { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select {fn abs(-2.3)} as abs "); @@ -314,7 +321,7 @@ public void testNumericFunctions() throws SQLException { } @Test - public void testStringFunctions() throws SQLException { + void stringFunctions() throws SQLException { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery( "select {fn ascii(' test')},{fn char(32)}" @@ -354,7 +361,7 @@ public void testStringFunctions() throws SQLException { } @Test - public void testDateFuncWithParam() throws SQLException { + void dateFuncWithParam() throws SQLException { // Prior to 8.0 there is not an interval + timestamp operator, // so timestampadd does not work. // @@ -369,7 +376,7 @@ public void testDateFuncWithParam() throws SQLException { } @Test - public void testDateFunctions() throws SQLException { + void dateFunctions() throws SQLException { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select {fn curdate()},{fn curtime()}" + ",{fn dayname({fn now()})}, {fn dayofmonth({fn now()})}" @@ -435,7 +442,7 @@ public void testDateFunctions() throws SQLException { // timestampadd(SQL_TSI_MONTH,3,{fn now()})})} "); // assertTrue(rs.next()); // assertEquals(3,rs.getInt(1)); - // QUARTER => backend assume there are 1 quater even in 270 days... + // QUARTER => backend assume there are 1 quarter even in 270 days... // rs = stmt.executeQuery("select {fn timestampdiff(SQL_TSI_QUARTER,{fn now()},{fn // timestampadd(SQL_TSI_QUARTER,3,{fn now()})})} "); // assertTrue(rs.next()); @@ -448,7 +455,7 @@ public void testDateFunctions() throws SQLException { } @Test - public void testSystemFunctions() throws SQLException { + void systemFunctions() throws SQLException { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery( "select {fn ifnull(null,'2')}" @@ -463,7 +470,7 @@ public void testSystemFunctions() throws SQLException { } @Test - public void testWarningsAreCleared() throws SQLException { + void warningsAreCleared() throws SQLException { Statement stmt = con.createStatement(); // Will generate a NOTICE: for primary key index creation stmt.execute("CREATE TEMP TABLE unused (a int primary key)"); @@ -474,7 +481,7 @@ public void testWarningsAreCleared() throws SQLException { } @Test - public void testWarningsAreAvailableAsap() + void warningsAreAvailableAsap() throws Exception { try (Connection outerLockCon = TestUtil.openDB()) { outerLockCon.setAutoCommit(false); @@ -500,12 +507,10 @@ public Void call() throws SQLException, InterruptedException { while (true) { SQLWarning warning = preparedStatement.getWarnings(); if (warning != null) { - assertEquals("First warning received not first notice raised", - "Test 1", warning.getMessage()); + assertEquals("Test 1", warning.getMessage(), "First warning received not first notice raised"); SQLWarning next = warning.getNextWarning(); if (next != null) { - assertEquals("Second warning received not second notice raised", - "Test 2", next.getMessage()); + assertEquals("Test 2", next.getMessage(), "Second warning received not second notice raised"); //Release the lock so that the notice generating statement can end. outerLockCon.commit(); return null; @@ -532,15 +537,15 @@ public Void call() throws SQLException, InterruptedException { } /** - *

          Demonstrates a safe approach to concurrently reading the latest - * warnings while periodically clearing them.

          + * Demonstrates a safe approach to concurrently reading the latest + * warnings while periodically clearing them. * *

          One drawback of this approach is that it requires the reader to make it to the end of the * warning chain before clearing it, so long as your warning processing step is not very slow, * this should happen more or less instantaneously even if you receive a lot of warnings.

          */ @Test - public void testConcurrentWarningReadAndClear() + void concurrentWarningReadAndClear() throws SQLException, InterruptedException, ExecutionException, TimeoutException { final int iterations = 1000; con.createStatement() @@ -571,8 +576,7 @@ public Void call() throws SQLException, InterruptedException { if (warn != null) { warnings++; //System.out.println("Processing " + warn.getMessage()); - assertEquals("Received warning out of expected order", - "Warning " + warnings, warn.getMessage()); + assertEquals("Warning " + warnings, warn.getMessage(), "Received warning out of expected order"); lastProcessed = warn; //If the processed warning was the head of the chain clear if (warn == statement.getWarnings()) { @@ -586,8 +590,7 @@ public Void call() throws SQLException, InterruptedException { Thread.sleep(10); } } - assertEquals("Didn't receive expected last warning", - "Warning " + iterations, lastProcessed.getMessage()); + assertEquals("Warning " + iterations, lastProcessed.getMessage(), "Didn't receive expected last warning"); return null; } }; @@ -609,7 +612,7 @@ public Void call() throws SQLException, InterruptedException { * multiple rule actions together in one statement. */ @Test - public void testParsingSemiColons() throws SQLException { + void parsingSemiColons() throws SQLException { Statement stmt = con.createStatement(); stmt.execute( "CREATE RULE r1 AS ON INSERT TO escapetest DO (DELETE FROM test_statement ; INSERT INTO test_statement VALUES (1); INSERT INTO test_statement VALUES (2); );"); @@ -619,11 +622,11 @@ public void testParsingSemiColons() throws SQLException { assertEquals(1, rs.getInt(1)); assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test - public void testParsingDollarQuotes() throws SQLException { + void parsingDollarQuotes() throws SQLException { // dollar-quotes are supported in the backend since version 8.0 Statement st = con.createStatement(); ResultSet rs; @@ -667,7 +670,7 @@ public void testParsingDollarQuotes() throws SQLException { } @Test - public void testUnbalancedParensParseError() throws SQLException { + void unbalancedParensParseError() throws SQLException { Statement stmt = con.createStatement(); try { stmt.executeQuery("SELECT i FROM test_statement WHERE (1 > 0)) ORDER BY i"); @@ -677,7 +680,7 @@ public void testUnbalancedParensParseError() throws SQLException { } @Test - public void testExecuteUpdateFailsOnSelect() throws SQLException { + void executeUpdateFailsOnSelect() throws SQLException { Statement stmt = con.createStatement(); try { stmt.executeUpdate("SELECT 1"); @@ -687,7 +690,7 @@ public void testExecuteUpdateFailsOnSelect() throws SQLException { } @Test - public void testExecuteUpdateFailsOnMultiStatementSelect() throws SQLException { + void executeUpdateFailsOnMultiStatementSelect() throws SQLException { Statement stmt = con.createStatement(); try { stmt.executeUpdate("/* */; SELECT 1"); @@ -697,8 +700,7 @@ public void testExecuteUpdateFailsOnMultiStatementSelect() throws SQLException { } @Test - @Category(SlowTests.class) - public void testSetQueryTimeout() throws SQLException { + void setQueryTimeout() throws SQLException { Statement stmt = con.createStatement(); long start = 0; boolean cancelReceived = false; @@ -720,15 +722,16 @@ public void testSetQueryTimeout() throws SQLException { } @Test - @Category(SlowTests.class) - public void testLongQueryTimeout() throws SQLException { + void longQueryTimeout() throws SQLException { Statement stmt = con.createStatement(); stmt.setQueryTimeout(Integer.MAX_VALUE); - Assert.assertEquals("setQueryTimeout(Integer.MAX_VALUE)", Integer.MAX_VALUE, - stmt.getQueryTimeout()); + assertEquals(Integer.MAX_VALUE, + stmt.getQueryTimeout(), + "setQueryTimeout(Integer.MAX_VALUE)"); stmt.setQueryTimeout(Integer.MAX_VALUE - 1); - Assert.assertEquals("setQueryTimeout(Integer.MAX_VALUE-1)", Integer.MAX_VALUE - 1, - stmt.getQueryTimeout()); + assertEquals(Integer.MAX_VALUE - 1, + stmt.getQueryTimeout(), + "setQueryTimeout(Integer.MAX_VALUE-1)"); } /** @@ -736,9 +739,7 @@ public void testLongQueryTimeout() throws SQLException { * one does not. The timeout of the first query should not impact the second one. */ @Test - @Category(SlowTests.class) - public void testShortQueryTimeout() throws SQLException { - assumeLongTest(); + void shortQueryTimeout() throws SQLException { long deadLine = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); Statement stmt = con.createStatement(); @@ -754,10 +755,10 @@ public void testShortQueryTimeout() throws SQLException { // but anything else is fatal. We can't differentiate other causes of statement cancel like // "canceling statement due to user request" without error message matching though, and we // don't want to do that. - Assert.assertEquals( - "Query is expected to be cancelled via st.close(), got " + e.getMessage(), + assertEquals( PSQLState.QUERY_CANCELED.getState(), - e.getSQLState()); + e.getSQLState(), + "Query is expected to be cancelled via st.close(), got " + e.getMessage()); } // Must never time out. stmt2.executeQuery("select 1;"); @@ -765,11 +766,10 @@ public void testShortQueryTimeout() throws SQLException { } @Test - public void testSetQueryTimeoutWithSleep() throws SQLException, InterruptedException { + void setQueryTimeoutWithSleep() throws SQLException, InterruptedException { // check that the timeout starts ticking at execute, not at the // setQueryTimeout call. - Statement stmt = con.createStatement(); - try { + try (Statement stmt = con.createStatement()) { stmt.setQueryTimeout(1); Thread.sleep(3000); stmt.execute("select pg_sleep(5)"); @@ -783,7 +783,7 @@ public void testSetQueryTimeoutWithSleep() throws SQLException, InterruptedExcep } @Test - public void testSetQueryTimeoutOnPrepared() throws SQLException, InterruptedException { + void setQueryTimeoutOnPrepared() throws SQLException, InterruptedException { // check that a timeout set on a prepared statement works on every // execution. PreparedStatement pstmt = con.prepareStatement("select pg_sleep(5)"); @@ -802,7 +802,7 @@ public void testSetQueryTimeoutOnPrepared() throws SQLException, InterruptedExce } @Test - public void testSetQueryTimeoutWithoutExecute() throws SQLException, InterruptedException { + void setQueryTimeoutWithoutExecute() throws SQLException, InterruptedException { // check that a timeout set on one statement doesn't affect another Statement stmt1 = con.createStatement(); stmt1.setQueryTimeout(1); @@ -812,7 +812,7 @@ public void testSetQueryTimeoutWithoutExecute() throws SQLException, Interrupted } @Test - public void testResultSetTwice() throws SQLException { + void resultSetTwice() throws SQLException { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select {fn abs(-2.3)} as abs "); @@ -823,55 +823,47 @@ public void testResultSetTwice() throws SQLException { } @Test - public void testMultipleCancels() throws Exception { - org.postgresql.util.SharedTimer sharedTimer = org.postgresql.Driver.getSharedTimer(); - - Connection connA = null; - Connection connB = null; - Statement stmtA = null; - Statement stmtB = null; - ResultSet rsA = null; - ResultSet rsB = null; - try { + void multipleCancels() throws Exception { + SharedTimer sharedTimer = Driver.getSharedTimer(); + + try (Connection connA = TestUtil.openDB(); + Connection connB = TestUtil.openDB(); + Statement stmtA = connA.createStatement(); + Statement stmtB = connB.createStatement(); + ) { assertEquals(0, sharedTimer.getRefCount()); - connA = TestUtil.openDB(); - connB = TestUtil.openDB(); - stmtA = connA.createStatement(); - stmtB = connB.createStatement(); stmtA.setQueryTimeout(1); stmtB.setQueryTimeout(1); - try { - rsA = stmtA.executeQuery("SELECT pg_sleep(2)"); + try (ResultSet rsA = stmtA.executeQuery("SELECT pg_sleep(2)")) { + fail("statement should have been canceled by query timeout since the sleep should take 2 sec and the timeout was 1 sec"); } catch (SQLException e) { - // ignore the expected timeout + assertEquals( + PSQLState.QUERY_CANCELED.getState(), e.getSQLState(), + "Query is expected to be cancelled since the sleep should take 2 sec and the timeout was 1 sec"); } assertEquals(1, sharedTimer.getRefCount()); - try { - rsB = stmtB.executeQuery("SELECT pg_sleep(2)"); + try (ResultSet rsB = stmtB.executeQuery("SELECT pg_sleep(2)");) { + fail("statement should have been canceled by query timeout since the sleep should take 2 sec and the timeout was 1 sec"); } catch (SQLException e) { - // ignore the expected timeout + assertEquals( + PSQLState.QUERY_CANCELED.getState(), e.getSQLState(), + "Query is expected to be cancelled since the sleep should take 2 sec and the timeout was 1 sec"); } - } finally { - TestUtil.closeQuietly(rsA); - TestUtil.closeQuietly(rsB); - TestUtil.closeQuietly(stmtA); - TestUtil.closeQuietly(stmtB); - TestUtil.closeQuietly(connA); - TestUtil.closeQuietly(connB); } assertEquals(0, sharedTimer.getRefCount()); } - @Test(timeout = 30000) - @Category(SlowTests.class) - public void testCancelQueryWithBrokenNetwork() throws SQLException, IOException, InterruptedException { + @Test + @Timeout(30) + void cancelQueryWithBrokenNetwork() throws SQLException, IOException, InterruptedException { // check that stmt.cancel() doesn't hang forever if the network is broken - ExecutorService executor = Executors.newSingleThreadExecutor(); + ExecutorService executor = Executors.newCachedThreadPool(); try (StrangeProxyServer proxyServer = new StrangeProxyServer(TestUtil.getServer(), TestUtil.getPort())) { Properties props = new Properties(); - props.setProperty(TestUtil.SERVER_HOST_PORT_PROP, String.format("%s:%s", "localhost", proxyServer.getServerPort())); + TestUtil.setTestUrlProperty(props, PGProperty.PG_HOST, "localhost"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_PORT, String.valueOf(proxyServer.getServerPort())); PGProperty.CANCEL_SIGNAL_TIMEOUT.set(props, 1); try (Connection conn = TestUtil.openDB(props); Statement stmt = conn.createStatement()) { @@ -881,21 +873,27 @@ public void testCancelQueryWithBrokenNetwork() throws SQLException, IOException, proxyServer.stopForwardingAllClients(); stmt.cancel(); + // Note: network is still inaccessible, so the statement execution is still in progress. + // So we abort the connection to allow implicit conn.close() + conn.abort(executor); } } executor.shutdownNow(); } - @Test(timeout = 10000) - public void testCloseInProgressStatement() throws Exception { + /* + We are going to use this test to test version 3.2 since the only change in 3.2 is the width of the + cancel key. We need a test that does a cancel. We call this below once without changing the + protocol version and once with protocol version 3.2 + */ + private void closePrivateInProgressStatement() throws Exception { ExecutorService executor = Executors.newSingleThreadExecutor(); - final Connection outerLockCon = TestUtil.openDB(); - outerLockCon.setAutoCommit(false); - //Acquire an exclusive lock so we can block the notice generating statement - outerLockCon.createStatement().execute("LOCK TABLE test_lock IN ACCESS EXCLUSIVE MODE;"); + try (Connection outerLockCon = TestUtil.openDB()) { + outerLockCon.setAutoCommit(false); + //Acquire an exclusive lock so we can block the notice generating statement + outerLockCon.createStatement().execute("LOCK TABLE test_lock IN ACCESS EXCLUSIVE MODE;"); - try { con.createStatement().execute("SET SESSION client_min_messages = 'NOTICE'"); con.createStatement() .execute("CREATE OR REPLACE FUNCTION notify_then_sleep() RETURNS VOID AS " @@ -927,10 +925,10 @@ public Void call() throws Exception { try { st.execute("select notify_then_sleep()"); } catch (SQLException e) { - Assert.assertEquals( - "Query is expected to be cancelled via st.close(), got " + e.getMessage(), + assertEquals( PSQLState.QUERY_CANCELED.getState(), - e.getSQLState() + e.getSQLState(), + "Query is expected to be cancelled via st.close(), got " + e.getMessage() ); cancels++; break; @@ -938,15 +936,78 @@ public Void call() throws Exception { TestUtil.closeQuietly(st); } } - Assert.assertNotEquals("At least one QUERY_CANCELED state is expected", 0, cancels); + assertNotEquals(0, cancels, "At least one QUERY_CANCELED state is expected"); + } finally { + executor.shutdown(); + } + } + + @Test + @Timeout(10) + void closeInProgressStatement() throws Exception { + closePrivateInProgressStatement(); + } + + @Test + @Timeout(10) + void closeInProgressStatementProtocol32() throws Exception { + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)); + Properties props = new Properties(); + con.close(); + PGProperty.PROTOCOL_VERSION.set(props, "3.2"); + con = TestUtil.openDB(props); + closePrivateInProgressStatement(); + } + + @Test + @Timeout(10) + void concurrentIsValid() throws Throwable { + ExecutorService executor = Executors.newCachedThreadPool(); + try { + List> results = new ArrayList<>(); + Random rnd = new Random(); + for (int i = 0; i < 10; i++) { + Future future = executor.submit(() -> { + try { + for (int j = 0; j < 50; j++) { + con.isValid(2); + try (PreparedStatement ps = + con.prepareStatement("select * from generate_series(1,?) as x(id)")) { + int limit = rnd.nextInt(10); + ps.setInt(1, limit); + try (ResultSet r = ps.executeQuery()) { + int cnt = 0; + String callName = "generate_series(1, " + limit + ") in thread " + + Thread.currentThread().getName(); + while (r.next()) { + cnt++; + assertEquals(cnt, r.getInt(1), callName + ", row " + cnt); + } + assertEquals(limit, cnt, callName + " number of rows"); + } + } + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + results.add(future); + } + for (Future result : results) { + // Propagate exception if any + result.get(); + } + } catch (ExecutionException e) { + throw e.getCause(); } finally { executor.shutdown(); - TestUtil.closeQuietly(outerLockCon); + executor.awaitTermination(10, TimeUnit.SECONDS); } } - @Test(timeout = 20000) - public void testFastCloses() throws SQLException { + @Test + @Timeout(40) + void fastCloses() throws SQLException { ExecutorService executor = Executors.newSingleThreadExecutor(); con.createStatement().execute("SET SESSION client_min_messages = 'NOTICE'"); con.createStatement() @@ -958,25 +1019,22 @@ public void testFastCloses() throws SQLException { + "END " + "$BODY$ " + "LANGUAGE plpgsql;"); - Map cnt = new HashMap(); + Map cnt = new HashMap<>(); final Random rnd = new Random(); for (int i = 0; i < 1000; i++) { final Statement st = con.createStatement(); - executor.submit(new Callable() { - @Override - public Void call() throws Exception { - int s = rnd.nextInt(10); - if (s > 8) { - try { - Thread.sleep(s - 9); - } catch (InterruptedException ex ) { - // don't execute the close here as this thread was cancelled below in shutdownNow - return null; - } + executor.submit((Callable) () -> { + int s = rnd.nextInt(10); + if (s > 8) { + try { + Thread.sleep(0); + } catch (InterruptedException ex) { + // don't execute the close here as this thread was cancelled below in shutdownNow + return null; } - st.close(); - return null; } + st.close(); + return null; }); ResultSet rs = null; String sqlState = "0"; @@ -987,10 +1045,10 @@ public Void call() throws Exception { sqlState = e.getSQLState(); if (!PSQLState.OBJECT_NOT_IN_STATE.getState().equals(sqlState) && !PSQLState.QUERY_CANCELED.getState().equals(sqlState)) { - Assert.assertEquals( - "Query is expected to be cancelled via st.close(), got " + e.getMessage(), + assertEquals( PSQLState.QUERY_CANCELED.getState(), - e.getSQLState() + e.getSQLState(), + "Query is expected to be cancelled via st.close(), got " + e.getMessage() ); } } finally { @@ -1001,7 +1059,6 @@ public Void call() throws Exception { val = (val == null ? 0 : val) + 1; cnt.put(sqlState, val); } - System.out.println("[testFastCloses] total counts for each sql state: " + cnt); executor.shutdown(); } @@ -1010,11 +1067,16 @@ public Void call() throws Exception { * in {@link java.util.ConcurrentModificationException}. */ @Test - public void testSideStatementFinalizers() throws SQLException { + void sideStatementFinalizers() throws SQLException { long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2); final AtomicInteger leaks = new AtomicInteger(); - final AtomicReference cleanupFailure = new AtomicReference(); + final AtomicReference cleanupFailure = new AtomicReference<>(); + // Create several cleaners, so they can clean leaks concurrently + List cleaners = new ArrayList<>(); + for (int i = 0; i < 16; i++) { + cleaners.add(new LazyCleanerImpl("pgjdbc-test-cleaner-" + i, Duration.ofSeconds(2))); + } for (int q = 0; System.nanoTime() < deadline || leaks.get() < 10000; q++) { for (int i = 0; i < 100; i++) { @@ -1022,20 +1084,16 @@ public void testSideStatementFinalizers() throws SQLException { ps.close(); } final int nextId = q; - new Object() { - PreparedStatement ps = con.prepareStatement("select /*leak*/ " + nextId); - - @Override - protected void finalize() throws Throwable { - super.finalize(); - try { - ps.close(); - } catch (Throwable t) { - cleanupFailure.compareAndSet(null, t); - } - leaks.incrementAndGet(); + int cleanerId = ThreadLocalRandom.current().nextInt(cleaners.size()); + PreparedStatement ps = con.prepareStatement("select /*leak*/ " + nextId); + cleaners.get(cleanerId).register(new Object(), leak -> { + try { + ps.close(); + } catch (Throwable t) { + cleanupFailure.compareAndSet(null, t); } - }; + leaks.incrementAndGet(); + }); } if (cleanupFailure.get() != null) { throw new IllegalStateException("Detected failure in cleanup thread", cleanupFailure.get()); @@ -1047,7 +1105,7 @@ protected void finalize() throws Throwable { * @throws SQLException if something goes wrong */ @Test - public void testJavascriptFunction() throws SQLException { + void javaScriptFunction() throws SQLException { String str = " var _modules = {};\n" + " var _current_stack = [];\n" + "\n" @@ -1063,38 +1121,38 @@ public void testJavascriptFunction() throws SQLException { ps = con.prepareStatement("select $JAVASCRIPT$" + str + "$JAVASCRIPT$"); ResultSet rs = ps.executeQuery(); rs.next(); - assertEquals("Javascript code has been protected with $JAVASCRIPT$", str, rs.getString(1)); + assertEquals(str, rs.getString(1), "JavaScript code has been protected with $JAVASCRIPT$"); } finally { TestUtil.closeQuietly(ps); } } @Test - public void testUnterminatedDollarQuotes() throws SQLException { + void unterminatedDollarQuotes() throws SQLException { ensureSyntaxException("dollar quotes", "CREATE OR REPLACE FUNCTION update_on_change() RETURNS TRIGGER AS $$\n" + "BEGIN"); } @Test - public void testUnterminatedNamedDollarQuotes() throws SQLException { + void unterminatedNamedDollarQuotes() throws SQLException { ensureSyntaxException("dollar quotes", "CREATE OR REPLACE FUNCTION update_on_change() RETURNS TRIGGER AS $ABC$\n" + "BEGIN"); } @Test - public void testUnterminatedComment() throws SQLException { + void unterminatedComment() throws SQLException { ensureSyntaxException("block comment", "CREATE OR REPLACE FUNCTION update_on_change() RETURNS TRIGGER AS /* $$\n" + "BEGIN $$"); } @Test - public void testUnterminatedLiteral() throws SQLException { + void unterminatedLiteral() throws SQLException { ensureSyntaxException("string literal", "CREATE OR REPLACE FUNCTION update_on_change() 'RETURNS TRIGGER AS $$\n" + "BEGIN $$"); } @Test - public void testUnterminatedIdentifier() throws SQLException { + void unterminatedIdentifier() throws SQLException { ensureSyntaxException("string literal", "CREATE OR REPLACE FUNCTION \"update_on_change() RETURNS TRIGGER AS $$\n" + "BEGIN $$"); } @@ -1106,8 +1164,7 @@ private void ensureSyntaxException(String errorType, String sql) throws SQLExcep ps.executeUpdate(); fail("Query with unterminated " + errorType + " should fail"); } catch (SQLException e) { - assertEquals("Query should fail with unterminated " + errorType, - PSQLState.SYNTAX_ERROR.getState(), e.getSQLState()); + assertEquals(PSQLState.SYNTAX_ERROR.getState(), e.getSQLState(), "Query should fail with unterminated " + errorType); } finally { TestUtil.closeQuietly(ps); } diff --git a/src/test/java/org/postgresql/test/jdbc2/StringTypeUnspecifiedArrayTest.java b/src/test/java/org/postgresql/test/jdbc2/StringTypeUnspecifiedArrayTest.java index 81b7bf7..af69f5a 100644 --- a/src/test/java/org/postgresql/test/jdbc2/StringTypeUnspecifiedArrayTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/StringTypeUnspecifiedArrayTest.java @@ -5,28 +5,29 @@ package org.postgresql.test.jdbc2; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.PGProperty; import org.postgresql.geometric.PGbox; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.Array; import java.util.ArrayList; import java.util.Collection; import java.util.Properties; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class StringTypeUnspecifiedArrayTest extends BaseTest4 { public StringTypeUnspecifiedArrayTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } @@ -43,6 +44,6 @@ protected void updateProperties(Properties props) { public void testCreateArrayWithNonCachedType() throws Exception { PGbox[] in = new PGbox[0]; Array a = con.createArrayOf("box", in); - Assert.assertEquals(1111, a.getBaseType()); + assertEquals(1111, a.getBaseType()); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/TestACL.java b/src/test/java/org/postgresql/test/jdbc2/TestACL.java index a42f181..ebf9cf3 100644 --- a/src/test/java/org/postgresql/test/jdbc2/TestACL.java +++ b/src/test/java/org/postgresql/test/jdbc2/TestACL.java @@ -8,12 +8,12 @@ import org.postgresql.jdbc.PgConnection; import org.postgresql.jdbc.PgDatabaseMetaData; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class TestACL { +class TestACL { @Test - public void testParseACL() { + void parseACL() { PgConnection pgConnection = null; PgDatabaseMetaData a = new PgDatabaseMetaData(pgConnection) { }; diff --git a/src/test/java/org/postgresql/test/jdbc2/TimeTest.java b/src/test/java/org/postgresql/test/jdbc2/TimeTest.java index c46f1fe..e400716 100644 --- a/src/test/java/org/postgresql/test/jdbc2/TimeTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/TimeTest.java @@ -5,15 +5,16 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; @@ -22,32 +23,33 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; +import java.sql.Types; import java.util.Calendar; import java.util.TimeZone; /* - * Some simple tests based on problems reported by users. Hopefully these will help prevent previous - * problems from re-occurring ;-) - * - */ -public class TimeTest { +* Some simple tests based on problems reported by users. Hopefully these will help prevent previous +* problems from re-occurring ;-) +* +*/ +class TimeTest { private Connection con; - private boolean testSetTime = false; + private boolean testSetTime; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { con = TestUtil.openDB(); TestUtil.createTempTable(con, "testtime", "tm time, tz time with time zone"); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { TestUtil.dropTable(con, "testtime"); TestUtil.closeDB(con); } - private long extractMillis(long time) { - return (time >= 0) ? (time % 1000) : (time % 1000 + 1000); + private static long extractMillis(long time) { + return time >= 0 ? (time % 1000) : (time % 1000 + 1000); } /* @@ -55,7 +57,8 @@ private long extractMillis(long time) { * Test use of calendar */ @Test - public void testGetTimeZone() throws Exception { + @SuppressWarnings("deprecation") + void getTimeZone() throws Exception { final Time midnight = new Time(0, 0, 0); Statement stmt = con.createStatement(); Calendar cal = Calendar.getInstance(); @@ -124,7 +127,7 @@ public void testGetTimeZone() throws Exception { * Tests the time methods in ResultSet */ @Test - public void testGetTime() throws SQLException { + void getTime() throws SQLException { Statement stmt = con.createStatement(); assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL("testtime", "'01:02:03'"))); @@ -147,7 +150,7 @@ public void testGetTime() throws SQLException { * Tests the time methods in PreparedStatement */ @Test - public void testSetTime() throws SQLException { + void setTime() throws SQLException { PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("testtime", "?")); Statement stmt = con.createStatement(); @@ -157,28 +160,28 @@ public void testSetTime() throws SQLException { ps.setTime(1, makeTime(23, 59, 59)); assertEquals(1, ps.executeUpdate()); - ps.setObject(1, Time.valueOf("12:00:00"), java.sql.Types.TIME); + ps.setObject(1, Time.valueOf("12:00:00"), Types.TIME); assertEquals(1, ps.executeUpdate()); - ps.setObject(1, Time.valueOf("05:15:21"), java.sql.Types.TIME); + ps.setObject(1, Time.valueOf("05:15:21"), Types.TIME); assertEquals(1, ps.executeUpdate()); - ps.setObject(1, Time.valueOf("16:21:51"), java.sql.Types.TIME); + ps.setObject(1, Time.valueOf("16:21:51"), Types.TIME); assertEquals(1, ps.executeUpdate()); - ps.setObject(1, Time.valueOf("12:15:12"), java.sql.Types.TIME); + ps.setObject(1, Time.valueOf("12:15:12"), Types.TIME); assertEquals(1, ps.executeUpdate()); - ps.setObject(1, "22:12:1", java.sql.Types.TIME); + ps.setObject(1, "22:12:1", Types.TIME); assertEquals(1, ps.executeUpdate()); - ps.setObject(1, "8:46:44", java.sql.Types.TIME); + ps.setObject(1, "8:46:44", Types.TIME); assertEquals(1, ps.executeUpdate()); - ps.setObject(1, "5:1:2-03", java.sql.Types.TIME); + ps.setObject(1, "5:1:2-03", Types.TIME); assertEquals(1, ps.executeUpdate()); - ps.setObject(1, "23:59:59+11", java.sql.Types.TIME); + ps.setObject(1, "23:59:59+11", Types.TIME); assertEquals(1, ps.executeUpdate()); // Need to let the test know this one has extra test cases. @@ -195,6 +198,7 @@ public void testSetTime() throws SQLException { /* * Helper for the TimeTests. It tests what should be in the db */ + @SuppressWarnings("deprecation") private void timeTest() throws SQLException { Statement st = con.createStatement(); ResultSet rs; @@ -264,12 +268,12 @@ private void timeTest() throws SQLException { assertEquals(makeTime(tmpTime.getHours(), tmpTime.getMinutes(), tmpTime.getSeconds()), t); } - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); } - private Time makeTime(int h, int m, int s) { + private static Time makeTime(int h, int m, int s) { return Time.valueOf(TestUtil.fix(h, 2) + ":" + TestUtil.fix(m, 2) + ":" + TestUtil.fix(s, 2)); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/TimestampTest.java b/src/test/java/org/postgresql/test/jdbc2/TimestampTest.java index 25dae65..5d3f92c 100644 --- a/src/test/java/org/postgresql/test/jdbc2/TimestampTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/TimestampTest.java @@ -5,11 +5,11 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.PGStatement; import org.postgresql.core.BaseConnection; @@ -17,10 +17,14 @@ import org.postgresql.jdbc.TimestampUtils; import org.postgresql.test.TestUtil; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -38,10 +42,12 @@ /* * Test get/setTimestamp for both timestamp with time zone and timestamp without time zone datatypes - * TODO: refactor to a property-based testing or paremeterized testing somehow so adding new times + * TODO: refactor to a property-based testing or parameterized testing somehow so adding new times * don't require to add constants and setters/getters. JUnit 5 would probably help here. */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") +@Isolated("Uses TimeZone.setDefault") public class TimestampTest extends BaseTest4 { public TimestampTest(BinaryMode binaryMode) { @@ -50,29 +56,43 @@ public TimestampTest(BinaryMode binaryMode) { private TimeZone currentTZ; - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, TSWTZ_TABLE, "ts timestamp with time zone"); + TestUtil.createTable(con, TSWOTZ_TABLE, "ts timestamp without time zone"); + TestUtil.createTable(con, DATE_TABLE, "ts date"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, TSWTZ_TABLE); + TestUtil.dropTable(con, TSWOTZ_TABLE); + TestUtil.dropTable(con, DATE_TABLE); + } + } + @Override public void setUp() throws Exception { super.setUp(); - TestUtil.createTable(con, TSWTZ_TABLE, "ts timestamp with time zone"); - TestUtil.createTable(con, TSWOTZ_TABLE, "ts timestamp without time zone"); - TestUtil.createTable(con, DATE_TABLE, "ts date"); + TestUtil.execute(con, "TRUNCATE " + TSWTZ_TABLE); + TestUtil.execute(con, "TRUNCATE " + TSWOTZ_TABLE); + TestUtil.execute(con, "TRUNCATE " + DATE_TABLE); currentTZ = TimeZone.getDefault(); } @Override public void tearDown() throws SQLException { - TestUtil.dropTable(con, TSWTZ_TABLE); - TestUtil.dropTable(con, TSWOTZ_TABLE); - TestUtil.dropTable(con, DATE_TABLE); TimeZone.setDefault(currentTZ); super.tearDown(); } @@ -179,6 +199,7 @@ private void runInfinityTests(String table, long value) throws SQLException { * value (don't use setTimestamp) then see that we get back the same value from getTimestamp */ @Test + @SuppressWarnings("deprecation") public void testGetTimestampWTZ() throws SQLException { assumeTrue(TestUtil.haveIntegerDateTimes(con)); @@ -187,14 +208,10 @@ public void testGetTimestampWTZ() throws SQLException { // Insert the three timestamp values in raw pg format for (int i = 0; i < 3; i++) { - assertEquals(1, - stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS1WTZ_PGFORMAT + "'"))); - assertEquals(1, - stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS2WTZ_PGFORMAT + "'"))); - assertEquals(1, - stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS3WTZ_PGFORMAT + "'"))); - assertEquals(1, - stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS4WTZ_PGFORMAT + "'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS1WTZ_PGFORMAT + "'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS2WTZ_PGFORMAT + "'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS3WTZ_PGFORMAT + "'"))); + assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + TS4WTZ_PGFORMAT + "'"))); } assertEquals(1, stmt.executeUpdate(TestUtil.insertSQL(TSWTZ_TABLE, "'" + tsu.toString(null, new Timestamp(tmpDate1.getTime())) + "'"))); @@ -299,6 +316,7 @@ public void testSetTimestampWTZ() throws SQLException { * getTimestamp */ @Test + @SuppressWarnings("deprecation") public void testGetTimestampWOTZ() throws SQLException { assumeTrue(TestUtil.haveIntegerDateTimes(con)); //Refer to #896 @@ -361,8 +379,8 @@ public void testSetTimestampWOTZ() throws SQLException { // With java.sql.Date, java.sql.Time for (java.util.Date date : TEST_DATE_TIMES) { pstmt.setObject(1, date, Types.TIMESTAMP); - assertEquals("insert into TSWOTZ_TABLE via setObject(1, " + date - + ", Types.TIMESTAMP) -> expecting one row inserted", 1, pstmt.executeUpdate()); + assertEquals(1, pstmt.executeUpdate(), "insert into TSWOTZ_TABLE via setObject(1, " + date + + ", Types.TIMESTAMP) -> expecting one row inserted"); } // Fall through helper @@ -563,7 +581,7 @@ private void timestampTestWOTZ() throws SQLException { assertTrue(rs.next()); t = rs.getTimestamp(1); assertNotNull(t); - assertEquals("rs.getTimestamp(1).getTime()", expected.getTime(), t.getTime()); + assertEquals(expected.getTime(), t.getTime(), "rs.getTimestamp(1).getTime()"); } assertTrue(!rs.next()); // end of table. Fail if more entries exist. @@ -572,6 +590,28 @@ private void timestampTestWOTZ() throws SQLException { stmt.close(); } + @Test + public void testJavaTimestampFromSQLTime() throws SQLException { + Statement st = con.createStatement(); + ResultSet rs = st.executeQuery("SELECT '00:00:05.123456'::time as t, '1970-01-01 00:00:05.123456'::timestamp as ts, " + + "'00:00:05.123456 +0300'::time with time zone as tz, '1970-01-01 00:00:05.123456 +0300'::timestamp with time zone as tstz "); + rs.next(); + Timestamp t = rs.getTimestamp("t"); + Timestamp ts = rs.getTimestamp("ts"); + Timestamp tz = rs.getTimestamp("tz"); + + Timestamp tstz = rs.getTimestamp("tstz"); + + Integer desiredNanos = 123456000; + Integer tNanos = t.getNanos(); + Integer tzNanos = tz.getNanos(); + + assertEquals(desiredNanos, tNanos, "Time should be microsecond-accurate"); + assertEquals(desiredNanos, tzNanos, "Time with time zone should be microsecond-accurate"); + assertEquals(ts, t, "Unix epoch timestamp and Time should match"); + assertEquals(tstz, tz, "Unix epoch timestamp with time zone and time with time zone should match"); + } + private static Timestamp getTimestamp(int y, int m, int d, int h, int mn, int se, int f, String tz) { Timestamp result = null; @@ -601,7 +641,7 @@ private static Timestamp getTimestamp(int y, int m, int d, int h, int mn, int se } private static final Timestamp TS1WTZ = - getTimestamp(1950, 2, 7, 15, 0, 0, 100000000, "PST"); + getTimestamp(1950, 2, 7, 15, 0, 0, 100000000, "GMT-08:00"); private static final String TS1WTZ_PGFORMAT = "1950-02-07 15:00:00.1-08"; private static final Timestamp TS2WTZ = diff --git a/src/test/java/org/postgresql/test/jdbc2/TimezoneCachingTest.java b/src/test/java/org/postgresql/test/jdbc2/TimezoneCachingTest.java index 97430e5..745d0d6 100644 --- a/src/test/java/org/postgresql/test/jdbc2/TimezoneCachingTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/TimezoneCachingTest.java @@ -5,17 +5,22 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assumptions.assumeFalse; import org.postgresql.core.BaseConnection; import org.postgresql.jdbc.TimestampUtils; import org.postgresql.test.TestUtil; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.lang.reflect.Field; import java.sql.BatchUpdateException; +import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -27,12 +32,14 @@ import java.util.GregorianCalendar; import java.util.TimeZone; +@Isolated("Uses TimeZone.setDefault") public class TimezoneCachingTest extends BaseTest4 { /** * Test to check the internal cached timezone of a prepared statement is set/cleared as expected. */ @Test + @SuppressWarnings("deprecation") public void testPreparedStatementCachedTimezoneInstance() throws SQLException { Timestamp ts = new Timestamp(2016 - 1900, 0, 31, 0, 0, 0, 0); Date date = new Date(2016 - 1900, 0, 31); @@ -41,90 +48,52 @@ public void testPreparedStatementCachedTimezoneInstance() throws SQLException { PreparedStatement pstmt = null; try { pstmt = con.prepareStatement("INSERT INTO testtz VALUES (?,?)"); - assertEquals( - "Cache never initialized: must be null", - null, getTimeZoneCache(pstmt)); + assertNull(getTimeZoneCache(pstmt), "Cache never initialized: must be null"); pstmt.setInt(1, 1); - assertEquals( - "Cache never initialized: must be null", - null, getTimeZoneCache(pstmt)); + assertNull(getTimeZoneCache(pstmt), "Cache never initialized: must be null"); pstmt.setTimestamp(2, ts); - assertEquals( - "Cache initialized by setTimestamp(xx): must not be null", - tz, getTimeZoneCache(pstmt)); + assertEquals(tz, getTimeZoneCache(pstmt), "Cache initialized by setTimestamp(xx): must not be null"); pstmt.addBatch(); - assertEquals( - "Cache was initialized, addBatch does not change that: must not be null", - tz, getTimeZoneCache(pstmt)); + assertEquals(tz, getTimeZoneCache(pstmt), "Cache was initialized, addBatch does not change that: must not be null"); pstmt.setInt(1, 2); pstmt.setNull(2, java.sql.Types.DATE); - assertEquals( - "Cache was initialized, setNull does not change that: must not be null", - tz, getTimeZoneCache(pstmt)); + assertEquals(tz, getTimeZoneCache(pstmt), "Cache was initialized, setNull does not change that: must not be null"); pstmt.addBatch(); - assertEquals( - "Cache was initialized, addBatch does not change that: must not be null", - tz, getTimeZoneCache(pstmt)); + assertEquals(tz, getTimeZoneCache(pstmt), "Cache was initialized, addBatch does not change that: must not be null"); pstmt.executeBatch(); - assertEquals( - "Cache reset by executeBatch(): must be null", - null, getTimeZoneCache(pstmt)); + assertNull(getTimeZoneCache(pstmt), "Cache reset by executeBatch(): must be null"); pstmt.setInt(1, 3); - assertEquals( - "Cache not initialized: must be null", - null, getTimeZoneCache(pstmt)); + assertNull(getTimeZoneCache(pstmt), "Cache not initialized: must be null"); pstmt.setInt(1, 4); pstmt.setNull(2, java.sql.Types.DATE); - assertEquals( - "Cache was not initialized, setNull does not change that: must be null", - null, getTimeZoneCache(pstmt)); + assertNull(getTimeZoneCache(pstmt), "Cache was not initialized, setNull does not change that: must be null"); pstmt.setTimestamp(2, ts); - assertEquals( - "Cache initialized by setTimestamp(xx): must not be null", - tz, getTimeZoneCache(pstmt)); + assertEquals(tz, getTimeZoneCache(pstmt), "Cache initialized by setTimestamp(xx): must not be null"); pstmt.clearParameters(); - assertEquals( - "Cache was initialized, clearParameters does not change that: must not be null", - tz, getTimeZoneCache(pstmt)); + assertEquals(tz, getTimeZoneCache(pstmt), "Cache was initialized, clearParameters does not change that: must not be null"); pstmt.setInt(1, 5); pstmt.setTimestamp(2, ts); pstmt.addBatch(); pstmt.executeBatch(); pstmt.close(); pstmt = con.prepareStatement("UPDATE testtz SET col2 = ? WHERE col1 = 1"); - assertEquals( - "Cache not initialized: must be null", - null, getTimeZoneCache(pstmt)); + assertNull(getTimeZoneCache(pstmt), "Cache not initialized: must be null"); pstmt.setDate(1, date); - assertEquals( - "Cache initialized by setDate(xx): must not be null", - tz, getTimeZoneCache(pstmt)); + assertEquals(tz, getTimeZoneCache(pstmt), "Cache initialized by setDate(xx): must not be null"); pstmt.execute(); - assertEquals( - "Cache reset by execute(): must be null", - null, getTimeZoneCache(pstmt)); + assertNull(getTimeZoneCache(pstmt), "Cache reset by execute(): must be null"); pstmt.setDate(1, date); - assertEquals( - "Cache initialized by setDate(xx): must not be null", - tz, getTimeZoneCache(pstmt)); + assertEquals(tz, getTimeZoneCache(pstmt), "Cache initialized by setDate(xx): must not be null"); pstmt.executeUpdate(); - assertEquals( - "Cache reset by executeUpdate(): must be null", - null, getTimeZoneCache(pstmt)); + assertNull(getTimeZoneCache(pstmt), "Cache reset by executeUpdate(): must be null"); pstmt.setTime(1, time); - assertEquals( - "Cache initialized by setTime(xx): must not be null", - tz, getTimeZoneCache(pstmt)); + assertEquals(tz, getTimeZoneCache(pstmt), "Cache initialized by setTime(xx): must not be null"); pstmt.close(); pstmt = con.prepareStatement("SELECT * FROM testtz WHERE col2 = ?"); pstmt.setDate(1, date); - assertEquals( - "Cache initialized by setDate(xx): must not be null", - tz, getTimeZoneCache(pstmt)); + assertEquals(tz, getTimeZoneCache(pstmt), "Cache initialized by setDate(xx): must not be null"); pstmt.executeQuery(); - assertEquals( - "Cache reset by executeQuery(): must be null", - null, getTimeZoneCache(pstmt)); + assertNull(getTimeZoneCache(pstmt), "Cache reset by executeQuery(): must be null"); } finally { TestUtil.closeQuietly(pstmt); } @@ -134,6 +103,7 @@ public void testPreparedStatementCachedTimezoneInstance() throws SQLException { * Test to check the internal cached timezone of a prepared statement is used as expected. */ @Test + @SuppressWarnings("deprecation") public void testPreparedStatementCachedTimezoneUsage() throws SQLException { Timestamp ts = new Timestamp(2016 - 1900, 0, 31, 0, 0, 0, 0); Statement stmt = null; @@ -236,6 +206,7 @@ public void testPreparedStatementCachedTimezoneUsage() throws SQLException { * Test to check the internal cached timezone of a result set is set/cleared as expected. */ @Test + @SuppressWarnings("deprecation") public void testResultSetCachedTimezoneInstance() throws SQLException { Timestamp ts = new Timestamp(2016 - 1900, 0, 31, 0, 0, 0, 0); TimeZone tz = TimeZone.getDefault(); @@ -251,28 +222,26 @@ public void testResultSetCachedTimezoneInstance() throws SQLException { stmt = con.createStatement(); rs = stmt.executeQuery("SELECT col1, col2 FROM testtz"); rs.next(); - assertEquals("Cache never initialized: must be null", null, getTimeZoneCache(rs)); + assertNull(getTimeZoneCache(rs), "Cache never initialized: must be null"); rs.getInt(1); - assertEquals("Cache never initialized: must be null", null, getTimeZoneCache(rs)); + assertNull(getTimeZoneCache(rs), "Cache never initialized: must be null"); rs.getTimestamp(2); - assertEquals("Cache initialized by getTimestamp(x): must not be null", - tz, getTimeZoneCache(rs)); + assertEquals(tz, getTimeZoneCache(rs), "Cache initialized by getTimestamp(x): must not be null"); rs.close(); rs = stmt.executeQuery("SELECT col1, col2 FROM testtz"); rs.next(); rs.getInt(1); - assertEquals("Cache never initialized: must be null", null, getTimeZoneCache(rs)); + assertNull(getTimeZoneCache(rs), "Cache never initialized: must be null"); rs.getObject(2); - assertEquals("Cache initialized by getObject(x) on a DATE column: must not be null", - tz, getTimeZoneCache(rs)); + assertEquals(tz, getTimeZoneCache(rs), "Cache initialized by getObject(x) on a DATE column: must not be null"); rs.close(); rs = stmt.executeQuery("SELECT col1, col2 FROM testtz"); rs.next(); - assertEquals("Cache should NOT be set", null, getTimeZoneCache(rs)); + assertNull(getTimeZoneCache(rs), "Cache should NOT be set"); rs.getInt(1); - assertEquals("Cache never initialized: must be null", null, getTimeZoneCache(rs)); + assertNull(getTimeZoneCache(rs), "Cache never initialized: must be null"); rs.getDate(2); - assertEquals("Cache initialized by getDate(x): must not be null", tz, getTimeZoneCache(rs)); + assertEquals(tz, getTimeZoneCache(rs), "Cache initialized by getDate(x): must not be null"); rs.close(); } finally { TestUtil.closeQuietly(rs); @@ -285,6 +254,7 @@ public void testResultSetCachedTimezoneInstance() throws SQLException { * Test to check the internal cached timezone of a result set is used as expected. */ @Test + @SuppressWarnings("deprecation") public void testResultSetCachedTimezoneUsage() throws SQLException { Statement stmt = null; PreparedStatement pstmt = null; @@ -307,43 +277,25 @@ public void testResultSetCachedTimezoneUsage() throws SQLException { rs = stmt.executeQuery("SELECT col1, col2 FROM testtz"); rs.next(); rs.getInt(1); - assertEquals( - "Current TZ is tz1, empty cache to be initialized to tz1 => retrieve in tz1, timestamps must be equal", - ts1, rs.getTimestamp(2)); + assertEquals(ts1, rs.getTimestamp(2), "Current TZ is tz1, empty cache to be initialized to tz1 => retrieve in tz1, timestamps must be equal"); rs.close(); rs = stmt.executeQuery("SELECT col1, col2 FROM testtz"); rs.next(); rs.getInt(1); TimeZone.setDefault(tz2); - assertEquals( - "Current TZ is tz2, empty cache to be initialized to tz2 => retrieve in tz2, timestamps cannot be equal", - ts2, rs.getTimestamp(2)); - assertEquals( - "Explicit tz1 calendar, so timestamps must be equal", - ts1, rs.getTimestamp(2, c1)); - assertEquals( - "Cache was initialized to tz2, so timestamps cannot be equal", - ts2, rs.getTimestamp(2)); + assertEquals(ts2, rs.getTimestamp(2), "Current TZ is tz2, empty cache to be initialized to tz2 => retrieve in tz2, timestamps cannot be equal"); + assertEquals(ts1, rs.getTimestamp(2, c1), "Explicit tz1 calendar, so timestamps must be equal"); + assertEquals(ts2, rs.getTimestamp(2), "Cache was initialized to tz2, so timestamps cannot be equal"); TimeZone.setDefault(tz1); - assertEquals( - "Cache was initialized to tz2, so timestamps cannot be equal", - ts2, rs.getTimestamp(2)); + assertEquals(ts2, rs.getTimestamp(2), "Cache was initialized to tz2, so timestamps cannot be equal"); rs.close(); rs = stmt.executeQuery("SELECT col1, col2 FROM testtz"); rs.next(); rs.getInt(1); - assertEquals( - "Explicit tz2 calendar, so timestamps cannot be equal", - ts2, rs.getTimestamp(2, c2)); - assertEquals( - "Current TZ is tz1, empty cache to be initialized to tz1 => retrieve in tz1, timestamps must be equal", - ts1, rs.getTimestamp(2)); - assertEquals( - "Explicit tz2 calendar, so timestamps cannot be equal", - ts2, rs.getTimestamp(2, c2)); - assertEquals( - "Explicit tz2 calendar, so timestamps must be equal", - ts1, rs.getTimestamp(2, c1)); + assertEquals(ts2, rs.getTimestamp(2, c2), "Explicit tz2 calendar, so timestamps cannot be equal"); + assertEquals(ts1, rs.getTimestamp(2), "Current TZ is tz1, empty cache to be initialized to tz1 => retrieve in tz1, timestamps must be equal"); + assertEquals(ts2, rs.getTimestamp(2, c2), "Explicit tz2 calendar, so timestamps cannot be equal"); + assertEquals(ts1, rs.getTimestamp(2, c1), "Explicit tz2 calendar, so timestamps must be equal"); rs.close(); } finally { TimeZone.setDefault(null); @@ -353,7 +305,7 @@ public void testResultSetCachedTimezoneUsage() throws SQLException { } } - private void checkTimestamp(String checkText, Statement stmt, Timestamp ts, TimeZone tz) + private static void checkTimestamp(String checkText, Statement stmt, Timestamp ts, TimeZone tz) throws SQLException { TimeZone prevTz = TimeZone.getDefault(); TimeZone.setDefault(tz); @@ -362,10 +314,10 @@ private void checkTimestamp(String checkText, Statement stmt, Timestamp ts, Time Timestamp dbTs = rs.getTimestamp(1); rs.close(); TimeZone.setDefault(prevTz); - assertEquals(checkText, ts, dbTs); + assertEquals(ts, dbTs, checkText); } - private TimeZone getTimeZoneCache(Object stmt) { + private static TimeZone getTimeZoneCache(Object stmt) { try { Field defaultTimeZoneField = stmt.getClass().getDeclaredField("defaultTimeZone"); defaultTimeZoneField.setAccessible(true); @@ -375,22 +327,29 @@ private TimeZone getTimeZoneCache(Object stmt) { return null; } + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "testtz", "col1 INTEGER, col2 TIMESTAMP"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "testtz"); + } + } + /* Set up the fixture for this test case: a connection to a database with a table for this test. */ + @SuppressWarnings("deprecation") public void setUp() throws Exception { super.setUp(); TimestampUtils timestampUtils = ((BaseConnection) con).getTimestampUtils(); - Assume.assumeFalse("If connection has fast access to TimeZone.getDefault," - + " then no cache is needed", timestampUtils.hasFastDefaultTimeZone()); - /* Drop the test table if it already exists for some reason. It is - not an error if it doesn't exist. */ - TestUtil.createTable(con, "testtz", "col1 INTEGER, col2 TIMESTAMP"); - } - - // Tear down the fixture for this test case. - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "testtz"); - super.tearDown(); + assumeFalse(timestampUtils.hasFastDefaultTimeZone(), "If connection has fast access to TimeZone.getDefault," + + " then no cache is needed"); + TestUtil.execute(con, "TRUNCATE testtz"); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java b/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java index 9b4047c..2bc80f5 100644 --- a/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/TimezoneTest.java @@ -5,16 +5,19 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.sql.Connection; import java.sql.Date; @@ -32,8 +35,8 @@ import java.util.TimeZone; /** - *

          Tests for time and date types with calendars involved. TimestampTest was melting my brain, so I - * started afresh. -O

          + * Tests for time and date types with calendars involved. TimestampTest was melting my brain, so I + * started afresh. -O * *

          Conversions that this code tests:

          * @@ -51,6 +54,7 @@ * *

          (this matches what we must support per JDBC 3.0, tables B-5 and B-6)

          */ +@Isolated("Uses TimeZone.setDefault") public class TimezoneTest { private static final int DAY = 24 * 3600 * 1000; private static final TimeZone saveTZ = TimeZone.getDefault(); @@ -65,10 +69,10 @@ public class TimezoneTest { // server timezone: GMT+0300 // test timezones: GMT+0000 GMT+0100 GMT+0300 GMT+1300 GMT-0500 - private Calendar cUTC; - private Calendar cGMT03; - private Calendar cGMT05; - private Calendar cGMT13; + private final Calendar cUTC; + private final Calendar cGMT03; + private final Calendar cGMT05; + private final Calendar cGMT13; public TimezoneTest() { TimeZone tzUTC = TimeZone.getTimeZone("UTC"); // +0000 always @@ -82,15 +86,29 @@ public TimezoneTest() { cGMT13 = Calendar.getInstance(tzGMT13); } - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "testtimezone", + "seq int4, tstz timestamp with time zone, ts timestamp without time zone, t time without time zone, tz time with time zone, d date"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "testtimezone"); + } + } + + @BeforeEach + void setUp() throws Exception { // We must change the default TZ before establishing the connection. // Arbitrary timezone that doesn't match our test timezones TimeZone.setDefault(TimeZone.getTimeZone("GMT+01")); connect(); - TestUtil.createTable(con, "testtimezone", - "seq int4, tstz timestamp with time zone, ts timestamp without time zone, t time without time zone, tz time with time zone, d date"); + TestUtil.execute(con, "TRUNCATE testtimezone"); // This is not obvious, but the "gmt-3" timezone is actually 3 hours *ahead* of GMT // so will produce +03 timestamptz output @@ -105,17 +123,16 @@ private void connect() throws Exception { con = TestUtil.openDB(p); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { // System.err.println("++++++ TESTS END (" + getName() + ") ++++++"); TimeZone.setDefault(saveTZ); - TestUtil.dropTable(con, "testtimezone"); TestUtil.closeDB(con); } @Test - public void testGetTimestamp() throws Exception { + void getTimestamp() throws Exception { con.createStatement().executeUpdate( "INSERT INTO testtimezone(tstz,ts,t,tz,d) VALUES('2005-01-01 15:00:00 +0300', '2005-01-01 15:00:00', '15:00:00', '15:00:00 +0300', '2005-01-01')"); @@ -144,7 +161,7 @@ public void testGetTimestamp() throws Exception { ts = rs.getTimestamp(1, cGMT13); // Represents an instant in time, timezone is irrelevant. assertEquals(1104580800000L, ts.getTime()); // 2005-01-01 12:00:00 UTC str = rs.getString(1); - assertEquals("tstz -> getString" + format, "2005-01-01 15:00:00+03", str); + assertEquals("2005-01-01 15:00:00+03", str, "tstz -> getString" + format); // timestamp: 2005-01-01 15:00:00 ts = rs.getTimestamp(2); // Convert timestamp to +0100 @@ -158,7 +175,7 @@ public void testGetTimestamp() throws Exception { ts = rs.getTimestamp(2, cGMT13); // Convert timestamp to +1300 assertEquals(1104544800000L, ts.getTime()); // 2005-01-01 15:00:00 +1300 str = rs.getString(2); - assertEquals("ts -> getString" + format, "2005-01-01 15:00:00", str); + assertEquals("2005-01-01 15:00:00", str, "ts -> getString" + format); // time: 15:00:00 ts = rs.getTimestamp(3); @@ -172,7 +189,7 @@ public void testGetTimestamp() throws Exception { ts = rs.getTimestamp(3, cGMT13); assertEquals(7200000L, ts.getTime()); // 1970-01-01 15:00:00 +1300 str = rs.getString(3); - assertEquals("time -> getString" + format, "15:00:00", str); + assertEquals("15:00:00", str, "time -> getString" + format); // timetz: 15:00:00+03 ts = rs.getTimestamp(4); @@ -191,7 +208,7 @@ public void testGetTimestamp() throws Exception { // 1970-01-01 15:00:00 +0300 -> 1970-01-02 01:00:00 +1300 assertEquals(43200000L, ts.getTime()); str = rs.getString(4); - assertEquals("timetz -> getString" + format, "15:00:00+03", str); + assertEquals("15:00:00+03", str, "timetz -> getString" + format); // date: 2005-01-01 ts = rs.getTimestamp(5); @@ -205,15 +222,15 @@ public void testGetTimestamp() throws Exception { ts = rs.getTimestamp(5, cGMT13); assertEquals(1104490800000L, ts.getTime()); // 2005-01-01 00:00:00 +1300 str = rs.getString(5); - assertEquals("date -> getString" + format, "2005-01-01", str); + assertEquals("2005-01-01", str, "date -> getString" + format); - assertTrue(!rs.next()); + assertFalse(rs.next()); ps.close(); } } @Test - public void testGetDate() throws Exception { + void getDate() throws Exception { con.createStatement().executeUpdate( "INSERT INTO testtimezone(tstz,ts,d) VALUES('2005-01-01 15:00:00 +0300', '2005-01-01 15:00:00', '2005-01-01')"); @@ -263,13 +280,13 @@ public void testGetDate() throws Exception { d = rs.getDate(3, cGMT13); // 2005-01-01 00:00:00 +1300 assertEquals(1104490800000L, d.getTime()); - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); } } @Test - public void testGetTime() throws Exception { + void getTime() throws Exception { con.createStatement().executeUpdate( "INSERT INTO testtimezone(tstz,ts,t,tz) VALUES('2005-01-01 15:00:00 +0300', '2005-01-01 15:00:00', '15:00:00', '15:00:00 +0300')"); @@ -345,7 +362,7 @@ public void testGetTime() throws Exception { * not accept timestamp with time zone style input on these servers. */ @Test - public void testSetTimestampOnTime() throws Exception { + void setTimestampOnTime() throws Exception { // Pre-7.4 servers cannot convert timestamps with timezones to times. for (int i = 0; i < PREPARE_THRESHOLD; i++) { con.createStatement().execute("delete from testtimezone"); @@ -412,13 +429,13 @@ public void testSetTimestampOnTime() throws Exception { assertEquals(seq++, rs.getInt(1)); assertEquals(normalizeTimeOfDayPart(instantTime, cGMT13), rs.getTimestamp(2, cGMT13)); - assertTrue(!rs.next()); + assertFalse(rs.next()); ps.close(); } } @Test - public void testSetTimestamp() throws Exception { + void setTimestamp() throws Exception { for (int i = 0; i < PREPARE_THRESHOLD; i++) { con.createStatement().execute("delete from testtimezone"); PreparedStatement insertTimestamp = @@ -539,13 +556,13 @@ public void testSetTimestamp() throws Exception { assertEquals(normalizeTimeOfDayPart(instantTime, cGMT13), rs.getTimestamp(4, cGMT13)); assertEquals(instantDateGMT13, rs.getTimestamp(5, cGMT13)); - assertTrue(!rs.next()); + assertFalse(rs.next()); ps.close(); } } @Test - public void testSetDate() throws Exception { + void setDate() throws Exception { for (int i = 0; i < PREPARE_THRESHOLD; i++) { con.createStatement().execute("delete from testtimezone"); PreparedStatement insertTimestamp = @@ -649,13 +666,13 @@ public void testSetDate() throws Exception { assertEquals(dGMT13, rs.getDate(3, cGMT13)); assertEquals(dGMT13, rs.getDate(4, cGMT13)); - assertTrue(!rs.next()); + assertFalse(rs.next()); ps.close(); } } @Test - public void testSetTime() throws Exception { + void setTime() throws Exception { for (int i = 0; i < PREPARE_THRESHOLD; i++) { con.createStatement().execute("delete from testtimezone"); PreparedStatement insertTimestamp = @@ -747,13 +764,13 @@ public void testSetTime() throws Exception { assertEquals(tGMT13, rs.getTime(2, cGMT13)); assertEquals(tGMT13, rs.getTime(3, cGMT13)); - assertTrue(!rs.next()); + assertFalse(rs.next()); ps.close(); } } @Test - public void testHalfHourTimezone() throws Exception { + void halfHourTimezone() throws Exception { Statement stmt = con.createStatement(); stmt.execute("SET TimeZone = 'GMT+3:30'"); for (int i = 0; i < PREPARE_THRESHOLD; i++) { @@ -766,7 +783,7 @@ public void testHalfHourTimezone() throws Exception { } @Test - public void testTimezoneWithSeconds() throws SQLException { + void timezoneWithSeconds() throws SQLException { Statement stmt = con.createStatement(); stmt.execute("SET TimeZone = 'Europe/Paris'"); for (int i = 0; i < PREPARE_THRESHOLD; i++) { @@ -781,43 +798,43 @@ public void testTimezoneWithSeconds() throws SQLException { } @Test - public void testLocalTimestampsInNonDSTZones() throws Exception { + void localTimestampsInNonDSTZones() throws Exception { for (int i = -12; i <= 13; i++) { localTimestamps(String.format("GMT%02d", i)); } } @Test - public void testLocalTimestampsInAfricaCasablanca() throws Exception { + void localTimestampsInAfricaCasablanca() throws Exception { localTimestamps("Africa/Casablanca"); // It is something like GMT+0..GMT+1 } @Test - public void testLocalTimestampsInAtlanticAzores() throws Exception { + void localTimestampsInAtlanticAzores() throws Exception { localTimestamps("Atlantic/Azores"); // It is something like GMT-1..GMT+0 } @Test - public void testLocalTimestampsInEuropeMoscow() throws Exception { + void localTimestampsInEuropeMoscow() throws Exception { localTimestamps("Europe/Moscow"); // It is something like GMT+3..GMT+4 for 2000s } @Test - public void testLocalTimestampsInPacificApia() throws Exception { + void localTimestampsInPacificApia() throws Exception { localTimestamps("Pacific/Apia"); // It is something like GMT+13..GMT+14 } @Test - public void testLocalTimestampsInPacificNiue() throws Exception { + void localTimestampsInPacificNiue() throws Exception { localTimestamps("Pacific/Niue"); // It is something like GMT-11..GMT-11 } @Test - public void testLocalTimestampsInAmericaAdak() throws Exception { + void localTimestampsInAmericaAdak() throws Exception { localTimestamps("America/Adak"); // It is something like GMT-10..GMT-9 } - private String setTimeTo00_00_00(String timestamp) { + private static String setTimeTo00_00_00(String timestamp) { return timestamp.substring(0, 11) + "00:00:00"; } @@ -875,23 +892,20 @@ public void localTimestamps(String timeZone) throws Exception { expectedTimestamp.setTime(sdf.parse(testDate)); assertEquals( - "getTimestamp: " + testDate + ", transfer format: " + (i == 0 ? "text" : "binary") - + ", timeZone: " + timeZone, - sdf.format(expectedTimestamp.getTimeInMillis()), sdf.format(getTimestamp)); + sdf.format(expectedTimestamp.getTimeInMillis()), sdf.format(getTimestamp), "getTimestamp: " + testDate + ", transfer format: " + (i == 0 ? "text" : "binary") + + ", timeZone: " + timeZone); assertEquals( - "getString: " + testDate + ", transfer format: " + (i == 0 ? "text" : "binary") - + ", timeZone: " + timeZone, - sdf.format(expectedTimestamp.getTimeInMillis()), sdf.format(sdf.parse(getString))); + sdf.format(expectedTimestamp.getTimeInMillis()), sdf.format(sdf.parse(getString)), "getString: " + testDate + ", transfer format: " + (i == 0 ? "text" : "binary") + + ", timeZone: " + timeZone); expectedTimestamp.set(Calendar.HOUR_OF_DAY, 0); expectedTimestamp.set(Calendar.MINUTE, 0); expectedTimestamp.set(Calendar.SECOND, 0); assertEquals( - "TIMESTAMP -> getDate: " + testDate + ", transfer format: " + (i == 0 ? "text" : "binary") - + ", timeZone: " + timeZone, - sdf.format(expectedTimestamp.getTimeInMillis()), sdf.format(getDate)); + sdf.format(expectedTimestamp.getTimeInMillis()), sdf.format(getDate), "TIMESTAMP -> getDate: " + testDate + ", transfer format: " + (i == 0 ? "text" : "binary") + + ", timeZone: " + timeZone); String expectedDateFromDateColumn = setTimeTo00_00_00(testDate); if ("Atlantic/Azores".equals(timeZone) && testDate.startsWith("2000-03-26")) { @@ -903,9 +917,8 @@ public void localTimestamps(String timeZone) throws Exception { } assertEquals( - "DATE -> getDate: " + expectedDateFromDateColumn + ", transfer format: " + (i == 0 ? "text" : "binary") - + ", timeZone: " + timeZone, - expectedDateFromDateColumn, sdf.format(getDateFromDateColumn)); + expectedDateFromDateColumn, sdf.format(getDateFromDateColumn), "DATE -> getDate: " + expectedDateFromDateColumn + ", transfer format: " + (i == 0 ? "text" : "binary") + + ", timeZone: " + timeZone); expectedTimestamp.setTime(sdf.parse(testDate)); expectedTimestamp.set(Calendar.YEAR, 1970); @@ -913,9 +926,8 @@ public void localTimestamps(String timeZone) throws Exception { expectedTimestamp.set(Calendar.DAY_OF_MONTH, 1); assertEquals( - "getTime: " + testDate + ", transfer format: " + (i == 0 ? "text" : "binary") - + ", timeZone: " + timeZone, - sdf.format(expectedTimestamp.getTimeInMillis()), sdf.format(getTime)); + sdf.format(expectedTimestamp.getTimeInMillis()), sdf.format(getTime), "getTime: " + testDate + ", transfer format: " + (i == 0 ? "text" : "binary") + + ", timeZone: " + timeZone); } rs.close(); @@ -928,20 +940,20 @@ public void localTimestamps(String timeZone) throws Exception { * @param query The query to run. * @param correct The correct answers in UTC time zone as formatted by backend. */ - private void checkDatabaseContents(String query, String[] correct) throws Exception { + private static void checkDatabaseContents(String query, String[] correct) throws Exception { checkDatabaseContents(query, new String[][]{correct}); } - private void checkDatabaseContents(String query, String[][] correct) throws Exception { + private static void checkDatabaseContents(String query, String[][] correct) throws Exception { Connection con2 = TestUtil.openDB(); Statement s = con2.createStatement(); assertFalse(s.execute("set time zone 'UTC'")); assertTrue(s.execute(query)); ResultSet rs = s.getResultSet(); - for (int j = 0; j < correct.length; ++j) { + for (int j = 0; j < correct.length; j++) { assertTrue(rs.next()); - for (int i = 0; i < correct[j].length; ++i) { - assertEquals("On row " + (j + 1), correct[j][i], rs.getString(i + 1)); + for (int i = 0; i < correct[j].length; i++) { + assertEquals(correct[j][i], rs.getString(i + 1), "On row " + (j + 1)); } } assertFalse(rs.next()); @@ -955,13 +967,13 @@ private void checkDatabaseContents(String query, String[][] correct) throws Exce * * @param t The time of day. Must be within -24 and + 24 hours of epoc. * @param tz The timezone to normalize to. - * @return the Time nomralized to 0 to 24 hours of epoc adjusted with given timezone. + * @return the Time normalized to 0 to 24 hours of epoc adjusted with given timezone. */ - private Timestamp normalizeTimeOfDayPart(Timestamp t, Calendar tz) { + private static Timestamp normalizeTimeOfDayPart(Timestamp t, Calendar tz) { return new Timestamp(normalizeTimeOfDayPart(t.getTime(), tz.getTimeZone())); } - private long normalizeTimeOfDayPart(long t, TimeZone tz) { + private static long normalizeTimeOfDayPart(long t, TimeZone tz) { long millis = t; long low = -tz.getOffset(millis); long high = low + DAY; diff --git a/src/test/java/org/postgresql/test/jdbc2/TransactionStateTest.java b/src/test/java/org/postgresql/test/jdbc2/TransactionStateTest.java new file mode 100644 index 0000000..25779d4 --- /dev/null +++ b/src/test/java/org/postgresql/test/jdbc2/TransactionStateTest.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2026, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.postgresql.core.BaseConnection; +import org.postgresql.core.TransactionState; +import org.postgresql.jdbc.AutoSave; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; +import java.util.stream.Stream; + +/** + * Tests for {@link TransactionState} transitions as reported by + * {@link org.postgresql.core.QueryExecutor#getTransactionState()}. + */ +class TransactionStateTest extends BaseTest4 { + + private TransactionState getTransactionState() throws SQLException { + return con.unwrap(BaseConnection.class).getTransactionState(); + } + + private boolean isAutoSaveAlways() throws SQLException { + return con.unwrap(BaseConnection.class).getQueryExecutor().getAutoSave() == AutoSave.ALWAYS; + } + + /** + * Returns the expected state after a query error inside a transaction. + * With {@code autosave=always} the driver automatically rolls back to a savepoint, + * so the transaction stays OPEN instead of entering FAILED. + */ + private TransactionState expectedStateAfterError() throws SQLException { + return isAutoSaveAlways() ? TransactionState.OPEN : TransactionState.FAILED; + } + + @Test + void initialStateIsIdle() throws Exception { + assertEquals(TransactionState.IDLE, getTransactionState(), + "a fresh connection should have IDLE transaction state"); + } + + @Test + void idleBeforeFirstQueryWithAutoCommitFalse() throws Exception { + con.setAutoCommit(false); + // BEGIN is deferred until the first query, so state should still be IDLE + assertEquals(TransactionState.IDLE, getTransactionState(), + "setAutoCommit(false) should not change transaction state before first query"); + } + + @Test + void openAfterQueryWithAutoCommitFalse() throws Exception { + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + } + assertEquals(TransactionState.OPEN, getTransactionState(), + "transaction state should be OPEN after executing a query with autoCommit=false"); + } + + static Stream startTransactionCommands() { + return Stream.of( + "BEGIN", + "START TRANSACTION", + "START TRANSACTION READ ONLY" + ); + } + + @ParameterizedTest + @MethodSource("startTransactionCommands") + void openAfterSqlBegin(String sql) throws Exception { + try (Statement stmt = con.createStatement()) { + stmt.execute(sql); + } + assertEquals(TransactionState.OPEN, getTransactionState(), + () -> "transaction state should be OPEN after " + sql); + } + + @ParameterizedTest + @ValueSource(strings = {"COMMIT", "ROLLBACK"}) + void idleAfterSqlEndTransaction(String sql) throws Exception { + try (Statement stmt = con.createStatement()) { + stmt.execute("BEGIN"); + stmt.execute(sql); + } + assertEquals(TransactionState.IDLE, getTransactionState(), + () -> "transaction state should be IDLE after " + sql); + } + + @Test + void idleAfterJdbcCommit() throws Exception { + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + } + con.commit(); + assertEquals(TransactionState.IDLE, getTransactionState(), + "transaction state should be IDLE after Connection.commit()"); + } + + @Test + void idleAfterJdbcRollback() throws Exception { + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + } + con.rollback(); + assertEquals(TransactionState.IDLE, getTransactionState(), + "transaction state should be IDLE after Connection.rollback()"); + } + + @Test + void idleAfterSetAutoCommitTrue() throws Exception { + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + } + assertEquals(TransactionState.OPEN, getTransactionState()); + con.setAutoCommit(true); + assertEquals(TransactionState.IDLE, getTransactionState(), + "transaction state should be IDLE after setAutoCommit(true)"); + } + + @Test + void failedAfterErrorInTransaction() throws Exception { + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + try { + stmt.execute("SELECT * FROM nonexistent_table_xyz_12345"); + } catch (SQLException expected) { + // expected + } + } + assertEquals(expectedStateAfterError(), getTransactionState(), + "transaction state after an error in a transaction"); + } + + @Test + void idleAfterRollbackFromFailed() throws Exception { + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + try { + stmt.execute("SELECT * FROM nonexistent_table_xyz_12345"); + } catch (SQLException expected) { + // expected + } + } + assertEquals(expectedStateAfterError(), getTransactionState()); + con.rollback(); + assertEquals(TransactionState.IDLE, getTransactionState(), + "transaction state should be IDLE after rollback"); + } + + @Test + void idleAfterErrorWithAutoCommitTrue() throws Exception { + // With autoCommit=true each statement runs in its own implicit transaction, + // so a failed statement should leave the connection IDLE (ReadyForQuery 'I'), + // not FAILED + try (Statement stmt = con.createStatement()) { + try { + stmt.execute("SELECT * FROM nonexistent_table_xyz_12345"); + } catch (SQLException expected) { + // expected + } + } + assertEquals(TransactionState.IDLE, getTransactionState(), + "error with autoCommit=true should leave state IDLE, not FAILED"); + } + + @Test + void idleAfterCommitFromFailed() throws Exception { + // PostgreSQL treats COMMIT in an aborted transaction as a transaction end + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + try { + stmt.execute("SELECT * FROM nonexistent_table_xyz_12345"); + } catch (SQLException expected) { + // expected + } + } + assertEquals(expectedStateAfterError(), getTransactionState()); + con.commit(); + assertEquals(TransactionState.IDLE, getTransactionState(), + "transaction state should be IDLE after commit()"); + } + + @Test + void openAfterRollbackToSavepointFromFailed() throws Exception { + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + Savepoint sp = con.setSavepoint("sp1"); + try { + stmt.execute("SELECT * FROM nonexistent_table_xyz_12345"); + } catch (SQLException expected) { + // expected + } + assertEquals(expectedStateAfterError(), getTransactionState()); + con.rollback(sp); + } + assertEquals(TransactionState.OPEN, getTransactionState(), + "ROLLBACK TO SAVEPOINT should move back to OPEN"); + } + + @Test + void idleAfterCommitWhileIdleWithAutoCommitFalse() throws Exception { + con.setAutoCommit(false); + // No queries executed, so transaction state is IDLE (BEGIN not sent yet) + assertEquals(TransactionState.IDLE, getTransactionState()); + con.commit(); + assertEquals(TransactionState.IDLE, getTransactionState(), + "commit() while IDLE with autoCommit=false should keep IDLE"); + } + + @Test + void idleAfterRollbackWhileIdleWithAutoCommitFalse() throws Exception { + con.setAutoCommit(false); + assertEquals(TransactionState.IDLE, getTransactionState()); + con.rollback(); + assertEquals(TransactionState.IDLE, getTransactionState(), + "rollback() while IDLE with autoCommit=false should keep IDLE"); + } + + @Test + void idleAfterSetAutoCommitTrueFromFailed() throws Exception { + // setAutoCommit(true) routes through commit() via PgConnection.setAutoCommit, + // which is a different path than calling commit() directly on a FAILED transaction + con.setAutoCommit(false); + try (Statement stmt = con.createStatement()) { + stmt.execute("SELECT 1"); + try { + stmt.execute("SELECT * FROM nonexistent_table_xyz_12345"); + } catch (SQLException expected) { + // expected + } + } + assertEquals(expectedStateAfterError(), getTransactionState()); + con.setAutoCommit(true); + assertEquals(TransactionState.IDLE, getTransactionState(), + "transaction state should be IDLE after setAutoCommit(true)"); + } +} diff --git a/src/test/java/org/postgresql/test/jdbc2/TypeCacheDLLStressTest.java b/src/test/java/org/postgresql/test/jdbc2/TypeCacheDLLStressTest.java index 1402f8a..d1e0453 100644 --- a/src/test/java/org/postgresql/test/jdbc2/TypeCacheDLLStressTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/TypeCacheDLLStressTest.java @@ -7,7 +7,9 @@ import org.postgresql.test.TestUtil; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.ResultSet; @@ -37,10 +39,18 @@ protected void updateProperties(Properties props) { } } - @Override - public void setUp() throws Exception { - super.setUp(); - TestUtil.createTable(con, "create_and_drop_table", "user_id serial PRIMARY KEY"); + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "create_and_drop_table", "user_id serial PRIMARY KEY"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "create_and_drop_table"); + } } @Override diff --git a/src/test/java/org/postgresql/test/jdbc2/UpdateableResultTest.java b/src/test/java/org/postgresql/test/jdbc2/UpdateableResultTest.java index 7c471d4..902373d 100644 --- a/src/test/java/org/postgresql/test/jdbc2/UpdateableResultTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/UpdateableResultTest.java @@ -5,20 +5,22 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.PGConnection; import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; import java.io.ByteArrayInputStream; import java.io.StringReader; @@ -34,58 +36,78 @@ import java.sql.Types; import java.util.TimeZone; +@Isolated("Uses TimeZone.setDefault") public class UpdateableResultTest extends BaseTest4 { - @Override - public void setUp() throws Exception { - super.setUp(); - TestUtil.createTable(con, "updateable", - "id int primary key, name text, notselected text, ts timestamp with time zone, intarr int[]"); - TestUtil.createTable(con, "hasdate", "id int primary key, dt date unique, name text"); - TestUtil.createTable(con, "unique_null_constraint", "u1 int unique, name1 text"); - TestUtil.createTable(con, "uniquekeys", "id int unique not null, id2 int unique, dt date"); - TestUtil.createTable(con, "partialunique", "subject text, target text, success boolean"); - TestUtil.execute("CREATE UNIQUE INDEX tests_success_constraint ON partialunique (subject, target) WHERE success", con); - TestUtil.createTable(con, "second", "id1 int primary key, name1 text"); - TestUtil.createTable(con, "primaryunique", "id int primary key, name text unique not null, dt date"); - TestUtil.createTable(con, "serialtable", "gen_id serial primary key, name text"); - TestUtil.createTable(con, "compositepktable", "gen_id serial, name text, dec_id serial"); - TestUtil.execute( "alter sequence compositepktable_dec_id_seq increment by 10; alter sequence compositepktable_dec_id_seq restart with 10", con); - TestUtil.execute( "alter table compositepktable add primary key ( gen_id, dec_id )", con); - TestUtil.createTable(con, "stream", "id int primary key, asi text, chr text, bin bytea"); - TestUtil.createTable(con, "multicol", "id1 int not null, id2 int not null, val text"); - TestUtil.createTable(con, "nopkmulticol", "id1 int not null, id2 int not null, val text"); - TestUtil.createTable(con, "booltable", "id int not null primary key, b boolean default false"); - TestUtil.execute("insert into booltable (id) values (1)", con); - TestUtil.execute("insert into uniquekeys(id, id2, dt) values (1, 2, now())", con); - - Statement st2 = con.createStatement(); - // create pk for multicol table - st2.execute("ALTER TABLE multicol ADD CONSTRAINT multicol_pk PRIMARY KEY (id1, id2)"); - // put some dummy data into second - st2.execute("insert into second values (1,'anyvalue' )"); - st2.close(); - TestUtil.execute("insert into unique_null_constraint values (1, 'dave')", con); - TestUtil.execute("insert into unique_null_constraint values (null, 'unknown')", con); - TestUtil.execute("insert into primaryunique values (1, 'dave', now())", con); + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "updateable", + "id int primary key, name text, notselected text, ts timestamp with time zone, intarr int[]"); + TestUtil.createTable(con, "hasdate", "id int primary key, dt date unique, name text"); + TestUtil.createTable(con, "unique_null_constraint", "u1 int unique, name1 text"); + TestUtil.createTable(con, "uniquekeys", "id int unique not null, id2 int unique, dt date"); + TestUtil.createTable(con, "partialunique", "subject text, target text, success boolean"); + TestUtil.execute(con, "CREATE UNIQUE INDEX tests_success_constraint ON partialunique (subject, target) WHERE success"); + TestUtil.createTable(con, "second", "id1 int primary key, name1 text"); + TestUtil.createTable(con, "primaryunique", "id int primary key, name text unique not null, dt date"); + TestUtil.createTable(con, "serialtable", "gen_id serial primary key, name text"); + TestUtil.createTable(con, "compositepktable", "gen_id serial, name text, dec_id serial"); + TestUtil.execute(con, "alter sequence compositepktable_dec_id_seq increment by 10; alter sequence compositepktable_dec_id_seq restart with 10"); + TestUtil.execute(con, "alter table compositepktable add primary key ( gen_id, dec_id )"); + TestUtil.createTable(con, "stream", "id int primary key, asi text, chr text, bin bytea"); + TestUtil.createTable(con, "multicol", "id1 int not null, id2 int not null, val text"); + TestUtil.execute(con, "ALTER TABLE multicol ADD CONSTRAINT multicol_pk PRIMARY KEY (id1, id2)"); + TestUtil.createTable(con, "nopkmulticol", "id1 int not null, id2 int not null, val text"); + TestUtil.createTable(con, "booltable", "id int not null primary key, b boolean default false"); + } + } + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "updateable"); + TestUtil.dropTable(con, "second"); + TestUtil.dropTable(con, "serialtable"); + TestUtil.dropTable(con, "compositepktable"); + TestUtil.dropTable(con, "stream"); + TestUtil.dropTable(con, "multicol"); + TestUtil.dropTable(con, "nopkmulticol"); + TestUtil.dropTable(con, "booltable"); + TestUtil.dropTable(con, "unique_null_constraint"); + TestUtil.dropTable(con, "hasdate"); + TestUtil.dropTable(con, "uniquekeys"); + TestUtil.dropTable(con, "partialunique"); + TestUtil.dropTable(con, "primaryunique"); + } } @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "updateable"); - TestUtil.dropTable(con, "second"); - TestUtil.dropTable(con, "serialtable"); - TestUtil.dropTable(con, "compositepktable"); - TestUtil.dropTable(con, "stream"); - TestUtil.dropTable(con, "nopkmulticol"); - TestUtil.dropTable(con, "booltable"); - TestUtil.dropTable(con, "unique_null_constraint"); - TestUtil.dropTable(con, "hasdate"); - TestUtil.dropTable(con, "uniquekeys"); - TestUtil.dropTable(con, "partialunique"); - TestUtil.dropTable(con, "primaryunique"); - super.tearDown(); + public void setUp() throws Exception { + super.setUp(); + TestUtil.execute(con, "TRUNCATE updateable CASCADE"); + TestUtil.execute(con, "TRUNCATE hasdate CASCADE"); + TestUtil.execute(con, "TRUNCATE unique_null_constraint"); + TestUtil.execute(con, "TRUNCATE uniquekeys CASCADE"); + TestUtil.execute(con, "TRUNCATE partialunique"); + TestUtil.execute(con, "TRUNCATE second CASCADE"); + TestUtil.execute(con, "TRUNCATE primaryunique CASCADE"); + TestUtil.execute(con, "TRUNCATE serialtable CASCADE"); + TestUtil.execute(con, "TRUNCATE compositepktable CASCADE"); + TestUtil.execute(con, "ALTER SEQUENCE compositepktable_gen_id_seq RESTART WITH 1"); + TestUtil.execute(con, "ALTER SEQUENCE compositepktable_dec_id_seq RESTART WITH 10"); + TestUtil.execute(con, "TRUNCATE stream CASCADE"); + TestUtil.execute(con, "TRUNCATE multicol CASCADE"); + TestUtil.execute(con, "TRUNCATE nopkmulticol"); + TestUtil.execute(con, "TRUNCATE booltable CASCADE"); + TestUtil.execute(con, "ALTER SEQUENCE serialtable_gen_id_seq RESTART WITH 1"); + + TestUtil.execute(con, "insert into booltable (id) values (1)"); + TestUtil.execute(con, "insert into uniquekeys(id, id2, dt) values (1, 2, now())"); + TestUtil.execute(con, "insert into second values (1,'anyvalue' )"); + TestUtil.execute(con, "insert into unique_null_constraint values (1, 'dave')"); + TestUtil.execute(con, "insert into unique_null_constraint values (null, 'unknown')"); + TestUtil.execute(con, "insert into primaryunique values (1, 'dave', now())"); } @Test @@ -154,7 +176,7 @@ public void testCancelRowUpdates() throws Exception { st.close(); } - private void checkPositioning(ResultSet rs) throws SQLException { + private static void checkPositioning(ResultSet rs) throws SQLException { try { rs.getInt(1); fail("Can't use an incorrectly positioned result set."); @@ -308,7 +330,6 @@ public void testUpdateTimestamp() throws SQLException { @Test public void testUpdateStreams() throws SQLException, UnsupportedEncodingException { - assumeByteaSupported(); String string = "Hello"; byte[] bytes = new byte[]{0, '\\', (byte) 128, (byte) 255}; @@ -464,7 +485,7 @@ public void testUpdateable() throws SQLException { @Test public void testUpdateDate() throws Exception { Date testDate = Date.valueOf("2021-01-01"); - TestUtil.execute("insert into hasdate values (1,'2021-01-01'::date)", con); + TestUtil.execute(con, "insert into hasdate values (1,'2021-01-01'::date)"); con.setAutoCommit(false); String sql = "SELECT * FROM hasdate where id=1"; ResultSet rs = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, @@ -693,7 +714,7 @@ public void testMultiColumnUpdate() throws Exception { @Test public void simpleAndUpdateableSameQuery() throws Exception { PGConnection unwrap = con.unwrap(PGConnection.class); - Assume.assumeNotNull(unwrap); + assumeTrue(unwrap != null, "con.unwrap(PGConnection.class) should not be null"); int prepareThreshold = unwrap.getPrepareThreshold(); String sql = "select * from second where id1=?"; for (int i = 0; i <= prepareThreshold; i++) { @@ -705,9 +726,9 @@ public void simpleAndUpdateableSameQuery() throws Exception { rs = ps.executeQuery(); rs.next(); String name1 = rs.getString("name1"); - Assert.assertEquals("anyvalue", name1); + assertEquals("anyvalue", name1); int id1 = rs.getInt("id1"); - Assert.assertEquals(1, id1); + assertEquals(1, id1); } finally { TestUtil.closeQuietly(rs); TestUtil.closeQuietly(ps); @@ -723,9 +744,9 @@ public void simpleAndUpdateableSameQuery() throws Exception { rs = ps.executeQuery(); rs.next(); String name1 = rs.getString("name1"); - Assert.assertEquals("anyvalue", name1); + assertEquals("anyvalue", name1); int id1 = rs.getInt("id1"); - Assert.assertEquals(1, id1); + assertEquals(1, id1); rs.updateString("name1", "updatedValue"); rs.updateRow(); } finally { @@ -751,8 +772,7 @@ public void testUpdateBoolean() throws Exception { @Test public void testOidUpdatable() throws Exception { - Connection privilegedCon = TestUtil.openPrivilegedDB(); - try { + try (Connection privilegedCon = TestUtil.openPrivilegedDB()) { Statement st = privilegedCon.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = st.executeQuery("SELECT oid,* FROM pg_class WHERE relname = 'pg_class'"); @@ -762,8 +782,6 @@ public void testOidUpdatable() throws Exception { rs.updateRow(); rs.close(); st.close(); - } finally { - privilegedCon.close(); } } @@ -778,8 +796,7 @@ public void testUniqueWithNullableColumnsNotUpdatable() throws Exception { rs.updateString("name1", "bob"); fail("Should have failed since unique column u1 is nullable"); } catch (SQLException ex) { - assertEquals("No eligible primary or unique key found for table unique_null_constraint.", - ex.getMessage()); + assertEquals("No eligible primary or unique key found for table unique_null_constraint.", ex.getMessage()); } rs.close(); st.close(); @@ -832,8 +849,8 @@ public void testUniqueWithNullAndNotNullableColumnUpdateable() throws Exception ResultSet rs = st.executeQuery("SELECT id, id2, dt from uniquekeys"); assertTrue(rs.next()); assertTrue(rs.first()); - id = rs.getInt(("id")); - id2 = rs.getInt(("id2")); + id = rs.getInt("id"); + id2 = rs.getInt("id2"); rs.updateDate("dt", Date.valueOf("1999-01-01")); rs.updateRow(); rs.close(); @@ -853,7 +870,7 @@ public void testUniqueWithNotNullableColumnUpdateable() throws Exception { ResultSet rs = st.executeQuery("SELECT id, dt from uniquekeys"); assertTrue(rs.next()); assertTrue(rs.first()); - id = rs.getInt(("id")); + id = rs.getInt("id"); rs.updateDate("dt", Date.valueOf("1999-01-01")); rs.updateRow(); rs.close(); @@ -876,8 +893,7 @@ public void testUniqueWithNullableColumnNotUpdateable() throws Exception { rs.updateDate("dt", Date.valueOf("1999-01-01")); fail("Should have failed since id2 is nullable column"); } catch (SQLException ex) { - assertEquals("No eligible primary or unique key found for table uniquekeys.", - ex.getMessage()); + assertEquals("No eligible primary or unique key found for table uniquekeys.", ex.getMessage()); } rs.close(); st.close(); @@ -894,8 +910,7 @@ public void testNoUniqueNotUpdateable() throws SQLException { rs.updateDate("dt", Date.valueOf("1999-01-01")); fail("Should have failed since no UK/PK are in the select statement"); } catch (SQLException ex) { - assertEquals("No eligible primary or unique key found for table uniquekeys.", - ex.getMessage()); + assertEquals("No eligible primary or unique key found for table uniquekeys.", ex.getMessage()); } rs.close(); st.close(); diff --git a/src/test/java/org/postgresql/test/jdbc2/UpsertTest.java b/src/test/java/org/postgresql/test/jdbc2/UpsertTest.java index f257dfe..5814db8 100644 --- a/src/test/java/org/postgresql/test/jdbc2/UpsertTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/UpsertTest.java @@ -5,15 +5,14 @@ package org.postgresql.test.jdbc2; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -25,16 +24,16 @@ /** * Tests {@code INSERT .. ON CONFLICT} introduced in PostgreSQL 9.5. */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class UpsertTest extends BaseTest4 { public UpsertTest(BinaryMode binaryMode, ReWriteBatchedInserts rewrite) { setBinaryMode(binaryMode); setReWriteBatchedInserts(rewrite); } - @Parameterized.Parameters(name = "binary = {0}, reWriteBatchedInserts = {1}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { for (ReWriteBatchedInserts rewrite : ReWriteBatchedInserts.values()) { ids.add(new Object[]{binaryMode, rewrite}); @@ -71,32 +70,28 @@ protected int executeUpdate(String sql) throws SQLException { public void testUpsertDoNothingConflict() throws SQLException { int count = executeUpdate( "INSERT INTO test_statement(i, t) VALUES (42, '42') ON CONFLICT DO NOTHING"); - assertEquals("insert on CONFLICT DO NOTHING should report 0 modified rows on CONFLICT", - 0, count); + assertEquals(0, count, "insert on CONFLICT DO NOTHING should report 0 modified rows on CONFLICT"); } @Test public void testUpsertDoNothingNoConflict() throws SQLException { int count = executeUpdate( "INSERT INTO test_statement(i, t) VALUES (43, '43') ON CONFLICT DO NOTHING"); - assertEquals("insert on conflict DO NOTHING should report 1 modified row on plain insert", - 1, count); + assertEquals(1, count, "insert on conflict DO NOTHING should report 1 modified row on plain insert"); } @Test public void testUpsertDoUpdateConflict() throws SQLException { int count = executeUpdate( "INSERT INTO test_statement(i, t) VALUES (42, '42') ON CONFLICT(i) DO UPDATE SET t='43'"); - assertEquals("insert ON CONFLICT DO UPDATE should report 1 modified row on CONFLICT", - 1, count); + assertEquals(1, count, "insert ON CONFLICT DO UPDATE should report 1 modified row on CONFLICT"); } @Test public void testUpsertDoUpdateNoConflict() throws SQLException { int count = executeUpdate( "INSERT INTO test_statement(i, t) VALUES (43, '43') ON CONFLICT(i) DO UPDATE SET t='43'"); - assertEquals("insert on conflict do update should report 1 modified row on plain insert", - 1, count); + assertEquals(1, count, "insert on conflict do update should report 1 modified row on plain insert"); } @Test @@ -142,7 +137,7 @@ public void testMultiValuedUpsertBatch() throws SQLException { ResultSet rs = st.executeQuery("select count(*) from test_statement where i between 50 and 53"); rs.next(); - Assert.assertEquals("test_statement should have 4 rows with 'i' of 50..53", 4, rs.getInt(1)); + assertEquals(4, rs.getInt(1), "test_statement should have 4 rows with 'i' of 50..53"); } finally { TestUtil.closeQuietly(ps); } diff --git a/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceFailoverUrlsTest.java b/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceFailoverUrlsTest.java index 71d185c..244b7a7 100644 --- a/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceFailoverUrlsTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceFailoverUrlsTest.java @@ -5,50 +5,52 @@ package org.postgresql.test.jdbc2.optional; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.postgresql.ds.common.BaseDataSource; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import javax.naming.NamingException; /** -* tests that failover urls survive the parse/rebuild roundtrip with and without specific ports -*/ -public class BaseDataSourceFailoverUrlsTest { + * tests that failover urls survive the parse/rebuild roundtrip with and without specific ports + */ +class BaseDataSourceFailoverUrlsTest { private static final String DEFAULT_PORT = "5432"; @Test - public void testFullDefault() throws ClassNotFoundException, NamingException, IOException { + void fullDefault() throws ClassNotFoundException, NamingException, IOException { roundTripFromUrl("jdbc:postgresql://server/database", "jdbc:postgresql://server:" + DEFAULT_PORT + "/database"); } @Test - public void testTwoNoPorts() throws ClassNotFoundException, NamingException, IOException { + void twoNoPorts() throws ClassNotFoundException, NamingException, IOException { roundTripFromUrl("jdbc:postgresql://server1,server2/database", "jdbc:postgresql://server1:" + DEFAULT_PORT + ",server2:" + DEFAULT_PORT + "/database"); } @Test - public void testTwoWithPorts() throws ClassNotFoundException, NamingException, IOException { + void twoWithPorts() throws ClassNotFoundException, NamingException, IOException { roundTripFromUrl("jdbc:postgresql://server1:1234,server2:2345/database", "jdbc:postgresql://server1:1234,server2:2345/database"); } @Test - public void testTwoFirstPort() throws ClassNotFoundException, NamingException, IOException { + void twoFirstPort() throws ClassNotFoundException, NamingException, IOException { roundTripFromUrl("jdbc:postgresql://server1,server2:2345/database", "jdbc:postgresql://server1:" + DEFAULT_PORT + ",server2:2345/database"); } @Test - public void testTwoLastPort() throws ClassNotFoundException, NamingException, IOException { + void twoLastPort() throws ClassNotFoundException, NamingException, IOException { roundTripFromUrl("jdbc:postgresql://server1:2345,server2/database", "jdbc:postgresql://server1:2345,server2:" + DEFAULT_PORT + "/database"); } @Test - public void testNullPorts() { + @SuppressWarnings("deprecation") + void nullPorts() { BaseDataSource bds = newDS(); bds.setDatabaseName("database"); bds.setPortNumbers(null); @@ -58,7 +60,8 @@ public void testNullPorts() { } @Test - public void testEmptyPorts() { + @SuppressWarnings("deprecation") + void emptyPorts() { BaseDataSource bds = newDS(); bds.setDatabaseName("database"); bds.setPortNumbers(new int[0]); @@ -67,7 +70,16 @@ public void testEmptyPorts() { assertEquals(0, bds.getPortNumbers()[0]); } - private BaseDataSource newDS() { + @Test + void wrongNumberOfPorts() { + BaseDataSource bds = newDS(); + bds.setDatabaseName("database"); + bds.setServerNames(new String[]{"localhost", "localhost1"}); + bds.setPortNumbers(new int[]{6432}); + assertThrows(IllegalArgumentException.class, bds::getUrl, "Number of ports not equal to the number of servers should throw an exception"); + } + + private static BaseDataSource newDS() { return new BaseDataSource() { @Override public String getDescription() { @@ -76,7 +88,7 @@ public String getDescription() { }; } - private void roundTripFromUrl(String in, String expected) throws NamingException, ClassNotFoundException, IOException { + private static void roundTripFromUrl(String in, String expected) throws NamingException, ClassNotFoundException, IOException { BaseDataSource bds = newDS(); bds.setUrl(in); diff --git a/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java b/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java index 5f8abef..5635c13 100644 --- a/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceTest.java @@ -5,21 +5,22 @@ package org.postgresql.test.jdbc2.optional; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGConnection; import org.postgresql.ds.common.BaseDataSource; import org.postgresql.test.TestUtil; +import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.test.util.MiniJndiContextFactory; +import org.postgresql.util.PSQLException; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.ResultSet; @@ -38,33 +39,42 @@ * * @author Aaron Mulder (ammulder@chariotsolutions.com) */ -public abstract class BaseDataSourceTest { +public abstract class BaseDataSourceTest extends BaseTest4 { public static final String DATA_SOURCE_JNDI = "BaseDataSource"; - protected Connection con; protected BaseDataSource bds; + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "poolingtest", "id int4 not null primary key, name varchar(50)"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "poolingtest"); + } + } + /** - * Creates a test table using a standard connection (not from a DataSource). + * Sets up seed data using a standard connection (not from a DataSource). */ - @Before + @Override public void setUp() throws Exception { - con = TestUtil.openDB(); - TestUtil.createTable(con, "poolingtest", "id int4 not null primary key, name varchar(50)"); - Statement stmt = con.createStatement(); - stmt.executeUpdate("INSERT INTO poolingtest VALUES (1, 'Test Row 1')"); - stmt.executeUpdate("INSERT INTO poolingtest VALUES (2, 'Test Row 2')"); - TestUtil.closeDB(con); + // TODO: why do we open a separate connection here? Can we reuse this.con? + try (Connection con = TestUtil.openDB()) { + TestUtil.execute(con, "TRUNCATE poolingtest"); + TestUtil.execute(con, "INSERT INTO poolingtest VALUES (1, 'Test Row 1'), (2, 'Test Row 2')"); + } } /** - * Removes the test table using a standard connection (not from a DataSource). + * Cleans up connections. */ - @After - public void tearDown() throws Exception { - TestUtil.closeDB(con); - con = TestUtil.openDB(); - TestUtil.dropTable(con, "poolingtest"); + @Override + public void tearDown() throws SQLException { TestUtil.closeDB(con); } @@ -82,17 +92,16 @@ protected Connection getDataSourceConnection() throws SQLException { * Creates an instance of the current BaseDataSource for testing. Must be customized by each * subclass. */ - protected abstract void initializeDataSource(); + protected abstract void initializeDataSource() throws PSQLException; - public static void setupDataSource(BaseDataSource bds) { + @SuppressWarnings("deprecation") + public static void setupDataSource(BaseDataSource bds) throws PSQLException { bds.setServerName(TestUtil.getServer()); bds.setPortNumber(TestUtil.getPort()); bds.setDatabaseName(TestUtil.getDatabase()); bds.setUser(TestUtil.getUser()); bds.setPassword(TestUtil.getPassword()); bds.setPrepareThreshold(TestUtil.getPrepareThreshold()); - bds.setLoggerLevel(TestUtil.getLogLevel()); - bds.setLoggerFile(TestUtil.getLogFile()); bds.setProtocolVersion(TestUtil.getProtocolVersion()); } @@ -100,7 +109,7 @@ public static void setupDataSource(BaseDataSource bds) { * Test to make sure you can instantiate and configure the appropriate DataSource. */ @Test - public void testCreateDataSource() { + public void testCreateDataSource() throws PSQLException { initializeDataSource(); } @@ -166,13 +175,11 @@ public void testDdlOverConnection() { */ @Test public void testNotPooledConnection() throws SQLException { - con = getDataSourceConnection(); - String name = con.toString(); - con.close(); - con = getDataSourceConnection(); - String name2 = con.toString(); - con.close(); - assertNotEquals(name, name2); + Connection con1 = getDataSourceConnection(); + con1.close(); + Connection con2 = getDataSourceConnection(); + con2.close(); + assertNotSame(con1, con2); } /** @@ -196,7 +203,7 @@ public void testPGConnection() { * mechanisms. Will probably be multiple tests when implemented. */ @Test - public void testJndi() { + public void testJndi() throws PSQLException { initializeDataSource(); BaseDataSource oldbds = bds; String oldurl = bds.getURL(); @@ -204,7 +211,7 @@ public void testJndi() { try { ic.rebind(DATA_SOURCE_JNDI, bds); bds = (BaseDataSource) ic.lookup(DATA_SOURCE_JNDI); - assertNotNull("Got null looking up DataSource from JNDI!", bds); + assertNotNull(bds, "Got null looking up DataSource from JNDI!"); compareJndiDataSource(oldbds, bds); } catch (NamingException e) { fail(e.getMessage()); @@ -212,17 +219,15 @@ public void testJndi() { oldbds = bds; String url = bds.getURL(); testUseConnection(); - assertSame("Test should not have changed DataSource (" + bds + " != " + oldbds + ")!", - oldbds , bds); - assertEquals("Test should not have changed DataSource URL", - oldurl, url); + assertSame(oldbds, bds, "Test should not have changed DataSource (" + bds + " != " + oldbds + ")!"); + assertEquals(oldurl, url, "Test should not have changed DataSource URL"); } /** * Uses the mini-JNDI implementation for testing purposes. */ - protected InitialContext getInitialContext() { - Hashtable env = new Hashtable(); + protected static InitialContext getInitialContext() { + Hashtable env = new Hashtable<>(); env.put(Context.INITIAL_CONTEXT_FACTORY, MiniJndiContextFactory.class.getName()); try { return new InitialContext(env); @@ -236,6 +241,6 @@ protected InitialContext getInitialContext() { * Check whether a DS was dereferenced from JNDI or recreated. */ protected void compareJndiDataSource(BaseDataSource oldbds, BaseDataSource bds) { - assertNotSame("DataSource was dereferenced, should have been serialized or recreated", oldbds, bds); + assertNotSame(oldbds, bds, "DataSource was dereferenced, should have been serialized or recreated"); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/optional/CaseOptimiserDataSourceTest.java b/src/test/java/org/postgresql/test/jdbc2/optional/CaseOptimiserDataSourceTest.java index 60c6283..013c3dc 100644 --- a/src/test/java/org/postgresql/test/jdbc2/optional/CaseOptimiserDataSourceTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/optional/CaseOptimiserDataSourceTest.java @@ -5,22 +5,24 @@ package org.postgresql.test.jdbc2.optional; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.core.BaseConnection; import org.postgresql.ds.common.BaseDataSource; -import org.postgresql.jdbc2.optional.SimpleDataSource; import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLException; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.sql.Statement; /** * DataSource test to ensure the BaseConnection is configured with column sanitiser disabled. @@ -29,28 +31,35 @@ public class CaseOptimiserDataSourceTest { private BaseDataSource bds; protected Connection conn; - @Before - public void setUp() throws SQLException { - Connection conn = getDataSourceConnection(); - assertTrue(conn instanceof BaseConnection); - BaseConnection bc = (BaseConnection) conn; - assertTrue("Expected state [TRUE] of base connection configuration failed test.", - bc.isColumnSanitiserDisabled()); - Statement insert = conn.createStatement(); - TestUtil.createTable(conn, "allmixedup", - "id int primary key, \"DESCRIPTION\" varchar(40), \"fOo\" varchar(3)"); - insert.execute(TestUtil.insertSQL("allmixedup", "1,'mixed case test', 'bar'")); - insert.close(); - conn.close(); + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "allmixedup", + "id int primary key, \"DESCRIPTION\" varchar(40), \"fOo\" varchar(3)"); + } } - @After - public void tearDown() throws SQLException { - Connection conn = getDataSourceConnection(); - Statement drop = conn.createStatement(); - drop.execute("drop table allmixedup"); - drop.close(); - conn.close(); + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "allmixedup"); + } + } + + @BeforeEach + void setUp() throws SQLException { + try (Connection conn = getDataSourceConnection()) { + assertInstanceOf(BaseConnection.class, conn); + BaseConnection bc = (BaseConnection) conn; + assertTrue(bc.isColumnSanitiserDisabled(), + "Expected state [TRUE] of base connection configuration failed test."); + TestUtil.execute(conn, "TRUNCATE allmixedup CASCADE"); + TestUtil.execute(conn, TestUtil.insertSQL("allmixedup", "1,'mixed case test', 'bar'")); + } + } + + @AfterEach + void tearDown() throws SQLException { bds.setDisableColumnSanitiser(false); } @@ -60,7 +69,7 @@ public void tearDown() throws SQLException { * found. */ @Test - public void testDataSourceDisabledSanitiserPropertySucceeds() throws SQLException { + void dataSourceDisabledSanitiserPropertySucceeds() throws SQLException { String label = "FOO"; Connection conn = getDataSourceConnection(); PreparedStatement query = @@ -82,23 +91,23 @@ protected Connection getDataSourceConnection() throws SQLException { return bds.getConnection(); } - protected void initializeDataSource() { + @SuppressWarnings("deprecation") + protected void initializeDataSource() throws PSQLException { if (bds == null) { - bds = new SimpleDataSource(); + bds = new org.postgresql.jdbc2.optional.SimpleDataSource(); setupDataSource(bds); bds.setDisableColumnSanitiser(true); } } - public static void setupDataSource(BaseDataSource bds) { + @SuppressWarnings("deprecation") + public static void setupDataSource(BaseDataSource bds) throws PSQLException { bds.setServerName(TestUtil.getServer()); bds.setPortNumber(TestUtil.getPort()); bds.setDatabaseName(TestUtil.getDatabase()); bds.setUser(TestUtil.getUser()); bds.setPassword(TestUtil.getPassword()); bds.setPrepareThreshold(TestUtil.getPrepareThreshold()); - bds.setLoggerLevel(TestUtil.getLogLevel()); - bds.setLoggerFile(TestUtil.getLogFile()); bds.setProtocolVersion(TestUtil.getProtocolVersion()); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/optional/ConnectionPoolTest.java b/src/test/java/org/postgresql/test/jdbc2/optional/ConnectionPoolTest.java index e5edcbe..9a96b51 100644 --- a/src/test/java/org/postgresql/test/jdbc2/optional/ConnectionPoolTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/optional/ConnectionPoolTest.java @@ -5,17 +5,19 @@ package org.postgresql.test.jdbc2.optional; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.ServerVersion; import org.postgresql.ds.PGConnectionPoolDataSource; -import org.postgresql.jdbc2.optional.ConnectionPool; import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLException; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -40,21 +42,22 @@ * @author Aaron Mulder (ammulder@chariotsolutions.com) */ public class ConnectionPoolTest extends BaseDataSourceTest { - private ArrayList connections = new ArrayList(); + private final ArrayList connections = new ArrayList<>(); /** * Creates and configures a ConnectionPool. */ @Override - protected void initializeDataSource() { + @SuppressWarnings("deprecation") + protected void initializeDataSource() throws PSQLException { if (bds == null) { - bds = new ConnectionPool(); + bds = new org.postgresql.jdbc2.optional.ConnectionPool(); setupDataSource(bds); } } @Override - public void tearDown() throws Exception { + public void tearDown() throws SQLException { for (PooledConnection c : connections) { try { c.close(); @@ -123,8 +126,7 @@ public void testPoolReuse() { String name2 = con.toString(); con.close(); pc.close(); - assertTrue("Physical connection doesn't appear to be reused across PooledConnection wrappers", - name.equals(name2)); + assertEquals(name, name2, "Physical connection doesn't appear to be reused across PooledConnection wrappers"); } catch (SQLException e) { fail(e.getMessage()); } @@ -142,8 +144,7 @@ public void testPoolCloseOldWrapper() { Connection con2 = pc.getConnection(); try { con.createStatement(); - fail( - "Original connection wrapper should be closed when new connection wrapper is generated"); + fail("Original connection wrapper should be closed when new connection wrapper is generated"); } catch (SQLException e) { } con2.close(); @@ -166,9 +167,7 @@ public void testPoolNewWrapper() { Connection con2 = pc.getConnection(); con2.close(); pc.close(); - assertTrue( - "Two calls to PooledConnection.getConnection should not return the same connection wrapper", - con != con2); + assertNotSame(con, con2, "Two calls to PooledConnection.getConnection should not return the same connection wrapper"); } catch (SQLException e) { fail(e.getMessage()); } @@ -278,7 +277,7 @@ public void testAutomaticCloseEvent() { assertEquals(0, cc.getErrorCount()); // Open a 2nd connection, causing the first to be closed. No even should be generated. Connection con2 = pc.getConnection(); - assertTrue("Connection handle was not closed when new handle was opened", con.isClosed()); + assertTrue(con.isClosed(), "Connection handle was not closed when new handle was opened"); assertEquals(1, cc.getCount()); assertEquals(0, cc.getErrorCount()); con2.close(); @@ -299,13 +298,13 @@ public void testIsClosed() { try { PooledConnection pc = getPooledConnection(); con = pc.getConnection(); - assertTrue(!con.isClosed()); + assertFalse(con.isClosed()); con.close(); assertTrue(con.isClosed()); con = pc.getConnection(); Connection con2 = pc.getConnection(); assertTrue(con.isClosed()); - assertTrue(!con2.isClosed()); + assertFalse(con2.isClosed()); con2.close(); assertTrue(con.isClosed()); pc.close(); @@ -323,10 +322,9 @@ public void testBackendIsClosed() throws Exception { try { PooledConnection pc = getPooledConnection(); con = pc.getConnection(); - assertTrue(!con.isClosed()); + assertFalse(con.isClosed()); - Assume.assumeTrue("pg_terminate_backend requires PostgreSQL 8.4+", - TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)); + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4), "pg_terminate_backend requires PostgreSQL 8.4+"); TestUtil.terminateBackend(con); try { @@ -466,8 +464,8 @@ public void connectionErrorOccurred(ConnectionEvent event) { * sees. */ private class CountClose implements ConnectionEventListener { - private int count = 0; - private int errorCount = 0; + private int count; + private int errorCount; @Override public void connectionClosed(ConnectionEvent event) { @@ -493,8 +491,9 @@ public void clear() { } @Test + @SuppressWarnings("deprecation") public void testSerializable() throws IOException, ClassNotFoundException { - ConnectionPool pool = new ConnectionPool(); + org.postgresql.jdbc2.optional.ConnectionPool pool = new org.postgresql.jdbc2.optional.ConnectionPool(); pool.setDefaultAutoCommit(false); pool.setServerName("db.myhost.com"); pool.setDatabaseName("mydb"); @@ -508,7 +507,7 @@ public void testSerializable() throws IOException, ClassNotFoundException { ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); - ConnectionPool pool2 = (ConnectionPool) ois.readObject(); + org.postgresql.jdbc2.optional.ConnectionPool pool2 = (org.postgresql.jdbc2.optional.ConnectionPool) ois.readObject(); assertEquals(pool.isDefaultAutoCommit(), pool2.isDefaultAutoCommit()); assertEquals(pool.getServerName(), pool2.getServerName()); diff --git a/src/test/java/org/postgresql/test/jdbc2/optional/PoolingDataSourceTest.java b/src/test/java/org/postgresql/test/jdbc2/optional/PoolingDataSourceTest.java index 73a9824..04d8be2 100644 --- a/src/test/java/org/postgresql/test/jdbc2/optional/PoolingDataSourceTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/optional/PoolingDataSourceTest.java @@ -5,14 +5,14 @@ package org.postgresql.test.jdbc2.optional; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.ds.common.BaseDataSource; -import org.postgresql.jdbc2.optional.PoolingDataSource; +import org.postgresql.util.PSQLException; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.ResultSet; @@ -28,9 +28,10 @@ public class PoolingDataSourceTest extends BaseDataSourceTest { private static final String DS_NAME = "JDBC 2 SE Test DataSource"; @Override - public void tearDown() throws Exception { - if (bds instanceof PoolingDataSource) { - ((PoolingDataSource) bds).close(); + @SuppressWarnings("deprecation") + public void tearDown() throws SQLException { + if (bds instanceof org.postgresql.jdbc2.optional.PoolingDataSource) { + ((org.postgresql.jdbc2.optional.PoolingDataSource) bds).close(); } super.tearDown(); } @@ -39,13 +40,16 @@ public void tearDown() throws Exception { * Creates and configures a new SimpleDataSource. */ @Override - protected void initializeDataSource() { + @SuppressWarnings("deprecation") + protected void initializeDataSource() throws PSQLException { if (bds == null) { - bds = new PoolingDataSource(); + org.postgresql.jdbc2.optional.PoolingDataSource ds = + new org.postgresql.jdbc2.optional.PoolingDataSource(); + bds = ds; setupDataSource(bds); - ((PoolingDataSource) bds).setDataSourceName(DS_NAME); - ((PoolingDataSource) bds).setInitialConnections(2); - ((PoolingDataSource) bds).setMaxConnections(10); + ds.setDataSourceName(DS_NAME); + ds.setInitialConnections(2); + ds.setMaxConnections(10); } } @@ -60,7 +64,7 @@ public void testNotPooledConnection() throws SQLException { con = getDataSourceConnection(); String name2 = con.toString(); con.close(); - assertEquals("Pooled DS doesn't appear to be pooling connections!", name, name2); + assertEquals(name, name2, "Pooled DS doesn't appear to be pooling connections!"); } /** @@ -68,17 +72,18 @@ public void testNotPooledConnection() throws SQLException { */ @Override protected void compareJndiDataSource(BaseDataSource oldbds, BaseDataSource bds) { - assertSame("DataSource was serialized or recreated, should have been dereferenced", - bds, oldbds); + assertSame(bds, oldbds, "DataSource was serialized or recreated, should have been dereferenced"); } /** * Check that 2 DS instances can't use the same name. */ @Test - public void testCantReuseName() { + @SuppressWarnings("deprecation") + public void testCantReuseName() throws PSQLException { initializeDataSource(); - PoolingDataSource pds = new PoolingDataSource(); + org.postgresql.jdbc2.optional.PoolingDataSource pds = + new org.postgresql.jdbc2.optional.PoolingDataSource(); try { pds.setDataSourceName(DS_NAME); fail("Should have denied 2nd DataSource with same name"); diff --git a/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceTest.java b/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceTest.java index 644d273..2785c44 100644 --- a/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceTest.java @@ -5,10 +5,12 @@ package org.postgresql.test.jdbc2.optional; +import static org.junit.jupiter.api.Assertions.assertThrows; + import org.postgresql.ds.PGSimpleDataSource; -import org.postgresql.jdbc2.optional.SimpleDataSource; +import org.postgresql.util.PSQLException; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Performs the basic tests defined in the superclass. Just adds the configuration logic. @@ -21,17 +23,21 @@ public class SimpleDataSourceTest extends BaseDataSourceTest { * Creates and configures a new SimpleDataSource. */ @Override - protected void initializeDataSource() { + @SuppressWarnings("deprecation") + protected void initializeDataSource() throws PSQLException { if (bds == null) { - bds = new SimpleDataSource(); + bds = new org.postgresql.jdbc2.optional.SimpleDataSource(); setupDataSource(bds); } } - @Test(expected = IllegalArgumentException.class) + @Test public void testTypoPostgresUrl() { PGSimpleDataSource ds = new PGSimpleDataSource(); - // this should fail because the protocol is wrong. - ds.setUrl("jdbc:postgres://localhost:5432/test"); + String url = "jdbc:postgres://localhost:5432/test"; + assertThrows( + IllegalArgumentException.class, + () -> ds.setUrl(url), + () -> "protocols is wrong when calling ds.setUrl(\"" + url + "\")"); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithSetURLTest.java b/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithSetURLTest.java index f9bbd5a..f636cad 100644 --- a/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithSetURLTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithSetURLTest.java @@ -5,14 +5,14 @@ package org.postgresql.test.jdbc2.optional; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.postgresql.Driver.parseURL; import org.postgresql.PGProperty; -import org.postgresql.jdbc2.optional.SimpleDataSource; import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLException; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Properties; @@ -24,11 +24,11 @@ public class SimpleDataSourceWithSetURLTest extends BaseDataSourceTest { * Creates and configures a new SimpleDataSource using setURL method. */ @Override - protected void initializeDataSource() { + @SuppressWarnings("deprecation") + protected void initializeDataSource() throws PSQLException { if (bds == null) { - bds = new SimpleDataSource(); - bds.setURL(String.format("jdbc:postgresql://%s:%d/%s?prepareThreshold=%d&loggerLevel=%s", TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getPrepareThreshold(), - TestUtil.getLogLevel())); + bds = new org.postgresql.jdbc2.optional.SimpleDataSource(); + bds.setURL(String.format("jdbc:postgresql://%s:%d/%s?prepareThreshold=%d", TestUtil.getServer(), TestUtil.getPort(), TestUtil.getDatabase(), TestUtil.getPrepareThreshold())); bds.setUser(TestUtil.getUser()); bds.setPassword(TestUtil.getPassword()); bds.setProtocolVersion(TestUtil.getProtocolVersion()); @@ -46,10 +46,10 @@ public void testGetURL() throws Exception { assertEquals(Integer.toString(TestUtil.getPort()), properties.getProperty(PGProperty.PG_PORT.getName())); assertEquals(TestUtil.getDatabase(), properties.getProperty(PGProperty.PG_DBNAME.getName())); assertEquals(Integer.toString(TestUtil.getPrepareThreshold()), properties.getProperty(PGProperty.PREPARE_THRESHOLD.getName())); - assertEquals(TestUtil.getLogLevel(), properties.getProperty(PGProperty.LOGGER_LEVEL.getName())); } @Test + @SuppressWarnings("deprecation") public void testSetURL() throws Exception { initializeDataSource(); @@ -57,6 +57,5 @@ public void testSetURL() throws Exception { assertEquals(TestUtil.getPort(), bds.getPortNumber()); assertEquals(TestUtil.getDatabase(), bds.getDatabaseName()); assertEquals(TestUtil.getPrepareThreshold(), bds.getPrepareThreshold()); - assertEquals(TestUtil.getLogLevel(), bds.getLoggerLevel()); } } diff --git a/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithUrlTest.java b/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithUrlTest.java index 041900c..2945dff 100644 --- a/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithUrlTest.java +++ b/src/test/java/org/postgresql/test/jdbc2/optional/SimpleDataSourceWithUrlTest.java @@ -5,8 +5,8 @@ package org.postgresql.test.jdbc2.optional; -import org.postgresql.jdbc2.optional.SimpleDataSource; import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLException; /** * Performs the basic tests defined in the superclass. Just adds the configuration logic. @@ -18,12 +18,12 @@ public class SimpleDataSourceWithUrlTest extends BaseDataSourceTest { * Creates and configures a new SimpleDataSource. */ @Override - protected void initializeDataSource() { + @SuppressWarnings("deprecation") + protected void initializeDataSource() throws PSQLException { if (bds == null) { - bds = new SimpleDataSource(); + bds = new org.postgresql.jdbc2.optional.SimpleDataSource(); bds.setUrl("jdbc:postgresql://" + TestUtil.getServer() + ":" + TestUtil.getPort() + "/" - + TestUtil.getDatabase() + "?prepareThreshold=" + TestUtil.getPrepareThreshold() - + "&logLevel=" + TestUtil.getLogLevel()); + + TestUtil.getDatabase() + "?prepareThreshold=" + TestUtil.getPrepareThreshold()); bds.setUser(TestUtil.getUser()); bds.setPassword(TestUtil.getPassword()); bds.setProtocolVersion(TestUtil.getProtocolVersion()); diff --git a/src/test/java/org/postgresql/test/jdbc3/CompositeQueryParseTest.java b/src/test/java/org/postgresql/test/jdbc3/CompositeQueryParseTest.java index dc18731..409ac1c 100644 --- a/src/test/java/org/postgresql/test/jdbc3/CompositeQueryParseTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/CompositeQueryParseTest.java @@ -5,199 +5,199 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.core.NativeQuery; import org.postgresql.core.Parser; import org.postgresql.core.SqlCommandType; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.SQLException; import java.util.List; -public class CompositeQueryParseTest { +class CompositeQueryParseTest { @Test - public void testEmptyQuery() { + void emptyQuery() { assertEquals("", reparse("", true, false, true)); } @Test - public void testWhitespaceQuery() { + void whitespaceQuery() { assertEquals("", reparse(" ", true, false, true)); } @Test - public void testOnlyEmptyQueries() { + void onlyEmptyQueries() { assertEquals("", reparse(";;;; ; \n;\n", true, false, true)); } @Test - public void testSimpleQuery() { + void simpleQuery() { assertEquals("select 1", reparse("select 1", true, false, true)); } @Test - public void testSimpleBind() { + void simpleBind() { assertEquals("select $1", reparse("select ?", true, true, true)); } @Test - public void testUnquotedQuestionmark() { + void unquotedQuestionmark() { assertEquals("select '{\"key\": \"val\"}'::jsonb ? 'key'", reparse("select '{\"key\": \"val\"}'::jsonb ? 'key'", true, false, true)); } @Test - public void testRepeatedQuestionmark() { + void repeatedQuestionmark() { assertEquals("select '{\"key\": \"val\"}'::jsonb ? 'key'", reparse("select '{\"key\": \"val\"}'::jsonb ?? 'key'", true, false, true)); } @Test - public void testQuotedQuestionmark() { + void quotedQuestionmark() { assertEquals("select '?'", reparse("select '?'", true, false, true)); } @Test - public void testDoubleQuestionmark() { + void doubleQuestionmark() { assertEquals("select '?', $1 ?=> $2", reparse("select '?', ? ??=> ?", true, true, true)); } @Test - public void testCompositeBasic() { + void compositeBasic() { assertEquals("select 1;/*cut*/\n select 2", reparse("select 1; select 2", true, false, true)); } @Test - public void testCompositeWithBinds() { + void compositeWithBinds() { assertEquals("select $1;/*cut*/\n select $1", reparse("select ?; select ?", true, true, true)); } @Test - public void testTrailingSemicolon() { + void trailingSemicolon() { assertEquals("select 1", reparse("select 1;", true, false, true)); } @Test - public void testTrailingSemicolonAndSpace() { + void trailingSemicolonAndSpace() { assertEquals("select 1", reparse("select 1; ", true, false, true)); } @Test - public void testMultipleTrailingSemicolons() { + void multipleTrailingSemicolons() { assertEquals("select 1", reparse("select 1;;;", true, false, true)); } @Test - public void testHasReturning() throws SQLException { + void hasReturning() throws SQLException { List queries = Parser.parseJdbcSql("insert into foo (a,b,c) values (?,?,?) RetuRning a", true, true, false, true, true); NativeQuery query = queries.get(0); - assertTrue("The parser should find the word returning", query.command.isReturningKeywordPresent()); + assertTrue(query.command.isReturningKeywordPresent(), "The parser should find the word returning"); queries = Parser.parseJdbcSql("insert into foo (a,b,c) values (?,?,?)", true, true, false, true, true); query = queries.get(0); - assertFalse("The parser should not find the word returning", query.command.isReturningKeywordPresent()); + assertFalse(query.command.isReturningKeywordPresent(), "The parser should not find the word returning"); queries = Parser.parseJdbcSql("insert into foo (a,b,c) values ('returning',?,?)", true, true, false, true, true); query = queries.get(0); - assertFalse("The parser should not find the word returning as it is in quotes ", query.command.isReturningKeywordPresent()); + assertFalse(query.command.isReturningKeywordPresent(), "The parser should not find the word returning as it is in quotes "); } @Test - public void testSelect() throws SQLException { + void select() throws SQLException { List queries; queries = Parser.parseJdbcSql("select 1 as returning from (update table)", true, true, false, true, true); NativeQuery query = queries.get(0); - assertEquals("This is a select ", SqlCommandType.SELECT, query.command.getType()); - assertTrue("Returning is OK here as it is not an insert command ", query.command.isReturningKeywordPresent()); + assertEquals(SqlCommandType.SELECT, query.command.getType(), "This is a select "); + assertTrue(query.command.isReturningKeywordPresent(), "Returning is OK here as it is not an insert command "); } @Test - public void testDelete() throws SQLException { + void delete() throws SQLException { List queries = Parser.parseJdbcSql("DeLeTe from foo where a=1", true, true, false, true, true); NativeQuery query = queries.get(0); - assertEquals("This is a delete command", SqlCommandType.DELETE, query.command.getType()); + assertEquals(SqlCommandType.DELETE, query.command.getType(), "This is a delete command"); } @Test - public void testMultiQueryWithBind() throws SQLException { + void multiQueryWithBind() throws SQLException { // braces around (42) are required to puzzle the parser String sql = "INSERT INTO inttable(a) VALUES (?);SELECT (42)"; - List queries = Parser.parseJdbcSql(sql, true, true, true,true, true); + List queries = Parser.parseJdbcSql(sql, true, true, true, true, true); NativeQuery query = queries.get(0); - assertEquals("query(0) of " + sql, - "INSERT: INSERT INTO inttable(a) VALUES ($1)", - query.command.getType() + ": " + query.nativeSql); + assertEquals("INSERT: INSERT INTO inttable(a) VALUES ($1)", + query.command.getType() + ": " + query.nativeSql, + "query(0) of " + sql); query = queries.get(1); - assertEquals("query(1) of " + sql, - "SELECT: SELECT (42)", - query.command.getType() + ": " + query.nativeSql); + assertEquals("SELECT: SELECT (42)", + query.command.getType() + ": " + query.nativeSql, + "query(1) of " + sql); } @Test - public void testMove() throws SQLException { + void move() throws SQLException { List queries = Parser.parseJdbcSql("MoVe NEXT FROM FOO", true, true, false, true, true); NativeQuery query = queries.get(0); - assertEquals("This is a move command", SqlCommandType.MOVE, query.command.getType()); + assertEquals(SqlCommandType.MOVE, query.command.getType(), "This is a move command"); } @Test - public void testUpdate() throws SQLException { + void update() throws SQLException { List queries; NativeQuery query; queries = Parser.parseJdbcSql("update foo set (a=?,b=?,c=?)", true, true, false, true, true); query = queries.get(0); - assertEquals("This is an UPDATE command", SqlCommandType.UPDATE, query.command.getType()); + assertEquals(SqlCommandType.UPDATE, query.command.getType(), "This is an UPDATE command"); } @Test - public void testInsert() throws SQLException { + void insert() throws SQLException { List queries = Parser.parseJdbcSql("InSeRt into foo (a,b,c) values (?,?,?) returning a", true, true, false, true, true); NativeQuery query = queries.get(0); - assertEquals("This is an INSERT command", SqlCommandType.INSERT, query.command.getType()); + assertEquals(SqlCommandType.INSERT, query.command.getType(), "This is an INSERT command"); queries = Parser.parseJdbcSql("select 1 as insert", true, true, false, true, true); query = queries.get(0); - assertEquals("This is a SELECT command", SqlCommandType.SELECT, query.command.getType()); + assertEquals(SqlCommandType.SELECT, query.command.getType(), "This is a SELECT command"); } @Test - public void testWithSelect() throws SQLException { + void withSelect() throws SQLException { List queries; queries = Parser.parseJdbcSql("with update as (update foo set (a=?,b=?,c=?)) select * from update", true, true, false, true, true); NativeQuery query = queries.get(0); - assertEquals("with ... () select", SqlCommandType.SELECT, query.command.getType()); + assertEquals(SqlCommandType.SELECT, query.command.getType(), "with ... () select"); } @Test - public void testWithInsert() throws SQLException { + void withInsert() throws SQLException { List queries; queries = Parser.parseJdbcSql("with update as (update foo set (a=?,b=?,c=?)) insert into table(select) values(1)", true, true, false, true, true); NativeQuery query = queries.get(0); - assertEquals("with ... () insert", SqlCommandType.INSERT, query.command.getType()); + assertEquals(SqlCommandType.INSERT, query.command.getType(), "with ... () insert"); } @Test - public void testMultipleEmptyQueries() { + void multipleEmptyQueries() { assertEquals("select 1;/*cut*/\n" + "select 2", reparse("select 1; ;\t;select 2", true, false, true)); } @Test - public void testCompositeWithComments() { + void compositeWithComments() { assertEquals("select 1;/*cut*/\n" + "/* noop */;/*cut*/\n" + "select 2", reparse("select 1;/* noop */;select 2", true, false, true)); } - private String reparse(String query, boolean standardConformingStrings, boolean withParameters, + private static String reparse(String query, boolean standardConformingStrings, boolean withParameters, boolean splitStatements) { try { return toString( @@ -207,7 +207,7 @@ private String reparse(String query, boolean standardConformingStrings, boolean } } - private String toString(List queries) { + private static String toString(List queries) { StringBuilder sb = new StringBuilder(); for (NativeQuery query : queries) { if (sb.length() != 0) { diff --git a/src/test/java/org/postgresql/test/jdbc3/CompositeTest.java b/src/test/java/org/postgresql/test/jdbc3/CompositeTest.java index 3e5a120..1fc07db 100644 --- a/src/test/java/org/postgresql/test/jdbc3/CompositeTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/CompositeTest.java @@ -5,9 +5,10 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.PGConnection; import org.postgresql.core.ServerVersion; @@ -15,11 +16,11 @@ import org.postgresql.test.TestUtil; import org.postgresql.util.PGobject; -import org.junit.After; -import org.junit.Assume; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Array; import java.sql.Connection; @@ -27,47 +28,52 @@ import java.sql.ResultSet; import java.sql.SQLException; -public class CompositeTest { +class CompositeTest { private Connection conn; - @BeforeClass - public static void beforeClass() throws Exception { - Connection conn = TestUtil.openDB(); - try { - Assume.assumeTrue("uuid requires PostgreSQL 8.3+", TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3)); - } finally { - conn.close(); + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3), "uuid requires PostgreSQL 8.3+"); + TestUtil.createSchema(conn, "\"Composites\""); + TestUtil.createCompositeType(conn, "simplecompositetest", "i int, d decimal, u uuid"); + TestUtil.createCompositeType(conn, "nestedcompositetest", "t text, s simplecompositetest"); + TestUtil.createCompositeType(conn, "\"Composites\".\"ComplexCompositeTest\"", + "l bigint[], n nestedcompositetest[], s simplecompositetest"); + TestUtil.createTable(conn, "compositetabletest", + "s simplecompositetest, cc \"Composites\".\"ComplexCompositeTest\"[]"); + TestUtil.createTable(conn, "\"Composites\".\"Table\"", + "s simplecompositetest, cc \"Composites\".\"ComplexCompositeTest\"[]"); } } - @Before - public void setUp() throws Exception { + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "\"Composites\".\"Table\""); + TestUtil.dropTable(conn, "compositetabletest"); + TestUtil.dropType(conn, "\"Composites\".\"ComplexCompositeTest\""); + TestUtil.dropType(conn, "nestedcompositetest"); + TestUtil.dropType(conn, "simplecompositetest"); + TestUtil.dropSchema(conn, "\"Composites\""); + } + } + + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); - TestUtil.createSchema(conn, "\"Composites\""); - TestUtil.createCompositeType(conn, "simplecompositetest", "i int, d decimal, u uuid"); - TestUtil.createCompositeType(conn, "nestedcompositetest", "t text, s simplecompositetest"); - TestUtil.createCompositeType(conn, "\"Composites\".\"ComplexCompositeTest\"", - "l bigint[], n nestedcompositetest[], s simplecompositetest"); - TestUtil.createTable(conn, "compositetabletest", - "s simplecompositetest, cc \"Composites\".\"ComplexCompositeTest\"[]"); - TestUtil.createTable(conn, "\"Composites\".\"Table\"", - "s simplecompositetest, cc \"Composites\".\"ComplexCompositeTest\"[]"); + TestUtil.execute(conn, "TRUNCATE compositetabletest CASCADE"); + TestUtil.execute(conn, "TRUNCATE \"Composites\".\"Table\" CASCADE"); } - @After - public void tearDown() throws SQLException { - TestUtil.dropTable(conn, "\"Composites\".\"Table\""); - TestUtil.dropTable(conn, "compositetabletest"); - TestUtil.dropType(conn, "\"Composites\".\"ComplexCompositeTest\""); - TestUtil.dropType(conn, "nestedcompositetest"); - TestUtil.dropType(conn, "simplecompositetest"); - TestUtil.dropSchema(conn, "\"Composites\""); + @AfterEach + void tearDown() throws SQLException { TestUtil.closeDB(conn); } @Test - public void testSimpleSelect() throws SQLException { + void simpleSelect() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("SELECT '(1,2.2,)'::simplecompositetest"); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); @@ -77,7 +83,7 @@ public void testSimpleSelect() throws SQLException { } @Test - public void testComplexSelect() throws SQLException { + void complexSelect() throws SQLException { PreparedStatement pstmt = conn.prepareStatement( "SELECT '(\"{1,2}\",{},\"(1,2.2,)\")'::\"Composites\".\"ComplexCompositeTest\""); ResultSet rs = pstmt.executeQuery(); @@ -88,8 +94,8 @@ public void testComplexSelect() throws SQLException { } @Test - public void testSimpleArgumentSelect() throws SQLException { - Assume.assumeTrue("Skip if running in simple query mode", conn.unwrap(PGConnection.class).getPreferQueryMode() != PreferQueryMode.SIMPLE); + void simpleArgumentSelect() throws SQLException { + assumeTrue(conn.unwrap(PGConnection.class).getPreferQueryMode() != PreferQueryMode.SIMPLE, "Skip if running in simple query mode"); PreparedStatement pstmt = conn.prepareStatement("SELECT ?"); PGobject pgo = new PGobject(); pgo.setType("simplecompositetest"); @@ -102,8 +108,8 @@ public void testSimpleArgumentSelect() throws SQLException { } @Test - public void testComplexArgumentSelect() throws SQLException { - Assume.assumeTrue("Skip if running in simple query mode", conn.unwrap(PGConnection.class).getPreferQueryMode() != PreferQueryMode.SIMPLE); + void complexArgumentSelect() throws SQLException { + assumeTrue(conn.unwrap(PGConnection.class).getPreferQueryMode() != PreferQueryMode.SIMPLE, "Skip if running in simple query mode"); PreparedStatement pstmt = conn.prepareStatement("SELECT ?"); PGobject pgo = new PGobject(); pgo.setType("\"Composites\".\"ComplexCompositeTest\""); @@ -116,7 +122,7 @@ public void testComplexArgumentSelect() throws SQLException { } @Test - public void testCompositeFromTable() throws SQLException { + void compositeFromTable() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("INSERT INTO compositetabletest VALUES(?, ?)"); PGobject pgo1 = new PGobject(); pgo1.setType("public.simplecompositetest"); @@ -151,7 +157,7 @@ public void testCompositeFromTable() throws SQLException { } @Test - public void testNullArrayElement() throws SQLException { + void nullArrayElement() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("SELECT array[NULL, NULL]::compositetabletest[]"); ResultSet rs = pstmt.executeQuery(); @@ -165,7 +171,7 @@ public void testNullArrayElement() throws SQLException { } @Test - public void testTableMetadata() throws SQLException { + void tableMetadata() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("INSERT INTO compositetabletest VALUES(?, ?)"); PGobject pgo1 = new PGobject(); pgo1.setType("public.simplecompositetest"); @@ -185,7 +191,7 @@ public void testTableMetadata() throws SQLException { } @Test - public void testComplexTableNameMetadata() throws SQLException { + void complexTableNameMetadata() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("INSERT INTO \"Composites\".\"Table\" VALUES(?, ?)"); PGobject pgo1 = new PGobject(); pgo1.setType("public.simplecompositetest"); diff --git a/src/test/java/org/postgresql/test/jdbc3/DatabaseMetaDataTest.java b/src/test/java/org/postgresql/test/jdbc3/DatabaseMetaDataTest.java index 904bdbb..0341191 100644 --- a/src/test/java/org/postgresql/test/jdbc3/DatabaseMetaDataTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/DatabaseMetaDataTest.java @@ -5,14 +5,15 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -20,20 +21,20 @@ import java.sql.Statement; import java.sql.Types; -public class DatabaseMetaDataTest { +class DatabaseMetaDataTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); Statement stmt = conn.createStatement(); stmt.execute("CREATE DOMAIN mydom AS int"); stmt.execute("CREATE TABLE domtab (a mydom)"); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { Statement stmt = conn.createStatement(); stmt.execute("DROP TABLE domtab"); stmt.execute("DROP DOMAIN mydom"); @@ -41,16 +42,24 @@ public void tearDown() throws Exception { } @Test - public void testGetColumnsForDomain() throws Exception { + void getColumnsForDomain_whenCatalogArgPercentSign_expectNoResults() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getColumns("%", "%", "domtab", "%"); + assertFalse(rs.next()); + } + + @Test + void getColumnsForDomain() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getColumns(null, "%", "domtab", "%"); assertTrue(rs.next()); assertEquals("a", rs.getString("COLUMN_NAME")); assertEquals(Types.DISTINCT, rs.getInt("DATA_TYPE")); assertEquals("mydom", rs.getString("TYPE_NAME")); assertEquals(Types.INTEGER, rs.getInt("SOURCE_DATA_TYPE")); - assertTrue(!rs.next()); + assertFalse(rs.next()); } } diff --git a/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallIfNoReturnTest.java b/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallIfNoReturnTest.java index de0eda6..ed29cd4 100644 --- a/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallIfNoReturnTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallIfNoReturnTest.java @@ -5,16 +5,16 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGProperty; import org.postgresql.core.ServerVersion; import org.postgresql.jdbc.EscapeSyntaxCallMode; import org.postgresql.util.PSQLState; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.CallableStatement; import java.sql.SQLException; @@ -46,7 +46,7 @@ public void testInvokeFunction() throws Throwable { cs.execute(); fail("Should throw an exception"); } catch (SQLException ex) { - assertEquals(expected.getState(),ex.getSQLState()); + assertEquals(expected.getState(), ex.getSQLState()); } } @@ -62,7 +62,7 @@ public void testInvokeFunctionHavingReturnParameter() throws Throwable { cs.setInt(3, 20); cs.execute(); int ret = cs.getInt(1); - assertTrue("Expected mysumproc(10,20) to return 30 but returned " + ret, ret == 30); + assertEquals(30, ret, "mysumproc(10,20)"); } @Test @@ -81,7 +81,7 @@ public void testInvokeProcedure() throws Throwable { // Expected output: a==1 (param 1), b==10 (param 2) int a = cs.getInt(1); int b = cs.getInt(2); - assertTrue("Expected myioproc() to return output parameter values 1,10 but returned " + a + "," + b, (a == 1 && b == 10)); + assertTrue((a == 1 && b == 10), "Expected myioproc() to return output parameter values 1,10 but returned " + a + "," + b); } } diff --git a/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallTest.java b/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallTest.java index e03ba6c..c258c1e 100644 --- a/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallTest.java @@ -5,9 +5,9 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGProperty; import org.postgresql.core.ServerVersion; @@ -15,7 +15,7 @@ import org.postgresql.test.TestUtil; import org.postgresql.util.PSQLState; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.CallableStatement; import java.sql.SQLException; @@ -46,7 +46,7 @@ public void testInvokeFunction() throws Throwable { cs.execute(); fail("Should throw an exception"); } catch (SQLException ex) { - assertEquals(expected.getState(),ex.getSQLState()); + assertEquals(expected.getState(), ex.getSQLState()); } } @@ -91,7 +91,7 @@ public void testInvokeProcedure() throws Throwable { // Expected output: a==1 (param 1), b==10 (param 2) int a = cs.getInt(1); int b = cs.getInt(2); - assertTrue("Expected myioproc() to return output parameter values 1,10 but returned " + a + "," + b, (a == 1 && b == 10)); + assertTrue((a == 1 && b == 10), "Expected myioproc() to return output parameter values 1,10 but returned " + a + "," + b); } } diff --git a/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeSelectTest.java b/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeSelectTest.java index 8147ec7..8eda461 100644 --- a/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeSelectTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeSelectTest.java @@ -5,16 +5,16 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGProperty; import org.postgresql.core.ServerVersion; import org.postgresql.jdbc.EscapeSyntaxCallMode; import org.postgresql.util.PSQLState; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.CallableStatement; import java.sql.SQLException; @@ -42,7 +42,7 @@ public void testInvokeFunction() throws Throwable { // Expected output: a==1 (param 1), b==10 (param 2) int a = cs.getInt(1); int b = cs.getInt(2); - assertTrue("Expected myiofunc() to return output parameter values 1,10 but returned " + a + "," + b, (a == 1 && b == 10)); + assertTrue((a == 1 && b == 10), "Expected myiofunc() to return output parameter values 1,10 but returned " + a + "," + b); } @Test @@ -56,7 +56,7 @@ public void testInvokeFunctionHavingReturnParameter() throws Throwable { cs.setInt(3, 20); cs.execute(); int ret = cs.getInt(1); - assertTrue("Expected mysumfunc(10,20) to return 30 but returned " + ret, ret == 30); + assertEquals(30, ret, "mysumfunc(10,20)"); } @Test @@ -74,7 +74,7 @@ public void testInvokeProcedure() throws Throwable { cs.execute(); fail("Should throw an exception"); } catch (SQLException ex) { - assertEquals(PSQLState.WRONG_OBJECT_TYPE.getState(),ex.getSQLState()); + assertEquals(PSQLState.WRONG_OBJECT_TYPE.getState(), ex.getSQLState()); } } diff --git a/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java b/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java index 045160a..29374fb 100644 --- a/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/GeneratedKeysTest.java @@ -5,12 +5,13 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.PGStatement; import org.postgresql.core.ServerVersion; @@ -18,10 +19,9 @@ import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.util.PSQLState; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -31,7 +31,8 @@ import java.util.ArrayList; import java.util.Collection; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class GeneratedKeysTest extends BaseTest4 { public enum ReturningInQuery { A("a"), @@ -76,9 +77,8 @@ public GeneratedKeysTest(ReturningInQuery returningInQuery, BinaryMode binaryMod setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "returningInQuery = {0}, binary = {1}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (ReturningInQuery returningInQuery : ReturningInQuery.values()) { for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{returningInQuery, binaryMode}); @@ -125,7 +125,7 @@ private void assert1a2(ResultSet rs) throws SQLException { assertEquals("2", rs.getString(3)); assertEquals(2, rs.getInt("c")); } - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test @@ -135,7 +135,7 @@ public void testStatementUpdateCount() throws SQLException { Statement.RETURN_GENERATED_KEYS); assertEquals(1, stmt.getUpdateCount()); assertNull(stmt.getResultSet()); - assertTrue(!stmt.getMoreResults()); + assertFalse(stmt.getMoreResults()); } @Test @@ -145,7 +145,7 @@ public void testCloseStatementClosesRS() throws SQLException { Statement.RETURN_GENERATED_KEYS); ResultSet rs = stmt.getGeneratedKeys(); stmt.close(); - assertTrue("statement was closed, thus the resultset should be closed as well", rs.isClosed()); + assertTrue(rs.isClosed(), "statement was closed, thus the resultset should be closed as well"); try { rs.next(); fail("Can't operate on a closed result set."); @@ -167,8 +167,7 @@ public void testEmptyRSWithoutReturning() throws SQLException { Statement.NO_GENERATED_KEYS); assertEquals(1, count); if (returningInQuery.columnsReturned() > 0) { - fail( - "A result was returned when none was expected error should happen when executing executeUpdate('... returning ...')"); + fail("A result was returned when none was expected error should happen when executing executeUpdate('... returning ...')"); } } catch (SQLException e) { if (returningInQuery.columnsReturned() > 0 && "0100E".equals(e.getSQLState())) { @@ -178,7 +177,7 @@ public void testEmptyRSWithoutReturning() throws SQLException { throw e; } ResultSet rs = stmt.getGeneratedKeys(); - assertFalse("Statement.NO_GENERATED_KEYS => stmt.getGeneratedKeys() should be empty", rs.next()); + assertFalse(rs.next(), "Statement.NO_GENERATED_KEYS => stmt.getGeneratedKeys() should be empty"); } @Test @@ -193,7 +192,7 @@ public void testMultipleRows() throws SQLException { assertCB1(rs); assertTrue(rs.next()); assertCB2(rs); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test @@ -208,7 +207,7 @@ public void testSerialWorks() throws SQLException { assertEquals(1, rs.getInt(1)); assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test @@ -221,7 +220,7 @@ public void testUpdate() throws SQLException { ResultSet rs = stmt.getGeneratedKeys(); assertTrue(rs.next()); assertCB1(rs); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test @@ -235,24 +234,24 @@ public void testWithInsertInsert() throws SQLException { ResultSet rs = stmt.getGeneratedKeys(); assertTrue(rs.next()); assertCB1(rs); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test public void testWithInsertSelect() throws SQLException { assumeMinimumServerVersion(ServerVersion.v9_1); - Assume.assumeTrue(returningInQuery != ReturningInQuery.NO); + assumeTrue(returningInQuery != ReturningInQuery.NO); Statement stmt = con.createStatement(); int count = stmt.executeUpdate( "WITH x as (INSERT INTO genkeys(a,b,c) VALUES (1, 'a', 2) " + returningClause + ") select * from x", new String[]{"c", "b"}); - assertEquals("rowcount", -1, count); + assertEquals(-1, count, "rowcount"); // TODO: should SELECT produce rows through getResultSet or getGeneratedKeys? ResultSet rs = stmt.getResultSet(); assertTrue(rs.next()); assertCB1(rs); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test @@ -265,7 +264,7 @@ public void testDelete() throws SQLException { ResultSet rs = stmt.getGeneratedKeys(); assertTrue(rs.next()); assertCB1(rs); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test @@ -283,7 +282,7 @@ public void testPSUpdate() throws SQLException { ResultSet rs = ps.getGeneratedKeys(); assertTrue(rs.next()); assertCB1(rs); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test @@ -301,14 +300,14 @@ public void testPSDelete() throws SQLException { ResultSet rs = ps.getGeneratedKeys(); assertTrue(rs.next()); assertCB1(rs); - assertTrue(!rs.next()); + assertFalse(rs.next()); ps.setInt(1, 2); assertEquals(1, ps.executeUpdate()); rs = ps.getGeneratedKeys(); assertTrue(rs.next()); assertCB2(rs); - assertTrue(!rs.next()); + assertFalse(rs.next()); } private void assertCB1(ResultSet rs) throws SQLException { @@ -323,30 +322,26 @@ private void assertCB1(ResultSet rs) throws SQLException { String columnNames = sb.toString(); switch (returningInQuery) { case NO: - assertEquals("Two columns should be returned since returning clause was empty and {c, b} was requested via API", - "c, b", columnNames); + assertEquals("c, b", columnNames, "Two columns should be returned since returning clause was empty and {c, b} was requested via API"); assertEquals(2, rs.getInt(1)); assertEquals("a", rs.getString(2)); assertEquals(2, rs.getInt("c")); assertEquals("a", rs.getString("b")); break; case A: - assertEquals("Just one column should be returned since returning clause was " + returningClause, - "a", columnNames); + assertEquals("a", columnNames, "Just one column should be returned since returning clause was " + returningClause); assertEquals(1, rs.getInt(1)); assertEquals(1, rs.getInt("a")); break; case AB: - assertEquals("Two columns should be returned since returning clause was " + returningClause, - "a, b", columnNames); + assertEquals("a, b", columnNames, "Two columns should be returned since returning clause was " + returningClause); assertEquals(1, rs.getInt(1)); assertEquals("a", rs.getString(2)); assertEquals(1, rs.getInt("a")); assertEquals("a", rs.getString("b")); break; case STAR: - assertEquals("Three columns should be returned since returning clause was " + returningClause, - "a, b, c", columnNames); + assertEquals("a, b, c", columnNames, "Three columns should be returned since returning clause was " + returningClause); assertEquals(1, rs.getInt(1)); assertEquals("a", rs.getString(2)); assertEquals(2, rs.getInt(3)); @@ -362,25 +357,21 @@ private void assertCB1(ResultSet rs) throws SQLException { private void assertCB2(ResultSet rs) throws SQLException { switch (returningInQuery) { case NO: - assertEquals("Two columns should be returned since returning clause was empty and {c, b} was requested via API", - 2, rs.getMetaData().getColumnCount()); + assertEquals(2, rs.getMetaData().getColumnCount(), "Two columns should be returned since returning clause was empty and {c, b} was requested via API"); assertEquals(4, rs.getInt(1)); assertEquals("b", rs.getString(2)); break; case A: - assertEquals("Just one column should be returned since returning clause was " + returningClause, - 1, rs.getMetaData().getColumnCount()); + assertEquals(1, rs.getMetaData().getColumnCount(), "Just one column should be returned since returning clause was " + returningClause); assertEquals(2, rs.getInt(1)); break; case AB: - assertEquals("Two columns should be returned since returning clause was " + returningClause, - 2, rs.getMetaData().getColumnCount()); + assertEquals(2, rs.getMetaData().getColumnCount(), "Two columns should be returned since returning clause was " + returningClause); assertEquals(2, rs.getInt(1)); assertEquals("b", rs.getString(2)); break; case STAR: - assertEquals("Three columns should be returned since returning clause was " + returningClause, - 3, rs.getMetaData().getColumnCount()); + assertEquals(3, rs.getMetaData().getColumnCount(), "Three columns should be returned since returning clause was " + returningClause); assertEquals(2, rs.getInt(1)); assertEquals("b", rs.getString(2)); assertEquals(4, rs.getInt(3)); @@ -409,7 +400,7 @@ public void testGeneratedKeysCleared() throws SQLException { throw e; } rs = stmt.getGeneratedKeys(); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test @@ -422,11 +413,11 @@ public void testBatchGeneratedKeys() throws SQLException { ps.addBatch(); ps.executeBatch(); ResultSet rs = ps.getGeneratedKeys(); - assertTrue("getGeneratedKeys.next() should be non-empty", rs.next()); + assertTrue(rs.next(), "getGeneratedKeys.next() should be non-empty"); assertEquals(1, rs.getInt("a")); assertTrue(rs.next()); assertEquals(2, rs.getInt("a")); - assertTrue(!rs.next()); + assertFalse(rs.next()); } private PreparedStatement prepareSelect() throws SQLException { @@ -449,7 +440,7 @@ private PreparedStatement prepareSelect() throws SQLException { public void selectWithGeneratedKeysViaPreparedExecuteQuery() throws SQLException { PreparedStatement ps = prepareSelect(); ResultSet rs = ps.executeQuery(); - assertFalse("genkeys table is empty, thus rs.next() should return false", rs.next()); + assertFalse(rs.next(), "genkeys table is empty, thus rs.next() should return false"); ps.close(); } @@ -458,7 +449,7 @@ public void selectWithGeneratedKeysViaPreparedExecute() throws SQLException { PreparedStatement ps = prepareSelect(); ps.execute(); ResultSet rs = ps.getResultSet(); - assertFalse("genkeys table is empty, thus rs.next() should return false", rs.next()); + assertFalse(rs.next(), "genkeys table is empty, thus rs.next() should return false"); ps.close(); } @@ -480,8 +471,8 @@ public void selectWithGeneratedKeysViaNonPrepared() throws SQLException { s.execute(sql, returningInQuery.columns); rs = s.getResultSet(); } - assertNotNull("SELECT statement should return results via getResultSet, not getGeneratedKeys", rs); - assertFalse("genkeys table is empty, thus rs.next() should return false", rs.next()); + assertNotNull(rs, "SELECT statement should return results via getResultSet, not getGeneratedKeys"); + assertFalse(rs.next(), "genkeys table is empty, thus rs.next() should return false"); s.close(); } diff --git a/src/test/java/org/postgresql/test/jdbc3/Jdbc3BlobTest.java b/src/test/java/org/postgresql/test/jdbc3/Jdbc3BlobTest.java index 66f3f3c..456d1a8 100644 --- a/src/test/java/org/postgresql/test/jdbc3/Jdbc3BlobTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/Jdbc3BlobTest.java @@ -5,16 +5,18 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.io.InputStream; @@ -33,15 +35,28 @@ public class Jdbc3BlobTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, TABLE, "ID INT PRIMARY KEY, DATA OID"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, TABLE); + } + } + + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); - TestUtil.createTable(conn, TABLE, "ID INT PRIMARY KEY, DATA OID"); conn.setAutoCommit(false); } - @After - public void tearDown() throws SQLException { + @AfterEach + void tearDown() throws SQLException { conn.setAutoCommit(true); try { Statement stmt = conn.createStatement(); @@ -54,8 +69,11 @@ public void tearDown() throws SQLException { } } } finally { - TestUtil.dropTable(conn, TABLE); - TestUtil.closeDB(conn); + try { + TestUtil.execute(conn, "TRUNCATE " + TABLE); + } finally { + TestUtil.closeDB(conn); + } } } @@ -63,7 +81,7 @@ public void tearDown() throws SQLException { * Test the writing and reading of a single byte. */ @Test - public void test1Byte() throws SQLException { + void test1Byte() throws SQLException { byte[] data = {(byte) 'a'}; readWrite(data); } @@ -72,7 +90,7 @@ public void test1Byte() throws SQLException { * Test the writing and reading of a few bytes. */ @Test - public void testManyBytes() throws SQLException { + void manyBytes() throws SQLException { byte[] data = "aaaaaaaaaa".getBytes(); readWrite(data); } @@ -81,7 +99,7 @@ public void testManyBytes() throws SQLException { * Test writing a single byte with an offset. */ @Test - public void test1ByteOffset() throws SQLException { + void test1ByteOffset() throws SQLException { byte[] data = {(byte) 'a'}; readWrite(10, data); } @@ -90,7 +108,7 @@ public void test1ByteOffset() throws SQLException { * Test the writing and reading of a few bytes with an offset. */ @Test - public void testManyBytesOffset() throws SQLException { + void manyBytesOffset() throws SQLException { byte[] data = "aaaaaaaaaa".getBytes(); readWrite(10, data); } @@ -99,7 +117,7 @@ public void testManyBytesOffset() throws SQLException { * Tests all of the byte values from 0 - 255. */ @Test - public void testAllBytes() throws SQLException { + void allBytes() throws SQLException { byte[] data = new byte[256]; for (int i = 0; i < data.length; i++) { data[i] = (byte) i; @@ -108,7 +126,7 @@ public void testAllBytes() throws SQLException { } @Test - public void testTruncate() throws SQLException { + void truncate() throws SQLException { if (!TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3)) { return; } @@ -180,7 +198,7 @@ public void readWrite(int offset, byte[] data) throws SQLException { assertTrue(rs.next()); b = rs.getBlob("DATA"); byte[] rspData = b.getBytes(offset, data.length); - assertArrayEquals("Request should be the same as the response", data, rspData); + assertArrayEquals(data, rspData, "Request should be the same as the response"); rs.close(); ps.close(); @@ -190,7 +208,7 @@ public void readWrite(int offset, byte[] data) throws SQLException { * Test the writing and reading of a single byte. */ @Test - public void test1ByteStream() throws SQLException, IOException { + void test1ByteStream() throws SQLException, IOException { byte[] data = {(byte) 'a'}; readWriteStream(data); } @@ -199,7 +217,7 @@ public void test1ByteStream() throws SQLException, IOException { * Test the writing and reading of a few bytes. */ @Test - public void testManyBytesStream() throws SQLException, IOException { + void manyBytesStream() throws SQLException, IOException { byte[] data = "aaaaaaaaaa".getBytes(); readWriteStream(data); } @@ -208,7 +226,7 @@ public void testManyBytesStream() throws SQLException, IOException { * Test writing a single byte with an offset. */ @Test - public void test1ByteOffsetStream() throws SQLException, IOException { + void test1ByteOffsetStream() throws SQLException, IOException { byte[] data = {(byte) 'a'}; readWriteStream(10, data); } @@ -217,7 +235,7 @@ public void test1ByteOffsetStream() throws SQLException, IOException { * Test the writing and reading of a few bytes with an offset. */ @Test - public void testManyBytesOffsetStream() throws SQLException, IOException { + void manyBytesOffsetStream() throws SQLException, IOException { byte[] data = "aaaaaaaaaa".getBytes(); readWriteStream(10, data); } @@ -226,7 +244,7 @@ public void testManyBytesOffsetStream() throws SQLException, IOException { * Tests all of the byte values from 0 - 255. */ @Test - public void testAllBytesStream() throws SQLException, IOException { + void allBytesStream() throws SQLException, IOException { byte[] data = new byte[256]; for (int i = 0; i < data.length; i++) { data[i] = (byte) i; @@ -270,15 +288,15 @@ public void readWriteStream(int offset, byte[] data) throws SQLException, IOExce in.read(rspData); in.close(); - assertArrayEquals("Request should be the same as the response", data, rspData); + assertArrayEquals(data, rspData, "Request should be the same as the response"); rs.close(); ps.close(); } @Test - public void testPattern() throws SQLException { - byte[] data = "abcdefghijklmnopqrstuvwxyx0123456789".getBytes(); + void pattern() throws SQLException { + byte[] data = "abcdefghijklmnopqrstuvwxyz0123456789".getBytes(); byte[] pattern = "def".getBytes(); PreparedStatement ps = conn.prepareStatement(INSERT); @@ -302,7 +320,7 @@ public void testPattern() throws SQLException { b = rs.getBlob("DATA"); long position = b.position(pattern, 1); byte[] rspData = b.getBytes(position, pattern.length); - assertArrayEquals("Request should be the same as the response", pattern, rspData); + assertArrayEquals(pattern, rspData, "Request should be the same as the response"); rs.close(); ps.close(); diff --git a/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java b/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java index 9dfa192..0574d06 100644 --- a/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java @@ -5,31 +5,41 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.util.PSQLState; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.sql.CallableStatement; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; +import java.time.LocalDate; /** * @author davec */ public class Jdbc3CallableStatementTest extends BaseTest4 { + @BeforeAll + public static void beforeClass() throws Exception { + try (Connection con = TestUtil.openDB()) { + assumeCallableStatementsSupported(con); + } + } @Override public void setUp() throws Exception { @@ -85,11 +95,30 @@ public void setUp() throws Exception { + "end;'" + "LANGUAGE plpgsql VOLATILE;" ); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getBooleanWithoutArg() " + + "RETURNS boolean AS ' " + + "begin return true; end; ' LANGUAGE plpgsql;"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getBit1WithoutArg() " + + "RETURNS bit(1) AS ' " + + "begin return B''1''; end; ' LANGUAGE plpgsql;"); + stmt.execute( + "CREATE OR REPLACE FUNCTION testspg__getBit2WithoutArg() " + + "RETURNS bit(2) AS ' " + + "begin return B''10''; end; ' LANGUAGE plpgsql;"); if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { stmt.execute( "CREATE OR REPLACE PROCEDURE inonlyprocedure(a IN int) AS 'BEGIN NULL; END;' LANGUAGE plpgsql"); stmt.execute( "CREATE OR REPLACE PROCEDURE inoutprocedure(a INOUT int) AS 'BEGIN a := a + a; END;' LANGUAGE plpgsql"); + + } + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v14)) { + stmt.execute("create or replace PROCEDURE testspg_refcursor(bar date, out cur1 refcursor) " + + " as $$ declare begin " + + "OPEN cur1 FOR " + + "SELECT now() as now; end $$ language plpgsql"); } } @@ -104,17 +133,22 @@ public void tearDown() throws SQLException { stmt.execute("drop function myif(a INOUT int, b IN int)"); stmt.execute("drop function mynoparams()"); stmt.execute("drop function mynoparamsproc()"); + stmt.execute("drop function testspg__getBooleanWithoutArg ();"); + stmt.execute("drop function testspg__getBit1WithoutArg ();"); + stmt.execute("drop function testspg__getBit2WithoutArg ();"); if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { stmt.execute("drop procedure inonlyprocedure(a IN int)"); stmt.execute("drop procedure inoutprocedure(a INOUT int)"); } + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v14)) { + stmt.execute("DROP PROCEDURE testspg_refcursor(date);"); + } stmt.close(); super.tearDown(); } @Test public void testSomeInOut() throws Throwable { - assumeCallableStatementsSupported(); CallableStatement call = con.prepareCall("{ call test_somein_someout(?,?,?) }"); call.registerOutParameter(2, Types.VARCHAR); @@ -126,7 +160,6 @@ public void testSomeInOut() throws Throwable { @Test public void testNotEnoughParameters() throws Throwable { - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("{call myiofunc(?,?)}"); cs.setInt(1, 2); cs.registerOutParameter(2, Types.INTEGER); @@ -174,8 +207,6 @@ public void testAllInOut() throws Throwable { @Test public void testNumeric() throws Throwable { - assumeCallableStatementsSupported(); - CallableStatement call = con.prepareCall("{ call Numeric_Proc(?,?,?) }"); call.registerOutParameter(1, Types.NUMERIC, 15); @@ -184,24 +215,20 @@ public void testNumeric() throws Throwable { call.executeUpdate(); BigDecimal ret = call.getBigDecimal(1); - assertTrue( - "correct return from getNumeric () should be 999999999999999.000000000000000 but returned " - + ret.toString(), - ret.equals(new BigDecimal("999999999999999.000000000000000"))); + assertTrue(ret.equals(new BigDecimal("999999999999999.000000000000000")), "correct return from getNumeric () should be 999999999999999.000000000000000 but returned " + + ret.toString()); ret = call.getBigDecimal(2); - assertTrue("correct return from getNumeric ()", - ret.equals(new BigDecimal("0.000000000000001"))); + assertTrue(ret.equals(new BigDecimal("0.000000000000001")), "correct return from getNumeric ()"); try { ret = call.getBigDecimal(3); } catch (NullPointerException ex) { - assertTrue("This should be null", call.wasNull()); + assertTrue(call.wasNull(), "This should be null"); } } @Test public void testGetObjectDecimal() throws Throwable { - assumeCallableStatementsSupported(); try { Statement stmt = con.createStatement(); stmt.execute( @@ -378,9 +405,33 @@ public void testSetObjectBit() throws Throwable { } } + @Test + public void testGetBit1WithoutArg() throws SQLException { + assumeNotSimpleQueryMode(); + try (CallableStatement call = con.prepareCall("{ ? = call testspg__getBit1WithoutArg () }")) { + call.registerOutParameter(1, Types.BOOLEAN); + call.execute(); + assertTrue(call.getBoolean(1)); + } + } + + @Test + public void testGetBit2WithoutArg() throws SQLException { + assumeNotSimpleQueryMode(); + try (CallableStatement call = con.prepareCall("{ ? = call testspg__getBit2WithoutArg () }")) { + call.registerOutParameter(1, Types.BOOLEAN); + try { + call.execute(); + assertTrue(call.getBoolean(1)); + fail("#getBoolean(int) on bit(2) should throw"); + } catch (SQLException e) { + assertEquals(PSQLState.CANNOT_COERCE.getState(), e.getSQLState()); + } + } + } + @Test public void testGetObjectLongVarchar() throws Throwable { - assumeCallableStatementsSupported(); try { Statement stmt = con.createStatement(); stmt.execute("create temp table longvarchar_tab ( t text, null_val text )"); @@ -417,7 +468,7 @@ public void testGetObjectLongVarchar() throws Throwable { cstmt.close(); cstmt = con.prepareCall("{ call lvarchar_in_name(?) }"); String maxFloat = "3.4E38"; - cstmt.setObject(1, new Float(maxFloat), Types.LONGVARCHAR); + cstmt.setObject(1, Float.valueOf(maxFloat), Types.LONGVARCHAR); cstmt.executeUpdate(); cstmt.close(); Statement stmt = con.createStatement(); @@ -439,7 +490,6 @@ public void testGetObjectLongVarchar() throws Throwable { @Test public void testGetBytes01() throws Throwable { - assumeByteaSupported(); byte[] testdata = "TestData".getBytes(); try { Statement stmt = con.createStatement(); @@ -549,11 +599,11 @@ public void testUpdateReal() throws Throwable { cstmt.close(); ResultSet rs = con.createStatement().executeQuery("select * from real_tab"); assertTrue(rs.next()); - Float oVal = new Float(intValues[0]); - Float rVal = new Float(rs.getObject(1).toString()); + Float oVal = (float) intValues[0]; + Float rVal = Float.valueOf(rs.getObject(1).toString()); assertTrue(oVal.equals(rVal)); - oVal = new Float(intValues[1]); - rVal = new Float(rs.getObject(2).toString()); + oVal = (float) intValues[1]; + rVal = Float.valueOf(rs.getObject(2).toString()); assertTrue(oVal.equals(rVal)); rs.close(); } catch (Exception ex) { @@ -610,7 +660,6 @@ public void testUpdateDecimal() throws Throwable { @Test public void testGetBytes02() throws Throwable { - assumeByteaSupported(); byte[] testdata = "TestData".getBytes(); try { Statement stmt = con.createStatement(); @@ -659,7 +708,6 @@ public void testGetBytes02() throws Throwable { @Test public void testGetObjectFloat() throws Throwable { - assumeCallableStatementsSupported(); try { Statement stmt = con.createStatement(); stmt.execute(createDecimalTab); @@ -696,7 +744,6 @@ public void testGetObjectFloat() throws Throwable { @Test public void testGetDouble01() throws Throwable { - assumeCallableStatementsSupported(); try { Statement stmt = con.createStatement(); stmt.execute("create temp table d_tab ( max_val float, min_val float, null_val float )"); @@ -737,7 +784,6 @@ public void testGetDouble01() throws Throwable { @Test public void testGetDoubleAsReal() throws Throwable { - assumeCallableStatementsSupported(); try { Statement stmt = con.createStatement(); stmt.execute("create temp table d_tab ( max_val float, min_val float, null_val float )"); @@ -778,7 +824,6 @@ public void testGetDoubleAsReal() throws Throwable { @Test public void testGetShort01() throws Throwable { - assumeCallableStatementsSupported(); try { Statement stmt = con.createStatement(); stmt.execute("create temp table short_tab ( max_val int2, min_val int2, null_val int2 )"); @@ -819,7 +864,6 @@ public void testGetShort01() throws Throwable { @Test public void testGetInt01() throws Throwable { - assumeCallableStatementsSupported(); try { Statement stmt = con.createStatement(); stmt.execute("create temp table i_tab ( max_val int, min_val int, null_val int )"); @@ -860,7 +904,6 @@ public void testGetInt01() throws Throwable { @Test public void testGetLong01() throws Throwable { - assumeCallableStatementsSupported(); try { Statement stmt = con.createStatement(); stmt.execute("create temp table l_tab ( max_val int8, min_val int8, null_val int8 )"); @@ -901,7 +944,6 @@ public void testGetLong01() throws Throwable { @Test public void testGetBoolean01() throws Throwable { - assumeCallableStatementsSupported(); try { Statement stmt = con.createStatement(); stmt.execute(createBitTab); @@ -940,9 +982,18 @@ public void testGetBoolean01() throws Throwable { } } + @Test + public void testGetBooleanWithoutArg() throws SQLException { + assumeNotSimpleQueryMode(); + try (CallableStatement call = con.prepareCall("{ ? = call testspg__getBooleanWithoutArg () }")) { + call.registerOutParameter(1, Types.BOOLEAN); + call.execute(); + assertTrue(call.getBoolean(1)); + } + } + @Test public void testGetByte01() throws Throwable { - assumeCallableStatementsSupported(); try { Statement stmt = con.createStatement(); stmt.execute("create temp table byte_tab ( max_val int2, min_val int2, null_val int2 )"); @@ -983,7 +1034,6 @@ public void testGetByte01() throws Throwable { @Test public void testMultipleOutExecutions() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("{call myiofunc(?, ?)}"); for (int i = 0; i < 10; i++) { cs.registerOutParameter(1, Types.INTEGER); @@ -998,38 +1048,34 @@ public void testMultipleOutExecutions() throws SQLException { @Test public void testSum() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("{?= call mysum(?, ?)}"); cs.registerOutParameter(1, Types.INTEGER); cs.setInt(2, 2); cs.setInt(3, 3); cs.execute(); - assertEquals("2+3 should be 5 when executed via {?= call mysum(?, ?)}", 5, cs.getInt(1)); + assertEquals(5, cs.getInt(1), "2+3 should be 5 when executed via {?= call mysum(?, ?)}"); } @Test public void testFunctionNoParametersWithParentheses() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("{?= call mynoparams()}"); cs.registerOutParameter(1, Types.INTEGER); cs.execute(); - assertEquals("{?= call mynoparam()} should return 733, but did not.", 733, cs.getInt(1)); + assertEquals(733, cs.getInt(1), "{?= call mynoparam()} should return 733, but did not."); TestUtil.closeQuietly(cs); } @Test public void testFunctionNoParametersWithoutParentheses() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("{?= call mynoparams}"); cs.registerOutParameter(1, Types.INTEGER); cs.execute(); - assertEquals("{?= call mynoparam()} should return 733, but did not.", 733, cs.getInt(1)); + assertEquals(733, cs.getInt(1), "{?= call mynoparam()} should return 733, but did not."); TestUtil.closeQuietly(cs); } @Test public void testProcedureNoParametersWithParentheses() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("{ call mynoparamsproc()}"); cs.execute(); TestUtil.closeQuietly(cs); @@ -1037,7 +1083,6 @@ public void testProcedureNoParametersWithParentheses() throws SQLException { @Test public void testProcedureNoParametersWithoutParentheses() throws SQLException { - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("{ call mynoparamsproc}"); cs.execute(); TestUtil.closeQuietly(cs); @@ -1046,7 +1091,6 @@ public void testProcedureNoParametersWithoutParentheses() throws SQLException { @Test public void testProcedureInOnlyNativeCall() throws SQLException { assumeMinimumServerVersion(ServerVersion.v11); - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("call inonlyprocedure(?)"); cs.setInt(1, 5); cs.execute(); @@ -1056,13 +1100,33 @@ public void testProcedureInOnlyNativeCall() throws SQLException { @Test public void testProcedureInOutNativeCall() throws SQLException { assumeMinimumServerVersion(ServerVersion.v11); - assumeCallableStatementsSupported(); // inoutprocedure(a INOUT int) returns a*2 via the INOUT parameter CallableStatement cs = con.prepareCall("call inoutprocedure(?)"); cs.setInt(1, 5); cs.registerOutParameter(1, Types.INTEGER); cs.execute(); - assertEquals("call inoutprocedure(?) should return 10 (when input param = 5) via the INOUT parameter, but did not.", 10, cs.getInt(1)); + assertEquals(10, cs.getInt(1), "call inoutprocedure(?) should return 10 (when input param = 5) via the INOUT parameter, but did not."); TestUtil.closeQuietly(cs); } + + @Test + public void testCall5Times() throws SQLException { + assumeMinimumServerVersion(ServerVersion.v14); + // call this enough times to change to binary mode + for (int i = 0; i < 6; i++) { + con.setAutoCommit(false); + try (CallableStatement proc = con.prepareCall("call testspg_refcursor( ? , ? )")) { + proc.setDate(1, java.sql.Date.valueOf(LocalDate.now())); + proc.registerOutParameter(2, Types.REF_CURSOR); + proc.execute(); + try (ResultSet results = (ResultSet) proc.getObject(2)) { + while (results.next()) { + // Getter should succeed without failure + assertNotNull(results.getTimestamp("now").toLocalDateTime()); + } + } + } + con.commit(); + } + } } diff --git a/src/test/java/org/postgresql/test/jdbc3/Jdbc3SavepointTest.java b/src/test/java/org/postgresql/test/jdbc3/Jdbc3SavepointTest.java index 46306e0..9188a13 100644 --- a/src/test/java/org/postgresql/test/jdbc3/Jdbc3SavepointTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/Jdbc3SavepointTest.java @@ -5,14 +5,16 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; @@ -21,25 +23,38 @@ import java.sql.Savepoint; import java.sql.Statement; -public class Jdbc3SavepointTest { +class Jdbc3SavepointTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "savepointtable", "id int primary key"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "savepointtable"); + } + } + + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); - TestUtil.createTable(conn, "savepointtable", "id int primary key"); + TestUtil.execute(conn, "TRUNCATE savepointtable CASCADE"); conn.setAutoCommit(false); } - @After - public void tearDown() throws SQLException { + @AfterEach + void tearDown() throws SQLException { conn.setAutoCommit(true); - TestUtil.dropTable(conn, "savepointtable"); TestUtil.closeDB(conn); } - private boolean hasSavepoints() throws SQLException { + private static boolean hasSavepoints() throws SQLException { return true; } @@ -60,7 +75,7 @@ private int countRows() throws SQLException { } @Test - public void testAutoCommitFails() throws SQLException { + void autoCommitFails() throws SQLException { if (!hasSavepoints()) { return; } @@ -81,7 +96,7 @@ public void testAutoCommitFails() throws SQLException { } @Test - public void testCantMixSavepointTypes() throws SQLException { + void cantMixSavepointTypes() throws SQLException { if (!hasSavepoints()) { return; } @@ -100,11 +115,10 @@ public void testCantMixSavepointTypes() throws SQLException { fail("Can't get name from unnamed savepoint."); } catch (SQLException sqle) { } - } @Test - public void testRollingBackToSavepoints() throws SQLException { + void rollingBackToSavepoints() throws SQLException { if (!hasSavepoints()) { return; } @@ -122,7 +136,7 @@ public void testRollingBackToSavepoints() throws SQLException { } @Test - public void testGlobalRollbackWorks() throws SQLException { + void globalRollbackWorks() throws SQLException { if (!hasSavepoints()) { return; } @@ -138,7 +152,7 @@ public void testGlobalRollbackWorks() throws SQLException { } @Test - public void testContinueAfterError() throws SQLException { + void continueAfterError() throws SQLException { if (!hasSavepoints()) { return; } @@ -158,7 +172,7 @@ public void testContinueAfterError() throws SQLException { } @Test - public void testReleaseSavepoint() throws SQLException { + void releaseSavepoint() throws SQLException { if (!hasSavepoints()) { return; } @@ -181,7 +195,7 @@ public void testReleaseSavepoint() throws SQLException { } @Test - public void testComplicatedSavepointName() throws SQLException { + void complicatedSavepointName() throws SQLException { if (!hasSavepoints()) { return; } @@ -192,7 +206,7 @@ public void testComplicatedSavepointName() throws SQLException { } @Test - public void testRollingBackToInvalidSavepointFails() throws SQLException { + void rollingBackToInvalidSavepointFails() throws SQLException { if (!hasSavepoints()) { return; } @@ -209,7 +223,7 @@ public void testRollingBackToInvalidSavepointFails() throws SQLException { } @Test - public void testRollbackMultipleTimes() throws SQLException { + void rollbackMultipleTimes() throws SQLException { if (!hasSavepoints()) { return; } diff --git a/src/test/java/org/postgresql/test/jdbc3/ParameterMetaDataTest.java b/src/test/java/org/postgresql/test/jdbc3/ParameterMetaDataTest.java index bafd44c..81faacd 100644 --- a/src/test/java/org/postgresql/test/jdbc3/ParameterMetaDataTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/ParameterMetaDataTest.java @@ -5,38 +5,64 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import java.sql.Connection; import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; +import java.util.ArrayList; +import java.util.Collection; +@ParameterizedClass +@MethodSource("data") public class ParameterMetaDataTest extends BaseTest4 { + public ParameterMetaDataTest(BinaryMode binaryMode) { + setBinaryMode(binaryMode); + } - @Override - public void setUp() throws Exception { - super.setUp(); - Assume.assumeTrue("simple protocol only does not support describe statement requests", - preferQueryMode != PreferQueryMode.SIMPLE); - TestUtil.createTable(con, "parametertest", - "a int4, b float8, c text, d point, e timestamp with time zone"); + public static Iterable data() { + Collection ids = new ArrayList<>(); + for (BinaryMode binaryMode : BinaryMode.values()) { + ids.add(new Object[]{binaryMode}); + } + return ids; + } + + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "parametertest", + "a int4, b float8, c text, d point, e timestamp with time zone"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "parametertest"); + } } @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "parametertest"); - super.tearDown(); + public void setUp() throws Exception { + super.setUp(); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE, "simple protocol only does not support describe statement requests"); } @Test diff --git a/src/test/java/org/postgresql/test/jdbc3/ProcedureTransactionTest.java b/src/test/java/org/postgresql/test/jdbc3/ProcedureTransactionTest.java index e1262df..dcd95b5 100644 --- a/src/test/java/org/postgresql/test/jdbc3/ProcedureTransactionTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/ProcedureTransactionTest.java @@ -5,9 +5,10 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGProperty; import org.postgresql.core.ServerVersion; @@ -16,15 +17,23 @@ import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.util.PSQLState; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.sql.CallableStatement; +import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; public class ProcedureTransactionTest extends BaseTest4 { + @BeforeAll + public static void beforeClass() throws Exception { + try (Connection con = TestUtil.openDB()) { + assumeCallableStatementsSupported(con); + } + } @Override protected void updateProperties(Properties props) { @@ -63,7 +72,6 @@ public void tearDown() throws SQLException { @Test public void testProcWithNoTxnControl() throws SQLException { assumeMinimumServerVersion(ServerVersion.v11); - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("call mynotxnproc(?)"); int val = 1; cs.setInt(1, val); @@ -75,7 +83,7 @@ public void testProcWithNoTxnControl() throws SQLException { ResultSet rs = cs.executeQuery(); assertTrue(rs.next()); - assertTrue(rs.getInt(1) == val); + assertEquals(val, rs.getInt(1)); TestUtil.closeQuietly(rs); TestUtil.closeQuietly(cs); @@ -84,7 +92,6 @@ public void testProcWithNoTxnControl() throws SQLException { @Test public void testProcWithCommitInside() throws SQLException { assumeMinimumServerVersion(ServerVersion.v11); - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("call mycommitproc(?)"); int val = 2; cs.setInt(1, val); @@ -96,7 +103,7 @@ public void testProcWithCommitInside() throws SQLException { ResultSet rs = cs.executeQuery(); assertTrue(rs.next()); - assertTrue(rs.getInt(1) == val); + assertEquals(val, rs.getInt(1)); TestUtil.closeQuietly(rs); TestUtil.closeQuietly(cs); @@ -105,7 +112,6 @@ public void testProcWithCommitInside() throws SQLException { @Test public void testProcWithRollbackInside() throws SQLException { assumeMinimumServerVersion(ServerVersion.v11); - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("call myrollbackproc(?)"); int val = 3; cs.setInt(1, val); @@ -148,7 +154,6 @@ public void testProcAutoCommitFalse() throws SQLException { private void testProcAutoCommit() throws SQLException { assumeMinimumServerVersion(ServerVersion.v11); - assumeCallableStatementsSupported(); CallableStatement cs = con.prepareCall("call mycommitproc(?)"); int val = 4; cs.setInt(1, val); @@ -160,7 +165,7 @@ private void testProcAutoCommit() throws SQLException { ResultSet rs = cs.executeQuery(); assertTrue(rs.next()); - assertTrue(rs.getInt(1) == val); + assertEquals(val, rs.getInt(1)); TestUtil.closeQuietly(rs); TestUtil.closeQuietly(cs); diff --git a/src/test/java/org/postgresql/test/jdbc3/ResultSetTest.java b/src/test/java/org/postgresql/test/jdbc3/ResultSetTest.java index 9febe7b..d2286d3 100644 --- a/src/test/java/org/postgresql/test/jdbc3/ResultSetTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/ResultSetTest.java @@ -5,26 +5,27 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -public class ResultSetTest { +class ResultSetTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); Statement stmt = conn.createStatement(); stmt.execute("CREATE TEMP TABLE hold(a int)"); @@ -33,8 +34,8 @@ public void setUp() throws Exception { stmt.close(); } - @After - public void tearDown() throws SQLException { + @AfterEach + void tearDown() throws SQLException { Statement stmt = conn.createStatement(); stmt.execute("DROP TABLE hold"); stmt.close(); @@ -42,7 +43,7 @@ public void tearDown() throws SQLException { } @Test - public void testHoldableResultSet() throws SQLException { + void holdableResultSet() throws SQLException { Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, ResultSet.HOLD_CURSORS_OVER_COMMIT); @@ -58,7 +59,7 @@ public void testHoldableResultSet() throws SQLException { assertTrue(rs.next()); assertEquals(2, rs.getInt(1)); - assertTrue(!rs.next()); + assertFalse(rs.next()); rs.close(); stmt.close(); diff --git a/src/test/java/org/postgresql/test/jdbc3/SendRecvBufferSizeTest.java b/src/test/java/org/postgresql/test/jdbc3/SendRecvBufferSizeTest.java index 2d309b6..513cc7d 100644 --- a/src/test/java/org/postgresql/test/jdbc3/SendRecvBufferSizeTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/SendRecvBufferSizeTest.java @@ -5,45 +5,53 @@ package org.postgresql.test.jdbc3; +import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; +import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; +import java.util.Properties; -public class SendRecvBufferSizeTest { +public class SendRecvBufferSizeTest extends BaseTest4 { - private Connection conn; + @Override + protected void updateProperties(Properties props) { + super.updateProperties(props); + PGProperty.SEND_BUFFER_SIZE.set(props, "1024"); + PGProperty.RECEIVE_BUFFER_SIZE.set(props, "1024"); + } - @Before - public void setUp() throws Exception { - System.setProperty("sendBufferSize", "1024"); - System.setProperty("receiveBufferSize", "1024"); - - conn = TestUtil.openDB(); - Statement stmt = conn.createStatement(); - stmt.execute("CREATE TEMP TABLE hold(a int)"); - stmt.execute("INSERT INTO hold VALUES (1)"); - stmt.execute("INSERT INTO hold VALUES (2)"); - stmt.close(); + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "hold", "a int"); + } } - @After - public void tearDown() throws SQLException { - Statement stmt = conn.createStatement(); - stmt.execute("DROP TABLE hold"); - stmt.close(); - TestUtil.closeDB(conn); + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "hold"); + } + } + + @Override + public void setUp() throws Exception { + super.setUp(); + TestUtil.execute(con, "TRUNCATE hold"); + TestUtil.execute(con, "INSERT INTO hold VALUES (1),(2)"); } // dummy test @Test public void testSelect() throws SQLException { - Statement stmt = conn.createStatement(); + Statement stmt = con.createStatement(); stmt.execute("select * from hold"); stmt.close(); } diff --git a/src/test/java/org/postgresql/test/jdbc3/SqlCommandParseTest.java b/src/test/java/org/postgresql/test/jdbc3/SqlCommandParseTest.java index 0965d91..8a3c2d4 100644 --- a/src/test/java/org/postgresql/test/jdbc3/SqlCommandParseTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/SqlCommandParseTest.java @@ -5,28 +5,20 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.postgresql.core.NativeQuery; import org.postgresql.core.Parser; import org.postgresql.core.SqlCommandType; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.SQLException; import java.util.Arrays; import java.util.List; -@RunWith(Parameterized.class) public class SqlCommandParseTest { - @Parameterized.Parameter(0) - public SqlCommandType type; - @Parameterized.Parameter(1) - public String sql; - - @Parameterized.Parameters(name = "expected={0}, sql={1}") public static Iterable data() { return Arrays.asList(new Object[][]{ {SqlCommandType.INSERT, "insert/**/ into table(select) values(1)"}, @@ -44,11 +36,12 @@ public static Iterable data() { }); } - @Test - public void run() throws SQLException { + @MethodSource("data") + @ParameterizedTest + void run(SqlCommandType type, String sql) throws SQLException { List queries; queries = Parser.parseJdbcSql(sql, true, true, false, true, true); NativeQuery query = queries.get(0); - assertEquals(sql, type, query.command.getType()); + assertEquals(type, query.command.getType(), sql); } } diff --git a/src/test/java/org/postgresql/test/jdbc3/StringTypeParameterTest.java b/src/test/java/org/postgresql/test/jdbc3/StringTypeParameterTest.java index bb1d1d7..6885c8e 100644 --- a/src/test/java/org/postgresql/test/jdbc3/StringTypeParameterTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/StringTypeParameterTest.java @@ -5,9 +5,11 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeFalse; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.ServerVersion; import org.postgresql.jdbc.PreferQueryMode; @@ -15,11 +17,13 @@ import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.util.PSQLState; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -28,7 +32,8 @@ import java.util.Collection; import java.util.Properties; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class StringTypeParameterTest extends BaseTest4 { private static final String UNSPECIFIED_STRING_TYPE = "unspecified"; @@ -38,13 +43,28 @@ public StringTypeParameterTest(String stringType) { this.stringType = stringType; } + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createEnumType(con, "mood", "'happy', 'sad'"); + TestUtil.createTable(con, "stringtypetest", "m mood"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "stringtypetest"); + TestUtil.dropType(con, "mood"); + } + } + @Override public void setUp() throws Exception { super.setUp(); // Assume enum supported - Assume.assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3)); - TestUtil.createEnumType(con, "mood", "'happy', 'sad'"); - TestUtil.createTable(con, "stringtypetest", "m mood"); + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3)); + TestUtil.execute(con, "TRUNCATE stringtypetest"); } @Override @@ -55,16 +75,8 @@ protected void updateProperties(Properties props) { } } - @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "stringtypetest"); - TestUtil.dropType(con, "mood"); - super.tearDown(); - } - - @Parameterized.Parameters(name = "stringType = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (String stringType : new String[]{null, "varchar", UNSPECIFIED_STRING_TYPE}) { ids.add(new Object[]{stringType}); } @@ -73,8 +85,8 @@ public static Iterable data() { @Test public void testVarcharAsEnum() throws Exception { - Assume.assumeFalse(UNSPECIFIED_STRING_TYPE.equals(stringType)); - Assume.assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE); + assumeFalse(UNSPECIFIED_STRING_TYPE.equals(stringType)); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE); PreparedStatement update = con.prepareStatement("insert into stringtypetest (m) values (?)"); for (int i = 0; i < 2; i++) { @@ -110,8 +122,8 @@ public void testOtherAsEnum() throws Exception { @Test public void testMultipleEnumBinds() throws Exception { - Assume.assumeFalse(UNSPECIFIED_STRING_TYPE.equals(stringType)); - Assume.assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE); + assumeFalse(UNSPECIFIED_STRING_TYPE.equals(stringType)); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE); PreparedStatement query = con.prepareStatement("select * from stringtypetest where m = ? or m = ?"); @@ -132,7 +144,7 @@ public void testMultipleEnumBinds() throws Exception { @Test public void testParameterUnspecified() throws Exception { - Assume.assumeTrue(UNSPECIFIED_STRING_TYPE.equals(stringType)); + assumeTrue(UNSPECIFIED_STRING_TYPE.equals(stringType)); PreparedStatement update = con.prepareStatement("insert into stringtypetest (m) values (?)"); update.setString(1, "happy"); diff --git a/src/test/java/org/postgresql/test/jdbc3/TestReturning.java b/src/test/java/org/postgresql/test/jdbc3/TestReturningTest.java similarity index 65% rename from src/test/java/org/postgresql/test/jdbc3/TestReturning.java rename to src/test/java/org/postgresql/test/jdbc3/TestReturningTest.java index 88514b3..7d5da48 100644 --- a/src/test/java/org/postgresql/test/jdbc3/TestReturning.java +++ b/src/test/java/org/postgresql/test/jdbc3/TestReturningTest.java @@ -5,18 +5,18 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.Connection; import java.sql.PreparedStatement; @@ -26,8 +26,9 @@ import java.util.Collection; import java.util.Properties; -@RunWith(Parameterized.class) -public class TestReturning extends BaseTest4 { +@ParameterizedClass +@MethodSource("data") +public class TestReturningTest extends BaseTest4 { public enum ColumnsReturned { Id("Id"), @@ -37,6 +38,7 @@ public enum ColumnsReturned { NO(); final String[] columns; + ColumnsReturned(String... columns) { this.columns = columns; } @@ -59,12 +61,11 @@ public String[] getColumnNames() { static String []returningOptions = {"true", "false"}; - @Parameterized.Parameters(name = "returningInQuery = {0}, quoteReturning = {1}") public static Iterable data() { - Collection ids = new ArrayList(); - for (ColumnsReturned columsReturned : ColumnsReturned.values()) { + Collection ids = new ArrayList<>(); + for (ColumnsReturned columnsReturned : ColumnsReturned.values()) { for (String q : returningOptions) { - ids.add(new Object[]{columsReturned, q}); + ids.add(new Object[]{columnsReturned, q}); } } return ids; @@ -73,7 +74,7 @@ public static Iterable data() { private final ColumnsReturned columnsReturned; private final String quoteReturning; - public TestReturning(ColumnsReturned columnsReturned, String quoteReturning) throws Exception { + public TestReturningTest(ColumnsReturned columnsReturned, String quoteReturning) throws Exception { this.columnsReturned = columnsReturned; this.quoteReturning = quoteReturning; } @@ -94,7 +95,7 @@ public void tearDown() throws SQLException { super.tearDown(); } - private void testGeneratedKeys(Connection conn, String sql, String[] columnNames, boolean exceptionExpected) throws SQLException { + private static void testGeneratedKeys(Connection conn, String sql, String[] columnNames, boolean exceptionExpected) throws SQLException { try (PreparedStatement stmt = conn.prepareStatement(sql, columnNames)) { stmt.execute(); @@ -114,10 +115,10 @@ public void testMixedCase() throws SQLException { String insertSql = "INSERT INTO genkeys (b,c) VALUES ('hello', 1)"; - testGeneratedKeys(con, insertSql, new String[] {"Id"}, quoteReturning.equals("false") ); - testGeneratedKeys(con, insertSql, new String[] {"id"}, true); - testGeneratedKeys(con, insertSql, new String[] {"ID"}, true); - testGeneratedKeys(con, insertSql, new String[] {"\"Id\""}, quoteReturning.equals("true")); - testGeneratedKeys(con, insertSql, new String[] {"bad"}, true); + testGeneratedKeys(con, insertSql, new String[]{"Id"}, "false".equals(quoteReturning)); + testGeneratedKeys(con, insertSql, new String[]{"id"}, true); + testGeneratedKeys(con, insertSql, new String[]{"ID"}, true); + testGeneratedKeys(con, insertSql, new String[]{"\"Id\""}, "true".equals(quoteReturning)); + testGeneratedKeys(con, insertSql, new String[]{"bad"}, true); } } diff --git a/src/test/java/org/postgresql/test/jdbc3/TypesTest.java b/src/test/java/org/postgresql/test/jdbc3/TypesTest.java index a6a64f0..b3cd00e 100644 --- a/src/test/java/org/postgresql/test/jdbc3/TypesTest.java +++ b/src/test/java/org/postgresql/test/jdbc3/TypesTest.java @@ -5,15 +5,15 @@ package org.postgresql.test.jdbc3; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.CallableStatement; import java.sql.Connection; @@ -54,7 +54,7 @@ public void testPreparedBoolean() throws SQLException { pstmt.setObject(4, Boolean.FALSE); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); - assertTrue(!rs.getBoolean(1)); + assertFalse(rs.getBoolean(1)); assertTrue(rs.wasNull()); assertNull(rs.getObject(2)); assertTrue(rs.getBoolean(3)); @@ -62,7 +62,7 @@ public void testPreparedBoolean() throws SQLException { // The V2 path will return a String because it doesn't know // any better. if (preferQueryMode != PreferQueryMode.SIMPLE) { - assertTrue(!((Boolean) rs.getObject(4)).booleanValue()); + assertFalse(((Boolean) rs.getObject(4)).booleanValue()); } } @@ -70,7 +70,7 @@ public void testPreparedBoolean() throws SQLException { public void testPreparedByte() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("SELECT ?,?"); pstmt.setByte(1, (byte) 1); - pstmt.setObject(2, new Byte((byte) 2)); + pstmt.setObject(2, (byte) 2); ResultSet rs = pstmt.executeQuery(); assertTrue(rs.next()); assertEquals((byte) 1, rs.getByte(1)); @@ -88,7 +88,7 @@ public void testCallableBoolean() throws SQLException { cs.registerOutParameter(1, Types.BOOLEAN); cs.setBoolean(2, true); cs.execute(); - assertEquals(true, cs.getBoolean(1)); + assertTrue(cs.getBoolean(1)); cs.close(); } @@ -98,8 +98,8 @@ public void testUnknownType() throws SQLException { ResultSet rs = stmt.executeQuery("select 'foo1' as icon1, 'foo2' as icon2 "); assertTrue(rs.next()); - assertEquals("failed", "foo1", rs.getString("icon1")); - assertEquals("failed", "foo2", rs.getString("icon2")); + assertEquals("foo1", rs.getString("icon1"), "failed"); + assertEquals("foo2", rs.getString("icon2"), "failed"); } } diff --git a/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java b/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java index 0820a66..85d0e82 100644 --- a/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java @@ -5,9 +5,15 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.ServerVersion; import org.postgresql.geometric.PGbox; @@ -19,11 +25,12 @@ import org.postgresql.util.PGobject; import org.postgresql.util.PGtokenizer; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.hamcrest.MatcherAssert; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.Array; import java.sql.Connection; @@ -37,7 +44,8 @@ import java.util.Collection; import java.util.UUID; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class ArrayTest extends BaseTest4 { private Connection conn; @@ -46,56 +54,65 @@ public ArrayTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } - @Override - public void setUp() throws Exception { - super.setUp(); - conn = con; + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "arrtest", + "intarr int[], decarr decimal(2,1)[], strarr text[]" + + (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3) ? ", uuidarr uuid[]" : "") + + ", floatarr float8[]" + + ", intarr2 int4[][]"); + TestUtil.createTable(conn, "arrcompprnttest", "id serial, name character(10)"); + TestUtil.createTable(conn, "arrcompchldttest", + "id serial, name character(10), description character varying, parent integer"); + TestUtil.createTable(conn, "\"CorrectCasing\"", "id serial"); + TestUtil.createTable(conn, "\"Evil.Table\"", "id serial"); + } + } - TestUtil.createTable(conn, "arrtest", - "intarr int[], decarr decimal(2,1)[], strarr text[]" - + (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3) ? ", uuidarr uuid[]" : "") - + ", floatarr float8[]" - + ", intarr2 int4[][]"); - TestUtil.createTable(conn, "arrcompprnttest", "id serial, name character(10)"); - TestUtil.createTable(conn, "arrcompchldttest", - "id serial, name character(10), description character varying, parent integer"); - TestUtil.createTable(conn, "\"CorrectCasing\"", "id serial"); - TestUtil.createTable(conn, "\"Evil.Table\"", "id serial"); + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "arrtest"); + TestUtil.dropTable(conn, "arrcompprnttest"); + TestUtil.dropTable(conn, "arrcompchldttest"); + TestUtil.dropTable(conn, "\"CorrectCasing\""); + TestUtil.dropTable(conn, "\"Evil.Table\""); + } } @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(conn, "arrtest"); - TestUtil.dropTable(conn, "arrcompprnttest"); - TestUtil.dropTable(conn, "arrcompchldttest"); - TestUtil.dropTable(conn, "\"CorrectCasing\""); + public void setUp() throws Exception { + super.setUp(); + conn = con; - super.tearDown(); + TestUtil.execute(conn, "TRUNCATE arrtest CASCADE"); + TestUtil.execute(conn, "TRUNCATE arrcompprnttest RESTART IDENTITY CASCADE"); + TestUtil.execute(conn, "TRUNCATE arrcompchldttest RESTART IDENTITY CASCADE"); } @Test public void testCreateArrayOfBool() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("SELECT ?::bool[]"); - pstmt.setArray(1, conn.unwrap(PgConnection.class).createArrayOf("boolean", new boolean[] { true, true, false })); + pstmt.setArray(1, conn.unwrap(PgConnection.class).createArrayOf("boolean", new boolean[]{true, true, false})); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); Boolean[] out = (Boolean[]) arr.getArray(); - Assert.assertEquals(3, out.length); - Assert.assertEquals(Boolean.TRUE, out[0]); - Assert.assertEquals(Boolean.TRUE, out[1]); - Assert.assertEquals(Boolean.FALSE, out[2]); + assertEquals(3, out.length); + assertEquals(Boolean.TRUE, out[0]); + assertEquals(Boolean.TRUE, out[1]); + assertEquals(Boolean.FALSE, out[2]); } @Test @@ -108,48 +125,48 @@ public void testCreateArrayOfInt() throws SQLException { pstmt.setArray(1, conn.createArrayOf("int4", in)); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); Integer[] out = (Integer[]) arr.getArray(); - Assert.assertEquals(3, out.length); - Assert.assertEquals(0, out[0].intValue()); - Assert.assertEquals(-1, out[1].intValue()); - Assert.assertEquals(2, out[2].intValue()); + assertEquals(3, out.length); + assertEquals(0, out[0].intValue()); + assertEquals(-1, out[1].intValue()); + assertEquals(2, out[2].intValue()); } @Test public void testCreateArrayOfBytes() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("SELECT ?::bytea[]"); - final byte[][] in = new byte[][] { { 0x01, (byte) 0xFF, (byte) 0x12 }, {}, { (byte) 0xAC, (byte) 0xE4 }, null }; + final byte[][] in = new byte[][]{{0x01, (byte) 0xFF, (byte) 0x12}, {}, {(byte) 0xAC, (byte) 0xE4}, null}; final Array createdArray = conn.createArrayOf("bytea", in); byte[][] inCopy = (byte[][]) createdArray.getArray(); - Assert.assertEquals(4, inCopy.length); + assertEquals(4, inCopy.length); - Assert.assertArrayEquals(in[0], inCopy[0]); - Assert.assertArrayEquals(in[1], inCopy[1]); - Assert.assertArrayEquals(in[2], inCopy[2]); - Assert.assertArrayEquals(in[3], inCopy[3]); - Assert.assertNull(inCopy[3]); + assertArrayEquals(in[0], inCopy[0]); + assertArrayEquals(in[1], inCopy[1]); + assertArrayEquals(in[2], inCopy[2]); + assertArrayEquals(in[3], inCopy[3]); + assertNull(inCopy[3]); pstmt.setArray(1, createdArray); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); byte[][] out = (byte[][]) arr.getArray(); - Assert.assertEquals(4, out.length); + assertEquals(4, out.length); - Assert.assertArrayEquals(in[0], out[0]); - Assert.assertArrayEquals(in[1], out[1]); - Assert.assertArrayEquals(in[2], out[2]); - Assert.assertArrayEquals(in[3], out[3]); - Assert.assertNull(out[3]); + assertArrayEquals(in[0], out[0]); + assertArrayEquals(in[1], out[1]); + assertArrayEquals(in[2], out[2]); + assertArrayEquals(in[3], out[3]); + assertNull(out[3]); } @Test @@ -159,23 +176,23 @@ public void testCreateArrayOfBytesFromString() throws SQLException { ServerVersion.v9_0); PreparedStatement pstmt = conn.prepareStatement("SELECT ?::bytea[]"); - final byte[][] in = new byte[][] { { 0x01, (byte) 0xFF, (byte) 0x12 }, {}, { (byte) 0xAC, (byte) 0xE4 }, null }; + final byte[][] in = new byte[][]{{0x01, (byte) 0xFF, (byte) 0x12}, {}, {(byte) 0xAC, (byte) 0xE4}, null}; pstmt.setString(1, "{\"\\\\x01ff12\",\"\\\\x\",\"\\\\xace4\",NULL}"); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); byte[][] out = (byte[][]) arr.getArray(); - Assert.assertEquals(4, out.length); + assertEquals(4, out.length); - Assert.assertArrayEquals(in[0], out[0]); - Assert.assertArrayEquals(in[1], out[1]); - Assert.assertArrayEquals(in[2], out[2]); - Assert.assertArrayEquals(in[3], out[3]); - Assert.assertNull(out[3]); + assertArrayEquals(in[0], out[0]); + assertArrayEquals(in[1], out[1]); + assertArrayEquals(in[2], out[2]); + assertArrayEquals(in[3], out[3]); + assertNull(out[3]); } @Test @@ -188,14 +205,14 @@ public void testCreateArrayOfSmallInt() throws SQLException { pstmt.setArray(1, conn.createArrayOf("int2", in)); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); Short[] out = (Short[]) arr.getArray(); - Assert.assertEquals(3, out.length); - Assert.assertEquals(0, out[0].shortValue()); - Assert.assertEquals(-1, out[1].shortValue()); - Assert.assertEquals(2, out[2].shortValue()); + assertEquals(3, out.length); + assertEquals(0, out[0].shortValue()); + assertEquals(-1, out[1].shortValue()); + assertEquals(2, out[2].shortValue()); } @Test @@ -209,16 +226,16 @@ public void testCreateArrayOfMultiString() throws SQLException { pstmt.setArray(1, conn.createArrayOf("text", in)); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); String[][] out = (String[][]) arr.getArray(); - Assert.assertEquals(2, out.length); - Assert.assertEquals(2, out[0].length); - Assert.assertEquals("a", out[0][0]); - Assert.assertEquals("", out[0][1]); - Assert.assertEquals("\\", out[1][0]); - Assert.assertEquals("\"\\'z", out[1][1]); + assertEquals(2, out.length); + assertEquals(2, out[0].length); + assertEquals("a", out[0][0]); + assertEquals("", out[0][1]); + assertEquals("\\", out[1][0]); + assertEquals("\"\\'z", out[1][1]); } @Test @@ -234,18 +251,18 @@ public void testCreateArrayOfMultiJson() throws SQLException { PGobject p2 = new PGobject(); p2.setType("json"); p2.setValue("{\"x\": 20}"); - PGobject[] in = new PGobject[] { p1, p2 }; + PGobject[] in = new PGobject[]{p1, p2}; pstmt.setArray(1, conn.createArrayOf("json", in)); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); ResultSet arrRs = arr.getResultSet(); - Assert.assertTrue(arrRs.next()); - Assert.assertEquals(in[0], arrRs.getObject(2)); + assertTrue(arrRs.next()); + assertEquals(in[0], arrRs.getObject(2)); - Assert.assertTrue(arrRs.next()); - Assert.assertEquals(in[1], arrRs.getObject(2)); + assertTrue(arrRs.next()); + assertEquals(in[1], arrRs.getObject(2)); } @Test @@ -257,14 +274,14 @@ public void testCreateArrayWithNonStandardDelimiter() throws SQLException { PreparedStatement pstmt = conn.prepareStatement("SELECT ?::box[]"); pstmt.setArray(1, conn.createArrayOf("box", in)); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); ResultSet arrRs = arr.getResultSet(); - Assert.assertTrue(arrRs.next()); - Assert.assertEquals(in[0], arrRs.getObject(2)); - Assert.assertTrue(arrRs.next()); - Assert.assertEquals(in[1], arrRs.getObject(2)); - Assert.assertFalse(arrRs.next()); + assertTrue(arrRs.next()); + assertEquals(in[0], arrRs.getObject(2)); + assertTrue(arrRs.next()); + assertEquals(in[1], arrRs.getObject(2)); + assertFalse(arrRs.next()); } @Test @@ -282,13 +299,13 @@ public void testCreateArrayOfNull() throws SQLException { pstmt.setArray(1, conn.createArrayOf("int8", in)); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); Long[] out = (Long[]) arr.getArray(); - Assert.assertEquals(2, out.length); - Assert.assertNull(out[0]); - Assert.assertNull(out[1]); + assertEquals(2, out.length); + assertNull(out[0]); + assertNull(out[1]); } @Test @@ -298,14 +315,14 @@ public void testCreateEmptyArrayOfIntViaAlias() throws SQLException { pstmt.setArray(1, conn.createArrayOf("integer", in)); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); Integer[] out = (Integer[]) arr.getArray(); - Assert.assertEquals(0, out.length); + assertEquals(0, out.length); ResultSet arrRs = arr.getResultSet(); - Assert.assertFalse(arrRs.next()); + assertFalse(arrRs.next()); } @Test @@ -319,12 +336,12 @@ public void testCreateArrayWithoutServer() throws SQLException { Array arr = conn.createArrayOf("varchar", in); String[][] out = (String[][]) arr.getArray(); - Assert.assertEquals(2, out.length); - Assert.assertEquals(2, out[0].length); - Assert.assertEquals("a", out[0][0]); - Assert.assertEquals("", out[0][1]); - Assert.assertEquals("\\", out[1][0]); - Assert.assertEquals("\"\\'z", out[1][1]); + assertEquals(2, out.length); + assertEquals(2, out[0].length); + assertEquals("a", out[0][0]); + assertEquals("", out[0][1]); + assertEquals("\\", out[1][0]); + assertEquals("\"\\'z", out[1][1]); } @Test @@ -338,20 +355,18 @@ public void testCreatePrimitiveArray() throws SQLException { Array arr = conn.createArrayOf("float8", in); Double[][] out = (Double[][]) arr.getArray(); - Assert.assertEquals(2, out.length); - Assert.assertEquals(2, out[0].length); - Assert.assertEquals(3.5, out[0][0], 0.00001); - Assert.assertEquals(-4.5, out[0][1], 0.00001); - Assert.assertEquals(10.0 / 3, out[1][0], 0.00001); - Assert.assertEquals(77, out[1][1], 0.00001); + assertEquals(2, out.length); + assertEquals(2, out[0].length); + assertEquals(3.5, out[0][0], 0.00001); + assertEquals(-4.5, out[0][1], 0.00001); + assertEquals(10.0 / 3, out[1][0], 0.00001); + assertEquals(77, out[1][1], 0.00001); } @Test public void testUUIDArray() throws SQLException { - Assume.assumeTrue("UUID is not supported in PreferQueryMode.SIMPLE", - preferQueryMode != PreferQueryMode.SIMPLE); - Assume.assumeTrue("UUID requires PostgreSQL 8.3+", - TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3)); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE, "UUID is not supported in PreferQueryMode.SIMPLE"); + assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3), "UUID requires PostgreSQL 8.3+"); UUID uuid1 = UUID.randomUUID(); UUID uuid2 = UUID.randomUUID(); UUID uuid3 = UUID.randomUUID(); @@ -365,14 +380,14 @@ public void testUUIDArray() throws SQLException { conn.prepareStatement("SELECT uuidarr FROM arrtest WHERE uuidarr @> ?"); pstmt2.setObject(1, conn.createArrayOf("uuid", new UUID[]{uuid1}), Types.OTHER); ResultSet rs = pstmt2.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array arr = rs.getArray(1); UUID[] out = (UUID[]) arr.getArray(); - Assert.assertEquals(3, out.length); - Assert.assertEquals(uuid1, out[0]); - Assert.assertEquals(uuid2, out[1]); - Assert.assertEquals(uuid3, out[2]); + assertEquals(3, out.length); + assertEquals(uuid1, out[0]); + assertEquals(uuid2, out[1]); + assertEquals(uuid3, out[2]); // concatenate a uuid, and check UUID uuid4 = UUID.randomUUID(); @@ -385,20 +400,20 @@ public void testUUIDArray() throws SQLException { // -- pstmt2.setObject(1, conn.createArrayOf("uuid", new UUID[]{uuid4}), Types.OTHER); rs = pstmt2.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); arr = rs.getArray(1); out = (UUID[]) arr.getArray(); - Assert.assertEquals(4, out.length); - Assert.assertEquals(uuid1, out[0]); - Assert.assertEquals(uuid2, out[1]); - Assert.assertEquals(uuid3, out[2]); - Assert.assertEquals(uuid4, out[3]); + assertEquals(4, out.length); + assertEquals(uuid1, out[0]); + assertEquals(uuid2, out[1]); + assertEquals(uuid3, out[2]); + assertEquals(uuid4, out[3]); } @Test public void testSetObjectFromJavaArray() throws SQLException { - String[] strArray = new String[] { "a", "b", "c" }; + String[] strArray = new String[]{"a", "b", "c"}; Object[] objCopy = Arrays.copyOf(strArray, strArray.length, Object[].class); PreparedStatement pstmt = conn.prepareStatement("INSERT INTO arrtest(strarr) VALUES (?)"); @@ -407,7 +422,7 @@ public void testSetObjectFromJavaArray() throws SQLException { try { pstmt.setObject(1, objCopy, Types.ARRAY); pstmt.executeUpdate(); - Assert.fail("setObject() with a Java array parameter and Types.ARRAY shouldn't succeed"); + fail("setObject() with a Java array parameter and Types.ARRAY shouldn't succeed"); } catch (org.postgresql.util.PSQLException ex) { // Expected failure. } @@ -415,7 +430,7 @@ public void testSetObjectFromJavaArray() throws SQLException { try { pstmt.setObject(1, objCopy); pstmt.executeUpdate(); - Assert.fail("setObject() with a Java array parameter and no Types argument shouldn't succeed"); + fail("setObject() with a Java array parameter and no Types argument shouldn't succeed"); } catch (org.postgresql.util.PSQLException ex) { // Expected failure. } @@ -437,8 +452,7 @@ public void testSetObjectFromJavaArray() throws SQLException { @Test public void testGetArrayOfComposites() throws SQLException { - Assume.assumeTrue("array_agg(expression) requires PostgreSQL 8.4+", - TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_4)); + assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_4), "array_agg(expression) requires PostgreSQL 8.4+"); PreparedStatement insertParentPstmt = conn.prepareStatement("INSERT INTO arrcompprnttest (name) " @@ -481,7 +495,7 @@ public void testGetArrayOfComposites() throws SQLException { ResultSet rs = pstmt.executeQuery(); assertNotNull(rs); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Array childrenArray = rs.getArray("children"); assertNotNull(childrenArray); @@ -496,17 +510,16 @@ public void testGetArrayOfComposites() throws SQLException { int childID = Integer.parseInt(token.getToken(0)); // remove double quotes escaping with double quotes String value = token.getToken(2).replace("\"\"", "\""); - Assert.assertEquals(children[childID - 1], value); + assertEquals(children[childID - 1], value); } else { - Assert.fail("Needs to have 3 tokens"); + fail("Needs to have 3 tokens"); } } } @Test public void testCasingComposite() throws SQLException { - Assume.assumeTrue("Arrays of composite types requires PostgreSQL 8.3+", - TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3)); + assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3), "Arrays of composite types requires PostgreSQL 8.3+"); PGobject cc = new PGobject(); cc.setType("\"CorrectCasing\""); @@ -519,12 +532,12 @@ public void testCasingComposite() throws SQLException { pstmt.setArray(1, arr); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Object[] resArr = (Object[]) rs.getArray(1).getArray(); - Assert.assertTrue(resArr[0] instanceof PGobject); + assertInstanceOf(PGobject.class, resArr[0]); PGobject resObj = (PGobject) resArr[0]; - Assert.assertEquals("(1)", resObj.getValue()); + assertEquals("(1)", resObj.getValue()); } @Test @@ -534,10 +547,10 @@ public void testCasingBuiltinAlias() throws SQLException { pstmt.setArray(1, arr); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Integer[] resArr = (Integer[]) rs.getArray(1).getArray(); - Assert.assertArrayEquals(new Integer[]{1, 2, 3}, resArr); + assertArrayEquals(new Integer[]{1, 2, 3}, resArr); } @Test @@ -547,16 +560,15 @@ public void testCasingBuiltinNonAlias() throws SQLException { pstmt.setArray(1, arr); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Integer[] resArr = (Integer[]) rs.getArray(1).getArray(); - Assert.assertArrayEquals(new Integer[]{1, 2, 3}, resArr); + assertArrayEquals(new Integer[]{1, 2, 3}, resArr); } @Test public void testEvilCasing() throws SQLException { - Assume.assumeTrue("Arrays of composite types requires PostgreSQL 8.3+", - TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3)); + assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3), "Arrays of composite types requires PostgreSQL 8.3+"); PGobject cc = new PGobject(); cc.setType("\"Evil.Table\""); @@ -569,12 +581,12 @@ public void testEvilCasing() throws SQLException { pstmt.setArray(1, arr); ResultSet rs = pstmt.executeQuery(); - Assert.assertTrue(rs.next()); + assertTrue(rs.next()); Object[] resArr = (Object[]) rs.getArray(1).getArray(); - Assert.assertTrue(resArr[0] instanceof PGobject); + assertInstanceOf(PGobject.class, resArr[0]); PGobject resObj = (PGobject) resArr[0]; - Assert.assertEquals("(1)", resObj.getValue()); + assertEquals("(1)", resObj.getValue()); } @Test @@ -616,9 +628,8 @@ public void testToString() throws SQLException { actual = actual.replaceAll("\"", ""); } //the string format may vary based on how data stored - Assert.assertThat(actual, RegexMatcher.matchesPattern("\\{3\\.5,-4\\.5,NULL,77(.0)?\\}")); + MatcherAssert.assertThat(actual, RegexMatcher.matchesPattern("\\{3\\.5,-4\\.5,NULL,77(.0)?\\}")); } - } finally { TestUtil.closeQuietly(rs); TestUtil.closeQuietly(stmt); @@ -635,23 +646,23 @@ public void nullArray() throws SQLException { ps.close(); ps = con.prepareStatement("select floatarr from arrtest"); ResultSet rs = ps.executeQuery(); - Assert.assertTrue("arrtest should contain a row", rs.next()); + assertTrue(rs.next(), "arrtest should contain a row"); Array getArray = rs.getArray(1); - Assert.assertNull("null array should return null value on getArray", getArray); + assertNull(getArray, "null array should return null value on getArray"); Object getObject = rs.getObject(1); - Assert.assertNull("null array should return null on getObject", getObject); + assertNull(getObject, "null array should return null on getObject"); } @Test public void createNullArray() throws SQLException { Array arr = con.createArrayOf("float8", null); assertNotNull(arr); - Assert.assertNull(arr.getArray()); + assertNull(arr.getArray()); } @Test public void multiDimIntArray() throws SQLException { - Array arr = con.createArrayOf("int4", new int[][]{{1,2}, {3,4}}); + Array arr = con.createArrayOf("int4", new int[][]{{1, 2}, {3, 4}}); PreparedStatement ps = con.prepareStatement("select ?::int4[][]"); ps.setArray(1, arr); ResultSet rs = ps.executeQuery(); @@ -665,14 +676,14 @@ public void multiDimIntArray() throws SQLException { } // Both {{"1","2"},{"3","4"}} and {{1,2},{3,4}} are the same array representation stringValue = stringValue.replaceAll("\"", ""); - Assert.assertEquals("{{1,2},{3,4}}", stringValue); + assertEquals("{{1,2},{3,4}}", stringValue); TestUtil.closeQuietly(rs); TestUtil.closeQuietly(ps); } @Test public void insertAndQueryMultiDimArray() throws SQLException { - Array arr = con.createArrayOf("int4", new int[][] { { 1, 2 }, { 3, 4 } }); + Array arr = con.createArrayOf("int4", new int[][]{{1, 2}, {3, 4}}); PreparedStatement insertPs = con.prepareStatement("INSERT INTO arrtest(intarr2) VALUES (?)"); insertPs.setArray(1, arr); insertPs.execute(); @@ -685,14 +696,14 @@ public void insertAndQueryMultiDimArray() throws SQLException { Array array = rs.getArray(1); Integer[][] secondRowValues = (Integer[][]) array.getArray(2, 1); - Assert.assertEquals(3, secondRowValues[0][0].intValue()); - Assert.assertEquals(4, secondRowValues[0][1].intValue()); + assertEquals(3, secondRowValues[0][0].intValue()); + assertEquals(4, secondRowValues[0][1].intValue()); } @Test public void testJsonbArray() throws SQLException { - Assume.assumeTrue("jsonb requires PostgreSQL 9.4+", TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_4)); - TestUtil.createTempTable(con, "jsonbarray", "jbarray jsonb[]" ); + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_4), "jsonb requires PostgreSQL 9.4+"); + TestUtil.createTempTable(con, "jsonbarray", "jbarray jsonb[]"); try (Statement stmt = con.createStatement()) { stmt.executeUpdate("insert into jsonbarray values( ARRAY['{\"a\":\"a\"}'::jsonb, '{\"b\":\"b\"}'::jsonb] )"); try (ResultSet rs = stmt.executeQuery("select jbarray from jsonbarray")) { diff --git a/src/test/java/org/postgresql/test/jdbc4/BinaryStreamTest.java b/src/test/java/org/postgresql/test/jdbc4/BinaryStreamTest.java index 3ed4230..68aee9f 100644 --- a/src/test/java/org/postgresql/test/jdbc4/BinaryStreamTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/BinaryStreamTest.java @@ -5,30 +5,31 @@ package org.postgresql.test.jdbc4; -import org.postgresql.test.SlowTests; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Assert; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.nio.ByteBuffer; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.SQLException; import java.util.Random; public class BinaryStreamTest extends BaseTest4 { - private ByteBuffer testData; + private static ByteBuffer testData; - @Override - public void setUp() throws Exception { - super.setUp(); - assumeByteaSupported(); - TestUtil.createTable(con, "images", "img bytea"); + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "images", "img bytea"); + } Random random = new Random(31459); testData = ByteBuffer.allocate(200 * 1024); @@ -37,10 +38,12 @@ public void setUp() throws Exception { } } - @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "images"); - super.tearDown(); + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "images"); + } + testData = null; } private void insertStreamKownLength(byte[] data) throws Exception { @@ -53,7 +56,7 @@ private void insertStreamKownLength(byte[] data) throws Exception { } } - private void insertStreamUnkownLength(byte[] data) throws Exception { + private void insertStreamUnknownLength(byte[] data) throws Exception { PreparedStatement updatePS = con.prepareStatement(TestUtil.insertSQL("images", "img", "?")); try { updatePS.setBinaryStream(1, new ByteArrayInputStream(data)); @@ -70,7 +73,7 @@ private void validateContent(byte[] data) throws Exception { try { rs.next(); byte[] actualData = rs.getBytes(1); - Assert.assertArrayEquals("Sent and received data are not the same", data, actualData); + assertArrayEquals(data, actualData, "Sent and received data are not the same"); } finally { rs.close(); } @@ -115,7 +118,6 @@ public void testKnownLength10Kb() throws Exception { } @Test - @Category(SlowTests.class) public void testKnownLength100Kb() throws Exception { byte[] data = getTestData(100 * 1024); insertStreamKownLength(data); @@ -123,7 +125,6 @@ public void testKnownLength100Kb() throws Exception { } @Test - @Category(SlowTests.class) public void testKnownLength200Kb() throws Exception { byte[] data = getTestData(200 * 1024); insertStreamKownLength(data); @@ -133,37 +134,35 @@ public void testKnownLength200Kb() throws Exception { @Test public void testUnknownLengthEmpty() throws Exception { byte[] data = getTestData(2 * 1024); - insertStreamUnkownLength(data); + insertStreamUnknownLength(data); validateContent(data); } @Test public void testUnknownLength2Kb() throws Exception { byte[] data = getTestData(2 * 1024); - insertStreamUnkownLength(data); + insertStreamUnknownLength(data); validateContent(data); } @Test public void testUnknownLength10Kb() throws Exception { byte[] data = getTestData(10 * 1024); - insertStreamUnkownLength(data); + insertStreamUnknownLength(data); validateContent(data); } @Test - @Category(SlowTests.class) public void testUnknownLength100Kb() throws Exception { byte[] data = getTestData(100 * 1024); - insertStreamUnkownLength(data); + insertStreamUnknownLength(data); validateContent(data); } @Test - @Category(SlowTests.class) public void testUnknownLength200Kb() throws Exception { byte[] data = getTestData(200 * 1024); - insertStreamUnkownLength(data); + insertStreamUnknownLength(data); validateContent(data); } } diff --git a/src/test/java/org/postgresql/test/jdbc4/BinaryTest.java b/src/test/java/org/postgresql/test/jdbc4/BinaryTest.java index 1089e3a..56c4c6a 100644 --- a/src/test/java/org/postgresql/test/jdbc4/BinaryTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/BinaryTest.java @@ -5,18 +5,21 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.PGConnection; import org.postgresql.PGResultSetMetaData; import org.postgresql.PGStatement; import org.postgresql.core.Field; +import org.postgresql.jdbc.PgStatement; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import java.nio.ByteBuffer; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -34,8 +37,9 @@ public class BinaryTest extends BaseTest4 { @Override public void setUp() throws Exception { super.setUp(); - Assume.assumeTrue("Server-prepared statements are not supported in 'simple protocol only'", - preferQueryMode != PreferQueryMode.SIMPLE); + assumeTrue( + preferQueryMode != PreferQueryMode.SIMPLE, + "Server-prepared statements are not supported in 'simple protocol only'"); statement = con.prepareStatement("select 1"); ((PGStatement) statement).setPrepareThreshold(5); @@ -123,14 +127,43 @@ public void testReceiveBinary() throws Exception { for (int i = 0; i < 10; i++) { ps.setInt(1, 42 + i); ResultSet rs = ps.executeQuery(); - assertEquals("One row should be returned", true, rs.next()); + assertTrue(rs.next(), "One row should be returned"); assertEquals(42 + i, rs.getInt(1)); rs.close(); } ps.close(); } - private int getFormat(ResultSet results) throws SQLException { + @Test + public void testGetMetaDataBeforeExecuteQuery() throws SQLException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = con.prepareStatement("select ?::int8"); + PgStatement unwrap = ps.unwrap(PgStatement.class); + unwrap.setPrepareThreshold(-1); + ps.getMetaData(); + long paramsLong = 1000L; + ps.setLong(1, paramsLong); + rs = ps.executeQuery(); + assertTrue(rs.next(), "One row should be returned"); + byte[] bytes = rs.getBytes(1); + ByteBuffer bf = ByteBuffer.wrap(bytes); + long longResult = bf.getLong(); + assertEquals(Field.BINARY_FORMAT, getFormat(rs)); + assertEquals(paramsLong, longResult); + } finally { + if (rs != null) { + rs.close(); + } + if (ps != null) { + ps.close(); + } + } + + } + + private static int getFormat(ResultSet results) throws SQLException { return ((PGResultSetMetaData) results.getMetaData()).getFormat(1); } } diff --git a/src/test/java/org/postgresql/test/jdbc4/BlobTest.java b/src/test/java/org/postgresql/test/jdbc4/BlobTest.java index 58ed16e..2d22a93 100644 --- a/src/test/java/org/postgresql/test/jdbc4/BlobTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/BlobTest.java @@ -5,16 +5,18 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -29,136 +31,161 @@ * This test-case is only for JDBC4 blob methods. Take a look at * {@link org.postgresql.test.jdbc2.BlobTest} for base tests concerning blobs */ -public class BlobTest { +class BlobTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "testblob", "id name,lo oid"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "testblob"); + } + } + + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); - TestUtil.createTable(conn, "testblob", "id name,lo oid"); conn.setAutoCommit(false); } - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { conn.setAutoCommit(true); try { - Statement stmt = conn.createStatement(); - try { + try (Statement stmt = conn.createStatement()) { stmt.execute("SELECT lo_unlink(lo) FROM testblob"); - } finally { - try { - stmt.close(); - } catch (Exception e) { - } } + TestUtil.execute(conn, "TRUNCATE testblob"); } finally { - TestUtil.dropTable(conn, "testblob"); TestUtil.closeDB(conn); } } @Test - public void testSetBlobWithStream() throws Exception { + void setBlobWithStream() throws Exception { byte[] data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque bibendum dapibus varius." .getBytes("UTF-8"); - PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("testblob", "lo", "?")); - try { + try ( PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("testblob", "lo", "?")) ) { insertPS.setBlob(1, new ByteArrayInputStream(data)); insertPS.executeUpdate(); - } finally { - insertPS.close(); } - Statement selectStmt = conn.createStatement(); - try { - ResultSet rs = selectStmt.executeQuery(TestUtil.selectSQL("testblob", "lo")); - assertTrue(rs.next()); + try (Statement selectStmt = conn.createStatement() ) { + try (ResultSet rs = selectStmt.executeQuery(TestUtil.selectSQL("testblob", "lo"))) { + assertTrue(rs.next()); - Blob actualBlob = rs.getBlob(1); - byte[] actualBytes = actualBlob.getBytes(1, (int) actualBlob.length()); + Blob actualBlob = rs.getBlob(1); + byte[] actualBytes = actualBlob.getBytes(1, (int) actualBlob.length()); - assertArrayEquals(data, actualBytes); - } finally { - selectStmt.close(); + assertArrayEquals(data, actualBytes); + } } } @Test - public void testSetBlobWithStreamAndLength() throws Exception { + void setBlobWithStreamAndLength() throws Exception { byte[] fullData = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse placerat tristique tellus, id tempus lectus." .getBytes("UTF-8"); byte[] data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.".getBytes("UTF-8"); - PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("testblob", "lo", "?")); - try { + + try ( PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("testblob", "lo", "?")) ) { insertPS.setBlob(1, new ByteArrayInputStream(fullData), data.length); insertPS.executeUpdate(); - } finally { - insertPS.close(); } - Statement selectStmt = conn.createStatement(); - try { - ResultSet rs = selectStmt.executeQuery(TestUtil.selectSQL("testblob", "lo")); - assertTrue(rs.next()); + try ( Statement selectStmt = conn.createStatement() ) { + try (ResultSet rs = selectStmt.executeQuery(TestUtil.selectSQL("testblob", "lo"))) { + assertTrue(rs.next()); - Blob actualBlob = rs.getBlob(1); - byte[] actualBytes = actualBlob.getBytes(1, (int) actualBlob.length()); + Blob actualBlob = rs.getBlob(1); + byte[] actualBytes = actualBlob.getBytes(1, (int) actualBlob.length()); - assertArrayEquals(data, actualBytes); - } finally { - selectStmt.close(); + assertArrayEquals(data, actualBytes); + } } } @Test - public void testGetBinaryStreamWithBoundaries() throws Exception { + void getBinaryStreamWithBoundaries() throws Exception { byte[] data = "Cras vestibulum tellus eu sapien imperdiet ornare.".getBytes("UTF-8"); - PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("testblob", "lo", "?")); - try { + try ( PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("testblob", "lo", "?")) ) { insertPS.setBlob(1, new ByteArrayInputStream(data), data.length); insertPS.executeUpdate(); - } finally { - insertPS.close(); } + try ( Statement selectStmt = conn.createStatement() ) { + try (ResultSet rs = selectStmt.executeQuery(TestUtil.selectSQL("testblob", "lo"))) { + assertTrue(rs.next()); - Statement selectStmt = conn.createStatement(); - try { - ResultSet rs = selectStmt.executeQuery(TestUtil.selectSQL("testblob", "lo")); - assertTrue(rs.next()); - - byte[] actualData = new byte[10]; - Blob actualBlob = rs.getBlob(1); - InputStream stream = actualBlob.getBinaryStream(6, 10); - try { - stream.read(actualData); - assertEquals("Stream should be at end", -1, stream.read(new byte[1])); - } finally { - stream.close(); + byte[] actualData = new byte[10]; + Blob actualBlob = rs.getBlob(1); + InputStream stream = actualBlob.getBinaryStream(6, 10); + try { + stream.read(actualData); + assertEquals(-1, stream.read(new byte[1]), "Stream should be at end"); + } finally { + stream.close(); + } + assertEquals("vestibulum", new String(actualData, "UTF-8")); } - assertEquals("vestibulum", new String(actualData, "UTF-8")); - } finally { - selectStmt.close(); } } @Test - public void testFree() throws SQLException { - Statement stmt = conn.createStatement(); - stmt.execute("INSERT INTO testblob(lo) VALUES(lo_creat(-1))"); - ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob"); - assertTrue(rs.next()); - - Blob blob = rs.getBlob(1); - blob.free(); - try { - blob.length(); - fail("Should have thrown an Exception because it was freed."); - } catch (SQLException sqle) { - // expected + void getBinaryStreamWithBoundaries2() throws Exception { + byte[] data = + "Cras vestibulum tellus eu sapien imperdiet ornare.".getBytes("UTF-8"); + + try ( PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("testblob", "lo", "?")) ) { + insertPS.setBlob(1, new ByteArrayInputStream(data), data.length); + insertPS.executeUpdate(); + } + + try ( Statement selectStmt = conn.createStatement() ) { + try (ResultSet rs = selectStmt.executeQuery(TestUtil.selectSQL("testblob", "lo"))) { + assertTrue(rs.next()); + + byte[] actualData = new byte[9]; + Blob actualBlob = rs.getBlob(1); + try ( InputStream stream = actualBlob.getBinaryStream(6, 10) ) { + // read 9 bytes 1 at a time + for (int i = 0; i < 9; i++) { + actualData[i] = (byte) stream.read(); + } + /* try to read past the end and make sure we get 1 byte */ + assertEquals(1, stream.read(new byte[2]), "There should be 1 byte left"); + /* now read one more and we should get an EOF */ + assertEquals(-1, stream.read(new byte[1]), "Stream should be at end"); + } + assertEquals("vestibulu", new String(actualData, "UTF-8")); + } + } + } + + @Test + void free() throws SQLException { + try ( Statement stmt = conn.createStatement() ) { + stmt.execute("INSERT INTO testblob(lo) VALUES(lo_creat(-1))"); + try (ResultSet rs = stmt.executeQuery("SELECT lo FROM testblob")) { + assertTrue(rs.next()); + + Blob blob = rs.getBlob(1); + blob.free(); + try { + blob.length(); + fail("Should have thrown an Exception because it was freed."); + } catch (SQLException sqle) { + // expected + } + } } } } diff --git a/src/test/java/org/postgresql/test/jdbc4/CharacterStreamTest.java b/src/test/java/org/postgresql/test/jdbc4/CharacterStreamTest.java index a9420fd..b260033 100644 --- a/src/test/java/org/postgresql/test/jdbc4/CharacterStreamTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/CharacterStreamTest.java @@ -5,13 +5,13 @@ package org.postgresql.test.jdbc4; -import org.postgresql.test.SlowTests; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Assert; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.Test; import java.io.Reader; import java.io.StringReader; @@ -40,8 +40,8 @@ public void setUp() throws Exception { private void insertStreamKnownIntLength(String data) throws Exception { PreparedStatement insertPS = con.prepareStatement(_insert); try { - Reader reader = (data != null) ? new StringReader(data) : null; - int length = (data != null) ? data.length() : 0; + Reader reader = data != null ? new StringReader(data) : null; + int length = data != null ? data.length() : 0; insertPS.setCharacterStream(1, reader, length); insertPS.executeUpdate(); } finally { @@ -52,9 +52,13 @@ private void insertStreamKnownIntLength(String data) throws Exception { private void insertStreamKnownLongLength(String data) throws Exception { PreparedStatement insertPS = con.prepareStatement(_insert); try { - Reader reader = (data != null) ? new StringReader(data) : null; - long length = (data != null) ? data.length() : 0; - insertPS.setCharacterStream(1, reader, length); + Reader reader = data != null ? new StringReader(data) : null; + long length = data != null ? data.length() : 0; + try { + insertPS.setCharacterStream(1, reader, length); + } catch (SQLFeatureNotSupportedException e) { + assumeTrue(false, "PreparedStatement.setCharacterStream(int, Reader, long) is not implemented"); + } insertPS.executeUpdate(); } finally { TestUtil.closeQuietly(insertPS); @@ -64,7 +68,7 @@ private void insertStreamKnownLongLength(String data) throws Exception { private void insertStreamUnknownLength(String data) throws Exception { PreparedStatement insertPS = con.prepareStatement(_insert); try { - Reader reader = (data != null) ? new StringReader(data) : null; + Reader reader = data != null ? new StringReader(data) : null; insertPS.setCharacterStream(1, reader); insertPS.executeUpdate(); } finally { @@ -74,10 +78,10 @@ private void insertStreamUnknownLength(String data) throws Exception { private void validateContent(String data) throws Exception { String actualData = TestUtil.queryForString(con, _select); - Assert.assertEquals("Sent and received data are not the same", data, actualData); + assertEquals(data, actualData, "Sent and received data are not the same"); } - private String getTestData(int size) { + private static String getTestData(int size) { StringBuilder buf = new StringBuilder(size); String s = "This is a test string.\n"; int slen = s.length(); @@ -103,7 +107,7 @@ public void testKnownIntLengthNull() throws Exception { validateContent(data); } - @Test(expected = SQLFeatureNotSupportedException.class) + @Test public void testKnownLongLengthNull() throws Exception { String data = null; insertStreamKnownLongLength(data); @@ -124,7 +128,7 @@ public void testKnownIntLengthEmpty() throws Exception { validateContent(data); } - @Test(expected = SQLFeatureNotSupportedException.class) + @Test public void testKnownLongLengthEmpty() throws Exception { String data = ""; insertStreamKnownLongLength(data); @@ -145,7 +149,7 @@ public void testKnownIntLength2Kb() throws Exception { validateContent(data); } - @Test(expected = SQLFeatureNotSupportedException.class) + @Test public void testKnownLongLength2Kb() throws Exception { String data = getTestData(2 * 1024); insertStreamKnownLongLength(data); @@ -166,7 +170,7 @@ public void testKnownIntLength10Kb() throws Exception { validateContent(data); } - @Test(expected = SQLFeatureNotSupportedException.class) + @Test public void testKnownLongLength10Kb() throws Exception { String data = getTestData(10 * 1024); insertStreamKnownLongLength(data); @@ -181,15 +185,13 @@ public void testUnknownLength10Kb() throws Exception { } @Test - @Category(SlowTests.class) public void testKnownIntLength100Kb() throws Exception { String data = getTestData(100 * 1024); insertStreamKnownIntLength(data); validateContent(data); } - @Test(expected = SQLFeatureNotSupportedException.class) - @Category(SlowTests.class) + @Test public void testKnownLongLength100Kb() throws Exception { String data = getTestData(100 * 1024); insertStreamKnownLongLength(data); @@ -197,7 +199,6 @@ public void testKnownLongLength100Kb() throws Exception { } @Test - @Category(SlowTests.class) public void testUnknownLength100Kb() throws Exception { String data = getTestData(100 * 1024); insertStreamUnknownLength(data); @@ -205,15 +206,13 @@ public void testUnknownLength100Kb() throws Exception { } @Test - @Category(SlowTests.class) public void testKnownIntLength200Kb() throws Exception { String data = getTestData(200 * 1024); insertStreamKnownIntLength(data); validateContent(data); } - @Test(expected = SQLFeatureNotSupportedException.class) - @Category(SlowTests.class) + @Test public void testKnownLongLength200Kb() throws Exception { String data = getTestData(200 * 1024); insertStreamKnownLongLength(data); @@ -221,7 +220,6 @@ public void testKnownLongLength200Kb() throws Exception { } @Test - @Category(SlowTests.class) public void testUnknownLength200Kb() throws Exception { String data = getTestData(200 * 1024); insertStreamUnknownLength(data); diff --git a/src/test/java/org/postgresql/test/jdbc4/ClientInfoTest.java b/src/test/java/org/postgresql/test/jdbc4/ClientInfoTest.java index 7b167a3..4276092 100644 --- a/src/test/java/org/postgresql/test/jdbc4/ClientInfoTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/ClientInfoTest.java @@ -5,16 +5,16 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.ResultSet; import java.sql.SQLClientInfoException; @@ -57,10 +57,14 @@ public void testExplicitSetAppNameNotificationIsParsed() throws SQLException { Statement s = con.createStatement(); s.execute("set application_name='" + appName + "'"); s.close(); - assertEquals("application_name was set to " + appName + ", and it should be visible via " - + "con.getClientInfo", appName, con.getClientInfo("ApplicationName")); - assertEquals("application_name was set to " + appName + ", and it should be visible via " - + "con.getClientInfo", appName, con.getClientInfo().get("ApplicationName")); + assertEquals( + appName, + con.getClientInfo("ApplicationName"), + () -> "set application_name='...' should be visible via con.getClientInfo(\"ApplicationName\")"); + assertEquals( + appName, + con.getClientInfo().get("ApplicationName"), + () -> "set application_name='...' should be visible via con.getClientInfo().get(\"ApplicationName\")"); } @Test @@ -83,9 +87,9 @@ public void testSetAppNameProps() throws SQLException { @Test public void testWarningOnUnknownName() throws SQLException { try { - con.setClientInfo("UnexisingClientInfoName", "NoValue"); + con.setClientInfo("NonexistentClientInfoName", "NoValue"); } catch (SQLClientInfoException e) { - fail("Trying to set an unexisting name must not throw an exception (spec)"); + fail("Trying to set a nonexistent name must not throw an exception (spec)"); } assertNotNull(con.getWarnings()); } diff --git a/src/test/java/org/postgresql/test/jdbc4/ConnectionValidTimeoutTest.java b/src/test/java/org/postgresql/test/jdbc4/ConnectionValidTimeoutTest.java index e3f393c..6eb9a77 100644 --- a/src/test/java/org/postgresql/test/jdbc4/ConnectionValidTimeoutTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/ConnectionValidTimeoutTest.java @@ -5,39 +5,25 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; +import org.postgresql.test.annotations.EnabledForServerVersionRange; import org.postgresql.test.util.StrangeProxyServer; -import org.postgresql.test.util.rules.annotation.HaveMinimalServerVersion; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.Timeout; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.Connection; import java.util.Arrays; import java.util.Properties; -import java.util.concurrent.TimeUnit; -@RunWith(Parameterized.class) -@HaveMinimalServerVersion("9.4") +@EnabledForServerVersionRange(gte = "9.4") public class ConnectionValidTimeoutTest { - @Rule - public Timeout timeout = new Timeout(30, TimeUnit.SECONDS); - - @Parameterized.Parameter(0) - public int networkTimeoutMillis; - @Parameterized.Parameter(1) - public int validationTimeoutSeconds; - @Parameterized.Parameter(2) - public int expectedMaxValidationTimeMillis; - - @Parameterized.Parameters(name = "networkTimeoutMillis={0}, validationTimeoutSeconds={1}, expectedMaxValidationTimeMillis={2}") public static Iterable data() { return Arrays.asList(new Object[][]{ {500, 1, 600}, @@ -47,16 +33,19 @@ public static Iterable data() { }); } - @Test - public void testIsValidRespectsSmallerTimeout() throws Exception { + @MethodSource("data") + @ParameterizedTest + @Timeout(30) + void isValidRespectsSmallerTimeout(int networkTimeoutMillis, int validationTimeoutSeconds, int expectedMaxValidationTimeMillis) throws Exception { try (StrangeProxyServer proxyServer = new StrangeProxyServer(TestUtil.getServer(), TestUtil.getPort())) { final Properties props = new Properties(); - props.setProperty(TestUtil.SERVER_HOST_PORT_PROP, String.format("%s:%s", "localhost", proxyServer.getServerPort())); + TestUtil.setTestUrlProperty(props, PGProperty.PG_HOST, "localhost"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_PORT, String.valueOf(proxyServer.getServerPort())); try (Connection conn = TestUtil.openDB(props)) { - assertTrue("Connection through proxy should be valid", conn.isValid(validationTimeoutSeconds)); + assertTrue(conn.isValid(validationTimeoutSeconds), "Connection through proxy should be valid"); conn.setNetworkTimeout(null, networkTimeoutMillis); - assertTrue("Connection through proxy should still be valid", conn.isValid(validationTimeoutSeconds)); + assertTrue(conn.isValid(validationTimeoutSeconds), "Connection through proxy should still be valid"); proxyServer.stopForwardingOlderClients(); @@ -64,17 +53,17 @@ public void testIsValidRespectsSmallerTimeout() throws Exception { boolean result = conn.isValid(validationTimeoutSeconds); long elapsed = System.currentTimeMillis() - start; - assertFalse("Broken connection should not be valid", result); + assertFalse(result, "Broken connection should not be valid"); - assertTrue(String.format( + assertTrue(elapsed <= expectedMaxValidationTimeMillis, + String.format( "Connection validation should not take longer than %d ms" + " when network timeout is %d ms and validation timeout is %d s" + " (actual result: %d ms)", expectedMaxValidationTimeMillis, networkTimeoutMillis, validationTimeoutSeconds, - elapsed), - elapsed <= expectedMaxValidationTimeMillis + elapsed) ); } } diff --git a/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataHideUnprivilegedObjectsTest.java b/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataHideUnprivilegedObjectsTest.java index a057b36..8c7afe4 100644 --- a/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataHideUnprivilegedObjectsTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataHideUnprivilegedObjectsTest.java @@ -8,17 +8,17 @@ import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGProperty; import org.postgresql.core.ServerVersion; import org.postgresql.jdbc.PgConnection; import org.postgresql.test.TestUtil; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -29,8 +29,6 @@ import java.util.List; import java.util.Properties; -; - /** * Tests that database objects for which the current user has no privileges are filtered out from * the DatabaseMetaData depending on whether the connection parameter hideUnprivilegedObjects is @@ -39,14 +37,14 @@ public class DatabaseMetaDataHideUnprivilegedObjectsTest { public static final String COLUMNS = "digit int4, name text"; private static Connection hidingCon; - private static Connection nonhidingCon; + private static Connection nonHidingCon; private static Connection privilegedCon; private static PgConnection pgConnection; private static DatabaseMetaData hidingDatabaseMetaData; - private static DatabaseMetaData nonhidingDatabaseMetaData; + private static DatabaseMetaData nonHidingDatabaseMetaData; - @BeforeClass - public static void setUp() throws Exception { + @BeforeAll + static void setUp() throws Exception { Properties props = new Properties(); privilegedCon = TestUtil.openPrivilegedDB(); pgConnection = privilegedCon.unwrap(PgConnection.class); @@ -69,7 +67,7 @@ public static void setUp() throws Exception { stmt.close(); - nonhidingDatabaseMetaData = getNonHidingDatabaseMetaData(props); + nonHidingDatabaseMetaData = getNonHidingDatabaseMetaData(props); hidingDatabaseMetaData = getHidingDatabaseMetaData(props); } @@ -84,8 +82,8 @@ private static DatabaseMetaData getHidingDatabaseMetaData(Properties props) thro } private static DatabaseMetaData getNonHidingDatabaseMetaData(Properties props) throws Exception { - nonhidingCon = TestUtil.openDB(props); - return nonhidingCon.getMetaData(); + nonHidingCon = TestUtil.openDB(props); + return nonHidingCon.getMetaData(); } private static void createTestDataObjectsWithRangeOfPrivilegesInSchema(String schema) @@ -212,7 +210,7 @@ private static void revokeAllOnTables(String schema, String[] tables stmt.executeUpdate("REVOKE ALL ON TABLE " + schema + "." + table + " FROM public RESTRICT"); stmt.executeUpdate( "REVOKE ALL ON TABLE " + schema + "." + table + " FROM " + TestUtil.getUser() - + " RESTRICT"); + + " RESTRICT"); } stmt.close(); } @@ -224,10 +222,10 @@ private static void createSimpleTablesInSchema(String schema, String[] tables } } - @AfterClass - public static void tearDown() throws SQLException { + @AfterAll + static void tearDown() throws SQLException { TestUtil.closeDB(hidingCon); - TestUtil.closeDB(nonhidingCon); + TestUtil.closeDB(nonHidingCon); TestUtil.dropSchema(privilegedCon, "high_privileges_schema"); TestUtil.dropSchema(privilegedCon, "low_privileges_schema"); TestUtil.dropSchema(privilegedCon, "no_privileges_schema"); @@ -240,13 +238,13 @@ private static boolean isSuperUser(Connection connection) throws SQLException { st.executeQuery("SHOW is_superuser;"); ResultSet rs = st.getResultSet(); rs.next(); // One row is guaranteed - boolean connIsSuper = rs.getString(1).equalsIgnoreCase("on"); + boolean connIsSuper = "on".equalsIgnoreCase(rs.getString(1)); st.close(); return connIsSuper; } @Test - public void testGetSchemas() throws SQLException { + void getSchemas() throws SQLException { List schemasWithHiding = getSchemaNames(hidingDatabaseMetaData); assertThat(schemasWithHiding, hasItems("pg_catalog", "information_schema", @@ -254,14 +252,14 @@ public void testGetSchemas() throws SQLException { assertThat(schemasWithHiding, not(hasItem("no_privileges_schema"))); - List schemasWithNoHiding = getSchemaNames(nonhidingDatabaseMetaData); + List schemasWithNoHiding = getSchemaNames(nonHidingDatabaseMetaData); assertThat(schemasWithNoHiding, hasItems("pg_catalog", "information_schema", "high_privileges_schema", "low_privileges_schema", "no_privileges_schema")); } List getSchemaNames(DatabaseMetaData databaseMetaData) throws SQLException { - List schemaNames = new ArrayList(); + List schemaNames = new ArrayList<>(); ResultSet rs = databaseMetaData.getSchemas(); while (rs.next()) { schemaNames.add(rs.getString("TABLE_SCHEM")); @@ -270,7 +268,7 @@ List getSchemaNames(DatabaseMetaData databaseMetaData) throws SQLExcepti } @Test - public void testGetTables() throws SQLException { + void getTables() throws SQLException { List tablesWithHiding = getTableNames(hidingDatabaseMetaData, "high_privileges_schema"); assertThat(tablesWithHiding, @@ -283,7 +281,7 @@ public void testGetTables() throws SQLException { not(hasItem("no_grants_table"))); List tablesWithNoHiding = - getTableNames(nonhidingDatabaseMetaData, "high_privileges_schema"); + getTableNames(nonHidingDatabaseMetaData, "high_privileges_schema"); assertThat(tablesWithNoHiding, hasItems( "owned_table", @@ -304,7 +302,7 @@ public void testGetTables() throws SQLException { not(hasItem("no_grants_table"))); tablesWithNoHiding = - getTableNames(nonhidingDatabaseMetaData, "low_privileges_schema"); + getTableNames(nonHidingDatabaseMetaData, "low_privileges_schema"); assertThat(tablesWithNoHiding, hasItems( "owned_table", @@ -313,7 +311,7 @@ public void testGetTables() throws SQLException { "select_granted_table", "no_grants_table")); - // Or should the the tables names not be returned because the schema is not visible? + // Or should the tables names not be returned because the schema is not visible? tablesWithHiding = getTableNames(hidingDatabaseMetaData, "no_privileges_schema"); assertThat(tablesWithHiding, @@ -326,7 +324,7 @@ public void testGetTables() throws SQLException { not(hasItem("no_grants_table"))); tablesWithNoHiding = - getTableNames(nonhidingDatabaseMetaData, "no_privileges_schema"); + getTableNames(nonHidingDatabaseMetaData, "no_privileges_schema"); assertThat(tablesWithNoHiding, hasItems( "owned_table", @@ -339,7 +337,7 @@ public void testGetTables() throws SQLException { List getTableNames(DatabaseMetaData databaseMetaData, String schemaPattern) throws SQLException { - List tableNames = new ArrayList(); + List tableNames = new ArrayList<>(); ResultSet rs = databaseMetaData.getTables(null, schemaPattern, null, new String[]{"TABLE"}); while (rs.next()) { tableNames.add(rs.getString("TABLE_NAME")); @@ -348,7 +346,7 @@ List getTableNames(DatabaseMetaData databaseMetaData, String schemaPatte } @Test - public void testGetViews() throws SQLException { + void getViews() throws SQLException { List viewsWithHiding = getViewNames(hidingDatabaseMetaData, "high_privileges_schema"); assertThat(viewsWithHiding, @@ -358,7 +356,7 @@ public void testGetViews() throws SQLException { not(hasItem("no_grants_view"))); List viewsWithNoHiding = - getViewNames(nonhidingDatabaseMetaData, "high_privileges_schema"); + getViewNames(nonHidingDatabaseMetaData, "high_privileges_schema"); assertThat(viewsWithNoHiding, hasItems( "select_granted_view", @@ -373,13 +371,13 @@ public void testGetViews() throws SQLException { not(hasItem("no_grants_view"))); viewsWithNoHiding = - getViewNames(nonhidingDatabaseMetaData, "low_privileges_schema"); + getViewNames(nonHidingDatabaseMetaData, "low_privileges_schema"); assertThat(viewsWithNoHiding, hasItems( "select_granted_view", "no_grants_view")); - // Or should the the view names not be returned because the schema is not visible? + // Or should the view names not be returned because the schema is not visible? viewsWithHiding = getViewNames(hidingDatabaseMetaData, "no_privileges_schema"); assertThat(viewsWithHiding, @@ -389,7 +387,7 @@ public void testGetViews() throws SQLException { not(hasItem("no_grants_view"))); viewsWithNoHiding = - getViewNames(nonhidingDatabaseMetaData, "no_privileges_schema"); + getViewNames(nonHidingDatabaseMetaData, "no_privileges_schema"); assertThat(viewsWithNoHiding, hasItems( "select_granted_view", @@ -399,7 +397,7 @@ public void testGetViews() throws SQLException { List getViewNames(DatabaseMetaData databaseMetaData, String schemaPattern) throws SQLException { - List viewNames = new ArrayList(); + List viewNames = new ArrayList<>(); ResultSet rs = databaseMetaData.getTables(null, schemaPattern, null, new String[]{"VIEW"}); while (rs.next()) { viewNames.add(rs.getString("TABLE_NAME")); @@ -408,7 +406,7 @@ List getViewNames(DatabaseMetaData databaseMetaData, String schemaPatter } @Test - public void testGetFunctions() throws SQLException { + void getFunctions() throws SQLException { List functionsWithHiding = getFunctionNames(hidingDatabaseMetaData, "high_privileges_schema"); assertThat(functionsWithHiding, @@ -417,7 +415,7 @@ public void testGetFunctions() throws SQLException { not(hasItem("no_grants_add_function"))); List functionsWithNoHiding = - getFunctionNames(nonhidingDatabaseMetaData, "high_privileges_schema"); + getFunctionNames(nonHidingDatabaseMetaData, "high_privileges_schema"); assertThat(functionsWithNoHiding, hasItems("execute_granted_add_function", "no_grants_add_function")); @@ -429,11 +427,11 @@ public void testGetFunctions() throws SQLException { not(hasItem("no_grants_add_function"))); functionsWithNoHiding = - getFunctionNames(nonhidingDatabaseMetaData, "low_privileges_schema"); + getFunctionNames(nonHidingDatabaseMetaData, "low_privileges_schema"); assertThat(functionsWithNoHiding, hasItems("execute_granted_add_function", "no_grants_add_function")); - // Or should the the function names not be returned because the schema is not visible? + // Or should the function names not be returned because the schema is not visible? functionsWithHiding = getFunctionNames(hidingDatabaseMetaData, "no_privileges_schema"); assertThat(functionsWithHiding, @@ -442,14 +440,14 @@ public void testGetFunctions() throws SQLException { not(hasItem("no_grants_add_function"))); functionsWithNoHiding = - getFunctionNames(nonhidingDatabaseMetaData, "no_privileges_schema"); + getFunctionNames(nonHidingDatabaseMetaData, "no_privileges_schema"); assertThat(functionsWithNoHiding, hasItems("execute_granted_add_function", "no_grants_add_function")); } List getFunctionNames(DatabaseMetaData databaseMetaData, String schemaPattern) throws SQLException { - List functionNames = new ArrayList(); + List functionNames = new ArrayList<>(); ResultSet rs = databaseMetaData.getFunctions(null, schemaPattern, null); while (rs.next()) { functionNames.add(rs.getString("FUNCTION_NAME")); @@ -458,7 +456,7 @@ List getFunctionNames(DatabaseMetaData databaseMetaData, String schemaPa } @Test - public void testGetProcedures() throws SQLException { + void getProcedures() throws SQLException { String executeGranted = TestUtil.haveMinimumServerVersion(hidingCon, ServerVersion.v11) ? "execute_granted_insert_procedure" : "execute_granted_add_function"; String noGrants = TestUtil.haveMinimumServerVersion(hidingCon, ServerVersion.v11) ? "no_grants_insert_procedure" : "no_grants_add_function"; @@ -470,7 +468,7 @@ public void testGetProcedures() throws SQLException { not(hasItem(noGrants))); List proceduresWithNoHiding = - getProcedureNames(nonhidingDatabaseMetaData, "high_privileges_schema"); + getProcedureNames(nonHidingDatabaseMetaData, "high_privileges_schema"); assertThat(proceduresWithNoHiding, hasItems(executeGranted, noGrants)); @@ -482,11 +480,11 @@ public void testGetProcedures() throws SQLException { not(hasItem(noGrants))); proceduresWithNoHiding = - getProcedureNames(nonhidingDatabaseMetaData, "low_privileges_schema"); + getProcedureNames(nonHidingDatabaseMetaData, "low_privileges_schema"); assertThat(proceduresWithNoHiding, hasItems(executeGranted, noGrants)); - // Or should the the function names not be returned because the schema is not visible? + // Or should the function names not be returned because the schema is not visible? proceduresWithHiding = getProcedureNames(hidingDatabaseMetaData, "no_privileges_schema"); assertThat(proceduresWithHiding, @@ -495,7 +493,7 @@ public void testGetProcedures() throws SQLException { not(hasItem(noGrants))); proceduresWithNoHiding = - getProcedureNames(nonhidingDatabaseMetaData, "no_privileges_schema"); + getProcedureNames(nonHidingDatabaseMetaData, "no_privileges_schema"); assertThat(proceduresWithNoHiding, hasItems(executeGranted, noGrants)); @@ -503,7 +501,7 @@ public void testGetProcedures() throws SQLException { List getProcedureNames(DatabaseMetaData databaseMetaData, String schemaPattern) throws SQLException { - List procedureNames = new ArrayList(); + List procedureNames = new ArrayList<>(); ResultSet rs = databaseMetaData.getProcedures(null, schemaPattern, null); while (rs.next()) { procedureNames.add(rs.getString("PROCEDURE_NAME")); @@ -511,11 +509,11 @@ List getProcedureNames(DatabaseMetaData databaseMetaData, String schemaP return procedureNames; } - @Test /* * According to the JDBC JavaDoc, the applicable UDTs are: JAVA_OBJECT, STRUCT, or DISTINCT. */ - public void testGetUDTs() throws SQLException { + @Test + void getUDTs() throws SQLException { if (pgConnection.haveMinimumServerVersion(ServerVersion.v9_2)) { List typesWithHiding = getTypeNames(hidingDatabaseMetaData, "high_privileges_schema"); assertThat(typesWithHiding, @@ -529,7 +527,7 @@ public void testGetUDTs() throws SQLException { assertThat(typesWithHiding, not(hasItems("no_grants_composite_type", "no_grants_us_postal_code_domain"))); - // Or should the the types names not be returned because the schema is not visible? + // Or should the types names not be returned because the schema is not visible? typesWithHiding = getTypeNames(hidingDatabaseMetaData, "no_privileges_schema"); assertThat(typesWithHiding, hasItems("usage_granted_composite_type", "usage_granted_us_postal_code_domain")); @@ -538,19 +536,19 @@ public void testGetUDTs() throws SQLException { } List typesWithNoHiding = - getTypeNames(nonhidingDatabaseMetaData, "high_privileges_schema"); + getTypeNames(nonHidingDatabaseMetaData, "high_privileges_schema"); assertThat(typesWithNoHiding, hasItems("usage_granted_composite_type", "no_grants_composite_type", "usage_granted_us_postal_code_domain", "no_grants_us_postal_code_domain")); typesWithNoHiding = - getTypeNames(nonhidingDatabaseMetaData, "low_privileges_schema"); + getTypeNames(nonHidingDatabaseMetaData, "low_privileges_schema"); assertThat(typesWithNoHiding, hasItems("usage_granted_composite_type", "no_grants_composite_type", "usage_granted_us_postal_code_domain", "no_grants_us_postal_code_domain")); typesWithNoHiding = - getTypeNames(nonhidingDatabaseMetaData, "no_privileges_schema"); + getTypeNames(nonHidingDatabaseMetaData, "no_privileges_schema"); assertThat(typesWithNoHiding, hasItems("usage_granted_composite_type", "no_grants_composite_type", "usage_granted_us_postal_code_domain", "no_grants_us_postal_code_domain")); @@ -562,7 +560,7 @@ public void testGetUDTs() throws SQLException { java.sql.Types.STRUCT to the Postgres type: TYPTYPE_DOMAIN 'd' # domain over another type */ List getTypeNames(DatabaseMetaData databaseMetaData, String schemaPattern) throws SQLException { - List typeNames = new ArrayList(); + List typeNames = new ArrayList<>(); ResultSet rs = databaseMetaData.getUDTs(null, schemaPattern, null, null); while (rs.next()) { typeNames.add(rs.getString("TYPE_NAME")); diff --git a/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java b/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java index 3986952..e56c60a 100644 --- a/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/DatabaseMetaDataTest.java @@ -7,22 +7,21 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.ServerVersion; -import org.postgresql.test.SlowTests; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Assume; -import org.junit.Before; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -32,46 +31,59 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; -public class DatabaseMetaDataTest { +class DatabaseMetaDataTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropSequence(conn, "sercoltest_a_seq"); + TestUtil.createTable(conn, "sercoltest", "a serial, b int"); + TestUtil.createSchema(conn, "hasfunctions"); + TestUtil.createSchema(conn, "nofunctions"); + TestUtil.createSchema(conn, "hasprocedures"); + TestUtil.createSchema(conn, "noprocedures"); + TestUtil.execute(conn, "create function hasfunctions.addfunction (integer, integer) " + + "RETURNS integer AS 'select $1 + $2;' LANGUAGE SQL IMMUTABLE"); + if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)) { + TestUtil.execute(conn, "create procedure hasprocedures.addprocedure() " + + "LANGUAGE plpgsql AS $$ BEGIN SELECT 1; END; $$"); + } + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropSequence(conn, "sercoltest_a_seq"); + TestUtil.dropTable(conn, "sercoltest"); + TestUtil.dropSchema(conn, "hasfunctions"); + TestUtil.dropSchema(conn, "nofunctions"); + TestUtil.dropSchema(conn, "hasprocedures"); + TestUtil.dropSchema(conn, "noprocedures"); + } + } + + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); - TestUtil.dropSequence(conn, "sercoltest_a_seq"); - TestUtil.createTable(conn, "sercoltest", "a serial, b int"); - TestUtil.createSchema(conn, "hasfunctions"); - TestUtil.createSchema(conn, "nofunctions"); - TestUtil.createSchema(conn, "hasprocedures"); - TestUtil.createSchema(conn, "noprocedures"); - TestUtil.execute("create function hasfunctions.addfunction (integer, integer) " - + "RETURNS integer AS 'select $1 + $2;' LANGUAGE SQL IMMUTABLE", conn); - if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)) { - TestUtil.execute("create procedure hasprocedures.addprocedure() " - + "LANGUAGE plpgsql AS $$ BEGIN SELECT 1; END; $$", conn); - } - } - - @After - public void tearDown() throws Exception { - TestUtil.dropSequence(conn, "sercoltest_a_seq"); - TestUtil.dropTable(conn, "sercoltest"); - TestUtil.dropSchema(conn, "hasfunctions"); - TestUtil.dropSchema(conn, "nofunctions"); - TestUtil.dropSchema(conn, "hasprocedures"); - TestUtil.dropSchema(conn, "noprocedures"); + } + + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(conn); } @Test - public void testGetClientInfoProperties() throws Exception { + void getClientInfoProperties() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getClientInfoProperties(); if (!TestUtil.haveMinimumServerVersion(conn, ServerVersion.v9_0)) { - assertTrue(!rs.next()); + assertFalse(rs.next()); return; } @@ -80,10 +92,18 @@ public void testGetClientInfoProperties() throws Exception { } @Test - public void testGetColumnsForAutoIncrement() throws Exception { + void getColumnsForAutoIncrement_whenCatalogArgPercentSign_expectNoResults() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getColumns("%", "%", "sercoltest", "%"); + assertFalse(rs.next()); + } + + @Test + void getColumnsForAutoIncrement() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getColumns(null, "%", "sercoltest", "%"); assertTrue(rs.next()); assertEquals("a", rs.getString("COLUMN_NAME")); assertEquals("YES", rs.getString("IS_AUTOINCREMENT")); @@ -92,72 +112,181 @@ public void testGetColumnsForAutoIncrement() throws Exception { assertEquals("b", rs.getString("COLUMN_NAME")); assertEquals("NO", rs.getString("IS_AUTOINCREMENT")); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test - public void testGetSchemas() throws SQLException { + void getSchemas_whenCatalogArgPercentSign_expectNoResults() throws SQLException { DatabaseMetaData dbmd = conn.getMetaData(); - ResultSet rs = dbmd.getSchemas("", "publ%"); + ResultSet rs = dbmd.getSchemas("%", "publ%"); + + assertFalse(rs.next()); + } + + @Test + void getSchemas() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getSchemas(null, "publ%"); assertTrue(rs.next()); assertEquals("public", rs.getString("TABLE_SCHEM")); - assertNull(rs.getString("TABLE_CATALOG")); - assertTrue(!rs.next()); + assertNotNull(rs.getString("TABLE_CATALOG")); + assertFalse(rs.next()); + } + + @Test + void getSchemas_whenNullCatalogAndSchemaPattern_expectRows() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getSchemas(null, null); + + assertTrue(rs.next()); + } + + @Test + void getSchemas_whenEmptySchemaPattern_expectNoRows() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getSchemas(null, ""); + + assertFalse(rs.next()); } @Test - public void testGetFunctionsInSchemaForFunctions() throws SQLException { + void getSchemas_whenEmptyCatalog_expectNoRows() throws SQLException { DatabaseMetaData dbmd = conn.getMetaData(); - try (ResultSet rs = dbmd.getFunctions("", "hasfunctions","")) { + ResultSet rs = dbmd.getSchemas("", null); + + assertFalse(rs.next()); + } + + @Test + void getSchemas_whenRandomCatalog_expectNoRows() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getSchemas(UUID.randomUUID().toString(), null); + + assertFalse(rs.next()); + } + + @Test + void getFunctionsInSchemaForFunctions_whenCatalogArgEmpty_expectNoResults() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + try (ResultSet rs = dbmd.getFunctions("", "hasfunctions", null)) { + assertFalse(rs.next()); + } + + try (ResultSet rs = dbmd.getFunctions("", "hasfunctions", "addfunction")) { + assertFalse(rs.next()); + } + + try (ResultSet rs = dbmd.getFunctions("", "nofunctions", null)) { + boolean hasFunctions = rs.next(); + assertFalse(hasFunctions, "There should be no functions in the nofunctions schema"); + } + } + + @Test + void getFunctionsInSchemaForFunctions() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + try (ResultSet rs = dbmd.getFunctions(null, "hasfunctions", null)) { List list = assertFunctionRSAndReturnList(rs); - assertEquals("There should be one function in the hasfunctions schema", list.size(), 1); + assertEquals(1, list.size(), "There should be one function in the hasfunctions schema"); assertListContains("getFunctions('', 'hasfunctions', '') must contain addfunction", list, "hasfunctions", "addfunction"); } - try (ResultSet rs = dbmd.getFunctions("", "hasfunctions", "addfunction")) { + try (ResultSet rs = dbmd.getFunctions(null, "hasfunctions", "addfunction")) { List list = assertFunctionRSAndReturnList(rs); - assertEquals("There should be one function in the hasfunctions schema with name addfunction", list.size(), 1); + assertEquals(1, list.size(), "There should be one function in the hasfunctions schema with name addfunction"); assertListContains("getFunctions('', 'hasfunctions', 'addfunction') must contain addfunction", list, "hasfunctions", "addfunction"); } - try (ResultSet rs = dbmd.getFunctions("", "nofunctions","")) { + try (ResultSet rs = dbmd.getFunctions(null, "nofunctions", null)) { boolean hasFunctions = rs.next(); - assertFalse("There should be no functions in the nofunctions schema", hasFunctions); + assertFalse(hasFunctions, "There should be no functions in the nofunctions schema"); } } @Test - public void testGetFunctionsInSchemaForProcedures() throws SQLException { + void getFunctionsInSchemaForProcedures_whenCatalogArgEmpty_expectNoResults() throws SQLException { // Due to the introduction of actual stored procedures in PostgreSQL 11, getFunctions should not return procedures for PostgreSQL versions 11+ // On older installation we do not create the procedures so the below schemas should all be empty DatabaseMetaData dbmd = conn.getMetaData(); // Search for functions in schema "hasprocedures" try (ResultSet rs = dbmd.getFunctions("", "hasprocedures", null)) { - assertFalse("The hasprocedures schema not return procedures from getFunctions", rs.next()); + assertFalse(rs.next(), "The hasprocedures schema not return procedures from getFunctions"); } // Search for functions in schema "noprocedures" (which should never expect records) try (ResultSet rs = dbmd.getFunctions("", "noprocedures", null)) { - assertFalse("The noprocedures schema should not have functions", rs.next()); + assertFalse(rs.next(), "The noprocedures schema should not have functions"); } // Search for functions by procedure name "addprocedure" try (ResultSet rs = dbmd.getFunctions("", "hasprocedures", "addprocedure")) { - assertFalse("Should not return procedures from getFunctions by schema + name", rs.next()); + assertFalse(rs.next(), "Should not return procedures from getFunctions by schema + name"); } } @Test - public void testGetProceduresInSchemaForFunctions() throws SQLException { + void getFunctionsInSchemaForProcedures() throws SQLException { + // Due to the introduction of actual stored procedures in PostgreSQL 11, getFunctions should not return procedures for PostgreSQL versions 11+ + // On older installation we do not create the procedures so the below schemas should all be empty + DatabaseMetaData dbmd = conn.getMetaData(); + + // Search for functions in schema "hasprocedures" + try (ResultSet rs = dbmd.getFunctions(null, "hasprocedures", null)) { + assertFalse(rs.next(), "The hasprocedures schema not return procedures from getFunctions"); + } + // Search for functions in schema "noprocedures" (which should never expect records) + try (ResultSet rs = dbmd.getFunctions(null, "noprocedures", null)) { + assertFalse(rs.next(), "The noprocedures schema should not have functions"); + } + // Search for functions by procedure name "addprocedure" + try (ResultSet rs = dbmd.getFunctions(null, "hasprocedures", "addprocedure")) { + assertFalse(rs.next(), "Should not return procedures from getFunctions by schema + name"); + } + } + + @Test + void getProceduresInSchemaForFunctions_whenCatalogArgEmpty_expectNoResults() throws SQLException { // Due to the introduction of actual stored procedures in PostgreSQL 11, getProcedures should not return functions for PostgreSQL versions 11+ DatabaseMetaData dbmd = conn.getMetaData(); // Search for procedures in schema "hasfunctions" (which should expect a record only for PostgreSQL < 11) - try (ResultSet rs = dbmd.getProcedures("", "hasfunctions",null)) { + try (ResultSet rs = dbmd.getProcedures("", "hasfunctions", null)) { + assertFalse(rs.next()); + } + + // Search for procedures in schema "nofunctions" (which should never expect records) + try (ResultSet rs = dbmd.getProcedures("", "nofunctions", null)) { + assertFalse(rs.next(), "getProcedures(...) should not return procedures for schema nofunctions"); + } + + // Search for procedures by function name "addfunction" within schema "hasfunctions" (which should expect a record for PostgreSQL < 11) + try (ResultSet rs = dbmd.getProcedures("", "hasfunctions", "addfunction")) { + assertFalse(rs.next()); + } + + // Search for procedures by function name "addfunction" within schema "nofunctions" (which should never expect records) + try (ResultSet rs = dbmd.getProcedures("", "nofunctions", "addfunction")) { + assertFalse(rs.next(), "getProcedures(...) should not return procedures for schema nofunctions + addfunction"); + } + } + + @Test + void getProceduresInSchemaForFunctions() throws SQLException { + // Due to the introduction of actual stored procedures in PostgreSQL 11, getProcedures should not return functions for PostgreSQL versions 11+ + DatabaseMetaData dbmd = conn.getMetaData(); + + // Search for procedures in schema "hasfunctions" (which should expect a record only for PostgreSQL < 11) + try (ResultSet rs = dbmd.getProcedures(null, "hasfunctions", null)) { if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)) { - assertFalse("PostgreSQL11+ should not return functions from getProcedures", rs.next()); + assertFalse(rs.next(), "PostgreSQL11+ should not return functions from getProcedures"); } else { // PostgreSQL prior to 11 should return functions from getProcedures assertProcedureRS(rs); @@ -165,14 +294,14 @@ public void testGetProceduresInSchemaForFunctions() throws SQLException { } // Search for procedures in schema "nofunctions" (which should never expect records) - try (ResultSet rs = dbmd.getProcedures("", "nofunctions", null)) { - assertFalse("getProcedures(...) should not return procedures for schema nofunctions", rs.next()); + try (ResultSet rs = dbmd.getProcedures(null, "nofunctions", null)) { + assertFalse(rs.next(), "getProcedures(...) should not return procedures for schema nofunctions"); } // Search for procedures by function name "addfunction" within schema "hasfunctions" (which should expect a record for PostgreSQL < 11) - try (ResultSet rs = dbmd.getProcedures("", "hasfunctions", "addfunction")) { + try (ResultSet rs = dbmd.getProcedures(null, "hasfunctions", "addfunction")) { if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)) { - assertFalse("PostgreSQL11+ should not return functions from getProcedures", rs.next()); + assertFalse(rs.next(), "PostgreSQL11+ should not return functions from getProcedures"); } else { // PostgreSQL prior to 11 should return functions from getProcedures assertProcedureRS(rs); @@ -180,76 +309,183 @@ public void testGetProceduresInSchemaForFunctions() throws SQLException { } // Search for procedures by function name "addfunction" within schema "nofunctions" (which should never expect records) - try (ResultSet rs = dbmd.getProcedures("", "nofunctions", "addfunction")) { - assertFalse("getProcedures(...) should not return procedures for schema nofunctions + addfunction", rs.next()); + try (ResultSet rs = dbmd.getProcedures(null, "nofunctions", "addfunction")) { + assertFalse(rs.next(), "getProcedures(...) should not return procedures for schema nofunctions + addfunction"); } } @Test - public void testGetProceduresInSchemaForProcedures() throws SQLException { + void getProceduresInSchemaForProcedures_whenCatalogArgEmpty_expectNoResults() throws SQLException { // Only run this test for PostgreSQL version 11+; assertions for versions prior would be vacuously true as we don't create a procedure in the setup for older versions - Assume.assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)); + assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)); DatabaseMetaData dbmd = conn.getMetaData(); try (ResultSet rs = dbmd.getProcedures("", "hasprocedures", null)) { - int count = assertProcedureRS(rs); - assertTrue("getProcedures() should be non-empty for the hasprocedures schema", count == 1); + assertFalse(rs.next()); } try (ResultSet rs = dbmd.getProcedures("", "noprocedures", null)) { - assertFalse("getProcedures() should be empty for the hasprocedures schema", rs.next()); + assertFalse(rs.next(), "getProcedures() should be empty for the noprocedures schema"); } try (ResultSet rs = dbmd.getProcedures("", "hasfunctions", null)) { - assertFalse("getProcedures() should be empty for the nofunctions schema", rs.next()); + assertFalse(rs.next(), "getProcedures() should be empty for the hasfunctions schema"); } try (ResultSet rs = dbmd.getProcedures("", "nofunctions", null)) { - assertFalse("getProcedures() should be empty for the nofunctions schema", rs.next()); + assertFalse(rs.next(), "getProcedures() should be empty for the nofunctions schema"); + } + } + + @Test + void getProceduresWithCorrectCatalogAndWithout() throws SQLException { + // Only run this test for PostgreSQL version 11+; assertions for versions prior would be vacuously true as we don't create a procedure in the setup for older versions + assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)); + + DatabaseMetaData dbmd = conn.getMetaData(); + try (ResultSet rs = dbmd.getProcedures(null, "hasprocedures", null)) { + int count = assertProcedureRS(rs); + assertEquals(1, count, "getProcedures() should be non-empty for the hasprocedures schema"); + } + try (ResultSet rs = dbmd.getProcedures("nonsensecatalog", "hasprocedures", null)) { + assertFalse(rs.next(),"This should not return results as the catalog is not the same as the database"); + } + try (ResultSet rs = dbmd.getProcedureColumns(null, "hasprocedures", null, null)) { + int count = assertProcedureColumnsRS(rs); + assertEquals(1, count, "getProcedureColumnss() should be non-empty for the hasprocedures schema"); + } + try (ResultSet rs = dbmd.getProcedureColumns("nonsensecatalog", "hasprocedures", null,null)) { + assertFalse(rs.next(),"This should not return results as the catalog is not the same as the database"); + } + } + + @Test + void getProceduresInSchemaForProcedures() throws SQLException { + // Only run this test for PostgreSQL version 11+; assertions for versions prior would be vacuously true as we don't create a procedure in the setup for older versions + assumeTrue(TestUtil.haveMinimumServerVersion(conn, ServerVersion.v11)); + + DatabaseMetaData dbmd = conn.getMetaData(); + + try (ResultSet rs = dbmd.getProcedures(null, "hasprocedures", null)) { + int count = assertProcedureRS(rs); + assertEquals(1, count, "getProcedures() should be non-empty for the hasprocedures schema"); + } + + try (ResultSet rs = dbmd.getProcedures(null, "noprocedures", null)) { + assertFalse(rs.next(), "getProcedures() should be empty for the noprocedures schema"); + } + + try (ResultSet rs = dbmd.getProcedures(null, "hasfunctions", null)) { + assertFalse(rs.next(), "getProcedures() should be empty for the hasfunctions schema"); + } + + try (ResultSet rs = dbmd.getProcedures(null, "nofunctions", null)) { + assertFalse(rs.next(), "getProcedures() should be empty for the nofunctions schema"); + } + } + + @Test + void getFunctionsWithEmptyPatterns() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + try (ResultSet rs = dbmd.getFunctions("", "", "")) { + assertFalse(rs.next()); + } + + try (ResultSet rs = dbmd.getFunctions("", null, null)) { + assertFalse(rs.next()); + } + + try (ResultSet rs = dbmd.getFunctions(null, "", null)) { + assertFalse(rs.next()); + } + + try (ResultSet rs = dbmd.getFunctions(null, null, "")) { + assertFalse(rs.next()); } } @Test - @Category(SlowTests.class) - public void testGetFunctionsWithBlankPatterns() throws SQLException { + void getFunctionsWithBadCatalog() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + + try (ResultSet rs = dbmd.getFunctions("nonsensecatalog", "", "")) { + assertFalse(rs.next()); + } + } + + @Test + void getFunctionsWithBlankPatterns() throws SQLException { int minFuncCount = 1000; DatabaseMetaData dbmd = conn.getMetaData(); final int totalCount; try (ResultSet rs = dbmd.getFunctions("", "", "")) { + assertFalse(rs.next()); + } + + // Should not be same as blank pattern + try (ResultSet rs = dbmd.getFunctions(null, null, null)) { List list = assertFunctionRSAndReturnList(rs); totalCount = list.size(); // Rest of this test will validate against this value assertThat(totalCount > minFuncCount, is(true)); assertListContains("getFunctions('', '', '') must contain addfunction", list, "hasfunctions", "addfunction"); } - // Should be same as blank pattern + // Catalog parameter has effect on our getFunctions filtering + try (ResultSet rs = dbmd.getFunctions("ANYTHING_WILL_WORK", null, null)) { + assertFalse(rs.next()); + } + + // Filter by schema + try (ResultSet rs = dbmd.getFunctions("", "pg_catalog", null)) { + assertFalse(rs.next()); + } + + // Filter by schema and function name + try (ResultSet rs = dbmd.getFunctions("", "pg_catalog", "abs")) { + assertFalse(rs.next()); + } + + // Filter by function name only + try (ResultSet rs = dbmd.getFunctions("", "", "abs")) { + assertFalse(rs.next()); + } + } + + @Test + void getFunctionsWithNullPatterns() throws SQLException { + int minFuncCount = 1000; + DatabaseMetaData dbmd = conn.getMetaData(); + + final int totalCount; + try (ResultSet rs = dbmd.getFunctions(null, null, null)) { - int count = assertGetFunctionRS(rs); - assertThat(count, is(totalCount)); + List list = assertFunctionRSAndReturnList(rs); + totalCount = list.size(); // Rest of this test will validate against this value + assertThat(totalCount > minFuncCount, is(true)); + assertListContains("getFunctions('', '', '') must contain addfunction", list, "hasfunctions", "addfunction"); } - // Catalog parameter has no affect on our getFunctions filtering - try (ResultSet rs = dbmd.getFunctions("ANYTHING_WILL_WORK", null, null)) { - int count = assertGetFunctionRS(rs); - assertThat(count, is(totalCount)); + // Catalog parameter has effect on our getFunctions filtering + try (ResultSet rs = dbmd.getFunctions(UUID.randomUUID().toString(), null, null)) { + assertFalse(rs.next()); } // Filter by schema - try (ResultSet rs = dbmd.getFunctions("", "pg_catalog", null)) { + try (ResultSet rs = dbmd.getFunctions(null, "pg_catalog", null)) { int count = assertGetFunctionRS(rs); assertThat(count > minFuncCount, is(true)); } // Filter by schema and function name - try (ResultSet rs = dbmd.getFunctions("", "pg_catalog", "abs")) { + try (ResultSet rs = dbmd.getFunctions(null, "pg_catalog", "abs")) { int count = assertGetFunctionRS(rs); assertThat(count >= 1, is(true)); } // Filter by function name only - try (ResultSet rs = dbmd.getFunctions("", "", "abs")) { + try (ResultSet rs = dbmd.getFunctions(null, null, "abs")) { int count = assertGetFunctionRS(rs); assertThat(count >= 1, is(true)); } @@ -272,10 +508,10 @@ private CatalogObject(String catalog, String schema, String name, String specifi public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ((catalog == null) ? 0 : catalog.hashCode()); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((schema == null) ? 0 : schema.hashCode()); - result = prime * result + ((specificName == null) ? 0 : specificName.hashCode()); + result = prime * result + (catalog == null ? 0 : catalog.hashCode()); + result = prime * result + (name == null ? 0 : name.hashCode()); + result = prime * result + (schema == null ? 0 : schema.hashCode()); + result = prime * result + (specificName == null ? 0 : specificName.hashCode()); return result; } @@ -286,7 +522,7 @@ public boolean equals(Object obj) { } else if (obj == this) { return true; } - return compareTo((CatalogObject)obj) == 0; + return compareTo((CatalogObject) obj) == 0; } @Override @@ -312,14 +548,14 @@ public int compareTo(CatalogObject other) { } /** Assert some basic result from ResultSet of a GetFunctions method. Return the total row count. */ - private int assertGetFunctionRS(ResultSet rs) throws SQLException { + private static int assertGetFunctionRS(ResultSet rs) throws SQLException { return assertFunctionRSAndReturnList(rs).size(); } - private List assertFunctionRSAndReturnList(ResultSet rs) throws SQLException { + private static List assertFunctionRSAndReturnList(ResultSet rs) throws SQLException { // There should be at least one row assertThat(rs.next(), is(true)); - assertThat(rs.getString("FUNCTION_CAT"), is(System.getProperty("database"))); + assertThat(rs.getString("FUNCTION_CAT"), is(TestUtil.getDatabase())); assertThat(rs.getString("FUNCTION_SCHEM"), notNullValue()); assertThat(rs.getString("FUNCTION_NAME"), notNullValue()); assertThat(rs.getShort("FUNCTION_TYPE") >= 0, is(true)); @@ -353,14 +589,14 @@ private List assertFunctionRSAndReturnList(ResultSet rs) throws S return result; } - private int assertProcedureRS(ResultSet rs) throws SQLException { + private static int assertProcedureRS(ResultSet rs) throws SQLException { return assertProcedureRSAndReturnList(rs).size(); } - private List assertProcedureRSAndReturnList(ResultSet rs) throws SQLException { + private static List assertProcedureRSAndReturnList(ResultSet rs) throws SQLException { // There should be at least one row assertThat(rs.next(), is(true)); - assertThat(rs.getString("PROCEDURE_CAT"), nullValue()); + assertThat(rs.getString("PROCEDURE_CAT"), is(TestUtil.getDatabase())); assertThat(rs.getString("PROCEDURE_SCHEM"), notNullValue()); assertThat(rs.getString("PROCEDURE_NAME"), notNullValue()); assertThat(rs.getShort("PROCEDURE_TYPE") >= 0, is(true)); @@ -395,13 +631,55 @@ private List assertProcedureRSAndReturnList(ResultSet rs) throws return result; } - private void assertListContains(String message, List list, String schema, String name) throws SQLException { + private static int assertProcedureColumnsRS(ResultSet rs) throws SQLException { + return assertProcedureColumnsRSAndReturnList(rs).size(); + } + + private static List assertProcedureColumnsRSAndReturnList(ResultSet rs) throws SQLException { + // There should be at least one row + assertThat(rs.next(), is(true)); + assertThat(rs.getString("PROCEDURE_CAT"), is(TestUtil.getDatabase())); + assertThat(rs.getString("PROCEDURE_SCHEM"), notNullValue()); + assertThat(rs.getString("PROCEDURE_NAME"), notNullValue()); + assertThat(rs.getString("COLUMN_NAME") , notNullValue()); + assertThat(rs.getString("SPECIFIC_NAME"), notNullValue()); + + // Ensure there is enough column and column value retrieve by index should be same as column name (ordered) + assertThat(rs.getMetaData().getColumnCount(), is(20)); + assertThat(rs.getString(1), is(rs.getString("PROCEDURE_CAT"))); + assertThat(rs.getString(2), is(rs.getString("PROCEDURE_SCHEM"))); + assertThat(rs.getString(3), is(rs.getString("PROCEDURE_NAME"))); + // Per JDBC spec, indexes 4, 5, and 6 are reserved for future use + assertThat(rs.getString(13), is(rs.getString("REMARKS"))); + assertThat(rs.getString(4), is(rs.getString("COLUMN_NAME"))); + assertThat(rs.getString(20), is(rs.getString("SPECIFIC_NAME"))); + + // Get all result and assert they are ordered per javadoc spec: + // FUNCTION_CAT, FUNCTION_SCHEM, FUNCTION_NAME and SPECIFIC_NAME + List result = new ArrayList<>(); + do { + CatalogObject obj = new CatalogObject( + rs.getString("PROCEDURE_CAT"), + rs.getString("PROCEDURE_SCHEM"), + rs.getString("PROCEDURE_NAME"), + rs.getString("SPECIFIC_NAME")); + result.add(obj); + } while (rs.next()); + + List orderedResult = new ArrayList<>(result); + Collections.sort(orderedResult); + assertThat(result, is(orderedResult)); + + return result; + } + + private static void assertListContains(String message, List list, String schema, String name) throws SQLException { boolean found = list.stream().anyMatch(item -> item.schema.equals(schema) && item.name.equals(name)); - assertTrue(message + "; schema=" + schema + " name=" + name, found); + assertTrue(found, message + "; schema=" + schema + " name=" + name); } @Test - public void testGetFunctionsWithSpecificTypes() throws SQLException { + void getFunctionsWithSpecificTypes_whenCatalogAndSchemaArgsEmpty_expectNoResults() throws SQLException { // These function creation are borrow from jdbc2/DatabaseMetaDataTest // We modify to ensure new function created are returned by getFunctions() @@ -409,21 +687,70 @@ public void testGetFunctionsWithSpecificTypes() throws SQLException { if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_4)) { Statement stmt = conn.createStatement(); stmt.execute( - "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); + "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); ResultSet rs = dbmd.getFunctions("", "", "getfunc_f1"); + assertThat(rs.next(), is(false)); + rs.close(); + stmt.execute("DROP FUNCTION getfunc_f1(int, varchar)"); + + stmt.execute( + "CREATE OR REPLACE FUNCTION getfunc_f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql"); + rs = dbmd.getFunctions("", "", "getfunc_f3"); + assertThat(rs.next(), is(false)); + rs.close(); + stmt.execute("DROP FUNCTION getfunc_f3(int, varchar)"); + + // RETURNS TABLE requires PostgreSQL 8.4+ + stmt.execute( + "CREATE OR REPLACE FUNCTION getfunc_f5() RETURNS TABLE (i int) LANGUAGE sql AS 'SELECT 1'"); + + rs = dbmd.getFunctions("", "", "getfunc_f5"); + assertThat(rs.next(), is(false)); + rs.close(); + stmt.execute("DROP FUNCTION getfunc_f5()"); + } else { + // For PG 8.3 or 8.2 it will resulted in unknown function type + Statement stmt = conn.createStatement(); + stmt.execute( + "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); + ResultSet rs = dbmd.getFunctions("", "", "getfunc_f1"); + assertThat(rs.next(), is(false)); + rs.close(); + stmt.execute("DROP FUNCTION getfunc_f1(int, varchar)"); + + stmt.execute( + "CREATE OR REPLACE FUNCTION getfunc_f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql"); + rs = dbmd.getFunctions("", "", "getfunc_f3"); + assertThat(rs.next(), is(false)); + rs.close(); + stmt.execute("DROP FUNCTION getfunc_f3(int, varchar)"); + } + } + + @Test + void getFunctionsWithSpecificTypes() throws SQLException { + // These function creation are borrow from jdbc2/DatabaseMetaDataTest + // We modify to ensure new function created are returned by getFunctions() + + DatabaseMetaData dbmd = conn.getMetaData(); + if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_4)) { + Statement stmt = conn.createStatement(); + stmt.execute( + "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); + ResultSet rs = dbmd.getFunctions(null, null, "getfunc_f1"); assertThat(rs.next(), is(true)); assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f1")); - assertThat(rs.getShort("FUNCTION_TYPE"), is((short)DatabaseMetaData.functionNoTable)); + assertThat(rs.getShort("FUNCTION_TYPE"), is((short) DatabaseMetaData.functionNoTable)); assertThat(rs.next(), is(false)); rs.close(); stmt.execute("DROP FUNCTION getfunc_f1(int, varchar)"); stmt.execute( "CREATE OR REPLACE FUNCTION getfunc_f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql"); - rs = dbmd.getFunctions("", "", "getfunc_f3"); + rs = dbmd.getFunctions(null, null, "getfunc_f3"); assertThat(rs.next(), is(true)); assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f3")); - assertThat(rs.getShort("FUNCTION_TYPE"), is((short)DatabaseMetaData.functionNoTable)); + assertThat(rs.getShort("FUNCTION_TYPE"), is((short) DatabaseMetaData.functionNoTable)); assertThat(rs.next(), is(false)); rs.close(); stmt.execute("DROP FUNCTION getfunc_f3(int, varchar)"); @@ -432,10 +759,10 @@ public void testGetFunctionsWithSpecificTypes() throws SQLException { stmt.execute( "CREATE OR REPLACE FUNCTION getfunc_f5() RETURNS TABLE (i int) LANGUAGE sql AS 'SELECT 1'"); - rs = dbmd.getFunctions("", "", "getfunc_f5"); + rs = dbmd.getFunctions(null, null, "getfunc_f5"); assertThat(rs.next(), is(true)); assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f5")); - assertThat(rs.getShort("FUNCTION_TYPE"), is((short)DatabaseMetaData.functionReturnsTable)); + assertThat(rs.getShort("FUNCTION_TYPE"), is((short) DatabaseMetaData.functionReturnsTable)); assertThat(rs.next(), is(false)); rs.close(); stmt.execute("DROP FUNCTION getfunc_f5()"); @@ -444,20 +771,20 @@ public void testGetFunctionsWithSpecificTypes() throws SQLException { Statement stmt = conn.createStatement(); stmt.execute( "CREATE OR REPLACE FUNCTION getfunc_f1(int, varchar) RETURNS int AS 'SELECT 1;' LANGUAGE SQL"); - ResultSet rs = dbmd.getFunctions("", "", "getfunc_f1"); + ResultSet rs = dbmd.getFunctions(null, null, "getfunc_f1"); assertThat(rs.next(), is(true)); assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f1")); - assertThat(rs.getShort("FUNCTION_TYPE"), is((short)DatabaseMetaData.functionResultUnknown)); + assertThat(rs.getShort("FUNCTION_TYPE"), is((short) DatabaseMetaData.functionResultUnknown)); assertThat(rs.next(), is(false)); rs.close(); stmt.execute("DROP FUNCTION getfunc_f1(int, varchar)"); stmt.execute( "CREATE OR REPLACE FUNCTION getfunc_f3(IN a int, INOUT b varchar, OUT c timestamptz) AS $f$ BEGIN b := 'a'; c := now(); return; END; $f$ LANGUAGE plpgsql"); - rs = dbmd.getFunctions("", "", "getfunc_f3"); + rs = dbmd.getFunctions(null, null, "getfunc_f3"); assertThat(rs.next(), is(true)); assertThat(rs.getString("FUNCTION_NAME"), is("getfunc_f3")); - assertThat(rs.getShort("FUNCTION_TYPE"), is((short)DatabaseMetaData.functionResultUnknown)); + assertThat(rs.getShort("FUNCTION_TYPE"), is((short) DatabaseMetaData.functionResultUnknown)); assertThat(rs.next(), is(false)); rs.close(); stmt.execute("DROP FUNCTION getfunc_f3(int, varchar)"); @@ -465,7 +792,7 @@ public void testGetFunctionsWithSpecificTypes() throws SQLException { } @Test - public void testSortedDataTypes() throws SQLException { + void sortedDataTypes() throws SQLException { // https://github.com/pgjdbc/pgjdbc/issues/716 DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getTypeInfo(); @@ -476,4 +803,25 @@ public void testSortedDataTypes() throws SQLException { lastType = type; } } + + @Test + void getSqlTypes() throws SQLException { + if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v10)) { + try (Connection privileged = TestUtil.openPrivilegedDB()) { + try (Statement stmt = privileged.createStatement()) { + // create a function called array_in + stmt.execute("CREATE OR REPLACE FUNCTION public.array_in(anyarray, oid, integer)\n" + + " RETURNS anyarray\n" + + " LANGUAGE internal\n" + + " STABLE PARALLEL SAFE STRICT\n" + + "AS $function$array_in$function$"); + } + DatabaseMetaData dbmd = privileged.getMetaData(); + ResultSet rs = dbmd.getTypeInfo(); + try (Statement stmt = privileged.createStatement()) { + stmt.execute("drop function public.array_in(anyarray, oid, integer)"); + } + } + } + } } diff --git a/src/test/java/org/postgresql/test/jdbc4/IsValidTest.java b/src/test/java/org/postgresql/test/jdbc4/IsValidTest.java index c68cc73..f6a5ee6 100644 --- a/src/test/java/org/postgresql/test/jdbc4/IsValidTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/IsValidTest.java @@ -5,16 +5,16 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.ServerVersion; import org.postgresql.core.TransactionState; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.SQLException; @@ -23,7 +23,7 @@ public class IsValidTest extends BaseTest4 { @Test public void testIsValidShouldNotModifyTransactionStateOutsideTransaction() throws SQLException { TransactionState initialTransactionState = TestUtil.getTransactionState(con); - assertTrue("Connection should be valid", con.isValid(0)); + assertTrue(con.isValid(0), "Connection should be valid"); TestUtil.assertTransactionState("Transaction state should not be modified by non-transactional Connection.isValid(...)", con, initialTransactionState); } @@ -32,7 +32,7 @@ public void testIsValidShouldNotModifyTransactionStateInEmptyTransaction() throw con.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); con.setAutoCommit(false); TransactionState transactionState = TestUtil.getTransactionState(con); - assertTrue("Connection should be valid", con.isValid(0)); + assertTrue(con.isValid(0), "Connection should be valid"); TestUtil.assertTransactionState("Transaction state should not be modified by Connection.isValid(...) within an empty transaction", con, transactionState); } @@ -42,25 +42,25 @@ public void testIsValidShouldNotModifyTransactionStateInNonEmptyTransaction() th con.setAutoCommit(false); TestUtil.executeQuery(con, "SELECT 1"); TransactionState transactionState = TestUtil.getTransactionState(con); - assertTrue("Connection should be valid", con.isValid(0)); + assertTrue(con.isValid(0), "Connection should be valid"); TestUtil.assertTransactionState("Transaction state should not be modified by Connection.isValid(...) within a non-empty transaction", con, transactionState); } @Test public void testIsValidRemoteClose() throws SQLException, InterruptedException { - Assume.assumeTrue("Unable to use pg_terminate_backend(...) before version 8.4", TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)); + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4), "Unable to use pg_terminate_backend(...) before version 8.4"); boolean wasTerminated = TestUtil.terminateBackend(con); - assertTrue("The backend should be terminated", wasTerminated); + assertTrue(wasTerminated, "The backend should be terminated"); // Keeps checking for up to 5-seconds that the connection is marked invalid - for (int i = 0;i < 500;i++) { + for (int i = 0; i < 500; i++) { if (!con.isValid(0)) { break; } // Wait a bit to give the connection a chance to gracefully handle the termination Thread.sleep(10); } - assertFalse("The terminated connection should not be valid", con.isValid(0)); + assertFalse(con.isValid(0), "The terminated connection should not be valid"); } } diff --git a/src/test/java/org/postgresql/test/jdbc4/JsonbTest.java b/src/test/java/org/postgresql/test/jdbc4/JsonbTest.java index 49b17cf..a6b82f9 100644 --- a/src/test/java/org/postgresql/test/jdbc4/JsonbTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/JsonbTest.java @@ -5,48 +5,71 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; +import org.postgresql.util.PGobject; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.Array; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class JsonbTest extends BaseTest4 { public JsonbTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + assumeTrue( + TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_4), + "jsonb requires PostgreSQL 9.4+"); + TestUtil.createTable(con, "jsonbtest", "detail jsonb"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "jsonbtest"); + } + } + @Override public void setUp() throws Exception { super.setUp(); - Assume.assumeTrue("jsonb requires PostgreSQL 9.4+", TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_4)); - TestUtil.createTable(con, "jsonbtest", "detail jsonb"); + assumeTrue( + TestUtil.haveMinimumServerVersion(con, ServerVersion.v9_4), + "jsonb requires PostgreSQL 9.4+"); + TestUtil.execute(con, "TRUNCATE jsonbtest"); Statement stmt = con.createStatement(); stmt.executeUpdate("INSERT INTO jsonbtest (detail) VALUES ('{\"a\": 1}')"); stmt.executeUpdate("INSERT INTO jsonbtest (detail) VALUES ('{\"b\": 1}')"); @@ -54,12 +77,6 @@ public void setUp() throws Exception { stmt.close(); } - @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "jsonbtest"); - super.tearDown(); - } - @Test public void testJsonbNonPreparedStatement() throws SQLException { Statement stmt = con.createStatement(); @@ -71,6 +88,28 @@ public void testJsonbNonPreparedStatement() throws SQLException { stmt.close(); } + @Test + public void testJsonbResultType() throws SQLException { + try (ResultSet rs = con.createStatement().executeQuery("SELECT '[1,2,3]'::jsonb")) { + ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals("jsonb", rsmd.getColumnTypeName(1)); + assertEquals(PGobject.class.getName(), rsmd.getColumnClassName(1)); + assertTrue(rs.next()); + assertEquals("[1, 2, 3]", rs.getString(1)); + } + } + + @Test + public void testJsonResultType() throws SQLException { + try (ResultSet rs = con.createStatement().executeQuery("SELECT '[1,2,3]'::json")) { + ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals("json", rsmd.getColumnTypeName(1)); + assertEquals(PGobject.class.getName(), rsmd.getColumnClassName(1)); + assertTrue(rs.next()); + assertEquals("[1,2,3]", rs.getString(1)); + } + } + @Test public void testJsonbPreparedStatement() throws SQLException { PreparedStatement stmt = con.prepareStatement("SELECT count(1) FROM jsonbtest WHERE detail ?? 'a' = false;"); @@ -97,17 +136,15 @@ private void jsonArrayGet(String type, Class arrayElement) throws SQLExceptio assertTrue(rs.next()); Array array = rs.getArray(1); Object[] objectArray = (Object[]) array.getArray(); - Assert.assertEquals( - "'{[2],[3]}'::" + type + "[] should come up as Java array with two entries", + assertEquals( "[[2], [3]]", - Arrays.deepToString(objectArray) - ); + Arrays.deepToString(objectArray), + () -> "'{[2],[3]}'::" + type + "[] should come up as Java array with two entries"); - Assert.assertEquals( - type + " array entries should come up as strings", + assertEquals( arrayElement.getName() + ", " + arrayElement.getName(), - objectArray[0].getClass().getName() + ", " + objectArray[1].getClass().getName() - ); + objectArray[0].getClass().getName() + ", " + objectArray[1].getClass().getName(), + () -> type + " array entries should come up as strings"); rs.close(); stmt.close(); } diff --git a/src/test/java/org/postgresql/test/jdbc4/LogTest.java b/src/test/java/org/postgresql/test/jdbc4/LogTest.java deleted file mode 100644 index 49d623f..0000000 --- a/src/test/java/org/postgresql/test/jdbc4/LogTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2007, PostgreSQL Global Development Group - * See the LICENSE file in the project root for more information. - */ - -package org.postgresql.test.jdbc4; - -import org.postgresql.PGProperty; -import org.postgresql.test.TestUtil; -import org.postgresql.test.jdbc2.BaseTest4; - -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.sql.Array; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Properties; - -@RunWith(Parameterized.class) -public class LogTest extends BaseTest4 { - - private String oldLevel; - - public LogTest(BinaryMode binaryMode) { - setBinaryMode(binaryMode); - long maxMemory = Runtime.getRuntime().maxMemory(); - if (maxMemory < 6L * 1024 * 1024 * 1024) { - // TODO: add hamcrest matches and replace with "greaterThan" or something like that - Assume.assumeTrue( - "The test requires -Xmx6g or more. MaxMemory is " + (maxMemory / 1024.0 / 1024) + " MiB", - false); - } - } - - @Parameterized.Parameters(name = "binary = {0}") - public static Iterable data() { - Collection ids = new ArrayList(); - for (BinaryMode binaryMode : BinaryMode.values()) { - ids.add(new Object[]{binaryMode}); - } - return ids; - } - - @Override - protected void updateProperties(Properties props) { - super.updateProperties(props); - PGProperty.LOGGER_LEVEL.set(props, "TRACE"); - } - - @Test - public void reallyLargeArgumentsBreaksLogging() throws SQLException { - String[] largeInput = new String[220]; - String largeString = String.format("%1048576s", " "); - for (int i = 0; i < largeInput.length; i++) { - largeInput[i] = largeString; - } - Array arr = con.createArrayOf("text", largeInput); - PreparedStatement ps = con.prepareStatement("select t from unnest(?::text[]) t"); - ps.setArray(1, arr); - ResultSet rs = ps.executeQuery(); - int x = 0; - while (rs.next()) { - x += 1; - String found = rs.getString(1); - Assert.assertEquals(largeString, found); - } - Assert.assertEquals(largeInput.length, x); - TestUtil.closeQuietly(rs); - TestUtil.closeQuietly(ps); - } -} diff --git a/src/test/java/org/postgresql/test/jdbc4/PGCopyInputStreamTest.java b/src/test/java/org/postgresql/test/jdbc4/PGCopyInputStreamTest.java index 50922bb..c92cc50 100644 --- a/src/test/java/org/postgresql/test/jdbc4/PGCopyInputStreamTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/PGCopyInputStreamTest.java @@ -5,16 +5,16 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import org.postgresql.PGConnection; import org.postgresql.copy.PGCopyInputStream; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -24,7 +24,7 @@ import java.util.ArrayList; import java.util.List; -public class PGCopyInputStreamTest { +class PGCopyInputStreamTest { private static final int NUM_TEST_ROWS = 4; /** * COPY .. TO STDOUT terminates each row of data with a LF regardless of platform so the size of @@ -36,66 +36,64 @@ public class PGCopyInputStreamTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); } - @After - public void tearDown() throws SQLException { + @AfterEach + void tearDown() throws SQLException { TestUtil.closeDB(conn); } @Test - public void testReadBytesCorrectlyHandlesEof() throws SQLException, IOException { + void readBytesCorrectlyHandlesEof() throws SQLException, IOException { PGConnection pgConn = conn.unwrap(PGConnection.class); try (PGCopyInputStream in = new PGCopyInputStream(pgConn, COPY_SQL)) { // large enough to read everything on the next step byte[] buf = new byte[COPY_DATA_SIZE + 100]; - assertEquals("First read should get the entire table into the byte array", - COPY_DATA_SIZE, in.read(buf)); - assertEquals("Subsequent read should return -1 to indicate stream is finished", - -1, in.read(buf)); + assertEquals(COPY_DATA_SIZE, in.read(buf), "First read should get the entire table into the byte array"); + assertEquals(-1, in.read(buf), "Subsequent read should return -1 to indicate stream is finished"); } } @Test - public void testReadBytesCorrectlyReadsDataInChunks() throws SQLException, IOException { + void readBytesCorrectlyReadsDataInChunks() throws SQLException, IOException { PGConnection pgConn = conn.unwrap(PGConnection.class); try (PGCopyInputStream in = new PGCopyInputStream(pgConn, COPY_SQL)) { // Read in row sized chunks List chunks = readFully(in, COPY_ROW_SIZE); - assertEquals("Should read one chunk per row", NUM_TEST_ROWS, chunks.size()); - assertEquals("Entire table should have be read", "0\n1\n2\n3\n", chunksToString(chunks)); + assertEquals(NUM_TEST_ROWS, chunks.size(), "Should read one chunk per row"); + assertEquals("0\n1\n2\n3\n", chunksToString(chunks), "Entire table should have be read"); } } @Test - public void testCopyAPI() throws SQLException, IOException { + void copyAPI() throws SQLException, IOException { PGConnection pgConn = conn.unwrap(PGConnection.class); try (PGCopyInputStream in = new PGCopyInputStream(pgConn, COPY_SQL)) { List chunks = readFromCopyFully(in); - assertEquals("Should read one chunk per row", NUM_TEST_ROWS, chunks.size()); - assertEquals("Entire table should have be read", "0\n1\n2\n3\n", chunksToString(chunks)); + assertEquals(NUM_TEST_ROWS, chunks.size(), "Should read one chunk per row"); + assertEquals("0\n1\n2\n3\n", chunksToString(chunks), "Entire table should have be read"); } } @Test - public void testMixedAPI() throws SQLException, IOException { + void mixedAPI() throws SQLException, IOException { PGConnection pgConn = conn.unwrap(PGConnection.class); try (PGCopyInputStream in = new PGCopyInputStream(pgConn, COPY_SQL)) { // First read using java.io.InputStream API byte[] firstChar = new byte[1]; in.read(firstChar); - assertArrayEquals("IO API should read first character", "0".getBytes(), firstChar); + assertArrayEquals("0".getBytes(), firstChar, "IO API should read first character"); // Read remainder of first row using CopyOut API - assertArrayEquals("readFromCopy() should return remainder of first row", "\n".getBytes(), in.readFromCopy()); + assertArrayEquals("\n".getBytes(), in.readFromCopy(), "readFromCopy() should return remainder of first row"); // Then read the rest using CopyOut API List chunks = readFromCopyFully(in); - assertEquals("Should read one chunk per row", NUM_TEST_ROWS - 1, chunks.size()); - assertEquals("Rest of table should have be read", "1\n2\n3\n", chunksToString(chunks)); + assertEquals(NUM_TEST_ROWS - 1, chunks.size(), "Should read one chunk per row"); + assertEquals("1\n2\n3\n", chunksToString(chunks), "Rest of table should have be read"); } } diff --git a/src/test/java/org/postgresql/test/jdbc4/UUIDTest.java b/src/test/java/org/postgresql/test/jdbc4/UUIDTest.java index 28c917e..7dad542 100644 --- a/src/test/java/org/postgresql/test/jdbc4/UUIDTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/UUIDTest.java @@ -5,8 +5,9 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.core.ServerVersion; import org.postgresql.jdbc.PreferQueryMode; @@ -14,10 +15,9 @@ import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.util.PSQLState; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -28,7 +28,8 @@ import java.util.Collection; import java.util.UUID; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class UUIDTest extends BaseTest4 { public UUIDTest(BinaryMode binaryMode, StringType stringType) { @@ -36,9 +37,8 @@ public UUIDTest(BinaryMode binaryMode, StringType stringType) { setStringType(stringType); } - @Parameterized.Parameters(name = "binary={0}, stringType={1}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { for (StringType stringType : StringType.values()) { ids.add(new Object[]{binaryMode, stringType}); @@ -93,9 +93,8 @@ public void testUUIDString() throws SQLException { try { ps.executeUpdate(); if (getStringType() == StringType.VARCHAR && preferQueryMode != PreferQueryMode.SIMPLE) { - Assert.fail( - "setString(, uuid) should fail to insert value into UUID column when stringType=varchar." - + " Expecting error <>"); + fail("setString(, uuid) should fail to insert value into UUID column when stringType=varchar." + + " Expecting error <>"); } } catch (SQLException e) { if (getStringType() == StringType.VARCHAR diff --git a/src/test/java/org/postgresql/test/jdbc4/WrapperTest.java b/src/test/java/org/postgresql/test/jdbc4/WrapperTest.java index f047c40..25f6f46 100644 --- a/src/test/java/org/postgresql/test/jdbc4/WrapperTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/WrapperTest.java @@ -5,37 +5,37 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGConnection; import org.postgresql.PGStatement; import org.postgresql.ds.PGSimpleDataSource; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; -public class WrapperTest { +class WrapperTest { private Connection conn; private Statement statement; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); statement = conn.prepareStatement("SELECT 1"); } - @After - public void tearDown() throws SQLException { + @AfterEach + void tearDown() throws SQLException { statement.close(); TestUtil.closeDB(conn); } @@ -47,22 +47,22 @@ private interface PrivateInterface { } @Test - public void testConnectionIsWrapperForPrivate() throws SQLException { + void connectionIsWrapperForPrivate() throws SQLException { assertFalse(conn.isWrapperFor(PrivateInterface.class)); } @Test - public void testConnectionIsWrapperForConnection() throws SQLException { + void connectionIsWrapperForConnection() throws SQLException { assertTrue(conn.isWrapperFor(Connection.class)); } @Test - public void testConnectionIsWrapperForPGConnection() throws SQLException { + void connectionIsWrapperForPGConnection() throws SQLException { assertTrue(conn.isWrapperFor(PGConnection.class)); } @Test - public void testConnectionUnwrapPrivate() throws SQLException { + void connectionUnwrapPrivate() throws SQLException { try { conn.unwrap(PrivateInterface.class); fail("unwrap of non-wrapped interface should fail"); @@ -71,52 +71,53 @@ public void testConnectionUnwrapPrivate() throws SQLException { } @Test - public void testConnectionUnwrapConnection() throws SQLException { + void connectionUnwrapConnection() throws SQLException { Object v = conn.unwrap(Connection.class); assertNotNull(v); - assertTrue("connection.unwrap(PGConnection.class) should return PGConnection instance" - + ", actual instance is " + v, v instanceof Connection); + assertTrue(v instanceof Connection, "connection.unwrap(PGConnection.class) should return PGConnection instance" + + ", actual instance is " + v); } @Test - public void testConnectionUnwrapPGConnection() throws SQLException { + void connectionUnwrapPGConnection() throws SQLException { Object v = conn.unwrap(PGConnection.class); assertNotNull(v); - assertTrue("connection.unwrap(PGConnection.class) should return PGConnection instance" - + ", actual instance is " + v, v instanceof PGConnection); + assertTrue(v instanceof PGConnection, "connection.unwrap(PGConnection.class) should return PGConnection instance" + + ", actual instance is " + v); } @Test - public void testConnectionUnwrapPGDataSource() throws SQLException { + @SuppressWarnings("deprecation") + void connectionUnwrapPGDataSource() throws SQLException { PGSimpleDataSource dataSource = new PGSimpleDataSource(); dataSource.setDatabaseName(TestUtil.getDatabase()); dataSource.setServerName(TestUtil.getServer()); dataSource.setPortNumber(TestUtil.getPort()); Connection connection = dataSource.getConnection(TestUtil.getUser(), TestUtil.getPassword()); - assertNotNull("Unable to obtain a connection from PGSimpleDataSource", connection); + assertNotNull(connection, "Unable to obtain a connection from PGSimpleDataSource"); Object v = connection.unwrap(PGConnection.class); - assertTrue("connection.unwrap(PGConnection.class) should return PGConnection instance" - + ", actual instance is " + v, - v instanceof PGConnection); + assertTrue(v instanceof PGConnection, + "connection.unwrap(PGConnection.class) should return PGConnection instance" + + ", actual instance is " + v); } @Test - public void testStatementIsWrapperForPrivate() throws SQLException { - assertFalse("Should not be a wrapper for PrivateInterface", statement.isWrapperFor(PrivateInterface.class)); + void statementIsWrapperForPrivate() throws SQLException { + assertFalse(statement.isWrapperFor(PrivateInterface.class), "Should not be a wrapper for PrivateInterface"); } @Test - public void testStatementIsWrapperForStatement() throws SQLException { - assertTrue("Should be a wrapper for Statement", statement.isWrapperFor(Statement.class)); + void statementIsWrapperForStatement() throws SQLException { + assertTrue(statement.isWrapperFor(Statement.class), "Should be a wrapper for Statement"); } @Test - public void testStatementIsWrapperForPGStatement() throws SQLException { - assertTrue("Should be a wrapper for PGStatement", statement.isWrapperFor(PGStatement.class)); + void statementIsWrapperForPGStatement() throws SQLException { + assertTrue(statement.isWrapperFor(PGStatement.class), "Should be a wrapper for PGStatement"); } @Test - public void testStatementUnwrapPrivate() throws SQLException { + void statementUnwrapPrivate() throws SQLException { try { statement.unwrap(PrivateInterface.class); fail("unwrap of non-wrapped interface should fail"); @@ -125,17 +126,17 @@ public void testStatementUnwrapPrivate() throws SQLException { } @Test - public void testStatementUnwrapStatement() throws SQLException { + void statementUnwrapStatement() throws SQLException { Object v = statement.unwrap(Statement.class); assertNotNull(v); - assertTrue("Should be instance of Statement, actual instance of " + v, v instanceof Statement); + assertTrue(v instanceof Statement, "Should be instance of Statement, actual instance of " + v); } @Test - public void testStatementUnwrapPGStatement() throws SQLException { + void statementUnwrapPGStatement() throws SQLException { Object v = statement.unwrap(PGStatement.class); assertNotNull(v); - assertTrue("Should be instance of PGStatement, actual instance of " + v,v instanceof PGStatement); + assertTrue(v instanceof PGStatement, "Should be instance of PGStatement, actual instance of " + v); } } diff --git a/src/test/java/org/postgresql/test/jdbc4/XmlTest.java b/src/test/java/org/postgresql/test/jdbc4/XmlTest.java index 3515964..42aaaaf 100644 --- a/src/test/java/org/postgresql/test/jdbc4/XmlTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/XmlTest.java @@ -5,16 +5,18 @@ package org.postgresql.test.jdbc4; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.ServerVersion; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.w3c.dom.Node; import java.io.IOException; @@ -69,7 +71,7 @@ public XmlTest() throws Exception { public void setUp() throws Exception { super.setUp(); assumeMinimumServerVersion(ServerVersion.v8_3); - assumeTrue("Server has been compiled --with-libxml", isXmlEnabled(con)); + assumeTrue(isXmlEnabled(con), "Server has been compiled --with-libxml"); Statement stmt = con.createStatement(); stmt.execute("CREATE TEMP TABLE xmltest(id int primary key, val xml)"); @@ -215,7 +217,7 @@ private void testWrite(Class resultClass) throws Exception xml = rs.getSQLXML(1); assertEquals(header + _xmlDocument, xml.getString()); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test @@ -260,10 +262,10 @@ public void testGetObject() throws SQLException { } private SQLXML newConsumableSQLXML(String content) throws Exception { - SQLXML xml = (SQLXML) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { SQLXML.class }, new InvocationHandler() { + SQLXML xml = (SQLXML) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{SQLXML.class}, new InvocationHandler() { SQLXML xml = con.createSQLXML(); boolean consumed = false; - Set consumingMethods = new HashSet(Arrays.asList( + Set consumingMethods = new HashSet<>(Arrays.asList( SQLXML.class.getMethod("getBinaryStream"), SQLXML.class.getMethod("getCharacterStream"), SQLXML.class.getMethod("getString") @@ -301,11 +303,11 @@ public void testSet() throws Exception { ResultSet rs = getRS(); assertTrue(rs.next()); Object o = rs.getObject(1); - assertTrue(o instanceof SQLXML); + assertInstanceOf(SQLXML.class, o); assertEquals(_xmlDocument, ((SQLXML) o).getString()); assertTrue(rs.next()); assertEquals(_xmlDocument, rs.getSQLXML(1).getString()); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test @@ -335,7 +337,7 @@ public void testSetNull() throws SQLException { assertNull(rs.getSQLXML(1)); assertTrue(rs.next()); assertNull(rs.getSQLXML("val")); - assertTrue(!rs.next()); + assertFalse(rs.next()); } @Test diff --git a/src/test/java/org/postgresql/test/jdbc4/jdbc41/AbortTest.java b/src/test/java/org/postgresql/test/jdbc4/jdbc41/AbortTest.java index e619cd9..8354cbd 100644 --- a/src/test/java/org/postgresql/test/jdbc4/jdbc41/AbortTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/jdbc41/AbortTest.java @@ -5,12 +5,13 @@ package org.postgresql.test.jdbc4.jdbc41; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.SQLException; import java.sql.Statement; @@ -89,8 +90,10 @@ public void testAbortOnClosedConnection() throws SQLException { * According to the javadoc, calling abort when the {@code executor} is {@code null} * results in SQLException */ - @Test(expected = SQLException.class) + @Test public void abortWithNullExecutor() throws SQLException { - con.abort(null); + assertThrows(SQLException.class, () -> { + con.abort(null); + }); } } diff --git a/src/test/java/org/postgresql/test/jdbc4/jdbc41/CloseOnCompletionTest.java b/src/test/java/org/postgresql/test/jdbc4/jdbc41/CloseOnCompletionTest.java index 0beee63..067f2c1 100644 --- a/src/test/java/org/postgresql/test/jdbc4/jdbc41/CloseOnCompletionTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/jdbc41/CloseOnCompletionTest.java @@ -5,15 +5,18 @@ package org.postgresql.test.jdbc4.jdbc41; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLState; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; @@ -21,18 +24,31 @@ import java.sql.SQLException; import java.sql.Statement; -public class CloseOnCompletionTest { +class CloseOnCompletionTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "testclosecompletion", "id integer"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "testclosecompletion"); + } + } + + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); - TestUtil.createTable(conn, "table1", "id integer"); + TestUtil.execute(conn, "TRUNCATE testclosecompletion"); } - @After - public void tearDown() throws SQLException { - TestUtil.dropTable(conn, "table1"); + @AfterEach + void tearDown() throws SQLException { TestUtil.closeDB(conn); } @@ -40,10 +56,10 @@ public void tearDown() throws SQLException { * Test that the statement is not automatically closed if we do not ask for it. */ @Test - public void testWithoutCloseOnCompletion() throws SQLException { + void withoutCloseOnCompletion() throws SQLException { Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "*")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testclosecompletion", "*")); rs.close(); assertFalse(stmt.isClosed()); } @@ -52,11 +68,11 @@ public void testWithoutCloseOnCompletion() throws SQLException { * Test the behavior of closeOnCompletion with a single result set. */ @Test - public void testSingleResultSet() throws SQLException { + void singleResultSet() throws SQLException { Statement stmt = conn.createStatement(); stmt.closeOnCompletion(); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "*")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testclosecompletion", "*")); rs.close(); assertTrue(stmt.isClosed()); } @@ -65,11 +81,11 @@ public void testSingleResultSet() throws SQLException { * Test the behavior of closeOnCompletion with a multiple result sets. */ @Test - public void testMultipleResultSet() throws SQLException { + void multipleResultSet() throws SQLException { Statement stmt = conn.createStatement(); stmt.closeOnCompletion(); - stmt.execute(TestUtil.selectSQL("table1", "*") + ";" + TestUtil.selectSQL("table1", "*") + ";"); + stmt.execute(TestUtil.selectSQL("testclosecompletion", "*") + ";" + TestUtil.selectSQL("testclosecompletion", "*") + ";"); ResultSet rs = stmt.getResultSet(); rs.close(); assertFalse(stmt.isClosed()); @@ -84,16 +100,16 @@ public void testMultipleResultSet() throws SQLException { * (spec). */ @Test - public void testNoResultSet() throws SQLException { + void noResultSet() throws SQLException { Statement stmt = conn.createStatement(); stmt.closeOnCompletion(); - stmt.executeUpdate(TestUtil.insertSQL("table1", "1")); + stmt.executeUpdate(TestUtil.insertSQL("testclosecompletion", "1")); assertFalse(stmt.isClosed()); } @Test - public void testExecuteTwice() throws SQLException { + void executeTwice() throws SQLException { PreparedStatement s = conn.prepareStatement("SELECT 1"); s.executeQuery(); @@ -102,7 +118,7 @@ public void testExecuteTwice() throws SQLException { } @Test - public void testCloseOnCompletionExecuteTwice() throws SQLException { + void closeOnCompletionExecuteTwice() throws SQLException { PreparedStatement s = conn.prepareStatement("SELECT 1"); /* @@ -115,8 +131,7 @@ public void testCloseOnCompletionExecuteTwice() throws SQLException { try { s.executeQuery(); } catch (SQLException ex) { - assertEquals(ex.getMessage(),"This statement has been closed."); + assertEquals(PSQLState.OBJECT_NOT_IN_STATE.getState(), ex.getSQLState(), "Expecting <>"); } - } } diff --git a/src/test/java/org/postgresql/test/jdbc4/jdbc41/GetObjectTest.java b/src/test/java/org/postgresql/test/jdbc4/jdbc41/GetObjectTest.java index d20a400..211be96 100644 --- a/src/test/java/org/postgresql/test/jdbc4/jdbc41/GetObjectTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/jdbc41/GetObjectTest.java @@ -5,10 +5,10 @@ package org.postgresql.test.jdbc4.jdbc41; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.core.BaseConnection; import org.postgresql.core.ServerVersion; @@ -24,10 +24,12 @@ import org.postgresql.util.PGmoney; import org.postgresql.util.PGobject; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; import java.math.BigInteger; @@ -54,7 +56,7 @@ import javax.sql.rowset.serial.SerialBlob; import javax.sql.rowset.serial.SerialClob; -public class GetObjectTest { +class GetObjectTest { private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); // +0000 always private static final TimeZone GMT03 = TimeZone.getTimeZone("GMT+03"); // +0300 always private static final TimeZone GMT05 = TimeZone.getTimeZone("GMT-05"); // -0500 always @@ -62,51 +64,64 @@ public class GetObjectTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "testgetobj41", "varchar_column varchar(16), " + + "char_column char(10), " + + "boolean_column boolean," + + "smallint_column smallint," + + "integer_column integer," + + "bigint_column bigint," + + "decimal_column decimal," + + "numeric_column numeric," + // smallserial requires 9.2 or later + + (((BaseConnection) conn).haveMinimumServerVersion(ServerVersion.v9_2) ? "smallserial_column smallserial," : "") + + "serial_column serial," + + "bigserial_column bigserial," + + "real_column real," + + "double_column double precision," + + "timestamp_without_time_zone_column timestamp without time zone," + + "timestamp_with_time_zone_column timestamp with time zone," + + "date_column date," + + "time_without_time_zone_column time without time zone," + + "time_with_time_zone_column time with time zone," + + "bytea_column bytea," + + "lob_column oid," + + "array_column text[]," + + "point_column point," + + "line_column line," + + "lseg_column lseg," + + "box_column box," + + "path_column path," + + "polygon_column polygon," + + "circle_column circle," + + "money_column money," + + "interval_column interval," + + (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3) ? "uuid_column uuid," : "") + + "inet_column inet," + + "cidr_column cidr," + + "macaddr_column macaddr" + + (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3) ? ",xml_column xml" : "") + ); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "testgetobj41"); + } + } + + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); - TestUtil.createTable(conn, "table1", "varchar_column varchar(16), " - + "char_column char(10), " - + "boolean_column boolean," - + "smallint_column smallint," - + "integer_column integer," - + "bigint_column bigint," - + "decimal_column decimal," - + "numeric_column numeric," - // smallserial requires 9.2 or later - + (((BaseConnection) conn).haveMinimumServerVersion(ServerVersion.v9_2) ? "smallserial_column smallserial," : "") - + "serial_column serial," - + "bigserial_column bigserial," - + "real_column real," - + "double_column double precision," - + "timestamp_without_time_zone_column timestamp without time zone," - + "timestamp_with_time_zone_column timestamp with time zone," - + "date_column date," - + "time_without_time_zone_column time without time zone," - + "time_with_time_zone_column time with time zone," - + "blob_column bytea," - + "lob_column oid," - + "array_column text[]," - + "point_column point," - + "line_column line," - + "lseg_column lseg," - + "box_column box," - + "path_column path," - + "polygon_column polygon," - + "circle_column circle," - + "money_column money," - + "interval_column interval," - + (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3) ? "uuid_column uuid," : "") - + "inet_column inet," - + "cidr_column cidr," - + "macaddr_column macaddr" - + (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3) ? ",xml_column xml" : "") - ); - } - - @After - public void tearDown() throws SQLException { - TestUtil.dropTable(conn, "table1"); + TestUtil.execute(conn, "TRUNCATE testgetobj41"); + } + + @AfterEach + void tearDown() throws SQLException { TestUtil.closeDB(conn); } @@ -114,11 +129,11 @@ public void tearDown() throws SQLException { * Test the behavior getObject for string columns. */ @Test - public void testGetString() throws SQLException { + void getString() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","varchar_column,char_column","'varchar_value','char_value'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "varchar_column,char_column", "'varchar_value','char_value'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "varchar_column, char_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "varchar_column, char_column")); try { assertTrue(rs.next()); assertEquals("varchar_value", rs.getObject("varchar_column", String.class)); @@ -134,12 +149,12 @@ public void testGetString() throws SQLException { * Test the behavior getObject for string columns. */ @Test - public void testGetClob() throws SQLException { + void getClob() throws SQLException { Statement stmt = conn.createStatement(); conn.setAutoCommit(false); try { char[] data = new char[]{'d', 'e', 'a', 'd', 'b', 'e', 'e', 'f'}; - PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("table1", "lob_column", "?")); + PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("testgetobj41", "lob_column", "?")); try { insertPS.setObject(1, new SerialClob(data), Types.CLOB); insertPS.executeUpdate(); @@ -147,7 +162,7 @@ public void testGetClob() throws SQLException { insertPS.close(); } - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "lob_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "lob_column")); try { assertTrue(rs.next()); Clob blob = rs.getObject("lob_column", Clob.class); @@ -171,11 +186,11 @@ public void testGetClob() throws SQLException { * Test the behavior getObject for big decimal columns. */ @Test - public void testGetBigDecimal() throws SQLException { + void getBigDecimal() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","decimal_column,numeric_column","0.1,0.1")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "decimal_column,numeric_column", "0.1,0.1")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "decimal_column, numeric_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "decimal_column, numeric_column")); try { assertTrue(rs.next()); assertEquals(new BigDecimal("0.1"), rs.getObject("decimal_column", BigDecimal.class)); @@ -191,11 +206,11 @@ public void testGetBigDecimal() throws SQLException { * Test the behavior getObject for timestamp columns. */ @Test - public void testGetTimestamp() throws SQLException { + void getTimestamp() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_without_time_zone_column","TIMESTAMP '2004-10-19 10:23:54'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "timestamp_without_time_zone_column", "TIMESTAMP '2004-10-19 10:23:54'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_without_time_zone_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "timestamp_without_time_zone_column")); try { assertTrue(rs.next()); Calendar calendar = GregorianCalendar.getInstance(); @@ -218,7 +233,7 @@ public void testGetTimestamp() throws SQLException { * Test the behavior getObject for timestamp columns. */ @Test - public void testGetJavaUtilDate() throws SQLException { + void getJavaUtilDate() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select TIMESTAMP '2004-10-19 10:23:54'::timestamp as timestamp_without_time_zone_column" + ", null::timestamp as null_timestamp"); @@ -245,7 +260,7 @@ public void testGetJavaUtilDate() throws SQLException { * Test the behavior getObject for timestamp columns. */ @Test - public void testGetTimestampWithTimeZone() throws SQLException { + void getTimestampWithTimeZone() throws SQLException { runGetTimestampWithTimeZone(UTC, "Z"); runGetTimestampWithTimeZone(GMT03, "+03:00"); runGetTimestampWithTimeZone(GMT05, "-05:00"); @@ -255,9 +270,9 @@ public void testGetTimestampWithTimeZone() throws SQLException { private void runGetTimestampWithTimeZone(TimeZone timeZone, String zoneString) throws SQLException { Statement stmt = conn.createStatement(); try { - stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_with_time_zone_column","TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54" + zoneString + "'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "timestamp_with_time_zone_column", "TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54" + zoneString + "'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_with_time_zone_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "timestamp_with_time_zone_column")); try { assertTrue(rs.next()); @@ -275,7 +290,7 @@ private void runGetTimestampWithTimeZone(TimeZone timeZone, String zoneString) t } finally { rs.close(); } - stmt.executeUpdate("DELETE FROM table1"); + stmt.executeUpdate("DELETE FROM testgetobj41"); } finally { stmt.close(); } @@ -285,7 +300,7 @@ private void runGetTimestampWithTimeZone(TimeZone timeZone, String zoneString) t * Test the behavior getObject for timestamp columns. */ @Test - public void testGetCalendar() throws SQLException { + void getCalendar() throws SQLException { Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select TIMESTAMP '2004-10-19 10:23:54'::timestamp as timestamp_without_time_zone_column" @@ -318,11 +333,11 @@ public void testGetCalendar() throws SQLException { * Test the behavior getObject for date columns. */ @Test - public void testGetDate() throws SQLException { + void getDate() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","date_column","DATE '1999-01-08'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "date_column", "DATE '1999-01-08'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "date_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "date_column")); try { assertTrue(rs.next()); Calendar calendar = GregorianCalendar.getInstance(); @@ -339,14 +354,14 @@ public void testGetDate() throws SQLException { } @Test - public void testGetNullDate() throws SQLException { + void getNullDate() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","date_column","NULL")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "date_column", "NULL")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "date_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "date_column")); try { assertTrue(rs.next()); - Date date = rs.getObject(1,Date.class); + Date date = rs.getObject(1, Date.class); assertTrue(rs.wasNull()); } finally { rs.close(); @@ -354,11 +369,11 @@ public void testGetNullDate() throws SQLException { } @Test - public void testGetNullTimestamp() throws SQLException { + void getNullTimestamp() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_without_time_zone_column","NULL")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "timestamp_without_time_zone_column", "NULL")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_without_time_zone_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "timestamp_without_time_zone_column")); try { assertTrue(rs.next()); java.util.Date ts = rs.getObject(1, java.util.Date.class); @@ -372,11 +387,11 @@ public void testGetNullTimestamp() throws SQLException { * Test the behavior getObject for time columns. */ @Test - public void testGetTime() throws SQLException { + void getTime() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","TIME '04:05:06'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "time_without_time_zone_column", "TIME '04:05:06'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "time_without_time_zone_column")); try { assertTrue(rs.next()); Calendar calendar = GregorianCalendar.getInstance(); @@ -399,11 +414,11 @@ public void testGetTime() throws SQLException { * Test the behavior getObject for small integer columns. */ @Test - public void testGetShort() throws SQLException { + void getShort() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","smallint_column","1")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "smallint_column", "1")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "smallint_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "smallint_column")); try { assertTrue(rs.next()); assertEquals(Short.valueOf((short) 1), rs.getObject("smallint_column", Short.class)); @@ -417,11 +432,11 @@ public void testGetShort() throws SQLException { * Test the behavior getObject for small integer columns. */ @Test - public void testGetShortNull() throws SQLException { + void getShortNull() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","smallint_column","NULL")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "smallint_column", "NULL")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "smallint_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "smallint_column")); try { assertTrue(rs.next()); assertNull(rs.getObject("smallint_column", Short.class)); @@ -435,11 +450,11 @@ public void testGetShortNull() throws SQLException { * Test the behavior getObject for integer columns. */ @Test - public void testGetInteger() throws SQLException { + void getInteger() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","smallint_column, integer_column","1, 2")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "smallint_column, integer_column", "1, 2")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "smallint_column, integer_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "smallint_column, integer_column")); try { assertTrue(rs.next()); assertEquals(Integer.valueOf(1), rs.getObject("smallint_column", Integer.class)); @@ -455,11 +470,11 @@ public void testGetInteger() throws SQLException { * Test the behavior getObject for integer columns. */ @Test - public void testGetIntegerNull() throws SQLException { + void getIntegerNull() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","smallint_column, integer_column","NULL, NULL")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "smallint_column, integer_column", "NULL, NULL")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "smallint_column, integer_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "smallint_column, integer_column")); try { assertTrue(rs.next()); assertNull(rs.getObject("smallint_column", Integer.class)); @@ -475,11 +490,11 @@ public void testGetIntegerNull() throws SQLException { * Test the behavior getObject for long columns. */ @Test - public void testGetBigInteger() throws SQLException { + void getBigInteger() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","bigint_column","2147483648")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "bigint_column", "2147483648")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "bigint_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "bigint_column")); try { assertTrue(rs.next()); assertEquals(BigInteger.valueOf(2147483648L), rs.getObject("bigint_column", BigInteger.class)); @@ -493,11 +508,11 @@ public void testGetBigInteger() throws SQLException { * Test the behavior getObject for long columns. */ @Test - public void testGetLong() throws SQLException { + void getLong() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","bigint_column","2147483648")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "bigint_column", "2147483648")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "bigint_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "bigint_column")); try { assertTrue(rs.next()); assertEquals(Long.valueOf(2147483648L), rs.getObject("bigint_column", Long.class)); @@ -511,11 +526,11 @@ public void testGetLong() throws SQLException { * Test the behavior getObject for long columns. */ @Test - public void testGetLongNull() throws SQLException { + void getLongNull() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","bigint_column","NULL")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "bigint_column", "NULL")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "bigint_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "bigint_column")); try { assertTrue(rs.next()); assertNull(rs.getObject("bigint_column", Long.class)); @@ -529,17 +544,19 @@ public void testGetLongNull() throws SQLException { * Test the behavior getObject for double columns. */ @Test - public void testGetDouble() throws SQLException { - Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","double_column","1.0")); - - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "double_column")); - try { - assertTrue(rs.next()); - assertEquals(Double.valueOf(1.0d), rs.getObject("double_column", Double.class)); - assertEquals(Double.valueOf(1.0d), rs.getObject(1, Double.class)); - } finally { - rs.close(); + void getDouble() throws SQLException { + try (Statement stmt = conn.createStatement()) { + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "double_column", "1.0")); + stmt.executeUpdate("insert into testgetobj41 (double_column) values ('-infinity'), ('+infinity')"); + try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "double_column", "true", "order by double_column asc"))) { + assertTrue(rs.next()); + assertEquals(Double.NEGATIVE_INFINITY, rs.getObject(1, Double.class)); + assertTrue(rs.next()); + assertEquals(Double.valueOf(1.0d), rs.getObject("double_column", Double.class)); + assertEquals(Double.valueOf(1.0d), rs.getObject(1, Double.class)); + assertTrue(rs.next()); + assertEquals(Double.POSITIVE_INFINITY, rs.getObject(1, Double.class)); + } } } @@ -547,11 +564,11 @@ public void testGetDouble() throws SQLException { * Test the behavior getObject for double columns. */ @Test - public void testGetDoubleNull() throws SQLException { + void getDoubleNull() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","double_column","NULL")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "double_column", "NULL")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "double_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "double_column")); try { assertTrue(rs.next()); assertNull(rs.getObject("double_column", Double.class)); @@ -565,11 +582,11 @@ public void testGetDoubleNull() throws SQLException { * Test the behavior getObject for float columns. */ @Test - public void testGetFloat() throws SQLException { + void getFloat() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","real_column","1.0")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "real_column", "1.0")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "real_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "real_column")); try { assertTrue(rs.next()); assertEquals(Float.valueOf(1.0f), rs.getObject("real_column", Float.class)); @@ -583,11 +600,11 @@ public void testGetFloat() throws SQLException { * Test the behavior getObject for float columns. */ @Test - public void testGetFloatNull() throws SQLException { + void getFloatNull() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","real_column","NULL")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "real_column", "NULL")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "real_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "real_column")); try { assertTrue(rs.next()); assertNull(rs.getObject("real_column", Float.class)); @@ -601,15 +618,15 @@ public void testGetFloatNull() throws SQLException { * Test the behavior getObject for serial columns. */ @Test - public void testGetSerial() throws SQLException { + void getSerial() throws SQLException { if (!((BaseConnection) conn).haveMinimumServerVersion(ServerVersion.v9_2)) { // smallserial requires 9.2 or later return; } Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","smallserial_column, serial_column","1, 2")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "smallserial_column, serial_column", "1, 2")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "smallserial_column, serial_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "smallserial_column, serial_column")); try { assertTrue(rs.next()); assertEquals(Integer.valueOf(1), rs.getObject("smallserial_column", Integer.class)); @@ -625,11 +642,11 @@ public void testGetSerial() throws SQLException { * Test the behavior getObject for boolean columns. */ @Test - public void testGetBoolean() throws SQLException { + void getBoolean() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","boolean_column","TRUE")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "boolean_column", "TRUE")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "boolean_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "boolean_column")); try { assertTrue(rs.next()); assertTrue(rs.getObject("boolean_column", Boolean.class)); @@ -643,11 +660,11 @@ public void testGetBoolean() throws SQLException { * Test the behavior getObject for boolean columns. */ @Test - public void testGetBooleanNull() throws SQLException { + void getBooleanNull() throws SQLException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","boolean_column","NULL")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "boolean_column", "NULL")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "boolean_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "boolean_column")); try { assertTrue(rs.next()); assertNull(rs.getObject("boolean_column", Boolean.class)); @@ -661,12 +678,12 @@ public void testGetBooleanNull() throws SQLException { * Test the behavior getObject for xml columns. */ @Test - public void testGetBlob() throws SQLException { + void getBlob() throws SQLException { Statement stmt = conn.createStatement(); conn.setAutoCommit(false); try { byte[] data = new byte[]{(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}; - PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("table1", "lob_column", "?")); + PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("testgetobj41", "lob_column", "?")); try { insertPS.setObject(1, new SerialBlob(data), Types.BLOB); insertPS.executeUpdate(); @@ -674,7 +691,7 @@ public void testGetBlob() throws SQLException { insertPS.close(); } - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "lob_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "lob_column")); try { assertTrue(rs.next()); Blob blob = rs.getObject("lob_column", Blob.class); @@ -694,16 +711,38 @@ public void testGetBlob() throws SQLException { } } + /** + * Test the behavior getObject for bytea columns. + */ + @Test + void getBytea() throws SQLException { + byte[] data = new byte[]{(byte) 0xDE, (byte) 0xAD, (byte) 0xBE, (byte) 0xEF}; + try (PreparedStatement insertPS = conn.prepareStatement(TestUtil.insertSQL("testgetobj41", "bytea_column", "?"))) { + insertPS.setBytes(1, data); + insertPS.executeUpdate(); + } + + Statement stmt = conn.createStatement(); + try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "bytea_column"))) { + assertTrue(rs.next()); + byte[] bytea = rs.getObject("bytea_column", byte[].class); + assertArrayEquals(data, bytea, "bytea by label"); + + bytea = rs.getObject(1, byte[].class); + assertArrayEquals(data, bytea, "bytea by index"); + } + } + /** * Test the behavior getObject for array columns. */ @Test - public void testGetArray() throws SQLException { + void getArray() throws SQLException { Statement stmt = conn.createStatement(); String[] data = new String[]{"java", "jdbc"}; - stmt.executeUpdate(TestUtil.insertSQL("table1","array_column","'{\"java\", \"jdbc\"}'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "array_column", "'{\"java\", \"jdbc\"}'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "array_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "array_column")); try { assertTrue(rs.next()); Array array = rs.getObject("array_column", Array.class); @@ -722,16 +761,16 @@ public void testGetArray() throws SQLException { * Test the behavior getObject for xml columns. */ @Test - public void testGetXml() throws SQLException { + void getXml() throws SQLException { if (!TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3)) { // XML column requires PostgreSQL 8.3+ return; } Statement stmt = conn.createStatement(); String content = "Manual"; - stmt.executeUpdate(TestUtil.insertSQL("table1","xml_column","XMLPARSE (DOCUMENT 'Manual')")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "xml_column", "XMLPARSE (DOCUMENT 'Manual')")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "xml_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "xml_column")); try { assertTrue(rs.next()); SQLXML sqlXml = rs.getObject("xml_column", SQLXML.class); @@ -747,18 +786,18 @@ public void testGetXml() throws SQLException { } /** - *

          Test the behavior getObject for money columns.

          + * Test the behavior getObject for money columns. * *

          The test is ignored as it is locale-dependent.

          */ - @Ignore + @Disabled @Test - public void testGetMoney() throws SQLException { + void getMoney() throws SQLException { Statement stmt = conn.createStatement(); String expected = "12.34"; - stmt.executeUpdate(TestUtil.insertSQL("table1","money_column","'12.34'::float8::numeric::money")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "money_column", "'12.34'::float8::numeric::money")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "money_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "money_column")); try { assertTrue(rs.next()); PGmoney money = rs.getObject("money_column", PGmoney.class); @@ -775,12 +814,12 @@ public void testGetMoney() throws SQLException { * Test the behavior getObject for point columns. */ @Test - public void testGetPoint() throws SQLException { + void getPoint() throws SQLException { Statement stmt = conn.createStatement(); PGpoint expected = new PGpoint(1.0d, 2.0d); - stmt.executeUpdate(TestUtil.insertSQL("table1","point_column","point '(1, 2)'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "point_column", "point '(1, 2)'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "point_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "point_column")); try { assertTrue(rs.next()); assertEquals(expected, rs.getObject("point_column", PGpoint.class)); @@ -794,7 +833,7 @@ public void testGetPoint() throws SQLException { * Test the behavior getObject for line columns. */ @Test - public void testGetLine() throws SQLException { + void getLine() throws SQLException { if (!((BaseConnection) conn).haveMinimumServerVersion(ServerVersion.v9_4)) { // only 9.4 and later ship with full line support by default return; @@ -802,9 +841,9 @@ public void testGetLine() throws SQLException { Statement stmt = conn.createStatement(); PGline expected = new PGline(1.0d, 2.0d, 3.0d); - stmt.executeUpdate(TestUtil.insertSQL("table1","line_column","line '{1, 2, 3}'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "line_column", "line '{1, 2, 3}'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "line_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "line_column")); try { assertTrue(rs.next()); assertEquals(expected, rs.getObject("line_column", PGline.class)); @@ -818,12 +857,12 @@ public void testGetLine() throws SQLException { * Test the behavior getObject for lseg columns. */ @Test - public void testGetLineseg() throws SQLException { + void getLineseg() throws SQLException { Statement stmt = conn.createStatement(); PGlseg expected = new PGlseg(1.0d, 2.0d, 3.0d, 4.0d); - stmt.executeUpdate(TestUtil.insertSQL("table1","lseg_column","lseg '[(1, 2), (3, 4)]'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "lseg_column", "lseg '[(1, 2), (3, 4)]'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "lseg_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "lseg_column")); try { assertTrue(rs.next()); assertEquals(expected, rs.getObject("lseg_column", PGlseg.class)); @@ -837,12 +876,12 @@ public void testGetLineseg() throws SQLException { * Test the behavior getObject for box columns. */ @Test - public void testGetBox() throws SQLException { + void getBox() throws SQLException { Statement stmt = conn.createStatement(); PGbox expected = new PGbox(1.0d, 2.0d, 3.0d, 4.0d); - stmt.executeUpdate(TestUtil.insertSQL("table1","box_column","box '((1, 2), (3, 4))'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "box_column", "box '((1, 2), (3, 4))'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "box_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "box_column")); try { assertTrue(rs.next()); assertEquals(expected, rs.getObject("box_column", PGbox.class)); @@ -856,12 +895,12 @@ public void testGetBox() throws SQLException { * Test the behavior getObject for path columns. */ @Test - public void testGetPath() throws SQLException { + void getPath() throws SQLException { Statement stmt = conn.createStatement(); PGpath expected = new PGpath(new PGpoint[]{new PGpoint(1.0d, 2.0d), new PGpoint(3.0d, 4.0d)}, true); - stmt.executeUpdate(TestUtil.insertSQL("table1","path_column","path '[(1, 2), (3, 4)]'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "path_column", "path '[(1, 2), (3, 4)]'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "path_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "path_column")); try { assertTrue(rs.next()); assertEquals(expected, rs.getObject("path_column", PGpath.class)); @@ -875,12 +914,12 @@ public void testGetPath() throws SQLException { * Test the behavior getObject for polygon columns. */ @Test - public void testGetPolygon() throws SQLException { + void getPolygon() throws SQLException { Statement stmt = conn.createStatement(); PGpolygon expected = new PGpolygon(new PGpoint[]{new PGpoint(1.0d, 2.0d), new PGpoint(3.0d, 4.0d)}); - stmt.executeUpdate(TestUtil.insertSQL("table1","polygon_column","polygon '((1, 2), (3, 4))'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "polygon_column", "polygon '((1, 2), (3, 4))'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "polygon_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "polygon_column")); try { assertTrue(rs.next()); assertEquals(expected, rs.getObject("polygon_column", PGpolygon.class)); @@ -894,12 +933,12 @@ public void testGetPolygon() throws SQLException { * Test the behavior getObject for circle columns. */ @Test - public void testGetCircle() throws SQLException { + void getCircle() throws SQLException { Statement stmt = conn.createStatement(); PGcircle expected = new PGcircle(1.0d, 2.0d, 3.0d); - stmt.executeUpdate(TestUtil.insertSQL("table1","circle_column","circle '<(1, 2), 3>'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "circle_column", "circle '<(1, 2), 3>'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "circle_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "circle_column")); try { assertTrue(rs.next()); assertEquals(expected, rs.getObject("circle_column", PGcircle.class)); @@ -913,12 +952,12 @@ public void testGetCircle() throws SQLException { * Test the behavior getObject for interval columns. */ @Test - public void testGetInterval() throws SQLException { + void getInterval() throws SQLException { Statement stmt = conn.createStatement(); PGInterval expected = new PGInterval(0, 0, 3, 4, 5, 6.0d); - stmt.executeUpdate(TestUtil.insertSQL("table1","interval_column","interval '3 4:05:06'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "interval_column", "interval '3 4:05:06'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "interval_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "interval_column")); try { assertTrue(rs.next()); assertEquals(expected, rs.getObject("interval_column", PGInterval.class)); @@ -932,16 +971,16 @@ public void testGetInterval() throws SQLException { * Test the behavior getObject for uuid columns. */ @Test - public void testGetUuid() throws SQLException { + void getUuid() throws SQLException { if (!TestUtil.haveMinimumServerVersion(conn, ServerVersion.v8_3)) { // UUID requires PostgreSQL 8.3+ return; } Statement stmt = conn.createStatement(); String expected = "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"; - stmt.executeUpdate(TestUtil.insertSQL("table1","uuid_column","'" + expected + "'")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "uuid_column", "'" + expected + "'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "uuid_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "uuid_column")); try { assertTrue(rs.next()); assertEquals(UUID.fromString(expected), rs.getObject("uuid_column", UUID.class)); @@ -955,11 +994,11 @@ public void testGetUuid() throws SQLException { * Test the behavior getObject for inet columns. */ @Test - public void testGetInetAddressNull() throws SQLException, UnknownHostException { + void getInetAddressNull() throws SQLException, UnknownHostException { Statement stmt = conn.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","inet_column","NULL")); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj41", "inet_column", "NULL")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "inet_column")); + ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj41", "inet_column")); try { assertTrue(rs.next()); assertNull(rs.getObject("inet_column", InetAddress.class)); @@ -977,12 +1016,12 @@ private void testInet(String inet, InetAddress expectedAddr, String expectedText ResultSet rs = stmt.executeQuery("SELECT '" + inet + "'::inet AS inet_column"); try { assertTrue(rs.next()); - assertEquals("The string value of the inet should match when fetched via getString(...)", expectedText, rs.getString(1)); - assertEquals("The string value of the inet should match when fetched via getString(...)", expectedText, rs.getString("inet_column")); - assertEquals("The object value of the inet should match when fetched via getObject(...)", expectedObj, rs.getObject(1)); - assertEquals("The object value of the inet should match when fetched via getObject(...)", expectedObj, rs.getObject("inet_column")); - assertEquals("The InetAddress value should match when fetched via getObject(..., InetAddress.class)", expectedAddr, rs.getObject("inet_column", InetAddress.class)); - assertEquals("The InetAddress value should match when fetched via getObject(..., InetAddress.class)", expectedAddr, rs.getObject(1, InetAddress.class)); + assertEquals(expectedText, rs.getString(1), "The string value of the inet should match when fetched via getString(...)"); + assertEquals(expectedText, rs.getString("inet_column"), "The string value of the inet should match when fetched via getString(...)"); + assertEquals(expectedObj, rs.getObject(1), "The object value of the inet should match when fetched via getObject(...)"); + assertEquals(expectedObj, rs.getObject("inet_column"), "The object value of the inet should match when fetched via getObject(...)"); + assertEquals(expectedAddr, rs.getObject("inet_column", InetAddress.class), "The InetAddress value should match when fetched via getObject(..., InetAddress.class)"); + assertEquals(expectedAddr, rs.getObject(1, InetAddress.class), "The InetAddress value should match when fetched via getObject(..., InetAddress.class)"); } finally { rs.close(); stmt.close(); @@ -993,7 +1032,7 @@ private void testInet(String inet, InetAddress expectedAddr, String expectedText * Test the behavior getObject for ipv4 inet columns. */ @Test - public void testGetInet4Address() throws SQLException, UnknownHostException { + void getInet4Address() throws SQLException, UnknownHostException { String inet = "192.168.100.128"; InetAddress addr = InetAddress.getByName(inet); testInet(inet, addr, inet); @@ -1005,7 +1044,7 @@ public void testGetInet4Address() throws SQLException, UnknownHostException { * Test the behavior getObject for ipv6 inet columns. */ @Test - public void testGetInet6Address() throws SQLException, UnknownHostException { + void getInet6Address() throws SQLException, UnknownHostException { String inet = "2001:4f8:3:ba:2e0:81ff:fe22:d1f1"; InetAddress addr = InetAddress.getByName(inet); testInet(inet, addr, inet); diff --git a/src/test/java/org/postgresql/test/jdbc4/jdbc41/NetworkTimeoutTest.java b/src/test/java/org/postgresql/test/jdbc4/jdbc41/NetworkTimeoutTest.java index ce62eb1..24fed8d 100644 --- a/src/test/java/org/postgresql/test/jdbc4/jdbc41/NetworkTimeoutTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/jdbc41/NetworkTimeoutTest.java @@ -5,34 +5,31 @@ package org.postgresql.test.jdbc4.jdbc41; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.test.TestUtil; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import java.util.concurrent.TimeUnit; -public class NetworkTimeoutTest { +class NetworkTimeoutTest { @Test - public void testSetNetworkTimeout() throws Exception { + void setNetworkTimeout() throws Exception { Connection conn = TestUtil.openDB(); - try { + assertDoesNotThrow(() -> { conn.setNetworkTimeout(null, 0); - } catch (SQLException e) { - fail("Connection.setNetworkTimeout() throw exception"); - } finally { - TestUtil.closeDB(conn); - } + }, "Connection.setNetworkTimeout() throw exception"); } @Test - public void testSetNetworkTimeoutInvalid() throws Exception { + void setNetworkTimeoutInvalid() throws Exception { Connection conn = TestUtil.openDB(); try { conn.setNetworkTimeout(null, -1); @@ -45,20 +42,16 @@ public void testSetNetworkTimeoutInvalid() throws Exception { } @Test - public void testSetNetworkTimeoutValid() throws Exception { + void setNetworkTimeoutValid() throws Exception { Connection conn = TestUtil.openDB(); - try { + assertDoesNotThrow(() -> { conn.setNetworkTimeout(null, (int) TimeUnit.SECONDS.toMillis(5)); assertEquals(TimeUnit.SECONDS.toMillis(5), conn.getNetworkTimeout()); - } catch (SQLException e) { - fail("Connection.setNetworkTimeout() throw exception"); - } finally { - TestUtil.closeDB(conn); - } + }, "Connection.setNetworkTimeout() throw exception"); } @Test - public void testSetNetworkTimeoutEnforcement() throws Exception { + void setNetworkTimeoutEnforcement() throws Exception { Connection conn = TestUtil.openDB(); Statement stmt = null; try { diff --git a/src/test/java/org/postgresql/test/jdbc4/jdbc41/SchemaTest.java b/src/test/java/org/postgresql/test/jdbc4/jdbc41/SchemaTest.java index f843ab5..83ebc1c 100644 --- a/src/test/java/org/postgresql/test/jdbc4/jdbc41/SchemaTest.java +++ b/src/test/java/org/postgresql/test/jdbc4/jdbc41/SchemaTest.java @@ -6,19 +6,22 @@ package org.postgresql.test.jdbc4.jdbc41; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.PreparedStatement; @@ -29,49 +32,67 @@ import java.sql.Types; import java.util.Properties; -public class SchemaTest { +class SchemaTest { private Connection conn; - private boolean dropUserSchema; + private static boolean dropUserSchema; - @Before - public void setUp() throws Exception { - conn = TestUtil.openDB(); - Statement stmt = conn.createStatement(); - try { - stmt.execute("CREATE SCHEMA " + TestUtil.getUser()); - dropUserSchema = true; - } catch (SQLException e) { - /* assume schema existed */ + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + Statement stmt = conn.createStatement(); + try { + stmt.execute("CREATE SCHEMA " + TestUtil.getUser()); + dropUserSchema = true; + } catch (SQLException e) { + /* assume schema existed */ + } + stmt.execute("CREATE SCHEMA schema1"); + stmt.execute("CREATE SCHEMA schema2"); + stmt.execute("CREATE SCHEMA \"schema 3\""); + stmt.execute("CREATE SCHEMA \"schema \"\"4\""); + stmt.execute("CREATE SCHEMA \"schema '5\""); + stmt.execute("CREATE SCHEMA \"schema ,6\""); + stmt.execute("CREATE SCHEMA \"UpperCase\""); + TestUtil.createTable(conn, "schema1.table1", "id integer"); + TestUtil.createTable(conn, "schema2.table2", "id integer"); + TestUtil.createTable(conn, "\"UpperCase\".table3", "id integer"); + TestUtil.createTable(conn, "schema1.sptest", "id integer"); + TestUtil.createTable(conn, "schema2.sptest", "id varchar"); + stmt.close(); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + Statement stmt = conn.createStatement(); + if (dropUserSchema) { + stmt.execute("DROP SCHEMA " + TestUtil.getUser() + " CASCADE"); + } + stmt.execute("DROP SCHEMA schema1 CASCADE"); + stmt.execute("DROP SCHEMA schema2 CASCADE"); + stmt.execute("DROP SCHEMA \"schema 3\" CASCADE"); + stmt.execute("DROP SCHEMA \"schema \"\"4\" CASCADE"); + stmt.execute("DROP SCHEMA \"schema '5\" CASCADE"); + stmt.execute("DROP SCHEMA \"schema ,6\""); + stmt.execute("DROP SCHEMA \"UpperCase\" CASCADE"); + stmt.close(); } - stmt.execute("CREATE SCHEMA schema1"); - stmt.execute("CREATE SCHEMA schema2"); - stmt.execute("CREATE SCHEMA \"schema 3\""); - stmt.execute("CREATE SCHEMA \"schema \"\"4\""); - stmt.execute("CREATE SCHEMA \"schema '5\""); - stmt.execute("CREATE SCHEMA \"schema ,6\""); - stmt.execute("CREATE SCHEMA \"UpperCase\""); - TestUtil.createTable(conn, "schema1.table1", "id integer"); - TestUtil.createTable(conn, "schema2.table2", "id integer"); - TestUtil.createTable(conn, "\"UpperCase\".table3", "id integer"); - TestUtil.createTable(conn, "schema1.sptest", "id integer"); - TestUtil.createTable(conn, "schema2.sptest", "id varchar"); } - @After - public void tearDown() throws SQLException { + @BeforeEach + void setUp() throws Exception { + conn = TestUtil.openDB(); + } + + @AfterEach + void tearDown() throws SQLException { conn.setAutoCommit(true); conn.setSchema(null); - Statement stmt = conn.createStatement(); - if (dropUserSchema) { - stmt.execute("DROP SCHEMA " + TestUtil.getUser() + " CASCADE"); - } - stmt.execute("DROP SCHEMA schema1 CASCADE"); - stmt.execute("DROP SCHEMA schema2 CASCADE"); - stmt.execute("DROP SCHEMA \"schema 3\" CASCADE"); - stmt.execute("DROP SCHEMA \"schema \"\"4\" CASCADE"); - stmt.execute("DROP SCHEMA \"schema '5\" CASCADE"); - stmt.execute("DROP SCHEMA \"schema ,6\""); - stmt.execute("DROP SCHEMA \"UpperCase\" CASCADE"); + // Clean up objects created by individual test methods + TestUtil.execute(conn, "DROP FUNCTION IF EXISTS schema2.check_fun()"); + TestUtil.execute(conn, "DROP FUNCTION IF EXISTS schema2.check_fun(text)"); + TestUtil.execute(conn, "DROP TABLE IF EXISTS schema1.check_table"); TestUtil.closeDB(conn); } @@ -79,7 +100,7 @@ public void tearDown() throws SQLException { * Test that what you set is what you get. */ @Test - public void testGetSetSchema() throws SQLException { + void getSetSchema() throws SQLException { conn.setSchema("schema1"); assertEquals("schema1", conn.getSchema()); conn.setSchema("schema2"); @@ -99,10 +120,10 @@ public void testGetSetSchema() throws SQLException { * objects from other schemas but doesn't prevent to prefix-access to them. */ @Test - public void testUsingSchema() throws SQLException { + void usingSchema() throws SQLException { Statement stmt = conn.createStatement(); try { - try { + assertDoesNotThrow(() -> { conn.setSchema("schema1"); stmt.executeQuery(TestUtil.selectSQL("table1", "*")); stmt.executeQuery(TestUtil.selectSQL("schema2.table2", "*")); @@ -132,9 +153,7 @@ public void testUsingSchema() throws SQLException { } catch (SQLException e) { // expected } - } catch (SQLException e) { - fail("Could not find expected schema elements: " + e.getMessage()); - } + }, "Could not find expected schema elements: "); } finally { try { stmt.close(); @@ -147,7 +166,7 @@ public void testUsingSchema() throws SQLException { * Test that get schema returns the schema with the highest priority in the search path. */ @Test - public void testMultipleSearchPath() throws SQLException { + void multipleSearchPath() throws SQLException { execute("SET search_path TO schema1,schema2"); assertEquals("schema1", conn.getSchema()); @@ -156,11 +175,10 @@ public void testMultipleSearchPath() throws SQLException { } @Test - public void testSchemaInProperties() throws Exception { + void schemaInProperties() throws Exception { Properties properties = new Properties(); properties.setProperty("currentSchema", "schema1"); - Connection conn = TestUtil.openDB(properties); - try { + try (Connection conn = TestUtil.openDB(properties)) { assertEquals("schema1", conn.getSchema()); Statement stmt = conn.createStatement(); @@ -172,13 +190,11 @@ public void testSchemaInProperties() throws Exception { } catch (SQLException e) { // expected } - } finally { - TestUtil.closeDB(conn); } } @Test - public void testSchemaPath$User() throws Exception { + public void schemaPath$User() throws Exception { execute("SET search_path TO \"$user\",public,schema2"); assertEquals(TestUtil.getUser(), conn.getSchema()); } @@ -196,18 +212,18 @@ private void execute(String sql) throws SQLException { } @Test - public void testSearchPathPreparedStatementAutoCommitFalse() throws SQLException { + void searchPathPreparedStatementAutoCommitFalse() throws SQLException { conn.setAutoCommit(false); - testSearchPathPreparedStatement(); + searchPathPreparedStatementAutoCommitTrue(); } @Test - public void testSearchPathPreparedStatementAutoCommitTrue() throws SQLException { - testSearchPathPreparedStatement(); + void searchPathPreparedStatementAutoCommitTrue() throws SQLException { + searchPathPreparedStatement(); } @Test - public void testSearchPathPreparedStatement() throws SQLException { + void searchPathPreparedStatement() throws SQLException { execute("set search_path to schema1,public"); PreparedStatement ps = conn.prepareStatement("select * from sptest"); for (int i = 0; i < 10; i++) { @@ -224,30 +240,30 @@ public void testSearchPathPreparedStatement() throws SQLException { } @Test - public void testCurrentSchemaPropertyVisibilityTableDuringFunctionCreation() throws SQLException { + void currentSchemaPropertyVisibilityTableDuringFunctionCreation() throws SQLException { Properties properties = new Properties(); properties.setProperty(PGProperty.CURRENT_SCHEMA.getName(), "public,schema1,schema2"); Connection connection = TestUtil.openDB(properties); - TestUtil.execute("create table schema1.check_table (test_col text)", connection); - TestUtil.execute("insert into schema1.check_table (test_col) values ('test_value')", connection); - TestUtil.execute("create or replace function schema2.check_fun () returns text as $$" + TestUtil.execute(connection, "create table schema1.check_table (test_col text)"); + TestUtil.execute(connection, "insert into schema1.check_table (test_col) values ('test_value')"); + TestUtil.execute(connection, "create or replace function schema2.check_fun () returns text as $$" + " select test_col from check_table" - + "$$ language sql immutable", connection); + + "$$ language sql stable"); connection.close(); } @Test - public void testCurrentSchemaPropertyNotVisibilityTableDuringFunctionCreation() throws SQLException { + void currentSchemaPropertyNotVisibilityTableDuringFunctionCreation() throws SQLException { Properties properties = new Properties(); properties.setProperty(PGProperty.CURRENT_SCHEMA.getName(), "public,schema2"); try (Connection connection = TestUtil.openDB(properties)) { - TestUtil.execute("create table schema1.check_table (test_col text)", connection); - TestUtil.execute("insert into schema1.check_table (test_col) values ('test_value')", connection); - TestUtil.execute("create or replace function schema2.check_fun (txt text) returns text as $$" + TestUtil.execute(connection, "create table schema1.check_table (test_col text)"); + TestUtil.execute(connection, "insert into schema1.check_table (test_col) values ('test_value')"); + TestUtil.execute(connection, "create or replace function schema2.check_fun (txt text) returns text as $$" + " select test_col from check_table" - + "$$ language sql immutable", connection); + + "$$ language sql immutable"); } catch (PSQLException e) { String sqlState = e.getSQLState(); String message = e.getMessage(); @@ -270,24 +286,24 @@ public void testCurrentSchemaPropertyNotVisibilityTableDuringFunctionCreation() } @Test - public void testCurrentSchemaPropertyVisibilityFunction() throws SQLException { - testCurrentSchemaPropertyVisibilityTableDuringFunctionCreation(); + void currentSchemaPropertyVisibilityFunction() throws SQLException { + currentSchemaPropertyVisibilityTableDuringFunctionCreation(); Properties properties = new Properties(); properties.setProperty(PGProperty.CURRENT_SCHEMA.getName(), "public,schema1,schema2"); Connection connection = TestUtil.openDB(properties); - TestUtil.execute("select check_fun()", connection); + TestUtil.execute(connection, "select check_fun()"); connection.close(); } @Test - public void testCurrentSchemaPropertyNotVisibilityTableInsideFunction() throws SQLException { - testCurrentSchemaPropertyVisibilityTableDuringFunctionCreation(); + void currentSchemaPropertyNotVisibilityTableInsideFunction() throws SQLException { + currentSchemaPropertyVisibilityTableDuringFunctionCreation(); Properties properties = new Properties(); properties.setProperty(PGProperty.CURRENT_SCHEMA.getName(), "public,schema2"); try (Connection connection = TestUtil.openDB(properties)) { - TestUtil.execute("select check_fun()", connection); + TestUtil.execute(connection, "select check_fun()"); } catch (PSQLException e) { String sqlState = e.getSQLState(); String message = e.getMessage(); @@ -307,12 +323,11 @@ public void testCurrentSchemaPropertyNotVisibilityTableInsideFunction() throws S } } - private void assertColType(PreparedStatement ps, String message, int expected) throws SQLException { + private static void assertColType(PreparedStatement ps, String message, int expected) throws SQLException { ResultSet rs = ps.executeQuery(); ResultSetMetaData md = rs.getMetaData(); int columnType = md.getColumnType(1); - assertEquals(message, - expected, columnType); + assertEquals(expected, columnType, message); rs.close(); } diff --git a/src/test/java/org/postgresql/test/jdbc4/jdbc41/SharedTimerClassLoaderLeakTest.java b/src/test/java/org/postgresql/test/jdbc4/jdbc41/SharedTimerClassLoaderLeakTest.java deleted file mode 100644 index 4ae8f72..0000000 --- a/src/test/java/org/postgresql/test/jdbc4/jdbc41/SharedTimerClassLoaderLeakTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2016, PostgreSQL Global Development Group - * See the LICENSE file in the project root for more information. - */ - -package org.postgresql.test.jdbc4.jdbc41; - -import org.postgresql.Driver; - -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import se.jiderhamn.classloader.PackagesLoadedOutsideClassLoader; -import se.jiderhamn.classloader.leak.JUnitClassloaderRunner; -import se.jiderhamn.classloader.leak.Leaks; - -/** - *

          Test case that verifies that the use of {@link org.postgresql.util.SharedTimer} within - * {@link org.postgresql.Driver} does not cause ClassLoader leaks.

          - * - *

          The class is placed in {@code jdbc41} package so it won't be tested in JRE6 build. - * {@link JUnitClassloaderRunner} does not support JRE6, so we have to skip the test there.

          - * - * @author Mattias Jiderhamn - */ -@RunWith(JUnitClassloaderRunner.class) -@PackagesLoadedOutsideClassLoader(packages = "org.postgresql", addToDefaults = true) -public class SharedTimerClassLoaderLeakTest { - - /** Starting a {@link org.postgresql.util.SharedTimer} should not cause ClassLoader leaks. */ - @Leaks(false) - @Test - public void sharedTimerDoesNotCauseLeak() { - Driver.getSharedTimer().getTimer(); // Start timer - } - - @After - public void tearDown() { - Driver.getSharedTimer().releaseTimer(); - } -} diff --git a/src/test/java/org/postgresql/test/jdbc42/AdaptiveFetchSizeTest.java b/src/test/java/org/postgresql/test/jdbc42/AdaptiveFetchSizeTest.java index 4fee96e..0634e6a 100644 --- a/src/test/java/org/postgresql/test/jdbc42/AdaptiveFetchSizeTest.java +++ b/src/test/java/org/postgresql/test/jdbc42/AdaptiveFetchSizeTest.java @@ -5,16 +5,16 @@ package org.postgresql.test.jdbc42; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.PGConnection; import org.postgresql.PGProperty; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Assume; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import java.lang.management.ManagementFactory; import java.sql.Connection; @@ -26,20 +26,20 @@ /** * Integration tests for adaptive fetch process. */ -public class AdaptiveFetchSizeTest { +class AdaptiveFetchSizeTest { private Connection connection; private PreparedStatement statement; private ResultSet resultSet; - private String table = "test_adaptive_fetch"; - private String columns = "value VARCHAR"; + private final String table = "test_adaptive_fetch"; + private final String columns = "value VARCHAR"; /** * Drop table and close connection. */ - @After - public void tearDown() throws SQLException { + @AfterEach + void tearDown() throws SQLException { if (connection != null && !connection.isClosed()) { connection.setAutoCommit(true); if (resultSet != null) { @@ -67,7 +67,7 @@ public void tearDown() throws SQLException { * - check if all 50 rows were read. */ @Test - public void testAdaptiveFetching() throws SQLException { + void adaptiveFetching() throws SQLException { int startFetchSize = 4; int expectedFirstSize = 8; int expectedSecondSize = 7; @@ -81,7 +81,7 @@ public void testAdaptiveFetching() throws SQLException { openConnectionAndCreateTable(properties); - for (int i = 0; i != expectedCounter; i++) { + for (int i = 0; i < expectedCounter; i++) { if (i == 4) { addStringWithSize(40); } else { @@ -91,12 +91,12 @@ public void testAdaptiveFetching() throws SQLException { executeFetchingQuery(); - for (int i = 0; i != 4; i++) { + for (int i = 0; i < 4; i++) { resultSet.next(); resultCounter++; assertEquals(startFetchSize, resultSet.getFetchSize()); } - for (int i = 0; i != 8; i++) { + for (int i = 0; i < 8; i++) { resultSet.next(); resultCounter++; assertEquals(expectedFirstSize, resultSet.getFetchSize()); @@ -121,7 +121,7 @@ public void testAdaptiveFetching() throws SQLException { * - check if all 50 rows were read. */ @Test - public void testAdaptiveFetchingWithMinimumSize() throws SQLException { + void adaptiveFetchingWithMinimumSize() throws SQLException { int startFetchSize = 4; int expectedSize = 10; int expectedCounter = 50; @@ -135,7 +135,7 @@ public void testAdaptiveFetchingWithMinimumSize() throws SQLException { openConnectionAndCreateTable(properties); - for (int i = 0; i != expectedCounter; i++) { + for (int i = 0; i < expectedCounter; i++) { if (i == 0) { addStringWithSize(270); } else { @@ -145,7 +145,7 @@ public void testAdaptiveFetchingWithMinimumSize() throws SQLException { executeFetchingQuery(); - for (int i = 0; i != 4; i++) { + for (int i = 0; i < 4; i++) { resultSet.next(); resultCounter++; assertEquals(startFetchSize, resultSet.getFetchSize()); @@ -171,7 +171,7 @@ public void testAdaptiveFetchingWithMinimumSize() throws SQLException { * - check if all 50 rows were read. */ @Test - public void testAdaptiveFetchingWithMaximumSize() throws SQLException { + void adaptiveFetchingWithMaximumSize() throws SQLException { int startFetchSize = 4; int expectedSize = 10; int expectedCounter = 50; @@ -185,7 +185,7 @@ public void testAdaptiveFetchingWithMaximumSize() throws SQLException { openConnectionAndCreateTable(properties); - for (int i = 0; i != expectedCounter; i++) { + for (int i = 0; i < expectedCounter; i++) { if (i < 4) { addStringWithSize(10); } else { @@ -195,7 +195,7 @@ public void testAdaptiveFetchingWithMaximumSize() throws SQLException { executeFetchingQuery(); - for (int i = 0; i != 4; i++) { + for (int i = 0; i < 4; i++) { resultSet.next(); resultCounter++; assertEquals(startFetchSize, resultSet.getFetchSize()); @@ -220,7 +220,7 @@ public void testAdaptiveFetchingWithMaximumSize() throws SQLException { * - check if all 1000 rows were read. */ @Test - public void testAdaptiveFetchingWithMoreData() throws SQLException { + void adaptiveFetchingWithMoreData() throws SQLException { int startFetchSize = 4; int expectedCounter = 1000; int resultCounter = 0; @@ -234,13 +234,13 @@ public void testAdaptiveFetchingWithMoreData() throws SQLException { openConnectionAndCreateTable(properties); - for (int i = 0; i != expectedCounter; i++) { + for (int i = 0; i < expectedCounter; i++) { addStringWithSize(10); } executeFetchingQuery(); - for (int i = 0; i != 4; i++) { + for (int i = 0; i < 4; i++) { resultSet.next(); resultCounter++; assertEquals(startFetchSize, resultSet.getFetchSize()); @@ -272,12 +272,12 @@ private void executeFetchingQuery() throws SQLException { private void addStringWithSize(int size) throws SQLException { StringBuilder sb = new StringBuilder(size + 2); sb.append("'"); - for (int i = 0; i != size; i++) { + for (int i = 0; i < size; i++) { sb.append('H'); } sb.append("'"); String insert = TestUtil.insertSQL(table, "value", sb.toString()); - TestUtil.execute(insert, connection); + TestUtil.execute(connection, insert); } /** @@ -297,12 +297,12 @@ private void openConnectionAndCreateTable(Properties properties) throws SQLExcep * * @param connection Connection to be checked. */ - private void checkIfFetchTestCanBePerformed(Connection connection) throws SQLException { + private static void checkIfFetchTestCanBePerformed(Connection connection) throws SQLException { PGConnection pgConnection = connection.unwrap(PGConnection.class); PreferQueryMode preferQueryMode = pgConnection == null ? PreferQueryMode.EXTENDED : pgConnection.getPreferQueryMode(); - Assume.assumeTrue("Fetching tests can't be performed in simple mode", - preferQueryMode != PreferQueryMode.SIMPLE); + assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE, + "Fetching tests can't be performed in simple mode"); } } diff --git a/src/test/java/org/postgresql/test/jdbc42/CustomizeDefaultFetchSizeTest.java b/src/test/java/org/postgresql/test/jdbc42/CustomizeDefaultFetchSizeTest.java index b9a753c..66ae16e 100644 --- a/src/test/java/org/postgresql/test/jdbc42/CustomizeDefaultFetchSizeTest.java +++ b/src/test/java/org/postgresql/test/jdbc42/CustomizeDefaultFetchSizeTest.java @@ -6,14 +6,15 @@ package org.postgresql.test.jdbc42; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; import org.hamcrest.CoreMatchers; -import org.junit.After; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import java.sql.CallableStatement; import java.sql.Connection; @@ -21,19 +22,19 @@ import java.sql.Statement; import java.util.Properties; -public class CustomizeDefaultFetchSizeTest { +class CustomizeDefaultFetchSizeTest { private Connection connection; - @After - public void tearDown() throws Exception { + @AfterEach + void tearDown() throws Exception { if (connection != null) { TestUtil.closeDB(connection); } } @Test - public void testSetPredefineDefaultFetchSizeOnStatement() throws Exception { + void setPredefineDefaultFetchSizeOnStatement() throws Exception { final int waitFetchSize = 13; Properties properties = new Properties(); PGProperty.DEFAULT_ROW_FETCH_SIZE.set(properties, waitFetchSize); @@ -52,7 +53,7 @@ public void testSetPredefineDefaultFetchSizeOnStatement() throws Exception { } @Test - public void testSetPredefineDefaultFetchSizeOnPreparedStatement() throws Exception { + void setPredefineDefaultFetchSizeOnPreparedStatement() throws Exception { final int waitFetchSize = 14; Properties properties = new Properties(); @@ -69,15 +70,17 @@ public void testSetPredefineDefaultFetchSizeOnPreparedStatement() throws Excepti resultFetchSize, CoreMatchers.equalTo(waitFetchSize)); } - @Test(expected = SQLException.class) - public void testNotAvailableSpecifyNegativeFetchSize() throws Exception { - Properties properties = new Properties(); - PGProperty.DEFAULT_ROW_FETCH_SIZE.set(properties, Integer.MIN_VALUE); + @Test + void notAvailableSpecifyNegativeFetchSize() throws Exception { + assertThrows(SQLException.class, () -> { + Properties properties = new Properties(); + PGProperty.DEFAULT_ROW_FETCH_SIZE.set(properties, Integer.MIN_VALUE); - connection = TestUtil.openDB(properties); + connection = TestUtil.openDB(properties); - fail( - "On step initialize connection we know about not valid parameter PGProperty.DEFAULT_ROW_FETCH_SIZE they can't be negative, " - + "so we should throw correspond exception about it rather than fall with exception in runtime for example during create statement"); + fail( + "On step initialize connection we know about not valid parameter PGProperty.DEFAULT_ROW_FETCH_SIZE they can't be negative, " + + "so we should throw correspond exception about it rather than fall with exception in runtime for example during create statement"); + }); } } diff --git a/src/test/java/org/postgresql/test/jdbc42/DatabaseMetaDataTest.java b/src/test/java/org/postgresql/test/jdbc42/DatabaseMetaDataTest.java index ee225dd..7dbad11 100644 --- a/src/test/java/org/postgresql/test/jdbc42/DatabaseMetaDataTest.java +++ b/src/test/java/org/postgresql/test/jdbc42/DatabaseMetaDataTest.java @@ -5,19 +5,23 @@ package org.postgresql.test.jdbc42; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.postgresql.core.ServerVersion; import org.postgresql.core.TypeInfo; import org.postgresql.jdbc.PgConnection; import org.postgresql.test.TestUtil; import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -25,37 +29,57 @@ import java.sql.SQLException; import java.sql.Types; -public class DatabaseMetaDataTest { +class DatabaseMetaDataTest { private Connection conn; - @Before - public void setUp() throws Exception { + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createSchema(conn, "test_schema"); + TestUtil.createEnumType(conn, "test_schema.test_enum", "'val'"); + TestUtil.createTable(conn, "test_schema.off_path_table", "var test_schema.test_enum[]"); + TestUtil.createEnumType(conn, "_test_enum", "'evil'"); + TestUtil.createEnumType(conn, "test_enum", "'other'"); + TestUtil.createTable(conn, "on_path_table", "a test_schema.test_enum[], b _test_enum, c test_enum[]"); + TestUtil.createTable(conn, "decimaltest", "a decimal, b decimal(10, 5)"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "decimaltest"); + TestUtil.dropTable(conn, "on_path_table"); + TestUtil.dropType(conn, "test_enum"); + TestUtil.dropType(conn, "_test_enum"); + TestUtil.dropSchema(conn, "test_schema"); + } + } + + @BeforeEach + void setUp() throws Exception { conn = TestUtil.openDB(); - TestUtil.createSchema(conn, "test_schema"); - TestUtil.createEnumType(conn, "test_schema.test_enum", "'val'"); - TestUtil.createTable(conn, "test_schema.off_path_table", "var test_schema.test_enum[]"); - TestUtil.createEnumType(conn, "_test_enum", "'evil'"); - TestUtil.createEnumType(conn, "test_enum", "'other'"); - TestUtil.createTable(conn, "on_path_table", "a test_schema.test_enum[], b _test_enum, c test_enum[]"); - TestUtil.createTable(conn, "decimaltest", "a decimal, b decimal(10, 5)"); - } - - @After - public void tearDown() throws Exception { - TestUtil.dropTable(conn, "decimaltest"); - TestUtil.dropTable(conn, "on_path_table"); - TestUtil.dropType(conn, "test_enum"); - TestUtil.dropType(conn, "_test_enum"); - TestUtil.dropSchema(conn, "test_schema"); + } + + @AfterEach + void tearDown() throws Exception { TestUtil.closeDB(conn); } @Test - public void testGetColumnsForNullScale() throws Exception { + void getColumnsForNullScale_whenCatalogArgPercentSign_expectNoResults() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getColumns("%", "%", "decimaltest", "%"); + assertFalse(rs.next()); + } + + @Test + void getColumnsForNullScale() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getColumns(null, "%", "decimaltest", "%"); assertTrue(rs.next()); assertEquals("a", rs.getString("COLUMN_NAME")); assertEquals(0, rs.getInt("DECIMAL_DIGITS")); @@ -66,50 +90,83 @@ public void testGetColumnsForNullScale() throws Exception { assertEquals(5, rs.getInt("DECIMAL_DIGITS")); assertFalse(rs.wasNull()); - assertTrue(!rs.next()); + assertFalse(rs.next()); + } + + @Test + void getColumnsForSchema() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + String catalog = conn.getCatalog(); + + ResultSet rs = dbmd.getColumns(null, "%", "decimaltest", "%"); + assertTrue(rs.next()); + assertEquals(catalog, rs.getString("TABLE_CAT")); + assertEquals("a", rs.getString("COLUMN_NAME")); + assertEquals(0, rs.getInt("DECIMAL_DIGITS")); } @Test - public void testGetCorrectSQLTypeForOffPathTypes() throws Exception { + void getCorrectSQLTypeForOffPathTypes_whenCatalogArgPercentSign_expectNoResults() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getColumns("%", "%", "off_path_table", "%"); + assertFalse(rs.next()); + } + + @Test + void getCorrectSQLTypeForOffPathTypes() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getColumns(null, "%", "off_path_table", "%"); assertTrue(rs.next()); assertEquals("var", rs.getString("COLUMN_NAME")); - assertEquals("Detects correct off-path type name", "\"test_schema\".\"_test_enum\"", rs.getString("TYPE_NAME")); - assertEquals("Detects correct SQL type for off-path types", Types.ARRAY, rs.getInt("DATA_TYPE")); + assertEquals("\"test_schema\".\"_test_enum\"", rs.getString("TYPE_NAME"), "Detects correct off-path type name"); + assertEquals(Types.ARRAY, rs.getInt("DATA_TYPE"), "Detects correct SQL type for off-path types"); assertFalse(rs.next()); } @Test - public void testGetCorrectSQLTypeForShadowedTypes() throws Exception { + void getCorrectSQLTypeForShadowedTypes_whenCatalogArgPercentSign_expectNoResults() throws Exception { DatabaseMetaData dbmd = conn.getMetaData(); ResultSet rs = dbmd.getColumns("%", "%", "on_path_table", "%"); + assertFalse(rs.next()); + } + + @Test + void getCorrectSQLTypeForShadowedTypes() throws Exception { + DatabaseMetaData dbmd = conn.getMetaData(); + + ResultSet rs = dbmd.getColumns(null, "%", "on_path_table", "%"); + assertTrue(rs.next()); assertEquals("a", rs.getString("COLUMN_NAME")); - assertEquals("Correctly maps types from other schemas","\"test_schema\".\"_test_enum\"", rs.getString("TYPE_NAME")); + assertEquals("\"test_schema\".\"_test_enum\"", rs.getString("TYPE_NAME"), "Correctly maps types from other schemas"); assertEquals(Types.ARRAY, rs.getInt("DATA_TYPE")); assertTrue(rs.next()); assertEquals("b", rs.getString("COLUMN_NAME")); // = TYPE _test_enum AS ENUM ('evil') - assertEquals( "_test_enum", rs.getString("TYPE_NAME")); + assertEquals("_test_enum", rs.getString("TYPE_NAME")); assertEquals(Types.VARCHAR, rs.getInt("DATA_TYPE")); assertTrue(rs.next()); assertEquals("c", rs.getString("COLUMN_NAME")); // = array of TYPE test_enum AS ENUM ('value') - assertEquals("Correctly detects shadowed array type name","___test_enum", rs.getString("TYPE_NAME")); - assertEquals("Correctly detects type of shadowed name", Types.ARRAY, rs.getInt("DATA_TYPE")); + if (TestUtil.haveMinimumServerVersion(conn, ServerVersion.v16)) { + assertEquals("_test_enum_1", rs.getString("TYPE_NAME"), "Correctly detects shadowed array type name"); + } else { + assertEquals("___test_enum", rs.getString("TYPE_NAME"), "Correctly detects shadowed array type name"); + } + assertEquals(Types.ARRAY, rs.getInt("DATA_TYPE"), "Correctly detects type of shadowed name"); assertFalse(rs.next()); } @Test - public void testLargeOidIsHandledCorrectly() throws SQLException { + void largeOidIsHandledCorrectly() throws SQLException { TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo(); try { @@ -120,7 +177,7 @@ public void testLargeOidIsHandledCorrectly() throws SQLException { } @Test - public void testOidConversion() throws SQLException { + void oidConversion() throws SQLException { TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo(); int oid = 0; long loid = 0; @@ -143,15 +200,27 @@ public void testOidConversion() throws SQLException { assertEquals(loid, ti.intOidToLong(oid)); } - @Test(expected = PSQLException.class) - public void testOidConversionThrowsForNegativeLongValues() throws SQLException { - TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo(); - ti.longOidToInt(-1); + @Test + void oidConversionThrowsForNegativeLongValues() throws SQLException { + assertThrows(PSQLException.class, () -> { + TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo(); + ti.longOidToInt(-1); + }); } - @Test(expected = PSQLException.class) - public void testOidConversionThrowsForTooLargeLongValues() throws SQLException { - TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo(); - ti.longOidToInt(1L << 32); + @Test + void oidConversionThrowsForTooLargeLongValues() throws SQLException { + assertThrows(PSQLException.class, () -> { + TypeInfo ti = conn.unwrap(PgConnection.class).getTypeInfo(); + ti.longOidToInt(1L << 32); + }); + } + + @Test + void getSchemasWithInvalidCatalog() throws SQLException { + DatabaseMetaData dbmd = conn.getMetaData(); + try ( ResultSet rs = dbmd.getSchemas("nonsenseschema", null)) { + assertFalse(rs.next()); + } } } diff --git a/src/test/java/org/postgresql/test/jdbc42/GetObject310InfinityTests.java b/src/test/java/org/postgresql/test/jdbc42/GetObject310InfinityTest.java similarity index 76% rename from src/test/java/org/postgresql/test/jdbc42/GetObject310InfinityTests.java rename to src/test/java/org/postgresql/test/jdbc42/GetObject310InfinityTest.java index 04b81c8..ec06a83 100644 --- a/src/test/java/org/postgresql/test/jdbc42/GetObject310InfinityTests.java +++ b/src/test/java/org/postgresql/test/jdbc42/GetObject310InfinityTest.java @@ -5,15 +5,16 @@ package org.postgresql.test.jdbc42; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Assert; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.lang.reflect.Field; import java.sql.PreparedStatement; @@ -26,14 +27,15 @@ import java.util.Arrays; import java.util.Collection; -@RunWith(Parameterized.class) -public class GetObject310InfinityTests extends BaseTest4 { +@ParameterizedClass +@MethodSource("data") +public class GetObject310InfinityTest extends BaseTest4 { private final String expression; private final String pgType; private final Class klass; private final Object expectedValue; - public GetObject310InfinityTests(BinaryMode binaryMode, String expression, + public GetObject310InfinityTest(BinaryMode binaryMode, String expression, String pgType, Class klass, Object expectedValue) { setBinaryMode(binaryMode); this.expression = expression; @@ -45,20 +47,20 @@ public GetObject310InfinityTests(BinaryMode binaryMode, String expression, @Override public void setUp() throws Exception { super.setUp(); - Assume.assumeTrue("PostgreSQL 8.3 does not support 'infinity' for 'date'", - !"date".equals(pgType) || TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)); + assumeTrue( + !"date".equals(pgType) || TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4), + "PostgreSQL 8.3 does not support 'infinity' for 'date'"); } - @Parameterized.Parameters(name = "binary = {0}, expr = {1}, pgType = {2}, klass = {3}") public static Iterable data() throws IllegalAccessException { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { for (String expression : Arrays.asList("-infinity", "infinity")) { for (String pgType : Arrays.asList("date", "timestamp", "timestamp with time zone")) { for (Class klass : Arrays.asList(LocalDate.class, LocalDateTime.class, OffsetDateTime.class)) { - if (klass.equals(LocalDate.class) && !pgType.equals("date")) { + if (klass.equals(LocalDate.class) && !"date".equals(pgType)) { continue; } if (klass.equals(LocalDateTime.class) && !pgType.startsWith("timestamp")) { @@ -67,7 +69,7 @@ public static Iterable data() throws IllegalAccessException { if (klass.equals(OffsetDateTime.class) && !pgType.startsWith("timestamp")) { continue; } - if (klass.equals(LocalDateTime.class) && pgType.equals("timestamp with time zone")) { + if (klass.equals(LocalDateTime.class) && "timestamp with time zone".equals(pgType)) { // org.postgresql.util.PSQLException: Cannot convert the column of type TIMESTAMPTZ to requested type timestamp. continue; } @@ -92,6 +94,6 @@ public void test() throws SQLException { ResultSet rs = stmt.executeQuery(); rs.next(); Object res = rs.getObject(1, klass); - Assert.assertEquals(expectedValue, res); + assertEquals(expectedValue, res); } } diff --git a/src/test/java/org/postgresql/test/jdbc42/GetObject310Test.java b/src/test/java/org/postgresql/test/jdbc42/GetObject310Test.java index b5dc6be..a4e996e 100644 --- a/src/test/java/org/postgresql/test/jdbc42/GetObject310Test.java +++ b/src/test/java/org/postgresql/test/jdbc42/GetObject310Test.java @@ -5,11 +5,12 @@ package org.postgresql.test.jdbc42; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; @@ -17,10 +18,14 @@ import org.postgresql.util.PSQLException; import org.postgresql.util.PSQLState; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -30,21 +35,23 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; +import java.time.OffsetTime; import java.time.ZoneId; import java.time.ZoneOffset; +import java.time.chrono.IsoChronology; import java.time.chrono.IsoEra; -import java.time.temporal.ChronoField; import java.time.temporal.Temporal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.TimeZone; -import java.util.function.UnaryOperator; import java.util.stream.Collectors; import java.util.stream.Stream; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") +@Isolated("Uses TimeZone.setDefault") public class GetObject310Test extends BaseTest4 { private static final TimeZone saveTZ = TimeZone.getDefault(); @@ -54,34 +61,48 @@ public class GetObject310Test extends BaseTest4 { private static final ZoneOffset GMT05 = ZoneOffset.of("-05:00"); // -0500 always private static final ZoneOffset GMT13 = ZoneOffset.of("+13:00"); // +1300 always + private static final IsoChronology ISO = IsoChronology.INSTANCE; + public GetObject310Test(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } + @BeforeAll + static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "testgetobj310", "timestamp_without_time_zone_column timestamp without time zone," + + "timestamp_with_time_zone_column timestamp with time zone," + + "date_column date," + + "time_without_time_zone_column time without time zone," + + "time_with_time_zone_column time with time zone" + ); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "testgetobj310"); + } + } + @Override public void setUp() throws Exception { super.setUp(); - TestUtil.createTable(con, "table1", "timestamp_without_time_zone_column timestamp without time zone," - + "timestamp_with_time_zone_column timestamp with time zone," - + "date_column date," - + "time_without_time_zone_column time without time zone," - + "time_with_time_zone_column time with time zone" - ); + TestUtil.execute(con, "TRUNCATE testgetobj310"); } @Override public void tearDown() throws SQLException { TimeZone.setDefault(saveTZ); - TestUtil.dropTable(con, "table1"); super.tearDown(); } @@ -90,17 +111,101 @@ public void tearDown() throws SQLException { */ @Test public void testGetLocalDate() throws SQLException { - Statement stmt = con.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","date_column","DATE '1999-01-08'")); + assumeTrue(TestUtil.haveIntegerDateTimes(con)); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "date_column")); - try { - assertTrue(rs.next()); - LocalDate localDate = LocalDate.of(1999, 1, 8); - assertEquals(localDate, rs.getObject("date_column", LocalDate.class)); - assertEquals(localDate, rs.getObject(1, LocalDate.class)); - } finally { - rs.close(); + List zoneIdsToTest = new ArrayList<>(); + zoneIdsToTest.add("Africa/Casablanca"); // It is something like GMT+0..GMT+1 + zoneIdsToTest.add("America/Adak"); // It is something like GMT-10..GMT-9 + zoneIdsToTest.add("Atlantic/Azores"); // It is something like GMT-1..GMT+0 + zoneIdsToTest.add("Europe/Berlin"); // It is something like GMT+1..GMT+2 + zoneIdsToTest.add("Europe/Moscow"); // It is something like GMT+3..GMT+4 for 2000s + zoneIdsToTest.add("Pacific/Apia"); // It is something like GMT+13..GMT+14 + zoneIdsToTest.add("Pacific/Niue"); // It is something like GMT-11..GMT-11 + for (int i = -12; i <= 13; i++) { + zoneIdsToTest.add(String.format("GMT%+02d", i)); + } + + List datesToTest = Arrays.asList("1998-01-08", + // Some random dates + "1981-12-11", "2022-02-22", + "2015-09-03", "2015-06-30", + "1997-06-30", "1997-07-01", "2012-06-30", "2012-07-01", + "2015-06-30", "2015-07-01", "2005-12-31", "2006-01-01", + "2008-12-31", "2009-01-01", "2015-06-30", "2015-07-31", + "2015-07-31", + + // On 2000-03-26 02:00:00 Moscow went to DST, thus local time became 03:00:00 + "2003-03-25", "2000-03-26", "2000-03-27", + + // This is a pre-1970 date, so check if it is rounded properly + "1950-07-20", + + // Ensure the calendar is proleptic + "1582-01-01", "1582-12-31", + "1582-09-30", "1582-10-16", + + // https://github.com/pgjdbc/pgjdbc/issues/2221 + "0001-01-01", + "1000-01-01", "1000-06-01", "0999-12-31", + + // On 2000-10-29 03:00:00 Moscow went to regular time, thus local time became 02:00:00 + "2000-10-28", "2000-10-29", "2000-10-30"); + + for (String zoneId : zoneIdsToTest) { + ZoneId zone = ZoneId.of(zoneId); + for (String date : datesToTest) { + localDate(zone, date); + } + } + } + + public void localDate(ZoneId zoneId, String date) throws SQLException { + TimeZone.setDefault(TimeZone.getTimeZone(zoneId)); + try (Statement stmt = con.createStatement() ) { + stmt.executeUpdate("DELETE FROM testgetobj310"); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj310", "date_column", "DATE '" + date + "'")); + + try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj310", "date_column")) ) { + assertTrue(rs.next()); + LocalDate localDate = LocalDate.parse(date); + assertEquals(localDate, rs.getObject("date_column", LocalDate.class)); + assertEquals(localDate, rs.getObject(1, LocalDate.class)); + } + } + } + + /** + * Test the behavior getObject for timetz columns. + */ + @Test + public void testGetOffsetTime() throws SQLException { + List timesToTest = Arrays.asList("00:00:00+00:00", "00:00:00+00:30", + "01:02:03.333444+02:00", "23:59:59.999999-12:00", + "11:22:59.4711-08:00", "23:59:59.0-12:00", + "11:22:59.4711+15:59:12", "23:59:59.0-15:59:12" + ); + + for (String time : timesToTest) { + try (Statement stmt = con.createStatement() ) { + stmt.executeUpdate("DELETE FROM testgetobj310"); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj310", "time_with_time_zone_column", "time with time zone '" + time + "'")); + + try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj310", "time_with_time_zone_column")) ) { + assertTrue(rs.next()); + OffsetTime offsetTime = OffsetTime.parse(time); + assertEquals(offsetTime, rs.getObject("time_with_time_zone_column", OffsetTime.class)); + assertEquals(offsetTime, rs.getObject(1, OffsetTime.class)); + + //Also test that we get the correct values when retrieving the data as OffsetDateTime objects on EPOCH (required by JDBC) + OffsetDateTime offsetDT = offsetTime.atDate(LocalDate.of(1970, 1, 1)); + assertEquals(offsetDT, rs.getObject("time_with_time_zone_column", OffsetDateTime.class)); + assertEquals(offsetDT, rs.getObject(1, OffsetDateTime.class)); + + assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDate.class); + assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalTime.class); + assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDateTime.class); + } + } } } @@ -109,17 +214,38 @@ public void testGetLocalDate() throws SQLException { */ @Test public void testGetLocalTime() throws SQLException { - Statement stmt = con.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","TIME '04:05:06.123456'")); + try (Statement stmt = con.createStatement() ) { + stmt.executeUpdate(TestUtil.insertSQL("testgetobj310", "time_without_time_zone_column", "TIME '04:05:06.123456'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column")); - try { - assertTrue(rs.next()); - LocalTime localTime = LocalTime.of(4, 5, 6, 123456000); - assertEquals(localTime, rs.getObject("time_without_time_zone_column", LocalTime.class)); - assertEquals(localTime, rs.getObject(1, LocalTime.class)); - } finally { - rs.close(); + try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj310", "time_without_time_zone_column"))) { + assertTrue(rs.next()); + LocalTime localTime = LocalTime.of(4, 5, 6, 123456000); + assertEquals(localTime, rs.getObject("time_without_time_zone_column", LocalTime.class)); + assertEquals(localTime, rs.getObject(1, LocalTime.class)); + + assertDataTypeMismatch(rs, "time_without_time_zone_column", OffsetTime.class); + assertDataTypeMismatch(rs, "time_without_time_zone_column", OffsetDateTime.class); + assertDataTypeMismatch(rs, "time_without_time_zone_column", LocalDate.class); + assertDataTypeMismatch(rs, "time_without_time_zone_column", LocalDateTime.class); + } + } + } + + /** + * Test the behavior getObject for time columns with value "24:00", which isn't supported by + * {@link LocalTime}, and thus gets converted to {@link LocalTime#MAX} + */ + @Test + public void testGetLocalTimeMax() throws SQLException { + try (Statement stmt = con.createStatement() ) { + stmt.executeUpdate(TestUtil.insertSQL("testgetobj310", "time_without_time_zone_column", "TIME '24:00'")); + + try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj310", "time_without_time_zone_column"))) { + assertTrue(rs.next()); + LocalTime localTime = LocalTime.MAX; + assertEquals(localTime, rs.getObject("time_without_time_zone_column", LocalTime.class)); + assertEquals(localTime, rs.getObject(1, LocalTime.class)); + } } } @@ -128,44 +254,31 @@ public void testGetLocalTime() throws SQLException { */ @Test public void testGetLocalTimeNull() throws SQLException { - Statement stmt = con.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","time_without_time_zone_column","NULL")); + try (Statement stmt = con.createStatement() ) { + stmt.executeUpdate(TestUtil.insertSQL("testgetobj310", "time_without_time_zone_column", "NULL")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_without_time_zone_column")); - try { - assertTrue(rs.next()); - assertNull(rs.getObject("time_without_time_zone_column", LocalTime.class)); - assertNull(rs.getObject(1, LocalTime.class)); - } finally { - rs.close(); + try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj310", "time_without_time_zone_column"))) { + assertTrue(rs.next()); + assertNull(rs.getObject("time_without_time_zone_column", LocalTime.class)); + assertNull(rs.getObject(1, LocalTime.class)); + } } } /** - * Test the behavior getObject for time columns with null. + * Test the behavior getObject for time columns with invalid type. */ @Test public void testGetLocalTimeInvalidType() throws SQLException { - Statement stmt = con.createStatement(); - stmt.executeUpdate(TestUtil.insertSQL("table1","time_with_time_zone_column", "TIME '04:05:06.123456-08:00'")); + try (Statement stmt = con.createStatement() ) { + stmt.executeUpdate(TestUtil.insertSQL("testgetobj310", "time_with_time_zone_column", "TIME '04:05:06.123456-08:00'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "time_with_time_zone_column")); - try { - assertTrue(rs.next()); - try { - assertNull(rs.getObject("time_with_time_zone_column", LocalTime.class)); - } catch (PSQLException e) { - assertTrue(e.getSQLState().equals(PSQLState.DATA_TYPE_MISMATCH.getState()) - || e.getSQLState().equals(PSQLState.BAD_DATETIME_FORMAT.getState())); - } - try { - assertNull(rs.getObject(1, LocalTime.class)); - } catch (PSQLException e) { - assertTrue(e.getSQLState().equals(PSQLState.DATA_TYPE_MISMATCH.getState()) - || e.getSQLState().equals(PSQLState.BAD_DATETIME_FORMAT.getState())); + try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj310", "time_with_time_zone_column"))) { + assertTrue(rs.next()); + assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalTime.class); + assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDateTime.class); + assertDataTypeMismatch(rs, "time_with_time_zone_column", LocalDate.class); } - } finally { - rs.close(); } } @@ -176,7 +289,7 @@ public void testGetLocalTimeInvalidType() throws SQLException { public void testGetLocalDateTime() throws SQLException { assumeTrue(TestUtil.haveIntegerDateTimes(con)); - List zoneIdsToTest = new ArrayList(); + List zoneIdsToTest = new ArrayList<>(); zoneIdsToTest.add("Africa/Casablanca"); // It is something like GMT+0..GMT+1 zoneIdsToTest.add("America/Adak"); // It is something like GMT-10..GMT-9 zoneIdsToTest.add("Atlantic/Azores"); // It is something like GMT-1..GMT+0 @@ -204,6 +317,11 @@ public void testGetLocalDateTime() throws SQLException { // Ensure the calendar is proleptic "1582-09-30T00:00:00", "1582-10-16T00:00:00", + // https://github.com/pgjdbc/pgjdbc/issues/2221 + "0001-01-01T00:00:00", + "1000-01-01T00:00:00", + "1000-01-01T23:59:59", "1000-06-01T01:00:00", "0999-12-31T23:59:59", + // On 2000-10-29 03:00:00 Moscow went to regular time, thus local time became 02:00:00 "2000-10-29T01:59:59", "2000-10-29T02:00:00", "2000-10-29T02:00:01", "2000-10-29T02:59:59", "2000-10-29T03:00:00", "2000-10-29T03:00:01", "2000-10-29T03:59:59", "2000-10-29T04:00:00", @@ -219,12 +337,11 @@ public void testGetLocalDateTime() throws SQLException { public void localTimestamps(ZoneId zoneId, String timestamp) throws SQLException { TimeZone.setDefault(TimeZone.getTimeZone(zoneId)); - Statement stmt = con.createStatement(); - try { - stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_without_time_zone_column","TIMESTAMP '" + timestamp + "'")); + try (Statement stmt = con.createStatement()) { + stmt.executeUpdate("DELETE FROM testgetobj310"); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj310", "timestamp_without_time_zone_column", "TIMESTAMP '" + timestamp + "'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_without_time_zone_column")); - try { + try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj310", "timestamp_without_time_zone_column"))) { assertTrue(rs.next()); LocalDateTime localDateTime = LocalDateTime.parse(timestamp); assertEquals(localDateTime, rs.getObject("timestamp_without_time_zone_column", LocalDateTime.class)); @@ -233,12 +350,11 @@ public void localTimestamps(ZoneId zoneId, String timestamp) throws SQLException //Also test that we get the correct values when retrieving the data as LocalDate objects assertEquals(localDateTime.toLocalDate(), rs.getObject("timestamp_without_time_zone_column", LocalDate.class)); assertEquals(localDateTime.toLocalDate(), rs.getObject(1, LocalDate.class)); - } finally { - rs.close(); + + assertDataTypeMismatch(rs, "timestamp_without_time_zone_column", OffsetTime.class); + // TODO: this should also not work, but that's an open discussion (see https://github.com/pgjdbc/pgjdbc/pull/2467): + // assertDataTypeMismatch(rs, "timestamp_without_time_zone_column", OffsetDateTime.class); } - stmt.executeUpdate("DELETE FROM table1"); - } finally { - stmt.close(); } } @@ -254,60 +370,55 @@ public void testGetTimestampWithTimeZone() throws SQLException { } private void runGetOffsetDateTime(ZoneOffset offset) throws SQLException { - Statement stmt = con.createStatement(); - try { - stmt.executeUpdate(TestUtil.insertSQL("table1","timestamp_with_time_zone_column","TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54.123456" + offset.toString() + "'")); + try (Statement stmt = con.createStatement()) { + stmt.executeUpdate("DELETE FROM testgetobj310"); + stmt.executeUpdate(TestUtil.insertSQL("testgetobj310", "timestamp_with_time_zone_column", "TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54.123456" + offset.toString() + "'")); - ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("table1", "timestamp_with_time_zone_column")); - try { + try (ResultSet rs = stmt.executeQuery(TestUtil.selectSQL("testgetobj310", "timestamp_with_time_zone_column"))) { assertTrue(rs.next()); LocalDateTime localDateTime = LocalDateTime.of(2004, 10, 19, 10, 23, 54, 123456000); OffsetDateTime offsetDateTime = localDateTime.atOffset(offset).withOffsetSameInstant(ZoneOffset.UTC); assertEquals(offsetDateTime, rs.getObject("timestamp_with_time_zone_column", OffsetDateTime.class)); assertEquals(offsetDateTime, rs.getObject(1, OffsetDateTime.class)); - } finally { - rs.close(); + + assertDataTypeMismatch(rs, "timestamp_with_time_zone_column", LocalTime.class); + assertDataTypeMismatch(rs, "timestamp_with_time_zone_column", LocalDateTime.class); } - stmt.executeUpdate("DELETE FROM table1"); - } finally { - stmt.close(); } } @Test - public void testBcTimestamp() throws SQLException { + public void testBcDate() throws SQLException { + try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 BC'::date")) { + assertTrue(rs.next()); + LocalDate expected = ISO.date(IsoEra.BCE, 1582, 9, 30); + LocalDate actual = rs.getObject(1, LocalDate.class); + assertEquals(expected, actual); + assertFalse(rs.next()); + } + } - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56 BC'::timestamp"); - try { + @Test + public void testBcTimestamp() throws SQLException { + try (Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56 BC'::timestamp")) { assertTrue(rs.next()); - LocalDateTime expected = LocalDateTime.of(1582, 9, 30, 12, 34, 56) - .with(ChronoField.ERA, IsoEra.BCE.getValue()); + LocalDateTime expected = ISO.date(IsoEra.BCE, 1582, 9, 30).atTime(12, 34, 56); LocalDateTime actual = rs.getObject(1, LocalDateTime.class); assertEquals(expected, actual); assertFalse(rs.next()); - } finally { - rs.close(); - stmt.close(); } } @Test public void testBcTimestamptz() throws SQLException { - - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56Z BC'::timestamp"); - try { + try (Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT '1582-09-30 12:34:56Z BC'::timestamp")) { assertTrue(rs.next()); - OffsetDateTime expected = OffsetDateTime.of(1582, 9, 30, 12, 34, 56, 0, UTC) - .with(ChronoField.ERA, IsoEra.BCE.getValue()); + OffsetDateTime expected = ISO.date(IsoEra.BCE, 1582, 9, 30).atTime(OffsetTime.of(12, 34, 56, 0, UTC)); OffsetDateTime actual = rs.getObject(1, OffsetDateTime.class); assertEquals(expected, actual); assertFalse(rs.next()); - } finally { - rs.close(); - stmt.close(); } } @@ -318,7 +429,7 @@ public void testProlepticCalendarTimestamp() throws SQLException { LocalDateTime start = LocalDate.of(1582, 9, 30).atStartOfDay(); LocalDateTime end = LocalDate.of(1582, 10, 16).atStartOfDay(); long numberOfDays = Duration.between(start, end).toDays() + 1L; - List range = Stream.iterate(start, new LocalDateTimePlusOneDay()) + List range = Stream.iterate(start, x -> x.plusDays(1)) .limit(numberOfDays) .collect(Collectors.toList()); @@ -332,7 +443,7 @@ public void testProlepticCalendarTimestamptz() throws SQLException { OffsetDateTime start = LocalDate.of(1582, 9, 30).atStartOfDay().atOffset(UTC); OffsetDateTime end = LocalDate.of(1582, 10, 16).atStartOfDay().atOffset(UTC); long numberOfDays = Duration.between(start, end).toDays() + 1L; - List range = Stream.iterate(start, new OffsetDateTimePlusOneDay()) + List range = Stream.iterate(start, x -> x.plusDays(1)) .limit(numberOfDays) .collect(Collectors.toList()); @@ -342,34 +453,23 @@ public void testProlepticCalendarTimestamptz() throws SQLException { private void runProlepticTests(Class clazz, String selectRange, List range) throws SQLException { List temporals = new ArrayList<>(range.size()); - PreparedStatement stmt = con.prepareStatement("SELECT * FROM generate_series(" + selectRange + ", '1 day');"); - ResultSet rs = stmt.executeQuery(); - try { + try (PreparedStatement stmt = con.prepareStatement("SELECT * FROM generate_series(" + selectRange + ", '1 day');"); + ResultSet rs = stmt.executeQuery()) { while (rs.next()) { T temporal = rs.getObject(1, clazz); temporals.add(temporal); } assertEquals(range, temporals); - } finally { - rs.close(); - stmt.close(); } } - private static class LocalDateTimePlusOneDay implements UnaryOperator { + /** checks if getObject with given column name or index 1 throws an exception with DATA_TYPE_MISMATCH as SQLState */ + private static void assertDataTypeMismatch(ResultSet rs, String columnName, Class typeToGet) { + PSQLException ex = assertThrows(PSQLException.class, () -> rs.getObject(columnName, typeToGet)); + assertEquals(PSQLState.DATA_TYPE_MISMATCH.getState(), ex.getSQLState()); - @Override - public LocalDateTime apply(LocalDateTime x) { - return x.plusDays(1); - } - } - - private static class OffsetDateTimePlusOneDay implements UnaryOperator { - - @Override - public OffsetDateTime apply(OffsetDateTime x) { - return x.plusDays(1); - } + ex = assertThrows(PSQLException.class, () -> rs.getObject(1, typeToGet)); + assertEquals(PSQLState.DATA_TYPE_MISMATCH.getState(), ex.getSQLState()); } } diff --git a/src/test/java/org/postgresql/test/jdbc42/Jdbc42CallableStatementTest.java b/src/test/java/org/postgresql/test/jdbc42/Jdbc42CallableStatementTest.java index cc48193..4041525 100644 --- a/src/test/java/org/postgresql/test/jdbc42/Jdbc42CallableStatementTest.java +++ b/src/test/java/org/postgresql/test/jdbc42/Jdbc42CallableStatementTest.java @@ -5,12 +5,12 @@ package org.postgresql.test.jdbc42; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.CallableStatement; import java.sql.ResultSet; @@ -31,7 +31,7 @@ public class Jdbc42CallableStatementTest extends BaseTest4 { public void setUp() throws Exception { super.setUp(); - try (Statement stmt = con.createStatement();) { + try (Statement stmt = con.createStatement()) { stmt.execute( "CREATE OR REPLACE FUNCTION testspg__getResultSetWithoutArg() " + "RETURNS refcursor AS ' " diff --git a/src/test/java/org/postgresql/test/jdbc42/LargeCountJdbc42Test.java b/src/test/java/org/postgresql/test/jdbc42/LargeCountJdbc42Test.java index fef3362..1b26f78 100644 --- a/src/test/java/org/postgresql/test/jdbc42/LargeCountJdbc42Test.java +++ b/src/test/java/org/postgresql/test/jdbc42/LargeCountJdbc42Test.java @@ -5,16 +5,21 @@ package org.postgresql.test.jdbc42; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.util.PSQLState; -import org.junit.Assert; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -27,7 +32,8 @@ * Test methods with small counts that return long and failure scenarios. This have two really big * and slow test, they are ignored for CI but can be tested locally to check that it works. */ -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") public class LargeCountJdbc42Test extends BaseTest4 { private final boolean insertRewrite; @@ -37,7 +43,6 @@ public LargeCountJdbc42Test(BinaryMode binaryMode, boolean insertRewrite) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}, insertRewrite = {1}") public static Iterable data() { Collection ids = new ArrayList<>(); for (BinaryMode binaryMode : BinaryMode.values()) { @@ -92,7 +97,7 @@ public void tearDown() throws SQLException { /* * Test PreparedStatement.executeLargeUpdate() and Statement.executeLargeUpdate(String sql) */ - @Ignore("This is the big and SLOW test") + @Disabled("This is the big and SLOW test") @Test public void testExecuteLargeUpdateBIG() throws Exception { long expected = Integer.MAX_VALUE + 110L; @@ -103,12 +108,12 @@ public void testExecuteLargeUpdateBIG() throws Exception { stmt.setLong(1, 1); stmt.setLong(2, 2_147_483_757L); // Integer.MAX_VALUE + 110L long count = stmt.executeLargeUpdate(); - Assert.assertEquals("PreparedStatement 110 rows more than Integer.MAX_VALUE", expected, count); + assertEquals(expected, count, "PreparedStatement 110 rows more than Integer.MAX_VALUE"); } // Test Statement.executeLargeUpdate(String sql) try (Statement stmt = con.createStatement()) { long count = stmt.executeLargeUpdate("delete from largetable"); - Assert.assertEquals("Statement 110 rows more than Integer.MAX_VALUE", expected, count); + assertEquals(expected, count, "Statement 110 rows more than Integer.MAX_VALUE"); } con.setAutoCommit(true); } @@ -122,7 +127,7 @@ public void testExecuteLargeUpdateStatementSMALL() throws Exception { long count = stmt.executeLargeUpdate("insert into largetable " + "select true from generate_series(1, 1010)"); long expected = 1010L; - Assert.assertEquals("Small long return 1010L", expected, count); + assertEquals(expected, count, "Small long return 1010L"); } } @@ -137,7 +142,7 @@ public void testExecuteLargeUpdatePreparedStatementSMALL() throws Exception { stmt.setLong(2, 1010L); long count = stmt.executeLargeUpdate(); long expected = 1010L; - Assert.assertEquals("Small long return 1010L", expected, count); + assertEquals(expected, count, "Small long return 1010L"); } } @@ -149,10 +154,10 @@ public void testGetLargeUpdateCountStatementSMALL() throws Exception { try (Statement stmt = con.createStatement()) { boolean isResult = stmt.execute("insert into largetable " + "select true from generate_series(1, 1010)"); - Assert.assertFalse("False if it is an update count or there are no results", isResult); + assertFalse(isResult, "False if it is an update count or there are no results"); long count = stmt.getLargeUpdateCount(); long expected = 1010L; - Assert.assertEquals("Small long return 1010L", expected, count); + assertEquals(expected, count, "Small long return 1010L"); } } @@ -166,10 +171,10 @@ public void testGetLargeUpdateCountPreparedStatementSMALL() throws Exception { stmt.setInt(1, 1); stmt.setInt(2, 1010); boolean isResult = stmt.execute(); - Assert.assertFalse("False if it is an update count or there are no results", isResult); + assertFalse(isResult, "False if it is an update count or there are no results"); long count = stmt.getLargeUpdateCount(); long expected = 1010L; - Assert.assertEquals("Small long return 1010L", expected, count); + assertEquals(expected, count, "Small long return 1010L"); } } @@ -180,9 +185,9 @@ public void testGetLargeUpdateCountPreparedStatementSMALL() throws Exception { public void testExecuteLargeUpdateStatementSELECT() throws Exception { try (Statement stmt = con.createStatement()) { long count = stmt.executeLargeUpdate("select true from generate_series(1, 5)"); - Assert.fail("A result was returned when none was expected. Returned: " + count); + fail("A result was returned when none was expected. Returned: " + count); } catch (SQLException e) { - Assert.assertEquals(PSQLState.TOO_MANY_RESULTS.getState(), e.getSQLState()); + assertEquals(PSQLState.TOO_MANY_RESULTS.getState(), e.getSQLState()); } } @@ -195,9 +200,9 @@ public void testExecuteLargeUpdatePreparedStatementSELECT() throws Exception { stmt.setLong(1, 1); stmt.setLong(2, 5L); long count = stmt.executeLargeUpdate(); - Assert.fail("A result was returned when none was expected. Returned: " + count); + fail("A result was returned when none was expected. Returned: " + count); } catch (SQLException e) { - Assert.assertEquals(PSQLState.TOO_MANY_RESULTS.getState(), e.getSQLState()); + assertEquals(PSQLState.TOO_MANY_RESULTS.getState(), e.getSQLState()); } } @@ -208,10 +213,10 @@ public void testExecuteLargeUpdatePreparedStatementSELECT() throws Exception { public void testGetLargeUpdateCountStatementSELECT() throws Exception { try (Statement stmt = con.createStatement()) { boolean isResult = stmt.execute("select true from generate_series(1, 5)"); - Assert.assertTrue("True since this is a SELECT", isResult); + assertTrue(isResult, "True since this is a SELECT"); long count = stmt.getLargeUpdateCount(); long expected = -1L; - Assert.assertEquals("-1 if the current result is a ResultSet object", expected, count); + assertEquals(expected, count, "-1 if the current result is a ResultSet object"); } } @@ -224,10 +229,10 @@ public void testGetLargeUpdateCountPreparedStatementSELECT() throws Exception { stmt.setLong(1, 1); stmt.setLong(2, 5L); boolean isResult = stmt.execute(); - Assert.assertTrue("True since this is a SELECT", isResult); + assertTrue(isResult, "True since this is a SELECT"); long count = stmt.getLargeUpdateCount(); long expected = -1L; - Assert.assertEquals("-1 if the current result is a ResultSet object", expected, count); + assertEquals(expected, count, "-1 if the current result is a ResultSet object"); } } @@ -262,7 +267,7 @@ public void testGetLargeUpdateCountPreparedStatementSELECT() throws Exception { /* * Test simple PreparedStatement.executeLargeBatch(); */ - @Ignore("This is the big and SLOW test") + @Disabled("This is the big and SLOW test") @Test public void testExecuteLargeBatchStatementBIG() throws Exception { con.setAutoCommit(false); @@ -278,7 +283,10 @@ public void testExecuteLargeBatchStatementBIG() throws Exception { stmt.setInt(2, 50); stmt.addBatch(); // statement three long[] actual = stmt.executeLargeBatch(); - Assert.assertArrayEquals("Large rows inserted via 3 batch", new long[]{200L, 3_000_000_000L, 50L}, actual); + assertArrayEquals( + new long[]{200L, 3_000_000_000L, 50L}, + actual, + "Large rows inserted via 3 batch"); } con.setAutoCommit(true); } @@ -294,7 +302,7 @@ public void testExecuteLargeBatchStatementSMALL() throws Exception { stmt.addBatch("insert into largetable(a) values(true)"); // statement three stmt.addBatch("insert into largetable values(false)"); // statement four long[] actual = stmt.executeLargeBatch(); - Assert.assertArrayEquals("Rows inserted via 4 batch", new long[]{1L, 1L, 1L, 1L}, actual); + assertArrayEquals(new long[]{1L, 1L, 1L, 1L}, actual, "Rows inserted via 4 batch"); } } @@ -316,7 +324,7 @@ public void testExecuteLargePreparedStatementStatementSMALL() throws Exception { stmt.addBatch(); // statement three stmt.addBatch(); // statement four, same parms as three long[] actual = stmt.executeLargeBatch(); - Assert.assertArrayEquals("Rows inserted via 4 batch", new long[]{200L, 100L, 50L, 50L}, actual); + assertArrayEquals(new long[]{200L, 100L, 50L, 50L}, actual, "Rows inserted via 4 batch"); } } @@ -334,7 +342,7 @@ public void testExecuteLargePreparedStatementStatementLoopSMALL() throws Excepti stmt.addBatch(); } long[] actual = stmt.executeLargeBatch(); - Assert.assertArrayEquals("Rows inserted via batch", loop, actual); + assertArrayEquals(loop, actual, "Rows inserted via batch"); } } @@ -350,12 +358,12 @@ public void testExecuteLargeBatchValuesInsertSMALL() throws Exception { stmt.addBatch(); } long[] actual = stmt.executeLargeBatch(); - Assert.assertEquals("Rows inserted via batch", loop.length, actual.length); + assertEquals(loop.length, actual.length, "Rows inserted via batch"); for (long i : actual) { if (insertRewrite) { - Assert.assertEquals(Statement.SUCCESS_NO_INFO, i); + assertEquals(Statement.SUCCESS_NO_INFO, i); } else { - Assert.assertEquals(1, i); + assertEquals(1, i); } } } @@ -368,7 +376,7 @@ public void testExecuteLargeBatchValuesInsertSMALL() throws Exception { public void testNullExecuteLargeBatchStatement() throws Exception { try (Statement stmt = con.createStatement()) { long[] actual = stmt.executeLargeBatch(); - Assert.assertArrayEquals("addBatch() not called batchStatements is null", new long[0], actual); + assertArrayEquals(new long[0], actual, "addBatch() not called batchStatements is null"); } } @@ -381,7 +389,7 @@ public void testEmptyExecuteLargeBatchStatement() throws Exception { stmt.addBatch(""); stmt.clearBatch(); long[] actual = stmt.executeLargeBatch(); - Assert.assertArrayEquals("clearBatch() called, batchStatements.isEmpty()", new long[0], actual); + assertArrayEquals(new long[0], actual, "clearBatch() called, batchStatements.isEmpty()"); } } @@ -392,7 +400,7 @@ public void testEmptyExecuteLargeBatchStatement() throws Exception { public void testNullExecuteLargeBatchPreparedStatement() throws Exception { try (PreparedStatement stmt = con.prepareStatement("")) { long[] actual = stmt.executeLargeBatch(); - Assert.assertArrayEquals("addBatch() not called batchStatements is null", new long[0], actual); + assertArrayEquals(new long[0], actual, "addBatch() not called batchStatements is null"); } } @@ -405,7 +413,7 @@ public void testEmptyExecuteLargeBatchPreparedStatement() throws Exception { stmt.addBatch(); stmt.clearBatch(); long[] actual = stmt.executeLargeBatch(); - Assert.assertArrayEquals("clearBatch() called, batchStatements.isEmpty()", new long[0], actual); + assertArrayEquals(new long[0], actual, "clearBatch() called, batchStatements.isEmpty()"); } } diff --git a/src/test/java/org/postgresql/test/jdbc42/PreparedStatement64KBindsTest.java b/src/test/java/org/postgresql/test/jdbc42/PreparedStatement64KBindsTest.java new file mode 100644 index 0000000..724ae1f --- /dev/null +++ b/src/test/java/org/postgresql/test/jdbc42/PreparedStatement64KBindsTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2022, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc42; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.postgresql.PGProperty; +import org.postgresql.jdbc.PreferQueryMode; +import org.postgresql.test.jdbc2.BaseTest4; +import org.postgresql.util.PSQLState; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.Array; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@ParameterizedClass +@MethodSource("data") +public class PreparedStatement64KBindsTest extends BaseTest4 { + private final int numBinds; + private final PreferQueryMode preferQueryMode; + private final BinaryMode binaryMode; + + public PreparedStatement64KBindsTest(int numBinds, PreferQueryMode preferQueryMode, + BinaryMode binaryMode) { + this.numBinds = numBinds; + this.preferQueryMode = preferQueryMode; + this.binaryMode = binaryMode; + } + + public static Iterable data() { + Collection ids = new ArrayList<>(); + for (PreferQueryMode preferQueryMode : PreferQueryMode.values()) { + for (BinaryMode binaryMode : BinaryMode.values()) { + for (int numBinds : new int[]{32766, 32767, 32768, 65534, 65535, 65536}) { + ids.add(new Object[]{numBinds, preferQueryMode, binaryMode}); + } + } + } + return ids; + } + + @Override + protected void updateProperties(Properties props) { + super.updateProperties(props); + PGProperty.PREFER_QUERY_MODE.set(props, preferQueryMode.value()); + setBinaryMode(binaryMode); + } + + @Test + public void executeWith65535BindsWorks() throws SQLException { + String sql = Collections.nCopies(numBinds, "?").stream() + .collect(Collectors.joining(",", "select ARRAY[", "]")); + + try (PreparedStatement ps = con.prepareStatement(sql)) { + for (int i = 1; i <= numBinds; i++) { + ps.setString(i, "v" + i); + } + String expected = Arrays.toString( + IntStream.rangeClosed(1, numBinds) + .mapToObj(i -> "v" + i).toArray() + ); + + try (ResultSet rs = ps.executeQuery()) { + rs.next(); + Array res = rs.getArray(1); + Object[] elements = (Object[]) res.getArray(); + String actual = Arrays.toString(elements); + + if (preferQueryMode == PreferQueryMode.SIMPLE || numBinds <= 65535) { + assertEquals(actual, expected, () -> "SELECT query with " + numBinds + " should work"); + } else { + fail("con.prepareStatement(..." + numBinds + " binds) should fail since the wire protocol allows only 65535 parameters"); + } + } + } catch (SQLException e) { + if (preferQueryMode != PreferQueryMode.SIMPLE && numBinds > 65535) { + assertEquals( + PSQLState.INVALID_PARAMETER_VALUE.getState(), + e.getSQLState(), + () -> "con.prepareStatement(..." + numBinds + " binds) should fail since the wire protocol allows only 65535 parameters. SQL State is " + ); + } else { + throw e; + } + } + } +} diff --git a/src/test/java/org/postgresql/test/jdbc42/PreparedStatementTest.java b/src/test/java/org/postgresql/test/jdbc42/PreparedStatementTest.java index 00ada27..07eb779 100644 --- a/src/test/java/org/postgresql/test/jdbc42/PreparedStatementTest.java +++ b/src/test/java/org/postgresql/test/jdbc42/PreparedStatementTest.java @@ -5,14 +5,19 @@ package org.postgresql.test.jdbc42; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -25,20 +30,28 @@ protected void updateProperties(Properties props) { PGProperty.PREFER_QUERY_MODE.set(props, "simple"); } - @Override - public void setUp() throws Exception { - super.setUp(); - TestUtil.createTable(con, "timestamptztable", "tstz timestamptz"); - TestUtil.createTable(con, "timetztable", "ttz timetz"); - TestUtil.createTable(con, "timetable", "id serial, tt time"); + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "timestamptztable", "tstz timestamptz"); + TestUtil.createTable(conn, "timetztable", "ttz timetz"); + TestUtil.createTable(conn, "timetable", "id serial, tt time"); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "timestamptztable"); + TestUtil.dropTable(conn, "timetztable"); + TestUtil.dropTable(conn, "timetable"); + } } @Override - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "timestamptztable"); - TestUtil.dropTable(con, "timetztable"); - TestUtil.dropTable(con, "timetable"); - super.tearDown(); + public void setUp() throws Exception { + super.setUp(); + TestUtil.execute(con, "TRUNCATE timestamptztable, timetztable, timetable"); } @Test @@ -51,7 +64,19 @@ public void testSetNumber() throws SQLException { BigDecimal d = rs.getBigDecimal(1); pstmt.close(); - Assert.assertEquals(new BigDecimal("3.2"), d); + assertEquals(new BigDecimal("3.2"), d); + } + + @Test + public void testSetBoolean() throws SQLException { + try (PreparedStatement ps = con.prepareStatement("select false union select (select ?)")) { + ps.setBoolean(1, true); + + try (ResultSet rs = ps.executeQuery()) { + assert (rs.next()); + rs.getBoolean(1); + } + } } @Test @@ -95,12 +120,12 @@ public void testLocalTimeMax() throws SQLException { pstmt.executeUpdate(); ResultSet rs = con.createStatement().executeQuery("select tt from timetable order by id asc"); - Assert.assertTrue(rs.next()); - LocalTime localTime = (LocalTime)rs.getObject(1,LocalTime.class); - Assert.assertEquals( LocalTime.MAX, localTime); + assertTrue(rs.next()); + LocalTime localTime = (LocalTime) rs.getObject(1, LocalTime.class); + assertEquals(LocalTime.MAX, localTime); - Assert.assertTrue(rs.next()); - localTime = (LocalTime)rs.getObject(1, LocalTime.class); - Assert.assertEquals( LocalTime.MIN, localTime); + assertTrue(rs.next()); + localTime = (LocalTime) rs.getObject(1, LocalTime.class); + assertEquals(LocalTime.MIN, localTime); } } diff --git a/src/test/java/org/postgresql/test/jdbc42/SetObject310InfinityTests.java b/src/test/java/org/postgresql/test/jdbc42/SetObject310InfinityTest.java similarity index 62% rename from src/test/java/org/postgresql/test/jdbc42/SetObject310InfinityTests.java rename to src/test/java/org/postgresql/test/jdbc42/SetObject310InfinityTest.java index 86457aa..250793f 100644 --- a/src/test/java/org/postgresql/test/jdbc42/SetObject310InfinityTests.java +++ b/src/test/java/org/postgresql/test/jdbc42/SetObject310InfinityTest.java @@ -5,20 +5,22 @@ package org.postgresql.test.jdbc42; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.After; -import org.junit.Assume; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -29,14 +31,14 @@ import java.util.ArrayList; import java.util.Collection; -@RunWith(Parameterized.class) -public class SetObject310InfinityTests extends BaseTest4 { +@ParameterizedClass +@MethodSource("data") +public class SetObject310InfinityTest extends BaseTest4 { - public SetObject310InfinityTests(BinaryMode binaryMode) { + public SetObject310InfinityTest(BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { Collection ids = new ArrayList<>(2); for (BaseTest4.BinaryMode binaryMode : BaseTest4.BinaryMode.values()) { @@ -45,22 +47,28 @@ public static Iterable data() { return ids; } + @BeforeAll + static void createTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.createTable(conn, "testsetobj310inf", "timestamp_without_time_zone_column timestamp without time zone," + + "timestamp_with_time_zone_column timestamp with time zone," + + "date_column date" + ); + } + } + + @AfterAll + static void dropTables() throws Exception { + try (Connection conn = TestUtil.openDB()) { + TestUtil.dropTable(conn, "testsetobj310inf"); + } + } + @Override public void setUp() throws Exception { super.setUp(); - Assume.assumeTrue("PostgreSQL 8.3 does not support 'infinity' for 'date'", - TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4)); - super.setUp(); - TestUtil.createTable(con, "table1", "timestamp_without_time_zone_column timestamp without time zone," - + "timestamp_with_time_zone_column timestamp with time zone," - + "date_column date" - ); - } - - @After - public void tearDown() throws SQLException { - TestUtil.dropTable(con, "table1"); - super.tearDown(); + assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_4), "PostgreSQL 8.3 does not support 'infinity' for 'date'"); + TestUtil.execute(con, "TRUNCATE testsetobj310inf"); } @Test @@ -91,7 +99,7 @@ private void runTestforType(Object max, Object min, String columnName, Integer t } private void insert(Object data, String columnName, Integer type) throws SQLException { - PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?")); + PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("testsetobj310inf", columnName, "?")); try { if (type != null) { ps.setObject(1, data, type); @@ -107,7 +115,7 @@ private void insert(Object data, String columnName, Integer type) throws SQLExce private String readString(String columnName) throws SQLException { Statement st = con.createStatement(); try { - ResultSet rs = st.executeQuery(TestUtil.selectSQL("table1", columnName)); + ResultSet rs = st.executeQuery(TestUtil.selectSQL("testsetobj310inf", columnName)); try { assertNotNull(rs); assertTrue(rs.next()); @@ -123,7 +131,7 @@ private String readString(String columnName) throws SQLException { private void delete() throws SQLException { Statement st = con.createStatement(); try { - st.execute("DELETE FROM table1"); + st.execute("DELETE FROM testsetobj310inf"); } finally { st.close(); } diff --git a/src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java b/src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java index 0b87e4e..2a1b18a 100644 --- a/src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java +++ b/src/test/java/org/postgresql/test/jdbc42/SetObject310Test.java @@ -5,20 +5,22 @@ package org.postgresql.test.jdbc42; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Isolated; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -29,6 +31,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; +import java.time.OffsetTime; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.chrono.IsoChronology; @@ -45,7 +48,9 @@ import java.util.Locale; import java.util.TimeZone; -@RunWith(Parameterized.class) +@ParameterizedClass +@MethodSource("data") +@Isolated("Uses TimeZone.setDefault") public class SetObject310Test extends BaseTest4 { private static final TimeZone saveTZ = TimeZone.getDefault(); @@ -73,35 +78,42 @@ public SetObject310Test(BaseTest4.BinaryMode binaryMode) { setBinaryMode(binaryMode); } - @Parameterized.Parameters(name = "binary = {0}") public static Iterable data() { - Collection ids = new ArrayList(); + Collection ids = new ArrayList<>(); for (BaseTest4.BinaryMode binaryMode : BaseTest4.BinaryMode.values()) { ids.add(new Object[]{binaryMode}); } return ids; } - @Before - public void setUp() throws Exception { - super.setUp(); - TestUtil.createTable(con, "table1", "timestamp_without_time_zone_column timestamp without time zone," - + "timestamp_with_time_zone_column timestamp with time zone," - + "date_column date," - + "time_without_time_zone_column time without time zone," - + "time_with_time_zone_column time with time zone" - ); + @BeforeAll + public static void createTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.createTable(con, "testsetobj310", "timestamp_without_time_zone_column timestamp without time zone," + + "timestamp_with_time_zone_column timestamp with time zone," + + "date_column date," + + "time_without_time_zone_column time without time zone," + + "time_with_time_zone_column time with time zone" + ); + } } - @After - public void tearDown() throws SQLException { + @AfterAll + public static void dropTables() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "testsetobj310"); + } TimeZone.setDefault(saveTZ); - TestUtil.dropTable(con, "table1"); - super.tearDown(); + } + + @Override + public void setUp() throws Exception { + super.setUp(); + TestUtil.execute(con, "delete from testsetobj310"); } private void insert(Object data, String columnName, Integer type) throws SQLException { - PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?")); + PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("testsetobj310", columnName, "?")); try { if (type != null) { ps.setObject(1, data, type); @@ -117,7 +129,7 @@ private void insert(Object data, String columnName, Integer type) throws SQLExce private String readString(String columnName) throws SQLException { Statement st = con.createStatement(); try { - ResultSet rs = st.executeQuery(TestUtil.selectSQL("table1", columnName)); + ResultSet rs = st.executeQuery(TestUtil.selectSQL("testsetobj310", columnName)); try { assertNotNull(rs); assertTrue(rs.next()); @@ -145,7 +157,11 @@ private void insertWithoutType(Object data, String columnName) throws SQLExcepti } private T insertThenReadWithoutType(Object data, String columnName, Class expectedType) throws SQLException { - PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?")); + return insertThenReadWithoutType(data, columnName, expectedType, true); + } + + private T insertThenReadWithoutType(Object data, String columnName, Class expectedType, boolean checkRoundtrip) throws SQLException { + PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("testsetobj310", columnName, "?")); try { ps.setObject(1, data); assertEquals(1, ps.executeUpdate()); @@ -155,11 +171,14 @@ private T insertThenReadWithoutType(Object data, String columnName, Class Statement st = con.createStatement(); try { - ResultSet rs = st.executeQuery(TestUtil.selectSQL("table1", columnName)); + ResultSet rs = st.executeQuery(TestUtil.selectSQL("testsetobj310", columnName)); try { assertNotNull(rs); assertTrue(rs.next()); + if (checkRoundtrip) { + assertEquals(data, rs.getObject(1, data.getClass()), "Roundtrip set/getObject with type should return same result"); + } return expectedType.cast(rs.getObject(1)); } finally { rs.close(); @@ -170,7 +189,11 @@ private T insertThenReadWithoutType(Object data, String columnName, Class } private T insertThenReadWithType(Object data, int sqlType, String columnName, Class expectedType) throws SQLException { - PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("table1", columnName, "?")); + return insertThenReadWithType(data, sqlType, columnName, expectedType, true); + } + + private T insertThenReadWithType(Object data, int sqlType, String columnName, Class expectedType, boolean checkRoundtrip) throws SQLException { + PreparedStatement ps = con.prepareStatement(TestUtil.insertSQL("testsetobj310", columnName, "?")); try { ps.setObject(1, data, sqlType); assertEquals(1, ps.executeUpdate()); @@ -180,11 +203,17 @@ private T insertThenReadWithType(Object data, int sqlType, String columnName Statement st = con.createStatement(); try { - ResultSet rs = st.executeQuery(TestUtil.selectSQL("table1", columnName)); + ResultSet rs = st.executeQuery(TestUtil.selectSQL("testsetobj310", columnName)); try { assertNotNull(rs); assertTrue(rs.next()); + if (checkRoundtrip) { + assertEquals( + data, + rs.getObject(1, data.getClass()), + "Roundtrip set/getObject with type should return same result"); + } return expectedType.cast(rs.getObject(1)); } finally { rs.close(); @@ -197,7 +226,7 @@ private T insertThenReadWithType(Object data, int sqlType, String columnName private void deleteRows() throws SQLException { Statement st = con.createStatement(); try { - st.executeUpdate("DELETE FROM table1"); + st.executeUpdate("DELETE FROM testsetobj310"); } finally { st.close(); } @@ -215,9 +244,7 @@ public void testSetLocalDateTime() throws SQLException { ZoneId zone = ZoneId.of(zoneId); for (String date : datesToTest) { LocalDateTime localDateTime = LocalDateTime.parse(date); - String expected = localDateTime.atZone(zone) - .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) - .replace('T', ' '); + String expected = date.replace('T', ' '); localTimestamps(zone, localDateTime, expected); } } @@ -229,7 +256,7 @@ public void testSetLocalDateTime() throws SQLException { @Test public void testSetOffsetDateTime() throws SQLException { List zoneIdsToTest = getZoneIdsToTest(); - List storeZones = new ArrayList(); + List storeZones = new ArrayList<>(); for (String zoneId : zoneIdsToTest) { storeZones.add(TimeZone.getTimeZone(zoneId)); } @@ -245,7 +272,7 @@ public void testSetOffsetDateTime() throws SQLException { } } - private List getDatesToTest() { + private static List getDatesToTest() { return Arrays.asList("2015-09-03T12:00:00", "2015-06-30T23:59:58", "1997-06-30T23:59:59", "1997-07-01T00:00:00", "2012-06-30T23:59:59", "2012-07-01T00:00:00", "2015-06-30T23:59:59", "2015-07-01T00:00:00", "2005-12-31T23:59:59", "2006-01-01T00:00:00", @@ -269,8 +296,8 @@ private List getDatesToTest() { "2000-10-29T04:00:01", "2000-10-29T04:00:00.000001"); } - private List getZoneIdsToTest() { - List zoneIdsToTest = new ArrayList(); + private static List getZoneIdsToTest() { + List zoneIdsToTest = new ArrayList<>(); zoneIdsToTest.add("Africa/Casablanca"); // It is something like GMT+0..GMT+1 zoneIdsToTest.add("America/Adak"); // It is something like GMT-10..GMT-9 zoneIdsToTest.add("Atlantic/Azores"); // It is something like GMT-1..GMT+0 @@ -287,14 +314,16 @@ private void localTimestamps(ZoneId zoneId, LocalDateTime localDateTime, String TimeZone.setDefault(TimeZone.getTimeZone(zoneId)); String readBack = insertThenReadStringWithoutType(localDateTime, "timestamp_without_time_zone_column"); assertEquals( - "LocalDateTime=" + localDateTime + ", with TimeZone.default=" + zoneId + ", setObject(int, Object)", - expected, readBack); + expected, + readBack, + () -> "LocalDateTime=" + localDateTime + ", with TimeZone.default=" + zoneId + ", setObject(int, Object)"); deleteRows(); readBack = insertThenReadStringWithType(localDateTime, "timestamp_without_time_zone_column"); assertEquals( - "LocalDateTime=" + localDateTime + ", with TimeZone.default=" + zoneId + ", setObject(int, Object, TIMESTAMP)", - expected, readBack); + expected, + readBack, + () -> "LocalDateTime=" + localDateTime + ", with TimeZone.default=" + zoneId + ", setObject(int, Object, TIMESTAMP)"); deleteRows(); } @@ -309,22 +338,38 @@ private void offsetTimestamps(ZoneId dataZone, LocalDateTime localDateTime, Stri try (ResultSet rs = ps.executeQuery()) { rs.next(); String noType = rs.getString(1); - OffsetDateTime noTypeRes = OffsetDateTime.parse(noType.replace(' ', 'T') + ":00"); + OffsetDateTime noTypeRes = parseBackendTimestamp(noType); assertEquals( - "OffsetDateTime=" + data + " (with ZoneId=" + dataZone + "), with TimeZone.default=" - + storeZone + ", setObject(int, Object)", data.toInstant(), - noTypeRes.toInstant()); + data.toInstant(), + noTypeRes.toInstant(), + () -> "OffsetDateTime=" + data + " (with ZoneId=" + dataZone + "), with TimeZone" + + ".default=" + storeZone + ", setObject(int, Object)"); String withType = rs.getString(2); - OffsetDateTime withTypeRes = OffsetDateTime.parse(withType.replace(' ', 'T') + ":00"); + OffsetDateTime withTypeRes = parseBackendTimestamp(withType); assertEquals( - "OffsetDateTime=" + data + " (with ZoneId=" + dataZone + "), with TimeZone.default=" - + storeZone + ", setObject(int, Object, TIMESTAMP_WITH_TIMEZONE)", - data.toInstant(), withTypeRes.toInstant()); + data.toInstant(), + withTypeRes.toInstant(), + () -> "OffsetDateTime=" + data + " (with ZoneId=" + dataZone + "), with TimeZone" + + ".default=" + storeZone + ", setObject(int, Object, TIMESTAMP_WITH_TIMEZONE)"); } } } } + /** + * Sometimes backend responds like {@code 1950-07-20 16:20:00+03} and sometimes it responds like + * {@code 1582-09-30 13:49:57+02:30:17}, so we need to handle cases when "offset minutes" is missing. + */ + private static OffsetDateTime parseBackendTimestamp(String backendTimestamp) { + String isoTimestamp = backendTimestamp.replace(' ', 'T'); + // If the pattern already has trailing :XX we are fine + // Otherwise add :00 for timezone offset minutes + if (isoTimestamp.charAt(isoTimestamp.length() - 3) != ':') { + isoTimestamp += ":00"; + } + return OffsetDateTime.parse(isoTimestamp); + } + @Test public void testLocalDateTimeRounding() throws SQLException { LocalDateTime dateTime = LocalDateTime.parse("2018-12-31T23:59:59.999999500"); @@ -336,7 +381,7 @@ public void testTimeStampRounding() throws SQLException { // TODO: fix for binary assumeBinaryModeRegular(); LocalTime time = LocalTime.parse("23:59:59.999999500"); - Time actual = insertThenReadWithoutType(time, "time_without_time_zone_column", Time.class); + Time actual = insertThenReadWithoutType(time, "time_without_time_zone_column", Time.class, false/*no roundtrip*/); assertEquals(Time.valueOf("24:00:00"), actual); } @@ -346,7 +391,7 @@ public void testTimeStampRoundingWithType() throws SQLException { assumeBinaryModeRegular(); LocalTime time = LocalTime.parse("23:59:59.999999500"); Time actual = - insertThenReadWithType(time, Types.TIME, "time_without_time_zone_column", Time.class); + insertThenReadWithType(time, Types.TIME, "time_without_time_zone_column", Time.class, false/*no roundtrip*/); assertEquals(Time.valueOf("24:00:00"), actual); } @@ -358,12 +403,17 @@ public void testSetLocalDateTimeBc() throws SQLException { assumeTrue(TestUtil.haveIntegerDateTimes(con)); // use BC for funsies - List bcDates = new ArrayList(); + List bcDates = new ArrayList<>(); bcDates.add(LocalDateTime.parse("1997-06-30T23:59:59.999999").with(ChronoField.ERA, IsoEra.BCE.getValue())); bcDates.add(LocalDateTime.parse("0997-06-30T23:59:59.999999").with(ChronoField.ERA, IsoEra.BCE.getValue())); for (LocalDateTime bcDate : bcDates) { String expected = LOCAL_TIME_FORMATTER.format(bcDate); + if (expected.endsWith(" BCE")) { + // Java 22.ea.25-open prints "BCE" even though previous releases printed "BC" + // See https://bugs.openjdk.org/browse/JDK-8320747 + expected = expected.substring(0, expected.length() - 1); + } localTimestamps(ZoneOffset.UTC, bcDate, expected); } } @@ -428,4 +478,22 @@ public void testSetLocalTimeWithoutType() throws SQLException { assertEquals(expected, actual); } + /** + * Test the behavior setObject for time columns. + */ + @Test + public void testSetOffsetTimeWithType() throws SQLException { + OffsetTime data = OffsetTime.parse("16:21:51+12:34"); + insertThenReadWithType(data, Types.TIME, "time_with_time_zone_column", Time.class); + } + + /** + * Test the behavior setObject for time columns. + */ + @Test + public void testSetOffsetTimeWithoutType() throws SQLException { + OffsetTime data = OffsetTime.parse("16:21:51+12:34"); + insertThenReadWithoutType(data, "time_with_time_zone_column", Time.class); + } + } diff --git a/src/test/java/org/postgresql/test/jdbc42/SimpleJdbc42Test.java b/src/test/java/org/postgresql/test/jdbc42/SimpleJdbc42Test.java index d765b6f..61ae8c3 100644 --- a/src/test/java/org/postgresql/test/jdbc42/SimpleJdbc42Test.java +++ b/src/test/java/org/postgresql/test/jdbc42/SimpleJdbc42Test.java @@ -5,11 +5,11 @@ package org.postgresql.test.jdbc42; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.test.jdbc2.BaseTest4; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Most basic test to check that the right package is compiled. diff --git a/src/test/java/org/postgresql/test/jdbc42/TimestampUtilsTest.java b/src/test/java/org/postgresql/test/jdbc42/TimestampUtilsTest.java index 7add302..c814676 100644 --- a/src/test/java/org/postgresql/test/jdbc42/TimestampUtilsTest.java +++ b/src/test/java/org/postgresql/test/jdbc42/TimestampUtilsTest.java @@ -5,64 +5,170 @@ package org.postgresql.test.jdbc42; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.postgresql.core.Provider; import org.postgresql.jdbc.TimestampUtils; +import org.postgresql.util.ByteConverter; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.SQLException; import java.time.LocalTime; +import java.time.OffsetTime; import java.util.TimeZone; -public class TimestampUtilsTest { +class TimestampUtilsTest { + private TimestampUtils timestampUtils; + + @BeforeEach + void setUp() { + timestampUtils = new TimestampUtils(false, TimeZone::getDefault); + } + + @Test + void toStringOfLocalTime() { + assertToStringOfLocalTime("00:00:00"); + assertToStringOfLocalTime("00:00:00.1"); + assertToStringOfLocalTime("00:00:00.12"); + assertToStringOfLocalTime("00:00:00.123"); + assertToStringOfLocalTime("00:00:00.1234"); + assertToStringOfLocalTime("00:00:00.12345"); + assertToStringOfLocalTime("00:00:00.123456"); + + assertToStringOfLocalTime("00:00:00.999999"); + assertToStringOfLocalTime("00:00:00.999999", "00:00:00.999999499", "499 NanoSeconds round down"); + assertToStringOfLocalTime("00:00:01", "00:00:00.999999500", "500 NanoSeconds round up"); + + assertToStringOfLocalTime("23:59:59"); + + assertToStringOfLocalTime("23:59:59.999999"); + assertToStringOfLocalTime("23:59:59.999999", "23:59:59.999999499", "499 NanoSeconds round down"); + assertToStringOfLocalTime("24:00:00", "23:59:59.999999500", "500 NanoSeconds round up"); + assertToStringOfLocalTime("24:00:00", "23:59:59.999999999", "999 NanoSeconds round up"); + } + + private void assertToStringOfLocalTime(String inputTime) { + assertToStringOfLocalTime(inputTime, inputTime, null); + } + + private void assertToStringOfLocalTime(String expectedOutput, String inputTime, String message) { + assertEquals( + expectedOutput, + timestampUtils.toString(LocalTime.parse(inputTime)), + "timestampUtils.toString(LocalTime.parse(" + inputTime + "))" + + (message == null ? ": " + message : "")); + } + + @Test + void toLocalTime() throws SQLException { + assertToLocalTime("00:00:00"); + + assertToLocalTime("00:00:00.1"); + assertToLocalTime("00:00:00.12"); + assertToLocalTime("00:00:00.123"); + assertToLocalTime("00:00:00.1234"); + assertToLocalTime("00:00:00.12345"); + assertToLocalTime("00:00:00.123456"); + assertToLocalTime("00:00:00.999999"); + + assertToLocalTime("23:59:59"); + assertToLocalTime("23:59:59.999999"); // 0 NanoSeconds + assertToLocalTime("23:59:59.9999999"); // 900 NanoSeconds + assertToLocalTime("23:59:59.99999999"); // 990 NanoSeconds + assertToLocalTime("23:59:59.999999998"); // 998 NanoSeconds + assertToLocalTime(LocalTime.MAX.toString(), "24:00:00", "LocalTime can't represent 24:00:00"); + } + + private void assertToLocalTime(String inputTime) throws SQLException { + assertToLocalTime(inputTime, inputTime, null); + } + + private void assertToLocalTime(String expectedOutput, String inputTime, String message) throws SQLException { + assertEquals( + LocalTime.parse(expectedOutput), + timestampUtils.toLocalTime(inputTime), + "timestampUtils.toLocalTime(" + inputTime + ")" + + (message == null ? ": " + message : "")); + } + + @Test + void toLocalTimeBin() throws SQLException { + assertToLocalTimeBin("00:00:00", 0L); + + assertToLocalTimeBin("00:00:00.1", 100_000L); + assertToLocalTimeBin("00:00:00.12", 120_000L); + assertToLocalTimeBin("00:00:00.123", 123_000L); + assertToLocalTimeBin("00:00:00.1234", 123_400L); + assertToLocalTimeBin("00:00:00.12345", 123_450L); + assertToLocalTimeBin("00:00:00.123456", 123_456L); + assertToLocalTimeBin("00:00:00.999999", 999_999L); + + assertToLocalTimeBin("23:59:59", 86_399_000_000L); + assertToLocalTimeBin("23:59:59.999999", 86_399_999_999L); + assertToLocalTimeBin(LocalTime.MAX.toString(), 86_400_000_000L, "LocalTime can't represent 24:00:00"); + } + + private void assertToLocalTimeBin(String expectedOutput, long inputMicros) throws SQLException { + assertToLocalTimeBin(expectedOutput, inputMicros, null); + } + + private void assertToLocalTimeBin(String expectedOutput, long inputMicros, String message) throws SQLException { + final byte[] bytes = new byte[8]; + ByteConverter.int8(bytes, 0, inputMicros); + assertEquals( + LocalTime.parse(expectedOutput), + timestampUtils.toLocalTimeBin(bytes), + "timestampUtils.toLocalTime(" + inputMicros + ")" + + (message == null ? ": " + message : "")); + } + @Test - public void testToStringOfLocalTime() { - TimestampUtils timestampUtils = createTimestampUtils(); - - assertEquals("00:00:00", timestampUtils.toString(LocalTime.parse("00:00:00"))); - assertEquals("00:00:00.1", timestampUtils.toString(LocalTime.parse("00:00:00.1"))); - assertEquals("00:00:00.12", timestampUtils.toString(LocalTime.parse("00:00:00.12"))); - assertEquals("00:00:00.123", timestampUtils.toString(LocalTime.parse("00:00:00.123"))); - assertEquals("00:00:00.1234", timestampUtils.toString(LocalTime.parse("00:00:00.1234"))); - assertEquals("00:00:00.12345", timestampUtils.toString(LocalTime.parse("00:00:00.12345"))); - assertEquals("00:00:00.123456", timestampUtils.toString(LocalTime.parse("00:00:00.123456"))); - - assertEquals("00:00:00.999999", timestampUtils.toString(LocalTime.parse("00:00:00.999999"))); - assertEquals("00:00:00.999999", timestampUtils.toString(LocalTime.parse("00:00:00.999999499"))); // 499 NanoSeconds - assertEquals("00:00:01", timestampUtils.toString(LocalTime.parse("00:00:00.999999500"))); // 500 NanoSeconds - - assertEquals("23:59:59", timestampUtils.toString(LocalTime.parse("23:59:59"))); - assertEquals("23:59:59.999999", timestampUtils.toString(LocalTime.parse("23:59:59.999999"))); // 0 NanoSeconds - assertEquals("23:59:59.999999", timestampUtils.toString(LocalTime.parse("23:59:59.999999499"))); // 499 NanoSeconds - assertEquals("24:00:00", timestampUtils.toString(LocalTime.parse("23:59:59.999999500")));// 500 NanoSeconds - assertEquals("24:00:00", timestampUtils.toString(LocalTime.parse("23:59:59.999999999")));// 999 NanoSeconds + void toStringOfOffsetTime() { + assertToStringOfOffsetTime("00:00:00+00", "00:00:00+00:00"); + assertToStringOfOffsetTime("00:00:00.1+01", "00:00:00.1+01:00"); + assertToStringOfOffsetTime("00:00:00.12+12", "00:00:00.12+12:00"); + assertToStringOfOffsetTime("00:00:00.123-01", "00:00:00.123-01:00"); + assertToStringOfOffsetTime("00:00:00.1234-02", "00:00:00.1234-02:00"); + assertToStringOfOffsetTime("00:00:00.12345-12", "00:00:00.12345-12:00"); + assertToStringOfOffsetTime("00:00:00.123456+01:30", "00:00:00.123456+01:30"); + assertToStringOfOffsetTime("00:00:00.123456-12:34", "00:00:00.123456-12:34"); + + assertToStringOfOffsetTime("23:59:59+01", "23:59:59+01:00"); + + assertToStringOfOffsetTime("23:59:59.999999+01", "23:59:59.999999+01:00"); + assertToStringOfOffsetTime("23:59:59.999999+01", "23:59:59.999999499+01:00"); // 499 NanoSeconds + assertToStringOfOffsetTime("24:00:00+01", "23:59:59.999999500+01:00"); // 500 NanoSeconds + assertToStringOfOffsetTime("24:00:00+01", "23:59:59.999999999+01:00"); // 999 NanoSeconds + } + + private void assertToStringOfOffsetTime(String expectedOutput, String inputTime) { + assertEquals(expectedOutput, + timestampUtils.toString(OffsetTime.parse(inputTime)), + "timestampUtils.toString(OffsetTime.parse(" + inputTime + "))"); } @Test - public void testToLocalTime() throws SQLException { - TimestampUtils timestampUtils = createTimestampUtils(); - - assertEquals(LocalTime.parse("00:00:00"), timestampUtils.toLocalTime("00:00:00")); - - assertEquals(LocalTime.parse("00:00:00.1"), timestampUtils.toLocalTime("00:00:00.1")); - assertEquals(LocalTime.parse("00:00:00.12"), timestampUtils.toLocalTime("00:00:00.12")); - assertEquals(LocalTime.parse("00:00:00.123"), timestampUtils.toLocalTime("00:00:00.123")); - assertEquals(LocalTime.parse("00:00:00.1234"), timestampUtils.toLocalTime("00:00:00.1234")); - assertEquals(LocalTime.parse("00:00:00.12345"), timestampUtils.toLocalTime("00:00:00.12345")); - assertEquals(LocalTime.parse("00:00:00.123456"), timestampUtils.toLocalTime("00:00:00.123456")); - assertEquals(LocalTime.parse("00:00:00.999999"), timestampUtils.toLocalTime("00:00:00.999999")); - - assertEquals(LocalTime.parse("23:59:59"), timestampUtils.toLocalTime("23:59:59")); - assertEquals(LocalTime.parse("23:59:59.999999"), timestampUtils.toLocalTime("23:59:59.999999")); // 0 NanoSeconds - assertEquals(LocalTime.parse("23:59:59.9999999"), timestampUtils.toLocalTime("23:59:59.9999999")); // 900 NanoSeconds - assertEquals(LocalTime.parse("23:59:59.99999999"), timestampUtils.toLocalTime("23:59:59.99999999")); // 990 NanoSeconds - assertEquals(LocalTime.parse("23:59:59.999999998"), timestampUtils.toLocalTime("23:59:59.999999998")); // 998 NanoSeconds - assertEquals(LocalTime.parse("23:59:59.999999999"), timestampUtils.toLocalTime("24:00:00")); + void toOffsetTime() throws SQLException { + assertToOffsetTime("00:00:00+00:00", "00:00:00+00"); + assertToOffsetTime("00:00:00.1+01:00", "00:00:00.1+01"); + assertToOffsetTime("00:00:00.12+12:00", "00:00:00.12+12"); + assertToOffsetTime("00:00:00.123-01:00", "00:00:00.123-01"); + assertToOffsetTime("00:00:00.1234-02:00", "00:00:00.1234-02"); + assertToOffsetTime("00:00:00.12345-12:00", "00:00:00.12345-12"); + assertToOffsetTime("00:00:00.123456+01:30", "00:00:00.123456+01:30"); + assertToOffsetTime("00:00:00.123456-12:34", "00:00:00.123456-12:34"); + + assertToOffsetTime("23:59:59.999999+01:00", "23:59:59.999999+01"); // 0 NanoSeconds + assertToOffsetTime("23:59:59.9999999+01:00", "23:59:59.9999999+01"); // 900 NanoSeconds + assertToOffsetTime("23:59:59.99999999+01:00", "23:59:59.99999999+01"); // 990 NanoSeconds + assertToOffsetTime("23:59:59.999999998+01:00", "23:59:59.999999998+01"); // 998 NanoSeconds + assertToOffsetTime(OffsetTime.MAX.toString(), "24:00:00+01"); } - private TimestampUtils createTimestampUtils() { - return new TimestampUtils(true, (Provider) TimeZone::getDefault); + private void assertToOffsetTime(String expectedOutput, String inputTime) throws SQLException { + assertEquals(OffsetTime.parse(expectedOutput), + timestampUtils.toOffsetTime(inputTime), + "timestampUtils.toOffsetTime(" + inputTime + ")"); } } diff --git a/src/test/java/org/postgresql/test/jre8/core/SocksProxyTest.java b/src/test/java/org/postgresql/test/jre8/core/SocksProxyTest.java index 2997731..ec8e368 100644 --- a/src/test/java/org/postgresql/test/jre8/core/SocksProxyTest.java +++ b/src/test/java/org/postgresql/test/jre8/core/SocksProxyTest.java @@ -5,12 +5,12 @@ package org.postgresql.test.jre8.core; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; import org.postgresql.test.TestUtil; -import org.junit.After; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.DriverManager; @@ -19,10 +19,10 @@ * @author Joe Kutner on 10/9/17. * Twitter: @codefinger */ -public class SocksProxyTest { +class SocksProxyTest { - @After - public void cleanup() { + @AfterEach + void cleanup() { System.clearProperty("socksProxyHost"); System.clearProperty("socksProxyPort"); System.clearProperty("socksNonProxyHosts"); @@ -32,7 +32,7 @@ public void cleanup() { * Tests the connect method by connecting to the test database. */ @Test - public void testConnectWithSocksNonProxyHost() throws Exception { + void connectWithSocksNonProxyHost() throws Exception { System.setProperty("socksProxyHost", "fake-socks-proxy"); System.setProperty("socksProxyPort", "9999"); System.setProperty("socksNonProxyHosts", TestUtil.getServer()); diff --git a/src/test/java/org/postgresql/test/plugin/AuthenticationPluginTest.java b/src/test/java/org/postgresql/test/plugin/AuthenticationPluginTest.java new file mode 100644 index 0000000..fc307ce --- /dev/null +++ b/src/test/java/org/postgresql/test/plugin/AuthenticationPluginTest.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.plugin; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.PGProperty; +import org.postgresql.core.ServerVersion; +import org.postgresql.plugin.AuthenticationPlugin; +import org.postgresql.plugin.AuthenticationRequestType; +import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLException; + +// import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Properties; +import java.util.function.Consumer; + +class AuthenticationPluginTest { + @BeforeAll + static void setUp() throws SQLException { + TestUtil.assumeHaveMinimumServerVersion(ServerVersion.v10); + } + + public static class DummyAuthenticationPlugin implements AuthenticationPlugin { + private static Consumer onGetPassword; + + @Override + public /* @Nullable */ char[] getPassword(AuthenticationRequestType type) throws PSQLException { + onGetPassword.accept(type); + + // Ex: "MD5" => "DUMMY-MD5" + return ("DUMMY-" + type.toString()).toCharArray(); + } + } + + private static void testAuthPlugin(String username, String passwordEncryption, AuthenticationRequestType expectedType) throws SQLException { + createRole(username, passwordEncryption, "DUMMY-" + expectedType.toString()); + try { + Properties props = new Properties(); + props.setProperty(PGProperty.AUTHENTICATION_PLUGIN_CLASS_NAME.getName(), DummyAuthenticationPlugin.class.getName()); + PGProperty.USER.set(props, username); + + boolean[] wasCalled = {false}; + DummyAuthenticationPlugin.onGetPassword = type -> { + wasCalled[0] = true; + assertEquals(expectedType, type, "The authentication type should match"); + }; + try (Connection conn = TestUtil.openDB(props)) { + assertTrue(wasCalled[0], "The custom authentication plugin should be invoked"); + } + } finally { + dropRole(username); + } + } + + @Test + void authPluginMD5() throws Exception { + testAuthPlugin("auth_plugin_test_md5", "md5", AuthenticationRequestType.MD5_PASSWORD); + } + + @Test + void authPluginSASL() throws Exception { + testAuthPlugin("auth_plugin_test_sasl", "scram-sha-256", AuthenticationRequestType.SASL); + } + + private static void createRole(String username, String passwordEncryption, String password) throws SQLException { + try (Connection conn = TestUtil.openPrivilegedDB()) { + TestUtil.execute(conn, "SET password_encryption='" + passwordEncryption + "'"); + TestUtil.execute(conn, "DROP ROLE IF EXISTS " + username); + TestUtil.execute(conn, "CREATE USER " + username + " WITH PASSWORD '" + password + "'"); + } + } + + private static void dropRole(String username) throws SQLException { + try (Connection conn = TestUtil.openPrivilegedDB()) { + TestUtil.execute(conn, "DROP ROLE IF EXISTS " + username); + } + } +} diff --git a/src/test/java/org/postgresql/test/socketfactory/CustomSocketFactory.java b/src/test/java/org/postgresql/test/socketfactory/CustomSocketFactory.java index bb94c62..8b6c2b9 100644 --- a/src/test/java/org/postgresql/test/socketfactory/CustomSocketFactory.java +++ b/src/test/java/org/postgresql/test/socketfactory/CustomSocketFactory.java @@ -21,7 +21,7 @@ public class CustomSocketFactory extends SocketFactory { public CustomSocketFactory(String argument) { if (instance != null) { - throw new IllegalStateException("Test failed, multiple custom socket factory instanciation"); + throw new IllegalStateException("Test failed, multiple custom socket factory instantiation"); } instance = this; this.argument = argument; diff --git a/src/test/java/org/postgresql/test/ssl/CommonNameVerifierTest.java b/src/test/java/org/postgresql/test/ssl/CommonNameVerifierTest.java index a162c90..d1f66bf 100644 --- a/src/test/java/org/postgresql/test/ssl/CommonNameVerifierTest.java +++ b/src/test/java/org/postgresql/test/ssl/CommonNameVerifierTest.java @@ -5,29 +5,16 @@ package org.postgresql.test.ssl; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.ssl.PGjdbcHostnameVerifier; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Arrays; -@RunWith(Parameterized.class) public class CommonNameVerifierTest { - - private final String a; - private final String b; - private final int expected; - - public CommonNameVerifierTest(String a, String b, int expected) { - this.a = a; - this.b = b; - this.expected = expected; - } - - @Parameterized.Parameters(name = "a={0}, b={1}") public static Iterable data() { return Arrays.asList(new Object[][]{ {"com", "host.com", -1}, @@ -40,12 +27,11 @@ public static Iterable data() { }); } - @Test - public void comparePatterns() throws Exception { - Assert.assertEquals(a + " vs " + b, - expected, PGjdbcHostnameVerifier.HOSTNAME_PATTERN_COMPARATOR.compare(a, b)); + @MethodSource("data") + @ParameterizedTest + void comparePatterns(String a, String b, int expected) throws Exception { + assertEquals(expected, PGjdbcHostnameVerifier.HOSTNAME_PATTERN_COMPARATOR.compare(a, b), a + " vs " + b); - Assert.assertEquals(b + " vs " + a, - -expected, PGjdbcHostnameVerifier.HOSTNAME_PATTERN_COMPARATOR.compare(b, a)); + assertEquals(-expected, PGjdbcHostnameVerifier.HOSTNAME_PATTERN_COMPARATOR.compare(b, a), b + " vs " + a); } } diff --git a/src/test/java/org/postgresql/test/ssl/LibPQFactoryHostNameTest.java b/src/test/java/org/postgresql/test/ssl/LibPQFactoryHostNameTest.java index 0d998b7..04f851f 100644 --- a/src/test/java/org/postgresql/test/ssl/LibPQFactoryHostNameTest.java +++ b/src/test/java/org/postgresql/test/ssl/LibPQFactoryHostNameTest.java @@ -5,30 +5,16 @@ package org.postgresql.test.ssl; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.ssl.PGjdbcHostnameVerifier; -import org.postgresql.ssl.jdbc4.LibPQFactory; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Arrays; -@RunWith(Parameterized.class) public class LibPQFactoryHostNameTest { - - private final String hostname; - private final String pattern; - private final boolean expected; - - public LibPQFactoryHostNameTest(String hostname, String pattern, boolean expected) { - this.hostname = hostname; - this.pattern = pattern; - this.expected = expected; - } - - @Parameterized.Parameters(name = "host={0}, pattern={1}") public static Iterable data() { return Arrays.asList(new Object[][]{ {"host.com", "pattern.com", false}, @@ -52,12 +38,12 @@ public static Iterable data() { }); } - @Test - public void checkPattern() throws Exception { - Assert.assertEquals(hostname + ", pattern: " + pattern, - expected, LibPQFactory.verifyHostName(hostname, pattern)); + @MethodSource("data") + @ParameterizedTest + @SuppressWarnings("deprecation") + void checkPattern(String hostname, String pattern, boolean expected) throws Exception { + assertEquals(expected, org.postgresql.ssl.jdbc4.LibPQFactory.verifyHostName(hostname, pattern), hostname + ", pattern: " + pattern); - Assert.assertEquals(hostname + ", pattern: " + pattern, - expected, PGjdbcHostnameVerifier.INSTANCE.verifyHostName(hostname, pattern)); + assertEquals(expected, PGjdbcHostnameVerifier.INSTANCE.verifyHostName(hostname, pattern), hostname + ", pattern: " + pattern); } } diff --git a/src/test/java/org/postgresql/test/ssl/PEMKeyManagerTest.java b/src/test/java/org/postgresql/test/ssl/PEMKeyManagerTest.java new file mode 100644 index 0000000..0a6841f --- /dev/null +++ b/src/test/java/org/postgresql/test/ssl/PEMKeyManagerTest.java @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2025, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.ssl; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.PGProperty; +import org.postgresql.core.ServerVersion; +import org.postgresql.ssl.BaseX509KeyManager; +import org.postgresql.ssl.PEMKeyManager; +import org.postgresql.test.TestUtil; +import org.postgresql.util.PSQLException; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.AclEntry; +import java.nio.file.attribute.AclEntryPermission; +import java.nio.file.attribute.AclEntryType; +import java.nio.file.attribute.AclFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.UserPrincipal; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.logging.Logger; + +import javax.security.auth.x500.X500Principal; + +public class PEMKeyManagerTest { + + private static final Logger LOGGER = Logger.getLogger(PEMKeyManagerTest.class.getName()); + + private Set originalPosixPermissions; + private List originalAclPermissions; + private Path keyFilePath; + + /** + * Let's attempt to make the goodclient.key file to have correct permissions (owner read-only permissions). + * If it's not possible, lets not fail, and go ahead with the tests. + */ + @BeforeEach + void setKeyFilePermissions() { + try { + String keyFilePathStr = TestUtil.getSslTestCertPath("goodclient.key"); + File keyFile = new File(keyFilePathStr); + + if (keyFile.exists()) { + keyFilePath = keyFile.toPath(); + + // Check if the file system supports POSIX permissions + if (Files.getFileAttributeView(keyFilePath, java.nio.file.attribute.PosixFileAttributeView.class) != null) { + // Save original POSIX permissions + originalPosixPermissions = Files.getPosixFilePermissions(keyFilePath); + LOGGER.fine("Saved original POSIX permissions for " + keyFilePathStr); + + // Set POSIX permissions to 400 (read-only for owner) + Set perms = new HashSet<>(); + perms.add(PosixFilePermission.OWNER_READ); + Files.setPosixFilePermissions(keyFilePath, perms); + LOGGER.fine("Set POSIX permissions to 400 for " + keyFilePathStr); + } else { + // Windows: Save original ACL and set owner-only read permissions + AclFileAttributeView aclView = Files.getFileAttributeView(keyFilePath, AclFileAttributeView.class); + if (aclView != null) { + originalAclPermissions = aclView.getAcl(); + LOGGER.fine("Saved original ACL permissions for " + keyFilePathStr); + } + setWindowsOwnerOnlyPermissions(keyFilePath); + LOGGER.fine("Set Windows ACL permissions to owner read-only for " + keyFilePathStr); + } + } + } catch (Exception e) { + // Log and ignore permission setting errors - tests may still pass + LOGGER.warning("Failed to set file permissions for goodclient.key: " + e.getMessage()); + } + } + + /** + * Restores the original file permissions that were saved in setKeyFilePermissions. + */ + @AfterEach + void restoreKeyFilePermissions() { + try { + if (keyFilePath != null && Files.exists(keyFilePath)) { + // Restore POSIX permissions if they were saved + if (originalPosixPermissions != null) { + Files.setPosixFilePermissions(keyFilePath, originalPosixPermissions); + LOGGER.fine("Restored original POSIX permissions for " + keyFilePath); + originalPosixPermissions = null; + } + + // Restore ACL permissions if they were saved + if (originalAclPermissions != null) { + AclFileAttributeView aclView = Files.getFileAttributeView(keyFilePath, AclFileAttributeView.class); + if (aclView != null) { + aclView.setAcl(originalAclPermissions); + LOGGER.fine("Restored original ACL permissions for " + keyFilePath); + } + originalAclPermissions = null; + } + + keyFilePath = null; + } + } catch (Exception e) { + // Log and ignore permission restoration errors + LOGGER.warning("Failed to restore file permissions for goodclient.key: " + e.getMessage()); + } + } + + /** + * Sets Windows ACL permissions to allow only the owner to read the file. + * This is equivalent to chmod 400 on Unix systems. + */ + private void setWindowsOwnerOnlyPermissions(Path path) throws Exception { + AclFileAttributeView aclView = Files.getFileAttributeView(path, AclFileAttributeView.class); + if (aclView == null) { + return; // ACL not supported + } + + UserPrincipal owner = Files.getOwner(path); + + // Create ACL entry that grants only read permissions to the owner + AclEntry entry = AclEntry.newBuilder() + .setType(AclEntryType.ALLOW) + .setPrincipal(owner) + .setPermissions( + EnumSet.of( + AclEntryPermission.READ_DATA, + AclEntryPermission.READ_ATTRIBUTES, + AclEntryPermission.READ_NAMED_ATTRS, + AclEntryPermission.SYNCHRONIZE + ) + ) + .build(); + + // Set the ACL with only the owner's read permissions + List acl = new ArrayList<>(); + acl.add(entry); + aclView.setAcl(acl); + } + + @Test + void TestChooseClientAlias() { + String sslKeyFile = TestUtil.getSslTestCertPath("goodclient.key"); + String sslCertFile = TestUtil.getSslTestCertPath("goodclient.crt"); + PEMKeyManager pemKeyManager = new PEMKeyManager(sslKeyFile, sslCertFile, "RSA"); + + X500Principal testPrincipal = new X500Principal("CN=root certificate, O=PgJdbc test, ST=CA, C=US"); + X500Principal[] issuers = new X500Principal[]{testPrincipal}; + + String validKeyType = pemKeyManager.chooseClientAlias(new String[]{"RSA"}, issuers, null); + assertNotNull(validKeyType); + + String ignoresCase = pemKeyManager.chooseClientAlias(new String[]{"rsa"}, issuers, null); + assertNotNull(ignoresCase); + + String invalidKeyType = pemKeyManager.chooseClientAlias(new String[]{"EC"}, issuers, null); + assertNull(invalidKeyType); + + String containsValidKeyType = pemKeyManager.chooseClientAlias(new String[]{"EC", "RSA"}, issuers, null); + assertNotNull(containsValidKeyType); + + String ignoresBlank = pemKeyManager.chooseClientAlias(new String[]{}, issuers, null); + assertNotNull(ignoresBlank); + } + + @Test + void TestGoodClientPEM() throws Exception { + TestUtil.assumeHaveMinimumServerVersion(ServerVersion.v9_5); + TestUtil.assumeSslTestsEnabled(); + + Properties props = new Properties(); + PGProperty.SSL_MODE.set(props, "prefer"); + PGProperty.SSL_KEY.set(props, TestUtil.getSslTestCertPath("goodclient.key")); + PGProperty.SSL_CERT.set(props, TestUtil.getSslTestCertPath("goodclient.crt")); + PGProperty.PEM_KEY_ALGORITHM.set(props, "RSA"); + + try (Connection conn = TestUtil.openDB(props)) { + boolean sslUsed = TestUtil.queryForBoolean(conn, "SELECT ssl FROM pg_stat_ssl WHERE pid = pg_backend_pid()"); + assertTrue(sslUsed, "SSL should be in use"); + } + } + + @Test + void testPermissionsOwnerOnly(@TempDir Path tempDir) throws Exception { + Assumptions.assumeTrue( + tempDir.getFileSystem().supportedFileAttributeViews().contains("posix"), + "POSIX file permissions not supported"); + + Path keyFile = tempDir.resolve("test.key"); + Files.createFile(keyFile); + Set perms = EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE); + Files.setPosixFilePermissions(keyFile, perms); + + assertDoesNotThrow(() -> BaseX509KeyManager.validateKeyFilePermissions(keyFile), + "File with 0600 permissions should pass validation"); + } + + @Test + void testPermissionsGroupReadNonRootFails(@TempDir Path tempDir) throws Exception { + Assumptions.assumeTrue( + tempDir.getFileSystem().supportedFileAttributeViews().contains("posix"), + "POSIX file permissions not supported"); + + Path keyFile = tempDir.resolve("test.key"); + Files.createFile(keyFile); + Set perms = EnumSet.of( + PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, + PosixFilePermission.GROUP_READ); + Files.setPosixFilePermissions(keyFile, perms); + + // Non-root owned file with GROUP_READ should fail + assertThrows(PSQLException.class, + () -> BaseX509KeyManager.validateKeyFilePermissions(keyFile), + "File with GROUP_READ and non-root owner should fail validation"); + } + + @Test + void testPermissionsOthersReadFails(@TempDir Path tempDir) throws Exception { + Assumptions.assumeTrue( + tempDir.getFileSystem().supportedFileAttributeViews().contains("posix"), + "POSIX file permissions not supported"); + + Path keyFile = tempDir.resolve("test.key"); + Files.createFile(keyFile); + Set perms = EnumSet.of( + PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OTHERS_READ); + Files.setPosixFilePermissions(keyFile, perms); + + assertThrows(PSQLException.class, + () -> BaseX509KeyManager.validateKeyFilePermissions(keyFile), + "File with OTHERS_READ should fail validation"); + } + + @Test + void testPermissionsOwnerReadOnly(@TempDir Path tempDir) throws Exception { + Assumptions.assumeTrue( + tempDir.getFileSystem().supportedFileAttributeViews().contains("posix"), + "POSIX file permissions not supported"); + + Path keyFile = tempDir.resolve("test.key"); + Files.createFile(keyFile); + Set perms = EnumSet.of(PosixFilePermission.OWNER_READ); + Files.setPosixFilePermissions(keyFile, perms); + + assertDoesNotThrow(() -> BaseX509KeyManager.validateKeyFilePermissions(keyFile), + "File with 0400 permissions should pass validation"); + } +} diff --git a/src/test/java/org/postgresql/test/ssl/LazyKeyManagerTest.java b/src/test/java/org/postgresql/test/ssl/PKCS12KeyManagerTest.java similarity index 58% rename from src/test/java/org/postgresql/test/ssl/LazyKeyManagerTest.java rename to src/test/java/org/postgresql/test/ssl/PKCS12KeyManagerTest.java index 87d3aca..da0154d 100644 --- a/src/test/java/org/postgresql/test/ssl/LazyKeyManagerTest.java +++ b/src/test/java/org/postgresql/test/ssl/PKCS12KeyManagerTest.java @@ -5,12 +5,13 @@ package org.postgresql.test.ssl; -import org.postgresql.ssl.LazyKeyManager; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + import org.postgresql.ssl.PKCS12KeyManager; import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.security.PrivateKey; @@ -20,35 +21,45 @@ import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.x500.X500Principal; -public class LazyKeyManagerTest { +class PKCS12KeyManagerTest { @Test - public void testLoadP12Key() throws Exception { + void chooseClientAlias() throws Exception { PKCS12KeyManager pkcs12KeyManager = new PKCS12KeyManager( TestUtil.getSslTestCertPath("goodclient.p12"), new TestCallbackHandler("sslpwd")); + X500Principal testPrincipal = new X500Principal("CN=root certificate, O=PgJdbc test, ST=CA, C=US"); + X500Principal[] issuers = new X500Principal[]{testPrincipal}; + + String validKeyType = pkcs12KeyManager.chooseClientAlias(new String[]{"RSA"}, issuers, null); + assertNotNull(validKeyType); + + String ignoresCase = pkcs12KeyManager.chooseClientAlias(new String[]{"rsa"}, issuers, null); + assertNotNull(ignoresCase); + + String invalidKeyType = pkcs12KeyManager.chooseClientAlias(new String[]{"EC"}, issuers, null); + assertNull(invalidKeyType); + + String containsValidKeyType = pkcs12KeyManager.chooseClientAlias(new String[]{"EC", "RSA"}, issuers, null); + assertNotNull(containsValidKeyType); + + String ignoresBlank = pkcs12KeyManager.chooseClientAlias(new String[]{}, issuers, null); + assertNotNull(ignoresBlank); + PrivateKey pk = pkcs12KeyManager.getPrivateKey("user"); - Assert.assertNotNull(pk); + assertNotNull(pk); + X509Certificate[] chain = pkcs12KeyManager.getCertificateChain("user"); - Assert.assertNotNull(chain); - } + assertNotNull(chain); - @Test - public void testLoadKey() throws Exception { - LazyKeyManager lazyKeyManager = new LazyKeyManager( - TestUtil.getSslTestCertPath("goodclient.crt"), - TestUtil.getSslTestCertPath("goodclient.pk8"), - new TestCallbackHandler("sslpwd"), - true); - PrivateKey pk = lazyKeyManager.getPrivateKey("user"); - Assert.assertNotNull(pk); } public static class TestCallbackHandler implements CallbackHandler { char [] password; - public TestCallbackHandler(String password) { + TestCallbackHandler(String password) { if (password != null) { this.password = password.toCharArray(); } diff --git a/src/test/java/org/postgresql/test/ssl/PKCS12KeyTest.java b/src/test/java/org/postgresql/test/ssl/PKCS12KeyTest.java index c0696c9..29ad52c 100644 --- a/src/test/java/org/postgresql/test/ssl/PKCS12KeyTest.java +++ b/src/test/java/org/postgresql/test/ssl/PKCS12KeyTest.java @@ -5,28 +5,85 @@ package org.postgresql.test.ssl; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.postgresql.PGProperty; +import org.postgresql.ssl.PKCS12KeyManager; import org.postgresql.test.TestUtil; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import java.io.IOException; import java.sql.Connection; import java.util.Properties; -public class PKCS12KeyTest { +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.x500.X500Principal; + +class PKCS12KeyTest { @Test - public void TestGoodClientP12() throws Exception { + void TestGoodClientP12() throws Exception { TestUtil.assumeSslTestsEnabled(); Properties props = new Properties(); - props.put(TestUtil.DATABASE_PROP, "hostssldb"); + TestUtil.setTestUrlProperty(props, PGProperty.PG_DBNAME, "hostssldb"); PGProperty.SSL_MODE.set(props, "prefer"); PGProperty.SSL_KEY.set(props, TestUtil.getSslTestCertPath("goodclient.p12")); try (Connection conn = TestUtil.openDB(props)) { boolean sslUsed = TestUtil.queryForBoolean(conn, "SELECT ssl_is_used()"); - Assert.assertTrue("SSL should be in use", sslUsed); + assertTrue(sslUsed, "SSL should be in use"); + } + } + + @Test + void TestChooseClientAlias() throws Exception { + PKCS12KeyManager pkcs12KeyManager = new PKCS12KeyManager(TestUtil.getSslTestCertPath("goodclient.p12"), new TestCallbackHandler("sslpwd")); + X500Principal testPrincipal = new X500Principal("CN=root certificate, O=PgJdbc test, ST=CA, C=US"); + X500Principal[] issuers = new X500Principal[]{testPrincipal}; + + String validKeyType = pkcs12KeyManager.chooseClientAlias(new String[]{"RSA"}, issuers, null); + assertNotNull(validKeyType); + + String ignoresCase = pkcs12KeyManager.chooseClientAlias(new String[]{"rsa"}, issuers, null); + assertNotNull(ignoresCase); + + String invalidKeyType = pkcs12KeyManager.chooseClientAlias(new String[]{"EC"}, issuers, null); + assertNull(invalidKeyType); + + String containsValidKeyType = pkcs12KeyManager.chooseClientAlias(new String[]{"EC", "RSA"}, issuers, null); + assertNotNull(containsValidKeyType); + + String ignoresBlank = pkcs12KeyManager.chooseClientAlias(new String[]{}, issuers, null); + assertNotNull(ignoresBlank); + } + + public static class TestCallbackHandler implements CallbackHandler { + char [] password; + + TestCallbackHandler(String password) { + if (password != null) { + this.password = password.toCharArray(); + } + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (!(callback instanceof PasswordCallback)) { + throw new UnsupportedCallbackException(callback); + } + PasswordCallback pwdCallback = (PasswordCallback) callback; + if (password != null) { + pwdCallback.setPassword(password); + continue; + } + } } } } diff --git a/src/test/java/org/postgresql/test/ssl/SingleCertValidatingFactoryTest.java b/src/test/java/org/postgresql/test/ssl/SingleCertValidatingFactoryTest.java index e0da8e8..1b47109 100644 --- a/src/test/java/org/postgresql/test/ssl/SingleCertValidatingFactoryTest.java +++ b/src/test/java/org/postgresql/test/ssl/SingleCertValidatingFactoryTest.java @@ -5,53 +5,53 @@ package org.postgresql.test.ssl; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.postgresql.PGProperty; import org.postgresql.test.TestUtil; // import org.checkerframework.checker.nullness.qual.Nullable; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.io.BufferedReader; -import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; +import javax.net.ssl.SSLHandshakeException; + public class SingleCertValidatingFactoryTest { - @BeforeClass - public static void setUp() { + @BeforeAll + static void setUp() { TestUtil.assumeSslTestsEnabled(); } - // The valid and invalid server SSL certfiicates: + // The valid and invalid server SSL certificates: private static final String goodServerCertPath = "../certdir/goodroot.crt"; private static final String badServerCertPath = "../certdir/badroot.crt"; - private String getGoodServerCert() { + private static String getGoodServerCert() { return loadFile(goodServerCertPath); } - private String getBadServerCert() { + private static String getBadServerCert() { return loadFile(badServerCertPath); } - protected /* @Nullable */ String getUsername() { - return System.getProperty("username"); - } - - protected /* @Nullable */ String getPassword() { - return System.getProperty("password"); - } - /** * Tests whether a given throwable or one of it's root causes matches of a given class. */ - private boolean matchesExpected(/* @Nullable */ Throwable t, + private static boolean matchesExpected(/* @Nullable */ Throwable t, Class expectedThrowable) throws SQLException { if (t == null || expectedThrowable == null) { @@ -63,7 +63,7 @@ private boolean matchesExpected(/* @Nullable */ Throwable t, return matchesExpected(t.getCause(), expectedThrowable); } - protected void testConnect(Properties info, boolean sslExpected) throws SQLException { + protected static void testConnect(Properties info, boolean sslExpected) throws SQLException { testConnect(info, sslExpected, null); } @@ -71,21 +71,21 @@ protected void testConnect(Properties info, boolean sslExpected) throws SQLExcep * Connects to the database with the given connection properties and then verifies that connection * is using SSL. */ - protected void testConnect(Properties info, boolean sslExpected, + protected static void testConnect(Properties info, boolean sslExpected, /* @Nullable */ Class expectedThrowable) throws SQLException { - info.setProperty(TestUtil.DATABASE_PROP, "hostdb"); + TestUtil.setTestUrlProperty(info, PGProperty.PG_DBNAME, "hostdb"); try (Connection conn = TestUtil.openDB(info)) { Statement stmt = conn.createStatement(); // Basic SELECT test: ResultSet rs = stmt.executeQuery("SELECT 1"); rs.next(); - Assert.assertEquals(1, rs.getInt(1)); + assertEquals(1, rs.getInt(1)); rs.close(); // Verify SSL usage is as expected: rs = stmt.executeQuery("SELECT ssl_is_used()"); rs.next(); boolean sslActual = rs.getBoolean(1); - Assert.assertEquals(sslExpected, sslActual); + assertEquals(sslExpected, sslActual); stmt.close(); } catch (Exception e) { if (matchesExpected(e, expectedThrowable)) { @@ -103,7 +103,7 @@ protected void testConnect(Properties info, boolean sslExpected, } if (expectedThrowable != null) { - Assert.fail("Expected exception " + expectedThrowable.getName() + " but it did not occur."); + fail("Expected exception " + expectedThrowable.getName() + " but it did not occur."); } } @@ -116,13 +116,13 @@ public void connectSSLWithValidationNoCert() throws SQLException { Properties info = new Properties(); info.setProperty("ssl", "true"); info.setProperty("sslfactory", "org.postgresql.ssl.DefaultJavaSSLFactory"); - testConnect(info, true, javax.net.ssl.SSLHandshakeException.class); + testConnect(info, true, SSLHandshakeException.class); } /** - *

          Connect using SSL and attempt to validate the server's certificate against the wrong pre shared + * Connect using SSL and attempt to validate the server's certificate against the wrong pre shared * certificate. This test uses a pre generated certificate that will *not* match the test - * PostgreSQL server (the certificate is for properssl.example.com).

          + * PostgreSQL server (the certificate is for properssl.example.com). * *

          This connection uses a custom SSLSocketFactory using a custom trust manager that validates the * remote server's certificate against the pre shared certificate.

          @@ -136,7 +136,7 @@ public void connectSSLWithValidationWrongCert() throws SQLException, IOException info.setProperty("ssl", "true"); info.setProperty("sslfactory", "org.postgresql.ssl.SingleCertValidatingFactory"); info.setProperty("sslfactoryarg", "file:" + badServerCertPath); - testConnect(info, true, javax.net.ssl.SSLHandshakeException.class); + testConnect(info, true, SSLHandshakeException.class); } @Test @@ -145,7 +145,7 @@ public void fileCertInvalid() throws SQLException, IOException { info.setProperty("ssl", "true"); info.setProperty("sslfactory", "org.postgresql.ssl.SingleCertValidatingFactory"); info.setProperty("sslfactoryarg", "file:foo/bar/baz"); - testConnect(info, true, java.io.FileNotFoundException.class); + testConnect(info, true, FileNotFoundException.class); } @Test @@ -154,7 +154,7 @@ public void stringCertInvalid() throws SQLException, IOException { info.setProperty("ssl", "true"); info.setProperty("sslfactory", "org.postgresql.ssl.SingleCertValidatingFactory"); info.setProperty("sslfactoryarg", "foobar!"); - testConnect(info, true, java.security.GeneralSecurityException.class); + testConnect(info, true, GeneralSecurityException.class); } /** @@ -163,7 +163,7 @@ public void stringCertInvalid() throws SQLException, IOException { * certificate from a local file. */ @Test - public void connectSSLWithValidationProperCertFile() throws SQLException, IOException { + void connectSSLWithValidationProperCertFile() throws SQLException, IOException { Properties info = new Properties(); info.setProperty("ssl", "true"); info.setProperty("sslfactory", "org.postgresql.ssl.SingleCertValidatingFactory"); @@ -177,7 +177,7 @@ public void connectSSLWithValidationProperCertFile() throws SQLException, IOExce * ----- ... etc"). */ @Test - public void connectSSLWithValidationProperCertString() throws SQLException, IOException { + void connectSSLWithValidationProperCertString() throws SQLException, IOException { Properties info = new Properties(); info.setProperty("ssl", "true"); info.setProperty("sslfactory", "org.postgresql.ssl.SingleCertValidatingFactory"); @@ -190,7 +190,7 @@ public void connectSSLWithValidationProperCertString() throws SQLException, IOEx * shared certificate. The certificate is specified as a system property. */ @Test - public void connectSSLWithValidationProperCertSysProp() throws SQLException, IOException { + void connectSSLWithValidationProperCertSysProp() throws SQLException, IOException { // System property name we're using for the SSL cert. This can be anything. String sysPropName = "org.postgresql.jdbc.test.sslcert"; @@ -209,8 +209,8 @@ public void connectSSLWithValidationProperCertSysProp() throws SQLException, IOE } /** - *

          Connect using SSL and attempt to validate the server's certificate against the proper pre - * shared certificate. The certificate is specified as an environment variable.

          + * Connect using SSL and attempt to validate the server's certificate against the proper pre + * shared certificate. The certificate is specified as an environment variable. * *

          Note: To execute this test successfully you need to set the value of the environment variable * DATASOURCE_SSL_CERT prior to running the test.

          @@ -218,13 +218,11 @@ public void connectSSLWithValidationProperCertSysProp() throws SQLException, IOE *

          Here's one way to do it: $ DATASOURCE_SSL_CERT=$(cat certdir/goodroot.crt) ant clean test

          */ @Test - public void connectSSLWithValidationProperCertEnvVar() throws SQLException, IOException { + void connectSSLWithValidationProperCertEnvVar() throws SQLException, IOException { String envVarName = "DATASOURCE_SSL_CERT"; - if (System.getenv(envVarName) == null) { - System.out.println( - "Skipping test connectSSLWithValidationProperCertEnvVar (env variable is not defined)"); - return; - } + assumeTrue(System.getenv(envVarName) != null, + () -> "Skipping test connectSSLWithValidationProperCertEnvVar (env variable " + envVarName + " is not defined)" + ); Properties info = new Properties(); info.setProperty("ssl", "true"); @@ -249,7 +247,7 @@ public void connectSSLWithValidationMissingSysProp() throws SQLException, IOExce info.setProperty("ssl", "true"); info.setProperty("sslfactory", "org.postgresql.ssl.SingleCertValidatingFactory"); info.setProperty("sslfactoryarg", "sys:" + sysPropName); - testConnect(info, true, java.security.GeneralSecurityException.class); + testConnect(info, true, GeneralSecurityException.class); } finally { // Clear it out when we're done: System.setProperty(sysPropName, ""); @@ -274,7 +272,7 @@ public void connectSSLWithValidationMissingEnvVar() throws SQLException, IOExcep info.setProperty("ssl", "true"); info.setProperty("sslfactory", "org.postgresql.ssl.SingleCertValidatingFactory"); info.setProperty("sslfactoryarg", "env:" + envVarName); - testConnect(info, true, java.security.GeneralSecurityException.class); + testConnect(info, true, GeneralSecurityException.class); } /////////////////////////////////////////////////////////////////// @@ -285,7 +283,7 @@ public void connectSSLWithValidationMissingEnvVar() throws SQLException, IOExcep public static String loadFile(String path) { BufferedReader br = null; try { - br = new BufferedReader(new InputStreamReader(new FileInputStream(path))); + br = Files.newBufferedReader(Paths.get(path)); StringBuilder sb = new StringBuilder(); String line; while ((line = br.readLine()) != null) { diff --git a/src/test/java/org/postgresql/test/ssl/SslTest.java b/src/test/java/org/postgresql/test/ssl/SslTest.java index c17452a..13c5c5f 100644 --- a/src/test/java/org/postgresql/test/ssl/SslTest.java +++ b/src/test/java/org/postgresql/test/ssl/SslTest.java @@ -5,17 +5,28 @@ package org.postgresql.test.ssl; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import org.postgresql.PGProperty; +import org.postgresql.core.ServerVersion; +import org.postgresql.core.Version; import org.postgresql.jdbc.GSSEncMode; import org.postgresql.jdbc.SslMode; +import org.postgresql.jdbc.SslNegotiation; +import org.postgresql.ssl.PGjdbcHostnameVerifier; import org.postgresql.test.TestUtil; import org.postgresql.util.PSQLState; // import org.checkerframework.checker.nullness.qual.Nullable; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.MethodSource; import java.io.EOFException; import java.io.FileNotFoundException; @@ -24,13 +35,20 @@ import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.net.ssl.SSLHandshakeException; -@RunWith(Parameterized.class) +// If you want to execute a specific test iteration, comment-out @MethodSource("data") and +// update dataSubset method to return only the iterations you need. +@ParameterizedClass +@MethodSource("data") +@MethodSource("dataSubset") +@Execution(ExecutionMode.CONCURRENT) public class SslTest { enum Hostname { GOOD("localhost"), @@ -95,100 +113,243 @@ enum ClientRootCertificate { } } - @Parameterized.Parameter(0) - public Hostname host; + enum ChannelBinding { + DISABLE("disable"), + PREFER("prefer"), + REQUIRE("require"), + ; - @Parameterized.Parameter(1) - public TestDatabase db; + public static final ChannelBinding[] VALUES = values(); + public final String value; - @Parameterized.Parameter(2) - public SslMode sslmode; + ChannelBinding(String value) { + this.value = value; + } + } - @Parameterized.Parameter(3) - public ClientCertificate clientCertificate; + enum Role { + CLIENT_CERT_ROLE("md5", TestUtil.getUser() /* it has to match CN in the certificate */), + MD5_ROLE("md5", "ssl_test_md5"), + SCRAM_ROLE("scram-sha-256", "ssl_test_scram"); - @Parameterized.Parameter(4) - public ClientRootCertificate clientRootCertificate; + public static final Role[] VALUES = values(); + public final String passwordEncryption; + public final String username; - @Parameterized.Parameter(5) - public GSSEncMode gssEncMode; + Role(String passwordEncryption, String username) { + this.passwordEncryption = passwordEncryption; + this.username = username; + } - @Parameterized.Parameters(name = "host={0}, db={1} sslMode={2}, cCert={3}, cRootCert={4}, gssEncMode={5}") - public static Iterable data() { - TestUtil.assumeSslTestsEnabled(); + String getPassword() { + return "ssl_test_pass_" + name().toLowerCase(Locale.ROOT); + } + } + + private final Hostname host; + private final TestDatabase db; + private final SslMode sslmode; + private final ChannelBinding channelBinding; + private final SslNegotiation sslNegotiation; + private final ClientCertificate clientCertificate; + private final ClientRootCertificate clientRootCertificate; + private final GSSEncMode gssEncMode; + private final Role clientRole; + + SslTest(Hostname host, TestDatabase db, SslMode sslmode, ChannelBinding channelBinding, SslNegotiation sslNegotiation, + ClientCertificate clientCertificate, ClientRootCertificate clientRootCertificate, + GSSEncMode gssEncMode, Role clientRole) { + this.host = host; + this.db = db; + this.sslmode = sslmode; + this.channelBinding = channelBinding; + this.sslNegotiation = sslNegotiation; + this.clientCertificate = clientCertificate; + this.clientRootCertificate = clientRootCertificate; + this.gssEncMode = gssEncMode; + this.clientRole = clientRole; + } + + public static List dataSubset() throws SQLException { + return data().subList(1, 1); + } - Collection tests = new ArrayList(); - - for (SslMode sslMode : SslMode.VALUES) { - for (Hostname hostname : Hostname.values()) { - for (TestDatabase database : TestDatabase.VALUES) { - for (ClientCertificate clientCertificate : ClientCertificate.VALUES) { - for (ClientRootCertificate rootCertificate : ClientRootCertificate.VALUES) { - if ((sslMode == SslMode.DISABLE - || database.rejectsSsl()) - && (clientCertificate != ClientCertificate.GOOD - || rootCertificate != ClientRootCertificate.GOOD)) { - // When SSL is disabled, it does not make sense to verify "bad certificates" - // since certificates are NOT used in plaintext connections + public static List data() throws SQLException { + List tests = new ArrayList<>(); + + Version serverVersion; + try (Connection con = TestUtil.openDB()) { + serverVersion = ServerVersion.from(con.getMetaData().getDatabaseProductVersion()); + } + + for (SslNegotiation sslNegotiation : SslNegotiation.values()) { + if (sslNegotiation == SslNegotiation.DIRECT) { + if (serverVersion.getVersionNum() < ServerVersion.v17.getVersionNum()) { + continue; // ignore direct connection unless we have version 17 + } + } + // iterate over all possible combinations of parameters + for (SslMode sslMode : SslMode.VALUES) { + if (sslMode == SslMode.DISABLE && sslNegotiation == SslNegotiation.DIRECT) { + // no need to test as this is the same as DISABLE and POSTGRESQL + continue; + } + for (ChannelBinding channelBinding : ChannelBinding.VALUES) { + if (serverVersion.getVersionNum() < ServerVersion.v11.getVersionNum() + && channelBinding != ChannelBinding.DISABLE) { + // PostgreSQL supports channel binding in 11+ + continue; + } + for (Hostname hostname : Hostname.values()) { + for (TestDatabase database : TestDatabase.VALUES) { + if (database.rejectsSsl() && sslNegotiation == SslNegotiation.DIRECT) { + // The database would reject TLS anyway, so there's no need to test "direct" TLS + // connection continue; } - if (database.rejectsSsl() - && (sslMode.verifyCertificate() + for (ClientCertificate clientCertificate : ClientCertificate.VALUES) { + for (ClientRootCertificate rootCertificate : ClientRootCertificate.VALUES) { + if ((sslMode == SslMode.DISABLE + || database.rejectsSsl()) + && (clientCertificate != ClientCertificate.EMPTY + || rootCertificate != ClientRootCertificate.EMPTY)) { + // When SSL is disabled, it does not make sense to verify "bad certificates" + // since certificates are NOT used in plaintext connections + continue; + } + if (database.rejectsSsl() + && (sslMode.verifyCertificate() || hostname == Hostname.BAD) - ) { - // DB would reject SSL connection, so it makes no sense to test cases like verify-full - continue; - } - for (GSSEncMode gssEncMode : GSSEncMode.values()) { - if (gssEncMode == GSSEncMode.REQUIRE) { - // TODO: support gss tests in /certdir/pg_hba.conf - continue; + ) { + // DB would reject SSL connection, so it makes no sense to test cases like verify-full + continue; + } + for (GSSEncMode gssEncMode : GSSEncMode.values()) { + if (gssEncMode == GSSEncMode.REQUIRE) { + // TODO: support gss tests in /certdir/pg_hba.conf + continue; + } + if (gssEncMode != GSSEncMode.DISABLE + && Boolean.getBoolean("skipGssEncryption")) { + // GSSENCRequest before SSL causes authentication timeout + // on certain platforms (e.g. PostgreSQL for Windows) + continue; + } + for (Role role : Role.VALUES) { + if (clientCertificate != ClientCertificate.EMPTY && role != Role.CLIENT_CERT_ROLE) { + // Skip client certificates (good, bad) for the other roles (md5, scram) + // We do not test mixed "client_cert + scram" auth for now. + // Client certificate auth requires username to be encoded within the CN, + // so we need to generate more certificates if we want to add such tests. + continue; + } + if (serverVersion.getVersionNum() < ServerVersion.v10.getVersionNum() && role != Role.CLIENT_CERT_ROLE) { + // PostgreSQL <10 supports only boolean password_encryption, + // so it makes no sense testing extra md5/scram roles for 9.x + continue; + } + if (channelBinding == ChannelBinding.REQUIRE && role == Role.CLIENT_CERT_ROLE + && database != TestDatabase.certdb) { + // TODO: currently, CLIENT_CERT_ROLE uses "test" user, and we do not control password_encryption + // for it. We should generate more certificates (e.g. for scram_... users) then we can enable + // test that uses both channelBinding and client certificate auth at the same time. + // For certdb we know the connection would fail with + // "server skipped the authorization", and we assert the error. + continue; + } + tests.add( + new Object[]{hostname, database, sslMode, channelBinding, sslNegotiation, + clientCertificate, rootCertificate, gssEncMode, role}); + } + } } - tests.add(new Object[]{hostname, database, sslMode, clientCertificate, rootCertificate, gssEncMode}); } } } } } } - return tests; } + @BeforeAll + static void createRoles() throws SQLException { + TestUtil.assumeSslTestsEnabled(); + try (Connection conn = TestUtil.openPrivilegedDB()) { + if (!TestUtil.haveMinimumServerVersion(conn, ServerVersion.v10)) { + // PostgreSQL <10 supports only boolean values for password_encryption, so we don't + // create extra roles + return; + } + for (Role role : Role.VALUES) { + if (role == Role.CLIENT_CERT_ROLE) { + continue; + } + TestUtil.execute(conn, "SET password_encryption = '" + role.passwordEncryption + "'"); + TestUtil.execute(conn, "DROP ROLE IF EXISTS " + role.username); + TestUtil.execute(conn, "CREATE ROLE " + role.username + " WITH LOGIN PASSWORD '" + role.getPassword() + "'"); + } + } + } + + @AfterAll + static void dropRoles() throws SQLException { + try (Connection conn = TestUtil.openPrivilegedDB()) { + for (Role role : Role.VALUES) { + if (role == Role.CLIENT_CERT_ROLE) { + continue; + } + TestUtil.execute(conn, "DROP ROLE IF EXISTS " + role.username); + } + } + } + private static boolean contains(/* @Nullable */ String value, String substring) { return value != null && value.contains(substring); } - private void assertClientCertRequired(SQLException e, String caseName) { + /** + * Asserts that the error is either an auth failure (28000) or a connection failure (08001). + * PostgreSQL on Windows may reset the connection instead of sending a FATAL auth error + * when pg_hba.conf rejects the connection. + */ + private static void assertAuthOrConnectFailure(SQLException e, String caseName) { + String sqlState = e.getSQLState(); + assertTrue( + PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState().equals(sqlState) + || PSQLState.CONNECTION_UNABLE_TO_CONNECT.getState().equals(sqlState), + () -> caseName + " ==> expected 28000 or 08001 but was: " + sqlState); + } + + private static void assertClientCertRequired(/* @Nullable */ SQLException e, String caseName) { if (e == null) { - Assert.fail(caseName + " should result in failure of client validation"); - } - Assert.assertEquals(caseName + " ==> CONNECTION_FAILURE is expected", - PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState(), e.getSQLState()); + fail(caseName + " should result in failure of client validation"); + } + String sqlState = e.getSQLState(); + // The server may either send a FATAL auth error (28000) or reset the connection (08001) + // when client certificate verification fails. The latter is observed on Windows. + assertTrue( + PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState().equals(sqlState) + || PSQLState.CONNECTION_UNABLE_TO_CONNECT.getState().equals(sqlState), + caseName + " ==> expected 28000 or 08001 but was: " + sqlState); } private void checkErrorCodes(/* @Nullable */ SQLException e) { if (e != null && e.getCause() instanceof FileNotFoundException && clientRootCertificate != ClientRootCertificate.EMPTY) { - Assert.fail("FileNotFoundException => it looks like a configuration failure"); - } - - if (e == null && sslmode == SslMode.ALLOW && !db.requiresSsl()) { - // allowed to connect with plain connection - return; + fail("FileNotFoundException => it looks like a configuration failure"); } if (clientRootCertificate == ClientRootCertificate.EMPTY && (sslmode == SslMode.VERIFY_CA || sslmode == SslMode.VERIFY_FULL)) { String caseName = "rootCertificate is missing and sslmode=" + sslmode; if (e == null) { - Assert.fail(caseName + " should result in FileNotFound exception for root certificate"); + fail(caseName + " should result in FileNotFound exception for root certificate"); } - Assert.assertEquals(caseName + " ==> CONNECTION_FAILURE is expected", - PSQLState.CONNECTION_FAILURE.getState(), e.getSQLState()); + assertEquals(PSQLState.CONNECTION_FAILURE.getState(), e.getSQLState(), caseName + " ==> CONNECTION_FAILURE is expected"); FileNotFoundException fnf = findCause(e, FileNotFoundException.class); if (fnf == null) { - Assert.fail(caseName + " ==> FileNotFoundException should be present in getCause chain"); + fail(caseName + " ==> FileNotFoundException should be present in getCause chain"); } return; } @@ -196,10 +357,9 @@ private void checkErrorCodes(/* @Nullable */ SQLException e) { if (db.requiresSsl() && sslmode == SslMode.DISABLE) { String caseName = "sslmode=DISABLE and database " + db + " requires SSL"; if (e == null) { - Assert.fail(caseName + " should result in connection failure"); + fail(caseName + " should result in connection failure"); } - Assert.assertEquals(caseName + " ==> INVALID_AUTHORIZATION_SPECIFICATION is expected", - PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState(), e.getSQLState()); + assertAuthOrConnectFailure(e, caseName); return; } @@ -207,10 +367,9 @@ private void checkErrorCodes(/* @Nullable */ SQLException e) { String caseName = "database " + db + " rejects SSL, and sslmode " + sslmode + " requires encryption"; if (e == null) { - Assert.fail(caseName + " should result in connection failure"); + fail(caseName + " should result in connection failure"); } - Assert.assertEquals(caseName + " ==> INVALID_AUTHORIZATION_SPECIFICATION is expected", - PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState(), e.getSQLState()); + assertAuthOrConnectFailure(e, caseName); return; } @@ -222,6 +381,9 @@ private void checkErrorCodes(/* @Nullable */ SQLException e) { return; } } catch (AssertionError ae) { + if (e != null) { + ae.addSuppressed(e); + } errors = addError(errors, ae); } @@ -230,6 +392,9 @@ private void checkErrorCodes(/* @Nullable */ SQLException e) { return; } } catch (AssertionError ae) { + if (e != null) { + ae.addSuppressed(e); + } errors = addError(errors, ae); } @@ -238,6 +403,20 @@ private void checkErrorCodes(/* @Nullable */ SQLException e) { return; } } catch (AssertionError ae) { + if (e != null) { + ae.addSuppressed(e); + } + errors = addError(errors, ae); + } + + try { + if (assertChannelBinding(e)) { + return; + } + } catch (AssertionError ae) { + if (e != null) { + ae.addSuppressed(e); + } errors = addError(errors, ae); } @@ -249,15 +428,14 @@ private void checkErrorCodes(/* @Nullable */ SQLException e) { "sslmode=ALLOW and db " + db + " requires SSL, and there are expected SSL failures"; if (errors == null) { if (e != null) { - Assert.fail(caseName + " ==> connection should be upgraded to SSL with no failures"); + fail(caseName + " ==> connection should be upgraded to SSL with no failures"); } } else { try { if (e == null) { - Assert.fail(caseName + " ==> connection should fail"); + fail(caseName + " ==> connection should fail"); } - Assert.assertEquals(caseName + " ==> INVALID_AUTHORIZATION_SPECIFICATION is expected", - PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState(), e.getSQLState()); + assertAuthOrConnectFailure(e, caseName); } catch (AssertionError er) { for (AssertionError error : errors) { er.addSuppressed(error); @@ -276,7 +454,7 @@ private void checkErrorCodes(/* @Nullable */ SQLException e) { // should be handled with assertions above return; } - Assert.fail("SQLException present when it was not expected"); + fail("SQLException present when it was not expected"); } AssertionError firstError = errors.get(0); @@ -292,9 +470,9 @@ private void checkErrorCodes(/* @Nullable */ SQLException e) { throw firstError; } - private List addError(/* @Nullable */ List errors, AssertionError ae) { + private static List addError(/* @Nullable */ List errors, AssertionError ae) { if (errors == null) { - errors = new ArrayList(); + errors = new ArrayList<>(); } errors.add(ae); return errors; @@ -307,7 +485,7 @@ private List addError(/* @Nullable */ List error * @return true when validation pass, false when the case is not applicable * @throws AssertionError when exception does not match expectations */ - private boolean assertServerCertificate(SQLException e) { + private boolean assertServerCertificate(/* @Nullable */ SQLException e) { if (clientRootCertificate == ClientRootCertificate.GOOD || (sslmode != SslMode.VERIFY_CA && sslmode != SslMode.VERIFY_FULL)) { return false; @@ -315,18 +493,16 @@ private boolean assertServerCertificate(SQLException e) { String caseName = "Server certificate is " + clientRootCertificate + " + sslmode=" + sslmode; if (e == null) { - Assert.fail(caseName + " should result in failure of server validation"); + fail(caseName + " should result in failure of server validation"); } - Assert.assertEquals(caseName + " ==> CONNECTION_FAILURE is expected", - PSQLState.CONNECTION_FAILURE.getState(), e.getSQLState()); + assertEquals(PSQLState.CONNECTION_FAILURE.getState(), e.getSQLState(), caseName + " ==> CONNECTION_FAILURE is expected"); CertPathValidatorException validatorEx = findCause(e, CertPathValidatorException.class); if (validatorEx == null) { - Assert.fail(caseName + " ==> exception should be caused by CertPathValidatorException," + fail(caseName + " ==> exception should be caused by CertPathValidatorException," + " but no CertPathValidatorException is present in the getCause chain"); } - Assert.assertEquals(caseName + " ==> CertPathValidatorException.getReason", - "NO_TRUST_ANCHOR", validatorEx.getReason().toString()); + assertEquals("NO_TRUST_ANCHOR", validatorEx.getReason().toString(), caseName + " ==> CertPathValidatorException.getReason"); return true; } @@ -337,25 +513,89 @@ private boolean assertServerCertificate(SQLException e) { * @return true when validation pass, false when the case is not applicable * @throws AssertionError when exception does not match expectations */ - private boolean assertServerHostname(SQLException e) { + private boolean assertServerHostname(/* @Nullable */ SQLException e) { if (sslmode != SslMode.VERIFY_FULL || host != Hostname.BAD) { return false; } String caseName = "VERIFY_FULL + hostname that does not match server certificate"; if (e == null) { - Assert.fail(caseName + " ==> CONNECTION_FAILURE expected"); + fail(caseName + " ==> CONNECTION_FAILURE expected"); } - Assert.assertEquals(caseName + " ==> CONNECTION_FAILURE is expected", - PSQLState.CONNECTION_FAILURE.getState(), e.getSQLState()); + assertEquals(PSQLState.CONNECTION_FAILURE.getState(), e.getSQLState(), caseName + " ==> CONNECTION_FAILURE is expected"); String message = e.getMessage(); if (message == null || !message.contains("PgjdbcHostnameVerifier")) { - Assert.fail(caseName + " ==> message should contain" + fail(caseName + " ==> message should contain" + " 'PgjdbcHostnameVerifier'. Actual message is " + message); } return true; } + /** + * Returns true if the error is expected. + * @param e sql exception to analyze, or null if no exception happened during connect + * @return true if the error is expected. + */ + private boolean assertChannelBinding(/* @Nullable */ SQLException e) { + if (channelBinding != ChannelBinding.REQUIRE) { + // So far we expect errors only with channelBinding=require + return false; + } + + if (sslmode == SslMode.DISABLE) { + String caseName = "channelBinding=require + sslmode=disable"; + if (e == null) { + fail(caseName + " ==> CONNECTION_REJECTED expected"); + } + assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(), caseName + " ==> CONNECTION_REJECTED is expected as channelBinding requires TLS"); + return true; + } + + if (db.rejectsSsl()) { + String caseName = "channelBinding=require + db.rejectsSsl()"; + if (e == null) { + fail(caseName + " ==> CONNECTION_REJECTED expected"); + } + if (sslmode == SslMode.PREFER && e.getSQLState().equals(PSQLState.INVALID_AUTHORIZATION_SPECIFICATION.getState()) + && e.getMessage().contains("pg_hba.conf")) { + // It is fine for the connection to fail as follows: + // FATAL: no pg_hba.conf entry for host "192.168.107.1", user "test", database "hostnossldb", SSL encryption + return true; + } + assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(), caseName + " ==> CONNECTION_REJECTED is expected as channelBinding requires TLS"); + return true; + } + + if (sslmode == SslMode.ALLOW && !db.requiresSsl()) { + String caseName = "channelBinding=require + sslMode=allow + !db.requiresSsl()"; + if (e == null) { + fail(caseName + " ==> CONNECTION_REJECTED expected"); + } + assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(), caseName + " ==> CONNECTION_REJECTED is expected as channelBinding requires TLS"); + return true; + } + + if (db == TestDatabase.certdb) { + String caseName = "channelBinding=require + db=certdb"; + if (e == null) { + fail(caseName + " ==> CONNECTION_REJECTED expected"); + } + assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(), caseName + " ==> CONNECTION_REJECTED is expected as channelBinding requires SCRAM auth type, not cert"); + return true; + } + + if (clientRole == Role.MD5_ROLE) { + String caseName = "channelBinding=require + authType=md5"; + if (e == null) { + fail(caseName + " ==> CONNECTION_REJECTED expected"); + } + assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(), caseName + " ==> CONNECTION_REJECTED is expected as channelBinding requires SCRAM auth type, not md5"); + return true; + } + + return false; + } + /** * Checks client certificate validation error. * @@ -363,7 +603,7 @@ private boolean assertServerHostname(SQLException e) { * @return true when validation pass, false when the case is not applicable * @throws AssertionError when exception does not match expectations */ - private boolean assertClientCertificate(SQLException e) { + private boolean assertClientCertificate(/* @Nullable */ SQLException e) { if (db.requiresClientCert() && clientCertificate == ClientCertificate.EMPTY) { String caseName = "client certificate was not sent and database " + db + " requires client certificate"; @@ -374,11 +614,16 @@ private boolean assertClientCertificate(SQLException e) { if (clientCertificate != ClientCertificate.BAD) { return false; } + if (!db.requiresSsl() && channelBinding != ChannelBinding.REQUIRE + && (sslmode == SslMode.DISABLE || sslmode == SslMode.ALLOW)) { + // Allow plaintext connection when sslMode=allow|disable and no channel binding required + return false; + } // Server verifies certificate no matter how it is configured, so sending BAD one // is doomed to fail String caseName = "BAD client certificate, and database " + db + " requires one"; if (e == null) { - Assert.fail(caseName + " should result in failure of client validation"); + fail(caseName + " should result in failure of client validation"); } // Note: Java's SSLSocket handshake does NOT process alert messages // even if they are present on the wire. This looks like a perfectly valid @@ -386,13 +631,13 @@ private boolean assertClientCertificate(SQLException e) { // message) discovers the alert message (e.g. "Received fatal alert: decrypt_error") // and converts that to exception. // That is why "CONNECTION_UNABLE_TO_CONNECT" is listed here for BAD client cert. - // Ideally, hanshake failure should be detected during the handshake, not after sending the startup + // Ideally, handshake failure should be detected during the handshake, not after sending the startup // message if (!PSQLState.CONNECTION_FAILURE.getState().equals(e.getSQLState()) && !(clientCertificate == ClientCertificate.BAD && PSQLState.CONNECTION_UNABLE_TO_CONNECT.getState().equals(e.getSQLState())) ) { - Assert.fail(caseName + " ==> CONNECTION_FAILURE(08006)" + fail(caseName + " ==> CONNECTION_FAILURE(08006)" + " or CONNECTION_UNABLE_TO_CONNECT(08001) is expected" + ", got " + e.getSQLState()); } @@ -401,17 +646,25 @@ private boolean assertClientCertificate(SQLException e) { // SSLHandshakeException: Received fatal alert: unknown_ca // EOFException // SocketException: broken pipe (write failed) - + // Invalid argument which is a result of calling TcpNoDelay on a broken pipe // decrypt_error does not look to be a valid case, however, we allow it for now // SSLHandshakeException: Received fatal alert: decrypt_error SocketException brokenPipe = findCause(e, SocketException.class); if (brokenPipe != null) { - if (!contains(brokenPipe.getMessage(), "Broken pipe")) { - Assert.fail( - caseName + " ==> server should have terminated the connection (broken pipe expected)" - + ", actual exception was " + brokenPipe.getMessage()); + if (contains(brokenPipe.getMessage(), "Broken pipe")) { + return true; + } + if (contains(brokenPipe.getMessage(), "Connection reset")) { + return true; + } + if (contains(brokenPipe.getMessage(), "Invalid argument")) { + return true; } + fail( + caseName + " ==> Invalid Exception" + + ", actual exception was " + brokenPipe.getMessage()); + return true; } @@ -425,7 +678,7 @@ private boolean assertClientCertificate(SQLException e) { final String handshakeMessage = handshakeException.getMessage(); if (!contains(handshakeMessage, "unknown_ca") && !contains(handshakeMessage, "decrypt_error")) { - Assert.fail( + fail( caseName + " ==> server should have terminated the connection (expected 'unknown_ca' or 'decrypt_error')" + ", actual exception was " + handshakeMessage); @@ -433,7 +686,7 @@ private boolean assertClientCertificate(SQLException e) { return true; } - Assert.fail(caseName + " ==> exception should be caused by SocketException(broken pipe)" + fail(caseName + " ==> exception should be caused by SocketException(broken pipe)" + " or EOFException," + " or SSLHandshakeException. No exceptions of such kind are present in the getCause chain"); return false; @@ -451,41 +704,61 @@ private boolean assertClientCertificate(SQLException e) { } @Test - public void run() throws SQLException { + void run() throws SQLException { Properties props = new Properties(); - props.put(TestUtil.SERVER_HOST_PORT_PROP, host.value + ":" + TestUtil.getPort()); - props.put(TestUtil.DATABASE_PROP, db.toString()); + if (clientRole != Role.CLIENT_CERT_ROLE) { + PGProperty.USER.set(props, clientRole.username); + PGProperty.PASSWORD.set(props, clientRole.getPassword()); + } + TestUtil.setTestUrlProperty(props, PGProperty.PG_HOST, host.value); + TestUtil.setTestUrlProperty(props, PGProperty.PG_DBNAME, db.toString()); PGProperty.SSL_MODE.set(props, sslmode.value); + PGProperty.SSL_NEGOTIATION.set(props, sslNegotiation.value()); + PGProperty.CHANNEL_BINDING.set(props, channelBinding.value); PGProperty.GSS_ENC_MODE.set(props, gssEncMode.value); if (clientCertificate == ClientCertificate.EMPTY) { PGProperty.SSL_CERT.set(props, ""); PGProperty.SSL_KEY.set(props, ""); } else { PGProperty.SSL_CERT.set(props, TestUtil.getSslTestCertPath(clientCertificate.fileName + ".crt")); - PGProperty.SSL_KEY.set(props, TestUtil.getSslTestCertPath(clientCertificate.fileName + ".pk8")); + PGProperty.SSL_KEY.set(props, TestUtil.getSslTestCertPath(clientCertificate.fileName + ".p12")); } if (clientRootCertificate == ClientRootCertificate.EMPTY) { PGProperty.SSL_ROOT_CERT.set(props, ""); } else { PGProperty.SSL_ROOT_CERT.set(props, TestUtil.getSslTestCertPath(clientRootCertificate.fileName + ".crt")); } - + // Suppress expected hostname verification warnings when testing with a bad hostname + Logger hostnameVerifierLogger = Logger.getLogger(PGjdbcHostnameVerifier.class.getName()); + Level previousLevel = hostnameVerifierLogger.getLevel(); + if (host == Hostname.BAD) { + // We expect "Server name validation failed" warnings, so we disable the logger in tests + hostnameVerifierLogger.setLevel(Level.OFF); + } try (Connection conn = TestUtil.openDB(props)) { boolean sslUsed = TestUtil.queryForBoolean(conn, "SELECT ssl_is_used()"); + // Verify the successful connection (it might be the connection was supposed to fail) if (sslmode == SslMode.ALLOW) { - Assert.assertEquals("SSL should be used if the DB requires SSL", db.requiresSsl(), sslUsed); + assertEquals(db.requiresSsl(), sslUsed, "SSL should be used if the DB requires SSL"); } else { - Assert.assertEquals("SSL should be used unless it is disabled or the DB rejects it", sslmode != SslMode.DISABLE && !db.rejectsSsl(), sslUsed); + assertEquals(sslmode != SslMode.DISABLE && !db.rejectsSsl(), sslUsed, "SSL should be used unless it is disabled or the DB rejects it"); + } + if (channelBinding == ChannelBinding.REQUIRE) { + assertTrue(sslUsed, "channelBinding=require requires SSL"); } + checkErrorCodes(null); } catch (SQLException e) { + // Verify the failed connections (it might be certain failures were expected) try { // Note that checkErrorCodes throws AssertionError for unexpected cases checkErrorCodes(e); } catch (AssertionError ae) { - // Make sure original SQLException is printed as well even in case of AssertionError + // Make sure the original SQLException is printed as well even in case of AssertionError ae.initCause(e); throw ae; } + } finally { + hostnameVerifierLogger.setLevel(previousLevel); } } } diff --git a/src/test/java/org/postgresql/test/util/BrokenInputStream.java b/src/test/java/org/postgresql/test/util/BrokenInputStream.java index 9224c05..831b314 100644 --- a/src/test/java/org/postgresql/test/util/BrokenInputStream.java +++ b/src/test/java/org/postgresql/test/util/BrokenInputStream.java @@ -28,4 +28,9 @@ public int read() throws IOException { return is.read(); } + + @Override + public int available() throws IOException { + return is.available(); + } } diff --git a/src/test/java/org/postgresql/test/util/ByteBufferByteStreamWriterTest.java b/src/test/java/org/postgresql/test/util/ByteBufferByteStreamWriterTest.java index 6893f6b..940331f 100644 --- a/src/test/java/org/postgresql/test/util/ByteBufferByteStreamWriterTest.java +++ b/src/test/java/org/postgresql/test/util/ByteBufferByteStreamWriterTest.java @@ -5,49 +5,49 @@ package org.postgresql.test.util; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.util.ByteBufferByteStreamWriter; import org.postgresql.util.ByteStreamWriter; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; -public class ByteBufferByteStreamWriterTest { +class ByteBufferByteStreamWriterTest { private ByteArrayOutputStream targetStream; private byte[] data; private ByteBufferByteStreamWriter writer; - @Before - public void setUp() throws Exception { + @BeforeEach + void setUp() throws Exception { targetStream = new ByteArrayOutputStream(); - data = new byte[] { 1, 2, 3, 4 }; + data = new byte[]{1, 2, 3, 4}; ByteBuffer buffer = ByteBuffer.wrap(data); writer = new ByteBufferByteStreamWriter(buffer); } @Test - public void testReportsLengthCorrectly() { - assertEquals("Incorrect length reported", 4, writer.getLength()); + void reportsLengthCorrectly() { + assertEquals(4, writer.getLength(), "Incorrect length reported"); } @Test - public void testCopiesDataCorrectly() throws IOException { + void copiesDataCorrectly() throws IOException { writer.writeTo(target(targetStream)); byte[] written = targetStream.toByteArray(); - assertArrayEquals("Incorrect data written to target stream", data, written); + assertArrayEquals(data, written, "Incorrect data written to target stream"); } @Test - public void testPropagatesException() throws IOException { + void propagatesException() throws IOException { final IOException e = new IOException("oh no"); OutputStream errorStream = new OutputStream() { @Override @@ -59,7 +59,7 @@ public void write(int b) throws IOException { writer.writeTo(target(errorStream)); fail("No exception thrown"); } catch (IOException caught) { - assertEquals("Exception was thrown that wasn't the expected one", caught, e); + assertEquals(caught, e, "Exception was thrown that wasn't the expected one"); } } diff --git a/src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java b/src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java index 38694b1..277dff5 100644 --- a/src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java +++ b/src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java @@ -5,19 +5,18 @@ package org.postgresql.test.util; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; -import org.postgresql.test.SlowTests; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.util.ByteBufferByteStreamWriter; import org.postgresql.util.ByteStreamWriter; -import org.junit.Test; -import org.junit.experimental.categories.Category; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.ByteBuffer; @@ -31,16 +30,18 @@ public class ByteStreamWriterTest extends BaseTest4 { @Override public void setUp() throws Exception { super.setUp(); - assumeByteaSupported(); TestUtil.createTempTable(con, "images", "img bytea"); } - private ByteBuffer testData(int size) { + private static ByteBuffer testData(int size) { ByteBuffer data = ByteBuffer.allocate(size); Random random = new Random(31459); - while (data.remaining() > 0) { + while (data.remaining() > 8) { data.putLong(random.nextLong()); } + while (data.remaining() > 0) { + data.put((byte) (random.nextInt() % 256)); + } data.rewind(); return data; } @@ -74,7 +75,7 @@ private void validateContent(byte [] data) throws Exception { try { rs.next(); byte[] actualData = rs.getBytes(1); - assertArrayEquals("Sent and received data are not the same", data, actualData); + assertArrayEquals(data, actualData, "Sent and received data are not the same"); } finally { rs.close(); } @@ -97,6 +98,52 @@ public void testLength2Kb() throws Exception { validateContent(testData); } + @Test + public void testLength37b() throws Exception { + ByteBuffer testData = testData(37); + insertStream(testData); + validateContent(testData); + } + + @Test + public void testLength2KbReadOnly() throws Exception { + ByteBuffer testData = testData(2 * 1024); + // Read-only buffer does not provide access to the array, so we test it separately + insertStream(testData.asReadOnlyBuffer()); + validateContent(testData); + } + + @Test + public void testTwoBuffers() throws Exception { + ByteBuffer testData = testData(20); + ByteBuffer part1 = testData.duplicate(); + part1.position(0); + part1.limit(9); + ByteBuffer part2 = testData.duplicate(); + part2.position(part1.limit()); + part2.limit(testData.limit()); + // Read-only buffer does not provide access to the array, so we test it separately + insertStream(ByteStreamWriter.of(part1, part2)); + validateContent(testData); + } + + @Test + public void testThreeBuffersWithReadonly() throws Exception { + ByteBuffer testData = testData(20); + ByteBuffer part1 = testData.duplicate(); + part1.position(0); + part1.limit(9); + ByteBuffer part2 = testData.duplicate(); + part2.position(part1.limit()); + part2.limit(15); + ByteBuffer part3 = testData.duplicate(); + part3.position(part2.limit()); + part3.limit(testData.limit()); + // Read-only buffer does not provide access to the array, so we test it separately + insertStream(ByteStreamWriter.of(part1, part2.asReadOnlyBuffer(), part3)); + validateContent(testData); + } + @Test public void testLength10Kb() throws Exception { ByteBuffer testData = testData(10 * 1024); @@ -105,7 +152,6 @@ public void testLength10Kb() throws Exception { } @Test - @Category(SlowTests.class) public void testLength100Kb() throws Exception { ByteBuffer testData = testData(100 * 1024); insertStream(testData); @@ -113,7 +159,6 @@ public void testLength100Kb() throws Exception { } @Test - @Category(SlowTests.class) public void testLength200Kb() throws Exception { ByteBuffer testData = testData(200 * 1024); insertStream(testData); @@ -139,9 +184,8 @@ public void testLengthLessThanContent() throws Exception { fail("did not throw exception when too much content"); } catch (SQLException e) { Throwable cause = e.getCause(); - assertTrue("cause wan't an IOException", cause instanceof IOException); - assertEquals("Incorrect exception message", - cause.getMessage(), "Attempt to write more than the specified 4 bytes"); + assertInstanceOf(IOException.class, cause, "cause wan't an IOException"); + assertEquals(cause.getMessage(), "Attempt to write more than the specified 4 bytes", "Incorrect exception message"); } } @@ -153,7 +197,7 @@ public void testIOExceptionPassedThroughAsCause() throws Exception { fail("did not throw exception when IOException thrown"); } catch (SQLException sqle) { Throwable cause = sqle.getCause(); - assertEquals("Incorrect exception cause", e, cause); + assertEquals(e, cause, "Incorrect exception cause"); } } @@ -165,11 +209,10 @@ public void testRuntimeExceptionPassedThroughAsIOException() throws Exception { fail("did not throw exception when RuntimeException thrown"); } catch (SQLException sqle) { Throwable cause = sqle.getCause(); - assertTrue("cause wan't an IOException", cause instanceof IOException); - assertEquals("Incorrect exception message", - cause.getMessage(), "Error writing bytes to stream"); + assertInstanceOf(IOException.class, cause, "cause wan't an IOException"); + assertEquals(cause.getMessage(), "Error writing bytes to stream", "Incorrect exception message"); Throwable nestedCause = cause.getCause(); - assertEquals("Incorrect exception cause", e, nestedCause); + assertEquals(e, nestedCause, "Incorrect exception cause"); } } diff --git a/src/test/java/org/postgresql/test/util/ExpressionPropertiesTest.java b/src/test/java/org/postgresql/test/util/ExpressionPropertiesTest.java index 1a68acc..b30d812 100644 --- a/src/test/java/org/postgresql/test/util/ExpressionPropertiesTest.java +++ b/src/test/java/org/postgresql/test/util/ExpressionPropertiesTest.java @@ -5,41 +5,40 @@ package org.postgresql.test.util; +import static org.junit.jupiter.api.Assertions.assertEquals; + import org.postgresql.util.ExpressionProperties; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.Properties; -public class ExpressionPropertiesTest { +class ExpressionPropertiesTest { @Test - public void simpleReplace() { + void simpleReplace() { ExpressionProperties p = new ExpressionProperties(); p.put("server", "app1"); p.put("file", "pgjdbc_${server}.txt"); - Assert.assertEquals("${server} should be replaced", "pgjdbc_app1.txt", p.getProperty("file")); + assertEquals("pgjdbc_app1.txt", p.getProperty("file"), "${server} should be replaced"); } @Test - public void replacementMissing() { + void replacementMissing() { ExpressionProperties p = new ExpressionProperties(); p.put("file", "pgjdbc_${server}.txt"); - Assert.assertEquals("${server} should be kept as is as there is no replacement", - "pgjdbc_${server}.txt", p.getProperty("file")); + assertEquals("pgjdbc_${server}.txt", p.getProperty("file"), "${server} should be kept as is as there is no replacement"); } @Test - public void multipleReplacements() { + void multipleReplacements() { ExpressionProperties p = new ExpressionProperties(); p.put("server", "app1"); p.put("file", "${server}${server}${server}${server}${server}"); - Assert.assertEquals("All the ${server} entries should be replaced", - "app1app1app1app1app1", p.getProperty("file")); + assertEquals("app1app1app1app1app1", p.getProperty("file"), "All the ${server} entries should be replaced"); } @Test - public void multipleParentProperties() { + void multipleParentProperties() { Properties p1 = new Properties(); p1.setProperty("server", "app1_${app.type}"); Properties p2 = new Properties(); @@ -48,16 +47,14 @@ public void multipleParentProperties() { ExpressionProperties p = new ExpressionProperties(p1, p2); p.put("file", "pgjdbc_${server}.txt"); - Assert.assertEquals("All the ${...} entries should be replaced", - "pgjdbc_app1_production.txt", p.getProperty("file")); + assertEquals("pgjdbc_app1_production.txt", p.getProperty("file"), "All the ${...} entries should be replaced"); } @Test - public void rawValue() { + void rawValue() { ExpressionProperties p = new ExpressionProperties(); p.put("server", "app1"); p.put("file", "${server}${server}${server}${server}${server}"); - Assert.assertEquals("No replacements in raw value expected", - "${server}${server}${server}${server}${server}", p.getRawPropertyValue("file")); + assertEquals("${server}${server}${server}${server}${server}", p.getRawPropertyValue("file"), "No replacements in raw value expected"); } } diff --git a/src/test/java/org/postgresql/test/util/HostSpecTest.java b/src/test/java/org/postgresql/test/util/HostSpecTest.java index 54aabf3..7aad5bc 100644 --- a/src/test/java/org/postgresql/test/util/HostSpecTest.java +++ b/src/test/java/org/postgresql/test/util/HostSpecTest.java @@ -5,65 +5,65 @@ package org.postgresql.test.util; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import org.postgresql.util.HostSpec; -import org.junit.After; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; /** * @author Joe Kutner on 10/19/17. * Twitter: @codefinger */ -public class HostSpecTest { +class HostSpecTest { - @After - public void cleanup() { + @AfterEach + void cleanup() { System.clearProperty("socksProxyHost"); System.clearProperty("socksProxyPort"); System.clearProperty("socksNonProxyHosts"); } @Test - public void testShouldResolve() throws Exception { + void shouldResolve() throws Exception { HostSpec hostSpec = new HostSpec("localhost", 5432); assertTrue(hostSpec.shouldResolve()); } @Test - public void testShouldResolveWithEmptySocksProxyHost() throws Exception { + void shouldResolveWithEmptySocksProxyHost() throws Exception { System.setProperty("socksProxyHost", ""); HostSpec hostSpec = new HostSpec("localhost", 5432); assertTrue(hostSpec.shouldResolve()); } @Test - public void testShouldResolveWithWhiteSpaceSocksProxyHost() throws Exception { + void shouldResolveWithWhiteSpaceSocksProxyHost() throws Exception { System.setProperty("socksProxyHost", " "); HostSpec hostSpec = new HostSpec("localhost", 5432); assertTrue(hostSpec.shouldResolve()); } @Test - public void testShouldResolveWithSocksProxyHost() throws Exception { + void shouldResolveWithSocksProxyHost() throws Exception { System.setProperty("socksProxyHost", "fake-socks-proxy"); HostSpec hostSpec = new HostSpec("example.com", 5432); assertFalse(hostSpec.shouldResolve()); } @Test - public void testShouldResolveWithSocksProxyHostWithLocalhost() throws Exception { + void shouldResolveWithSocksProxyHostWithLocalhost() throws Exception { System.setProperty("socksProxyHost", "fake-socks-proxy"); HostSpec hostSpec = new HostSpec("localhost", 5432); assertTrue(hostSpec.shouldResolve()); } @Test - public void testShouldResolveWithSocksNonProxyHost() throws Exception { + void shouldResolveWithSocksNonProxyHost() throws Exception { System.setProperty("socksProxyHost", "fake-socks-proxy"); System.setProperty("socksNonProxyHosts", "example.com"); HostSpec hostSpec = new HostSpec("example.com", 5432); @@ -71,7 +71,7 @@ public void testShouldResolveWithSocksNonProxyHost() throws Exception { } @Test - public void testShouldResolveWithSocksNonProxyHosts() throws Exception { + void shouldResolveWithSocksNonProxyHosts() throws Exception { System.setProperty("socksProxyHost", "fake-socks-proxy"); System.setProperty("socksNonProxyHosts", "example.com|localhost"); HostSpec hostSpec = new HostSpec("example.com", 5432); @@ -79,7 +79,7 @@ public void testShouldResolveWithSocksNonProxyHosts() throws Exception { } @Test - public void testShouldResolveWithSocksNonProxyHostsNotMatching() throws Exception { + void shouldResolveWithSocksNonProxyHostsNotMatching() throws Exception { System.setProperty("socksProxyHost", "fake-socks-proxy"); System.setProperty("socksNonProxyHosts", "example.com|localhost"); HostSpec hostSpec = new HostSpec("example.org", 5432); @@ -87,13 +87,13 @@ public void testShouldResolveWithSocksNonProxyHostsNotMatching() throws Exceptio } @Test - public void testShouldReturnEmptyLocalAddressBind() throws Exception { + void shouldReturnEmptyLocalAddressBind() throws Exception { HostSpec hostSpec = new HostSpec("example.org", 5432); assertNull(hostSpec.getLocalSocketAddress()); } @Test - public void testShouldReturnLocalAddressBind() throws Exception { + void shouldReturnLocalAddressBind() throws Exception { HostSpec hostSpec = new HostSpec("example.org", 5432, "foo"); assertEquals("foo", hostSpec.getLocalSocketAddress()); } diff --git a/src/test/java/org/postgresql/test/util/LargeObjectVacuum.java b/src/test/java/org/postgresql/test/util/LargeObjectVacuum.java new file mode 100644 index 0000000..288f512 --- /dev/null +++ b/src/test/java/org/postgresql/test/util/LargeObjectVacuum.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.util; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Autovacuum does not always keeps up with the generated bloat, so this class helps vacuuming + * the pg_largeobject table when it grows too large. + */ +public class LargeObjectVacuum { + private final Connection connection; + private final long maxSize; + + public LargeObjectVacuum(Connection connection) { + this(connection, 1024 * 1024 * 1024); + } + + public LargeObjectVacuum(Connection connection, long maxSize) { + this.connection = connection; + this.maxSize = maxSize; + } + + public void vacuum() throws SQLException { + if (getLargeObjectTableSize() > maxSize) { + vacuumLargeObjectTable(); + } + } + + private void vacuumLargeObjectTable() throws SQLException { + // Vacuum can't be executed in a transaction, so we go into autocommit mode + connection.setAutoCommit(true); + try (PreparedStatement vacuum = + connection.prepareStatement("VACUUM FULL ANALYZE pg_largeobject")) { + vacuum.execute(); + } + connection.setAutoCommit(false); + } + + private long getLargeObjectTableSize() throws SQLException { + try (PreparedStatement ps = + connection.prepareStatement("select pg_table_size('pg_largeobject')")) { + try (ResultSet rs = ps.executeQuery()) { + rs.next(); + return rs.getLong(1); + } + } + } +} diff --git a/src/test/java/org/postgresql/test/util/LruCacheTest.java b/src/test/java/org/postgresql/test/util/LruCacheTest.java index 645a941..59594eb 100644 --- a/src/test/java/org/postgresql/test/util/LruCacheTest.java +++ b/src/test/java/org/postgresql/test/util/LruCacheTest.java @@ -5,14 +5,14 @@ package org.postgresql.test.util; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; import org.postgresql.util.CanEstimateSize; import org.postgresql.util.LruCache; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.SQLException; import java.util.ArrayDeque; @@ -22,7 +22,7 @@ /** * Tests {@link org.postgresql.util.LruCache}. */ -public class LruCacheTest { +class LruCacheTest { private static class Entry implements CanEstimateSize { private final int id; @@ -43,16 +43,16 @@ public String toString() { } private final Integer[] expectCreate = new Integer[1]; - private final Deque expectEvict = new ArrayDeque(); + private final Deque expectEvict = new ArrayDeque<>(); private final Entry dummy = new Entry(-999); private LruCache cache; - @Before - public void setUp() throws Exception { - cache = new LruCache(4, 1000, false, new LruCache.CreateAction() { + @BeforeEach + void setUp() throws Exception { + cache = new LruCache<>(4, 1000, false, new LruCache.CreateAction() { @Override public Entry create(Integer key) throws SQLException { - assertEquals("Unexpected create", expectCreate[0], key); + assertEquals(expectCreate[0], key, "Unexpected create"); return new Entry(key); } }, new LruCache.EvictAction() { @@ -62,13 +62,13 @@ public void evict(Entry entry) throws SQLException { fail("Unexpected entry was evicted: " + entry); } Entry expected = expectEvict.removeFirst(); - assertEquals("Unexpected evict", expected, entry); + assertEquals(expected, entry, "Unexpected evict"); } }); } @Test - public void testEvictsByNumberOfEntries() throws SQLException { + void evictsByNumberOfEntries() throws SQLException { Entry a; Entry b; Entry c; @@ -83,7 +83,7 @@ public void testEvictsByNumberOfEntries() throws SQLException { } @Test - public void testEvictsBySize() throws SQLException { + void evictsBySize() throws SQLException { Entry a; Entry b; Entry c; @@ -95,7 +95,7 @@ public void testEvictsBySize() throws SQLException { } @Test - public void testEvictsLeastRecentlyUsed() throws SQLException { + void evictsLeastRecentlyUsed() throws SQLException { Entry a; Entry b; Entry c; @@ -110,7 +110,7 @@ public void testEvictsLeastRecentlyUsed() throws SQLException { } @Test - public void testCyclicReplacement() throws SQLException { + void cyclicReplacement() throws SQLException { Entry a; Entry b; Entry c; @@ -133,7 +133,7 @@ public void testCyclicReplacement() throws SQLException { } @Test - public void testDuplicateKey() throws SQLException { + void duplicateKey() throws SQLException { Entry a; a = use(1); @@ -145,7 +145,7 @@ public void testDuplicateKey() throws SQLException { } @Test - public void testCaching() throws SQLException { + void caching() throws SQLException { Entry a; Entry b; Entry c; diff --git a/src/test/java/org/postgresql/test/util/MiniJndiContext.java b/src/test/java/org/postgresql/test/util/MiniJndiContext.java index a891507..cdb947a 100644 --- a/src/test/java/org/postgresql/test/util/MiniJndiContext.java +++ b/src/test/java/org/postgresql/test/util/MiniJndiContext.java @@ -5,6 +5,7 @@ package org.postgresql.test.util; +import java.io.IOException; import java.io.Serializable; import java.rmi.MarshalledObject; import java.util.HashMap; @@ -29,15 +30,17 @@ * @author Aaron Mulder (ammulder@chariotsolutions.com) */ public class MiniJndiContext implements Context { - private Map map = new HashMap(); + private final Map map = new HashMap<>(); public MiniJndiContext() { } + @Override public Object lookup(Name name) throws NamingException { return lookup(name.get(0)); } + @Override public Object lookup(String name) throws NamingException { Object o = map.get(name); if (o == null) { @@ -48,16 +51,14 @@ public Object lookup(String name) throws NamingException { try { Class factoryClass = Class.forName(ref.getFactoryClassName()); ObjectFactory fac = (ObjectFactory) factoryClass.newInstance(); - Object result = fac.getObjectInstance(ref, null, this, null); - return result; + return fac.getObjectInstance(ref, null, this, null); } catch (Exception e) { throw new NamingException("Unable to dereference to object: " + e); } } else if (o instanceof MarshalledObject) { try { - Object result = ((MarshalledObject) o).get(); - return result; - } catch (java.io.IOException e) { + return ((MarshalledObject) o).get(); + } catch (IOException e) { throw new NamingException("Unable to deserialize object: " + e); } catch (ClassNotFoundException e) { throw new NamingException("Unable to deserialize object: " + e); @@ -67,27 +68,31 @@ public Object lookup(String name) throws NamingException { } } + @Override public void bind(Name name, Object obj) throws NamingException { rebind(name.get(0), obj); } + @Override public void bind(String name, Object obj) throws NamingException { rebind(name, obj); } + @Override public void rebind(Name name, Object obj) throws NamingException { rebind(name.get(0), obj); } + @Override public void rebind(String name, Object obj) throws NamingException { if (obj instanceof Referenceable) { Reference ref = ((Referenceable) obj).getReference(); map.put(name, ref); } else if (obj instanceof Serializable) { try { - MarshalledObject mo = new MarshalledObject(obj); + MarshalledObject mo = new MarshalledObject<>(obj); map.put(name, mo); - } catch (java.io.IOException e) { + } catch (IOException e) { throw new NamingException("Unable to serialize object to JNDI: " + e); } } else { @@ -96,91 +101,114 @@ public void rebind(String name, Object obj) throws NamingException { } } + @Override public void unbind(Name name) throws NamingException { unbind(name.get(0)); } + @Override public void unbind(String name) throws NamingException { map.remove(name); } + @Override public void rename(Name oldName, Name newName) throws NamingException { rename(oldName.get(0), newName.get(0)); } + @Override public void rename(String oldName, String newName) throws NamingException { map.put(newName, map.remove(oldName)); } + @Override public NamingEnumeration list(Name name) throws NamingException { return null; } + @Override public NamingEnumeration list(String name) throws NamingException { return null; } + @Override public NamingEnumeration listBindings(Name name) throws NamingException { return null; } + @Override public NamingEnumeration listBindings(String name) throws NamingException { return null; } + @Override public void destroySubcontext(Name name) throws NamingException { } + @Override public void destroySubcontext(String name) throws NamingException { } + @Override public Context createSubcontext(Name name) throws NamingException { return null; } + @Override public Context createSubcontext(String name) throws NamingException { return null; } + @Override public Object lookupLink(Name name) throws NamingException { return null; } + @Override public Object lookupLink(String name) throws NamingException { return null; } + @Override public NameParser getNameParser(Name name) throws NamingException { return null; } + @Override public NameParser getNameParser(String name) throws NamingException { return null; } + @Override public Name composeName(Name name, Name prefix) throws NamingException { return null; } + @Override public String composeName(String name, String prefix) throws NamingException { return null; } + @Override public Object addToEnvironment(String propName, Object propVal) throws NamingException { return null; } + @Override public Object removeFromEnvironment(String propName) throws NamingException { return null; } + @Override public Hashtable getEnvironment() throws NamingException { return null; } + @Override public void close() throws NamingException { } + @Override public String getNameInNamespace() throws NamingException { return null; } diff --git a/src/test/java/org/postgresql/test/util/MiniJndiContextFactory.java b/src/test/java/org/postgresql/test/util/MiniJndiContextFactory.java index 2c5def0..1b82ab2 100644 --- a/src/test/java/org/postgresql/test/util/MiniJndiContextFactory.java +++ b/src/test/java/org/postgresql/test/util/MiniJndiContextFactory.java @@ -18,6 +18,7 @@ * @author Aaron Mulder (ammulder@chariotsolutions.com) */ public class MiniJndiContextFactory implements InitialContextFactory { + @Override public Context getInitialContext(Hashtable environment) throws NamingException { return new MiniJndiContext(); } diff --git a/src/test/java/org/postgresql/test/util/ObjectFactoryTest.java b/src/test/java/org/postgresql/test/util/ObjectFactoryTest.java new file mode 100644 index 0000000..a04b8ea --- /dev/null +++ b/src/test/java/org/postgresql/test/util/ObjectFactoryTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.util; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.postgresql.PGProperty; +import org.postgresql.jdbc.SslMode; +import org.postgresql.test.TestUtil; +import org.postgresql.util.ObjectFactory; +import org.postgresql.util.PSQLState; + +import org.junit.jupiter.api.Test; +import org.opentest4j.MultipleFailuresError; + +import java.sql.SQLException; +import java.util.Properties; + +import javax.net.SocketFactory; + +class ObjectFactoryTest { + Properties props = new Properties(); + + static class BadObject { + static boolean wasInstantiated; + + BadObject() { + wasInstantiated = true; + throw new RuntimeException("I should not be instantiated"); + } + } + + private void testInvalidInstantiation(PGProperty prop, PSQLState expectedSqlState) { + prop.set(props, BadObject.class.getName()); + + BadObject.wasInstantiated = false; + SQLException ex = assertThrows(SQLException.class, () -> { + TestUtil.openDB(props); + }); + + try { + assertAll( + () -> assertFalse(BadObject.wasInstantiated, "ObjectFactory should not have " + + "instantiated bad object for " + prop), + () -> assertEquals(expectedSqlState.getState(), ex.getSQLState(), () -> "#getSQLState()"), + () -> { + assertThrows( + ClassCastException.class, + () -> { + throw ex.getCause(); + }, + () -> "Wrong class specified for " + prop.name() + + " => ClassCastException is expected in SQLException#getCause()" + ); + } + ); + } catch (MultipleFailuresError e) { + // Add the original exception so it is easier to understand the reason for the test to fail + e.addSuppressed(ex); + throw e; + } + } + + @Test + void invalidSocketFactory() { + testInvalidInstantiation(PGProperty.SOCKET_FACTORY, PSQLState.CONNECTION_FAILURE); + } + + @Test + void invalidSSLFactory() { + TestUtil.assumeSslTestsEnabled(); + // We need at least "require" to trigger SslSockerFactory instantiation + PGProperty.SSL_MODE.set(props, SslMode.REQUIRE.value); + testInvalidInstantiation(PGProperty.SSL_FACTORY, PSQLState.CONNECTION_FAILURE); + } + + @Test + void invalidAuthenticationPlugin() { + testInvalidInstantiation(PGProperty.AUTHENTICATION_PLUGIN_CLASS_NAME, + PSQLState.INVALID_PARAMETER_VALUE); + } + + @Test + void invalidSslHostnameVerifier() { + TestUtil.assumeSslTestsEnabled(); + // Hostname verification is done at verify-full level only + PGProperty.SSL_MODE.set(props, SslMode.VERIFY_FULL.value); + PGProperty.SSL_ROOT_CERT.set(props, TestUtil.getSslTestCertPath("goodroot.crt")); + testInvalidInstantiation(PGProperty.SSL_HOSTNAME_VERIFIER, PSQLState.CONNECTION_FAILURE); + } + + @Test + void instantiateInvalidSocketFactory() { + Properties props = new Properties(); + assertThrows(ClassCastException.class, () -> { + ObjectFactory.instantiate(SocketFactory.class, BadObject.class.getName(), props, + false, null); + }); + } +} diff --git a/src/test/java/org/postgresql/test/util/PGPropertyMaxResultBufferParserTest.java b/src/test/java/org/postgresql/test/util/PGPropertyMaxResultBufferParserTest.java index 910dcad..df106ef 100644 --- a/src/test/java/org/postgresql/test/util/PGPropertyMaxResultBufferParserTest.java +++ b/src/test/java/org/postgresql/test/util/PGPropertyMaxResultBufferParserTest.java @@ -5,38 +5,28 @@ package org.postgresql.test.util; -import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.postgresql.test.annotations.DisableLogger; import org.postgresql.util.PGPropertyMaxResultBufferParser; import org.postgresql.util.PSQLException; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.lang.management.ManagementFactory; import java.util.Arrays; import java.util.Collection; -@RunWith(Parameterized.class) public class PGPropertyMaxResultBufferParserTest { - - @Parameterized.Parameter(0) - public String valueToParse; - - @Parameterized.Parameter(1) - public long expectedResult; - - @Parameterized.Parameters(name = "{index}: Test with valueToParse={0}, expectedResult={1}") public static Collection data() { Object[][] data = new Object[][]{ {"100", 100L}, {"10K", 10L * 1000}, {"25M", 25L * 1000 * 1000}, - //next two should be too big - {"35G", (long) (0.90 * ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax())}, - {"1T", (long) (0.90 * ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax())}, //percent test {"5p", (long) (0.05 * ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax())}, {"10pct", (long) (0.10 * ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax())}, @@ -49,21 +39,38 @@ public static Collection data() { return Arrays.asList(data); } - @Test - public void testGetMaxResultBufferValue() { - try { + public static Collection largeData() { + Object[][] data = new Object[][]{ + //next two should be too big + {"35G", (long) (0.90 * ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax())}, + {"1T", (long) (0.90 * ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax())}, + }; + return Arrays.asList(data); + } + + @MethodSource("data") + @ParameterizedTest + void getMaxResultBufferValue(String valueToParse, long expectedResult) { + assertDoesNotThrow(() -> { long result = PGPropertyMaxResultBufferParser.parseProperty(valueToParse); - Assert.assertEquals(expectedResult, result); - } catch (PSQLException e) { - //shouldn't occur - fail(); - } + assertEquals(expectedResult, result); + }); } - @Test(expected = PSQLException.class) - public void testGetMaxResultBufferValueException() throws PSQLException { - long result = PGPropertyMaxResultBufferParser.parseProperty("abc"); - fail(); + @MethodSource("largeData") + @ParameterizedTest + @DisableLogger(PGPropertyMaxResultBufferParser.class) + void getMaxResultBufferValueLargeBuffers(String valueToParse, long expectedResult) { + assertDoesNotThrow(() -> { + long result = PGPropertyMaxResultBufferParser.parseProperty(valueToParse); + assertEquals(expectedResult, result); + }); } + @Test + void getMaxResultBufferValueException() throws PSQLException { + assertThrows(PSQLException.class, () -> { + long ignore = PGPropertyMaxResultBufferParser.parseProperty("abc"); + }); + } } diff --git a/src/test/java/org/postgresql/test/util/PasswordUtilTest.java b/src/test/java/org/postgresql/test/util/PasswordUtilTest.java new file mode 100644 index 0000000..8311cd2 --- /dev/null +++ b/src/test/java/org/postgresql/test/util/PasswordUtilTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.PGConnection; +import org.postgresql.core.Utils; +import org.postgresql.test.TestUtil; +import org.postgresql.test.annotations.EnabledForServerVersionRange; +import org.postgresql.util.PasswordUtil; + +// import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.jupiter.api.Test; + +import java.security.SecureRandom; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Properties; + +class PasswordUtilTest { + private static final SecureRandom rng = new SecureRandom(); + + private static String randomSuffix() { + return Long.toHexString(rng.nextLong()); + } + + private static void assertValidUsernamePassword(String user, String password) { + Properties props = new Properties(); + props.setProperty("user", user); + props.setProperty("password", password); + try (Connection conn = TestUtil.openDB(props)) { + String actualUser = TestUtil.queryForString(conn, "SELECT USER"); + assertEquals(user, actualUser, "User should match"); + } catch (SQLException e) { + throw new RuntimeException("Failed to authenticate using supplied user and password", e); + } + } + + private static void assertInvalidUsernamePassword(String user, String password) { + Properties props = new Properties(); + props.setProperty("user", user); + props.setProperty("password", password); + assertThrows(SQLException.class, () -> { + try (Connection conn = TestUtil.openDB(props)) { + conn.getSchema(); // Do something with conn to appease checkstyle + } + }, "User should not be able to authenticate"); + } + + private static void assertWiped(char[] passwordChars) { + char[] expected = Arrays.copyOf(passwordChars, passwordChars.length); + Arrays.fill(passwordChars, (char) 0); + assertArrayEquals(expected, passwordChars, "password array should be all zeros after use"); + } + + private static void testUserPassword(/* @Nullable */ String encryptionType, String username, String password, + String encodedPassword) throws SQLException { + String escapedUsername = Utils.escapeIdentifier(null, username).toString(); + + try (Connection superConn = TestUtil.openPrivilegedDB()) { + TestUtil.execute(superConn, "CREATE USER " // + + escapedUsername // + + " WITH PASSWORD '" + encodedPassword + "'"); + + String shadowPass = TestUtil.queryForString(superConn, // + "SELECT passwd FROM pg_shadow WHERE usename = ?", username); + assertEquals(shadowPass, encodedPassword, "pg_shadow value of password must match encoded"); + + // We should be able to log in using our new user: + assertValidUsernamePassword(username, password); + // We also check that we cannot log in with the wrong password to ensure that + // the server is not simply trusting everything + assertInvalidUsernamePassword(username, "Bad Password:" + password); + + String newPassword = "mySecretNewPassword" + randomSuffix(); + PGConnection pgConn = superConn.unwrap(PGConnection.class); + char[] newPasswordChars = newPassword.toCharArray(); + pgConn.alterUserPassword(username, newPasswordChars, encryptionType); + assertNotEquals(newPassword, String.valueOf(newPasswordChars), "newPassword char[] array should be wiped and not match original after encoding"); + assertWiped(newPasswordChars); + + // We should be able to log in using our new password + assertValidUsernamePassword(username, newPassword); + // We also check that we cannot log in with the wrong password to ensure that + // the server is not simply trusting everything + assertInvalidUsernamePassword(username, "Bad Password:" + newPassword); + } finally { + try (Connection superConn = TestUtil.openPrivilegedDB()) { + TestUtil.execute(superConn, "DROP USER " + escapedUsername); + } catch (Exception ignore) { } + } + } + + private static void testUserPassword(/* @Nullable */ String encryptionType, String username, String password) throws SQLException { + char[] passwordChars = password.toCharArray(); + String encodedPassword = PasswordUtil.encodePassword( + username, passwordChars, + encryptionType == null ? "scram-sha-256" : encryptionType); + assertNotEquals(password, String.valueOf(passwordChars), "password char[] array should be wiped and not match original password after encoding"); + assertWiped(passwordChars); + testUserPassword(encryptionType, username, password, encodedPassword); + } + + private static void testUserPassword(/* @Nullable */ String encryptionType) throws SQLException { + String username = "test_password_"; + String password = "t0pSecret" + randomSuffix(); + if (encryptionType == null) { + encryptionType = "scram-sha-256"; + } + if (encryptionType.equals("md5")) { + username += "md5" + randomSuffix(); + } else { + username += "scram" + randomSuffix(); + } + + testUserPassword(encryptionType, username, password); + testUserPassword(encryptionType, username, "password with spaces"); + testUserPassword(encryptionType, username, "password with single ' quote'"); + testUserPassword(encryptionType, username, "password with double \" quote'"); + testUserPassword(encryptionType, username + " with spaces", password); + testUserPassword(encryptionType, username + " with single ' quote", password); + testUserPassword(encryptionType, username + " with single \" quote", password); + } + + @Test + @EnabledForServerVersionRange(gte = "10.0") + void encodePasswordWithServersPasswordEncryption() throws SQLException { + String encryptionType; + try (Connection conn = TestUtil.openPrivilegedDB()) { + encryptionType = TestUtil.queryForString(conn, "SHOW password_encryption"); + } + testUserPassword(encryptionType); + } + + @Test + @EnabledForServerVersionRange(gte = "10.0") + void alterUserPasswordSupportsNullEncoding() throws SQLException { + testUserPassword(null); + } + + @Test + void mD5() throws SQLException { + testUserPassword("md5"); + } + + @Test + @EnabledForServerVersionRange(gte = "10.0") + void encryptionTypeValueOfOn() throws SQLException { + testUserPassword("on"); + } + + @Test + @EnabledForServerVersionRange(gte = "10.0") + void encryptionTypeValueOfOff() throws SQLException { + testUserPassword("off"); + } + + @Test + @EnabledForServerVersionRange(gte = "10.0") + void scramSha256() throws SQLException { + testUserPassword("scram-sha-256"); + } + + @Test + @EnabledForServerVersionRange(gte = "10.0") + void customScramParams() throws SQLException { + String username = "test_password_" + randomSuffix(); + String password = "t0pSecret" + randomSuffix(); + byte[] salt = new byte[32]; + rng.nextBytes(salt); + int iterations = 12345; + String encodedPassword = PasswordUtil.encodeScramSha256(password.toCharArray(), iterations, salt); + assertTrue(encodedPassword.startsWith("SCRAM-SHA-256$" + iterations + ":"), "encoded password should have custom iteration count"); + testUserPassword("scram-sha-256", username, password, encodedPassword); + } + + @Test + void unknownEncryptionType() throws SQLException { + String username = "test_password_" + randomSuffix(); + String password = "t0pSecret" + randomSuffix(); + char[] passwordChars = password.toCharArray(); + assertThrows(SQLException.class, () -> { + PasswordUtil.encodePassword(username, passwordChars, "not-a-real-encryption-type"); + }); + assertWiped(passwordChars); + } +} diff --git a/src/test/java/org/postgresql/test/util/ServerVersionParseTest.java b/src/test/java/org/postgresql/test/util/ServerVersionParseTest.java index ffe1a90..49c32ea 100644 --- a/src/test/java/org/postgresql/test/util/ServerVersionParseTest.java +++ b/src/test/java/org/postgresql/test/util/ServerVersionParseTest.java @@ -5,30 +5,18 @@ package org.postgresql.test.util; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + import org.postgresql.core.ServerVersion; import org.postgresql.core.Version; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.util.Arrays; -@RunWith(Parameterized.class) public class ServerVersionParseTest { - - private final String versionString; - private final int versionNum; - private final String rejectReason; - - public ServerVersionParseTest(String versionString, int versionNum, String rejectReason) { - this.versionString = versionString; - this.versionNum = versionNum; - this.rejectReason = rejectReason; - } - - @Parameterized.Parameters(name = "str = {0}, expected = {1}") public static Iterable data() { return Arrays.asList(new Object[][]{ /* 4 part version tests */ @@ -81,14 +69,15 @@ public static Iterable data() { }); } - @Test - public void run() { + @MethodSource("data") + @ParameterizedTest + void run(String versionString, int versionNum, String rejectReason) { try { Version version = ServerVersion.from(versionString); if (rejectReason == null) { - Assert.assertEquals("Parsing " + versionString, versionNum, version.getVersionNum()); + assertEquals(versionNum, version.getVersionNum(), "Parsing " + versionString); } else { - Assert.fail("Should fail to parse " + versionString + ", " + rejectReason); + fail("Should fail to parse " + versionString + ", " + rejectReason); } } catch (NumberFormatException e) { if (rejectReason != null) { diff --git a/src/test/java/org/postgresql/test/util/ServerVersionTest.java b/src/test/java/org/postgresql/test/util/ServerVersionTest.java index a0a8061..99f106d 100644 --- a/src/test/java/org/postgresql/test/util/ServerVersionTest.java +++ b/src/test/java/org/postgresql/test/util/ServerVersionTest.java @@ -5,31 +5,33 @@ package org.postgresql.test.util; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.postgresql.core.ServerVersion; -import org.junit.Assert; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class ServerVersionTest { +class ServerVersionTest { @Test - public void versionIncreases() { + void versionIncreases() { ServerVersion prev = null; for (ServerVersion serverVersion : ServerVersion.values()) { if (prev != null) { - Assert.assertTrue(prev + " should be less than " + serverVersion, - prev.getVersionNum() < serverVersion.getVersionNum()); + assertTrue(prev.getVersionNum() < serverVersion.getVersionNum(), + prev + " should be less than " + serverVersion); } prev = serverVersion; } } @Test - public void testVersions() { - Assert.assertEquals(ServerVersion.v12.getVersionNum(), ServerVersion.from("12.0").getVersionNum()); - Assert.assertEquals(120004, ServerVersion.from("12.4").getVersionNum()); - Assert.assertEquals(ServerVersion.v11.getVersionNum(), ServerVersion.from("11.0").getVersionNum()); - Assert.assertEquals(110006, ServerVersion.from("11.6").getVersionNum()); - Assert.assertEquals(ServerVersion.v10.getVersionNum(), ServerVersion.from("10.0").getVersionNum()); - Assert.assertTrue(ServerVersion.v9_6.getVersionNum() < ServerVersion.from("9.6.4").getVersionNum()); + void versions() { + assertEquals(ServerVersion.v12.getVersionNum(), ServerVersion.from("12.0").getVersionNum()); + assertEquals(120004, ServerVersion.from("12.4").getVersionNum()); + assertEquals(ServerVersion.v11.getVersionNum(), ServerVersion.from("11.0").getVersionNum()); + assertEquals(110006, ServerVersion.from("11.6").getVersionNum()); + assertEquals(ServerVersion.v10.getVersionNum(), ServerVersion.from("10.0").getVersionNum()); + assertTrue(ServerVersion.v9_6.getVersionNum() < ServerVersion.from("9.6.4").getVersionNum()); } } diff --git a/src/test/java/org/postgresql/test/util/StrangeInputStream.java b/src/test/java/org/postgresql/test/util/StrangeInputStream.java index 36dcd0b..819fe5c 100644 --- a/src/test/java/org/postgresql/test/util/StrangeInputStream.java +++ b/src/test/java/org/postgresql/test/util/StrangeInputStream.java @@ -18,20 +18,55 @@ public class StrangeInputStream extends FilterInputStream { private final Random rand = new Random(); // generator of fun events - public StrangeInputStream(InputStream is, long seed) throws FileNotFoundException { + public StrangeInputStream(long seed, InputStream is) throws FileNotFoundException { super(is); rand.setSeed(seed); } @Override public int read(byte[] b) throws IOException { - int maxRead = rand.nextInt(b.length); - return super.read(b, 0, maxRead); + if (rand.nextInt(10) > 4) { + // Test read(byte[]) implementation + return super.read(b); + } + // Test teared reads + int maxRead = rand.nextInt(b.length + 1); + return read(b, 0, maxRead); } @Override public int read(byte[] b, int off, int len) throws IOException { - int maxRead = rand.nextInt(len); - return super.read(b, off, maxRead); + if (len > 0 && rand.nextInt(10) > 7) { + int next = super.read(); + if (next == -1) { + return -1; + } + b[off] = (byte) next; + return 1; + } + int n = 0; + while (len > 0) { + int maxRead = rand.nextInt(len + 1); + int read = super.read(b, off, maxRead); + if (read == -1) { + return n == 0 ? -1 : n; + } + n += read; + off += read; + len -= read; + } + return n; + } + + @Override + public long skip(long n) throws IOException { + long maxSkip = rand.nextLong() % (n + 1); + return super.skip(maxSkip); + } + + @Override + public int available() throws IOException { + int available = super.available(); + return rand.nextInt(available + 1); } } diff --git a/src/test/java/org/postgresql/test/util/StrangeOutputStream.java b/src/test/java/org/postgresql/test/util/StrangeOutputStream.java new file mode 100644 index 0000000..be450d0 --- /dev/null +++ b/src/test/java/org/postgresql/test/util/StrangeOutputStream.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2004, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.util; + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Random; + +/** + * {@link OutputStream} implementation that breaks writes into several individual writes. It + * allows to stress test other {@link OutputStream} implementations. + * For instance, it allows to test non-zero offset writes from the source buffers, + * and it might convert buffered writes into individual byte-by-byte writes. + */ +public class StrangeOutputStream extends FilterOutputStream { + private final Random rand = new Random(); // generator of fun events + private final byte[] oneByte = new byte[1]; + private final double flushProbability; + + public StrangeOutputStream(OutputStream os, long seed, double flushProbability) { + super(os); + this.flushProbability = flushProbability; + rand.setSeed(seed); + } + + @Override + public void write(int b) throws IOException { + oneByte[0] = (byte) b; + out.write(oneByte); + if (rand.nextDouble() < flushProbability) { + flush(); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + while (len > 0) { + int maxWrite = rand.nextInt(len + 1); + if (maxWrite == 1 && rand.nextBoolean()) { + out.write(b[off]); + } else { + out.write(b, off, maxWrite); + } + off += maxWrite; + len -= maxWrite; + if (rand.nextDouble() < flushProbability) { + flush(); + } + } + } +} diff --git a/src/test/java/org/postgresql/test/util/StrangeProxyServer.java b/src/test/java/org/postgresql/test/util/StrangeProxyServer.java index d964eaf..9df5574 100644 --- a/src/test/java/org/postgresql/test/util/StrangeProxyServer.java +++ b/src/test/java/org/postgresql/test/util/StrangeProxyServer.java @@ -5,8 +5,6 @@ package org.postgresql.test.util; -import org.postgresql.test.TestUtil; - import java.io.Closeable; import java.io.IOException; import java.io.InputStream; @@ -14,6 +12,8 @@ import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; /** * Proxy server that allows for pretending that traffic did not arrive at the @@ -25,7 +25,8 @@ public class StrangeProxyServer implements Closeable { private final ServerSocket serverSock; private volatile boolean keepRunning = true; - private volatile long minAcceptedAt = 0; + private volatile long minAcceptedAt; + private final List clientSockets = new CopyOnWriteArrayList<>(); public StrangeProxyServer(String destHost, int destPort) throws IOException { this.serverSock = new ServerSocket(0); @@ -34,8 +35,10 @@ public StrangeProxyServer(String destHost, int destPort) throws IOException { while (keepRunning) { try { Socket sourceSock = serverSock.accept(); + clientSockets.add(sourceSock); final long acceptedAt = System.currentTimeMillis(); Socket destSock = new Socket(destHost, destPort); + clientSockets.add(destSock); doAsync(() -> transferOneByOne(acceptedAt, sourceSock, destSock)); doAsync(() -> transferOneByOne(acceptedAt, destSock, sourceSock)); } catch (SocketTimeoutException ignore) { @@ -43,7 +46,11 @@ public StrangeProxyServer(String destHost, int destPort) throws IOException { throw new RuntimeException(e); } } - TestUtil.closeQuietly(serverSock); + try { + serverSock.close(); + } catch (IOException ignore) { + // ignore + } }); } @@ -64,14 +71,30 @@ public void stopForwardingAllClients() { this.minAcceptedAt = Long.MAX_VALUE; } - private void doAsync(Runnable task) { + /** + * Closes all tracked client and destination sockets, causing an immediate + * IOException on any in-progress reads or writes. + */ + public void closeAllClients() { + for (Socket socket : clientSockets) { + try { + socket.close(); + } catch (IOException ignore) { + // ignore + } + } + clientSockets.clear(); + } + + private static void doAsync(Runnable task) { Thread thread = new Thread(task); thread.setDaemon(true); thread.start(); } private void transferOneByOne(long acceptedAt, Socket source, Socket dest) { - try { + try (Closeable sourceSocket = source; + Closeable destSocket = dest) { InputStream in = source.getInputStream(); OutputStream out = dest.getOutputStream(); int b; @@ -83,9 +106,6 @@ private void transferOneByOne(long acceptedAt, Socket source, Socket dest) { } } } catch (IOException ignore) { - } finally { - TestUtil.closeQuietly(source); - TestUtil.closeQuietly(dest); } } } diff --git a/src/test/java/org/postgresql/test/util/rules/ServerVersionRule.java b/src/test/java/org/postgresql/test/util/rules/ServerVersionRule.java deleted file mode 100644 index 11951f0..0000000 --- a/src/test/java/org/postgresql/test/util/rules/ServerVersionRule.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2016, PostgreSQL Global Development Group - * See the LICENSE file in the project root for more information. - */ - -package org.postgresql.test.util.rules; - -import org.postgresql.core.ServerVersion; -import org.postgresql.core.Version; -import org.postgresql.jdbc.PgConnection; -import org.postgresql.test.TestUtil; -import org.postgresql.test.util.rules.annotation.HaveMinimalServerVersion; - -import org.junit.AssumptionViolatedException; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -/** - *

          Rule for ignore test if current version postgresql to old to use. And it necessary because - * without it test will fail. For use it method test class or test method should be annotate with - * {@link HaveMinimalServerVersion} annotation.

          - * - *

          Example use: - *

          - * @HaveMinimalServerVersion("8.4")
          - * public class CopyAPITest {
          - *     @Rule
          - *     private ServerVersionRule versionRule = new ServerVersionRule();
          - *
          - *     @Test
          - *     public void testCopyFromFile() throws Exception {
          - *         // test copy api introduce in 8.4 version
          - *     }
          - * }
          - * 
          - *
          - * public class LogicalReplicationTest {
          - *     @Rule
          - *     private ServerVersionRule versionRule = new ServerVersionRule();
          - *
          - *     @Test
          - *     @HaveMinimalServerVersion("9.4")
          - *     public void testStartLogicalReplication() throws Exception {
          - *         // test logical replication introduced in 9.4
          - *     }
          - * }
          - * 
          - *

          - */ -public class ServerVersionRule implements TestRule { - /** - * Server version in form x.y.z. - */ - private final String currentDisplayVersion; - private final Version currentVersion; - - public ServerVersionRule() { - PgConnection connection = null; - try { - connection = (PgConnection) TestUtil.openDB(); - currentDisplayVersion = connection.getDBVersionNumber(); - currentVersion = ServerVersion.from(currentDisplayVersion); - - } catch (Exception e) { - throw new IllegalStateException("Not available open connection", e); - } finally { - TestUtil.closeQuietly(connection); - } - } - - public Statement apply(final Statement base, final Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - HaveMinimalServerVersion requiredVersion = - description.getAnnotation(HaveMinimalServerVersion.class); - - if (requiredVersion == null) { - Class testClass = description.getTestClass(); - if (testClass != null && testClass.isAnnotationPresent(HaveMinimalServerVersion.class)) { - requiredVersion = testClass.getAnnotation(HaveMinimalServerVersion.class); - } - } - - if (requiredVersion != null) { - Version version = ServerVersion.from(requiredVersion.value()); - if (version.getVersionNum() <= 0) { - throw new IllegalArgumentException( - "Server version " + requiredVersion.value() + " not valid for " - + description.getDisplayName()); - } - - if (version.getVersionNum() > currentVersion.getVersionNum()) { - throw new AssumptionViolatedException( - "Required for test version " + requiredVersion.value() - + " but current server version " + currentDisplayVersion - ); - } - } - base.evaluate(); - } - }; - } -} diff --git a/src/test/java/org/postgresql/test/util/rules/annotation/HaveMinimalServerVersion.java b/src/test/java/org/postgresql/test/util/rules/annotation/HaveMinimalServerVersion.java deleted file mode 100644 index 9d5eae8..0000000 --- a/src/test/java/org/postgresql/test/util/rules/annotation/HaveMinimalServerVersion.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2016, PostgreSQL Global Development Group - * See the LICENSE file in the project root for more information. - */ - -package org.postgresql.test.util.rules.annotation; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.ElementType.TYPE; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation use to ignore test if the current server version less than specified version. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(value = {METHOD, TYPE}) -public @interface HaveMinimalServerVersion { - /** - * @return not null sever version in form x.y.z like 9.4, 9.5.3, etc. - * @see org.postgresql.core.ServerVersion - */ - String value(); -} diff --git a/src/test/java/org/postgresql/test/xa/XADataSourceTest.java b/src/test/java/org/postgresql/test/xa/XADataSourceTest.java index 8a28c3b..72da8c9 100644 --- a/src/test/java/org/postgresql/test/xa/XADataSourceTest.java +++ b/src/test/java/org/postgresql/test/xa/XADataSourceTest.java @@ -5,25 +5,33 @@ package org.postgresql.test.xa; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.junit.Assume.assumeTrue; +import static javax.transaction.xa.XAException.XA_RDONLY; +import static javax.transaction.xa.XAResource.XA_OK; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; import org.postgresql.test.TestUtil; +import org.postgresql.test.annotations.tags.Xa; import org.postgresql.test.jdbc2.optional.BaseDataSourceTest; +import org.postgresql.util.PSQLException; import org.postgresql.xa.PGXADataSource; // import org.checkerframework.checker.nullness.qual.Nullable; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Timestamp; import java.util.Arrays; import java.util.Random; @@ -33,6 +41,7 @@ import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; +@Xa public class XADataSourceTest { private XADataSource xaDs; @@ -44,15 +53,33 @@ public class XADataSourceTest { private XAResource xaRes; private Connection conn; - public XADataSourceTest() { + public XADataSourceTest() throws PSQLException { xaDs = new PGXADataSource(); BaseDataSourceTest.setupDataSource((PGXADataSource) xaDs); } - @Before - public void setUp() throws Exception { + @BeforeAll + static void beforeClass() throws Exception { + try (Connection con = TestUtil.openDB()) { + assumeTrue(isPreparedTransactionEnabled(con), "max_prepared_transactions should be non-zero for XA tests"); + TestUtil.createTable(con, "testxa1", "foo int"); + TestUtil.createTable(con, "testxa2", "foo int primary key"); + TestUtil.createTable(con, "testxa3", "foo int references testxa2(foo) deferrable"); + } + } + + @AfterAll + static void afterClass() throws Exception { + try (Connection con = TestUtil.openDB()) { + TestUtil.dropTable(con, "testxa3"); + TestUtil.dropTable(con, "testxa2"); + TestUtil.dropTable(con, "testxa1"); + } + } + + @BeforeEach + void setUp() throws Exception { dbConn = TestUtil.openDB(); - assumeTrue(isPreparedTransactionEnabled(dbConn)); // Check if we're operating as a superuser; some tests require it. Statement st = dbConn.createStatement(); @@ -62,9 +89,9 @@ public void setUp() throws Exception { connIsSuper = rs.getBoolean(1); // One col is guaranteed st.close(); - TestUtil.createTable(dbConn, "testxa1", "foo int"); - TestUtil.createTable(dbConn, "testxa2", "foo int primary key"); - TestUtil.createTable(dbConn, "testxa3", "foo int references testxa2(foo) deferrable"); + TestUtil.execute(dbConn, "TRUNCATE testxa3 CASCADE"); + TestUtil.execute(dbConn, "TRUNCATE testxa2 CASCADE"); + TestUtil.execute(dbConn, "TRUNCATE testxa1 CASCADE"); clearAllPrepared(); @@ -83,39 +110,34 @@ private static boolean isPreparedTransactionEnabled(Connection connection) throw return mpt > 0; } - @After - public void tearDown() throws SQLException { + @AfterEach + void tearDown() throws SQLException, XAException { try { xaconn.close(); } catch (Exception ignored) { } clearAllPrepared(); - TestUtil.dropTable(dbConn, "testxa3"); - TestUtil.dropTable(dbConn, "testxa2"); - TestUtil.dropTable(dbConn, "testxa1"); TestUtil.closeDB(dbConn); } - private void clearAllPrepared() throws SQLException { - Statement st = dbConn.createStatement(); + private void clearAllPrepared() throws SQLException, XAException { + XAConnection con = xaDs.getXAConnection(); + XAResource xaResource = con.getXAResource(); try { - ResultSet rs = st.executeQuery( - "SELECT x.gid, x.owner = current_user " - + "FROM pg_prepared_xacts x " - + "WHERE x.database = current_database()"); - - Statement st2 = dbConn.createStatement(); - while (rs.next()) { - // TODO: This should really use org.junit.Assume once we move to JUnit 4 - assertTrue("Only prepared xacts owned by current user may be present in db", - rs.getBoolean(2)); - st2.executeUpdate("ROLLBACK PREPARED '" + rs.getString(1) + "'"); + // Get the first batch of the xids + Xid[] xids = xaResource.recover(XAResource.TMSTARTRSCAN); + while (xids.length != 0) { + for (Xid xid : xids) { + xaResource.rollback(xid); + } + // Get the next batch of the xids + xids = xaResource.recover(XAResource.TMNOFLAGS); } - st2.close(); } finally { - st.close(); + xaResource.recover(XAResource.TMENDRSCAN); + con.close(); } } @@ -164,11 +186,7 @@ public boolean equals(/* @Nullable */ Object o) { if (!Arrays.equals(other.getBranchQualifier(), this.getBranchQualifier())) { return false; } - if (!Arrays.equals(other.getGlobalTransactionId(), this.getGlobalTransactionId())) { - return false; - } - - return true; + return Arrays.equals(other.getGlobalTransactionId(), this.getGlobalTransactionId()); } @Override @@ -187,14 +205,14 @@ public int hashCode() { * PGXAConnection.getConnection(). */ @Test - public void testWrapperEquals() throws Exception { - assertTrue("Wrappers should be equal", conn.equals(conn)); - assertFalse("Wrapper should be unequal to null", conn.equals(null)); - assertFalse("Wrapper should be unequal to unrelated object", conn.equals("dummy string object")); + void wrapperEquals() throws Exception { + assertEquals(conn, conn, "Wrappers should be equal"); + assertNotEquals(null, conn, "Wrapper should be unequal to null"); + assertNotEquals("dummy string object", conn, "Wrapper should be unequal to unrelated object"); } @Test - public void testOnePhase() throws Exception { + void onePhase() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); conn.createStatement().executeQuery("SELECT * FROM testxa1"); @@ -203,17 +221,28 @@ public void testOnePhase() throws Exception { } @Test - public void testTwoPhaseCommit() throws Exception { + void twoPhaseCommit() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); conn.createStatement().executeQuery("SELECT * FROM testxa1"); xaRes.end(xid, XAResource.TMSUCCESS); - xaRes.prepare(xid); + assertEquals(XA_OK, xaRes.prepare(xid)); + xaRes.commit(xid, false); + } + + @Test + void twoPhasePrepareReadOnly() throws Exception { + Xid xid = new CustomXid(1); + conn.setReadOnly(true); + xaRes.start(xid, XAResource.TMNOFLAGS); + conn.createStatement().executeQuery("SELECT * FROM testxa1"); + xaRes.end(xid, XAResource.TMSUCCESS); + assertEquals(XA_RDONLY, xaRes.prepare(xid)); xaRes.commit(xid, false); } @Test - public void testCloseBeforeCommit() throws Exception { + void closeBeforeCommit() throws Exception { Xid xid = new CustomXid(5); xaRes.start(xid, XAResource.TMNOFLAGS); assertEquals(1, conn.createStatement().executeUpdate("INSERT INTO testxa1 VALUES (1)")); @@ -227,7 +256,7 @@ public void testCloseBeforeCommit() throws Exception { } @Test - public void testRecover() throws Exception { + void recover() throws Exception { Xid xid = new CustomXid(12345); xaRes.start(xid, XAResource.TMNOFLAGS); conn.createStatement().executeQuery("SELECT * FROM testxa1"); @@ -246,7 +275,7 @@ public void testRecover() throws Exception { } } - assertTrue("Did not recover prepared xid", recoveredXid); + assertTrue(recoveredXid, "Did not recover prepared xid"); assertEquals(0, xaRes.recover(XAResource.TMNOFLAGS).length); } @@ -264,12 +293,12 @@ public void testRecover() throws Exception { } } - assertFalse("Recovered rolled back xid", recoveredXid); + assertFalse(recoveredXid, "Recovered rolled back xid"); } } @Test - public void testRollback() throws XAException { + void rollback() throws XAException { Xid xid = new CustomXid(3); xaRes.start(xid, XAResource.TMNOFLAGS); @@ -279,7 +308,7 @@ public void testRollback() throws XAException { } @Test - public void testRollbackWithoutPrepare() throws XAException { + void rollbackWithoutPrepare() throws XAException { Xid xid = new CustomXid(4); xaRes.start(xid, XAResource.TMNOFLAGS); @@ -288,7 +317,7 @@ public void testRollbackWithoutPrepare() throws XAException { } @Test - public void testAutoCommit() throws Exception { + void autoCommit() throws Exception { Xid xid = new CustomXid(6); // When not in an XA transaction, autocommit should be true @@ -331,13 +360,13 @@ public void testAutoCommit() throws Exception { conn.createStatement().executeQuery("SELECT * FROM testxa1"); - java.sql.Timestamp ts1 = getTransactionTimestamp(conn); + Timestamp ts1 = getTransactionTimestamp(conn); conn.close(); conn = xaconn.getConnection(); assertFalse(conn.getAutoCommit()); - java.sql.Timestamp ts2 = getTransactionTimestamp(conn); + Timestamp ts2 = getTransactionTimestamp(conn); /* * Check that we're still in the same transaction. close+getConnection() should not rollback the @@ -352,7 +381,7 @@ public void testAutoCommit() throws Exception { } /** - *

          Get the time the current transaction was started from the server.

          + * Get the time the current transaction was started from the server. * *

          This can be used to check that transaction doesn't get committed/ rolled back inadvertently, by * calling this once before and after the suspected piece of code, and check that they match. It's @@ -360,14 +389,14 @@ public void testAutoCommit() throws Exception { * runs fast enough, and/or the server clock is very coarse grained. But it'll do for testing * purposes.

          */ - private static java.sql.Timestamp getTransactionTimestamp(Connection conn) throws SQLException { + private static Timestamp getTransactionTimestamp(Connection conn) throws SQLException { ResultSet rs = conn.createStatement().executeQuery("SELECT now()"); rs.next(); return rs.getTimestamp(1); } @Test - public void testEndThenJoin() throws XAException { + void endThenJoin() throws XAException { Xid xid = new CustomXid(5); xaRes.start(xid, XAResource.TMNOFLAGS); @@ -378,7 +407,7 @@ public void testEndThenJoin() throws XAException { } @Test - public void testRestoreOfAutoCommit() throws Exception { + void restoreOfAutoCommit() throws Exception { conn.setAutoCommit(false); Xid xid = new CustomXid(14); @@ -387,8 +416,8 @@ public void testRestoreOfAutoCommit() throws Exception { xaRes.commit(xid, true); assertFalse( - "XaResource should have restored connection autocommit mode after commit or rollback to the initial state.", - conn.getAutoCommit()); + conn.getAutoCommit(), + "XaResource should have restored connection autocommit mode after commit or rollback to the initial state."); // Test true case conn.setAutoCommit(true); @@ -399,13 +428,13 @@ public void testRestoreOfAutoCommit() throws Exception { xaRes.commit(xid, true); assertTrue( - "XaResource should have restored connection autocommit mode after commit or rollback to the initial state.", - conn.getAutoCommit()); + conn.getAutoCommit(), + "XaResource should have restored connection autocommit mode after commit or rollback to the initial state."); } @Test - public void testRestoreOfAutoCommitEndThenJoin() throws Exception { + void restoreOfAutoCommitEndThenJoin() throws Exception { // Test with TMJOIN conn.setAutoCommit(true); @@ -417,8 +446,8 @@ public void testRestoreOfAutoCommitEndThenJoin() throws Exception { xaRes.commit(xid, true); assertTrue( - "XaResource should have restored connection autocommit mode after start(TMNOFLAGS) end() start(TMJOIN) and then commit or rollback to the initial state.", - conn.getAutoCommit()); + conn.getAutoCommit(), + "XaResource should have restored connection autocommit mode after start(TMNOFLAGS) end() start(TMJOIN) and then commit or rollback to the initial state."); } @@ -428,7 +457,7 @@ public void testRestoreOfAutoCommitEndThenJoin() throws Exception { * transaction with identifier "blah" does not exist */ @Test - public void testRepeatedRolledBack() throws Exception { + void repeatedRolledBack() throws Exception { Xid xid = new CustomXid(654321); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -440,8 +469,7 @@ public void testRepeatedRolledBack() throws Exception { xaRes.rollback(xid); fail("Rollback was successful"); } catch (XAException xae) { - assertEquals("Checking the errorCode is XAER_NOTA indicating the " + "xid does not exist.", - xae.errorCode, XAException.XAER_NOTA); + assertEquals(XAException.XAER_NOTA, xae.errorCode, "Checking the errorCode is XAER_NOTA indicating the " + "xid does not exist."); } } @@ -450,7 +478,7 @@ public void testRepeatedRolledBack() throws Exception { * with error code {@link XAException#XAER_PROTO}. */ @Test - public void testPreparingPreparedXid() throws Exception { + void preparingPreparedXid() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -459,8 +487,7 @@ public void testPreparingPreparedXid() throws Exception { xaRes.prepare(xid); fail("Prepare is expected to fail with XAER_PROTO as xid was already prepared"); } catch (XAException xae) { - assertEquals("Prepare call on already prepared xid " + xid + " expects XAER_PROTO", - XAException.XAER_PROTO, xae.errorCode); + assertEquals(XAException.XAER_PROTO, xae.errorCode, "Prepare call on already prepared xid " + xid + " expects XAER_PROTO"); } finally { xaRes.rollback(xid); } @@ -471,7 +498,7 @@ public void testPreparingPreparedXid() throws Exception { * with error code {@link XAException#XAER_NOTA}. */ @Test - public void testCommitingCommittedXid() throws Exception { + void committingCommittedXid() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -482,8 +509,7 @@ public void testCommitingCommittedXid() throws Exception { xaRes.commit(xid, false); fail("Commit is expected to fail with XAER_NOTA as xid was already committed"); } catch (XAException xae) { - assertEquals("Commit call on already committed xid " + xid + " expects XAER_NOTA", - XAException.XAER_NOTA, xae.errorCode); + assertEquals(XAException.XAER_NOTA, xae.errorCode, "Commit call on already committed xid " + xid + " expects XAER_NOTA"); } } @@ -492,7 +518,7 @@ public void testCommitingCommittedXid() throws Exception { * That different connection could be for example transaction manager recovery. */ @Test - public void testCommitByDifferentConnection() throws Exception { + void commitByDifferentConnection() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -515,8 +541,7 @@ public void testCommitByDifferentConnection() throws Exception { xaRes.commit(xid, false); fail("Commit is expected to fail with XAER_RMERR as somebody else already committed"); } catch (XAException xae) { - assertEquals("Commit call on already committed xid " + xid + " expects XAER_RMERR", - XAException.XAER_RMERR, xae.errorCode); + assertEquals(XAException.XAER_RMERR, xae.errorCode, "Commit call on already committed xid " + xid + " expects XAER_RMERR"); } } @@ -525,7 +550,7 @@ public void testCommitByDifferentConnection() throws Exception { * That different connection could be for example transaction manager recovery. */ @Test - public void testRollbackByDifferentConnection() throws Exception { + void rollbackByDifferentConnection() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -548,8 +573,7 @@ public void testRollbackByDifferentConnection() throws Exception { xaRes.rollback(xid); fail("Rollback is expected to fail with XAER_RMERR as somebody else already rolled-back"); } catch (XAException xae) { - assertEquals("Rollback call on already rolled-back xid " + xid + " expects XAER_RMERR", - XAException.XAER_RMERR, xae.errorCode); + assertEquals(XAException.XAER_RMERR, xae.errorCode, "Rollback call on already rolled-back xid " + xid + " expects XAER_RMERR"); } } @@ -557,7 +581,7 @@ public void testRollbackByDifferentConnection() throws Exception { * One-phase commit of prepared {@link Xid} should throw exception. */ @Test - public void testOnePhaseCommitOfPrepared() throws Exception { + void onePhaseCommitOfPrepared() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -567,8 +591,7 @@ public void testOnePhaseCommitOfPrepared() throws Exception { xaRes.commit(xid, true); fail("One-phase commit is expected to fail with XAER_PROTO when called on prepared xid"); } catch (XAException xae) { - assertEquals("One-phase commit of prepared xid " + xid + " expects XAER_PROTO", - XAException.XAER_PROTO, xae.errorCode); + assertEquals(XAException.XAER_PROTO, xae.errorCode, "One-phase commit of prepared xid " + xid + " expects XAER_PROTO"); } } @@ -577,7 +600,7 @@ public void testOnePhaseCommitOfPrepared() throws Exception { * {@link XAException} being thrown with error code {@link XAException#XAER_NOTA}. */ @Test - public void testOnePhaseCommitingCommittedXid() throws Exception { + void onePhaseCommittingCommittedXid() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -587,8 +610,7 @@ public void testOnePhaseCommitingCommittedXid() throws Exception { xaRes.commit(xid, true); fail("One-phase commit is expected to fail with XAER_NOTA as xid was already committed"); } catch (XAException xae) { - assertEquals("One-phase commit call on already committed xid " + xid + " expects XAER_NOTA", - XAException.XAER_NOTA, xae.errorCode); + assertEquals(XAException.XAER_NOTA, xae.errorCode, "One-phase commit call on already committed xid " + xid + " expects XAER_NOTA"); } } @@ -597,14 +619,13 @@ public void testOnePhaseCommitingCommittedXid() throws Exception { * is {@link XAException#XAER_NOTA}. */ @Test - public void testPrepareUnknownXid() throws Exception { + void prepareUnknownXid() throws Exception { Xid xid = new CustomXid(1); try { xaRes.prepare(xid); fail("Prepare is expected to fail with XAER_NOTA as used unknown xid"); } catch (XAException xae) { - assertEquals("Prepare call on unknown xid " + xid + " expects XAER_NOTA", - XAException.XAER_NOTA, xae.errorCode); + assertEquals(XAException.XAER_NOTA, xae.errorCode, "Prepare call on unknown xid " + xid + " expects XAER_NOTA"); } } @@ -613,7 +634,7 @@ public void testPrepareUnknownXid() throws Exception { * is {@link XAException#XAER_NOTA}. */ @Test - public void testCommitUnknownXid() throws Exception { + void commitUnknownXid() throws Exception { Xid xid = new CustomXid(1); Xid unknownXid = new CustomXid(42); xaRes.start(xid, XAResource.TMNOFLAGS); @@ -623,8 +644,7 @@ public void testCommitUnknownXid() throws Exception { xaRes.commit(unknownXid, false); fail("Commit is expected to fail with XAER_NOTA as used unknown xid"); } catch (XAException xae) { - assertEquals("Commit call on unknown xid " + unknownXid + " expects XAER_NOTA", - XAException.XAER_NOTA, xae.errorCode); + assertEquals(XAException.XAER_NOTA, xae.errorCode, "Commit call on unknown xid " + unknownXid + " expects XAER_NOTA"); } finally { xaRes.rollback(xid); } @@ -635,7 +655,7 @@ public void testCommitUnknownXid() throws Exception { * the expected {@link XAException#errorCode} is {@link XAException#XAER_NOTA}. */ @Test - public void testOnePhaseCommitUnknownXid() throws Exception { + void onePhaseCommitUnknownXid() throws Exception { Xid xid = new CustomXid(1); Xid unknownXid = new CustomXid(42); xaRes.start(xid, XAResource.TMNOFLAGS); @@ -644,8 +664,7 @@ public void testOnePhaseCommitUnknownXid() throws Exception { xaRes.commit(unknownXid, true); fail("One-phase commit is expected to fail with XAER_NOTA as used unknown xid"); } catch (XAException xae) { - assertEquals("Commit call on unknown xid " + unknownXid + " expects XAER_NOTA", - XAException.XAER_NOTA, xae.errorCode); + assertEquals(XAException.XAER_NOTA, xae.errorCode, "Commit call on unknown xid " + unknownXid + " expects XAER_NOTA"); } finally { xaRes.rollback(xid); } @@ -656,7 +675,7 @@ public void testOnePhaseCommitUnknownXid() throws Exception { * is {@link XAException#XAER_NOTA}. */ @Test - public void testRollbackUnknownXid() throws Exception { + void rollbackUnknownXid() throws Exception { Xid xid = new CustomXid(1); Xid unknownXid = new CustomXid(42); xaRes.start(xid, XAResource.TMNOFLAGS); @@ -666,8 +685,7 @@ public void testRollbackUnknownXid() throws Exception { xaRes.rollback(unknownXid); fail("Rollback is expected to fail as used unknown xid"); } catch (XAException xae) { - assertEquals("Commit call on unknown xid " + unknownXid + " expects XAER_NOTA", - XAException.XAER_NOTA, xae.errorCode); + assertEquals(XAException.XAER_NOTA, xae.errorCode, "Commit call on unknown xid " + unknownXid + " expects XAER_NOTA"); } finally { xaRes.rollback(xid); } @@ -678,7 +696,7 @@ public void testRollbackUnknownXid() throws Exception { * Resource manager can't expect state of the {@link Xid}. */ @Test - public void testDatabaseRemovesPreparedBeforeCommit() throws Exception { + void databaseRemovesPreparedBeforeCommit() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -690,8 +708,7 @@ public void testDatabaseRemovesPreparedBeforeCommit() throws Exception { xaRes.commit(xid, false); fail("Commit is expected to fail as committed xid was removed before"); } catch (XAException xae) { - assertEquals("Commit call on xid " + xid + " not known to DB expects XAER_RMERR", - XAException.XAER_RMERR, xae.errorCode); + assertEquals(XAException.XAER_RMERR, xae.errorCode, "Commit call on xid " + xid + " not known to DB expects XAER_RMERR"); } } @@ -700,7 +717,7 @@ public void testDatabaseRemovesPreparedBeforeCommit() throws Exception { * Resource manager can't expect state of the {@link Xid}. */ @Test - public void testDatabaseRemovesPreparedBeforeRollback() throws Exception { + void databaseRemovesPreparedBeforeRollback() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -712,8 +729,7 @@ public void testDatabaseRemovesPreparedBeforeRollback() throws Exception { xaRes.rollback(xid); fail("Rollback is expected to fail as committed xid was removed before"); } catch (XAException xae) { - assertEquals("Rollback call on xid " + xid + " not known to DB expects XAER_RMERR", - XAException.XAER_RMERR, xae.errorCode); + assertEquals(XAException.XAER_RMERR, xae.errorCode, "Rollback call on xid " + xid + " not known to DB expects XAER_RMERR"); } } @@ -722,7 +738,7 @@ public void testDatabaseRemovesPreparedBeforeRollback() throws Exception { * {@link XAException} error code {@link XAException#XAER_RMFAIL} is expected. */ @Test - public void testNetworkIssueOnCommit() throws Exception { + void networkIssueOnCommit() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -734,8 +750,7 @@ public void testNetworkIssueOnCommit() throws Exception { xaRes.commit(xid, false); fail("Commit is expected to fail as connection was closed"); } catch (XAException xae) { - assertEquals("Commit call on closed connection expects XAER_RMFAIL", - XAException.XAER_RMFAIL, xae.errorCode); + assertEquals(XAException.XAER_RMFAIL, xae.errorCode, "Commit call on closed connection expects XAER_RMFAIL"); } } @@ -744,7 +759,7 @@ public void testNetworkIssueOnCommit() throws Exception { * {@link XAException} error code {@link XAException#XAER_RMFAIL} is expected. */ @Test - public void testNetworkIssueOnOnePhaseCommit() throws Exception { + void networkIssueOnOnePhaseCommit() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -755,8 +770,7 @@ public void testNetworkIssueOnOnePhaseCommit() throws Exception { xaRes.commit(xid, true); fail("One-phase commit is expected to fail as connection was closed"); } catch (XAException xae) { - assertEquals("One-phase commit call on closed connection expects XAER_RMFAIL", - XAException.XAER_RMFAIL, xae.errorCode); + assertEquals(XAException.XAER_RMFAIL, xae.errorCode, "One-phase commit call on closed connection expects XAER_RMFAIL"); } } @@ -765,7 +779,7 @@ public void testNetworkIssueOnOnePhaseCommit() throws Exception { * {@link XAException} error code {@link XAException#XAER_RMFAIL} is expected. */ @Test - public void testNetworkIssueOnRollback() throws Exception { + void networkIssueOnRollback() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); xaRes.end(xid, XAResource.TMSUCCESS); @@ -777,17 +791,16 @@ public void testNetworkIssueOnRollback() throws Exception { xaRes.rollback(xid); fail("Rollback is expected to fail as connection was closed"); } catch (XAException xae) { - assertEquals("Rollback call on closed connection expects XAER_RMFAIL", - XAException.XAER_RMFAIL, xae.errorCode); + assertEquals(XAException.XAER_RMFAIL, xae.errorCode, "Rollback call on closed connection expects XAER_RMFAIL"); } } /** - * When using deferred constraints a contraint violation can occur on prepare. This has to be + * When using deferred constraints a constraint violation can occur on prepare. This has to be * mapped to the correct XA Error Code */ @Test - public void testMappingOfConstraintViolations() throws Exception { + void mappingOfConstraintViolations() throws Exception { Xid xid = new CustomXid(1); xaRes.start(xid, XAResource.TMNOFLAGS); assertEquals(0, conn.createStatement().executeUpdate("SET CONSTRAINTS ALL DEFERRED")); @@ -799,8 +812,7 @@ public void testMappingOfConstraintViolations() throws Exception { fail("Prepare is expected to fail as an integrity violation occurred"); } catch (XAException xae) { - assertEquals("Prepare call with deferred constraints violations expects XA_RBINTEGRITY", - XAException.XA_RBINTEGRITY, xae.errorCode); + assertEquals(XAException.XA_RBINTEGRITY, xae.errorCode, "Prepare call with deferred constraints violations expects XA_RBINTEGRITY"); } } diff --git a/src/test/java/org/postgresql/util/Await.java b/src/test/java/org/postgresql/util/Await.java new file mode 100644 index 0000000..c40c44c --- /dev/null +++ b/src/test/java/org/postgresql/util/Await.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +/* changes were made to move it into the org.postgresql.util package + * + * Copyright 2022 Juan Lopes + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.postgresql.util; + +import java.time.Duration; + +public class Await { + public static void until(String message, Duration timeout, Condition condition) throws InterruptedException { + long deadline = System.currentTimeMillis() + timeout.toMillis(); + while (!condition.get()) { + if (System.currentTimeMillis() > deadline) { + throw new AssertionError("Condition not met within " + timeout + ": " + message); + } + Thread.sleep(100); + } + } + + public interface Condition { + boolean get(); + } +} diff --git a/src/test/java/org/postgresql/util/BigDecimalByteConverterTest.java b/src/test/java/org/postgresql/util/BigDecimalByteConverterTest.java index 97b08e4..2d6e6e7 100644 --- a/src/test/java/org/postgresql/util/BigDecimalByteConverterTest.java +++ b/src/test/java/org/postgresql/util/BigDecimalByteConverterTest.java @@ -5,12 +5,11 @@ package org.postgresql.util; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import java.math.BigDecimal; import java.math.BigInteger; @@ -21,59 +20,71 @@ * * @author Brett Okken */ -@RunWith(Parameterized.class) public class BigDecimalByteConverterTest { - - @Parameter - public BigDecimal number; - - @Parameterized.Parameters(name = "number = {0,number,#,###.##################################################}") public static Iterable data() { - final Collection numbers = new ArrayList(); - numbers.add(new Object[] {new BigDecimal("1.0")}); - numbers.add(new Object[] {new BigDecimal("0.000000000000000000000000000000000000000000000000000")}); - numbers.add(new Object[] {new BigDecimal("0.100000000000000000000000000000000000000000000009900")}); - numbers.add(new Object[] {new BigDecimal("-1.0")}); - numbers.add(new Object[] {new BigDecimal("-1")}); - numbers.add(new Object[] {new BigDecimal("1.2")}); - numbers.add(new Object[] {new BigDecimal("-2.05")}); - numbers.add(new Object[] {new BigDecimal("0.000000000000000000000000000990")}); - numbers.add(new Object[] {new BigDecimal("-0.000000000000000000000000000990")}); - numbers.add(new Object[] {new BigDecimal("10.0000000000099")}); - numbers.add(new Object[] {new BigDecimal(".10000000000000")}); - numbers.add(new Object[] {new BigDecimal("1.10000000000000")}); - numbers.add(new Object[] {new BigDecimal("99999.2")}); - numbers.add(new Object[] {new BigDecimal("99999")}); - numbers.add(new Object[] {new BigDecimal("-99999.2")}); - numbers.add(new Object[] {new BigDecimal("-99999")}); - numbers.add(new Object[] {new BigDecimal("2147483647")}); - numbers.add(new Object[] {new BigDecimal("-2147483648")}); - numbers.add(new Object[] {new BigDecimal("2147483648")}); - numbers.add(new Object[] {new BigDecimal("-2147483649")}); - numbers.add(new Object[] {new BigDecimal("9223372036854775807")}); - numbers.add(new Object[] {new BigDecimal("-9223372036854775808")}); - numbers.add(new Object[] {new BigDecimal("9223372036854775808")}); - numbers.add(new Object[] {new BigDecimal("-9223372036854775809")}); - numbers.add(new Object[] {new BigDecimal("10223372036850000000")}); - numbers.add(new Object[] {new BigDecimal("19223372036854775807")}); - numbers.add(new Object[] {new BigDecimal("19223372036854775807.300")}); - numbers.add(new Object[] {new BigDecimal("-19223372036854775807.300")}); - numbers.add(new Object[] {new BigDecimal(BigInteger.valueOf(1234567890987654321L), -1)}); - numbers.add(new Object[] {new BigDecimal(BigInteger.valueOf(1234567890987654321L), -5)}); - numbers.add(new Object[] {new BigDecimal(BigInteger.valueOf(-1234567890987654321L), -3)}); - numbers.add(new Object[] {new BigDecimal(BigInteger.valueOf(6), -8)}); - numbers.add(new Object[] {new BigDecimal("30000")}); - numbers.add(new Object[] {new BigDecimal("40000").setScale(15)}); - numbers.add(new Object[] {new BigDecimal("20000.000000000000000000")}); - numbers.add(new Object[] {new BigDecimal("9990000").setScale(8)}); - numbers.add(new Object[] {new BigDecimal("1000000").setScale(31)}); - numbers.add(new Object[] {new BigDecimal("10000000000000000000000000000000000000").setScale(14)}); - numbers.add(new Object[] {new BigDecimal("90000000000000000000000000000000000000")}); + final Collection numbers = new ArrayList<>(); + numbers.add(new Object[]{new BigDecimal("0.1")}); + numbers.add(new Object[]{new BigDecimal("0.10")}); + numbers.add(new Object[]{new BigDecimal("0.01")}); + numbers.add(new Object[]{new BigDecimal("0.001")}); + numbers.add(new Object[]{new BigDecimal("0.0001")}); + numbers.add(new Object[]{new BigDecimal("0.00001")}); + numbers.add(new Object[]{new BigDecimal("1.0")}); + numbers.add(new Object[]{new BigDecimal("0.000000000000000000000000000000000000000000000000000")}); + numbers.add(new Object[]{new BigDecimal("0.100000000000000000000000000000000000000000000009900")}); + numbers.add(new Object[]{new BigDecimal("-1.0")}); + numbers.add(new Object[]{new BigDecimal("-1")}); + numbers.add(new Object[]{new BigDecimal("1.2")}); + numbers.add(new Object[]{new BigDecimal("-2.05")}); + numbers.add(new Object[]{new BigDecimal("0.000000000000000000000000000990")}); + numbers.add(new Object[]{new BigDecimal("-0.000000000000000000000000000990")}); + numbers.add(new Object[]{new BigDecimal("10.0000000000099")}); + numbers.add(new Object[]{new BigDecimal(".10000000000000")}); + numbers.add(new Object[]{new BigDecimal("1.10000000000000")}); + numbers.add(new Object[]{new BigDecimal("99999.2")}); + numbers.add(new Object[]{new BigDecimal("99999")}); + numbers.add(new Object[]{new BigDecimal("-99999.2")}); + numbers.add(new Object[]{new BigDecimal("-99999")}); + numbers.add(new Object[]{new BigDecimal("2147483647")}); + numbers.add(new Object[]{new BigDecimal("-2147483648")}); + numbers.add(new Object[]{new BigDecimal("2147483648")}); + numbers.add(new Object[]{new BigDecimal("-2147483649")}); + numbers.add(new Object[]{new BigDecimal("9223372036854775807")}); + numbers.add(new Object[]{new BigDecimal("-9223372036854775808")}); + numbers.add(new Object[]{new BigDecimal("9223372036854775808")}); + numbers.add(new Object[]{new BigDecimal("-9223372036854775809")}); + numbers.add(new Object[]{new BigDecimal("10223372036850000000")}); + numbers.add(new Object[]{new BigDecimal("19223372036854775807")}); + numbers.add(new Object[]{new BigDecimal("19223372036854775807.300")}); + numbers.add(new Object[]{new BigDecimal("-19223372036854775807.300")}); + numbers.add(new Object[]{new BigDecimal(BigInteger.valueOf(1234567890987654321L), -1)}); + numbers.add(new Object[]{new BigDecimal(BigInteger.valueOf(1234567890987654321L), -5)}); + numbers.add(new Object[]{new BigDecimal(BigInteger.valueOf(-1234567890987654321L), -3)}); + numbers.add(new Object[]{new BigDecimal(BigInteger.valueOf(6), -8)}); + numbers.add(new Object[]{new BigDecimal("30000")}); + numbers.add(new Object[]{new BigDecimal("40000").setScale(15)}); + numbers.add(new Object[]{new BigDecimal("20000.000000000000000000")}); + numbers.add(new Object[]{new BigDecimal("9990000").setScale(8)}); + numbers.add(new Object[]{new BigDecimal("1000000").setScale(31)}); + numbers.add(new Object[]{new BigDecimal("10000000000000000000000000000000000000").setScale(14)}); + numbers.add(new Object[]{new BigDecimal("90000000000000000000000000000000000000")}); return numbers; } + @MethodSource("data") + @ParameterizedTest(name = "number = {0,number,#,###.##################################################}") + void binary(BigDecimal number) { + testBinaryConversion(number); + } + @Test - public void testBinary() { + void bigDecimal10_pow_131072_minus_1() { + testBinaryConversion( + new BigDecimal(BigInteger.TEN.pow(131072).subtract(BigInteger.ONE)) + ); + } + + static void testBinaryConversion(BigDecimal number) { final byte[] bytes = ByteConverter.numeric(number); final BigDecimal actual = (BigDecimal) ByteConverter.numeric(bytes); if (number.scale() >= 0) { @@ -82,4 +93,14 @@ public void testBinary() { assertEquals(number.toPlainString(), actual.toPlainString()); } } + + @Test + void testSpecialNumericValues() { + Number nan = ByteConverter.numeric(new byte[]{0, 0, 0, 0, (byte) 0xC0, 0, 0, 0}); + assertEquals(Double.NaN, nan); + Number pinf = ByteConverter.numeric(new byte[]{0, 0, 0, 0, (byte) 0xD0, 0, 0, 0}); + assertEquals(Double.POSITIVE_INFINITY, pinf); + Number ninf = ByteConverter.numeric(new byte[]{0, 0, 0, 0, (byte) 0xF0, 0, 0, 0}); + assertEquals(Double.NEGATIVE_INFINITY, ninf); + } } diff --git a/src/test/java/org/postgresql/util/IntListTest.java b/src/test/java/org/postgresql/util/IntListTest.java new file mode 100644 index 0000000..e7d1451 --- /dev/null +++ b/src/test/java/org/postgresql/util/IntListTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link IntList}. + */ +class IntListTest { + + @Test + void size() { + final IntList list = new IntList(); + assertEquals(0, list.size()); + list.add(3); + assertEquals(1, list.size()); + + for (int i = 0; i < 48; i++) { + list.add(i); + } + assertEquals(49, list.size()); + + list.clear(); + assertEquals(0, list.size()); + } + + @Test + void get_empty() { + final IntList list = new IntList(); + assertThrows(ArrayIndexOutOfBoundsException.class, () -> list.get(0)); + } + + @Test + void get_negative() { + final IntList list = new IntList(); + list.add(3); + assertThrows(ArrayIndexOutOfBoundsException.class, () -> list.get(-1)); + } + + @Test + void get_tooLarge() { + final IntList list = new IntList(); + list.add(3); + assertThrows(ArrayIndexOutOfBoundsException.class, () -> list.get(1)); + } + + @Test + void get() { + final IntList list = new IntList(); + list.add(3); + assertEquals(3, list.get(0)); + + for (int i = 0; i < 1048; i++) { + list.add(i); + } + + assertEquals(3, list.get(0)); + + for (int i = 0; i < 1048; i++) { + assertEquals(i, list.get(i + 1)); + } + + list.clear(); + list.add(4); + assertEquals(4, list.get(0)); + } + + @Test + void toArray() { + int[] emptyArray = new IntList().toArray(); + IntList list = new IntList(); + assertSame(emptyArray, list.toArray(), "emptyList.toArray()"); + + list.add(45); + assertArrayEquals(new int[]{45}, list.toArray()); + + list.clear(); + assertSame(emptyArray, list.toArray(), "emptyList.toArray() after clearing the list"); + + final int[] expected = new int[1048]; + for (int i = 0; i < 1048; i++) { + list.add(i); + expected[i] = i; + } + assertArrayEquals(expected, list.toArray()); + } +} diff --git a/src/test/java/org/postgresql/util/LazyCleanerTest.java b/src/test/java/org/postgresql/util/LazyCleanerTest.java new file mode 100644 index 0000000..9ba1850 --- /dev/null +++ b/src/test/java/org/postgresql/util/LazyCleanerTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +/* changes were made to move it into the org.postgresql.util package + * + * Copyright 2022 Juan Lopes + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.postgresql.util; + +import static java.time.Duration.ofSeconds; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.core.JavaVersion; +import org.postgresql.test.annotations.DisableLogger; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +public class LazyCleanerTest { + @Test + @DisableLogger(LazyCleanerImpl.class) + void phantomCleaner() throws InterruptedException { + List list = new ArrayList<>(Arrays.asList( + new Object(), new Object(), new Object())); + + LazyCleanerImpl t = new LazyCleanerImpl("Cleaner", ofSeconds(5)); + + String[] collected = new String[list.size()]; + List> cleaners = new ArrayList<>(); + for (int i = 0; i < list.size(); i++) { + final int ii = i; + cleaners.add( + t.register( + list.get(i), + leak -> { + collected[ii] = leak ? "LEAK" : "NO LEAK"; + if (ii == 0) { + throw new RuntimeException( + "Exception from cleanup action to verify if the cleaner thread would survive" + ); + } + } + ) + ); + } + + if (JavaVersion.getRuntimeVersion() == JavaVersion.v1_8) { + assertTrue(t.isThreadRunning(), + "cleanup thread should be running, and it should wait for the leaks"); + } + + cleaners.get(1).clean(); + + list.set(0, null); + System.gc(); + System.gc(); + + list.clear(); + System.gc(); + System.gc(); + + if (JavaVersion.getRuntimeVersion() == JavaVersion.v1_8) { + Await.until( + "The cleanup thread should detect leaks and terminate within 5-10 seconds after GC", + ofSeconds(10), + () -> !t.isThreadRunning() + ); + } else { + // Allow some room for Java's Cleaner to clean the refs + Thread.sleep(1000); + } + + assertEquals( + Arrays.asList("LEAK", "NO LEAK", "LEAK").toString(), + Arrays.asList(collected).toString(), + "Second object has been released properly, so it should be reported as NO LEAK" + ); + } + + @Test + void cleanupCompletesAfterManualClean() throws InterruptedException { + String threadName = UUID.randomUUID().toString(); + LazyCleanerImpl t = new LazyCleanerImpl(threadName, ofSeconds(5)); + + AtomicBoolean cleaned = new AtomicBoolean(); + List list = new ArrayList<>(); + list.add(new Object()); + + LazyCleaner.Cleanable cleanable = + t.register( + list.get(0), + leak -> cleaned.set(true) + ); + + if (JavaVersion.getRuntimeVersion() == JavaVersion.v1_8) { + assertTrue(t.isThreadRunning(), + "cleanup thread should be running when there are objects to monitor"); + } + + // Manually clean the object + cleanable.clean(); + + // Verify it was cleaned + assertTrue(cleaned.get(), "Object should be cleaned after manual clean"); + + // Clear the reference and verify cleanup thread eventually stops + list.clear(); + System.gc(); + System.gc(); + + if (JavaVersion.getRuntimeVersion() == JavaVersion.v1_8) { + Await.until( + "Cleanup thread should stop when no objects remain", + ofSeconds(10), + () -> !t.isThreadRunning() + ); + } + } + + @Test + @DisableLogger(LazyCleanerImpl.class) + void exceptionsDuringCleanupAreHandled() throws InterruptedException { + LazyCleanerImpl t = new LazyCleanerImpl("test-cleaner", ofSeconds(5)); + + java.util.concurrent.atomic.AtomicInteger cleanupCount = new java.util.concurrent.atomic.AtomicInteger(0); + List list = new ArrayList<>(); + + // Register object that throws during cleanup + list.add(new Object()); + t.register( + list.get(0), + leak -> { + cleanupCount.incrementAndGet(); + throw new IllegalStateException("test exception from CleaningAction"); + } + ); + + // Register another object that should still be cleaned + list.add(new Object()); + AtomicBoolean secondCleaned = new AtomicBoolean(false); + t.register( + list.get(1), + leak -> secondCleaned.set(true) + ); + + if (JavaVersion.getRuntimeVersion() == JavaVersion.v1_8) { + assertTrue(t.isThreadRunning(), + "cleanup thread should be running when there are objects to monitor"); + } + + // Trigger cleanup + list.clear(); + System.gc(); + System.gc(); + + Await.until( + "Both cleanups should complete despite exception", + ofSeconds(10), + () -> cleanupCount.get() == 1 && secondCleaned.get() + ); + + if (JavaVersion.getRuntimeVersion() == JavaVersion.v1_8) { + Await.until( + "Cleanup thread should stop after all objects are cleaned", + ofSeconds(10), + () -> !t.isThreadRunning() + ); + } + } + + @Test + void exceptionOnCleanRethrowsToCaller() { + LazyCleanerImpl t = new LazyCleanerImpl("test-cleaner", ofSeconds(5)); + Object obj = new Object(); + RuntimeException expectedException = new RuntimeException("test exception"); + + LazyCleaner.Cleanable cleanable = t.register(obj, leak -> { + throw expectedException; + }); + + RuntimeException thrownException = assertThrows(RuntimeException.class, cleanable::clean); + assertSame(expectedException, thrownException, "Expected same exception instance to be thrown"); + } +} diff --git a/src/test/java/org/postgresql/util/NumberParserTest.java b/src/test/java/org/postgresql/util/NumberParserTest.java new file mode 100644 index 0000000..ebe2824 --- /dev/null +++ b/src/test/java/org/postgresql/util/NumberParserTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +class NumberParserTest { + @Test + void getFastLong_normalLongs() { + List tests = new ArrayList<>(); + for (long base : new long[]{0, 42, 65536, -65536, Long.MAX_VALUE}) { + for (int diff = -10; diff <= 10; diff++) { + tests.add(base + diff); + } + } + + for (Long test : tests) { + assertGetLongResult(Long.toString(test), test); + } + } + + @Test + void getFastLong_discardsFractionalPart() { + assertGetLongResult("234.435", 234); + assertGetLongResult("-234234.", -234234); + } + + @Test + void getFastLong_failOnIncorrectStrings() { + assertGetLongFail(""); + assertGetLongFail("-234.12542."); + assertGetLongFail("."); + assertGetLongFail("-."); + assertGetLongFail(Long.toString(Long.MIN_VALUE).substring(1)); + } + + private static void assertGetLongResult(String s, long expected) { + try { + assertEquals( + expected, + NumberParser.getFastLong(s.getBytes(), Long.MIN_VALUE, Long.MAX_VALUE), + "string \"" + s + "\" parsed well to number " + expected + ); + } catch (NumberFormatException nfe) { + fail("failed to parse(NumberFormatException) string \"" + s + "\", expected result " + expected); + } + } + + private static void assertGetLongFail(String s) { + try { + long ret = NumberParser.getFastLong(s.getBytes(), Long.MIN_VALUE, Long.MAX_VALUE); + fail("Expected NumberFormatException on parsing \"" + s + "\", but result: " + ret); + } catch (NumberFormatException nfe) { + // ok + } + } +} diff --git a/src/test/java/org/postgresql/util/OSUtilTest.java b/src/test/java/org/postgresql/util/OSUtilTest.java new file mode 100644 index 0000000..6c020e8 --- /dev/null +++ b/src/test/java/org/postgresql/util/OSUtilTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import uk.org.webcompere.systemstubs.environment.EnvironmentVariables; +import uk.org.webcompere.systemstubs.properties.SystemProperties; +import uk.org.webcompere.systemstubs.resource.Resources; + +import java.io.File; + +@StubEnvironmentAndProperties +class OSUtilTest { + + @Test + void getUserConfigRootDirectory() throws Exception { + // windows + Resources.with(new EnvironmentVariables("APPDATA", "C:\\Users\\realuser\\AppData\\Roaming"), + new SystemProperties("os.name", "Windows 10")).execute(() -> { + String result = OSUtil.getUserConfigRootDirectory(); + assertEquals("C:\\Users\\realuser\\AppData\\Roaming" + File.separator + "postgresql", result); + } + ); + // linux + Resources.with(new SystemProperties("os.name", "Linux", "user.home", "/home/realuser")).execute(() -> { + String result = OSUtil.getUserConfigRootDirectory(); + assertEquals("/home/realuser", result); + } + ); + } +} diff --git a/src/test/java/org/postgresql/util/PGPropertyUtilTest.java b/src/test/java/org/postgresql/util/PGPropertyUtilTest.java new file mode 100644 index 0000000..db0af36 --- /dev/null +++ b/src/test/java/org/postgresql/util/PGPropertyUtilTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.PGProperty; +import org.postgresql.test.annotations.DisableLogger; + +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +class PGPropertyUtilTest { + + @Test + void propertiesConsistencyCheck() { + // PGPORT + Properties properties = new Properties(); + PGProperty.PG_PORT.set(properties, "1"); + assertTrue(PGPropertyUtil.propertiesConsistencyCheck(properties)); + PGProperty.PG_PORT.set(properties, "5432"); + assertTrue(PGPropertyUtil.propertiesConsistencyCheck(properties)); + PGProperty.PG_PORT.set(properties, "65535"); + assertTrue(PGPropertyUtil.propertiesConsistencyCheck(properties)); + // any other not handled + properties = new Properties(); + properties.setProperty("not-handled-key", "not-handled-value"); + assertTrue(PGPropertyUtil.propertiesConsistencyCheck(properties)); + } + + @Test + @DisableLogger(PGPropertyUtil.class) + void invalidPortCheck() { + // PGPORT + Properties properties = new Properties(); + PGProperty.PG_PORT.set(properties, "0"); + assertFalse(PGPropertyUtil.propertiesConsistencyCheck(properties)); + PGProperty.PG_PORT.set(properties, "65536"); + assertFalse(PGPropertyUtil.propertiesConsistencyCheck(properties)); + PGProperty.PG_PORT.set(properties, "abcdef"); + assertFalse(PGPropertyUtil.propertiesConsistencyCheck(properties)); + } + + // data for next two test methods + private static final String[][] TRANSLATION_TABLE = { + {"allowEncodingChanges", "allowEncodingChanges"}, + {"port", "PGPORT"}, + {"host", "PGHOST"}, + {"dbname", "PGDBNAME"}, + }; + + @Test + void translatePGServiceToPGProperty() { + for (String[] row : TRANSLATION_TABLE) { + assertEquals(row[1], PGPropertyUtil.translatePGServiceToPGProperty(row[0])); + } + } + + @Test + void translatePGPropertyToPGService() { + for (String[] row : TRANSLATION_TABLE) { + assertEquals(row[0], PGPropertyUtil.translatePGPropertyToPGService(row[1])); + } + } +} diff --git a/src/test/java/org/postgresql/util/PGbyteaTest.java b/src/test/java/org/postgresql/util/PGbyteaTest.java index bb6ab00..3a32f1d 100644 --- a/src/test/java/org/postgresql/util/PGbyteaTest.java +++ b/src/test/java/org/postgresql/util/PGbyteaTest.java @@ -5,22 +5,22 @@ package org.postgresql.util; -import static org.junit.Assert.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.sql.SQLException; import java.util.Random; -public class PGbyteaTest { +class PGbyteaTest { - private static final byte[] HEX_DIGITS_U = new byte[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', - 'C', 'D', 'E', 'F' }; - private static final byte[] HEX_DIGITS_L = new byte[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', - 'c', 'd', 'e', 'f' }; + private static final byte[] HEX_DIGITS_U = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', + 'C', 'D', 'E', 'F'}; + private static final byte[] HEX_DIGITS_L = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f'}; @Test - public void testHexDecode_lower() throws SQLException { + void hexDecode_lower() throws SQLException { final byte[] data = new byte[1023]; new Random(7).nextBytes(data); final byte[] encoded = hexEncode(data, HEX_DIGITS_L); @@ -29,7 +29,7 @@ public void testHexDecode_lower() throws SQLException { } @Test - public void testHexDecode_upper() throws SQLException { + void hexDecode_upper() throws SQLException { final byte[] data = new byte[9513]; new Random(-8).nextBytes(data); final byte[] encoded = hexEncode(data, HEX_DIGITS_U); @@ -44,7 +44,7 @@ private static byte[] hexEncode(byte[] data, byte[] hexDigits) { final byte[] encoded = new byte[2 + (data.length << 1)]; encoded[0] = '\\'; encoded[1] = 'x'; - for (int i = 0; i < data.length; ++i) { + for (int i = 0; i < data.length; i++) { final int idx = (i << 1) + 2; final byte b = data[i]; encoded[idx] = hexDigits[(b & 0xF0) >>> 4]; diff --git a/src/test/java/org/postgresql/util/PGtokenizerTest.java b/src/test/java/org/postgresql/util/PGtokenizerTest.java index fc66157..26926ff 100644 --- a/src/test/java/org/postgresql/util/PGtokenizerTest.java +++ b/src/test/java/org/postgresql/util/PGtokenizerTest.java @@ -7,39 +7,38 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Assert; import org.junit.jupiter.api.Test; class PGtokenizerTest { @Test void tokenize() { - PGtokenizer pGtokenizer = new PGtokenizer("1,2EC1830300027,1,,",','); - assertEquals(5,pGtokenizer.getSize()); + PGtokenizer pGtokenizer = new PGtokenizer("1,2EC1830300027,1,,", ','); + assertEquals(5, pGtokenizer.getSize()); } @Test void tokenize2() { PGtokenizer pGtokenizer = new PGtokenizer(",,d,\"f(10\",\"(mime,pdf,pdf)\",test,2018-10-11,1010", ','); - assertEquals(8,pGtokenizer.getSize()); + assertEquals(8, pGtokenizer.getSize()); } @Test void tokenize3() { PGtokenizer pGtokenizer = new PGtokenizer(",,d,\"f)10\",\"(mime,pdf,pdf)\",test,2018-10-11,1010", ','); - assertEquals(8,pGtokenizer.getSize()); + assertEquals(8, pGtokenizer.getSize()); } @Test void tokenize4() { PGtokenizer pGtokenizer = new PGtokenizer(",,d,\"f()10\",\"(mime,pdf,pdf)\",test,2018-10-11,1010", ','); - assertEquals(8,pGtokenizer.getSize()); + assertEquals(8, pGtokenizer.getSize()); } @Test void removePara() { String string = PGtokenizer.removePara("(1,2EC1830300027,1,,)"); - Assert.assertEquals("1,2EC1830300027,1,,", string); + assertEquals("1,2EC1830300027,1,,", string); } } diff --git a/src/test/java/org/postgresql/util/ReaderInputStreamTest.java b/src/test/java/org/postgresql/util/ReaderInputStreamTest.java index ec33583..cf33a98 100644 --- a/src/test/java/org/postgresql/util/ReaderInputStreamTest.java +++ b/src/test/java/org/postgresql/util/ReaderInputStreamTest.java @@ -5,9 +5,10 @@ package org.postgresql.util; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; import java.io.CharArrayReader; @@ -19,7 +20,7 @@ import java.nio.charset.MalformedInputException; import java.util.Arrays; -public class ReaderInputStreamTest { +class ReaderInputStreamTest { // 132878 = U+2070E - chosen because it is the first supplementary character // in the International Ideographic Core (IICore) // see http://www.i18nguy.com/unicode/supplementary-test.html for further explanation @@ -30,15 +31,19 @@ public class ReaderInputStreamTest { // Character.lowSurrogate(132878) = 0xdf0e private static final char TRAILING_SURROGATE = 0xdf0e; - @Test(expected = IllegalArgumentException.class) + @Test @SuppressWarnings("nullability") - public void NullReaderTest() { - new ReaderInputStream(null); + void NullReaderTest() { + assertThrows(IllegalArgumentException.class, () -> { + new ReaderInputStream(null); + }); } - @Test(expected = IllegalArgumentException.class) - public void cbufTooSmallReaderTest() { - new ReaderInputStream(new StringReader("abc"), 1); + @Test + void cbufTooSmallReaderTest() { + assertThrows(IllegalArgumentException.class, () -> { + new ReaderInputStream(new StringReader("abc"), 1); + }); } private static void read(InputStream is, int... expected) throws IOException { @@ -54,13 +59,13 @@ private static void read(InputStream is, int... expected) throws IOException { expected = Arrays.copyOf(expected, 4); assertEquals(Arrays.toString(expected), Arrays.toString(actualInts)); } else { - assertEquals("should be end-of-stream", -1, nActual); + assertEquals(-1, nActual, "should be end-of-stream"); is.close(); } } @Test - public void SimpleTest() throws IOException { + void SimpleTest() throws IOException { char[] chars = {'a', 'b', 'c'}; Reader reader = new CharArrayReader(chars); InputStream is = new ReaderInputStream(reader); @@ -69,7 +74,7 @@ public void SimpleTest() throws IOException { } @Test - public void inputSmallerThanCbufsizeTest() throws IOException { + void inputSmallerThanCbufsizeTest() throws IOException { char[] chars = {'a'}; Reader reader = new CharArrayReader(chars); InputStream is = new ReaderInputStream(reader, 2); @@ -78,19 +83,19 @@ public void inputSmallerThanCbufsizeTest() throws IOException { } @Test - public void tooManyReadsTest() throws IOException { + void tooManyReadsTest() throws IOException { char[] chars = {'a'}; Reader reader = new CharArrayReader(chars); InputStream is = new ReaderInputStream(reader, 2); read(is, 0x61); - assertEquals("should be end-of-stream", -1, is.read()); - assertEquals("should be end-of-stream", -1, is.read()); - assertEquals("should be end-of-stream", -1, is.read()); + assertEquals(-1, is.read(), "should be end-of-stream"); + assertEquals(-1, is.read(), "should be end-of-stream"); + assertEquals(-1, is.read(), "should be end-of-stream"); is.close(); } @Test - public void surrogatePairSpansCharBufBoundaryTest() throws IOException { + void surrogatePairSpansCharBufBoundaryTest() throws IOException { char[] chars = {'a', LEADING_SURROGATE, TRAILING_SURROGATE}; Reader reader = new CharArrayReader(chars); InputStream is = new ReaderInputStream(reader, 2); @@ -99,95 +104,113 @@ public void surrogatePairSpansCharBufBoundaryTest() throws IOException { read(is); } - @Test(expected = MalformedInputException.class) - public void invalidInputTest() throws IOException { - char[] chars = {'a', LEADING_SURROGATE, LEADING_SURROGATE}; - Reader reader = new CharArrayReader(chars); - InputStream is = new ReaderInputStream(reader, 2); - read(is); + @Test + void invalidInputTest() throws IOException { + assertThrows(MalformedInputException.class, () -> { + char[] chars = {'a', LEADING_SURROGATE, LEADING_SURROGATE}; + Reader reader = new CharArrayReader(chars); + InputStream is = new ReaderInputStream(reader, 2); + read(is); + }); } - @Test(expected = MalformedInputException.class) - public void unmatchedLeadingSurrogateInputTest() throws IOException { - char[] chars = {LEADING_SURROGATE}; - Reader reader = new CharArrayReader(chars); - InputStream is = new ReaderInputStream(reader, 2); - read(is, 0x00); + @Test + void unmatchedLeadingSurrogateInputTest() throws IOException { + assertThrows(MalformedInputException.class, () -> { + char[] chars = {LEADING_SURROGATE}; + Reader reader = new CharArrayReader(chars); + InputStream is = new ReaderInputStream(reader, 2); + read(is, 0x00); + }); } - @Test(expected = MalformedInputException.class) - public void unmatchedTrailingSurrogateInputTest() throws IOException { - char[] chars = {TRAILING_SURROGATE}; - Reader reader = new CharArrayReader(chars); - InputStream is = new ReaderInputStream(reader, 2); - read(is); + @Test + void unmatchedTrailingSurrogateInputTest() throws IOException { + assertThrows(MalformedInputException.class, () -> { + char[] chars = {TRAILING_SURROGATE}; + Reader reader = new CharArrayReader(chars); + InputStream is = new ReaderInputStream(reader, 2); + read(is); + }); } - @Test(expected = NullPointerException.class) + @Test @SuppressWarnings("nullness") - public void nullArrayReadTest() throws IOException { - Reader reader = new StringReader("abc"); - InputStream is = new ReaderInputStream(reader); - is.read(null, 0, 4); + void nullArrayReadTest() throws IOException { + assertThrows(NullPointerException.class, () -> { + Reader reader = new StringReader("abc"); + InputStream is = new ReaderInputStream(reader); + is.read(null, 0, 4); + }); } - @Test(expected = IndexOutOfBoundsException.class) - public void invalidOffsetArrayReadTest() throws IOException { - Reader reader = new StringReader("abc"); - InputStream is = new ReaderInputStream(reader); - byte[] bytes = new byte[4]; - is.read(bytes, 5, 4); + @Test + void invalidOffsetArrayReadTest() throws IOException { + assertThrows(IndexOutOfBoundsException.class, () -> { + Reader reader = new StringReader("abc"); + InputStream is = new ReaderInputStream(reader); + byte[] bytes = new byte[4]; + is.read(bytes, 5, 4); + }); } - @Test(expected = IndexOutOfBoundsException.class) - public void negativeOffsetArrayReadTest() throws IOException { - Reader reader = new StringReader("abc"); - InputStream is = new ReaderInputStream(reader); - byte[] bytes = new byte[4]; - is.read(bytes, -1, 4); + @Test + void negativeOffsetArrayReadTest() throws IOException { + assertThrows(IndexOutOfBoundsException.class, () -> { + Reader reader = new StringReader("abc"); + InputStream is = new ReaderInputStream(reader); + byte[] bytes = new byte[4]; + is.read(bytes, -1, 4); + }); } - @Test(expected = IndexOutOfBoundsException.class) - public void invalidLengthArrayReadTest() throws IOException { - Reader reader = new StringReader("abc"); - InputStream is = new ReaderInputStream(reader); - byte[] bytes = new byte[4]; - is.read(bytes, 1, 4); + @Test + void invalidLengthArrayReadTest() throws IOException { + assertThrows(IndexOutOfBoundsException.class, () -> { + Reader reader = new StringReader("abc"); + InputStream is = new ReaderInputStream(reader); + byte[] bytes = new byte[4]; + is.read(bytes, 1, 4); + }); } - @Test(expected = IndexOutOfBoundsException.class) - public void negativeLengthArrayReadTest() throws IOException { - Reader reader = new StringReader("abc"); - InputStream is = new ReaderInputStream(reader); - byte[] bytes = new byte[4]; - is.read(bytes, 1, -2); + @Test + void negativeLengthArrayReadTest() throws IOException { + assertThrows(IndexOutOfBoundsException.class, () -> { + Reader reader = new StringReader("abc"); + InputStream is = new ReaderInputStream(reader); + byte[] bytes = new byte[4]; + is.read(bytes, 1, -2); + }); } @Test - public void zeroLengthArrayReadTest() throws IOException { + void zeroLengthArrayReadTest() throws IOException { Reader reader = new StringReader("abc"); InputStream is = new ReaderInputStream(reader); byte[] bytes = new byte[4]; - assertEquals("requested 0 byte read", 0, is.read(bytes, 1, 0)); + assertEquals(0, is.read(bytes, 1, 0), "requested 0 byte read"); } @Test - public void singleCharArrayReadTest() throws IOException { + void singleCharArrayReadTest() throws IOException { Reader reader = new SingleCharPerReadReader(LEADING_SURROGATE, TRAILING_SURROGATE); InputStream is = new ReaderInputStream(reader); read(is, 0xF0, 0xA0, 0x9C, 0x8E); read(is); } - @Test(expected = MalformedInputException.class) - public void malformedSingleCharArrayReadTest() throws IOException { - Reader reader = new SingleCharPerReadReader(LEADING_SURROGATE, LEADING_SURROGATE); - InputStream is = new ReaderInputStream(reader); - read(is, 0xF0, 0xA0, 0x9C, 0x8E); + @Test + void malformedSingleCharArrayReadTest() throws IOException { + assertThrows(MalformedInputException.class, () -> { + Reader reader = new SingleCharPerReadReader(LEADING_SURROGATE, LEADING_SURROGATE); + InputStream is = new ReaderInputStream(reader); + read(is, 0xF0, 0xA0, 0x9C, 0x8E); + }); } @Test - public void readsEqualToBlockSizeTest() throws Exception { + void readsEqualToBlockSizeTest() throws Exception { final int blockSize = 8 * 1024; final int dataSize = blockSize + 57; final byte[] data = new byte[dataSize]; @@ -201,7 +224,7 @@ public void readsEqualToBlockSizeTest() throws Exception { total += r.read(buffer, 0, blockSize); total += r.read(buffer, 0, blockSize); - assertEquals("Data not read completely: missing " + (dataSize - total) + " bytes", dataSize, total); + assertEquals(dataSize, total, "Data not read completely: missing " + (dataSize - total) + " bytes"); } private static class SingleCharPerReadReader extends Reader { diff --git a/src/test/java/org/postgresql/util/StubEnvironmentAndProperties.java b/src/test/java/org/postgresql/util/StubEnvironmentAndProperties.java new file mode 100644 index 0000000..07a54ca --- /dev/null +++ b/src/test/java/org/postgresql/util/StubEnvironmentAndProperties.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Isolated; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to mark a test method as a test that should be run with stubbing system + * calls like {@code System#getProperty} and {@code System#getenv}. + * + *

          The tests should be run in isolation to prevent concurrent modification of properties and + * the environment.

          + * + *

          Note: environment mocking works from a single thread only until + * Fix multi-threaded + * environment variable mocking, and Mocked + * static methods are not available in other threads are resolved

          + */ +@Isolated +@ExtendWith(SystemStubsExtension.class) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.TYPE}) +public @interface StubEnvironmentAndProperties { +} diff --git a/src/test/java/org/postgresql/util/TestLogHandler.java b/src/test/java/org/postgresql/util/TestLogHandler.java index 6c71464..30850a1 100644 --- a/src/test/java/org/postgresql/util/TestLogHandler.java +++ b/src/test/java/org/postgresql/util/TestLogHandler.java @@ -14,7 +14,7 @@ import java.util.regex.Pattern; public class TestLogHandler extends Handler { - public Queue records = new ConcurrentLinkedQueue(); + public Queue records = new ConcurrentLinkedQueue<>(); @Override public void publish(LogRecord record) { @@ -30,7 +30,7 @@ public void close() throws SecurityException { } public List getRecordsMatching(Pattern messagePattern) { - List matches = new ArrayList(); + List matches = new ArrayList<>(); for (LogRecord r: this.records) { String message = r.getMessage(); if (message != null && messagePattern.matcher(message).find()) { diff --git a/src/test/java/org/postgresql/util/UnusualBigDecimalByteConverterTest.java b/src/test/java/org/postgresql/util/UnusualBigDecimalByteConverterTest.java index bb3c2cf..1cf056e 100644 --- a/src/test/java/org/postgresql/util/UnusualBigDecimalByteConverterTest.java +++ b/src/test/java/org/postgresql/util/UnusualBigDecimalByteConverterTest.java @@ -5,9 +5,9 @@ package org.postgresql.util; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.math.BigDecimal; @@ -15,18 +15,18 @@ * Tests unusual binary representations of numeric values. * @author Brett Okken */ -public class UnusualBigDecimalByteConverterTest { +class UnusualBigDecimalByteConverterTest { /** * Typically a number < 1 would have sections of leading '0' values represented in weight * rather than including as short values. */ @Test - public void test_4_leading_0() { + void test_4_leading_0() { //len 2 //weight -1 //scale 5 - final byte[] data = new byte[] {0, 2, -1, -1, 0, 0, 0, 5, 0, 0, 23, 112}; + final byte[] data = new byte[]{0, 2, -1, -1, 0, 0, 0, 5, 0, 0, 23, 112}; final BigDecimal actual = (BigDecimal) ByteConverter.numeric(data); assertEquals(new BigDecimal("0.00006"), actual); } diff --git a/src/test/java/org/postgresql/util/internal/IntSetTest.java b/src/test/java/org/postgresql/util/internal/IntSetTest.java new file mode 100644 index 0000000..e5226c6 --- /dev/null +++ b/src/test/java/org/postgresql/util/internal/IntSetTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util.internal; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.postgresql.core.Oid; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Arrays; +import java.util.List; + +public class IntSetTest { + private final IntSet intSet = new IntSet(); + + @ParameterizedTest + @ValueSource(ints = {Oid.UNSPECIFIED, Oid.INT4, Oid.BYTEA, Oid.VARCHAR, Integer.MIN_VALUE, + Integer.MAX_VALUE}) + void empty_contains_no_values(int oid) { + assertFalse(intSet.contains(oid), () -> "empty intset should not contain oid " + oid); + } + + @ParameterizedTest + @ValueSource(ints = {Oid.UNSPECIFIED, Oid.INT4, Oid.BYTEA, Oid.VARCHAR, Integer.MIN_VALUE, + Integer.MAX_VALUE}) + void contains_added_value(int oid) { + intSet.add(oid); + assertTrue(intSet.contains(oid), + () -> "intset should contain the oid that was just added: " + oid); + } + + @ParameterizedTest + @ValueSource(ints = {Oid.UNSPECIFIED, Oid.INT4, Oid.BYTEA, Oid.VARCHAR, Integer.MIN_VALUE, + Integer.MAX_VALUE}) + void clear_removes_elements(int oid) { + intSet.add(oid); + intSet.clear(); + assertFalse(intSet.contains(oid), + () -> "intset should contain oid " + oid + " after .clear()"); + } + + @ParameterizedTest + @ValueSource(ints = {Oid.UNSPECIFIED, Oid.INT4, Oid.BYTEA, Oid.VARCHAR, Integer.MIN_VALUE, + Integer.MAX_VALUE}) + void compact_does_not_contain_other_values(int oid) { + intSet.add(Oid.INT8); + intSet.add(Oid.BIT); + assertFalse(intSet.contains(oid), + () -> "intset contains only INT8 and BIT, so it should not contain oid " + oid); + } + + @ParameterizedTest + @ValueSource(ints = {Oid.UNSPECIFIED, Oid.INT4, Oid.BYTEA, Oid.VARCHAR, Integer.MIN_VALUE, + Integer.MAX_VALUE}) + void non_compact_does_not_contain_other_values(int oid) { + intSet.add(Oid.INT8); + intSet.add(Oid.BIT); + intSet.add(23465234); + assertFalse(intSet.contains(oid), + () -> "intset contains only INT8, BIT, and 23465234, so it should not contain oid " + oid); + } + + @Test + void addAll_contains_all_values() { + List values = Arrays.asList(Oid.UNSPECIFIED, Oid.INT4, Oid.BYTEA, Oid.VARCHAR); + intSet.addAll(values); + for (Integer value : values) { + assertTrue(intSet.contains(value), + () -> "intset should contain oid " + value + " after addAdd(" + values + ")"); + } + } + + @Test + void addAll_does_not_contains_other_values() { + List values = Arrays.asList(Oid.UNSPECIFIED, Oid.INT4, Oid.BYTEA, Oid.VARCHAR, + Integer.MIN_VALUE, Integer.MAX_VALUE); + intSet.addAll(values); + assertFalse(intSet.contains(Oid.BIT), + () -> "intset should not contain BIT after addAll(" + values + ")"); + + assertFalse(intSet.contains(2736453), + () -> "intset should not contain 2736453 after addAll(" + values + ")"); + + assertFalse(intSet.contains(-2736453), + () -> "intset should not contain -2736453 after addAll(" + values + ")"); + } + + @Test + void toMutableSet_contains_all_values() { + List values = Arrays.asList(Oid.UNSPECIFIED, Oid.INT4, Oid.BYTEA, Oid.VARCHAR, 24337); + intSet.addAll(values); + } +} diff --git a/src/test/java/org/postgresql/util/internal/PgBufferedOutputStreamTest.java b/src/test/java/org/postgresql/util/internal/PgBufferedOutputStreamTest.java new file mode 100644 index 0000000..0c05fa4 --- /dev/null +++ b/src/test/java/org/postgresql/util/internal/PgBufferedOutputStreamTest.java @@ -0,0 +1,343 @@ +/* + * Copyright (c) 2024, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.util.internal; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +import org.postgresql.test.util.StrangeOutputStream; + +// import org.checkerframework.checker.nullness.qual.Nullable; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class PgBufferedOutputStreamTest { + static class AssertBufferedWrites extends FilterOutputStream { + private /* @Nullable */ String writesForbidden; + int position; + private int expectedWriteSize; + + AssertBufferedWrites(OutputStream out) { + super(out); + } + + @Override + public void write(int b) throws IOException { + throw new IllegalArgumentException("Unexpected write(" + b + ")"); + } + + public void forbidWrites(String message) { + writesForbidden = message; + } + + @Override + public void write(byte[] b) throws IOException { + assertWritesAllowed(b.length); + write(b, 0, b.length); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + assertWritesAllowed(len); + assertEquals(expectedWriteSize, len, "write size"); + out.write(b, off, len); + position += len; + } + + private void assertWritesAllowed(int len) { + if (len == 0) { + fail("Zero-length writes are not allowed"); + } + if (writesForbidden != null) { + throw new IllegalStateException( + "Writes are forbidden: " + writesForbidden + + ", got write of " + len + " bytes" + + " at position " + position); + } + } + + public void allowWrites(int size) { + this.expectedWriteSize = size; + writesForbidden = null; + } + } + + @Nested + class SingleByteTests { + @Test + void writeOneByteDoesNotThrow() throws IOException { + OutputStream baos = new ByteArrayOutputStream(); + PgBufferedOutputStream buf = new PgBufferedOutputStream(baos, 1024); + for (int i = 0; i < 1024 * 20; i++) { + buf.write(i & 0xff); + } + } + + @Test + void bufferFlushedOnlyWhenFull() throws IOException { + AssertBufferedWrites dst = new AssertBufferedWrites(new ByteArrayOutputStream()); + int bufferSize = 128; + PgBufferedOutputStream out = new PgBufferedOutputStream(dst, bufferSize); + for (int i = 0; i < bufferSize * 2; i++) { + if (i > 0 && (i % bufferSize) == 0) { + dst.allowWrites(bufferSize); + } else { + dst.forbidWrites("Data must be buffered"); + } + out.write(i & 0xff); + } + } + } + + @Nested + class Int2Tests { + @Test + void writeInt2ByteDoesNotThrow() throws IOException { + OutputStream baos = new ByteArrayOutputStream(); + PgBufferedOutputStream buf = new PgBufferedOutputStream(baos, 1024); + for (int i = 0; i < 1024 * 20; i++) { + buf.writeInt2(i); + } + } + + @ParameterizedTest + @ValueSource(ints = {0, 1}) + void bufferFlushedOnlyWhenFull(int offset) throws IOException { + AssertBufferedWrites dst = new AssertBufferedWrites(new ByteArrayOutputStream()); + int bufferSize = 128; + PgBufferedOutputStream out = new PgBufferedOutputStream(dst, bufferSize); + int bufferPosition = offset; + out.writeZeros(offset); + for (int i = 0; i < bufferSize * 2; i++) { + if (bufferSize - bufferPosition < 2) { + dst.allowWrites(bufferPosition); + bufferPosition = 0; + } else { + dst.forbidWrites("Data must be buffered"); + } + out.writeInt2(i & 0xffff); + bufferPosition += 2; + } + } + } + + @Nested + class Int4Tests { + @Test + void writeInt4ByteDoesNotThrow() throws IOException { + OutputStream baos = new ByteArrayOutputStream(); + PgBufferedOutputStream buf = new PgBufferedOutputStream(baos, 1024); + for (int i = 0; i < 1024 * 20; i++) { + buf.writeInt4(i); + } + } + + @ParameterizedTest + @ValueSource(ints = {0, 1, 2, 3}) + void bufferFlushedOnlyWhenFull(int offset) throws IOException { + AssertBufferedWrites dst = new AssertBufferedWrites(new ByteArrayOutputStream()); + int bufferSize = 128; + PgBufferedOutputStream out = new PgBufferedOutputStream(dst, bufferSize); + int bufferPosition = offset; + out.writeZeros(offset); + for (int i = 0; i < bufferSize * 2; i++) { + if (bufferSize - bufferPosition < 4) { + dst.allowWrites(bufferPosition); + bufferPosition = 0; + } else { + dst.forbidWrites("Data must be buffered"); + } + out.writeInt4(i & 0xffff); + bufferPosition += 4; + } + } + } + + private static final int ZEROS_BUFFER_SIZE = 16; + + public static Iterable zerosOffsetAndNumber() { + List res = new ArrayList<>(); + for (int offset : new int[]{ + 0, 1, 2, 3, + ZEROS_BUFFER_SIZE - 3, ZEROS_BUFFER_SIZE - 2, ZEROS_BUFFER_SIZE - 1, + ZEROS_BUFFER_SIZE, + ZEROS_BUFFER_SIZE + 1, ZEROS_BUFFER_SIZE + 2, ZEROS_BUFFER_SIZE + 3}) { + for (int numZeros : new int[]{ + 1, 2, 3, + ZEROS_BUFFER_SIZE - 2, ZEROS_BUFFER_SIZE - 1, ZEROS_BUFFER_SIZE - 1, + ZEROS_BUFFER_SIZE, + ZEROS_BUFFER_SIZE + 1, ZEROS_BUFFER_SIZE + 2, ZEROS_BUFFER_SIZE + 3, + ZEROS_BUFFER_SIZE * 2 - 2, ZEROS_BUFFER_SIZE * 2 - 1, ZEROS_BUFFER_SIZE * 2 - 1, + ZEROS_BUFFER_SIZE * 2, + ZEROS_BUFFER_SIZE * 2 + 1, ZEROS_BUFFER_SIZE * 2 + 2, ZEROS_BUFFER_SIZE * 2 + 3}) { + res.add(arguments(offset, numZeros)); + } + } + return res; + } + + @Nested + class ZeroTests { + @ParameterizedTest + @MethodSource("org.postgresql.util.internal.PgBufferedOutputStreamTest#zerosOffsetAndNumber") + void bufferFlushedOnlyWhenFull(int offset, int numZeros) throws IOException { + AssertBufferedWrites dst = new AssertBufferedWrites(new ByteArrayOutputStream()); + int bufferSize = ZEROS_BUFFER_SIZE; + PgBufferedOutputStream out = new PgBufferedOutputStream(dst, bufferSize); + + if (offset < bufferSize) { + dst.forbidWrites("Writing less data than the buffer size should not cause flushes"); + } else { + dst.allowWrites(bufferSize); + } + out.writeZeros(offset); + if (numZeros + offset >= bufferSize) { + dst.allowWrites(bufferSize); + } + out.writeZeros(numZeros); + dst.allowWrites((numZeros + offset) % bufferSize); + out.flush(); + } + + @ParameterizedTest + @MethodSource("org.postgresql.util.internal.PgBufferedOutputStreamTest#zerosOffsetAndNumber") + void writesZeros(int offset, int numZeros) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int bufferSize = ZEROS_BUFFER_SIZE; + PgBufferedOutputStream out = new PgBufferedOutputStream(baos, bufferSize); + // Fill the buffer with non-zero values + for (int i = 0; i < bufferSize; i++) { + out.write(0xff); + } + // Shift the offset within the buffer + for (int i = 0; i < offset; i++) { + out.write(0xfe); + } + out.writeZeros(numZeros); + out.write(0xca); + out.flush(); + byte[] res = baos.toByteArray(); + assertAll( + () -> assertEquals(bufferSize + offset + numZeros + 1, res.length, + () -> "Result should have " + + bufferSize + " 0xff prefix bytes, " + + offset + " 0xfe offset bytes, " + + numZeros + " 0x00 zero bytes" + + ", and the final 0xca" + + ", result: " + Arrays.toString(res)), + () -> { + for (int i = 0; i < bufferSize; i++) { + int pos = i; + assertEquals( + 0xff, res[pos] & 0xff, + () -> "bytes [0.." + bufferSize + ") should be 0xff as they were written" + + " with single-byte write(0xff) calls to fill the buffer" + + ", mismatch at position " + pos + + ", result: " + Arrays.toString(res)); + } + }, + () -> { + for (int i = 0; i < offset; i++) { + int pos = bufferSize + i; + assertEquals( + 0xfe, res[pos] & 0xff, + () -> "bytes [" + bufferSize + ".." + (bufferSize + offset) + ")" + + " should be 0xfe as they were written" + + " with single-byte write(0xfe) calls to shift the buffer position" + + ", mismatch at position " + pos + + ", result: " + Arrays.toString(res)); + } + }, + () -> { + for (int i = 0; i < numZeros; i++) { + int pos = bufferSize + offset + i; + assertEquals( + 0, res[pos] & 0xff, + () -> "bytes [" + (bufferSize + offset) + ".." + (bufferSize + offset + numZeros) + ")" + + " should be 0xff as they were written" + + " with writeZeros(len)" + + ", mismatch at position " + pos + + ", result: " + Arrays.toString(res)); + } + }, + () -> { + assertEquals( + 0xca, res[bufferSize + offset + numZeros] & 0xff, + "the last byte should be 0xca as it was written with write(0xca)" + + " as a terminator char" + + ", result: " + Arrays.toString(res)); + } + ); + } + } + + @Test + void writeAndCompare() throws IOException { + byte[] data = new byte[1024 * 1024]; + // Use the same data every time for easier debugging + new Random(0).nextBytes(data); + Instant deadline = Instant.now().plus(5, ChronoUnit.SECONDS); + while (Instant.now().isBefore(deadline)) { + writeAndCompareOne(ThreadLocalRandom.current().nextLong(), data); + } + } + + private void writeAndCompareOne(long seed, byte[] data) throws IOException { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + Random rnd = new Random(seed); + int bufferSize = rnd.nextInt(16 * 1024) + 1; + int writeSize = rnd.nextInt(data.length + 1); + PgBufferedOutputStream buffered = new PgBufferedOutputStream(baos, bufferSize); + StrangeOutputStream out = new StrangeOutputStream(buffered, seed, 0.1); + out.write(data, 0, writeSize); + out.flush(); + byte[] result = baos.toByteArray(); + assertEquals( + writeSize, + result.length, + "The number of bytes produced by PgBufferedOutputStream should match the number" + + " of the written bytes" + ); + for (int i = 0; i < writeSize; i++) { + int pos = i; + assertEquals( + data[i], + result[i], + () -> "The result of PgBufferedOutputStream should match the input data. " + + "Mismatch detected at position " + pos + " of " + writeSize + ); + } + } catch (Throwable t) { + String message = "Test seed is " + seed; + t.addSuppressed(new Throwable(message) { + @Override + public Throwable fillInStackTrace() { + return this; + } + }); + throw t; + } + } +} diff --git a/src/test/resources/pg_service/.pg_service.conf b/src/test/resources/pg_service/.pg_service.conf new file mode 100644 index 0000000..7fa2b3b --- /dev/null +++ b/src/test/resources/pg_service/.pg_service.conf @@ -0,0 +1,35 @@ +# not used section +[mydb1] +host=local-somehost1 +port=5433 +# next line has invalid key +user =admin +line with invalid syntax + +[test-service1] +host=local-test-host.test.net +port=5433 + # comment +user=admin +# space after equal sign is intentional +dbname= test_dbname + +[fail-case-1] +# space before equal sign is intentional +host =local-somehost1 +port=5433 +user=admin + +[fail-CASE-2] +host=local-somehost2 + +[fail-case-3] +host + +[ success-case-3 ] +host=local-somehost3 + +[success case 4] +host=local-somehost4 + +[empty-service1] diff --git a/src/test/resources/pg_service/.pgpass b/src/test/resources/pg_service/.pgpass new file mode 100644 index 0000000..327e135 --- /dev/null +++ b/src/test/resources/pg_service/.pgpass @@ -0,0 +1,25 @@ + +# hostname:port:database:username:password + +localhost:5432:postgres:postgres:postgres1 +localhost2:5432:postgres:postgres:postgres\ +localhost3:5432:postgres:postgres:postgres\: +localhost4:5432:postgres:postgres:postgres1\:: +localhost5:5432:postgres:postgres:postgres5: +localhost6:5432:postgres:postgres:post\\gres\\ +localhost7:5432:postgres:postgres: ab cd +# NB! no spaces at the end of line +localhost8:5432:postgres:postgres: + +::1:1234:colon:db:colon:user:pass\:pass +::1:12345:colon\:db:colon\:user:pass\:pass1 + +::1:1234:slash\db:slash\user:pass\\pass +\:\:1:12345:slash\\db:slash\\user:pass\\pass1 + +*:5432:postgres:postgres:anyhost5 +localhost11:*:postgres:postgres:anyport5 +localhost12:5432:*:postgres:anydb5 +localhost13:5432:postgres:*:anyuser5 + +*:*:*:*:absolute-any diff --git a/src/test/resources/pg_service/pg_service.conf b/src/test/resources/pg_service/pg_service.conf new file mode 100644 index 0000000..0e3aa67 --- /dev/null +++ b/src/test/resources/pg_service/pg_service.conf @@ -0,0 +1,8 @@ +[test-service1] +host=global-test-host.test.net +port=5433 + # comment +user=admin +dbname=test_dbname + +[empty-service1] diff --git a/src/test/resources/pg_service/pgpassfileEnv.conf b/src/test/resources/pg_service/pgpassfileEnv.conf new file mode 100644 index 0000000..3f19950 --- /dev/null +++ b/src/test/resources/pg_service/pgpassfileEnv.conf @@ -0,0 +1 @@ +localhost:5432:postgres1:postgres2:postgres3 diff --git a/src/test/resources/pg_service/pgpassfileProps.conf b/src/test/resources/pg_service/pgpassfileProps.conf new file mode 100644 index 0000000..6d46415 --- /dev/null +++ b/src/test/resources/pg_service/pgpassfileProps.conf @@ -0,0 +1,5 @@ +# intentional short line +localhost88 +localhost9\ +# +localhost77:5432:*:postgres11:postgres22 diff --git a/src/test/resources/pg_service/pgservicefileEnv.conf b/src/test/resources/pg_service/pgservicefileEnv.conf new file mode 100644 index 0000000..32ff545 --- /dev/null +++ b/src/test/resources/pg_service/pgservicefileEnv.conf @@ -0,0 +1,20 @@ + +# comment +[test-service1] + host=pgservicefileEnv-test-host.test.net + port=5433 + # comment +user=admin +dbname=test_dbname +sslmode=disable + +# duplicate service section is intentional +[test-service1] +host=another-pgservicefileEnv-test-host.test.net + +[mydb2] +host=global-somehost2 +port=5433 +user=admin + +[empty-service1] diff --git a/src/test/resources/pg_service/pgservicefileProps.conf b/src/test/resources/pg_service/pgservicefileProps.conf new file mode 100644 index 0000000..922a6b5 --- /dev/null +++ b/src/test/resources/pg_service/pgservicefileProps.conf @@ -0,0 +1,29 @@ +# comment +[mydb1] +host=global-somehost1 +port=5433 +user=admin + +[test-service1] +host=pgservicefileProps-test-host.test.net +port=5433 + # comment +user=admin +dbname=test_dbname + +[empty-service1] + +[mydb2] +host=global-somehost2 +port=5433 +user=admin + +[driverTestService1] +host=test-host1 +port=5444 +dbname=testdb1 +[driverTestService2] +host=test-host1,[::1],test-host2 +# intentional: less ports than hosts +port=5541,5542 +dbname=testdb1 diff --git a/src/test/resources/pg_service/postgresql/.pg_service.conf b/src/test/resources/pg_service/postgresql/.pg_service.conf new file mode 100644 index 0000000..7fa2b3b --- /dev/null +++ b/src/test/resources/pg_service/postgresql/.pg_service.conf @@ -0,0 +1,35 @@ +# not used section +[mydb1] +host=local-somehost1 +port=5433 +# next line has invalid key +user =admin +line with invalid syntax + +[test-service1] +host=local-test-host.test.net +port=5433 + # comment +user=admin +# space after equal sign is intentional +dbname= test_dbname + +[fail-case-1] +# space before equal sign is intentional +host =local-somehost1 +port=5433 +user=admin + +[fail-CASE-2] +host=local-somehost2 + +[fail-case-3] +host + +[ success-case-3 ] +host=local-somehost3 + +[success case 4] +host=local-somehost4 + +[empty-service1] diff --git a/src/test/resources/pg_service/postgresql/pgpass.conf b/src/test/resources/pg_service/postgresql/pgpass.conf new file mode 100644 index 0000000..327e135 --- /dev/null +++ b/src/test/resources/pg_service/postgresql/pgpass.conf @@ -0,0 +1,25 @@ + +# hostname:port:database:username:password + +localhost:5432:postgres:postgres:postgres1 +localhost2:5432:postgres:postgres:postgres\ +localhost3:5432:postgres:postgres:postgres\: +localhost4:5432:postgres:postgres:postgres1\:: +localhost5:5432:postgres:postgres:postgres5: +localhost6:5432:postgres:postgres:post\\gres\\ +localhost7:5432:postgres:postgres: ab cd +# NB! no spaces at the end of line +localhost8:5432:postgres:postgres: + +::1:1234:colon:db:colon:user:pass\:pass +::1:12345:colon\:db:colon\:user:pass\:pass1 + +::1:1234:slash\db:slash\user:pass\\pass +\:\:1:12345:slash\\db:slash\\user:pass\\pass1 + +*:5432:postgres:postgres:anyhost5 +localhost11:*:postgres:postgres:anyport5 +localhost12:5432:*:postgres:anydb5 +localhost13:5432:postgres:*:anyuser5 + +*:*:*:*:absolute-any