├── .github └── workflows │ ├── dump-bindings.yml-disabled │ └── tests.yml ├── .gitignore ├── .yardopts ├── COPYING ├── COPYING_old ├── ChangeLog ├── Makefile ├── NEWS ├── README.md ├── dist-files ├── docs ├── bind-array-to-in_cond.md ├── conflicts-local-connections-and-processes.md ├── hanging-after-inactivity.md ├── install-binary-package.md ├── install-full-client.md ├── install-instant-client.md ├── install-on-osx.md ├── ldap-auth-and-function-interposition.md ├── number-type-mapping.md ├── platform-specific-issues.md ├── report-installation-issue.md └── timeout-parameters.md ├── ext └── oci8 │ ├── .document │ ├── MANIFEST │ ├── apiwrap.c.tmpl │ ├── apiwrap.h.tmpl │ ├── apiwrap.rb │ ├── apiwrap.yml │ ├── attr.c │ ├── bind.c │ ├── connection_pool.c │ ├── dump_bindings.c │ ├── encoding.c │ ├── env.c │ ├── error.c │ ├── extconf.rb │ ├── hook_funcs.c │ ├── lob.c │ ├── metadata.c │ ├── object.c │ ├── oci8.c │ ├── oci8.h │ ├── oci8lib.c │ ├── ocidatetime.c │ ├── ocihandle.c │ ├── ocinumber.c │ ├── oraconf.rb │ ├── oradate.c │ ├── oranumber_util.c │ ├── oranumber_util.h │ ├── plthook.h │ ├── plthook_elf.c │ ├── plthook_osx.c │ ├── plthook_win32.c │ ├── post-config.rb │ ├── stmt.c │ ├── thread_util.c │ ├── thread_util.h │ ├── util.c │ └── win32.c ├── lib ├── .document ├── dbd │ └── OCI8.rb ├── oci8.rb ├── oci8 │ ├── .document │ ├── bindtype.rb │ ├── check_load_error.rb │ ├── compat.rb │ ├── connection_pool.rb │ ├── cursor.rb │ ├── datetime.rb │ ├── encoding-init.rb │ ├── encoding.yml │ ├── metadata.rb │ ├── object.rb │ ├── oci8.rb │ ├── ocihandle.rb │ ├── oracle_version.rb │ ├── properties.rb │ └── version.rb └── ruby-oci8.rb ├── metaconfig ├── mkpkg-win32.rb ├── pre-distclean.rb ├── ruby-oci8.gemspec ├── setup.rb └── test ├── README.md ├── config.rb ├── setup.sql ├── setup_test_object.sql ├── setup_test_package.sql ├── test_all.rb ├── test_appinfo.rb ├── test_array_dml.rb ├── test_bind_array.rb ├── test_bind_boolean.rb ├── test_bind_integer.rb ├── test_bind_raw.rb ├── test_bind_string.rb ├── test_bind_time.rb ├── test_break.rb ├── test_clob.rb ├── test_connection_pool.rb ├── test_connstr.rb ├── test_datetime.rb ├── test_dbi.rb ├── test_dbi_clob.rb ├── test_encoding.rb ├── test_error.rb ├── test_metadata.rb ├── test_object.rb ├── test_oci8.rb ├── test_oracle_version.rb ├── test_oradate.rb ├── test_oranumber.rb ├── test_package_type.rb ├── test_properties.rb └── test_rowid.rb /.github/workflows/dump-bindings.yml-disabled: -------------------------------------------------------------------------------- 1 | name: dump bindings 2 | on: [push, pull_request] 3 | jobs: 4 | dump_bindings: 5 | strategy: 6 | fail-fast: false 7 | matrix: 8 | include: 9 | - os: macos-15 10 | ruby: 3.3 11 | basiclite-url: https://download.oracle.com/otn_software/mac/instantclient/instantclient-basiclite-macos-arm64.dmg 12 | sdk-url: https://download.oracle.com/otn_software/mac/instantclient/instantclient-sdk-macos-arm64.dmg 13 | - os: macos-14 14 | ruby: 3.3 15 | basiclite-url: https://download.oracle.com/otn_software/mac/instantclient/instantclient-basiclite-macos-arm64.dmg 16 | sdk-url: https://download.oracle.com/otn_software/mac/instantclient/instantclient-sdk-macos-arm64.dmg 17 | runs-on: ${{ matrix.os }} 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Get ETag 21 | id: get-etag 22 | run: | 23 | ETAG=$(wget --method=HEAD --server-response ${{ matrix.basiclite-url }} 2>&1 | grep ETag | sed -e 's/ *ETag: *//' -e 's/[^A-Za-z0-9]/_/g') 24 | test "$ETAG" 25 | echo "etag=$ETAG" >> $GITHUB_OUTPUT 26 | - name: Cache Oracle instant client 27 | id: cache-oracle-instant-client 28 | uses: actions/cache@v4 29 | with: 30 | path: ~/Downloads/instantclient* 31 | key: oracle-instant-client-${{ matrix.os }}-${{ steps.get-etag.outputs.etag }} 32 | - name: install Oracle instant client 33 | if: ${{ steps.cache-oracle-instant-client.outputs.cache-hit != 'true' }} 34 | run: | 35 | for url in ${{ matrix.basiclite-url }} ${{ matrix.sdk-url }}; do 36 | curl -Lo instantclient.dmg $url 37 | hdiutil mount instantclient.dmg 38 | for dir in $(ls -d /Volumes/instantclient-*); do 39 | pushd $dir 40 | test -f install_ic.sh && echo "run $dir/install_ic.sh" && ./install_ic.sh 41 | popd 42 | hdiutil detach $dir 43 | done 44 | rm instantclient.dmg 45 | done 46 | - name: Set up ruby 47 | uses: ruby/setup-ruby@v1 48 | with: 49 | ruby-version: ${{ matrix.ruby }} 50 | - name: test 51 | run: | 52 | export OCI_DIR=$(ls -d $HOME/Downloads/instantclient*) 53 | echo OCI_DIR=$OCI_DIR 54 | make 55 | export DYLD_BIND_AT_LAUNCH=1 56 | echo "=================================================================================" 57 | ruby -Ilib -Iext/oci8 -roci8 -e "OCI8.dump_bindings" 58 | echo "=================================================================================" 59 | cc -o dump_bindings -DDUMP_BINDIGINGS_MAIN ext/oci8/dump_bindings.c ext/oci8/plthook_osx.c -L$OCI_DIR -Wl,-rpath,$OCI_DIR -lclntsh 60 | ./dump_bindings 61 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push, pull_request] 3 | jobs: 4 | tests: 5 | strategy: 6 | fail-fast: false 7 | matrix: 8 | ruby: [2.7, '3.0', 3.1, 3.2, 3.3, truffleruby-head] 9 | 10 | runs-on: ubuntu-latest 11 | 12 | env: 13 | NLS_LANG: AMERICAN_AMERICA.AL32UTF8 14 | 15 | services: 16 | oracle: 17 | image: gvenzl/oracle-free:latest 18 | env: 19 | ORACLE_PASSWORD: sys_passwd 20 | ports: 21 | - 1521:1521 22 | options: >- 23 | --health-cmd healthcheck.sh 24 | --health-interval 10s 25 | --health-timeout 5s 26 | --health-retries 10 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - name: Install the latest Oracle instant client 32 | run: | 33 | curl -Lo basic.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-basic-linuxx64.zip 34 | curl -Lo sqlplus.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-sqlplus-linuxx64.zip 35 | curl -Lo sdk.zip https://download.oracle.com/otn_software/linux/instantclient/instantclient-sdk-linuxx64.zip 36 | unzip basic.zip -x "META-INF/*" 37 | unzip sqlplus.zip -x "META-INF/*" 38 | unzip sdk.zip -x "META-INF/*" 39 | IC_DIR=$PWD/$(ls -d instantclient*) 40 | ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 $IC_DIR/libaio.so.1 41 | echo LD_LIBRARY_PATH=$IC_DIR:$LD_LIBRARY_PATH >> $GITHUB_ENV 42 | echo $IC_DIR >> $GITHUB_PATH 43 | 44 | - name: Get the Oracle container IP address 45 | env: 46 | ORACLE_SERVICE_ID: ${{ job.services.oracle.id }} 47 | run: | 48 | ORACLE_IP_ADDRESS=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' $ORACLE_SERVICE_ID) 49 | if test -z "$ORACLE_IP_ADDRESS"; then 50 | echo "Cannot get ORACLE_IP_ADDRESS." 51 | docker inspect $ORACLE_SERVICE_ID 52 | exit 1 53 | fi 54 | echo TWO_TASK=//$ORACLE_IP_ADDRESS:1521/FREEPDB1 >> $GITHUB_ENV 55 | 56 | - name: Enable OOB 57 | env: 58 | ORACLE_SERVICE_ID: ${{ job.services.oracle.id }} 59 | run: | 60 | docker exec $ORACLE_SERVICE_ID bash -c 'sed -i "s/^DISABLE_OOB/#DISABLE_OOB/; s/^BREAK_POLL_SKIP=1000/#BREAK_POLL_SKIP=1000/" "${ORACLE_BASE_HOME}/network/admin/sqlnet.ora"' 61 | docker exec $ORACLE_SERVICE_ID lsnrctl reload 62 | 63 | - name: Set up ruby 64 | uses: ruby/setup-ruby@v1 65 | with: 66 | ruby-version: ${{ matrix.ruby }} 67 | 68 | - name: Run tests 69 | run: | 70 | echo 'exit' | sqlplus -L -S sys/sys_passwd AS SYSDBA @test/setup.sql 71 | make check 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *~ 3 | .svn 4 | .yardoc 5 | config.save 6 | doc 7 | docs/.svn 8 | ext/.svn 9 | ext/oci8/.svn 10 | ext/oci8/Makefile 11 | ext/oci8/OCI.def 12 | ext/oci8/apiwrap.h 13 | ext/oci8/apiwrap.c 14 | ext/oci8/depend 15 | ext/oci8/extconf.h 16 | ext/oci8/mkmf.log 17 | ext/oci8/oci8lib_*.def 18 | ext/oci8/*.bundle 19 | ext/oci8/*.def 20 | ext/oci8/*.map 21 | ext/oci8/*.o 22 | ext/oci8/*.so 23 | ext/oci8/*.obj 24 | ext/oci8/*~ 25 | lib/oci8.rb 26 | lib/*.map 27 | lib/*.rbc 28 | lib/*.so 29 | lib/*~ 30 | lib/oci8/*.rbc 31 | lib/oci8/*~ 32 | test/*.rbc 33 | test/*~ 34 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --protected 2 | --no-private 3 | --exclude ext/oci8/apiwrap.rb 4 | --exclude ext/oci8/oraconf.rb 5 | --exclude lib/oci8/compat.rb 6 | --exclude lib/dbd 7 | --hide-void-return 8 | --charset UTF-8 9 | --asset docs/osx-install-dev-tools.png:osx-install-dev-tools.png 10 | - 11 | NEWS 12 | COPYING 13 | COPYING_old 14 | docs/*.md 15 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | # @markup pre 2 | 3 | License from ruby-oci8 2.1.3 4 | 5 | --- 6 | Copyright 2002-2012 Kubo Takehiro . All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, are 9 | permitted provided that the following conditions are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, this list of 12 | conditions and the following disclaimer. 13 | 14 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 15 | of conditions and the following disclaimer in the documentation and/or other materials 16 | provided with the distribution. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED 19 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR 21 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 24 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 26 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | The views and conclusions contained in the software and documentation are those of the 29 | authors and should not be interpreted as representing official policies, either expressed 30 | or implied, of the authors. 31 | -------------------------------------------------------------------------------- /COPYING_old: -------------------------------------------------------------------------------- 1 | # @markup pre 2 | 3 | License until ruby-oci8 2.1.2 4 | 5 | The license of ruby-oci8 was changed to 2-clause BSD license by using the following 6 | 2.d clause. 7 | 8 | --- 9 | Ruby-oci8 is copyrighted free software by Kubo Takehiro . 10 | You can redistribute it and/or modify it under either the terms of the GPL 11 | version 2 (see the file GPL), or the conditions below: 12 | 13 | 1. You may make and give away verbatim copies of the source form of the 14 | software without restriction, provided that you duplicate all of the 15 | original copyright notices and associated disclaimers. 16 | 17 | 2. You may modify your copy of the software in any way, provided that 18 | you do at least ONE of the following: 19 | 20 | a) place your modifications in the Public Domain or otherwise 21 | make them Freely Available, such as by posting said 22 | modifications to Usenet or an equivalent medium, or by allowing 23 | the author to include your modifications in the software. 24 | 25 | b) use the modified software only within your corporation or 26 | organization. 27 | 28 | c) give non-standard binaries non-standard names, with 29 | instructions on where to get the original software distribution. 30 | 31 | d) make other distribution arrangements with the author. 32 | 33 | 3. You may distribute the software in object code or binary form, 34 | provided that you do at least ONE of the following: 35 | 36 | a) distribute the binaries and library files of the software, 37 | together with instructions (in the manual page or equivalent) 38 | on where to get the original distribution. 39 | 40 | b) accompany the distribution with the machine-readable source of 41 | the software. 42 | 43 | c) give non-standard binaries non-standard names, with 44 | instructions on where to get the original software distribution. 45 | 46 | d) make other distribution arrangements with the author. 47 | 48 | 4. You may modify and include the part of the software into any other 49 | software (possibly commercial). But some files in the distribution 50 | are not written by the author, so that they are not under these terms. 51 | 52 | For the list of those files and their copying conditions, see the 53 | file LEGAL. 54 | 55 | 5. The scripts and library files supplied as input to or produced as 56 | output from the software do not automatically fall under the 57 | copyright of the software, but belong to whomever generated them, 58 | and may be sold commercially, and may be aggregated with this 59 | software. 60 | 61 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 62 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 63 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 64 | PURPOSE. 65 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = `$(RUBY) -I. -rlib/oci8/version.rb -e "print OCI8::VERSION"` 2 | RUBY = ruby -w 3 | 4 | all: build 5 | 6 | config.save: 7 | $(RUBY) setup.rb config 8 | 9 | build: config.save 10 | $(RUBY) setup.rb setup 11 | 12 | check: build 13 | $(RUBY) setup.rb test 14 | 15 | clean: 16 | $(RUBY) setup.rb clean 17 | 18 | distclean: 19 | $(RUBY) setup.rb distclean 20 | 21 | install: 22 | $(RUBY) setup.rb install 23 | 24 | site-install: 25 | $(RUBY) setup.rb install 26 | 27 | format_c_source: 28 | astyle --options=none --style=linux --indent=spaces=4 --brackets=linux --suffix=none ext/oci8/*.[ch] 29 | 30 | # internal use only 31 | .PHONY: dist dist-check doc copy-doc 32 | 33 | doc: 34 | rm -rf doc 35 | TZ= yard 36 | echo DirectoryIndex frames.html > doc/.htaccess 37 | 38 | copy-doc: doc 39 | rsync -avz doc/ rubyforge.org:/var/www/gforge-projects/ruby-oci8/en 40 | 41 | dist: 42 | -rm -rf ruby-oci8-$(VERSION) 43 | mkdir ruby-oci8-$(VERSION) 44 | tar cf - `cat dist-files` | (cd ruby-oci8-$(VERSION); tar xf - ) 45 | tar cfz ruby-oci8-$(VERSION).tar.gz ruby-oci8-$(VERSION) 46 | 47 | dist-check: dist 48 | cd ruby-oci8-$(VERSION) && $(MAKE) RUBY="$(RUBY)" 49 | cd ruby-oci8-$(VERSION) && $(MAKE) RUBY="$(RUBY)" check 50 | cd ruby-oci8-$(VERSION)/src && $(MAKE) RUBY="$(RUBY)" sitearchdir=../work sitelibdir=../work site-install 51 | cd ruby-oci8-$(VERSION)/test && $(RUBY) -I../work -I../support test_all.rb 52 | 53 | # 54 | # for Windows 55 | # 56 | RUBY_18 = c:\ruby 57 | RUBY_191 = c:\ruby-1.9.1 58 | GEMPKG = ruby-oci8-2.0.3-x86-mswin32-60.gem 59 | 60 | ext\oci8\oci8lib_18.so: 61 | $(RUBY_18)\bin\ruby -r fileutils -e "FileUtils.rm_rf('ruby18')" 62 | md ruby18 63 | cd ruby18 64 | $(RUBY_18)\bin\ruby ..\setup.rb config -- --with-runtime-check 65 | $(RUBY_18)\bin\ruby ..\setup.rb setup 66 | rem $(RUBY_18)\bin\ruby ..\setup.rb test 67 | cd .. 68 | copy ruby18\ext\oci8\oci8lib_18.so ext\oci8\oci8lib_18.so 69 | 70 | ext\oci8\oci8lib_191.so: 71 | $(RUBY_191)\bin\ruby -r fileutils -e "FileUtils.rm_rf('ruby191')" 72 | md ruby191 73 | cd ruby191 74 | $(RUBY_191)\bin\ruby ..\setup.rb config -- --with-runtime-check 75 | $(RUBY_191)\bin\ruby ..\setup.rb setup 76 | rem $(RUBY_191)\bin\ruby ..\setup.rb test 77 | cd .. 78 | copy ruby191\ext\oci8\oci8lib_191.so ext\oci8\oci8lib_191.so 79 | copy ruby191\lib\oci8.rb lib\oci8.rb 80 | 81 | $(GEMPKG): ext\oci8\oci8lib_18.so ext\oci8\oci8lib_191.so ruby-oci8.gemspec 82 | $(RUBY_191)\bin\gem build ruby-oci8.gemspec -- current 83 | 84 | test-win32-ruby18: $(GEMPKG) 85 | $(RUBY_18)\bin\gem install $(GEMPKG) --no-rdoc --no-ri --local 86 | $(RUBY_18)\bin\ruby -rubygems test\test_all.rb 87 | 88 | test-win32-ruby191: $(GEMPKG) 89 | $(RUBY_191)\bin\gem install $(GEMPKG) --no-rdoc --no-ri --local 90 | $(RUBY_191)\bin\ruby test\test_all.rb 91 | 92 | test-win32: test-win32-ruby18 test-win32-ruby191 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ruby-oci8 2 | ========= 3 | 4 | [![Gem Version](https://badge.fury.io/rb/ruby-oci8.svg)](http://badge.fury.io/rb/ruby-oci8) 5 | [![Tests](https://github.com/kubo/ruby-oci8/actions/workflows/tests.yml/badge.svg)](https://github.com/kubo/ruby-oci8/actions/workflows/tests.yml) 6 | 7 | What is ruby-oci8 8 | ----------------- 9 | 10 | Ruby-oci8 is a ruby interface for Oracle Database. The latest version 11 | is available for all Oracle versions after Oracle 10g including Oracle 12 | Instant Client. 13 | 14 | Use ruby-oci8 2.0.6 for Oracle 8 or use ruby-oci8 2.1.8 for Oracle 9i. 15 | 16 | Note that ruby 1.8 support was dropped in ruby-oci8 2.2.0. 17 | Use ruby-oci8 2.1.8 for ruby 1.8. 18 | 19 | What's new 20 | ---------- 21 | 22 | See {file:NEWS}. 23 | 24 | Sample one-liner 25 | ---------------- 26 | 27 | When you have an Oracle database server to which `sqlplus scott/tiger` can connect 28 | and `scott` user has `emp` table, you can select `emp` and print rows 29 | as CSV by the followig one liner. 30 | 31 | ruby -r oci8 -e "OCI8.new('scott', 'tiger').exec('select * from emp') do |r| puts r.join(','); end" 32 | 33 | Homepage 34 | -------- 35 | 36 | * http://www.rubydoc.info/github/kubo/ruby-oci8 37 | 38 | Installation 39 | ------------ 40 | 41 | * {file:docs/install-full-client.md Install for Oracle Full Client} 42 | * {file:docs/install-instant-client.md Install for Oracle Instant Client} 43 | * {file:docs/install-binary-package.md Install Binary Package} 44 | * {file:docs/install-on-osx.md Install on OS X} 45 | 46 | Report issues 47 | ------------- 48 | 49 | * {file:docs/report-installation-issue.md Report Installation Issues} 50 | * [The issues page on github](https://github.com/kubo/ruby-oci8/issues) 51 | 52 | Other documents 53 | --------------- 54 | 55 | * {file:docs/number-type-mapping.md Number Type Mapping between Oracle and Ruby} 56 | * {file:docs/timeout-parameters.md Timeout Parameters} 57 | * {file:docs/conflicts-local-connections-and-processes.md Conflicts between Local Connections and Child Process Handling on Unix} 58 | * {file:docs/hanging-after-inactivity.md Hanging After a Long Period of Inactivity} 59 | * {file:docs/bind-array-to-in_cond.md Bind an Array to IN-condition} 60 | * {file:docs/ldap-auth-and-function-interposition.md LDAP Authentication and Function Interposition} 61 | 62 | License 63 | ------- 64 | 65 | * {file:COPYING 2-clause BSD-style license} from ruby-oci8 2.1.3 66 | * {file:COPYING_old old Ruby license} until 2.1.2 67 | -------------------------------------------------------------------------------- /dist-files: -------------------------------------------------------------------------------- 1 | .yardopts 2 | COPYING 3 | COPYING_old 4 | ChangeLog 5 | Makefile 6 | NEWS 7 | README.md 8 | dist-files 9 | metaconfig 10 | pre-distclean.rb 11 | ruby-oci8.gemspec 12 | setup.rb 13 | docs/bind-array-to-in_cond.md 14 | docs/conflicts-local-connections-and-processes.md 15 | docs/hanging-after-inactivity.md 16 | docs/install-binary-package.md 17 | docs/install-full-client.md 18 | docs/install-instant-client.md 19 | docs/install-on-osx.md 20 | docs/ldap-auth-and-function-interposition.md 21 | docs/number-type-mapping.md 22 | docs/platform-specific-issues.md 23 | docs/report-installation-issue.md 24 | docs/timeout-parameters.md 25 | ext/oci8/.document 26 | ext/oci8/MANIFEST 27 | ext/oci8/apiwrap.c.tmpl 28 | ext/oci8/apiwrap.h.tmpl 29 | ext/oci8/apiwrap.rb 30 | ext/oci8/apiwrap.yml 31 | ext/oci8/attr.c 32 | ext/oci8/bind.c 33 | ext/oci8/connection_pool.c 34 | ext/oci8/encoding.c 35 | ext/oci8/env.c 36 | ext/oci8/error.c 37 | ext/oci8/extconf.rb 38 | ext/oci8/hook_funcs.c 39 | ext/oci8/lob.c 40 | ext/oci8/metadata.c 41 | ext/oci8/object.c 42 | ext/oci8/oci8.c 43 | ext/oci8/oci8.h 44 | ext/oci8/oci8lib.c 45 | ext/oci8/ocidatetime.c 46 | ext/oci8/ocihandle.c 47 | ext/oci8/ocinumber.c 48 | ext/oci8/oraconf.rb 49 | ext/oci8/oradate.c 50 | ext/oci8/oranumber_util.c 51 | ext/oci8/oranumber_util.h 52 | ext/oci8/plthook.h 53 | ext/oci8/plthook_elf.c 54 | ext/oci8/plthook_osx.c 55 | ext/oci8/plthook_win32.c 56 | ext/oci8/post-config.rb 57 | ext/oci8/stmt.c 58 | ext/oci8/thread_util.c 59 | ext/oci8/thread_util.h 60 | ext/oci8/util.c 61 | ext/oci8/win32.c 62 | lib/.document 63 | lib/oci8.rb 64 | lib/dbd/OCI8.rb 65 | lib/oci8/.document 66 | lib/oci8/bindtype.rb 67 | lib/oci8/check_load_error.rb 68 | lib/oci8/compat.rb 69 | lib/oci8/connection_pool.rb 70 | lib/oci8/cursor.rb 71 | lib/oci8/datetime.rb 72 | lib/oci8/encoding-init.rb 73 | lib/oci8/encoding.yml 74 | lib/oci8/metadata.rb 75 | lib/oci8/object.rb 76 | lib/oci8/oci8.rb 77 | lib/oci8/ocihandle.rb 78 | lib/oci8/oracle_version.rb 79 | lib/oci8/properties.rb 80 | lib/oci8/version.rb 81 | lib/ruby-oci8.rb 82 | test/README.md 83 | test/config.rb 84 | test/setup_test_object.sql 85 | test/setup_test_package.sql 86 | test/test_all.rb 87 | test/test_appinfo.rb 88 | test/test_array_dml.rb 89 | test/test_bind_array.rb 90 | test/test_bind_boolean.rb 91 | test/test_bind_raw.rb 92 | test/test_bind_string.rb 93 | test/test_bind_time.rb 94 | test/test_bind_integer.rb 95 | test/test_break.rb 96 | test/test_clob.rb 97 | test/test_connection_pool.rb 98 | test/test_connstr.rb 99 | test/test_encoding.rb 100 | test/test_datetime.rb 101 | test/test_dbi.rb 102 | test/test_dbi_clob.rb 103 | test/test_error.rb 104 | test/test_metadata.rb 105 | test/test_object.rb 106 | test/test_oci8.rb 107 | test/test_oracle_version.rb 108 | test/test_oradate.rb 109 | test/test_oranumber.rb 110 | test/test_package_type.rb 111 | test/test_properties.rb 112 | test/test_rowid.rb 113 | -------------------------------------------------------------------------------- /docs/bind-array-to-in_cond.md: -------------------------------------------------------------------------------- 1 | # @title Bind an Array to IN-condition 2 | 3 | Bind an Array to IN-condition 4 | ============================= 5 | 6 | Binding an arbitrary-length array to IN-condition is not simple. 7 | You need to create an SQL statement containing a comma-separated 8 | list whose length is same with the input data. 9 | 10 | Example: 11 | 12 | ids = [ ... ] # an arbitrary-length array containing user IDs. 13 | 14 | place_holder_string = Array.new(ids.length) {|index| ":id_#{index}"}.join(', ') 15 | # place_holder_string is: 16 | # ":id_0" if ids.length == 1 17 | # ":id_0, :id_1" if ids.length == 2 18 | # ... 19 | cursor = conn.parse("select * from users where id in (#{place_holder_string})") 20 | ids.each_with_index do |id, index| 21 | cursor.bind_param("id#{index}", id) # bind each element 22 | end 23 | cursor.exec() 24 | 25 | However this is awkward. So {OCI8.in_cond} was added in ruby-oci8 2.2.2. 26 | The above code is rewritten as follows: 27 | 28 | ids = [ ... ] # an arbitrary-length array containing user IDs. 29 | 30 | in_cond = OCI8::in_cond(:id, ids) 31 | cursor = conn.exec("select * from users where id in (#{in_cond.names})", *in_cond.values) 32 | 33 | or 34 | 35 | ids = [ ... ] # an arbitrary-length array containing user IDs. 36 | 37 | in_cond = OCI8::in_cond(:id, ids, Integer) # set the data type explicitly 38 | cursor = conn.exec("select * from users where id in (#{in_cond.names})", *in_cond.values) 39 | -------------------------------------------------------------------------------- /docs/conflicts-local-connections-and-processes.md: -------------------------------------------------------------------------------- 1 | # @title Conflicts between Local Connections and Child Process Handling on Unix 2 | 3 | Conflicts between Local Connections and Child Process Handling on Unix 4 | ====================================================================== 5 | 6 | Background 7 | ---------- 8 | 9 | When a local (not-TCP) Oracle connection is established on Unix, 10 | Oracle client library changes signal handlers in the process to reap 11 | all dead child processes. However it conflicts with child process 12 | handling in ruby. 13 | 14 | Problem 1: It trashes child process handling of Open3::popen. 15 | ---------- 16 | 17 | require 'oci8' 18 | require 'open3' 19 | 20 | Open3::popen3('true') do |i, o, e, w| 21 | p w.value 22 | end 23 | 24 | conn = OCI8.new(username, password) 25 | puts "establish a local connection" 26 | 27 | Open3::popen3('true') do |i, o, e, w| 28 | p w.value 29 | end 30 | 31 | The above code outputs the following result: 32 | 33 | # 34 | establish a local connection 35 | nil 36 | 37 | `w.value` after a local connection doesn't work because Oracle reaps 38 | the child process on the process termination before ruby detects it. 39 | 40 | Problem 2: It creates defunct processes after disconnection if signal handlers are reset. 41 | ---------- 42 | 43 | The `system` function overwrites signal handlers. 44 | It fixes the problem 1 as follows. 45 | 46 | require 'oci8' 47 | require 'open3' 48 | 49 | Open3::popen3('true') do |i, o, e, w| 50 | p w.value 51 | end 52 | 53 | conn = OCI8.new(username, password) # Signal handlers are changed here. 54 | puts "establish a local connection" 55 | system('true') # Signal handlers are reset here. 56 | 57 | Open3::popen3('true') do |i, o, e, w| 58 | p w.value 59 | end 60 | 61 | The above code outputs the following result: 62 | 63 | # 64 | establish a local connection 65 | # 66 | 67 | `w.value` after a local connection works. 68 | 69 | However it makes another problem. 70 | 71 | require 'oci8' 72 | 73 | conn = OCI8.new(username, password) # Signal handlers are changed here. 74 | # An Oracle server process is created here. 75 | puts "establish a local connection" 76 | system('true') # Signal handlers are reset here. 77 | 78 | conn.logoff # The Oracle server process exits and become defunct. 79 | 80 | # ... do other stuffs... 81 | 82 | If a program repeatedly creates a local connection and disconnects it, 83 | the number of defunct processes increases gradually. 84 | 85 | Solution: BEQUEATH_DETACH=YES 86 | ---------- 87 | 88 | By setting [BEQUEATH_DETACH=YES][] in `sqlnet.ora`, Oracle client library 89 | doesn't change signal handlers. The above two problems are fixed. 90 | 91 | Oracle client library reads `sqlnet.ora` in the following locations: 92 | 93 | * `$TNS_ADMIN/sqlnet.ora` if the environment variable `TNS_ADMIN` 94 | is set and `$TNS_ADMIN/sqlnet.ora` exists. 95 | Otherwise, `$ORACLE_HOME/network/admin/sqlnet.ora`. 96 | * `$HOME/.sqlnet.ora` 97 | 98 | [BEQUEATH_DETACH=YES]: https://docs.oracle.com/database/121/NETRF/sqlnet.htm#NETRF183 99 | -------------------------------------------------------------------------------- /docs/hanging-after-inactivity.md: -------------------------------------------------------------------------------- 1 | # @title Hanging After a Long Period of Inactivity 2 | 3 | Hanging After a Long Period of Inactivity 4 | ========================================= 5 | 6 | When a database connection hangs after a long period of inactivity, 7 | this document may help you. 8 | 9 | When a firewall or a NAT proxy resides between Oracle database server 10 | and client, it sometimes drops inactive connections as dead ones. If a 11 | client connects to an Oracle server and tries to use the connection 12 | after a long sleep (> 1 hour), the client may hang in an effort to use the 13 | dropped connection. This issue will be solved by setting TCP keepalive 14 | packets whose keepalive time parameter is smaller than the inactive 15 | connection timeout. 16 | 17 | TCP keepalive is enabled by [(ENABLE=broken)][] in a connect 18 | descriptor. If you use easy connect naming such as `//hostname/service_name`, 19 | ruby-oci8 sets the parameter on behalf of you when the `tcp_keepalive` 20 | property is set. (This is available since ruby-oci8 2.2.4.) 21 | 22 | OCI8.properties[:tcp_keepalive] = true 23 | conn = OCI8.new(username, password, '//hostname/service_name') 24 | 25 | This is equivalent to the following: 26 | 27 | connect_descriptor = "(DESCRIPTION=(ENABLE=broken)(ADDRESS=(PROTOCOL=tcp)(HOST=hostname)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=service_name)))" 28 | conn = OCI8.new(username, password, connect_descriptor) 29 | 30 | The default TCP keepalive time is two hours, which may be larger 31 | than the inactive connection timeout. The default 32 | value in the system is configurable via [procfs and sysctl on Linux][] 33 | or [registry parameters on Windows][]. If you have no privilege to 34 | customize the system, you can change per-connection keepalive time 35 | by the `tcp_keepalive_time` property. 36 | 37 | OCI8.properties[:tcp_keepalive_time] = 600 # 10 minutes 38 | 39 | It is supported on the following platforms since ruby-oci8 2.2.4. 40 | 41 | * Linux i386 and x86_64 42 | * macOS x86_64 43 | * Windows x86 and x64 44 | * Solaris x86_64 45 | 46 | When it is set on unsupported platforms, you get `NotImplementedError`. 47 | 48 | This feature is implemented with hackish way. When 49 | `tcp_keepalive_time` is set, Oracle client library's 50 | procedure linkage table is modified to intercept [setsockopt][] system 51 | call. When Oracle client library issues the system call which enables 52 | TCP keepalive, [ruby-oci8 changes][] the per-connection TCP keepalive time 53 | immediately. 54 | 55 | I hope that Oracle adds a new OCI handle attribute to support this 56 | feature natively in the future. 57 | 58 | [(ENABLE=broken)]: https://docs.oracle.com/database/121/NETRF/tnsnames.htm#CHDCDGCE 59 | [procfs and sysctl on Linux]: http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html 60 | [registry parameters on Windows]: https://blogs.technet.microsoft.com/nettracer/2010/06/03/things-that-you-may-want-to-know-about-tcp-keepalives/ 61 | [plthook]: https://github.com/kubo/plthook 62 | [setsockopt]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/setsockopt.html 63 | [ruby-oci8 changes]: https://github.com/kubo/ruby-oci8/blob/ruby-oci8-2.2.4/ext/oci8/hook_funcs.c#L302-L318 64 | -------------------------------------------------------------------------------- /docs/install-binary-package.md: -------------------------------------------------------------------------------- 1 | # @title Install Binary Package 2 | 3 | Install Binary Package 4 | ====================== 5 | 6 | Windows 7 | ------- 8 | 9 | You need to install Oracle client in advance. 10 | If you don't have installed Oracle client, install {file:docs/install-instant-client.md#Install_Oracle_Instant_Client_Packages instant client}. Only the Basic or Basic Lite package is needed to use ruby-oci8. 11 | However it is better to install SQL*Plus also because it is usable to check whether 12 | a problem is in Oracle setting or in ruby-oci8. 13 | 14 | Run the following command to install ruby-oci8. 15 | 16 | gem install ruby-oci8 17 | 18 | If you uses mswin32 ruby, use the following command instead. 19 | 20 | gem install --platform x86-mingw32 ruby-oci8 21 | or 22 | gem install --platform x64-mingw32 ruby-oci8 23 | 24 | Other platforms 25 | --------------- 26 | 27 | We doesn't make binary gems for other platforms. 28 | If you need to distribute a binary gem, compile ruby-oci8 and run the following command. 29 | 30 | gem build ruby-oci8.gemspec -- current 31 | 32 | Note that you need to compile it on the oldest versions of the platform you support 33 | and for the oldest versions of the Oracle databases. 34 | 35 | If you could not fix the Oracle versions, compile ruby-oci8 and make a gem as follows: 36 | 37 | gzip -dc ruby-oci8-VERSION.tar.gz | tar xvf - 38 | cd ruby-oci8-VERSION 39 | ruby setup.rb config -- --with-runtime-check 40 | make 41 | gem build ruby-oci8.gemspec -- current 42 | 43 | When the option `--with-runtime-check` is set, ruby-oci8 checks the Oracle version 44 | and available OCI functions at runtime. 45 | -------------------------------------------------------------------------------- /docs/install-full-client.md: -------------------------------------------------------------------------------- 1 | # @title Install for Oracle Full Client 2 | 3 | Install for Oracle Full Client 4 | ============================== 5 | 6 | Introduction 7 | ------------ 8 | 9 | This page explains the way to install ruby-oci8 for Oracle Full Client 10 | installations. 11 | 12 | For Oracle Instant Client, look at {file:docs/install-instant-client.md}. 13 | For Windows, look at {file:docs/install-binary-package.md} unless you 14 | have a special need to compile ruby-oci8 by yourself. 15 | 16 | Check the environment 17 | --------------------- 18 | 19 | ### Oracle installation 20 | 21 | Run the following command and confirm it works fine. If it doesn't 22 | work well, you need to ask to your database administrator. 23 | 24 | sqlplus USERNAME/PASSWORD 25 | 26 | ### ruby installation 27 | 28 | Run the following command. If it ends with "can't find header files 29 | for ruby" or "ruby: no such file to load -- mkmf (LoadError)", you need 30 | to install ruby-devel(redhat) or ruby-dev(debian/ubuntu). 31 | 32 | ruby -r mkmf -e "" 33 | 34 | ### development tools 35 | 36 | You need a C compiler and development tools such as make or nmake. 37 | Note that they must be same with ones used to compile the ruby. 38 | For example, you need Oracle Solaris Studio, not gcc, for ruby 39 | compiled by Oracle Solaris Studio. 40 | 41 | Installation 42 | ------------ 43 | 44 | If you get a problem in the following steps, look at {file:docs/platform-specific-issues.md} 45 | and {file:docs/report-installation-issue.md}. 46 | 47 | ### Set the library search path 48 | 49 | #### UNIX 50 | 51 | Set the library search path, whose name depends on the OS, to point to 52 | $ORACLE\_HOME/lib. If the database is 64-bit and the ruby is 32-bit, 53 | use $ORACLE\_HOME/lib32 instead. 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
OS library search path
Linux LD_LIBRARY_PATH
Solaris 32-bit ruby LD_LIBRARY_PATH_32 or LD_LIBRARY_PATH
Solaris 64-bit ruby LD_LIBRARY_PATH_64 or LD_LIBRARY_PATH
HP-UX PA-RISC 32-bit ruby SHLIB_PATH
HP-UX PA-RISC 64-bit ruby LD_LIBRARY_PATH
HP-UX IA64 LD_LIBRARY_PATH
Mac OS X DYLD_LIBRARY_PATH
AIX LIBPATH
70 | 71 | Do not forget to export the variable as follows: 72 | 73 | $ LD_LIBRARY_PATH=$ORACLE_HOME/lib 74 | $ export LD_LIBRARY_PATH 75 | 76 | #### Windows(mswin32, mingw32, cygwin) 77 | 78 | If sqlplus runs correctly, library search path has no problem. 79 | 80 | ### gem package 81 | 82 | Run the following command. 83 | 84 | gem install ruby-oci8 85 | 86 | ### tar.gz package 87 | 88 | #### Download the source code 89 | 90 | Download the latest tar.gz package from [download page][]. 91 | 92 | #### Run make and install 93 | 94 | ##### UNIX or Windows(mingw32, cygwin) 95 | 96 | gzip -dc ruby-oci8-VERSION.tar.gz | tar xvf - 97 | cd ruby-oci8-VERSION 98 | make 99 | make install 100 | 101 | note: If you use '`sudo`', use it only when running '`make install`'. 102 | '`sudo`' doesn't pass library search path to the executing command for security reasons. 103 | 104 | ##### Windows(mswin32) 105 | 106 | gzip -dc ruby-oci8-VERSION.tar.gz | tar xvf - 107 | cd ruby-oci8-VERSION 108 | nmake 109 | nmake install 110 | 111 | [download page]: https://bintray.com/kubo/generic/ruby-oci8 112 | -------------------------------------------------------------------------------- /docs/install-instant-client.md: -------------------------------------------------------------------------------- 1 | # @title Install for Oracle Instant Client 2 | 3 | Install for Oracle Instant Client 4 | ================================= 5 | 6 | Introduction 7 | ------------ 8 | 9 | This page explains the way to install ruby-oci8 for Oracle Instant Client. 10 | 11 | Look at {file:docs/install-full-client.md} for Oracle Full Client. 12 | 13 | Look at {file:docs/install-binary-package.md} for Windows unless you 14 | have a special need to compile ruby-oci8 by yourself. 15 | 16 | Look at {file:docs/install-on-osx.md} for OS X. 17 | 18 | Install Oracle Instant Client Packages 19 | -------------------------------------- 20 | 21 | ### Download Instant Client Packages 22 | 23 | Download the following packages from [Oracle Technology Network](http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html). 24 | 25 | * Instant Client Package - Basic or Basic Lite 26 | * Instant Client Package - SDK 27 | * Instant Client Package - SQL*Plus 28 | 29 | Note: use 32-bit packages for 32-bit ruby even though the OS is 64-bit. 30 | 31 | ### UNIX zip packages 32 | 33 | Unzip the packages as follows: 34 | 35 | mkdir /opt 36 | mkdir /opt/oracle 37 | cd /opt/oracle 38 | unzip path/to/instantclient-basic-OS-VERSION.zip 39 | unzip path/to/instantclient-sdk-OS-VERSION.zip 40 | unzip path/to/instantclient-sqlplus-OS-VERSION.zip 41 | 42 | If /opt/oracle/instantclient12_1/libclntsh.so is not found, make 43 | a symbolic link to link the library. 44 | 45 | cd /opt/oracle/instantclient12_1 46 | ln -s libclntsh.so.12.1 libclntsh.so 47 | 48 | Note: 49 | 50 | * use libclntsh.sl instead of libclntsh.so on HP-UX PA-RISC. 51 | * skip this step for AIX. 52 | * run `yum install libaio` also on Redhat. 53 | * run `apt-get install libaio1` also on Ubuntu. 54 | 55 | Set the library search path, whose name depends on the OS, to point to 56 | the installed directory. 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
OS library search path
Linux LD_LIBRARY_PATH
Solaris 32-bit ruby LD_LIBRARY_PATH_32 or LD_LIBRARY_PATH
Solaris 64-bit ruby LD_LIBRARY_PATH_64 or LD_LIBRARY_PATH
HP-UX PA-RISC 32-bit ruby SHLIB_PATH
HP-UX PA-RISC 64-bit ruby LD_LIBRARY_PATH
HP-UX IA64 LD_LIBRARY_PATH
AIX LIBPATH
72 | Note: Look at {file:docs/install-on-osx.md} for OS X. 73 | 74 | For example: 75 | 76 | $ LD_LIBRARY_PATH=/opt/oracle/instantclient_12_1 77 | $ export LD_LIBRARY_PATH 78 | 79 | Though many instant client pages indicate that the environment varialbe 80 | `ORACLE_HOME` should be set, it isn't necessary unless Oracle Net 81 | configuration files such as `tnsnames.ora` and `sqlnet.ora` are in 82 | `$ORACLE_HOME/network/admin/`. 83 | 84 | ### Linux rpm packages 85 | 86 | Install the downloaded packages as follows: 87 | 88 | rpm -i path/to/oracle-instantclient-basic-VERSION-ARCH.rpm 89 | rpm -i path/to/oracle-instantclient-devel-VERSION-ARCH.rpm 90 | rpm -i path/to/oracle-instantclient-sqlplus-VERSION-ARCH.rpm 91 | 92 | Set LD_LIBRARY_PATH to point to the directory where libclntsh.so is installed. 93 | 94 | For example: 95 | 96 | $ LD_LIBRARY_PATH=/usr/lib/oracle/12.1/client/lib 97 | $ export LD_LIBRARY_PATH 98 | 99 | ### Windows 100 | 101 | Unzip the packages and set `PATH` to point to the directory where `OCI.DLL` is installed. 102 | 103 | If `require "ruby-oci8"` raises a load error such as "OCI.DLL: 126(The 104 | specified module could not be found. )", either `OCI.DLL` or a DLL depended 105 | by `OCI.DLL` could not be found in `PATH`. 106 | 107 | If `OCI.DLL` is surely in `PATH`, the missing module is a Visual C++ runtime 108 | library in most cases. You need to install "Microsoft Visual C++ Redistributable 109 | Package" or copy a runtime library to the directory where `ruby.exe` resides. 110 | 111 | | Oracle Version | Package | Runtime Library| 112 | |---|---|---| 113 | | 18.3 | [Microsoft Visual C++ 2013 Redistributable Package][2013] | MSVCR120.DLL | 114 | | 12.2.0.x | [Microsoft Visual C++ 2013 Redistributable Package][2013] | MSVCR120.DLL | 115 | | 12.1.0.x | [Microsoft Visual C++ 2010 Redistributable Package][2010] | MSVCR100.DLL | 116 | | 11.2.0.x | Microsoft Visual C++ 2005 SP1 Redistributable Package ([x86][2005SP1_x86], [x64][2005SP1_x64]) | MSVCR80.DLL(The file version must be 8.0.50727.762.) | 117 | | 11.1.0.x | [No separate redistributable package][2003] | MSVCR71.DLL | 118 | | 10.2.0.x | [No separate redistributable package][2003] | MSVCR71.DLL | 119 | 120 | [2013]: https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads 121 | [2010]: http://www.microsoft.com/en-us/download/details.aspx?id=26999 122 | [2005SP1_x86]: https://www.microsoft.com/en-us/download/details.aspx?id=5638 123 | [2005SP1_x64]: https://www.microsoft.com/en-us/download/details.aspx?id=18471 124 | [2003]: http://stackoverflow.com/questions/1596167/where-to-download-microsoft-visual-c-2003-redistributable#6132093 125 | 126 | Check the environment 127 | --------------------- 128 | 129 | ### Oracle installation 130 | 131 | Run the following command and confirm it works fine. If it doesn't 132 | work well, check `LD_LIBRARY_PATH` or PATH. 133 | 134 | sqlplus USERNAME/PASSWORD 135 | 136 | ### ruby installation 137 | 138 | Run the following command. If it ends with "can't find header files 139 | for ruby" or "ruby: no such file to load -- mkmf (LoadError)", you need 140 | to install ruby-devel(redhat) or ruby-dev(debian/ubuntu). 141 | 142 | ruby -r mkmf -e "" 143 | 144 | ### development tools 145 | 146 | You need a C compiler and development tools such as make or nmake. 147 | Note that they must be same with ones used to compile the ruby. 148 | For example, you need Oracle Solaris Studio, not gcc, for ruby 149 | compiled by Oracle Solaris Studio. 150 | 151 | Installation 152 | ------------ 153 | 154 | If you get a problem in the following steps, look at {file:docs/platform-specific-issues.md} 155 | and {file:docs/report-installation-issue.md}. 156 | 157 | ### gem package 158 | 159 | Run the following command. 160 | 161 | gem install ruby-oci8 162 | 163 | If you get a problem, look at {file:docs/platform-specific-issues.md} 164 | and {file:docs/report-installation-issue.md}. 165 | 166 | ### tar.gz package 167 | 168 | #### Download the source code 169 | 170 | Download the latest tar.gz package from [download page][]. 171 | 172 | Note: if you are using Windows and you have no special need to compile 173 | it by yourself, look at {file:docs/install-binary-packge.md}. 174 | 175 | #### Run make and install 176 | 177 | ##### UNIX or Windows(mingw32, cygwin) 178 | 179 | gzip -dc ruby-oci8-VERSION.tar.gz | tar xvf - 180 | cd ruby-oci8-VERSION 181 | make 182 | make install 183 | 184 | note: If you use '`sudo`', use it only when running '`make install`'. 185 | '`sudo`' doesn't pass library search path to the executing command for security reasons. 186 | 187 | ##### Windows(mswin32) 188 | 189 | gzip -dc ruby-oci8-VERSION.tar.gz | tar xvf - 190 | cd ruby-oci8-VERSION 191 | nmake 192 | nmake install 193 | 194 | [download page]: https://bintray.com/kubo/generic/ruby-oci8 195 | -------------------------------------------------------------------------------- /docs/install-on-osx.md: -------------------------------------------------------------------------------- 1 | # @title Install ruby-oci8 on macOS 2 | 3 | Install ruby-oci8 on macOS 4 | ========================= 5 | 6 | **Note: Ruby-oci8 doesn't run on Apple Silicon because Oracle instant client 7 | for Apple Silicon has not been released yet.** 8 | 9 | Prerequisite 10 | ------------ 11 | 12 | * Command line tools for Xcode or Xcode (by executing `xcode-select --install`) or [Xcode] 13 | 14 | Install Oracle Instant Client Packages 15 | -------------------------------------- 16 | 17 | If you have installed [Homebrew], use the following command: 18 | 19 | ```shell 20 | $ brew tap InstantClientTap/instantclient 21 | $ brew install instantclient-basic # or instantclient-basiclite 22 | $ brew install instantclient-sdk 23 | $ brew install instantclient-sqlplus # (optionally) 24 | ``` 25 | 26 | Otherwise, look at this [page][OTN] and set the environment variable 27 | `OCI_DIR` to point the the directory where instant client is installed. 28 | Ruby-oci8 installation script checks the directory. 29 | 30 | ```shell 31 | export OCI_DIR=$HOME/Downloads/instantclient_19_8 # for example 32 | ``` 33 | 34 | Install ruby-oci8 35 | ----------------- 36 | 37 | Note that `/usr/bin/ruby` isn't available. You need to use [`rbenv`] or so. 38 | 39 | ```shell 40 | $ gem install ruby-oci8 41 | ``` 42 | 43 | [Homebrew]: http://brew.sh/ 44 | [OTN]: https://www.oracle.com/database/technologies/instant-client/macos-intel-x86-downloads.html#ic_osx_inst 45 | [Xcode]: https://apps.apple.com/us/app/xcode/id497799835 46 | [`rbenv`]: https://github.com/rbenv/rbenv 47 | -------------------------------------------------------------------------------- /docs/ldap-auth-and-function-interposition.md: -------------------------------------------------------------------------------- 1 | # @title LDAP Authentication and Function Interposition 2 | 3 | LDAP Authentication and Function Interposition 4 | ============================================== 5 | 6 | Problems 7 | -------- 8 | 9 | The following code may trigger segmentation faults or unexpected behaviours. 10 | 11 | require 'pg' # or any modules using LDAP such as ActiveLdap 12 | require 'oci8' 13 | 14 | conn = OCI8.new('username/password@dbname.example.com') 15 | ... 16 | 17 | It happens when all the following conditions are satisfied. 18 | 19 | * The platform is Unix 20 | * The PostgreSQL client library, which `pg` depends, was compiled with LDAP support. 21 | * LDAP authentication is used to connect to an Oracle server. 22 | 23 | It is caused by function interposition as follows: 24 | 25 | * The ruby process loads `pq` and its depending libraries such as 26 | `libpq.so`(PostgreSQL client library) and `libldap_r.so`(LDAP library). 27 | * Then it loads `oci8` and its depending libraries such as 28 | `libclntsh.so`(Oracle client library). 29 | * When LDAP authentication is used, `libclntsh.so` tries to use 30 | LDAP functions in the library. 31 | * However it uses LDAP functions in `libldap_r.so` because the function 32 | in the firstly loaded library is used when more than one library exports 33 | functions whose names are same. 34 | * It triggers segmentation faults or unexpected behaviours because 35 | implementations of LDAP functions are different even though their names 36 | are same. 37 | 38 | The reverse may cause same results by the following code. 39 | 40 | require 'oci8' 41 | require 'pg' 42 | 43 | ... connect to PostgreSQL using LDAP ... 44 | 45 | ### Note for macOS 46 | 47 | Libraries in two-level namespaces are free from function interposition on macOS. 48 | See the second paragraph of [this document][mach-o]. If `TWOLEVEL` is 49 | found in the output of `otool -hV /path/to/library`, it is in a 50 | two-level namespace. Otherwise it is in a single-level (flat) namespace. 51 | 52 | Oracle client library (`libclntsh.dylib.12.1`) is in a flat namespace. 53 | It suffers from function interposition. 54 | 55 | $ otool -hV libclntsh.dylib.12.1 56 | Mach header 57 | magic cputype cpusubtype caps filetype ncmds sizeofcmds flags 58 | MH_MAGIC_64 X86_64 ALL 0x00 DYLIB 19 2360 DYLDLINK NO_REEXPORTED_DYLIBS MH_HAS_TLV_DESCRIPTORS 59 | 60 | PostgreSQL client library (`libpq.5.dylib`) installed by [brew][] depends on an OS-supplied LDAP library. 61 | 62 | $ otool -L libpq.5.dylib 63 | libpq.5.dylib: 64 | /usr/local/opt/postgresql/lib/libpq.5.dylib (compatibility version 5.0.0, current version 5.9.0) 65 | /usr/local/opt/openssl/lib/libssl.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0) 66 | /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0) 67 | /System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos (compatibility version 5.0.0, current version 6.0.0) 68 | /System/Library/Frameworks/LDAP.framework/Versions/A/LDAP (compatibility version 1.0.0, current version 2.4.0) 69 | /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.0.0) 70 | 71 | The OS-supplied LDAP library is in a two-level namespace. 72 | 73 | $ otool -hV /System/Library/Frameworks/LDAP.framework/Versions/A/LDAP 74 | Mach header 75 | magic cputype cpusubtype caps filetype ncmds sizeofcmds flags 76 | MH_MAGIC_64 X86_64 ALL 0x00 DYLIB 22 2528 NOUNDEFS DYLDLINK TWOLEVEL NO_REEXPORTED_DYLIBS APP_EXTENSION_SAFE 77 | 78 | As a result, the PostgreSQL client library is free from function interposition. 79 | 80 | Solution 1 81 | ---------- 82 | 83 | If you don't connect to PostgreSQL using LDAP, use the following code. 84 | 85 | require 'oci8' # This must be before "require 'pg'". 86 | require 'pg' 87 | 88 | conn = OCI8.new('username/password@dbname.example.com') 89 | ... 90 | ... connect to a PostgreSQL server ... 91 | 92 | Oracle client library uses LDAP functions in `libclntsh.so` because `libclntsh.so` 93 | is loaded before `libldap_r.so`. 94 | 95 | Don't connect to PostgreSQL using LDAP because `libpq.so` tries to use 96 | LDAP functions in `libldap_r.so` but faultily uses functions in `libclntsh.so`. 97 | 98 | Note for macOS: This fixes all function interposition issues if the LDAP library 99 | in a two-level namespace. 100 | 101 | Solution 2 102 | ---------- 103 | 104 | If LDAP is used to connect to both Oracle and PostgreSQL and the platform 105 | is Linux or macOS, use ruby-oci8 2.2.4 or later and use the following code. 106 | 107 | require 'pg' 108 | require 'oci8' # This must be after "require 'pg'". 109 | 110 | conn = OCI8.new('username/password@dbname.example.com') 111 | ... 112 | ... connect to a PostgreSQL server using LDAP ... 113 | 114 | PostgreSQL client library uses LDAP functions in `libldap_r.so` because `libldap_r.so` 115 | is loaded before `libclntsh.so`. 116 | 117 | Oracle client library uses LDAP functions in `libclntsh.so` because ruby-oci8 118 | forcedly modifies PLT (Procedure Linkage Table) entries to point to 119 | functions in `libclntsh.so` if they point to functions in other libraries. 120 | (PLT is equivalent to IAT (Import Address Table) on Windows.) 121 | 122 | [mach-o]: https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/MachOTopics/1-Articles/executing_files.html 123 | [brew]: http://brew.sh/ 124 | -------------------------------------------------------------------------------- /docs/number-type-mapping.md: -------------------------------------------------------------------------------- 1 | # @title Number Type Mapping between Oracle and Ruby 2 | 3 | Number Type Mapping between Oracle and Ruby 4 | =========================================== 5 | 6 | Default mapping 7 | --------------- 8 | 9 | Oracle numbers in select statements are fetched as followings by default: 10 | 11 | | Oracle Data Type | Ruby Class | 12 | |---|---| 13 | | NUMBER(prec) or NUMBER(prec, 0) | Integer | 14 | | NUMBER(prec, scale) where prec < 15 and scale != 0 | Float | 15 | | NUMBER(prec, scale) where prec >= 15 and scale != 0 | BigDecimal | 16 | | FLOAT or FLOAT(prec) | Float | 17 | | NUMBER without precision and scale | BigDecimal | 18 | | number type returned by functions or calculated number | BigDecimal | 19 | | BINARY_FLOAT | Float | 20 | | BINARY_DOUBLE | Float | 21 | 22 | When the data type is within Integer or Float class, it is fetched 23 | as Integer or Float. Otherwise, BigDecimal. 24 | 25 | Note that the mapping is determined by the column definition in 26 | select statements, not by the actual value fetched. 27 | For example the column in `select count(*) from table_name` is 28 | fetched as BigDecimal because it is returned from `count` function. 29 | 30 | The mapping is customizable by `OCI8::BindType::Mapping`. 31 | The default values of Oracle number data type mapping are: 32 | 33 | # NUMBER or FLOAT data type, used for the first six rows in the above table 34 | OCI8::BindType::Mapping[:number] = OCI8::BindType::Number 35 | # BINARY_FLOAT data type, used for the seventh row in the above table 36 | OCI8::BindType::Mapping[:binary_float] = OCI8::BindType::BinaryDouble 37 | # BINARY_DOUBLE data type, used for the eighth row in the above table 38 | OCI8::BindType::Mapping[:binary_double] = OCI8::BindType::BinaryDouble 39 | 40 | `OCI8::BindType::Number` checks precision and scale to determine 41 | ruby class. The first four rows in the above table are hard-coded. 42 | The fifth and sixth rows are, however, customizable by 43 | `OCI8::BindType::Mapping[:number_no_prec_setting]` and 44 | `OCI8::BindType::Mapping[:number_unknown_prec]` respectively. 45 | 46 | The default values are: 47 | 48 | OCI8::BindType::Mapping[:number_no_prec_setting] = OCI8::BindType::BigDecimal 49 | OCI8::BindType::Mapping[:number_unknown_prec] = OCI8::BindType::BigDecimal 50 | 51 | The mapping may be changed as follows in future. 52 | 53 | | Oracle Data Type | Ruby Class | 54 | |---|---| 55 | | NUMBER(prec) or NUMBER(prec, 0) | Integer | 56 | | other NUMBER | OraNumber | 57 | | BINARY_FLOAT | Float | 58 | | BINARY_DOUBLE | Float | 59 | 60 | Customize mapping 61 | ----------------- 62 | 63 | Add the following code to fetch all number or float columns as {OraNumber}. 64 | 65 | OCI8::BindType::Mapping[:number] = OCI8::BindType::OraNumber 66 | 67 | Otherwise, add the following code to customize the fifth and sixth rows only 68 | in the above table. 69 | 70 | OCI8::BindType::Mapping[:number_no_prec_setting] = OCI8::BindType::OraNumber 71 | OCI8::BindType::Mapping[:number_unknown_prec] = OCI8::BindType::OraNumber 72 | 73 | If you want to fetch numbers as Integer or Float by its actual value, use 74 | the following code: 75 | 76 | # Fetch numbers as Integer when their fractional part is zero. 77 | # Otherwise, Float. For example when a column contains 10 and 78 | # 10.1, they are fetched as Integer and Float respectively. 79 | OCI8::BindType::Mapping[:number] = OCI8::BindType::BasicNumberType 80 | -------------------------------------------------------------------------------- /docs/platform-specific-issues.md: -------------------------------------------------------------------------------- 1 | # @title Platform Specific Issues 2 | 3 | Platform Specific Issues 4 | ======================== 5 | 6 | Linux 7 | ----- 8 | 9 | ### Ubuntu 11.10 (Oneiric Ocelot) 10 | 11 | If the following error occurs even though libc6-dev is installed, 12 | 13 | Do you install glibc-devel(redhat) or libc6-dev(debian)? 14 | You need /usr/include/sys/types.h to compile ruby-oci8. 15 | 16 | You need to use ruby-oci8 2.1.0 or upper. Otherwise, run the following command and re-install ruby-oci8. 17 | 18 | $ sudo ln -s /usr/include/linux/ /usr/include/sys 19 | 20 | ### General Linux 21 | 22 | Use the same bit-width of libraries with ruby. For example, x86\_64 23 | instant client for x86\_64 ruby and 32-bit instant client for 32-bit 24 | ruby. It depends on the ruby but not on the OS. As for full client, 25 | x86\_64 ruby cannot use with 32-bit full client, but 32-bit ruby can 26 | use with 64-bit full client because 32-bit libraries are in 27 | $ORACLE\_HOME/lib32. 28 | 29 | To check which type of ruby do you use: 30 | 31 | file `which ruby` 32 | 33 | Note: "`" is a back quote. 34 | 35 | Mac OS X 36 | -------- 37 | 38 | ### OS X 10.7+ 39 | 40 | Use the latest 64-bit instant client. The older 64-bit (10.2.0.4) instant client [doesn't work](https://forums.oracle.com/forums/thread.jspa?threadID=2187558). The older 32-bit instant client will work but only with 32-bit ruby or jruby (using JDBC). 41 | 42 | ### Intel Mac (64-bit) 43 | 44 | See [How to setup Ruby and Oracle Instant Client on Snow Leopard](http://blog.rayapps.com/2009/09/06/how-to-setup-ruby-and-oracle-instant-client-on-snow-leopard/). 45 | Note that setting the environment variable RC\_ARCHS=x86\_64 instead of ARCHFLAGS="-arch x86\_64" will work fine also. 46 | 47 | ### Intel Mac (32-bit) 48 | 49 | See [How to setup Ruby and Oracle Instant Client on Snow Leopard](http://blog.rayapps.com/2009/09/06/how-to-setup-ruby-and-oracle-instant-client-on-snow-leopard/). Note that you need to replace x86\_64 with i386 in the blog. 50 | 51 | The Intel Instant client is for Mac OS X 10.5 Leopard. If you are using 10.4 Tiger, 52 | use one of the following workarounds. 53 | 54 | * compile ruby as ppc. (look at [How to setup Ruby and Oracle client on Intel Mac](http://blog.rayapps.com/2007/08/27/how-to-setup-ruby-and-oracle-client-on-intel-mac/)) 55 | * use [ruby-odbc](http://www.ch-werner.de/rubyodbc/) and a third party ODBC driver ([Actual Technologies](http://www.actualtechnologies.com) or [OpenLink Software](http://uda.openlinksw.com/)). 56 | * use JRuby and Oracle JDBC driver. 57 | 58 | ### PowerPC Mac 59 | 60 | See [How to setup Ruby and Oracle Instant Client on Snow Leopard](http://blog.rayapps.com/2009/09/06/how-to-setup-ruby-and-oracle-instant-client-on-snow-leopard/). Note that you need to replace x86\_64 with ppc in the blog. 61 | 62 | Solaris 63 | ------- 64 | 65 | You need a same compiler which is used to make ruby itself. 66 | For example, if the ruby is compiled by gcc, you need gcc. If it is compiled by Oracle Solaris Studio 67 | (formerly called as Sun Studio), you need Oracle Solaris Studio. 68 | 69 | If you use Blastwave.org's ruby and want not to install Sun Studio, 70 | you can edit rbconfig.rb by your self. [(look at here)](http://forum.textdrive.com/viewtopic.php?id=12630) 71 | 72 | If you use Sunfreeware.com's ruby and 73 | 74 | $ ruby -r rbconfig -e "p Config::CONFIG['GNU_LD']" 75 | 76 | prints "yes", you may need to edit rbconfig.rb distributed with the ruby 77 | as follows: 78 | 79 | from: CONFIG["LDFLAGS"] = "-L. -Wl,-E" 80 | to: CONFIG["LDFLAGS"] = "-L. " 81 | 82 | FreeBSD 83 | ------- 84 | 85 | Oracle8-client port isn't available because it is too old. 86 | You need to use linux emulator. 87 | 88 | I have not run ruby-oci8 on linux emulator, but I guess it will 89 | run as follows. If it works, please tell me. 90 | 91 | If FreeBSD has a cross-compiler which can generate linux binaries, 92 | use it to compile ruby and ruby-oci8. 93 | 94 | If FreeBSD doesn't have such a compiler, install a linux distribution 95 | which FreeBSD can emulate to a PC machine, make ruby and ruby-oci8 in 96 | the linux box and copy them to the FreeBSD box as follows. 97 | 98 | On linux: 99 | 100 | # make ruby 101 | tar xvfz ruby-1.8.x.tar.gz 102 | cd ruby-1.8.x 103 | ./configure --prefix=/usr/local/ruby-1.8.x --enable-pthread 104 | make 105 | make install 106 | # setup instant client 107 | .... 108 | # make ruby-oci8 109 | PATH=/usr/local/ruby-1.8.x/bin:$PATH 110 | tar xvfz ruby-oci8-1.0.x.tar.gz 111 | cd ruby-oci8-1.0.x 112 | make 113 | make install 114 | cd /usr/local/ 115 | tar cvfz ruby-1.8.x-linux.tar.gz ruby-1.8.x 116 | 117 | Copy ruby-1.8.x-linux.tar.gz to the FreeBSD box. 118 | 119 | On freebsd: 120 | 121 | cd /compat/linux/usr/local 122 | tar xvfz ruby-1.8.x-linux.tar.gz 123 | 124 | HP-UX 125 | ----- 126 | 127 | You need a ruby which is linked with ''libpthread'' and ''libcl''. 128 | 129 | Run `configure`. 130 | 131 | tar xvfz ruby-1.9.x.tar.gz 132 | cd ruby-1.9.x 133 | ./configure 134 | 135 | Then open `Makefile` to add '-lpthread -lcl' to `LIBS` and run `make` 136 | and `make install`. 137 | 138 | make 139 | make install 140 | 141 | The rest steps are described at {file:docs/install-full-client.md}. 142 | 143 | Windows 144 | ------- 145 | 146 | On some machines using a slow disk, you may get the following error. 147 | 148 | Permission denied - conftest.exe 149 | 150 | Edit mkmf.rb for a workaround. 151 | 152 | def try_run(src, opt="") 153 | if try_link0(src, opt) 154 | xsystem("./conftest") 155 | else 156 | nil 157 | end 158 | ensure 159 | # add the following one line. 160 | sleep 1 if /mswin32|cygwin|mingw32|bccwin32/ =~ RUBY_PLATFORM 161 | rm_f "conftest*" 162 | end 163 | 164 | See also: {http://abstractplain.net/blog/?p=788}. 165 | -------------------------------------------------------------------------------- /docs/report-installation-issue.md: -------------------------------------------------------------------------------- 1 | # @title Report Installation Issues 2 | 3 | Report Installation Issues 4 | ========================== 5 | 6 | Look at {file:docs/platform-specific-issues.md} and [the issues page on github][github] to check whether your issue is fixed or not. 7 | 8 | If it is a new one, post the following information to [github][]. 9 | 10 | [github]: https://github.com/kubo/ruby-oci8/issues 11 | 12 | * Messages printed out to the console 13 | 14 | * `gem_make.out` if you install a gem 15 | 16 | * Last 100 lines of 'ext/oci8/mkmf.log' 17 | 18 | Get them as follows. 19 | 20 | tail -100 ext/oci8/mkmf.log 21 | 22 | * The results of the following commands: 23 | 24 | file `which ruby` 25 | ruby --version 26 | ruby -r rbconfig -e "p RbConfig::CONFIG['host']" 27 | ruby -r rbconfig -e "p RbConfig::CONFIG['CC']" 28 | ruby -r rbconfig -e "p RbConfig::CONFIG['CFLAGS']" 29 | ruby -r rbconfig -e "p RbConfig::CONFIG['LDSHARED']" 30 | ruby -r rbconfig -e "p RbConfig::CONFIG['LDFLAGS']" 31 | ruby -r rbconfig -e "p RbConfig::CONFIG['DLDLAGS']" 32 | ruby -r rbconfig -e "p RbConfig::CONFIG['LIBS']" 33 | ruby -r rbconfig -e "p RbConfig::CONFIG['GNU_LD']" 34 | 35 | # if you use gcc, 36 | gcc --print-prog-name=ld 37 | gcc --print-prog-name=as 38 | 39 | # Oracle full client 40 | file $ORACLE_HOME/bin/oracle 41 | 42 | # Oracle Instant client. You need to change INSTANT_CLIENT_DIRECTORY. 43 | file INSTANT_CLIENT_DIRECTORY/libclntsh.* 44 | 45 | echo $LD_LIBRARY_PATH 46 | echo $LIBPATH # AIX 47 | echo $SHLIB_PATH # HP-UX PA-RISC 32-bit ruby 48 | echo $DYLD_LIBRARY_PATH # Mac OS X 49 | echo $LD_LIBRARY_PATH_32 # Solaris 32-bit ruby 50 | echo $LD_LIBRARY_PATH_64 # Solaris 64-bit ruby 51 | -------------------------------------------------------------------------------- /docs/timeout-parameters.md: -------------------------------------------------------------------------------- 1 | # @title Timeout Parameters 2 | 3 | Timeout Parameters 4 | ================== 5 | 6 | The following timeout parameters are available since ruby-oci8 2.2.2. 7 | 8 | * tcp_connect_timeout 9 | * connect_timeout 10 | * send_timeout 11 | * recv_timeout 12 | 13 | For example: 14 | 15 | OCI8.properties[:tcp_connect_timeout] = 10 16 | OCI8.properties[:connect_timeout] = 15 17 | OCI8.properties[:send_timeout] = 60 18 | OCI8.properties[:recv_timeout] = 60 19 | 20 | These parameters are applied only to TCP/IP connections. 21 | 22 | The first two parameters `tcp_connect_timeout` and `connect_timeout` 23 | are applied only to [connect descriptors][connect descriptor] using [Easy Connect Naming Method][EZCONNECT]. 24 | If you use a net service name, you should set [TRANSPORT_CONNECT_TIMEOUT][] and/or 25 | [CONNECT_TIMEOUT][] in the address descriptor in `tnsnames.ora` instead of these parameters. 26 | If you use easy connect naming method without any of `port`, `service_name`, `server` and `instance_name`, 27 | you need to use `//host` to distinguish it from a net service name. 28 | 29 | The next two parameters `send_timeout` and `recv_timeout` are available on Oracle 11g client 30 | or upper. Use these parameters to prevent a ruby process from being blocked by poor quality network. 31 | Otherwise, the ruby process may be blocked until TCP keepalive time (2 hours). 32 | 33 | See {file:docs/hanging-after-inactivity.md Hanging After a Long Period of Inactivity} 34 | for TCP keepalive time. 35 | 36 | tcp_connect_timeout 37 | ------------------- 38 | 39 | `tcp_connect_timeout` is equivalent to [TCP.CONNECT_TIMEOUT][] in the client-side `sqlnet.ora` and 40 | [TRANSPORT_CONNECT_TIMEOUT][] in the address descriptor. 41 | See description about [TCP.CONNECT_TIMEOUT][] and [TRANSPORT_CONNECT_TIMEOUT][]. 42 | 43 | connect_timeout 44 | --------------- 45 | 46 | `connect_timeout` is equivalent to [SQLNET.OUTBOUND_CONNECT_TIMEOUT][] in the client-side `sqlnet.ora` 47 | and [CONNECT_TIMEOUT][] in the address description. 48 | See description about [SQLNET.OUTBOUND_CONNECT_TIMEOUT][] and [CONNECT_TIMEOUT][]. 49 | 50 | Note: this parameter isn't equivalent to login timeout. It needs the following three 51 | steps to establish a database connection. 52 | 53 | 1. Establish a TCP/IP connection. 54 | 2. Establish an [Oracle Net][] connection on the TCP/IP connection. 55 | 3. Authenticate and authorize the database user. 56 | 57 | `tcp_connect_timeout` sets the timeout of the first step. 58 | `connect_timeout` sets the total timeout of the first and the second steps. 59 | There is no timeout parameter to limit the maximum time of all three steps. 60 | 61 | Use `send_timeout` and `recv_timeout` in case that a TCP/IP connection stalls 62 | in the third step. 63 | 64 | send_timeout 65 | ------------ 66 | 67 | `send_timeout` is equivalent to [SQLNET.SEND_TIMEOUT][] in the client-side `sqlnet.ora`. 68 | See description about [SQLNET.SEND_TIMEOUT][]. 69 | 70 | Note that the connection becomes unusable on timeout. 71 | 72 | See also {OCI8#send_timeout=}. 73 | 74 | recv_timeout 75 | ------------ 76 | 77 | `recv_timeout` is equivalent to [SQLNET.RECV_TIMEOUT][] in the client-side `sqlnet.ora`. 78 | See description about [SQLNET.RECV_TIMEOUT][]. 79 | 80 | Note that the connection becomes unusable on timeout. 81 | 82 | See also {OCI8#recv_timeout=}. 83 | 84 | Note: This parameter must be larger than the longest SQL execution time in your applications. 85 | 86 | [TCP.CONNECT_TIMEOUT]: http://docs.oracle.com/database/121/NETRF/sqlnet.htm#BIIDDACA 87 | [SQLNET.OUTBOUND_CONNECT_TIMEOUT]: https://docs.oracle.com/database/121/NETRF/sqlnet.htm#NETRF427 88 | [SQLNET.SEND_TIMEOUT]: http://docs.oracle.com/database/121/NETRF/sqlnet.htm#NETRF228 89 | [SQLNET.RECV_TIMEOUT]: http://docs.oracle.com/database/121/NETRF/sqlnet.htm#NETRF227 90 | [connect descriptor]: https://docs.oracle.com/database/121/NETRF/glossary.htm#BGBEDFBF 91 | [EZCONNECT]: https://docs.oracle.com/database/121/NETAG/naming.htm#NETAG255 92 | [CONNECT_TIMEOUT]: https://docs.oracle.com/database/121/NETRF/tnsnames.htm#NETRF666 93 | [TRANSPORT_CONNECT_TIMEOUT]: https://docs.oracle.com/database/121/NETRF/tnsnames.htm#NETRF1982 94 | [Oracle Net]: https://en.wikipedia.org/wiki/Oracle_Net_Services#Oracle_Net 95 | -------------------------------------------------------------------------------- /ext/oci8/.document: -------------------------------------------------------------------------------- 1 | #attr.c 2 | #oci8lib.c 3 | #env.c 4 | 5 | oci8.c 6 | ocihandle.c 7 | bind.c 8 | stmt.c 9 | encoding.c 10 | error.c 11 | metadata.c 12 | 13 | lob.c 14 | ocinumber.c 15 | ocidatetime.c 16 | oradate.c 17 | object.c 18 | connection_pool.c 19 | -------------------------------------------------------------------------------- /ext/oci8/MANIFEST: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | attr.c 3 | bind.c 4 | const.c 5 | descriptor.c 6 | env.c 7 | error.c 8 | extconf.rb 9 | handle.c 10 | lob.c 11 | oci8.c 12 | oci8.h 13 | oraconf.rb 14 | oradate.c 15 | oranumber.c 16 | param.c 17 | stmt.c 18 | svcctx.c 19 | -------------------------------------------------------------------------------- /ext/oci8/apiwrap.c.tmpl: -------------------------------------------------------------------------------- 1 | /* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ 2 | <%= header_comment 3 | # This file is processed by apiwrap.rb. 4 | %> 5 | #define API_WRAP_C 1 6 | #include "apiwrap.h" 7 | 8 | <% 9 | prev_name = '' 10 | funcs.each do |f| 11 | next if f.name == prev_name 12 | prev_name = f.name 13 | %> 14 | /* <%=f.name%> */ 15 | #if defined RUNTIME_API_CHECK 16 | typedef <%=f.ret%> (*oci8_<%=f.name%>_func_t)(<%=f.args.collect {|arg| arg.dcl}.join(', ')%>); 17 | static oci8_<%=f.name%>_func_t oci8_<%=f.name%>_func; 18 | #define <%=f.name%> oci8_<%=f.name%>_func 19 | #elif ORACLE_CLIENT_VERSION < <%=f.version_num%> 20 | #define <%=f.name%>(<%=f.args.collect {|a| a.name}.join(', ')%>) (0) 21 | #endif 22 | <% 23 | end # funcs.each 24 | 25 | funcs.each do |f| 26 | if f.remote 27 | ###################################################################### 28 | ## 29 | ## remote API with runtime-check - start 30 | ## 31 | ###################################################################### 32 | %> 33 | /* 34 | * <%=f.name%>_nb 35 | */ 36 | typedef struct { 37 | oci8_svcctx_t *svcctx; 38 | <% 39 | f.ret != 'void' 40 | %> <%= f.ret %> rv; 41 | <% 42 | f.args.each do |a| 43 | %> <%= a.dcl %>; 44 | <% end 45 | %>} oci8_<%=f.name%>_data_t; 46 | 47 | #if defined RUNTIME_API_CHECK 48 | int oci8_have_<%=f.name%>_nb; 49 | #endif 50 | 51 | #if defined RUNTIME_API_CHECK || ORACLE_CLIENT_VERSION >= <%=f.version_num%> 52 | static void *oci8_<%=f.name%>_cb(void *user_data) 53 | { 54 | oci8_<%=f.name%>_data_t *data = (oci8_<%=f.name%>_data_t *)user_data; 55 | <% if f.ret == 'void' 56 | %> <%=f.name%>(<%= f.args.collect do |a| 'data->' + a.name; end.join(', ') %>); 57 | <% else 58 | %> data->rv = <%=f.name%>(<%= f.args.collect do |a| 'data->' + a.name; end.join(', ') %>); 59 | <% end %> 60 | <% if f.ret == 'sword' 61 | %> return (void*)(VALUE)data->rv; 62 | <% else 63 | %> return NULL; 64 | <% end %> 65 | } 66 | #else 67 | #define oci8_<%=f.name%>_cb NULL 68 | #endif 69 | 70 | <%=f.ret%> oci8_<%=f.name%>_nb(oci8_svcctx_t *svcctx, <%=f.args.collect {|arg| arg.dcl}.join(', ')%>, const char *file, int line) 71 | { 72 | if (have_<%=f.name%>_nb) { 73 | oci8_<%=f.name%>_data_t data; 74 | data.svcctx = svcctx; 75 | <% f.args.each do |a| 76 | %> data.<%=a.name%> = <%=a.name%>; 77 | <% end 78 | %> oci8_call_without_gvl(svcctx, oci8_<%=f.name%>_cb, &data); 79 | <% if f.ret != 'void' 80 | %> return data.rv; 81 | <% end 82 | %> } else { 83 | rb_raise(rb_eRuntimeError, "undefined OCI function %s is called", "<%=f.name%>_nb"); 84 | } 85 | } 86 | <% 87 | ###################################################################### 88 | ## 89 | ## remote API with runtime-check - end 90 | ## 91 | ###################################################################### 92 | else 93 | ###################################################################### 94 | ## 95 | ## local API with runtime-check - start 96 | ## 97 | ###################################################################### 98 | %> 99 | /* 100 | * <%=f.name%> 101 | */ 102 | #if defined RUNTIME_API_CHECK 103 | int oci8_have_<%=f.name%>; 104 | #endif 105 | 106 | <%=f.ret%> oci8_<%=f.name%>(<%=f.args.collect {|arg| arg.dcl}.join(', ')%>, const char *file, int line) 107 | { 108 | if (have_<%=f.name%>) { 109 | <% if f.ret == 'void' 110 | %> <%=f.name%>(<%=f.args.collect {|arg| arg.name}.join(', ')%>); 111 | <% else 112 | %> return <%=f.name%>(<%=f.args.collect {|arg| arg.name}.join(', ')%>); 113 | <% end 114 | %> } else { 115 | <% if f.code_if_not_found %><%=f.code_if_not_found.split("\n").collect {|line| " " + line}.join("\n")%> 116 | <% else 117 | %> rb_raise(rb_eRuntimeError, "undefined OCI function %s is called", "<%=f.name%>"); 118 | <% end 119 | %> } 120 | } 121 | <% 122 | ###################################################################### 123 | ## 124 | ## local API with runtime-check - end 125 | ## 126 | ###################################################################### 127 | end 128 | end # funcs.each 129 | ###################################################################### 130 | ## 131 | ## RUNTIME_API_CHECK 132 | ## 133 | ###################################################################### 134 | current_version_num = funcs[0].version_num 135 | current_version_str = funcs[0].version_str 136 | have_vars = [] 137 | %> 138 | #if defined RUNTIME_API_CHECK 139 | int oracle_client_version; 140 | 141 | void Init_oci8_apiwrap(void) 142 | { 143 | oracle_client_version = 0; 144 | <% 145 | funcs.each do |f| 146 | if current_version_num != f.version_num 147 | unless have_vars.empty? 148 | %> /* pass Oracle <%=current_version_str%> API */ 149 | oracle_client_version = <%=current_version_num%>; 150 | <% have_vars.each do |v| 151 | %> <%=v%> = 1; 152 | <% end 153 | end 154 | have_vars = [] 155 | current_version_num = f.version_num 156 | current_version_str = f.version_str 157 | %> 158 | /* 159 | * checking Oracle <%=f.version_str%> API 160 | */ 161 | <% 162 | end 163 | have_vars << 'have_' + f.name + (f.remote ? '_nb' : '') 164 | %> oci8_<%=f.name%>_func = (oci8_<%=f.name%>_func_t)oci8_find_symbol("<%=f.name%>"); 165 | if (oci8_<%=f.name%>_func == NULL) 166 | return; 167 | <% 168 | end 169 | %> /* pass Oracle <%=current_version_str%> API */ 170 | oracle_client_version = <%=current_version_num%>; 171 | <% have_vars.each do |v| 172 | %> <%=v%> = 1; 173 | <% end 174 | %>} 175 | #endif /* RUNTIME_API_CHECK */ 176 | -------------------------------------------------------------------------------- /ext/oci8/apiwrap.h.tmpl: -------------------------------------------------------------------------------- 1 | /* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ 2 | <%= header_comment 3 | # This file is processed by apiwrap.rb. 4 | %> 5 | #ifndef APIWRAP_H 6 | #define APIWRAP_H 1 7 | #include 8 | 9 | #if defined RUNTIME_API_CHECK 10 | void Init_oci8_apiwrap(void); 11 | extern int oracle_client_version; 12 | #else 13 | #define oracle_client_version ORACLE_CLIENT_VERSION 14 | #endif 15 | <% 16 | funcs.each do |f| 17 | if f.remote 18 | %> 19 | /* 20 | * <%=f.name%> 21 | * version: <%=f.version_str%> 22 | * remote: true 23 | */ 24 | <%=f.ret%> oci8_<%=f.name%>_nb(oci8_svcctx_t *svcctx, <%=f.args.collect {|arg| arg.dcl}.join(', ')%>, const char *file, int line); 25 | #define <%=f.name%>_nb(svcctx, <%=f.args.collect do |a| a.name; end.join(', ')%>) \ 26 | oci8_<%=f.name%>_nb(svcctx, <%=f.args.collect do |a| a.name; end.join(', ')%>, __FILE__, __LINE__) 27 | #if defined RUNTIME_API_CHECK 28 | extern int oci8_have_<%=f.name%>_nb; 29 | #define have_<%=f.name%>_nb oci8_have_<%=f.name%>_nb 30 | #elif ORACLE_CLIENT_VERSION >= <%=f.version_num%> 31 | #define have_<%=f.name%>_nb (1) 32 | #else 33 | #define have_<%=f.name%>_nb (0) 34 | #endif 35 | <% 36 | else 37 | %> 38 | /* 39 | * <%=f.name%> 40 | * version: <%=f.version_str%> 41 | * remote: false 42 | */ 43 | <%=f.ret%> oci8_<%=f.name%>(<%=f.args.collect {|arg| arg.dcl}.join(', ')%>, const char *file, int line); 44 | #ifndef API_WRAP_C 45 | #undef <%=f.name%> 46 | #define <%=f.name%>(<%=f.args.collect do |a| a.name; end.join(', ')%>) \ 47 | oci8_<%=f.name%>(<%=f.args.collect do |a| a.name; end.join(', ')%>, __FILE__, __LINE__) 48 | #endif 49 | #if defined RUNTIME_API_CHECK 50 | extern int oci8_have_<%=f.name%>; 51 | #define have_<%=f.name%> oci8_have_<%=f.name%> 52 | #elif ORACLE_CLIENT_VERSION >= <%=f.version_num%> 53 | #define have_<%=f.name%> (1) 54 | #else 55 | #define have_<%=f.name%> (0) 56 | #endif 57 | <% 58 | end 59 | end # funcs.each 60 | %> 61 | #endif /* APIWRAP_H */ 62 | -------------------------------------------------------------------------------- /ext/oci8/apiwrap.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'erb' 3 | 4 | class ArgDef 5 | attr_reader :dcl 6 | attr_reader :name 7 | 8 | def initialize(arg) 9 | /(\w+)\s*$/ =~ arg 10 | /\(\*(\w+)\)/ =~ arg if $1.nil? 11 | @dcl = arg 12 | @name = $1 13 | end 14 | end 15 | 16 | class FuncDef 17 | attr_reader :name 18 | attr_reader :version 19 | attr_reader :version_num 20 | attr_reader :version_str 21 | attr_reader :remote 22 | attr_reader :args 23 | attr_reader :ret 24 | attr_reader :code_if_not_found 25 | 26 | def initialize(key, val) 27 | if key[-3..-1] == '_nb' 28 | @name = key[0..-4] 29 | @remote = true 30 | else 31 | @name = key 32 | @remote = false 33 | end 34 | ver = val[:version] 35 | ver_major = (ver / 100) 36 | ver_minor = (ver / 10) % 10 37 | ver_update = ver % 10 38 | @version = if ver_major >= 18 39 | ((ver_major << 24) | (ver_minor << 16) | (ver_update << 12)) 40 | else 41 | ((ver_major << 24) | (ver_minor << 20) | (ver_update << 12)) 42 | end 43 | case @version 44 | when 0x08000000; @version_num = 'ORAVER_8_0' 45 | when 0x08100000; @version_num = 'ORAVER_8_1' 46 | when 0x09000000; @version_num = 'ORAVER_9_0' 47 | when 0x09200000; @version_num = 'ORAVER_9_2' 48 | when 0x0a100000; @version_num = 'ORAVER_10_1' 49 | when 0x0a200000; @version_num = 'ORAVER_10_2' 50 | when 0x0b100000; @version_num = 'ORAVER_11_1' 51 | when 0x12000000; @version_num = 'ORAVER_18' 52 | end 53 | @version_str = "#{ver_major}.#{ver_minor}.#{ver_update}" 54 | @ret = val[:ret] || 'sword' 55 | @args = val[:args].collect do |arg| 56 | ArgDef.new(arg) 57 | end 58 | @code_if_not_found = val[:code_if_not_found] 59 | end 60 | end 61 | 62 | def create_apiwrap 63 | funcs = [] 64 | YAML.load(open(File.dirname(__FILE__) + '/apiwrap.yml')).each do |key, val| 65 | funcs << FuncDef.new(key, val) 66 | end 67 | funcs.sort! do |a, b| 68 | if a.version == b.version 69 | a.name <=> b.name 70 | else 71 | a.version <=> b.version 72 | end 73 | end 74 | 75 | header_comment = < 6 | */ 7 | #include "oci8.h" 8 | 9 | #define MAX_ROWID_LEN 128 10 | 11 | typedef struct { 12 | oci8_base_t *base; 13 | OCIStmt *stmtp; 14 | ub4 attrtype; 15 | OCIRowid *ridp; 16 | } rowid_arg_t; 17 | 18 | static VALUE get_rowid_attr(VALUE varg) 19 | { 20 | rowid_arg_t *arg = (rowid_arg_t *)varg; 21 | oci8_base_t *base = arg->base; 22 | ub4 attrtype = arg->attrtype; 23 | char buf[MAX_ROWID_LEN]; 24 | ub2 buflen; 25 | sword rv; 26 | 27 | /* get a rowid descriptor from OCIHandle */ 28 | rv = OCIDescriptorAlloc(oci8_envhp, (dvoid*)&arg->ridp, OCI_DTYPE_ROWID, 0, NULL); 29 | if (rv != OCI_SUCCESS) 30 | oci8_env_raise(oci8_envhp, rv); 31 | chker3(OCIAttrGet(base->hp.ptr, base->type, arg->ridp, 0, attrtype, oci8_errhp), 32 | base, arg->stmtp); 33 | /* convert the rowid descriptor to a string. */ 34 | buflen = MAX_ROWID_LEN; 35 | chker3(OCIRowidToChar(arg->ridp, TO_ORATEXT(buf), &buflen, oci8_errhp), 36 | base, arg->stmtp); 37 | return rb_external_str_new_with_enc(buf, buflen, rb_usascii_encoding()); 38 | } 39 | 40 | static VALUE rowid_ensure(VALUE varg) 41 | { 42 | rowid_arg_t *arg = (rowid_arg_t *)varg; 43 | if (arg->ridp != NULL) { 44 | OCIDescriptorFree(arg->ridp, OCI_DTYPE_ROWID); 45 | } 46 | return Qnil; 47 | } 48 | 49 | VALUE oci8_get_rowid_attr(oci8_base_t *base, ub4 attrtype, OCIStmt *stmtp) 50 | { 51 | rowid_arg_t arg; 52 | arg.base = base; 53 | arg.stmtp = stmtp; 54 | arg.attrtype = attrtype; 55 | arg.ridp = NULL; 56 | return rb_ensure(get_rowid_attr, (VALUE)&arg, rowid_ensure, (VALUE)&arg); 57 | } 58 | -------------------------------------------------------------------------------- /ext/oci8/dump_bindings.c: -------------------------------------------------------------------------------- 1 | #ifdef RUBY_EXTCONF_H 2 | #include "oci8.h" 3 | #endif 4 | 5 | #if defined __APPLE__ || defined __linux__ 6 | #include 7 | #include 8 | #include 9 | #include "plthook.h" 10 | #include 11 | 12 | typedef struct mem_map mem_map_t; 13 | static int mem_map_init(mem_map_t *mm); 14 | static int mem_map_get(mem_map_t *mm, const char **name, void **addr); 15 | 16 | #ifdef __linux__ 17 | #include 18 | 19 | #define SOEXT "so" 20 | 21 | struct mem_map { 22 | struct link_map *map; 23 | }; 24 | 25 | static int mem_map_init(mem_map_t *mm) 26 | { 27 | mm->map = _r_debug.r_map; 28 | return 0; 29 | } 30 | 31 | static int mem_map_get(mem_map_t *mm, const char **name, void **addr) 32 | { 33 | while (mm->map != NULL) { 34 | struct link_map *map = mm->map; 35 | mm->map = map->l_next; 36 | if (map->l_name[0] == '/') { 37 | *name = map->l_name; 38 | *addr = (void*)map->l_addr; 39 | return 0; 40 | } 41 | } 42 | return -1; 43 | } 44 | #endif 45 | 46 | #ifdef __APPLE__ 47 | #include 48 | 49 | #define SOEXT "dylib" 50 | 51 | struct mem_map { 52 | mach_port_t task; 53 | vm_address_t addr; 54 | void *prev_fbase; 55 | }; 56 | 57 | static int mem_map_init(mem_map_t *mm) 58 | { 59 | mach_port_t task = 60 | mm->task = mach_task_self(); 61 | mm->addr = 0; 62 | mm->prev_fbase = NULL; 63 | return 0; 64 | } 65 | 66 | static int mem_map_get(mem_map_t *mm, const char **name, void **addr) 67 | { 68 | vm_size_t size; 69 | vm_region_basic_info_data_64_t info; 70 | mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; 71 | memory_object_name_t object = 0; 72 | 73 | while (vm_region_64(mm->task, &mm->addr, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&info, &info_count, &object) == KERN_SUCCESS) { 74 | Dl_info info; 75 | if (dladdr((void*)mm->addr, &info)) { 76 | if (mm->prev_fbase != info.dli_fbase) { 77 | mm->prev_fbase = info.dli_fbase; 78 | *name = info.dli_fname; 79 | *addr = info.dli_fbase; 80 | mm->addr += size; 81 | return 0; 82 | } 83 | } 84 | mm->addr += size; 85 | } 86 | return -1; 87 | } 88 | #endif 89 | 90 | static int is_oracle_lib(const char *name) 91 | { 92 | static const char * const oracle_libs[] = { 93 | "/libclntshcore." SOEXT, 94 | "/libnnz." SOEXT, 95 | "/libociicus." SOEXT, 96 | "/libociei." SOEXT, 97 | "/libclntsh." SOEXT, 98 | NULL, 99 | }; 100 | 101 | for (int i = 0; oracle_libs[i] != NULL; i++) { 102 | if (strstr(name, oracle_libs[i]) != NULL) { 103 | return 1; 104 | } 105 | } 106 | return 0; 107 | } 108 | 109 | static void dump_bindings_in_lib(const char *name, void *addr) 110 | { 111 | plthook_t *ph; 112 | if (plthook_open_by_address(&ph, addr) != 0) { 113 | fprintf(stderr, " plthook_open error: %s\n", plthook_error()); 114 | return; 115 | } 116 | unsigned int pos = 0; 117 | plthook_entry_t entry; 118 | while (plthook_enum_entry(ph, &pos, &entry) == 0) { 119 | Dl_info info; 120 | if (dladdr(*entry.addr, &info)) { 121 | size_t addend = (size_t)*entry.addr - (info.dli_sname ? (size_t)info.dli_saddr : (size_t)info.dli_fbase); 122 | #ifdef __APPLE__ 123 | fprintf(stderr, "%s:%s+%x -> %s:%s+%zx\n", 124 | name, entry.name, entry.addend, 125 | info.dli_fname, info.dli_sname, addend); 126 | #else 127 | fprintf(stderr, "%s:%s -> %s:%s+%zx\n", 128 | name, entry.name, 129 | info.dli_fname, info.dli_sname, addend); 130 | #endif 131 | } 132 | } 133 | plthook_close(ph); 134 | } 135 | 136 | #define HAVE_OCI8_DUMP_BINDINGS 137 | static void oci8_dump_bindings(void) 138 | { 139 | mem_map_t mm; 140 | const char *name; 141 | void *addr; 142 | 143 | if (mem_map_init(&mm) != 0) { 144 | return; 145 | } 146 | while (mem_map_get(&mm, &name, &addr) == 0) { 147 | if (is_oracle_lib(name)) { 148 | dump_bindings_in_lib(name, addr); 149 | } 150 | } 151 | } 152 | 153 | #ifdef RUBY_EXTCONF_H 154 | static VALUE oci8_s_dump_bindings(VALUE klass) 155 | { 156 | oci8_dump_bindings(); 157 | return Qnil; 158 | } 159 | #endif // RUBY_EXTCONF_H 160 | #endif // defined __APPLE__ || defined __linux__ 161 | 162 | #ifdef RUBY_EXTCONF_H 163 | void Init_oci8_dump_bindings(VALUE cOCI8) 164 | { 165 | #ifdef HAVE_OCI8_DUMP_BINDINGS 166 | rb_define_singleton_method_nodoc(cOCI8, "dump_bindings", oci8_s_dump_bindings, 0); 167 | #endif 168 | } 169 | #endif 170 | 171 | #ifdef DUMP_BINDIGINGS_MAIN 172 | void OCIClientVersion(int *, int *, int *, int *, int *); 173 | 174 | int main(int argc, char **argv) 175 | { 176 | void *func = OCIClientVersion; 177 | oci8_dump_bindings(); 178 | return 0; 179 | } 180 | #endif 181 | -------------------------------------------------------------------------------- /ext/oci8/encoding.c: -------------------------------------------------------------------------------- 1 | /* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ 2 | /* 3 | * encoding.c - part of ruby-oci8 4 | * 5 | * Copyright (C) 2008-2010 KUBO Takehiro 6 | * 7 | */ 8 | #include "oci8.h" 9 | 10 | #ifndef OCI_NLS_MAXBUFSZ 11 | #define OCI_NLS_MAXBUFSZ 100 12 | #endif 13 | 14 | /* NLS ratio, maximum number of bytes per one chracter */ 15 | int oci8_nls_ratio = 1; 16 | 17 | rb_encoding *oci8_encoding; 18 | 19 | /* 20 | * call-seq: 21 | * charset_id2name(charset_id) -> charset_name 22 | * 23 | * Returns the Oracle character set name from the specified 24 | * character set ID if it is valid. Otherwise, +nil+ is returned. 25 | * 26 | * @param [Integer] charset_id Oracle character set id 27 | * @return [String] Oracle character set name or nil 28 | * @since 2.2.0 29 | */ 30 | VALUE oci8_s_charset_id2name(VALUE klass, VALUE csid) 31 | { 32 | char buf[OCI_NLS_MAXBUFSZ]; 33 | sword rv; 34 | 35 | Check_Type(csid, T_FIXNUM); 36 | rv = OCINlsCharSetIdToName(oci8_envhp, TO_ORATEXT(buf), sizeof(buf), (ub2)FIX2INT(csid)); 37 | if (rv != OCI_SUCCESS) { 38 | return Qnil; 39 | } 40 | return rb_usascii_str_new_cstr(buf); 41 | } 42 | 43 | /* 44 | * call-seq: 45 | * charset_name2id(charset_name) -> charset_id 46 | * 47 | * Returns the Oracle character set ID for the specified Oracle 48 | * character set name if it is valid. Othewise, +nil+ is returned. 49 | * 50 | * @param [String] charset_name Oracle character set name 51 | * @return [Integer] Oracle character set id or nil 52 | * @since 2.2.0 53 | */ 54 | static VALUE oci8_s_charset_name2id(VALUE klass, VALUE name) 55 | { 56 | ub2 rv; 57 | 58 | rv = OCINlsCharSetNameToId(oci8_envhp, TO_ORATEXT(StringValueCStr(name))); 59 | if (rv == 0) { 60 | return Qnil; 61 | } 62 | return INT2FIX(rv); 63 | } 64 | 65 | /* 66 | * call-seq: 67 | * OCI8.nls_ratio -> integer 68 | * 69 | * Gets NLS ratio, maximum number of bytes per one character of the 70 | * current NLS chracter set. It is a factor to calculate the 71 | * internal buffer size of a string bind variable whose nls length 72 | * semantics is char. 73 | * 74 | * @return [Integer] NLS ratio 75 | * @since 2.1.0 76 | * @private 77 | */ 78 | static VALUE oci8_get_nls_ratio(VALUE klass) 79 | { 80 | return INT2NUM(oci8_nls_ratio); 81 | } 82 | 83 | /* 84 | * call-seq: 85 | * OCI8.encoding -> enc 86 | * 87 | * Returns the Oracle client encoding. 88 | * 89 | * When string data, such as SQL statements and bind variables, 90 | * are passed to Oracle, they are converted to +OCI8.encoding+ 91 | * in advance. 92 | * 93 | * @example 94 | * # When OCI8.encoding is ISO-8859-1, 95 | * conn.exec('insert into country_code values(:1, :2, :3)', 96 | * 'AT', 'Austria', "\u00d6sterreichs") 97 | * # "\u00d6sterreichs" is 'Österreichs' encoded by UTF-8. 98 | * # It is converted to ISO-8859-1 before it is passed to 99 | * # the Oracle C API. 100 | * 101 | * 102 | * When string data, such as fetched values and bind variable 103 | * for output, are retrieved from Oracle, they are encoded 104 | * by +OCI8.encoding+ if +Encoding.default_internal+ is +nil+. 105 | * If it isn't +nil+, they are converted from +OCI8.encoding+ 106 | * to +Encoding.default_internal+. 107 | * 108 | * If +OCI8.encoding+ is ASCII-8BIT, no encoding conversions 109 | * are done. 110 | * 111 | * @return [Encoding] 112 | * @since 2.0.0 and ruby 1.9 113 | * @private 114 | * @see OCI8.client_charset_name 115 | */ 116 | static VALUE oci8_get_encoding(VALUE klass) 117 | { 118 | return rb_enc_from_encoding(oci8_encoding); 119 | } 120 | 121 | /* 122 | * call-seq: 123 | * OCI8.encoding = enc or nil 124 | * 125 | * Sets Oracle client encoding. You must not use this method. 126 | * You should set the environment variable NLS_LANG properly to 127 | * change +OCI8.encoding+. 128 | * 129 | * @param [Encoding] enc 130 | * @since 2.0.0 and ruby 1.9 131 | * @private 132 | */ 133 | static VALUE oci8_set_encoding(VALUE klass, VALUE encoding) 134 | { 135 | if (NIL_P(encoding)) { 136 | oci8_encoding = NULL; 137 | oci8_nls_ratio = 1; 138 | } else { 139 | oci8_encoding = rb_to_encoding(encoding); 140 | oci8_nls_ratio = rb_enc_mbmaxlen(oci8_encoding); 141 | } 142 | return encoding; 143 | } 144 | 145 | /* 146 | * call-seq: 147 | * charset_name2id(charset_name) -> charset_id 148 | * 149 | * Returns the Oracle character set ID for the specified Oracle 150 | * character set name if it is valid. Othewise, +nil+ is returned. 151 | * 152 | * @param [String] charset_name Oracle character set name 153 | * @return [Integer] Oracle character set id or nil 154 | * @since 2.0.0 155 | * @deprecated Use {OCI8.charset_name2id} instead. 156 | */ 157 | static VALUE oci8_charset_name2id(VALUE svc, VALUE name) 158 | { 159 | rb_warning("Use OCI8.charset_name2id instead of OCI8#charset_name2id."); 160 | return oci8_s_charset_name2id(Qnil, name); 161 | } 162 | 163 | /* 164 | * call-seq: 165 | * charset_id2name(charset_id) -> charset_name 166 | * 167 | * Returns the Oracle character set name from the specified 168 | * character set ID if it is valid. Otherwise, +nil+ is returned. 169 | * 170 | * @param [Integer] charset_id Oracle character set id 171 | * @return [String] Oracle character set name or nil 172 | * @since 2.0.0 173 | * @deprecated Use {OCI8.charset_id2name} instead. 174 | */ 175 | static VALUE oci8_charset_id2name(VALUE svc, VALUE name) 176 | { 177 | rb_warning("Use OCI8.charset_id2name instead of OCI8#charset_id2name."); 178 | return oci8_s_charset_id2name(Qnil, name); 179 | } 180 | 181 | 182 | void Init_oci8_encoding(VALUE cOCI8) 183 | { 184 | #if 0 185 | oci8_cOCIHandle = rb_define_class("OCIHandle", rb_cObject); 186 | cOCI8 = rb_define_class("OCI8", oci8_cOCIHandle); 187 | #endif 188 | 189 | rb_define_singleton_method(cOCI8, "charset_name2id", oci8_s_charset_name2id, 1); 190 | rb_define_singleton_method(cOCI8, "charset_id2name", oci8_s_charset_id2name, 1); 191 | rb_define_singleton_method(cOCI8, "nls_ratio", oci8_get_nls_ratio, 0); 192 | rb_define_singleton_method(cOCI8, "encoding", oci8_get_encoding, 0); 193 | rb_define_singleton_method(cOCI8, "encoding=", oci8_set_encoding, 1); 194 | rb_define_method(cOCI8, "charset_name2id", oci8_charset_name2id, 1); 195 | rb_define_method(cOCI8, "charset_id2name", oci8_charset_id2name, 1); 196 | } 197 | -------------------------------------------------------------------------------- /ext/oci8/env.c: -------------------------------------------------------------------------------- 1 | /* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ 2 | /* 3 | * env.c - part of ruby-oci8 4 | * 5 | * Copyright (C) 2002-2011 KUBO Takehiro 6 | */ 7 | #include "oci8.h" 8 | #include 9 | 10 | ub4 oci8_env_mode = OCI_OBJECT | OCI_THREADED; 11 | 12 | OCIEnv *oci8_global_envhp; 13 | 14 | OCIEnv *oci8_make_envhp(void) 15 | { 16 | sword rv; 17 | OCIEnv *envhp = NULL; 18 | 19 | rv = OCIEnvCreate(&envhp, oci8_env_mode, NULL, NULL, NULL, NULL, 0, NULL); 20 | if (rv != OCI_SUCCESS) { 21 | if (envhp != NULL) { 22 | oci8_env_free_and_raise(envhp, rv); 23 | } else { 24 | oci8_raise_init_error(); 25 | } 26 | } 27 | oci8_global_envhp = envhp; 28 | return oci8_global_envhp; 29 | } 30 | 31 | /* 32 | * Setup thread-local oci8_errhp. 33 | */ 34 | 35 | oci8_tls_key_t oci8_tls_key; /* native thread key */ 36 | 37 | /* This function is called on the native thread termination 38 | * if the thread local errhp is not null. 39 | */ 40 | static void oci8_free_errhp(void *errhp) 41 | { 42 | OCIHandleFree(errhp, OCI_HTYPE_ERROR); 43 | } 44 | 45 | #ifdef _WIN32 46 | static int dllmain_is_called; 47 | 48 | __declspec(dllexport) 49 | BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) 50 | { 51 | void *errhp; 52 | 53 | switch (fdwReason) { 54 | case DLL_PROCESS_ATTACH: 55 | dllmain_is_called = 1; 56 | break; 57 | case DLL_THREAD_ATTACH: 58 | /* do nothing */ 59 | break; 60 | case DLL_THREAD_DETACH: 61 | errhp = oci8_tls_get(oci8_tls_key); 62 | if (errhp != NULL) { 63 | oci8_free_errhp(errhp); 64 | } 65 | break; 66 | case DLL_PROCESS_DETACH: 67 | /* do nothing */ 68 | break; 69 | } 70 | return TRUE; 71 | } 72 | #endif 73 | 74 | OCIError *oci8_make_errhp(void) 75 | { 76 | OCIError *errhp; 77 | sword rv; 78 | 79 | /* create a new errhp. */ 80 | rv = OCIHandleAlloc(oci8_envhp, (dvoid *)&errhp, OCI_HTYPE_ERROR, 0, NULL); 81 | if (rv != OCI_SUCCESS) { 82 | oci8_env_raise(oci8_envhp, rv); 83 | } 84 | /* Set the errhp to the thread local storage. 85 | * It is freed by oci8_free_errhp(). 86 | */ 87 | oci8_tls_set(oci8_tls_key, (void*)errhp); 88 | return errhp; 89 | } 90 | 91 | void Init_oci8_env(void) 92 | { 93 | int error; 94 | 95 | /* workaround code. 96 | * 97 | * When ORACLE_HOME ends with '/' and the Oracle client is 98 | * an instant client lower than 10.2.0.3, OCIEvnCreate() 99 | * doesn't work even though the combination of OCIInitialize() 100 | * and OCIEnvInit() works fine. Delete the last slash for 101 | * a workaround. 102 | */ 103 | if (oracle_client_version < ORAVERNUM(10, 2, 0, 3, 0)) { 104 | #ifdef _WIN32 105 | #define DIR_SEP '\\' 106 | #else 107 | #define DIR_SEP '/' 108 | #endif 109 | char *home = getenv("ORACLE_HOME"); 110 | if (home != NULL) { 111 | size_t homelen = strlen(home); 112 | if (homelen > 0 && home[homelen - 1] == DIR_SEP) { 113 | home = ruby_strdup(home); 114 | home[homelen - 1] = '\0'; 115 | ruby_setenv("ORACLE_HOME", home); 116 | xfree(home); 117 | } 118 | } 119 | } 120 | 121 | #if defined(_WIN32) 122 | if (!dllmain_is_called) { 123 | /* sanity check */ 124 | rb_raise(rb_eRuntimeError, "DllMain is not unexpectedly called. This causes resource leaks."); 125 | } 126 | oci8_tls_key = TlsAlloc(); 127 | if (oci8_tls_key == 0xFFFFFFFF) { 128 | error = GetLastError(); 129 | } else { 130 | error = 0; 131 | } 132 | #else 133 | error = pthread_key_create(&oci8_tls_key, oci8_free_errhp); 134 | #endif 135 | if (error != 0) { 136 | rb_raise(rb_eRuntimeError, "Cannot create thread local key (errno = %d)", error); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /ext/oci8/extconf.rb: -------------------------------------------------------------------------------- 1 | if RUBY_VERSION < "1.9" 2 | puts "Ruby-oci8 doesn't work ruby 1.8 since ruby-oci8 2.2.0." 3 | exit 1 4 | end 5 | 6 | begin 7 | require 'mkmf' 8 | rescue LoadError 9 | if /linux/ =~ RUBY_PLATFORM 10 | raise <. 129 | # 130 | case RUBY_VERSION 131 | when /^1\.9\.0/ 132 | raise 'unsupported ruby version: 1.9.0' 133 | when /^1\.9/ 134 | so_basename += '191' 135 | when /^1\.8/ 136 | so_basename += '18' 137 | else 138 | so_basename += RUBY_VERSION.gsub(/(\d+)\.(\d+).(.*)/, '\1\20') 139 | end 140 | when 'rbx' 141 | so_basename += 'rbx' 142 | when 'jruby' 143 | raise "Ruby-oci8 doesn't support jruby because its C extension support is in development in jruby 1.6 and deprecated in jruby 1.7." 144 | when 'truffleruby' 145 | so_basename += 'truffleruby' 146 | else 147 | raise 'unsupported ruby engine: ' + RUBY_ENGINE 148 | end 149 | 150 | print "checking for plthook... " 151 | STDOUT.flush 152 | case RUBY_PLATFORM 153 | when /mswin32|cygwin|mingw/ 154 | plthook_src = "plthook_win32.c" 155 | when /darwin/ 156 | plthook_src = "plthook_osx.c" 157 | else 158 | plthook_src = "plthook_elf.c" 159 | end 160 | 161 | FileUtils.copy(File.dirname(__FILE__) + "/" + plthook_src, CONFTEST_C) 162 | if xsystem(cc_command("")) 163 | FileUtils.rm_f("#{CONFTEST}.#{$OBJEXT}") 164 | puts plthook_src 165 | $objs << plthook_src.gsub(/\.c$/, '.o') 166 | $objs << "hook_funcs.o" 167 | $defs << "-DHAVE_PLTHOOK" 168 | have_library('dbghelp', 'ImageDirectoryEntryToData', ['windows.h', 'dbghelp.h']) if RUBY_PLATFORM =~ /cygwin/ 169 | $libs += ' -lws2_32' if RUBY_PLATFORM =~ /cygwin/ 170 | else 171 | puts "no" 172 | end 173 | 174 | $defs << "-DInit_oci8lib=Init_#{so_basename}" 175 | $defs << "-Doci8lib=#{so_basename}" 176 | $defs << "-DOCI8LIB_VERSION=\\\"#{OCI8::VERSION}\\\"" 177 | $defs << "-DCHAR_IS_NOT_A_SHORTCUT_TO_ID" if ruby_engine != 'ruby' 178 | 179 | create_header() 180 | 181 | # make dependency file 182 | open("depend", "w") do |f| 183 | extconf_opt = '' 184 | ['oracle-version', 'runtime-check'].each do |arg| 185 | opt = with_config(arg) 186 | case opt 187 | when String 188 | extconf_opt += " --with-#{arg}=#{opt}" 189 | when true 190 | extconf_opt += " --with-#{arg}=yes" 191 | when false 192 | extconf_opt += " --with-#{arg}=no" 193 | end 194 | end 195 | f.puts("Makefile: $(srcdir)/extconf.rb $(srcdir)/oraconf.rb") 196 | f.puts("\t$(RUBY) $(srcdir)/extconf.rb#{extconf_opt}") 197 | $objs.each do |obj| 198 | f.puts("#{obj}: $(srcdir)/#{obj.sub(/\.o$/, ".c")} $(srcdir)/oci8.h apiwrap.h Makefile") 199 | end 200 | f.puts("apiwrap.c apiwrap.h: $(srcdir)/apiwrap.c.tmpl $(srcdir)/apiwrap.h.tmpl $(srcdir)/apiwrap.yml $(srcdir)/apiwrap.rb") 201 | f.puts("\t$(RUBY) $(srcdir)/apiwrap.rb") 202 | end 203 | 204 | create_apiwrap() 205 | 206 | case RUBY_PLATFORM 207 | when /mingw32/ 208 | # Drop '-s' option from LDSHARED and explicitly run 'strip' to get the map file. 209 | if RbConfig::MAKEFILE_CONFIG["LDSHARED"].gsub!(/-s\b/, '') 210 | alias :oci8_configuration_orig :configuration 211 | def configuration(*args) 212 | prefix = [nil].pack('P').size == 4 ? '_' : '' 213 | oci8_configuration_orig(*args) << < $(TARGET).map 218 | strip -s $(DLLIB) 219 | EOS 220 | end 221 | end 222 | end 223 | 224 | create_makefile(so_basename) 225 | 226 | exit 0 227 | -------------------------------------------------------------------------------- /ext/oci8/oranumber_util.h: -------------------------------------------------------------------------------- 1 | /* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ 2 | /* 3 | * oranumber_util.h - part of ruby-oci8 4 | * 5 | * Copyright (C) 2010 KUBO Takehiro 6 | */ 7 | #ifndef ORANUMBER_UTIL_H 8 | #define ORANUMBER_UTIL_H 1 9 | #include 10 | 11 | #define ORANUMBER_INVALID_INTERNAL_FORMAT -1 12 | #define ORANUMBER_TOO_SHORT_BUFFER -2 13 | 14 | #define ORANUMBER_SUCCESS 0 15 | #define ORANUMBER_INVALID_NUMBER 1722 16 | #define ORANUMBER_NUMERIC_OVERFLOW 1426 17 | 18 | int oranumber_to_str(const OCINumber *on, char *buf, int buflen); 19 | int oranumber_from_str(OCINumber *on, const char *buf, int buflen); 20 | 21 | #define ORANUMBER_DUMP_BUF_SIZ 99 22 | int oranumber_dump(const OCINumber *on, char *buf); 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /ext/oci8/plthook.h: -------------------------------------------------------------------------------- 1 | /* -*- indent-tabs-mode: nil -*- 2 | * 3 | * plthook.h -- the header file of plthook 4 | * 5 | * URL: https://github.com/kubo/plthook 6 | * 7 | * ------------------------------------------------------ 8 | * 9 | * Copyright 2013-2024 Kubo Takehiro 10 | * 11 | * Redistribution and use in source and binary forms, with or without modification, are 12 | * permitted provided that the following conditions are met: 13 | * 14 | * 1. Redistributions of source code must retain the above copyright notice, this list of 15 | * conditions and the following disclaimer. 16 | * 17 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list 18 | * of conditions and the following disclaimer in the documentation and/or other materials 19 | * provided with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED 22 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 23 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR 24 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 29 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | * 31 | * The views and conclusions contained in the software and documentation are those of the 32 | * authors and should not be interpreted as representing official policies, either expressed 33 | * or implied, of the authors. 34 | * 35 | */ 36 | #ifndef PLTHOOK_H 37 | #define PLTHOOK_H 1 38 | 39 | #define PLTHOOK_SUCCESS 0 40 | #define PLTHOOK_FILE_NOT_FOUND 1 41 | #define PLTHOOK_INVALID_FILE_FORMAT 2 42 | #define PLTHOOK_FUNCTION_NOT_FOUND 3 43 | #define PLTHOOK_INVALID_ARGUMENT 4 44 | #define PLTHOOK_OUT_OF_MEMORY 5 45 | #define PLTHOOK_INTERNAL_ERROR 6 46 | #define PLTHOOK_NOT_IMPLEMENTED 7 47 | 48 | typedef struct plthook plthook_t; 49 | 50 | #ifdef __cplusplus 51 | extern "C" { 52 | #endif 53 | 54 | int plthook_open(plthook_t **plthook_out, const char *filename); 55 | int plthook_open_by_handle(plthook_t **plthook_out, void *handle); 56 | int plthook_open_by_address(plthook_t **plthook_out, void *address); 57 | int plthook_enum(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out); 58 | int plthook_replace(plthook_t *plthook, const char *funcname, void *funcaddr, void **oldfunc); 59 | void plthook_close(plthook_t *plthook); 60 | const char *plthook_error(void); 61 | 62 | /* enumerate entries with memory protection information (bitwise-OR of PROT_READ, PROT_WRITE and PROT_EXEC) 63 | * 64 | * source: plthook_elf.c and plthook_osx.c 65 | */ 66 | int plthook_enum_with_prot(plthook_t *plthook, unsigned int *pos, const char **name_out, void ***addr_out, int *prot); 67 | 68 | typedef struct { 69 | const char *name; 70 | void **addr; 71 | // memory protection information. bitwise-OR of PROT_READ, PROT_WRITE and PROT_EXEC 72 | int prot; 73 | #ifdef __APPLE__ 74 | int addend; 75 | char weak; 76 | #endif 77 | } plthook_entry_t; 78 | 79 | int plthook_enum_entry(plthook_t *plthook, unsigned int *pos, plthook_entry_t *entry); 80 | 81 | #ifdef __cplusplus 82 | } /* extern "C" */ 83 | #endif 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /ext/oci8/post-config.rb: -------------------------------------------------------------------------------- 1 | File.foreach("#{curr_objdir}/extconf.h") do |line| 2 | if /^#define OCI8_CLIENT_VERSION "(...)"/ =~ line 3 | set_config('oracle_version', $1) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /ext/oci8/thread_util.c: -------------------------------------------------------------------------------- 1 | /* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ 2 | /* 3 | * thread_util.c - part of ruby-oci8 4 | * 5 | * Copyright (C) 2011 KUBO Takehiro 6 | */ 7 | #include "oci8.h" 8 | #include 9 | 10 | #ifndef WIN32 11 | #include 12 | static pthread_attr_t detached_thread_attr; 13 | #endif 14 | 15 | typedef struct { 16 | void *(*func)(void *); 17 | void *arg; 18 | } adapter_arg_t; 19 | 20 | void Init_oci8_thread_util(void) 21 | { 22 | #ifndef WIN32 23 | pthread_attr_init(&detached_thread_attr); 24 | pthread_attr_setdetachstate(&detached_thread_attr, PTHREAD_CREATE_DETACHED); 25 | #endif 26 | } 27 | 28 | #ifdef WIN32 29 | 30 | static void __cdecl adapter(void *arg) 31 | { 32 | adapter_arg_t *aa = (adapter_arg_t *)arg; 33 | aa->func(aa->arg); 34 | free(aa); 35 | } 36 | 37 | int oci8_run_native_thread(void *(*func)(void*), void *arg) 38 | { 39 | adapter_arg_t *aa = malloc(sizeof(adapter_arg_t)); 40 | if (aa == NULL) { 41 | return ENOMEM; 42 | } 43 | 44 | aa->func = func; 45 | aa->arg = arg; 46 | if (_beginthread(adapter, 0, aa) == (uintptr_t)-1L) { 47 | int err = errno; 48 | free(aa); 49 | return err; 50 | } 51 | return 0; 52 | } 53 | 54 | #else /* WIN32 */ 55 | 56 | static void *adapter(void *arg) 57 | { 58 | adapter_arg_t *aa = (adapter_arg_t *)arg; 59 | aa->func(aa->arg); 60 | free(aa); 61 | return NULL; 62 | } 63 | 64 | int oci8_run_native_thread(void *(*func)(void *), void *arg) 65 | { 66 | pthread_t thread; 67 | adapter_arg_t *aa = malloc(sizeof(adapter_arg_t)); 68 | int rv; 69 | if (aa == NULL) { 70 | return ENOMEM; 71 | } 72 | 73 | aa->func = func; 74 | aa->arg = arg; 75 | rv = pthread_create(&thread, &detached_thread_attr, adapter, aa); 76 | if (rv != 0) { 77 | free(aa); 78 | } 79 | return rv; 80 | } 81 | #endif /* WIN32 */ 82 | -------------------------------------------------------------------------------- /ext/oci8/thread_util.h: -------------------------------------------------------------------------------- 1 | /* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ 2 | /* 3 | * thread_util.h - part of ruby-oci8 4 | * 5 | * Copyright (C) 2011 KUBO Takehiro 6 | */ 7 | 8 | /* 9 | * Prepare to execute thread-related functions. 10 | */ 11 | void Init_oci8_thread_util(void); 12 | 13 | /* 14 | * Run the func in a new native thread. 15 | * Don't call any ruby functions in the func. 16 | * The return value is errno. 17 | */ 18 | int oci8_run_native_thread(void *(*func)(void *), void *arg); 19 | -------------------------------------------------------------------------------- /ext/oci8/util.c: -------------------------------------------------------------------------------- 1 | /* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ 2 | /* 3 | util.c - part of ruby-oci8 4 | 5 | Copyright (C) 2015 Kubo Takehiro 6 | */ 7 | #if defined __linux && !defined(_GNU_SOURCE) 8 | #define _GNU_SOURCE 1 9 | #endif 10 | #include "oci8.h" 11 | #ifdef HAVE_DLADDR 12 | #include 13 | #endif 14 | #ifdef __CYGWIN__ 15 | #undef boolean 16 | #include 17 | #endif 18 | 19 | const char *oci8_dll_path(void) 20 | { 21 | #if defined _WIN32 || defined __CYGWIN__ 22 | HMODULE hMod = GetModuleHandleA("OCI.DLL"); 23 | static char buf[MAX_PATH]; 24 | if (hMod != NULL) { 25 | if (GetModuleFileName(hMod, buf, sizeof(buf))) { 26 | return buf; 27 | } 28 | } 29 | #elif defined HAVE_DLADDR && defined RTLD_DEFAULT 30 | void *addr = dlsym(RTLD_DEFAULT, "OCIEnvCreate"); 31 | Dl_info info; 32 | if (addr != NULL && dladdr(addr, &info)) { 33 | return info.dli_fname; 34 | } 35 | #elif defined HAVE_DLMODINFO && defined HAVE_DLGETNAME && defined RTLD_DEFAULT 36 | void *addr = dlsym(RTLD_DEFAULT, "OCIEnvCreate"); 37 | if (addr != NULL) { 38 | struct load_module_desc desc; 39 | if (dlmodinfo((uint64_t)addr, &desc, sizeof(desc), NULL, 0, 0) != 0) { 40 | return dlgetname(&desc, sizeof(desc), NULL, 0, 0); 41 | } 42 | } 43 | #endif 44 | return NULL; 45 | } 46 | 47 | /* 48 | * Returns the full path of Oracle client library used by the current process. 49 | * 50 | * @return [String] 51 | */ 52 | static VALUE dll_path(VALUE module) 53 | { 54 | const char *path = oci8_dll_path(); 55 | if (path == NULL) { 56 | return Qnil; 57 | } 58 | return rb_external_str_new_with_enc(path, strlen(path), rb_filesystem_encoding()); 59 | } 60 | 61 | void Init_oci8_util(VALUE cOCI8) 62 | { 63 | #if 0 64 | /* for yard */ 65 | cOCIHandle = rb_define_class("OCIHandle", rb_cObject); 66 | cOCI8 = rb_define_class("OCI8", cOCIHandle); 67 | #endif 68 | VALUE mUtil = rb_define_module_under(cOCI8, "Util"); 69 | 70 | rb_define_module_function(mUtil, "dll_path", dll_path, 0); 71 | } 72 | -------------------------------------------------------------------------------- /ext/oci8/win32.c: -------------------------------------------------------------------------------- 1 | /* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ 2 | /* 3 | win32.c - part of ruby-oci8 4 | 5 | Copyright (C) 2009-2015 Kubo Takehiro 6 | */ 7 | #include "oci8.h" 8 | #ifdef __CYGWIN__ 9 | /* boolean is defined as a macro in oratypes.h. 10 | * It conflicts with the definition in windows.h. 11 | */ 12 | #undef boolean 13 | #endif 14 | #include 15 | 16 | /* 17 | * Document-module: OCI8::Win32Util 18 | * 19 | * Windows specific utility module. 20 | * 21 | * @private 22 | */ 23 | 24 | typedef struct { 25 | HKEY hKey; 26 | HKEY hSubKey; 27 | } enum_homes_arg_t; 28 | 29 | static VALUE enum_homes_real(VALUE varg) 30 | { 31 | enum_homes_arg_t *arg = (enum_homes_arg_t *)varg; 32 | LONG rv; 33 | DWORD type; 34 | DWORD idx; 35 | char name[1024]; 36 | DWORD name_len; 37 | FILETIME ft; 38 | 39 | rv = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\ORACLE", 0, KEY_ENUMERATE_SUB_KEYS, &arg->hKey); 40 | if (rv != ERROR_SUCCESS) { 41 | return Qnil; 42 | } 43 | for (idx = 0; ; idx++) { 44 | volatile VALUE oracle_home; 45 | volatile VALUE nls_lang; 46 | 47 | /* Get subkey name */ 48 | name_len = sizeof(name); 49 | rv = RegEnumKeyEx(arg->hKey, idx, name, &name_len, NULL, NULL, NULL, &ft); 50 | if (rv != ERROR_SUCCESS) { 51 | break; 52 | } 53 | /* Open subkey */ 54 | if (arg->hSubKey != NULL) { 55 | RegCloseKey(arg->hSubKey); 56 | arg->hSubKey = NULL; 57 | } 58 | rv = RegOpenKeyEx(arg->hKey, name, 0, KEY_QUERY_VALUE, &arg->hSubKey); 59 | if (rv != ERROR_SUCCESS) { 60 | continue; 61 | } 62 | /* Get ORACLE_HOME */ 63 | name_len = sizeof(name); 64 | rv = RegQueryValueEx(arg->hSubKey, "ORACLE_HOME", NULL, &type, TO_ORATEXT(name), &name_len); 65 | if (rv != ERROR_SUCCESS || type != REG_SZ) { 66 | continue; 67 | } 68 | oracle_home = rb_locale_str_new_cstr(name); 69 | /* Get NLS_LANG */ 70 | name_len = sizeof(name); 71 | rv = RegQueryValueEx(arg->hSubKey, "NLS_LANG", NULL, &type, TO_ORATEXT(name), &name_len); 72 | if (rv != ERROR_SUCCESS || type != REG_SZ) { 73 | continue; 74 | } 75 | nls_lang = rb_locale_str_new_cstr(name); 76 | rb_yield_values(2, oracle_home, nls_lang); 77 | } 78 | return Qnil; 79 | } 80 | 81 | static VALUE enum_homes_ensure(VALUE varg) 82 | { 83 | enum_homes_arg_t *arg = (enum_homes_arg_t *)varg; 84 | if (arg->hKey != NULL) { 85 | RegCloseKey(arg->hKey); 86 | arg->hKey = NULL; 87 | } 88 | if (arg->hSubKey != NULL) { 89 | RegCloseKey(arg->hSubKey); 90 | arg->hSubKey = NULL; 91 | } 92 | return Qnil; 93 | } 94 | 95 | /* 96 | * Enumerates full clients' Oracle homes and NLS_LANG parameters 97 | * registerd in the Windows registry. 98 | * 99 | * @yield [oracle_home, nls_lang] 100 | * @yieldparam [String] oracle_home 101 | * @yieldparam [String] nls_lang 102 | * @return [nil] 103 | */ 104 | static VALUE enum_homes(VALUE module) 105 | { 106 | enum_homes_arg_t arg; 107 | arg.hKey = NULL; 108 | arg.hSubKey = NULL; 109 | return rb_ensure(enum_homes_real, (VALUE)&arg, enum_homes_ensure, (VALUE)&arg); 110 | } 111 | 112 | void Init_oci8_win32(VALUE cOCI8) 113 | { 114 | VALUE mWin32Util = rb_define_module_under(cOCI8, "Win32Util"); 115 | 116 | rb_define_module_function(mWin32Util, "enum_homes", enum_homes, 0); 117 | } 118 | -------------------------------------------------------------------------------- /lib/.document: -------------------------------------------------------------------------------- 1 | oci8 2 | -------------------------------------------------------------------------------- /lib/oci8.rb: -------------------------------------------------------------------------------- 1 | # --*- ruby -*-- 2 | # This is based on yoshidam's oracle.rb. 3 | # 4 | # sample one liner: 5 | # ruby -r oci8 -e 'OCI8.new("scott", "tiger", nil).exec("select * from emp") do |r| puts r.join(","); end' 6 | # # select all data from emp and print them as CVS format. 7 | 8 | ENV['ORA_SDTZ'] = ENV['TZ'] unless ENV['ORA_SDTZ'] 9 | 10 | if RUBY_PLATFORM =~ /cygwin/ 11 | # Cygwin manages environment variables by itself. 12 | # They don't synchroize with Win32's ones. 13 | # This set some Oracle's environment variables to win32's enviroment. 14 | require 'fiddle' 15 | win32setenv = Fiddle::Function.new( Fiddle.dlopen('Kernel32.dll')['SetEnvironmentVariableA'], 16 | [Fiddle::TYPE_VOIDP,Fiddle::TYPE_VOIDP], 17 | Fiddle::TYPE_INT ) 18 | 19 | ['NLS_LANG', 'TNS_ADMIN', 'LOCAL'].each do |name| 20 | val = ENV[name] 21 | win32setenv.call(name, val && val.dup) 22 | end 23 | ENV.each do |name, val| 24 | win32setenv.call(name, val && val.dup) if name =~ /^ORA/ 25 | end 26 | end 27 | 28 | ruby_engine = (defined? RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' 29 | 30 | so_basename = 'oci8lib_' 31 | case ruby_engine 32 | when 'ruby' 33 | # The extension library name includes the ruby ABI (application binary interface) 34 | # version. It is for binary gems which contain more than one extension library 35 | # and use correct one depending on the ABI version. 36 | # 37 | # ruby version | ABI version 38 | # --------------+-------------- 39 | # 1.8.6 | 1.8 40 | # 1.8.7 | 1.8 41 | # --------------+-------------- 42 | # 1.9.0 | 1.9.0 43 | # 1.9.1 | 1.9.1 44 | # 1.9.2 | 1.9.1 45 | # 1.9.3 | 1.9.1 46 | # --------------+-------------- 47 | # 2.0.0 | 2.0.0 48 | # --------------+-------------- 49 | # 2.1.0 | 2.1.0 50 | # ... | ... 51 | # 52 | # The ABI version of ruby 2.1.1 will be 2.1.0. 53 | # See "ABI Compatibility" in . 54 | # 55 | case RUBY_VERSION 56 | when /^1\.9\.0/ 57 | raise 'unsupported ruby version: 1.9.0' 58 | when /^1\.9/ 59 | so_basename += '191' 60 | when /^1\.8/ 61 | so_basename += '18' 62 | else 63 | so_basename += RUBY_VERSION.gsub(/(\d+)\.(\d+).(.*)/, '\1\20') 64 | end 65 | when 'rbx' 66 | so_basename += 'rbx' 67 | when 'jruby' 68 | raise "Ruby-oci8 doesn't support jruby because its C extension support is in development in jruby 1.6 and deprecated in jruby 1.7." 69 | when 'truffleruby' 70 | so_basename += 'truffleruby' 71 | else 72 | raise 'unsupported ruby engine: ' + RUBY_ENGINE 73 | end 74 | 75 | dll_dir = nil 76 | begin 77 | require 'ruby_installer/runtime' # RubyInstaller2 for Windows 78 | 79 | dll_arch = proc do |file| 80 | begin 81 | File.open(file, 'rb') do |f| 82 | if f.read(2) == 'MZ' # IMAGE_DOS_HEADER.e_magic 83 | f.seek(60, IO::SEEK_SET) 84 | pe_offset = f.read(4).unpack('V')[0] # IMAGE_DOS_HEADER.e_lfanew 85 | f.seek(pe_offset) 86 | if f.read(4) == "PE\000\000" # IMAGE_NT_HEADERS.Signature 87 | case f.read(2).unpack('v')[0] # IMAGE_FILE_HEADER.Machine 88 | when 0x014c # IMAGE_FILE_MACHINE_I386 89 | :x86 90 | when 0x8664 # IMAGE_FILE_MACHINE_AMD64 91 | :x64 92 | end 93 | end 94 | end 95 | end 96 | rescue 97 | nil 98 | end 99 | end 100 | 101 | ruby_arch = [nil].pack('P').size == 8 ? :x64 : :x86 102 | ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| 103 | if !path.empty? && dll_arch.call(File.join(path, 'OCI.DLL')) == ruby_arch 104 | dll_dir = RubyInstaller::Runtime.add_dll_directory(path) 105 | break 106 | end 107 | end 108 | rescue LoadError 109 | end 110 | 111 | begin 112 | require so_basename 113 | rescue LoadError 114 | require 'oci8/check_load_error' 115 | OCI8::Util::check_load_error($!) 116 | raise 117 | ensure 118 | dll_dir.remove if dll_dir 119 | end 120 | 121 | require 'oci8/version.rb' 122 | if OCI8::VERSION != OCI8::LIB_VERSION 123 | require 'rbconfig' 124 | so_name = so_basename + "." + RbConfig::CONFIG['DLEXT'] 125 | raise "VERSION MISMATCH! #{so_name} version is #{OCI8::LIB_VERSION}, but oci8.rb version is #{OCI8::VERSION}." 126 | end 127 | 128 | require 'oci8/encoding-init.rb' 129 | require 'oci8/oracle_version.rb' 130 | 131 | class OCI8 132 | # @private 133 | ORAVER_8_0 = OCI8::OracleVersion.new(8, 0) 134 | # @private 135 | ORAVER_8_1 = OCI8::OracleVersion.new(8, 1) 136 | # @private 137 | ORAVER_9_0 = OCI8::OracleVersion.new(9, 0) 138 | # @private 139 | ORAVER_9_2 = OCI8::OracleVersion.new(9, 2) 140 | # @private 141 | ORAVER_10_1 = OCI8::OracleVersion.new(10, 1) 142 | # @private 143 | ORAVER_10_2 = OCI8::OracleVersion.new(10, 2) 144 | # @private 145 | ORAVER_11_1 = OCI8::OracleVersion.new(11, 1) 146 | # @private 147 | ORAVER_12_1 = OCI8::OracleVersion.new(12, 1) 148 | # @private 149 | ORAVER_18 = OCI8::OracleVersion.new(18) 150 | # @private 151 | ORAVER_23 = OCI8::OracleVersion.new(23) 152 | 153 | # @private 154 | @@oracle_client_version = OCI8::OracleVersion.new(self.oracle_client_vernum) 155 | 156 | # Returns an OCI8::OracleVersion of the Oracle client version. 157 | # 158 | # If this library is configured without '--with-runtime-check', 159 | # and compiled for Oracle 10.1 or lower, the major and minor 160 | # numbers are determined at compile-time. The rests are zeros. 161 | # 162 | # If this library is configured with '--with-runtime-check' 163 | # and the runtime Oracle library is Oracle 10.1 or lower, the 164 | # major and minor numbers are determined at run-time. The 165 | # rests are zeros. 166 | # 167 | # Otherwise, it is the version retrieved from an OCI function 168 | # OCIClientVersion(). 169 | # 170 | # @return [OCI8::OracleVersion] Oracle client version 171 | # @see OCI8#oracle_server_version 172 | def self.oracle_client_version 173 | @@oracle_client_version 174 | end 175 | 176 | # defined for backward compatibility. 177 | # @private 178 | CLIENT_VERSION = @@oracle_client_version.major.to_s + 179 | @@oracle_client_version.minor.to_s + 180 | @@oracle_client_version.update.to_s 181 | end 182 | 183 | require 'oci8/ocihandle.rb' 184 | require 'oci8/datetime.rb' 185 | require 'oci8/oci8.rb' 186 | require 'oci8/cursor.rb' 187 | require 'oci8/bindtype.rb' 188 | require 'oci8/metadata.rb' 189 | require 'oci8/compat.rb' 190 | require 'oci8/object.rb' 191 | require 'oci8/connection_pool.rb' 192 | require 'oci8/properties.rb' 193 | -------------------------------------------------------------------------------- /lib/oci8/.document: -------------------------------------------------------------------------------- 1 | datetime.rb 2 | object.rb 3 | metadata.rb 4 | oracle_version.rb 5 | oci8.rb 6 | ocihandle.rb 7 | connection_pool.rb 8 | properties.rb 9 | -------------------------------------------------------------------------------- /lib/oci8/check_load_error.rb: -------------------------------------------------------------------------------- 1 | # This file is loaded only on LoadError. 2 | 3 | class OCI8 4 | module Util 5 | 6 | case RUBY_PLATFORM 7 | when /mswin32|cygwin|mingw/ 8 | 9 | require 'fiddle/import' 10 | require 'fiddle/types' 11 | 12 | extend Fiddle::Importer 13 | dlload 'kernel32.dll' 14 | include Fiddle::BasicTypes 15 | include Fiddle::Win32Types 16 | 17 | typealias "HANDLE", "void*" 18 | typealias "HMODULE", "void*" 19 | extern "DWORD GetModuleFileNameA(HMODULE, LPSTR, DWORD)" 20 | extern "UINT GetSystemDirectoryA(LPCSTR, UINT)" 21 | extern "UINT GetWindowsDirectoryA(LPCSTR, UINT)" 22 | extern "HMODULE LoadLibraryExA(LPCSTR, HANDLE, DWORD)" 23 | extern "BOOL FreeLibrary(HMODULE)" 24 | 25 | MAX_PATH = 260 26 | DONT_RESOLVE_DLL_REFERENCES = 0x00000001 27 | 28 | def self.check_os_specific_load_error(exc) 29 | case exc.message 30 | when /^OCI\.DLL: 193\(/, /^193: / # "OCI.DLL: 193(%1 is not a valid Win32 application.)" in English 31 | check_win32_pe_arch(exc.message.split(' - ')[1], "ruby-oci8") 32 | dll_load_path_list.each do |path| 33 | check_win32_pe_arch(File.join(path, '\OCI.DLL'), "Oracle client") 34 | end 35 | when /^OCI.DLL: 126\(/, /^126: / # "OCI.DLL: 126(The specified module could not be found.)" in English 36 | oci_dll_files = dll_load_path_list.inject([]) do |files, path| 37 | file = File.join(path, '\OCI.DLL') 38 | files << file if File.exist?(file) 39 | files 40 | end 41 | if oci_dll_files.empty? 42 | raise LoadError, "Cannot find OCI.DLL in PATH." 43 | end 44 | if oci_dll_files.none? {|file| open(file, 'rb') {true} rescue false} 45 | raise LoadError, "OCI.DLL in PATH isn't readable." 46 | end 47 | first_error = nil 48 | oci_dll_files.each do |file| 49 | begin 50 | check_win32_pe_arch(file, "Oracle client") 51 | valid_arch = true 52 | rescue LoadError 53 | first_error ||= $! 54 | valid_arch = false 55 | end 56 | if valid_arch 57 | handle = LoadLibraryExA(file, nil, DONT_RESOLVE_DLL_REFERENCES) 58 | unless handle.null? 59 | FreeLibrary(handle) 60 | raise LoadError, < 5 | #++ 6 | 7 | # 8 | class OCI8 9 | 10 | # Connection pooling is the use of a group (the pool) of reusable 11 | # physical connections by several sessions to balance loads. 12 | # See: {Oracle Call Interface Manual}[http://docs.oracle.com/cd/E11882_01/appdev.112/e10646/oci09adv.htm#sthref1479] 13 | # 14 | # This is equivalent to Oracle JDBC Driver {OCI Connection Pooling}[http://docs.oracle.com/cd/E11882_01/java.112/e16548/ociconpl.htm#JJDBC28789]. 15 | # 16 | # Note that this is different with generally called connection pooling. 17 | # Generally connection pooling caches connections in a pool. 18 | # When an application needs a new connection, a connection is got from 19 | # the pool. So that the amount of time to establish a connection is 20 | # reduced. However connection pooling in ruby-oci8 is different with 21 | # above. 22 | # 23 | # One Oracle connection is devided into two layers: One is physical 24 | # connection such as TCP/IP. The other is logical connection which 25 | # is used by an application. The connection pooling in ruby-oci8 26 | # caches physical connections in a pool, not logical connections. 27 | # When an application needs a new connection, a logical connection 28 | # is created via a physical connection, which needs time to 29 | # establish a connection unlike generally called connection pooling. 30 | # When an application sends a query via a logical connection, a 31 | # physical connection is got from the pool. The physical connection 32 | # returns to the pool automatically when the query finishes. 33 | # As long as logical connections are used sequentially, only one 34 | # physical connection is used. When an application sends 35 | # queries at a time, it needs more than one physical connection 36 | # because one physical connection cannot transmit more then one 37 | # query at a time. 38 | # 39 | # Example: 40 | # # Create a connection pool. 41 | # # The number of initial physical connections: 1 42 | # # The number of maximum physical connections: 5 43 | # # the connection increment parameter: 2 44 | # # username and password are required to establish an implicit primary session. 45 | # cpool = OCI8::ConnectionPool.new(1, 5, 2, username, password, database) 46 | # 47 | # # The number of physical connections: 1 48 | # # The number of logical connections: 0 49 | # 50 | # # Create a session. 51 | # # Pass the connection pool to the third argument. 52 | # conn1 = OCI8.new(username, password, cpool) 53 | # 54 | # # One logical connection was created. 55 | # # The number of physical connections: 1 56 | # # The number of logical connections: 1 57 | # 58 | # # Create another session. 59 | # conn2 = OCI8.new(username, password, cpool) 60 | # 61 | # # Another logical connection was created. 62 | # # The number of physical connections: 1 63 | # # The number of logical connections: 2 64 | # 65 | # # Use conn1 and conn2 sequentially 66 | # conn1.exec('...') 67 | # conn2.exec('...') 68 | # 69 | # # Both logical connections use one physical connection. 70 | # # The number of physical connections: 1 71 | # # The number of logical connections: 2 72 | # 73 | # thr1 = Thread.new do 74 | # conn1.exec('...') 75 | # end 76 | # thr2 = Thread.new do 77 | # conn2.exec('...') 78 | # end 79 | # thr1.join 80 | # thr2.join 81 | # 82 | # # Logical connections cannot use one physical connection at a time. 83 | # # So that the number of physical connections is incremented. 84 | # # The number of physical connections: 3 85 | # # The number of logical connections: 2 86 | # 87 | # conn1.logoff 88 | # 89 | # # One logical connection was closed. 90 | # # The number of physical connections: 3 91 | # # The number of logical connections: 1 92 | # 93 | # conn2.logoff 94 | # 95 | # # All logical connections were closed. 96 | # # The number of physical connections: 3 97 | # # The number of logical connections: 0 98 | # 99 | class ConnectionPool 100 | 101 | # Connections idle for more than this time value (in seconds) are 102 | # terminated, to maintain an optimum number of open 103 | # connections. If it is zero, the connections are never timed out. 104 | # The default value is zero. 105 | # 106 | # Note: Shrinkage of the pool only occurs when there is a network 107 | # round trip. If there are no operations, then the connections 108 | # stay alive. 109 | # 110 | # @return [Integer] 111 | def timeout 112 | attr_get_ub4(OCI_ATTR_CONN_TIMEOUT) 113 | end 114 | 115 | # Changes the timeout in seconds to terminate idle connections. 116 | # 117 | # @param [Integer] val 118 | def timeout=(val) 119 | attr_set_ub4(OCI_ATTR_CONN_TIMEOUT, val) 120 | end 121 | 122 | # If true, an error is thrown when all the connections in the pool 123 | # are busy and the number of connections has already reached the 124 | # maximum. Otherwise the call waits till it gets a connection. 125 | # The default value is false. 126 | def nowait? 127 | attr_get_ub1(OCI_ATTR_CONN_NOWAIT) != 0 128 | end 129 | 130 | # Changes the behavior when all the connections in the pool 131 | # are busy and the number of connections has already reached the 132 | # maximum. 133 | # 134 | # @param [Boolean] val 135 | def nowait=(val) 136 | attr_set_ub1(OCI_ATTR_CONN_NOWAIT, val ? 1 : 0) 137 | end 138 | 139 | # Returns the number of busy physical connections. 140 | # 141 | # @return [Integer] 142 | def busy_count 143 | attr_get_ub4(OCI_ATTR_CONN_BUSY_COUNT) 144 | end 145 | 146 | # Returns the number of open physical connections. 147 | # 148 | # @return [Integer] 149 | def open_count 150 | attr_get_ub4(OCI_ATTR_CONN_OPEN_COUNT) 151 | end 152 | 153 | # Returns the number of minimum physical connections. 154 | # 155 | # @return [Integer] 156 | def min 157 | attr_get_ub4(OCI_ATTR_CONN_MIN) 158 | end 159 | 160 | # Returns the number of maximum physical connections. 161 | # 162 | # @return [Integer] 163 | def max 164 | attr_get_ub4(OCI_ATTR_CONN_MAX) 165 | end 166 | 167 | # Returns the connection increment parameter. 168 | # 169 | # @return [Integer] 170 | def incr 171 | attr_get_ub4(OCI_ATTR_CONN_INCR) 172 | end 173 | 174 | # 175 | def destroy 176 | free 177 | end 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /lib/oci8/encoding-init.rb: -------------------------------------------------------------------------------- 1 | # 2 | # setup default OCI encoding from NLS_LANG. 3 | # 4 | 5 | # 6 | class OCI8 7 | 8 | @@client_charset_name = charset_id2name(@@environment_handle.send(:attr_get_ub2, 31)) 9 | # 31 is OCI_ATTR_ENV_CHARSET_ID. 10 | 11 | if @@client_charset_name == 'US7ASCII' 12 | # Check whether US7ASCII is explicitly set by NLS_LANG or not. 13 | nls_lang = ENV['NLS_LANG'] 14 | if nls_lang.nil? and defined? OCI8::Win32Util 15 | if OCI8::Util::dll_path =~ /\\BIN\\OCI\.DLL$/i 16 | oracle_home = $` 17 | OCI8::Win32Util.enum_homes do |home, lang| 18 | if oracle_home == home.upcase 19 | nls_lang = lang 20 | break 21 | end 22 | end 23 | end 24 | end 25 | if nls_lang.nil? 26 | warn "Warning: NLS_LANG is not set. fallback to US7ASCII." 27 | end 28 | end 29 | 30 | if defined? DEFAULT_OCI8_ENCODING 31 | enc = DEFAULT_OCI8_ENCODING 32 | else 33 | require 'yaml' 34 | yaml_file = File.dirname(__FILE__) + '/encoding.yml' 35 | enc = YAML::load_file(yaml_file)[@@client_charset_name] 36 | if enc.nil? 37 | raise "Cannot convert Oracle charset name #{@@client_charset_name} to Ruby encoding name in #{yaml_file}." 38 | end 39 | if enc.is_a? Array 40 | # Use the first available encoding in the array. 41 | enc = enc.find do |e| Encoding.find(e) rescue false; end 42 | end 43 | end 44 | OCI8.encoding = enc 45 | end 46 | -------------------------------------------------------------------------------- /lib/oci8/oracle_version.rb: -------------------------------------------------------------------------------- 1 | # oracle_version.rb implements OCI8::OracleVersion. 2 | # 3 | # Copyright (C) 2009-2013 Kubo Takehiro 4 | 5 | # 6 | class OCI8 7 | 8 | # The data class, representing Oracle version. 9 | # 10 | # Oracle version is represented by five numbers: 11 | # *major*, *minor*, *update*, *patch* and *port_update*. 12 | # 13 | # @see OCI8.oracle_client_version 14 | # @see OCI8#oracle_server_version 15 | class OracleVersion 16 | include Comparable 17 | 18 | # The first part of the Oracle version. 19 | attr_reader :major 20 | # The second part of the Oracle version. 21 | attr_reader :minor 22 | # The third part of the Oracle version. 23 | attr_reader :update 24 | # The fourth part of the Oracle version. 25 | attr_reader :patch 26 | # The fifth part of the Oracle version. 27 | attr_reader :port_update 28 | 29 | # Creates an OCI8::OracleVersion object. 30 | # 31 | # If the first argument _arg_ is a String, it is parsed as dotted 32 | # version string. If it is bigger than 0x08000000, it is parsed as 33 | # a number contains 5-digit Oracle version. Otherwise, it is used 34 | # as a major version and the rest arguments are minor, update, 35 | # patch and port_update. Unspecified version numbers are zeros by 36 | # default. 37 | # 38 | # @example 39 | # # When the first argument is a String, 40 | # oraver = OCI8::OracleVersion.new('11.2.0.3') 41 | # oraver.major # => 11 42 | # oraver.minor # => 2 43 | # oraver.update # => 0 44 | # oraver.patch # => 3 45 | # oraver.port_update # => 0 46 | # 47 | # # When the first argument is bigger than 0x08000000, 48 | # oraver = OCI8::OracleVersion.new(0x0b200300) 49 | # oraver.major # => 11 50 | # oraver.minor # => 2 51 | # oraver.update # => 0 52 | # oraver.patch # => 3 53 | # oraver.port_update # => 0 54 | # 55 | # # Otherwise, 56 | # oraver = OCI8::OracleVersion.new(11, 2, 0, 3) 57 | # oraver.major # => 11 58 | # oraver.minor # => 2 59 | # oraver.update # => 0 60 | # oraver.patch # => 3 61 | # oraver.port_update # => 0 62 | # 63 | # @return [OCI8::OracleVersion] 64 | def initialize(arg, minor = nil, update = nil, patch = nil, port_update = nil) 65 | if arg.is_a? String 66 | major, minor, update, patch, port_update = arg.split('.').collect do |v| 67 | v.to_i 68 | end 69 | elsif arg >= 0x12000000 70 | major = (arg & 0xFF000000) >> 24 71 | minor = (arg & 0x00FF0000) >> 16 72 | update = (arg & 0x0000F000) >> 12 73 | patch = (arg & 0x00000FF0) >> 4 74 | port_update = (arg & 0x0000000F) 75 | elsif arg >= 0x08000000 76 | major = (arg & 0xFF000000) >> 24 77 | minor = (arg & 0x00F00000) >> 20 78 | update = (arg & 0x000FF000) >> 12 79 | patch = (arg & 0x00000F00) >> 8 80 | port_update = (arg & 0x000000FF) 81 | else 82 | major = arg 83 | end 84 | @major = major 85 | @minor = minor || 0 86 | @update = update || 0 87 | @patch = patch || 0 88 | @port_update = port_update || 0 89 | @vernum = if @major >= 18 90 | (@major << 24) | (@minor << 16) | (@update << 12) | (@patch << 4) | @port_update 91 | else 92 | (@major << 24) | (@minor << 20) | (@update << 12) | (@patch << 8) | @port_update 93 | end 94 | end 95 | 96 | # Compares +self+ and +other+. 97 | # 98 | # <=> is the basis for the methods <, <=, ==, >, >=, and between?, 99 | # included from the Comparable module. 100 | # 101 | # @return [-1, 0, +1] 102 | def <=>(other) 103 | @vernum <=> other.to_i 104 | end 105 | 106 | # Returns an integer number contains 5-digit Oracle version. 107 | # 108 | # If the hexadecimal notation is 0xAABCCDEE, *major*, *minor*, 109 | # *update*, *patch* and *port_update* are 0xAA, 0xB, 0xCC, 0xD and 110 | # 0xEE respectively. 111 | # 112 | # @example 113 | # oraver = OCI8::OracleVersion.new('11.2.0.3') 114 | # oraver.to_i # => 186647296 115 | # '%08x' % oraver.to_i # => "0b200300" 116 | # 117 | # @return [Integer] 118 | def to_i 119 | @vernum 120 | end 121 | 122 | # Returns a dotted version string of the Oracle version. 123 | # 124 | # @example 125 | # oraver = OCI8::OracleVersion.new('11.2.0.3') 126 | # oraver.to_s # => '11.2.0.3.0' 127 | # 128 | # @return [String] 129 | def to_s 130 | version_format = 131 | if @major >= 23 && @patch != 0 && @port_update != 0 132 | # The fifth numeral is the release month (01 through 12) since Oracle 23ai 133 | # See https://docs.oracle.com/en/database/oracle/oracle-database/23/upgrd/oracle-database-release-numbers.html#GUID-1E2F3945-C0EE-4EB2-A933-8D1862D8ECE2__GUID-704EC7BB-4EEA-4A92-96FC-579B216DCA97 134 | '%d.%d.%d.%d.%02d' 135 | else 136 | '%d.%d.%d.%d.%d' 137 | end 138 | format(version_format, @major, @minor, @update, @patch, @port_update) 139 | end 140 | 141 | # Returns true if +self+ and +other+ are the same type and have 142 | # equal values. 143 | # 144 | # @return [true or false] 145 | def eql?(other) 146 | other.is_a? OCI8::OracleVersion and (self <=> other) == 0 147 | end 148 | 149 | # Returns a hash based on the value of +self+. 150 | # 151 | # @return [Integer] 152 | def hash 153 | @vernum 154 | end 155 | 156 | # @private 157 | def inspect 158 | "#<#{self.class.to_s}: #{self.to_s}>" 159 | end 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /lib/oci8/properties.rb: -------------------------------------------------------------------------------- 1 | # properties.rb -- implements OCI8.properties 2 | # 3 | # Copyright (C) 2010-2015 Kubo Takehiro 4 | 5 | # 6 | class OCI8 7 | 8 | # @private 9 | @@properties = { 10 | :length_semantics => :byte, 11 | :bind_string_as_nchar => false, 12 | :float_conversion_type => OCI8.__get_prop(1) ? :ruby : :oracle, 13 | :statement_cache_size => 0, 14 | :events_mode => ((OCI8.__get_prop(2) & 4) != 0), # 4 <- OCI_EVENTS in oci.h 15 | :cancel_read_at_exit => false, 16 | :tcp_connect_timeout => nil, 17 | :connect_timeout => nil, 18 | :send_timeout => nil, 19 | :recv_timeout => nil, 20 | :tcp_keepalive => false, 21 | :tcp_keepalive_time => nil, 22 | } 23 | 24 | # @private 25 | def @@properties.[](name) 26 | raise IndexError, "No such property name: #{name}" unless @@properties.has_key?(name) 27 | super(name) 28 | end 29 | 30 | # @private 31 | def @@properties.[]=(name, val) 32 | raise IndexError, "No such property name: #{name}" unless @@properties.has_key?(name) 33 | case name 34 | when :length_semantic 35 | if val != :byte and val != :char 36 | raise ArgumentError, "Invalid property value #{val} for :length_semantics." 37 | end 38 | when :bind_string_as_nchar 39 | val = val ? true : false 40 | when :float_conversion_type 41 | case val 42 | when :ruby 43 | OCI8.__set_prop(1, true) 44 | when :oracle 45 | OCI8.__set_prop(1, false) 46 | else 47 | raise ArgumentError, "float_conversion_type's value should be either :ruby or :oracle." 48 | end 49 | when :statement_cache_size 50 | val = val.to_i 51 | raise ArgumentError, "The property value for :statement_cache_size must not be negative." if val < 0 52 | when :events_mode 53 | val = val ? true : false 54 | if val 55 | OCI8.__set_prop(2, OCI8.__get_prop(2) | 4) # set OCI_EVENTS 56 | else 57 | OCI8.__set_prop(2, OCI8.__get_prop(2) & ~4) # unset OCI_EVENTS 58 | end 59 | when :cancel_read_at_exit 60 | val = val ? true : false 61 | OCI8.__set_prop(3, val) 62 | when :tcp_connect_timeout, :connect_timeout, :send_timeout, :recv_timeout 63 | if !val.nil? 64 | val = val.to_i 65 | raise ArgumentError, "The property value for :#{name} must be nil or a positive integer." if val <= 0 66 | end 67 | when :tcp_keepalive 68 | val = val ? true : false 69 | when :tcp_keepalive_time 70 | if !val.nil? 71 | val = val.to_i 72 | raise ArgumentError, "The property value for :#{name} must be nil or a positive integer." if val <= 0 73 | end 74 | OCI8.__set_prop(4, val) 75 | end 76 | super(name, val) 77 | end 78 | 79 | # Returns a Hash which ruby-oci8 global settings. 80 | # The hash's setter and getter methods are customized to check 81 | # property names and values. 82 | # 83 | # # get properties 84 | # OCI8.properties[:bind_string_as_nchar] # => false 85 | # OCI8.properties[:invalid_property_name] # raises an IndexError 86 | # 87 | # # set properties 88 | # OCI8.properties[:bind_string_as_nchar] = true 89 | # OCI8.properties[:invalid_property_name] = true # raises an IndexError 90 | # 91 | # Supported properties are listed below: 92 | # 93 | # [:length_semantics] 94 | # 95 | # +:char+ when Oracle character length is counted by the number of characters. 96 | # +:byte+ when it is counted by the number of bytes. 97 | # The default setting is +:byte+ because +:char+ causes unexpected behaviour on 98 | # Oracle 9i. 99 | # 100 | # *Since:* 2.1.0 101 | # 102 | # [:bind_string_as_nchar] 103 | # 104 | # +true+ when string bind variables are bound as NCHAR, 105 | # otherwise +false+. The default value is +false+. 106 | # 107 | # [:float_conversion_type] 108 | # 109 | # +:ruby+ when Oracle decimal numbers are converted to ruby Float values 110 | # same as Float#to_s does. (default) 111 | # +:oracle:+ when they are done by Oracle OCI functions. 112 | # 113 | # From ruby 1.9.2, a float value converted from Oracle number 15.7 by 114 | # the Oracle function OCINumberToReal() makes a string representation 115 | # 15.700000000000001 by Float#to_s. 116 | # See: https://web.archive.org/web/20140521195004/https://rubyforge.org/forum/forum.php?thread_id=50030&forum_id=1078 117 | # 118 | # *Since:* 2.1.0 119 | # 120 | # [:statement_cache_size] 121 | # 122 | # The statement cache size per each session. The default size is 0, which 123 | # means no statement cache, since 2.1.2. It was 20 in 2.1.1. 124 | # See: http://docs.oracle.com/cd/E11882_01/appdev.112/e10646/oci09adv.htm#i471377 125 | # 126 | # *Since:* 2.1.1 127 | # 128 | # [:events_mode] 129 | # 130 | # +true+ when Fast Application Notification (FAN) Support is enabled. 131 | # +false+ when it is disabled. The default value is +false+. 132 | # This corresponds to {http://php.net/manual/en/oci8.configuration.php#ini.oci8.events +oci8.events+ in PHP}. 133 | # 134 | # This parameter can be changed only when no OCI methods are called. 135 | # 136 | # require 'oci8' 137 | # OCI8.properties[:events_mode] = true # works fine. 138 | # # ... call some OCI methods ... 139 | # OCI8.properties[:events_mode] = true # raises a runtime error. 140 | # 141 | # *Since:* 2.1.4 142 | # 143 | # [:cancel_read_at_exit] 144 | # 145 | # +true+ when read system calls are canceled at exit. Otherwise, +false+. 146 | # The default value is +false+ because it uses unusual technique which 147 | # hooks read system calls issued by Oracle client library and it works 148 | # only on Linux, OSX and Windows. 149 | # This feature is added not to block ruby process termination when 150 | # network quality is poor and packets are lost irregularly. 151 | # 152 | # *Since:* 2.1.8 153 | # 154 | # [:tcp_connect_timeout] 155 | # 156 | # See {file:docs/timeout-parameters.md} 157 | # 158 | # *Since:* 2.2.2 159 | # 160 | # [:connect_timeout] 161 | # 162 | # See {file:docs/timeout-parameters.md} 163 | # 164 | # *Since:* 2.2.2 165 | # 166 | # [:send_timeout] 167 | # 168 | # See {file:docs/timeout-parameters.md} 169 | # 170 | # *Since:* 2.2.2 171 | # 172 | # [:recv_timeout] 173 | # 174 | # See {file:docs/timeout-parameters.md} 175 | # 176 | # *Since:* 2.2.2 177 | # 178 | # [:tcp_keepalive] 179 | # 180 | # See {file:docs/hanging-after-inactivity.md} 181 | # 182 | # *Since:* 2.2.4 183 | # 184 | # [:tcp_keepalive_time] 185 | # 186 | # See {file:docs/hanging-after-inactivity.md} 187 | # 188 | # *Since:* 2.2.4 189 | # 190 | # @return [a customized Hash] 191 | # @since 2.0.5 192 | # 193 | def self.properties 194 | @@properties 195 | end 196 | end 197 | -------------------------------------------------------------------------------- /lib/oci8/version.rb: -------------------------------------------------------------------------------- 1 | class OCI8 2 | VERSION = "2.2.14" 3 | end 4 | -------------------------------------------------------------------------------- /lib/ruby-oci8.rb: -------------------------------------------------------------------------------- 1 | require 'oci8' 2 | -------------------------------------------------------------------------------- /metaconfig: -------------------------------------------------------------------------------- 1 | # --*- ruby -*-- 2 | 3 | ToplevelInstaller::TASKS.push ['test', 'run test.'] 4 | ToplevelInstaller::TASKS.push ['distbin', 'make binary package.'] 5 | ToplevelInstaller.module_eval { 6 | alias parsearg_test parsearg_no_options 7 | alias parsearg_distbin parsearg_no_options 8 | 9 | def exec_test 10 | old = Dir.pwd 11 | @installer.mkdir_p "#{objdir_root}/test" 12 | Dir.chdir "#{objdir_root}/test" 13 | @installer.ruby("-w -I#{srcdir_root}/ext/oci8 -I#{srcdir_root}/lib -I#{srcdir_root}/support -I#{srcdir_root}/test #{srcdir_root}/test/test_all.rb") 14 | Dir.chdir old 15 | end 16 | 17 | def get_zcontent(file) 18 | require 'base64' 19 | require 'zlib' 20 | File.open(file, 'rb') do |f| 21 | Base64.encode64(Zlib::Deflate::deflate(f.read)) 22 | end 23 | end 24 | 25 | def exec_distbin 26 | File.open("ruby-oci8-#{RUBY_PLATFORM}.rb", 'w') do |f| 27 | f.write <<-EOT 28 | # ruby-oci8 binary installer for #{RUBY_PLATFORM} 29 | require 'rbconfig' 30 | require 'base64' 31 | require 'ftools' 32 | require 'zlib' 33 | 34 | def msg_error(msg) 35 | puts "ERROR: " 36 | puts msg 37 | exit 1 38 | end 39 | 40 | def msg_ok(msg) 41 | puts msg 42 | end 43 | 44 | def msg_ask(msg) 45 | puts msg 46 | $stdout.print "Enter Yes/No: " 47 | $stdout.flush 48 | ret = gets().chomp.downcase 49 | if ret == 'y' || ret == 'ye' || ret == 'yes' 50 | return 51 | else 52 | exit 1 53 | end 54 | end 55 | 56 | if '#{RUBY_PLATFORM}' != RUBY_PLATFORM 57 | msg_ask "This package is for #{RUBY_PLATFORM}\\ninstall anyway?" 58 | end 59 | 60 | class Installer 61 | @@log = "" 62 | def initialize(install_files) 63 | @files = [] 64 | install_files.each do |file, content| 65 | basename = File.basename(file) 66 | dirname = File.dirname(file) 67 | if dirname == '.' 68 | dirname = '' 69 | else 70 | dirname = '/' + dirname 71 | end 72 | case basename 73 | when /\.so$/ 74 | dirname = "\#{Config::CONFIG['sitearchdir']}\#{dirname}" 75 | else 76 | dirname = "\#{Config::CONFIG['sitelibdir']}\#{dirname}" 77 | end 78 | @files << [basename, dirname, content] 79 | end 80 | @files.sort! do |x, y| x[0] <=> y[0] end 81 | end 82 | 83 | def confirm 84 | msg = "" 85 | @files.each do |f| 86 | basename, dirname, content = f 87 | msg << "Copy \#{basename} to \#{dirname}\\n" 88 | end 89 | msg << "OK?\\n" 90 | msg_ask(msg) 91 | end 92 | 93 | def install 94 | @@log = "" 95 | @files.each do |f| 96 | basename, dirname, content = f 97 | @@log << "Copying \#{basename} to \#{dirname} ... " 98 | if not FileTest.directory?(dirname) 99 | File.makedirs(dirname) 100 | end 101 | File.open("\#{dirname}/\#{basename}", "wb") do |f| 102 | f.write(Zlib::Inflate::inflate(Base64.decode64(content))) 103 | end 104 | @@log << "done\\n" 105 | end 106 | @@log << "OK\\n" 107 | msg_ok(@@log) 108 | end 109 | 110 | def self.log 111 | @@log 112 | end 113 | end 114 | 115 | install_files = {} 116 | 117 | install_files['oci8lib.so'] = <<-EOS 118 | #{get_zcontent('ext/oci8/oci8lib.so')}EOS 119 | 120 | install_files['oci8.rb'] = <<-EOS 121 | #{get_zcontent('lib/oci8.rb')}EOS 122 | 123 | install_files['DBD/OCI8/OCI8.rb'] = <<-EOS 124 | #{get_zcontent('lib/DBD/OCI8/OCI8.rb')}EOS 125 | 126 | begin 127 | installer = Installer.new(install_files) 128 | installer.confirm 129 | installer.install 130 | rescue 131 | msg_error(Installer.log + "\\n" + $!.to_s) 132 | end 133 | EOT 134 | end 135 | end 136 | } 137 | 138 | 139 | ConfigTable.add_entry('oracle_version', ['', 140 | 'name', 141 | 'Oracle version name', 142 | 'NONE' ]) 143 | -------------------------------------------------------------------------------- /mkpkg-win32.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | $ruby_oci8_version = open('lib/oci8/version.rb').readlines.collect do |line| 4 | (line =~ /VERSION = "([^"]*)"/) && $1 5 | end.compact[0] 6 | 7 | $platform = ARGV[0] 8 | 9 | def run_cmd(cmd, raise_on_error = true) 10 | system(cmd) 11 | raise $?.to_s if raise_on_error && $?.to_i != 0 12 | end 13 | 14 | case $platform 15 | when 'x86-mingw32' 16 | $ruby_base_dirs = [] 17 | 18 | $ruby_base_dirs = 19 | [ 20 | 'c:\ruby\rubyinstaller-2.7.0-1-x86', 21 | 'c:\ruby\rubyinstaller-3.0.0-1-x86', 22 | 'c:\ruby\rubyinstaller-3.1.0-1-x86', 23 | 'c:\ruby\rubyinstaller-3.2.0-1-x86', 24 | 'c:\ruby\rubyinstaller-3.3.0-1-x86', 25 | ] 26 | 27 | $oracle_path = 'c:\oracle\instantclient_19_9-win32' 28 | $ridk_setup = 'ridk enable' 29 | 30 | $build_ruby_dirs = 31 | [ 32 | ['c:\ruby\rubyinstaller-2.7.0-1-x86', $ridk_setup], 33 | ['c:\ruby\rubyinstaller-3.0.0-1-x86', $ridk_setup], 34 | ['c:\ruby\rubyinstaller-3.1.0-1-x86', $ridk_setup], 35 | ['c:\ruby\rubyinstaller-3.2.0-1-x86', $ridk_setup], 36 | ['c:\ruby\rubyinstaller-3.3.0-1-x86', $ridk_setup], 37 | ] 38 | 39 | when 'x64-mingw32' 40 | $ruby_base_dirs = 41 | [ 42 | # RubyInstaller 43 | 'c:\ruby\rubyinstaller-2.7.0-1-x64', 44 | 'c:\ruby\rubyinstaller-3.0.0-1-x64', 45 | 'c:\ruby\rubyinstaller-3.1.0-1-x64', 46 | ] 47 | 48 | $oracle_path = 'c:\oracle\instantclient_19_9-win64' 49 | $ridk_setup = 'ridk enable' 50 | $build_ruby_dirs = 51 | [ 52 | ['c:\ruby\rubyinstaller-2.7.0-1-x64', $ridk_setup], 53 | ['c:\ruby\rubyinstaller-3.0.0-1-x64', $ridk_setup], 54 | ['c:\ruby\rubyinstaller-3.1.0-1-x64', $ridk_setup], 55 | ] 56 | 57 | when 'x64-mingw-ucrt' 58 | $ruby_base_dirs = 59 | [ 60 | # RubyInstaller 61 | 'c:\ruby\rubyinstaller-3.1.0-1-x64', 62 | 'c:\ruby\rubyinstaller-3.2.0-1-x64', 63 | 'c:\ruby\rubyinstaller-3.3.0-1-x64', 64 | ] 65 | 66 | $oracle_path = 'c:\oracle\instantclient_19_9-win64' 67 | $ridk_setup = 'ridk enable' 68 | $build_ruby_dirs = 69 | [ 70 | ['c:\ruby\rubyinstaller-3.1.0-1-x64', $ridk_setup], 71 | ['c:\ruby\rubyinstaller-3.2.0-1-x64', $ridk_setup], 72 | ['c:\ruby\rubyinstaller-3.3.0-1-x64', $ridk_setup], 73 | ] 74 | 75 | when 'all' 76 | files = File.read('dist-files').split("\n") 77 | mkpkg_rb = File.absolute_path?($0) ? $0 : File.join('..', $0) 78 | ['x86-mingw32', 'x64-mingw32', 'x64-mingw-ucrt'].each do |platform| 79 | FileUtils.rm_rf platform 80 | files.each do |file| 81 | dest = File.join(platform, file) 82 | FileUtils.mkdir_p File.dirname(dest) 83 | FileUtils.copy(file, dest) 84 | end 85 | Dir.chdir platform do 86 | run_cmd("#{RbConfig.ruby} #{mkpkg_rb} #{platform}") 87 | end 88 | end 89 | exit 0 90 | else 91 | puts "#{ARGV[0]} (x86-mingw32|x64-mingw32|x64-mingw-ucrt|all)" 92 | exit 0 93 | end 94 | 95 | $gem_package = "ruby-oci8-#{$ruby_oci8_version}-#{$platform}.gem" 96 | ENV['PATH'] = $oracle_path + ';c:\Windows\System32' 97 | ENV.delete('MSYSTEM') 98 | 99 | def prepend_path(basedir, others = []) 100 | orig_env = ENV['PATH'] 101 | begin 102 | ENV['PATH'] = basedir + '\bin;' + others.collect{|p| p + ';'}.join('') + orig_env 103 | yield 104 | ensure 105 | ENV['PATH'] = orig_env 106 | end 107 | end 108 | 109 | def ruby_oci8_gem_is_installed? 110 | open("|gem list -d ruby-oci8", 'r') do |f| 111 | while line = f.gets 112 | if line =~ /ruby-oci8/ 113 | return true 114 | end 115 | end 116 | end 117 | false 118 | end 119 | 120 | def make_gem 121 | Dir.glob('ext/oci8/oci8lib_*.so') do |file| 122 | File.delete file 123 | end 124 | 125 | $build_ruby_dirs.each do |ruby, dev_setup, disable_fortify_source| 126 | prepend_path(ruby) do 127 | fix_makefile = '' 128 | if disable_fortify_source 129 | fix_makefile += ' && sed -i "s/-D_FORTIFY_SOURCE=2/-D_FORTIFY_SOURCE=0/" ext\oci8\Makefile' 130 | end 131 | run_cmd("#{dev_setup} && ruby setup.rb config -- --with-runtime-check #{fix_makefile} && ruby setup.rb setup") 132 | end 133 | end 134 | 135 | ruby_for_gem_build = $build_ruby_dirs[0][0] 136 | prepend_path(ruby_for_gem_build) do 137 | run_cmd("gem build ruby-oci8.gemspec -- current") 138 | end 139 | end 140 | 141 | def install_and_test 142 | $ruby_base_dirs.each do |base_dir| 143 | prepend_path(base_dir) do 144 | puts "==================================================" 145 | puts " #{base_dir}" 146 | puts "==================================================" 147 | STDOUT.flush 148 | if ruby_oci8_gem_is_installed? 149 | run_cmd("gem uninstall ruby-oci8 --all") 150 | if ruby_oci8_gem_is_installed? 151 | raise "ruby-oci8 gem in #{base_dir} could not be uninstalled." 152 | end 153 | end 154 | begin 155 | run_cmd("gem install ./#{$gem_package} --local") 156 | run_cmd('ruby -roci8 -e "OCI8.properties[:tcp_keepalive_time] = 300"') 157 | run_cmd("ruby -I. test/test_all.rb", false) 158 | ensure 159 | run_cmd("gem uninstall ruby-oci8 --platform #{$platform}") 160 | end 161 | end 162 | end 163 | end 164 | 165 | make_gem 166 | ENV['LOCAL'] ||= '//172.17.0.2/ORCLPDB1' 167 | ENV['NLS_LANG'] ||= 'American_America.AL32UTF8' 168 | install_and_test 169 | -------------------------------------------------------------------------------- /pre-distclean.rb: -------------------------------------------------------------------------------- 1 | rm_f "#{curr_objdir}/ext/oci8/oci8lib_*.map" 2 | if RUBY_PLATFORM =~ /cygwin/ 3 | rm_f "#{curr_objdir}/ext/oci8/OCI.def" 4 | rm_f "#{curr_objdir}/ext/oci8/libOCI.a" 5 | end 6 | -------------------------------------------------------------------------------- /ruby-oci8.gemspec: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | # 3 | # To make a pure ruby gems package: 4 | # gem build ruby-oci8.gemspec 5 | # 6 | # To make a binary gems package: 7 | # gem build ruby-oci8.gemspec -- current 8 | # 9 | require 'fileutils' 10 | 11 | if ARGV.include?("--") and ARGV[(ARGV.index("--") + 1)] == 'current' 12 | gem_platform = 'current' 13 | else 14 | gem_platform = Gem::Platform::RUBY 15 | end 16 | 17 | if defined? OCI8 18 | oci8_version = OCI8::VERSION 19 | else 20 | # Load oci8/version.rb temporarily and delete OCI8. 21 | # Some 'bundler' tasks load this file before 'require "oci8"' 22 | # and fail with 'TypeError: superclass mismatch for class OCI8.' 23 | Kernel.load File.dirname(__FILE__) + '/lib/oci8/version.rb' 24 | oci8_version = OCI8::VERSION 25 | Object.send(:remove_const, :OCI8) 26 | end 27 | 28 | spec = Gem::Specification.new do |s| 29 | s.name = 'ruby-oci8' 30 | s.version = oci8_version 31 | s.summary = 'Ruby interface for Oracle using OCI8 API' 32 | s.email = 'kubo@jiubao.org' 33 | s.homepage = 'http://www.rubydoc.info/github/kubo/ruby-oci8' 34 | s.description = < true 73 | files.reject! do |fname| 74 | fname =~ /^ext/ 75 | end 76 | so_files.each do |fname| 77 | files << 'lib/' + File.basename(fname) 78 | end 79 | files << 'lib/oci8.rb' 80 | end 81 | s.require_paths = ['lib'] 82 | s.files = files 83 | s.test_files = 'test/test_all.rb' 84 | s.extra_rdoc_files = ['README.md'] 85 | end 86 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | Before running unit test: 2 | 3 | 1. Connect to Oracle as sys 4 | ```shell 5 | $ sqlplus sys/ as sysdba 6 | SQL> 7 | ``` 8 | 2. Create user ruby 9 | ```sql 10 | SQL> CREATE USER ruby IDENTIFIED BY oci8 DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp; 11 | SQL> alter user ruby quota unlimited on users; 12 | ``` 13 | 3. Grant the privilege to connect and execute. 14 | ```sql 15 | SQL> GRANT connect, resource, create view, create synonym TO ruby; 16 | SQL> GRANT execute ON dbms_lock TO ruby; 17 | ``` 18 | 4. Connect as ruby user. 19 | ```shell 20 | $ sqlplus ruby/oci8 21 | SQL> 22 | ``` 23 | 5. Create object types 24 | ```sql 25 | SQL> @test/setup_test_object.sql 26 | SQL> @test/setup_test_package.sql 27 | ``` 28 | 6. change $dbname in test/config.rb. 29 | 30 | Then run the following command: 31 | ```shell 32 | $ make check 33 | ``` 34 | or 35 | ``` 36 | $ nmake check (If your compiler is MS Visual C++.) 37 | ```` 38 | -------------------------------------------------------------------------------- /test/config.rb: -------------------------------------------------------------------------------- 1 | # $dbuser must have permission to run DBMS_LOCK.SLEEP 2 | # connect as sys 3 | # GRANT EXECUTE ON dbms_lock TO ruby; 4 | $dbuser = "ruby" 5 | $dbpass = "oci8" 6 | $dbname = nil 7 | 8 | # for test_bind_string_as_nchar in test_encoding.rb 9 | ENV['ORA_NCHAR_LITERAL_REPLACE'] = 'TRUE' if OCI8.client_charset_name.include? 'UTF8' 10 | 11 | # test_clob.rb 12 | 13 | nls_lang = ENV['NLS_LANG'] 14 | nls_lang = nls_lang.split('.')[1] unless nls_lang.nil? 15 | nls_lang = nls_lang.upcase unless nls_lang.nil? 16 | $lobfile = File.dirname(__FILE__) + '/../setup.rb' 17 | $lobreadnum = 256 # counts in charactors 18 | 19 | # don't modify below. 20 | 21 | begin 22 | require 'minitest/autorun' 23 | Minitest = MiniTest unless defined? Minitest 24 | Minitest::Test = Minitest::Unit::TestCase unless defined? Minitest::Test 25 | rescue LoadError 26 | require 'test/unit' 27 | module Minitest 28 | Test = ::Test::Unit::TestCase 29 | Assertions = ::Test::Unit::Assertions 30 | module Assertions 31 | alias refute_nil assert_not_nil 32 | end 33 | end 34 | end 35 | 36 | # $oracle_server_version: database compatible level of the Oracle server. 37 | # $oracle_client_version: Oracle client library version for which oci8 is compiled. 38 | # $oracle_version: lower value of $oracle_server_version and $oracle_client_version. 39 | conn = OCI8.new($dbuser, $dbpass, $dbname) 40 | begin 41 | conn.exec('select value from database_compatible_level') do |row| 42 | $oracle_server_version = OCI8::OracleVersion.new(row[0]) 43 | end 44 | rescue OCIError 45 | raise if $!.code != 942 # ORA-00942: table or view does not exist 46 | $oracle_server_version = OCI8::ORAVER_8_0 47 | end 48 | conn.logoff 49 | 50 | if $oracle_server_version < OCI8.oracle_client_version 51 | $oracle_version = $oracle_server_version 52 | else 53 | $oracle_version = OCI8.oracle_client_version 54 | end 55 | 56 | if $oracle_version < OCI8::ORAVER_8_1 57 | $test_clob = false 58 | else 59 | $test_clob = true 60 | end 61 | 62 | begin 63 | Time.new(2001, 1, 1, 0, 0, 0, '+00:00') 64 | # ruby 1.9.2 or later 65 | def convert_to_time(year, month, day, hour, minute, sec, subsec, timezone) 66 | subsec ||= 0 67 | if timezone 68 | # with time zone 69 | Time.new(year, month, day, hour, minute, sec + subsec, timezone) 70 | else 71 | # without time zone 72 | Time.local(year, month, day, hour, minute, sec, subsec * 1000000) 73 | end 74 | end 75 | rescue 76 | # ruby 1.9.1 or former 77 | def convert_to_time(year, month, day, hour, minute, sec, subsec, timezone) 78 | subsec ||= 0 79 | if timezone 80 | # with time zone 81 | /([+-])(\d+):(\d+)/ =~ timezone 82 | offset = ($1 + '1').to_i * ($2.to_i * 60 + $3.to_i) 83 | if offset == 0 84 | Time.utc(year, month, day, hour, minute, sec, subsec * 1000000) 85 | else 86 | tm = Time.local(year, month, day, hour, minute, sec, subsec * 1000000) 87 | raise "Failed to convert #{str} to Time" if tm.utc_offset != offset * 60 88 | tm 89 | end 90 | else 91 | # without time zone 92 | Time.local(year, month, day, hour, minute, sec, subsec * 1000000) 93 | end 94 | end 95 | end 96 | 97 | def convert_to_datetime(year, month, day, hour, minute, sec, subsec, timezone) 98 | subsec ||= 0 99 | utc_offset = if timezone 100 | # with time zone 101 | /([+-])(\d+):(\d+)/ =~ timezone 102 | ($1 + '1').to_i * ($2.to_i * 60 + $3.to_i) * 60 103 | else 104 | Time.local(year, month, day, hour, minute, sec).utc_offset 105 | end 106 | begin 107 | DateTime.civil(year, month, day, hour, minute, sec + subsec, utc_offset.to_r / 86400) 108 | rescue ArgumentError 109 | raise $! if $!.to_s != 'invalid date' 110 | # for ruby 1.8.6. 111 | # convert to a DateTime via a String as a workaround. 112 | if utc_offset >= 0 113 | sign = ?+ 114 | else 115 | sign = ?- 116 | utc_offset = - utc_offset; 117 | end 118 | tz_min = utc_offset / 60 119 | tz_hour, tz_min = tz_min.divmod 60 120 | time_str = format("%04d-%02d-%02dT%02d:%02d:%02d.%09d%c%02d:%02d", 121 | year, month, day, hour, minute, sec, (subsec * 1000_000_000).to_i, sign, tz_hour, tz_min) 122 | ::DateTime.parse(time_str) 123 | end 124 | end 125 | 126 | class Minitest::Test 127 | 128 | # @!visibility private 129 | # dirty hack to suppress "warning: constant ::Fixnum is deprecated" 130 | Fixnum = (0.class == ::Integer) ? ::Integer : ::Fixnum 131 | # @!visibility private 132 | # dirty hack to suppress "warning: constant ::Bignum is deprecated" 133 | Bignum = (0.class == ::Integer) ? ::Integer : ::Bignum 134 | 135 | def get_oci8_connection() 136 | OCI8.new($dbuser, $dbpass, $dbname) 137 | rescue OCIError 138 | raise if $!.code != 12516 && $!.code != 12520 139 | # sleep a few second and try again if 140 | # the error code is ORA-12516 or ORA-12520. 141 | # 142 | # ORA-12516 - TNS:listener could not find available handler with 143 | # matching protocol stack 144 | # ORA-12520 - TNS:listener could not find available handler for 145 | # requested type of server 146 | # 147 | # Thanks to Christopher Jones. 148 | # 149 | # Ref: The Underground PHP and Oracle Manual (page 175 in vesion 1.4) 150 | # http://www.oracle.com/technology/tech/php/pdf/underground-php-oracle-manual.pdf 151 | # 152 | sleep(5) 153 | OCI8.new($dbuser, $dbpass, $dbname) 154 | end 155 | 156 | def get_dbi_connection() 157 | DBI.connect("dbi:OCI8:#{$dbname}", $dbuser, $dbpass, 'AutoCommit' => false) 158 | rescue DBI::DatabaseError 159 | raise if $!.err != 12516 && $!.err != 12520 160 | # same as get_oci8_connection() 161 | sleep(5) 162 | DBI.connect("dbi:OCI8:#{$dbname}", $dbuser, $dbpass, 'AutoCommit' => false) 163 | end 164 | 165 | def drop_table(table_name) 166 | if $oracle_server_version < OCI8::ORAVER_10_1 167 | # Oracle 8 - 9i 168 | sql = "DROP TABLE #{table_name}" 169 | else 170 | # Oracle 10g - 171 | sql = "DROP TABLE #{table_name} PURGE" 172 | end 173 | 174 | if defined? @conn 175 | begin 176 | @conn.exec(sql) 177 | rescue OCIError 178 | raise if $!.code != 942 # table or view does not exist 179 | end 180 | elsif instance_variable_get(:@dbh) 181 | begin 182 | @dbh.do(sql) 183 | rescue DBI::DatabaseError 184 | raise if $!.err != 942 # table or view does not exist 185 | end 186 | end 187 | end # drop_table 188 | 189 | def drop_type(type_name) 190 | begin 191 | @conn.exec("DROP TYPE BODY #{type_name}") 192 | rescue OCIError 193 | raise if $!.code != 4043 194 | end 195 | begin 196 | @conn.exec("DROP TYPE #{type_name}") 197 | rescue OCIError 198 | raise if $!.code != 4043 199 | end 200 | end # drop_type 201 | end 202 | -------------------------------------------------------------------------------- /test/setup.sql: -------------------------------------------------------------------------------- 1 | CREATE USER ruby IDENTIFIED BY oci8 DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp; 2 | GRANT connect, resource, create view, create synonym, unlimited tablespace TO ruby; 3 | GRANT EXECUTE ON dbms_lock TO ruby; 4 | 5 | CONNECT ruby/oci8 6 | @test/setup_test_object.sql 7 | @test/setup_test_package.sql 8 | -------------------------------------------------------------------------------- /test/setup_test_object.sql: -------------------------------------------------------------------------------- 1 | drop table rb_test_obj_tab1 purge 2 | / 3 | drop table rb_test_obj_tab2 purge 4 | / 5 | drop type rb_test_obj 6 | / 7 | drop type rb_test_obj_elem_ary_of_ary 8 | / 9 | drop type rb_test_obj_elem_array 10 | / 11 | drop type rb_test_obj_elem 12 | / 13 | drop type rb_test_obj_sub 14 | / 15 | drop type rb_test_obj_base 16 | / 17 | create type rb_test_obj_elem as object ( 18 | x integer, 19 | y integer 20 | ) 21 | / 22 | create or replace type rb_test_int_array as array(50) of integer 23 | / 24 | create or replace type rb_test_flt_array as array(50) of float 25 | / 26 | create or replace type rb_test_num_array as array(50) of number(10,1) 27 | / 28 | create or replace type rb_test_bdbl_array as array(50) of binary_double 29 | / 30 | create or replace type rb_test_bflt_array as array(50) of binary_float 31 | / 32 | create or replace type rb_test_str_array as array(50) of varchar2(50) 33 | / 34 | create or replace type rb_test_raw_array as array(50) of raw(50) 35 | / 36 | create or replace type rb_test_obj_elem_array as array(50) of rb_test_obj_elem 37 | / 38 | create or replace type rb_test_obj_elem_ary_of_ary as array(50) of rb_test_obj_elem_array 39 | / 40 | --create or replace type rb_test_date_array as array(50) of date 41 | --/ 42 | create type rb_test_obj as object ( 43 | int_val integer, 44 | flt_val float, 45 | num_val number(10,1), 46 | bdbl_val binary_double, 47 | bflt_val binary_float, 48 | str_val varchar2(50), 49 | raw_val raw(50), 50 | clob_val clob, 51 | nclob_val nclob, 52 | blob_val blob, 53 | obj_val rb_test_obj_elem, 54 | int_array_val rb_test_int_array, 55 | flt_array_val rb_test_flt_array, 56 | num_array_val rb_test_num_array, 57 | bdbl_array_val rb_test_bdbl_array, 58 | bflt_array_val rb_test_bflt_array, 59 | str_array_val rb_test_str_array, 60 | raw_array_val rb_test_raw_array, 61 | obj_array_val rb_test_obj_elem_array, 62 | obj_ary_of_ary_val rb_test_obj_elem_ary_of_ary, 63 | date_val date, 64 | timestamp_val timestamp(9), 65 | timestamp_tz_val timestamp(9) with time zone, 66 | -- date_array_val rb_test_date_array, 67 | 68 | constructor function rb_test_obj(n number) return self as result, 69 | static function test_object_version return integer, 70 | static function class_func(n number) return rb_test_obj, 71 | static procedure class_proc1(obj out rb_test_obj, n number), 72 | static procedure class_proc2(obj in out rb_test_obj), 73 | member function member_func return integer, 74 | member procedure member_proc(n in integer) 75 | ) 76 | / 77 | create type body rb_test_obj is 78 | constructor function rb_test_obj(n number) return self as result is 79 | str varchar(28); 80 | ts timestamp(9); 81 | ts_tz timestamp(9) with time zone; 82 | begin 83 | str := to_char(1990 + n, 'FM0000') || 84 | to_char(mod(round(n) * 5, 12) + 1, 'FM00') || 85 | to_char(mod(round(n) * 7, 27) + 1, 'FM00') || 86 | to_char(mod(round(n) * 9, 24), 'FM00') || 87 | to_char(mod(round(n) * 11, 60), 'FM00') || 88 | to_char(mod(round(n) * 13, 60), 'FM00') || 89 | to_char(mod(round(n) * 333333333, 1000000000), 'FM000000000'); 90 | ts := to_timestamp(str, 'yyyymmddhh24missff9'); 91 | str := str || to_char(mod(round(n) * 15, 24) - 11, 'FMS00') || 92 | to_char(mod(round(n) * 17, 60), 'FM00'); 93 | ts_tz := to_timestamp_tz(str, 'yyyymmddhh24missff9tzhtzm'); 94 | self.int_val := n; 95 | self.flt_val := n; 96 | self.num_val := n; 97 | self.bdbl_val := n; 98 | self.bflt_val := n; 99 | self.str_val := to_char(n); 100 | self.raw_val := utl_raw.cast_to_raw(to_char(n)); 101 | self.clob_val := to_clob(n); 102 | self.nclob_val := to_clob(n); 103 | self.blob_val := to_blob(utl_raw.cast_to_raw(to_char(n))); 104 | self.obj_val := rb_test_obj_elem(n, n + 1); 105 | self.date_val := ts; 106 | self.timestamp_val := ts; 107 | self.timestamp_tz_val := ts_tz; 108 | if self.int_val != 1 then 109 | self.int_array_val := rb_test_int_array(n, n + 1, n + 2); 110 | self.flt_array_val := rb_test_flt_array(n, n + 1, n + 2); 111 | self.num_array_val := rb_test_num_array(n, n + 1, n + 2); 112 | self.bdbl_array_val := rb_test_bdbl_array(n, n + 1, n + 2); 113 | self.bflt_array_val := rb_test_bflt_array(n, n + 1, n + 2); 114 | self.str_array_val := rb_test_str_array(to_char(n), to_char(n + 1), to_char(n + 2)); 115 | self.raw_array_val := rb_test_raw_array(utl_raw.cast_to_raw(to_char(n)), 116 | utl_raw.cast_to_raw(to_char(n + 1)), 117 | utl_raw.cast_to_raw(to_char(n + 2))); 118 | self.obj_array_val := rb_test_obj_elem_array(rb_test_obj_elem(n + 0, n + 1), 119 | rb_test_obj_elem(n + 1, n + 2), 120 | rb_test_obj_elem(n + 2, n + 3)); 121 | self.obj_ary_of_ary_val := rb_test_obj_elem_ary_of_ary(self.obj_array_val); 122 | -- self.date_array_val := rb_test_date_array(to_test_date(n), 123 | -- to_test_date(n + 1), 124 | -- to_test_date(n + 2)); 125 | end if; 126 | return; 127 | end; 128 | 129 | static function test_object_version return integer is 130 | begin 131 | return 4; 132 | end; 133 | 134 | static function class_func(n number) return rb_test_obj is 135 | begin 136 | return rb_test_obj(n); 137 | end; 138 | 139 | static procedure class_proc1(obj out rb_test_obj, n number) is 140 | begin 141 | obj := rb_test_obj(n); 142 | end; 143 | 144 | static procedure class_proc2(obj in out rb_test_obj) is 145 | begin 146 | obj.int_val := obj.int_val + 1; 147 | end; 148 | 149 | member function member_func return integer is 150 | begin 151 | return int_val; 152 | end; 153 | 154 | member procedure member_proc(n in integer) is 155 | begin 156 | self.int_val := n; 157 | end; 158 | end; 159 | / 160 | create table rb_test_obj_tab1 ( 161 | n number primary key, 162 | obj rb_test_obj 163 | ) 164 | / 165 | create table rb_test_obj_tab2 of rb_test_obj ( 166 | int_val primary key 167 | ) 168 | / 169 | declare 170 | n number := 0; 171 | obj rb_test_obj; 172 | begin 173 | loop 174 | n := n + 1.2; 175 | exit when n > 20; 176 | obj := rb_test_obj(n); 177 | insert into rb_test_obj_tab1 values (n, obj); 178 | insert into rb_test_obj_tab2 values (obj); 179 | end loop; 180 | end; 181 | / 182 | 183 | create type rb_test_obj_base as object ( 184 | id varchar2(30) 185 | ) not final 186 | / 187 | create type rb_test_obj_sub under rb_test_obj_base ( 188 | subid varchar2(30) 189 | ) final 190 | / 191 | create or replace function rb_test_obj_get_object(get_base integer) return rb_test_obj_base is 192 | begin 193 | if get_base = 0 then 194 | return rb_test_obj_base('base'); 195 | else 196 | return rb_test_obj_sub('sub', 'subid'); 197 | end if; 198 | end; 199 | / 200 | -------------------------------------------------------------------------------- /test/setup_test_package.sql: -------------------------------------------------------------------------------- 1 | create or replace package rb_test_pkg is 2 | package_version pls_integer := 1; 3 | 4 | type array_of_integer is array(50) of integer; 5 | type table_of_pls_integer is table of pls_integer; 6 | type table_of_boolean is table of boolean; 7 | type indexed_table_of_varchar2 is table of varchar2(10) index by varchar2(5); 8 | type rec1 is record (i pls_integer, j integer); 9 | type rec2 is record (b boolean, it indexed_table_of_varchar2, rec rec1); 10 | type table_of_rec1 is table of rec1; 11 | type table_of_rec2 is table of rec2; 12 | 13 | function sum_table_of_pls_integer(tbl in table_of_pls_integer) return pls_integer; 14 | function add_rec1_values(tbl in table_of_rec1) return pls_integer; 15 | procedure out_rec1_values(tbl out table_of_rec1); 16 | end; 17 | / 18 | create or replace package body rb_test_pkg is 19 | function sum_table_of_pls_integer(tbl in table_of_pls_integer) return pls_integer is 20 | i pls_integer; 21 | ret pls_integer := 0; 22 | begin 23 | for i in tbl.first..tbl.last loop 24 | ret := ret + tbl(i); 25 | end loop; 26 | return ret; 27 | end; 28 | 29 | function add_rec1_values(tbl in table_of_rec1) return pls_integer is 30 | i pls_integer; 31 | ret pls_integer := 0; 32 | begin 33 | for i in tbl.first..tbl.last loop 34 | ret := ret + nvl(tbl(i).i, 0) + nvl(tbl(i).j, 0); 35 | end loop; 36 | return ret; 37 | end; 38 | 39 | procedure out_rec1_values(tbl out table_of_rec1) is 40 | i pls_integer; 41 | rec1val rec1; 42 | begin 43 | tbl := table_of_rec1(); 44 | for i in 1..20 loop 45 | tbl.extend; 46 | if i mod 6 != 0 then 47 | if i mod 3 != 0 then 48 | rec1val.i := i; 49 | rec1val.j := i; 50 | else 51 | rec1val.i := null; 52 | rec1val.j := null; 53 | end if; 54 | tbl(i) := rec1val; 55 | end if; 56 | end loop; 57 | end; 58 | end; 59 | / 60 | -------------------------------------------------------------------------------- /test/test_all.rb: -------------------------------------------------------------------------------- 1 | srcdir = File.dirname(__FILE__) 2 | 3 | require 'oci8' 4 | require "#{srcdir}/config" 5 | 6 | require "#{srcdir}/test_oradate" 7 | require "#{srcdir}/test_oranumber" 8 | require "#{srcdir}/test_bind_array.rb" 9 | require "#{srcdir}/test_bind_string" 10 | require "#{srcdir}/test_bind_time" 11 | require "#{srcdir}/test_bind_raw" 12 | require "#{srcdir}/test_bind_integer" 13 | if $test_clob 14 | require "#{srcdir}/test_clob" 15 | end 16 | 17 | require "#{srcdir}/test_break" 18 | require "#{srcdir}/test_oci8" 19 | require "#{srcdir}/test_datetime" 20 | require "#{srcdir}/test_connstr" 21 | require "#{srcdir}/test_metadata" 22 | require "#{srcdir}/test_array_dml" 23 | require "#{srcdir}/test_rowid" 24 | require "#{srcdir}/test_appinfo" 25 | require "#{srcdir}/test_oracle_version" 26 | require "#{srcdir}/test_error" 27 | require "#{srcdir}/test_connection_pool" 28 | require "#{srcdir}/test_object" 29 | require "#{srcdir}/test_properties.rb" 30 | 31 | if OCI8.respond_to? :encoding 32 | require "#{srcdir}/test_encoding" 33 | end 34 | 35 | if $oracle_version >= OCI8::ORAVER_12_1 36 | require "#{srcdir}/test_package_type" 37 | require "#{srcdir}/test_bind_boolean.rb" 38 | end 39 | 40 | # Ruby/DBI 41 | begin 42 | require 'dbi' 43 | rescue LoadError 44 | begin 45 | require 'rubygems' 46 | require 'dbi' 47 | rescue LoadError 48 | dbi_not_found = true 49 | end 50 | end 51 | unless dbi_not_found 52 | require "#{srcdir}/test_dbi" 53 | if $test_clob 54 | require "#{srcdir}/test_dbi_clob" 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/test_appinfo.rb: -------------------------------------------------------------------------------- 1 | require 'oci8' 2 | require File.dirname(__FILE__) + '/config' 3 | 4 | class TestAppInfo < Minitest::Test 5 | 6 | def setup 7 | @conn = get_oci8_connection 8 | end 9 | 10 | def test_set_client_identifier 11 | # set client_id 12 | client_id = "ruby-oci8:#{Process.pid()}" 13 | @conn.client_identifier = client_id 14 | assert_equal(client_id, @conn.select_one("SELECT SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER') FROM DUAL")[0]); 15 | # check the first character 16 | assert_raises ArgumentError do 17 | @conn.client_identifier = ':bad_identifier' 18 | end 19 | 20 | # clear client_id 21 | @conn.client_identifier = nil 22 | assert_nil(@conn.select_one("SELECT SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER') FROM DUAL")[0]); 23 | end 24 | 25 | def test_set_module 26 | # FIXME: check again after upgrading Oracle 9.2 to 9.2.0.4. 27 | return if @conn.oracle_server_version < OCI8::ORAVER_10_1 28 | 29 | # set module 30 | @conn.module = 'ruby-oci8' 31 | assert_equal('ruby-oci8', @conn.select_one("SELECT SYS_CONTEXT('USERENV', 'MODULE') FROM DUAL")[0]); 32 | # clear module 33 | @conn.module = nil 34 | assert_nil(@conn.select_one("SELECT SYS_CONTEXT('USERENV', 'MODULE') FROM DUAL")[0]); 35 | end 36 | 37 | def test_set_action 38 | # FIXME: check again after upgrading Oracle 9.2 to 9.2.0.4. 39 | return if @conn.oracle_server_version < OCI8::ORAVER_10_1 40 | 41 | # set action 42 | @conn.action = 'test_set_action' 43 | assert_equal('test_set_action', @conn.select_one("SELECT SYS_CONTEXT('USERENV', 'ACTION') FROM DUAL")[0]); 44 | # clear action 45 | @conn.action = nil 46 | assert_nil(@conn.select_one("SELECT SYS_CONTEXT('USERENV', 'ACTION') FROM DUAL")[0]); 47 | end 48 | 49 | def test_set_client_info 50 | # set client_info 51 | client_info = "ruby-oci8:#{Process.pid()}" 52 | @conn.client_info = client_info 53 | assert_equal(client_info, @conn.select_one("SELECT SYS_CONTEXT('USERENV', 'CLIENT_INFO') FROM DUAL")[0]); 54 | # clear client_info 55 | @conn.client_info = nil 56 | assert_nil(@conn.select_one("SELECT SYS_CONTEXT('USERENV', 'CLIENT_INFO') FROM DUAL")[0]); 57 | end 58 | 59 | def teardown 60 | @conn.logoff 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/test_bind_array.rb: -------------------------------------------------------------------------------- 1 | require 'oci8' 2 | require File.dirname(__FILE__) + '/config' 3 | 4 | class TestBindArray < Minitest::Test 5 | 6 | def test_bind_array_names 7 | assert_equal(":id_0", OCI8::in_cond(:id, []).names) 8 | assert_equal(":id_0", OCI8::in_cond(:id, [1]).names) 9 | assert_equal(":id_0, :id_1", OCI8::in_cond(:id, [1, 2]).names) 10 | assert_equal(":id_0, :id_1, :id_2", OCI8::in_cond(:id, [1, 2, 3]).names) 11 | end 12 | 13 | def test_bind_array_values 14 | assert_equal([[nil, String, nil]], OCI8::in_cond(:id, []).values) 15 | assert_equal([[1, nil, nil]], OCI8::in_cond(:id, [1]).values) 16 | assert_equal([[1, nil, nil], [2, nil, nil]], OCI8::in_cond(:id, [1, 2]).values) 17 | assert_equal([[1, nil, nil], [2, nil, nil], [3, nil, nil]], OCI8::in_cond(:id, [1, 2, 3]).values) 18 | end 19 | 20 | def test_bind_array_values_containg_nil 21 | assert_equal([[nil, String]], OCI8::in_cond(:id, [nil]).values) 22 | assert_equal([[nil, Fixnum], [2, nil, nil], [3, nil, nil]], OCI8::in_cond(:id, [nil, 2, 3]).values) 23 | assert_equal([[1, nil, nil], [nil, Fixnum], [3, nil, nil]], OCI8::in_cond(:id, [1, nil, 3]).values) 24 | end 25 | 26 | def test_bind_array_values_with_type 27 | assert_equal([[nil, Integer, nil]], OCI8::in_cond(:id, [], Integer).values) 28 | assert_equal([[1, Integer, nil]], OCI8::in_cond(:id, [1], Integer).values) 29 | assert_equal([[1, Integer, nil], [2, Integer, nil]], OCI8::in_cond(:id, [1, 2], Integer).values) 30 | assert_equal([[1, Integer, nil], [2, Integer, nil], [3, Integer, nil]], OCI8::in_cond(:id, [1, 2, 3], Integer).values) 31 | assert_equal([[nil, Integer, nil], [2, Integer, nil], [3, Integer, nil]], OCI8::in_cond(:id, [nil, 2, 3], Integer).values) 32 | assert_equal([[1, Integer, nil], [nil, Integer, nil], [3, Integer, nil]], OCI8::in_cond(:id, [1, nil, 3], Integer).values) 33 | end 34 | 35 | def test_select 36 | @conn = get_oci8_connection 37 | begin 38 | drop_table('test_table') 39 | @conn.exec(< 0 THEN 62 | bool_val := TRUE; 63 | ELSE 64 | bool_val := FALSE; 65 | END IF; 66 | :out := bool_val; 67 | END; 68 | EOS 69 | cursor = @conn.parse(stmt) 70 | cursor.bind_param(:in, nil, Integer) 71 | cursor.bind_param(:out, nil, TrueClass) 72 | 73 | cursor[:in] = 1 74 | cursor.exec 75 | assert_equal(true, cursor[:out]) 76 | 77 | cursor[:in] = 0 78 | cursor.exec 79 | assert_equal(false, cursor[:out]) 80 | 81 | do_block_cnt = 0 82 | @conn.exec(stmt, 1, true) do |in_val, out_val| 83 | assert_equal(1, in_val) 84 | assert_equal(true, out_val) 85 | do_block_cnt += 1 86 | end 87 | 88 | @conn.exec(stmt, 0, true) do |in_val, out_val| 89 | assert_equal(0, in_val) 90 | assert_equal(false, out_val) 91 | do_block_cnt += 1 92 | end 93 | assert_equal(2, do_block_cnt) 94 | end 95 | 96 | def teardown 97 | @conn.logoff 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /test/test_bind_integer.rb: -------------------------------------------------------------------------------- 1 | require 'oci8' 2 | require File.dirname(__FILE__) + '/config' 3 | 4 | class TestBindInteger < Minitest::Test 5 | 6 | def setup 7 | @conn = get_oci8_connection 8 | end 9 | 10 | POSITIVE_INTS = [ 11 | 100, 12 | 2**30, 13 | 2**31, 14 | 2**32, 15 | 2**33, 16 | 2**62, 17 | 2**63, 18 | 2**64, 19 | ('9' * 38).to_i 20 | ] 21 | 22 | def bind_and_get(input_value, output_type) 23 | cursor = @conn.parse("BEGIN :out := :in; END;") 24 | cursor.bind_param(:out, output_type) 25 | cursor.bind_param(:in, input_value) 26 | cursor.exec 27 | result = cursor[:out] 28 | cursor.close 29 | result 30 | end 31 | 32 | (POSITIVE_INTS + [0] + POSITIVE_INTS.map(&:-@)).each do |num| 33 | define_method("test_bind_param_with_input_of '#{num}'") do 34 | assert_equal(OraNumber.new(num.to_s), bind_and_get(num, OraNumber)) 35 | end 36 | 37 | define_method("test_bind_param_with_output_of '#{num}'") do 38 | result = bind_and_get(OraNumber.new(num.to_s), Integer) 39 | assert_equal(num, result) 40 | assert_kind_of(Integer, result) 41 | end 42 | end 43 | 44 | def teardown 45 | @conn.logoff 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/test_bind_raw.rb: -------------------------------------------------------------------------------- 1 | # Low-level API 2 | require 'oci8' 3 | require File.dirname(__FILE__) + '/config' 4 | 5 | class TestBindRaw < Minitest::Test 6 | CHECK_TARGET = [ 7 | ["0123456789:;<=>?", "303132333435363738393A3B3C3D3E3F"], 8 | ["@ABCDEFGHIJKLMNO", "404142434445464748494A4B4C4D4E4F"], 9 | ["PQRSTUVWXYZ[\\]^_", "505152535455565758595A5B5C5D5E5F"], 10 | ["`abcdefghijklmno", "606162636465666768696A6B6C6D6E6F"], 11 | ["pqrstuvwxyz{|}~", "707172737475767778797A7B7C7D7E"], 12 | ] 13 | 14 | def setup 15 | @conn = get_oci8_connection() 16 | end 17 | 18 | def test_set_raw 19 | cursor = @conn.parse("BEGIN :hex := RAWTOHEX(:raw); END;") 20 | cursor.bind_param(:raw, nil, OCI8::RAW, 16) 21 | cursor.bind_param(:hex, nil, String, 32) 22 | 23 | CHECK_TARGET.each do |raw, hex| 24 | cursor[:raw] = raw 25 | cursor.exec 26 | assert_equal(hex, cursor[:hex]) 27 | end 28 | end 29 | 30 | def test_get_raw 31 | cursor = @conn.parse("BEGIN :raw := HEXTORAW(:hex); END;") 32 | cursor.bind_param(:hex, nil, String, 32) 33 | cursor.bind_param(:raw, nil, OCI8::RAW, 16) 34 | 35 | CHECK_TARGET.each do |raw, hex| 36 | cursor[:hex] = hex 37 | cursor.exec 38 | assert_equal(raw, cursor[:raw]) 39 | end 40 | end 41 | 42 | def teardown 43 | @conn.logoff 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/test_bind_string.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require 'oci8' 3 | require File.dirname(__FILE__) + '/config' 4 | 5 | class TestBindString < Minitest::Test 6 | def setup 7 | @conn = get_oci8_connection 8 | end 9 | 10 | def teardown 11 | @conn.logoff 12 | end 13 | 14 | if OCI8.client_charset_name.include? 'UTF8' 15 | 16 | def test_bind_string_as_nchar 17 | if ['AL32UTF8', 'UTF8', 'ZHS32GB18030'].include? @conn.database_charset_name 18 | warn "Skip test_bind_string_as_nchar. It needs Oracle server whose database chracter set is incompatible with unicode." 19 | else 20 | drop_table('test_table') 21 | @conn.exec("CREATE TABLE test_table (ID NUMBER(5), V VARCHAR2(10), NV1 NVARCHAR2(10), NV2 NVARCHAR2(10))") 22 | 23 | orig_prop = OCI8.properties[:bind_string_as_nchar] 24 | begin 25 | utf8_string = "a¡あ" 26 | 27 | OCI8.properties[:bind_string_as_nchar] = false 28 | @conn.exec("INSERT INTO test_table VALUES (1, N'#{utf8_string}', N'#{utf8_string}', :1)", utf8_string) 29 | v, nv1, nv2 = @conn.select_one('select V, NV1, NV2 from test_table where ID = 1') 30 | assert_not_equal(utf8_string, v) # Some UTF-8 chracters should be garbled. 31 | assert_equal(utf8_string, nv1) # No garbled characters 32 | assert_equal(v, nv2) # Garbled as VARCHAR2 column. 33 | 34 | OCI8.properties[:bind_string_as_nchar] = true 35 | @conn.exec("INSERT INTO test_table VALUES (2, N'#{utf8_string}', N'#{utf8_string}', :1)", utf8_string) 36 | v, nv1, nv2 = @conn.select_one('select V, NV1, NV2 from test_table where ID = 2') 37 | assert_not_equal(utf8_string, v) # Some UTF-8 chracters should be garbled. 38 | assert_equal(utf8_string, nv1) # No garbled characters 39 | assert_equal(nv1, nv2) # Same as NVARCHAR2. 40 | 41 | @conn.commit 42 | ensure 43 | OCI8.properties[:bind_string_as_nchar] = orig_prop 44 | end 45 | end 46 | end 47 | 48 | def test_length_semantics # This test needs to be revised. 49 | orig_prop = OCI8.properties[:length_semantics] 50 | begin 51 | utf8_string = "a¡あ" 52 | 53 | OCI8.properties[:length_semantics] = :byte 54 | assert_equal(:byte, OCI8.properties[:length_semantics]) 55 | cursor = @conn.parse <= "2.0") || (defined? RUBY_ENGINE and RUBY_ENGINE == "truffleruby") 66 | # Rubinius 2.0.0. 67 | # DBMS_LOCK.SLEEP blocks ruby threads which try to call C-API. 68 | expect[SEND_BREAK] = TIME_TO_BREAK 69 | else 70 | # MRI and Rubinius 1.2.4. 71 | # DBMS_LOCK.SLEEP blocks all ruby threads. 72 | expect[SEND_BREAK] = TIME_IN_PLSQL + TIME_TO_BREAK 73 | end 74 | do_test_ocibreak(@conn, expect) 75 | end 76 | 77 | def test_non_blocking_mode 78 | @conn.non_blocking = true 79 | assert_equal(true, @conn.non_blocking?) 80 | expect = [] 81 | if server_is_runing_on_windows? 82 | if $oracle_server_version >= OCI8::ORAVER_9_0 83 | # raise after sleeping #{TIME_IN_PLSQL} seconds. 84 | expect[PLSQL_DONE] = "Invalid status" 85 | expect[OCIBREAK] = TIME_IN_PLSQL 86 | else 87 | expect[PLSQL_DONE] = TIME_IN_PLSQL 88 | expect[OCIBREAK] = "Invalid status" 89 | end 90 | else 91 | # raise immediately by OCI8#break. 92 | expect[PLSQL_DONE] = "Invalid status" 93 | expect[OCIBREAK] = TIME_TO_BREAK 94 | end 95 | expect[SEND_BREAK] = TIME_TO_BREAK 96 | do_test_ocibreak(@conn, expect) 97 | end 98 | 99 | def test_timeout 100 | @conn.non_blocking = true 101 | start_time = Time.now 102 | if server_is_runing_on_windows? 103 | end_time = start_time + 5 104 | else 105 | end_time = start_time + 1 106 | end 107 | 108 | if defined? Rubinius and Rubinius::VERSION < "2.0" 109 | # Rubinius 1.2.4 110 | expected = OCIBreak 111 | else 112 | # MRI and Rubinius 2.0.0 113 | expected = Timeout::Error 114 | end 115 | assert_raises(expected) do 116 | Timeout.timeout(1) do 117 | @conn.exec("BEGIN DBMS_LOCK.SLEEP(5); END;") 118 | end 119 | end 120 | assert_in_delta(Time.now, end_time, 1) 121 | sleep(0.01) # for truffleruby. Is truffleruby too fast? 122 | @conn.exec("BEGIN NULL; END;") 123 | assert_in_delta(Time.now, end_time, 1) 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /test/test_clob.rb: -------------------------------------------------------------------------------- 1 | # Low-level API 2 | require 'oci8' 3 | require File.dirname(__FILE__) + '/config' 4 | 5 | class TestCLob < Minitest::Test 6 | 7 | def setup 8 | @conn = get_oci8_connection 9 | drop_table('test_table') 10 | @conn.exec('CREATE TABLE test_table (filename VARCHAR2(40), content CLOB)') 11 | end 12 | 13 | def test_insert 14 | filename = File.basename($lobfile) 15 | @conn.exec("DELETE FROM test_table WHERE filename = :1", filename) 16 | @conn.exec("INSERT INTO test_table(filename, content) VALUES (:1, EMPTY_CLOB())", filename) 17 | cursor = @conn.exec("SELECT content FROM test_table WHERE filename = :1 FOR UPDATE", filename) 18 | lob = cursor.fetch[0] 19 | open($lobfile) do |f| 20 | while s = f.read(1000) 21 | lob.write(s) 22 | end 23 | end 24 | lob.close 25 | end 26 | 27 | def test_insert_symbol 28 | filename = 'test_symbol' 29 | value = :foo_bar 30 | @conn.exec("DELETE FROM test_table WHERE filename = :1", filename) 31 | @conn.exec("INSERT INTO test_table(filename, content) VALUES (:1, EMPTY_CLOB())", filename) 32 | cursor = @conn.exec("SELECT content FROM test_table WHERE filename = :1 FOR UPDATE", filename) 33 | lob = cursor.fetch[0] 34 | lob.write(value) 35 | lob.rewind 36 | assert_equal(value.to_s, lob.read); 37 | lob.close 38 | end 39 | 40 | def test_read 41 | test_insert() # first insert data. 42 | filename = File.basename($lobfile) 43 | cursor = @conn.exec("SELECT content FROM test_table WHERE filename = :1 FOR UPDATE", filename) 44 | lob = cursor.fetch[0] 45 | 46 | open($lobfile) do |f| 47 | while buf = lob.read($lobreadnum) 48 | fbuf = f.read(buf.size) 49 | assert_equal(fbuf, buf) 50 | # offset += buf.size will not work fine, 51 | # Though buf.size counts in byte, 52 | # offset and $lobreadnum count in character. 53 | end 54 | assert_nil(buf) 55 | assert(f.eof?) 56 | assert(lob.eof?) 57 | end 58 | lob.close 59 | end 60 | 61 | # https://github.com/kubo/ruby-oci8/issues/20 62 | def test_github_issue_20 63 | # Skip this test if FULLTEST isn't set because it takes 4 minutes in my Linux box. 64 | return if ENV['FULLTEST'] 65 | 66 | lob1 = OCI8::CLOB.new(@conn, ' ' * (1024 * 1024)) 67 | lob1.read(1) # to suppress `warning: assigned but unused variable - lob1` 68 | begin 69 | lob2 = OCI8::CLOB.new(@conn, ' ' * (128 * 1024 * 1024)) 70 | rescue OCIError 71 | raise if $!.code != 24817 72 | # ORA-24817: Unable to allocate the given chunk for current lob operation 73 | GC.start 74 | # allocate smaller size 75 | lob2 = OCI8::CLOB.new(@conn, ' ' * (16 * 1024 * 1024)) 76 | end 77 | lob1 = nil # lob1's value will be freed in GC. 78 | lob2.read # GC must run here to reproduce the issue. 79 | end 80 | 81 | def teardown 82 | drop_table('test_table') 83 | @conn.logoff 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /test/test_connection_pool.rb: -------------------------------------------------------------------------------- 1 | require 'oci8' 2 | require File.dirname(__FILE__) + '/config' 3 | 4 | class TestConnectionPool < Minitest::Test 5 | 6 | def create_pool(min, max, incr) 7 | OCI8::ConnectionPool.new(min, max, incr, $dbuser, $dbpass, $dbname) 8 | rescue OCIError 9 | raise if $!.code != 12516 && $!.code != 12520 10 | sleep(5) 11 | OCI8::ConnectionPool.new(min, max, incr, $dbuser, $dbpass, $dbname) 12 | end 13 | 14 | def test_connect 15 | pool = create_pool(1, 5, 3) 16 | assert_equal(1, pool.min) 17 | assert_equal(5, pool.max) 18 | assert_equal(3, pool.incr) 19 | end 20 | 21 | def test_reinitialize 22 | pool = create_pool(1, 5, 3) 23 | pool.reinitialize(2, 6, 4) 24 | assert_equal(2, pool.min) 25 | assert_equal(6, pool.max) 26 | assert_equal(4, pool.incr) 27 | end 28 | 29 | def test_busy_and_open_count 30 | check_busy_and_open_count(1, 5, 3) 31 | check_busy_and_open_count(2, 4, 1) 32 | end 33 | 34 | def check_busy_and_open_count(min_cnt, max_cnt, incr_cnt) 35 | msg = "create_pool(#{min_cnt}, #{max_cnt}, #{incr_cnt})" 36 | # Create a connection pool. 37 | pool = create_pool(min_cnt, max_cnt, incr_cnt) 38 | assert_equal(min_cnt, pool.open_count, msg) 39 | assert_equal(0, pool.busy_count, msg) 40 | 41 | non_blocking = true 42 | 43 | # Create connections from the pool. 44 | conns = [] 45 | max_cnt.times do |cnt| 46 | conn = OCI8.new($dbuser, $dbpass, pool) 47 | if cnt == 0 48 | unless conn.non_blocking? 49 | non_blocking = false 50 | assert_raises(RuntimeError) do 51 | # This should raise "Could not set non-blocking mode to a connection allocated from OCI8::ConnectionPool." 52 | conn.non_blocking = true 53 | end 54 | end 55 | end 56 | conns << conn 57 | end 58 | assert_equal(min_cnt, pool.open_count, msg) 59 | assert_equal(0, pool.busy_count, msg) 60 | 61 | # Execute blocking SQL statements sequentially. 62 | max_cnt.times do |n| 63 | thread = Thread.start do 64 | conns[n].exec "BEGIN DBMS_LOCK.SLEEP(1); END;" 65 | end 66 | sleep(0.5) 67 | assert_equal(min_cnt, pool.open_count, msg) 68 | assert_equal(non_blocking ? 1 : 0, pool.busy_count, msg) 69 | thread.join 70 | end 71 | assert_equal(min_cnt, pool.open_count, msg) 72 | assert_equal(0, pool.busy_count, msg) 73 | 74 | # Execute blocking SQL statements parallel to increment open_count. 75 | threads = [] 76 | (min_cnt + 1).times do |n| 77 | threads << Thread.start do 78 | conns[n].exec "BEGIN DBMS_LOCK.SLEEP(5); END;" 79 | end 80 | end 81 | sleep(0.5) 82 | assert_equal(non_blocking ? (min_cnt + incr_cnt) : min_cnt, pool.open_count, msg) 83 | assert_equal(non_blocking ? (min_cnt + 1) : 0, pool.busy_count, msg) 84 | 85 | # Execute blocking SQL statements parallel up to maximum. 86 | (min_cnt + 1).upto(max_cnt - 1) do |n| 87 | threads << Thread.start do 88 | conns[n].exec "BEGIN DBMS_LOCK.SLEEP(4); END;" 89 | end 90 | end 91 | sleep(0.5) 92 | assert_equal(non_blocking ? max_cnt : min_cnt, pool.open_count, msg) 93 | assert_equal(non_blocking ? max_cnt : 0, pool.busy_count, msg) 94 | 95 | # 96 | threads.each do |thr| 97 | thr.join 98 | end 99 | assert_equal(non_blocking ? max_cnt : min_cnt, pool.open_count, msg) 100 | assert_equal(0, pool.busy_count, msg) 101 | 102 | # Set timeout 103 | pool.timeout = 1 104 | sleep(1.5) 105 | assert_equal(non_blocking ? max_cnt : min_cnt, pool.open_count, msg) # open_count doesn't shrink. 106 | assert_equal(0, pool.busy_count, msg) 107 | conns[0].ping # make a network roundtrip. 108 | sleep(0.5) 109 | # open_count shrinks. 110 | # The decrement count depends on Oracle version. 111 | assert_operator(pool.open_count, :<, max_cnt, msg) 112 | assert_equal(0, pool.busy_count, msg) 113 | 114 | # Close all conections. 115 | conns.each do | conn | 116 | conn.logoff 117 | end 118 | assert_equal(min_cnt, pool.open_count, msg) 119 | assert_equal(0, pool.busy_count, msg) 120 | end 121 | 122 | def test_nowait 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /test/test_dbi_clob.rb: -------------------------------------------------------------------------------- 1 | require 'dbi' 2 | require 'oci8' 3 | require File.dirname(__FILE__) + '/config' 4 | 5 | class TestDbiCLob < Minitest::Test 6 | 7 | def setup 8 | @dbh = get_dbi_connection() 9 | drop_table('test_table') 10 | @dbh.execute('CREATE TABLE test_table (filename VARCHAR2(40), content CLOB)') 11 | end 12 | 13 | def test_insert 14 | filename = File.basename($lobfile) 15 | 16 | # insert an empty clob and get the rowid. 17 | rowid = @dbh.execute("INSERT INTO test_table(filename, content) VALUES (:1, EMPTY_CLOB())", filename) do |sth| 18 | sth.func(:rowid) 19 | end 20 | lob = @dbh.select_one("SELECT content FROM test_table WHERE filename = :1 FOR UPDATE", filename)[0] 21 | begin 22 | open($lobfile) do |f| 23 | while f.gets() 24 | lob.write($_) 25 | end 26 | end 27 | ensure 28 | lob.close() 29 | end 30 | end 31 | 32 | def test_read 33 | filename = File.basename($lobfile) 34 | test_insert() # first insert data. 35 | lob = @dbh.select_one("SELECT content FROM test_table WHERE filename = :1 FOR UPDATE", filename)[0] 36 | begin 37 | open($lobfile) do |f| 38 | while buf = lob.read($lobreadnum) 39 | fbuf = f.read(buf.size) 40 | assert_equal(fbuf, buf) 41 | end 42 | assert_equal(nil, buf) 43 | assert_equal(true, f.eof?) 44 | end 45 | ensure 46 | lob.close() 47 | end 48 | end 49 | 50 | def teardown 51 | @dbh.disconnect 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/test_encoding.rb: -------------------------------------------------------------------------------- 1 | require 'oci8' 2 | require File.dirname(__FILE__) + '/config' 3 | 4 | class TestEncoding < Minitest::Test 5 | def setup 6 | @conn = get_oci8_connection 7 | end 8 | 9 | def test_select 10 | drop_table('test_table') 11 | @conn.exec(<, OCI8::OracleVersion.new('7.1.6.2.3')) 45 | assert_operator(oraver, :>, OCI8::OracleVersion.new('8.0.6.2.3')) 46 | assert_operator(oraver, :>, OCI8::OracleVersion.new('8.1.5.2.3')) 47 | assert_operator(oraver, :>, OCI8::OracleVersion.new('8.1.6.1.3')) 48 | assert_operator(oraver, :>, OCI8::OracleVersion.new('8.1.6.2.2')) 49 | end 50 | 51 | def test_to_s 52 | oraver = OCI8::OracleVersion.new('8.1.6.2.3') 53 | assert_equal('8.1.6.2.3', oraver.to_s) 54 | end 55 | 56 | def test_to_i 57 | oraver = OCI8::OracleVersion.new('8.1.6.2.3') 58 | assert_equal(0x08106203, oraver.to_i) 59 | end 60 | 61 | def test_eql 62 | oraver = OCI8::OracleVersion.new('8.1.6.2.3') 63 | assert_equal(true, oraver.eql?(OCI8::OracleVersion.new('8.1.6.2.3'))) 64 | assert_equal(false, oraver.eql?(OCI8::OracleVersion.new('8.2.6.2.3'))) 65 | assert_equal(false, oraver.eql?('8.1.6.2.3')) 66 | end 67 | end 68 | 69 | -------------------------------------------------------------------------------- /test/test_properties.rb: -------------------------------------------------------------------------------- 1 | require 'oci8' 2 | require File.dirname(__FILE__) + '/config' 3 | 4 | class TestProperties < Minitest::Test 5 | def test_tcp_keepalive_time 6 | begin 7 | oldval = OCI8.properties[:tcp_keepalive_time] 8 | begin 9 | OCI8.properties[:tcp_keepalive_time] = 600 10 | assert(true) 11 | ensure 12 | OCI8.properties[:tcp_keepalive_time] = oldval 13 | end 14 | rescue NotImplementedError 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/test_rowid.rb: -------------------------------------------------------------------------------- 1 | require 'oci8' 2 | require File.dirname(__FILE__) + '/config' 3 | 4 | class TestRowid < Minitest::Test 5 | 6 | def setup 7 | @conn = get_oci8_connection 8 | end 9 | 10 | def test_rowid 11 | drop_table('test_table') 12 | sql = <<-EOS 13 | CREATE TABLE test_table (N NUMBER(38)) 14 | EOS 15 | @conn.exec(sql) 16 | cursor = @conn.parse("INSERT INTO test_table values(1)"); 17 | cursor.exec 18 | rid1 = cursor.rowid 19 | assert_instance_of(String, rid1) 20 | cursor.close 21 | rid2 = nil 22 | @conn.exec('select rowid from test_table where rowid = :1', rid1) do |row| 23 | rid2 = row[0] 24 | end 25 | assert_equal(rid2, rid1) 26 | drop_table('test_table') 27 | end 28 | 29 | def teardown 30 | @conn.logoff 31 | end 32 | end 33 | --------------------------------------------------------------------------------