diff --git a/.github/workflows/jruby_head.yml b/.github/workflows/jruby_head.yml index 423d87a..7525731 100644 --- a/.github/workflows/jruby_head.yml +++ b/.github/workflows/jruby_head.yml @@ -8,7 +8,7 @@ jobs: build: runs-on: ubuntu-latest - continue-on-error: true + continue-on-error: false strategy: matrix: ruby: [ @@ -39,6 +39,11 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: oracle + java-version: '21' - name: Set up Ruby uses: ruby/setup-ruby@v1 with: @@ -57,7 +62,7 @@ jobs: echo "/opt/oracle/instantclient_23_26" >> $GITHUB_PATH - name: Install JDBC Driver run: | - wget -q https://download.oracle.com/otn-pub/otn_software/jdbc/233/ojdbc11.jar -O ./lib/ojdbc11.jar + wget -q https://download.oracle.com/otn-pub/otn_software/jdbc/23261/ojdbc17.jar -O ./lib/ojdbc17.jar - name: Create database user run: | ./ci/setup_accounts.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c8859e3..ba65423 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,14 +8,15 @@ jobs: build: runs-on: ubuntu-latest - continue-on-error: true + continue-on-error: false strategy: matrix: ruby: [ '4.0', '3.4', '3.3', - '3.2' + '3.2', + 'jruby-10.0.5.0', ] env: ORACLE_HOME: /opt/oracle/instantclient_23_26 @@ -42,6 +43,12 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Set up Java + if: startsWith(matrix.ruby, 'jruby') + uses: actions/setup-java@v4 + with: + distribution: oracle + java-version: '21' - name: Set up Ruby uses: ruby/setup-ruby@v1 with: @@ -61,8 +68,9 @@ jobs: sudo unzip -qo instantclient-sqlplus-linux.x64-23.26.1.0.0.zip -d /opt/oracle/ echo "/opt/oracle/instantclient_23_26" >> $GITHUB_PATH - name: Install JDBC Driver + if: startsWith(matrix.ruby, 'jruby') run: | - wget -q https://download.oracle.com/otn-pub/otn_software/jdbc/233/ojdbc11.jar -O ./lib/ojdbc11.jar + wget -q https://download.oracle.com/otn-pub/otn_software/jdbc/23261/ojdbc17.jar -O ./lib/ojdbc17.jar - name: Create database user run: | ./ci/setup_accounts.sh diff --git a/.github/workflows/test_11g.yml b/.github/workflows/test_11g.yml index 8083a6e..a816d9b 100644 --- a/.github/workflows/test_11g.yml +++ b/.github/workflows/test_11g.yml @@ -12,14 +12,15 @@ jobs: if: github.event_name != 'pull_request' || !github.event.pull_request.draft runs-on: ubuntu-latest - continue-on-error: true + continue-on-error: false strategy: matrix: ruby: [ '4.0', '3.4', '3.3', - '3.2' + '3.2', + 'jruby-10.0.5.0', ] env: ORACLE_HOME: /opt/oracle/instantclient_21_15 @@ -46,6 +47,12 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Set up Java + if: startsWith(matrix.ruby, 'jruby') + uses: actions/setup-java@v4 + with: + distribution: oracle + java-version: '21' - name: Set up Ruby uses: ruby/setup-ruby@v1 with: @@ -66,20 +73,21 @@ jobs: sudo unzip -qo instantclient-sdk-linux.x64-21.15.0.0.0dbru.zip -d /opt/oracle echo "/opt/oracle/instantclient_21_15" >> $GITHUB_PATH - name: Install JDBC Driver + if: startsWith(matrix.ruby, 'jruby') run: | - wget -q https://download.oracle.com/otn-pub/otn_software/jdbc/233/ojdbc11.jar -O ./lib/ojdbc11.jar + wget -q https://download.oracle.com/otn-pub/otn_software/jdbc/23261/ojdbc17.jar -O ./lib/ojdbc17.jar - name: Configure ORA_TZFILE to match Oracle 11g server run: | - # Oracle 11g XE uses timezone file v14; Instant Client 21.15 embeds v35. - # This mismatch causes ORA-01805 when ruby-oci8 fetches DATE/TIMESTAMP + # Oracle 11g XE uses timezone file v14; Instant Client 21.15 embeds + # v35. This mismatch causes ORA-01805 when fetching DATE/TIMESTAMP # values. Copy the v14 files from the 11g container and point the # Instant Client at them via ORA_TZFILE. - ORACLE_CONTAINER=$(docker ps --filter "ancestor=gvenzl/oracle-xe:11" -q) - sudo mkdir -p /opt/oracle/instantclient_21_15/oracore/zoneinfo + ORACLE_CONTAINER="${{ job.services.oracle.id }}" + sudo mkdir -p "$ORACLE_HOME/oracore/zoneinfo" docker cp "$ORACLE_CONTAINER":/u01/app/oracle/product/11.2.0/xe/oracore/zoneinfo/timezlrg_14.dat /tmp/timezlrg_14.dat docker cp "$ORACLE_CONTAINER":/u01/app/oracle/product/11.2.0/xe/oracore/zoneinfo/timezone_14.dat /tmp/timezone_14.dat - sudo mv /tmp/timezlrg_14.dat /opt/oracle/instantclient_21_15/oracore/zoneinfo/ - sudo mv /tmp/timezone_14.dat /opt/oracle/instantclient_21_15/oracore/zoneinfo/ + sudo mv /tmp/timezlrg_14.dat "$ORACLE_HOME/oracore/zoneinfo/" + sudo mv /tmp/timezone_14.dat "$ORACLE_HOME/oracore/zoneinfo/" echo "ORA_TZFILE=timezlrg_14.dat" >> $GITHUB_ENV - name: Create database user run: | diff --git a/.github/workflows/test_11g_ojdbc11.yml b/.github/workflows/test_11g_ojdbc11.yml new file mode 100644 index 0000000..9873e70 --- /dev/null +++ b/.github/workflows/test_11g_ojdbc11.yml @@ -0,0 +1,94 @@ +name: test_11g_ojdbc11 + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + build: + if: github.event_name != 'pull_request' || !github.event.pull_request.draft + + runs-on: ubuntu-latest + continue-on-error: false + strategy: + matrix: + ruby: [ + 'jruby-10.0.5.0' + ] + env: + ORACLE_HOME: /opt/oracle/instantclient_21_15 + LD_LIBRARY_PATH: /opt/oracle/instantclient_21_15 + NLS_LANG: AMERICAN_AMERICA.AL32UTF8 + TNS_ADMIN: ./ci/network/admin + DATABASE_NAME: XE + TZ: Europe/Riga + DATABASE_SYS_PASSWORD: Oracle18 + + services: + oracle: + image: gvenzl/oracle-xe:11 + ports: + - 1521:1521 + env: + TZ: Europe/Riga + ORACLE_PASSWORD: Oracle18 + options: >- + --health-cmd healthcheck.sh + --health-interval 10s + --health-timeout 5s + --health-retries 10 + + steps: + - uses: actions/checkout@v6 + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: oracle + java-version: '21' + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - name: Create symbolic link for libaio library compatibility + run: | + sudo ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1 + - name: Download Oracle instant client + run: | + wget -q https://download.oracle.com/otn_software/linux/instantclient/2115000/instantclient-basic-linux.x64-21.15.0.0.0dbru.zip + wget -q https://download.oracle.com/otn_software/linux/instantclient/2115000/instantclient-sqlplus-linux.x64-21.15.0.0.0dbru.zip + wget -q https://download.oracle.com/otn_software/linux/instantclient/2115000/instantclient-sdk-linux.x64-21.15.0.0.0dbru.zip + - name: Install Oracle instant client + run: | + sudo mkdir -p /opt/oracle/ + sudo unzip -q instantclient-basic-linux.x64-21.15.0.0.0dbru.zip -d /opt/oracle + sudo unzip -qo instantclient-sqlplus-linux.x64-21.15.0.0.0dbru.zip -d /opt/oracle + sudo unzip -qo instantclient-sdk-linux.x64-21.15.0.0.0dbru.zip -d /opt/oracle + echo "/opt/oracle/instantclient_21_15" >> $GITHUB_PATH + - name: Install JDBC Driver + run: | + wget -q https://download.oracle.com/otn-pub/otn_software/jdbc/23261/ojdbc11.jar -O ./lib/ojdbc11.jar + - name: Configure ORA_TZFILE to match Oracle 11g server + run: | + # Oracle 11g XE uses timezone file v14; Instant Client 21.15 embeds + # v35. This mismatch causes ORA-01805 when fetching DATE/TIMESTAMP + # values. Copy the v14 files from the 11g container and point the + # Instant Client at them via ORA_TZFILE. + ORACLE_CONTAINER="${{ job.services.oracle.id }}" + sudo mkdir -p "$ORACLE_HOME/oracore/zoneinfo" + docker cp "$ORACLE_CONTAINER":/u01/app/oracle/product/11.2.0/xe/oracore/zoneinfo/timezlrg_14.dat /tmp/timezlrg_14.dat + docker cp "$ORACLE_CONTAINER":/u01/app/oracle/product/11.2.0/xe/oracore/zoneinfo/timezone_14.dat /tmp/timezone_14.dat + sudo mv /tmp/timezlrg_14.dat "$ORACLE_HOME/oracore/zoneinfo/" + sudo mv /tmp/timezone_14.dat "$ORACLE_HOME/oracore/zoneinfo/" + echo "ORA_TZFILE=timezlrg_14.dat" >> $GITHUB_ENV + - name: Create database user + run: | + ./ci/setup_accounts.sh + - name: Bundle install + run: | + bundle install --jobs 4 --retry 3 + - name: Run RSpec + run: | + RUBYOPT=-w bundle exec rspec diff --git a/.github/workflows/test_gemfiles.yml b/.github/workflows/test_gemfiles.yml index b50d3eb..1f5268b 100644 --- a/.github/workflows/test_gemfiles.yml +++ b/.github/workflows/test_gemfiles.yml @@ -82,9 +82,6 @@ jobs: sudo unzip -qo instantclient-sdk-linux.x64-23.26.1.0.0.zip -d /opt/oracle/ sudo unzip -qo instantclient-sqlplus-linux.x64-23.26.1.0.0.zip -d /opt/oracle/ echo "/opt/oracle/instantclient_23_26" >> $GITHUB_PATH - - name: Install JDBC Driver - run: | - wget -q https://download.oracle.com/otn-pub/otn_software/jdbc/233/ojdbc11.jar -O ./lib/ojdbc11.jar - name: Create database user run: | ./ci/setup_accounts.sh diff --git a/README.md b/README.md index ad52fca..f42932a 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ In addition install either ruby-oci8 (for MRI/YARV) or copy Oracle JDBC driver t If you are using MRI Ruby implementation then you need to install ruby-oci8 gem (version 2.1 or higher) as well as Oracle client, e.g. [Oracle Instant Client](http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html). -If you are using JRuby then you need to download latest [Oracle JDBC driver](http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html) - either ojdbc7.jar for Java 8 and 7, ojdbc6.jar for Java 6, 7, 8 or ojdbc5.jar for Java 5. You can refer [the support matrix](http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-faq-090281.html#01_03) for details. +If you are using JRuby then you need to download the appropriate [Oracle JDBC driver](https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html) for your Java version - ojdbc17.jar for Java 17+, ojdbc11.jar for Java 11+, ojdbc8.jar for Java 8+, ojdbc7.jar for Java 7, ojdbc6.jar for Java 6, or ojdbc5.jar for Java 5. And copy this file to one of these locations. JDBC driver will be searched in this order: diff --git a/lib/plsql/jdbc_connection.rb b/lib/plsql/jdbc_connection.rb index 95900b8..478b81a 100644 --- a/lib/plsql/jdbc_connection.rb +++ b/lib/plsql/jdbc_connection.rb @@ -1,23 +1,26 @@ +ojdbc_jars = [] + begin require "java" require "jruby" - # ojdbc6.jar or ojdbc5.jar file should be in JRUBY_HOME/lib or should be in ENV['PATH'] or load path + # Oracle JDBC driver jar should be in JRUBY_HOME/lib or should be in ENV['PATH'] or load path java_version = java.lang.System.getProperty("java.version") - ojdbc_jars = if java_version =~ /^1.5/ - %w(ojdbc5.jar) - elsif java_version =~ /^1.6/ - %w(ojdbc6.jar) - elsif java_version >= "1.7" - # Oracle 11g client ojdbc6.jar is also compatible with Java 1.7 - # Oracle 12c client provides new ojdbc7.jar - %w(ojdbc7.jar ojdbc6.jar) + java_major = if java_version =~ /^1\.(\d+)/ + $1.to_i else - [] + java_version.to_i end - if ENV_JAVA["java.class.path"] !~ Regexp.new(ojdbc_jars.join("|")) + ojdbc_jars << "ojdbc17.jar" if java_major >= 17 + ojdbc_jars << "ojdbc11.jar" if java_major >= 11 + ojdbc_jars << "ojdbc8.jar" if java_major >= 8 + ojdbc_jars << "ojdbc7.jar" if java_major >= 7 + ojdbc_jars << "ojdbc6.jar" if java_major >= 6 + ojdbc_jars << "ojdbc5.jar" if java_major == 5 + + if ENV_JAVA["java.class.path"] !~ Regexp.union(ojdbc_jars) # On Unix environment variable should be PATH, on Windows it is sometimes Path env_path = (ENV["PATH"] || ENV["Path"] || "").split(File::PATH_SEPARATOR) # Look for JDBC driver at first in lib subdirectory (application specific JDBC file version) @@ -33,23 +36,43 @@ end end - java.sql.DriverManager.registerDriver Java::oracle.jdbc.OracleDriver.new - # set tns_admin property from TNS_ADMIN environment variable if !java.lang.System.get_property("oracle.net.tns_admin") && ENV["TNS_ADMIN"] java.lang.System.set_property("oracle.net.tns_admin", ENV["TNS_ADMIN"]) end -rescue LoadError, NameError +rescue LoadError # JDBC driver is unavailable. raise LoadError, "ERROR: ruby-plsql could not load Oracle JDBC driver. Please install #{ojdbc_jars.empty? ? "Oracle JDBC" : ojdbc_jars.join(' or ') } library." end module PLSQL class JDBCConnection < Connection # :nodoc: + begin + ORACLE_DRIVER = Java::oracle.jdbc.OracleDriver.new + java.sql.DriverManager.registerDriver ORACLE_DRIVER + rescue NameError + raise LoadError, "ERROR: ruby-plsql could not load Oracle JDBC driver. " \ + "Please install the appropriate Oracle JDBC driver. " \ + "See https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html" + end + def self.create_raw(params) url = jdbc_connection_url(params) - new(java.sql.DriverManager.getConnection(url, params[:username], params[:password])) + conn = begin + java.sql.DriverManager.getConnection(url, params[:username], params[:password]) + rescue Java::JavaSql::SQLException => e + raise unless e.message =~ /no suitable driver/i + # bypass DriverManager to work in cases where ojdbc*.jar + # is added to the load path at runtime and not on the + # system classpath + ORACLE_DRIVER.connect(url, java.util.Properties.new.tap do |props| + props.setProperty("user", params[:username]) + props.setProperty("password", params[:password]) + end) + end + conn.setAutoCommit(false) + new(conn) end def self.jdbc_connection_url(params) diff --git a/spec/plsql/procedure_spec.rb b/spec/plsql/procedure_spec.rb index b0be396..6a126f1 100644 --- a/spec/plsql/procedure_spec.rb +++ b/spec/plsql/procedure_spec.rb @@ -1875,7 +1875,7 @@ def new_candidate(status) expect(plsql.test_cursor do |cursor| cursor2 = cursor end).to be_nil - expect { cursor2.fetch }.to raise_error(/Cursor was already closed|Closed Statement/) + expect { cursor2.fetch }.to raise_error(/Cursor was already closed|Closed Statement|Closed ResultSet/) end it "should not raise error if cursor is closed inside block" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bee44ff..8d7cba6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -88,7 +88,18 @@ def get_connection(user_number = 0) end else try_to_connect(Java::JavaSql::SQLException) do - java.sql.DriverManager.getConnection(get_connection_url, database_user, database_password) + begin + java.sql.DriverManager.getConnection(get_connection_url, database_user, database_password) + rescue Java::JavaSql::SQLException => e + raise unless e.message =~ /no suitable driver/i + # bypass DriverManager to work in cases where ojdbc*.jar + # is added to the load path at runtime and not on the + # system classpath + PLSQL::JDBCConnection::ORACLE_DRIVER.connect(get_connection_url, java.util.Properties.new.tap do |props| + props.setProperty("user", database_user) + props.setProperty("password", database_password) + end) + end.tap { |c| c.setAutoCommit(false) } end end end