注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:



Oracle的学习心得和知识总结(三十一)| ODBC开放式数据库连接概述及应用程序开发-LMLPHP


文章快速说明索引

学习目标:

目的:接下来这段时间我想做一些兼容Oracle数据库Real Application Testing (即:RAT)上的一些功能开发,本专栏这里主要是学习以及介绍Oracle数据库功能的使用场景、原理说明和注意事项等,基于PostgreSQL数据库的功能开发等之后 由新博客进行介绍和分享!


学习内容:(详见目录)

1、ODBC开放式数据库连接概述


学习时间:

2023年12月17日 14:07:20


学习产出:

1、ODBC开放式数据库连接概述
2、CSDN 技术博客 1篇


注:下面我们所有的学习环境是Centos7+PostgreSQL16.1+Oracle19c+MySQL5.7

postgres=# select version();
                                                 version                                                 
---------------------------------------------------------------------------------------------------------
 PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-15), 64-bit
(1 row)

postgres=#

#-----------------------------------------------------------------------------#

SQL> select * from v$version; 

BANNER									    BANNER_FULL 								BANNER_LEGACY									CON_ID
--------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- ----------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production	    Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production	Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production		     0
									    Version 19.3.0.0.0


SQL>
#-----------------------------------------------------------------------------#

mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.19    |
+-----------+
1 row in set (0.06 sec)

mysql>

开放式数据库连接

开放式数据库连接 (ODBC) 是一种广泛接受的应用程序编程接口 (API),适用于数据库访问。其基于 Open Group 和 ISO/IEC 的数据库 API 调用级别接口 (CLI) 规范,并使用结构化查询语言 (SQL) 作为其数据库访问语言。

ODBC 旨在实现最大的互操作性,即单个应用程序能够使用相同的源代码访问不同的数据库管理系统 (DBMS)。数据库应用程序会在 ODBC 接口中调用函数,这些函数在特定于数据库的模块(称为“驱动程序”)中实现。驱动程序的使用将应用程序与特定于数据库的调用隔离,就像打印机驱动程序将字处理程序与特定于打印机的命令隔离一样。由于驱动程序在运行时加载,因此用户需要添加新驱动程序才能访问新的 DBMS;无需重新编译或重新链接应用程序。

ODBC 是为客户应用程序访问关系数据库时提供的一个标准的接口,对于不同的数据库,ODBC 提供了统一的 API,使应用程序调用提供的 API 来访问任何提供了 ODBC 驱动程序的数据库:

  • 应用程序(Application,即对应下图的客户程序):应用程序本身不直接与数据库打交道,主要负责处理并调用ODBC函数,发送对数据库的SQL请求及获取结果
  • 驱动程序管理器(Driver Manager,即对应下图的 ODBC 驱动管理程序):驱动程序管理器是一个带有输入程序的动态链接库(DLL),主要目的是加载驱动程序,处理ODBC调用的初始化调用,提供ODBC调用的参数有效性和序列有效性
  • 驱动程序(Driver,即对应下图的 ODBC 驱动程序):驱动程序是一个完成ODBC函数调用并与数据库相互影响的DLL,这些驱动程序可以处理对于特定的数据的数据库访问请求。对于应用驱动程序管理器送来的命令,驱动程序再进行解释形成自己的数据库所能理解的命令。驱动程序将处理所有的数据库访问请求,对于应用程序来讲不需要关注所使用的是本地数据库还是网络数据库

Oracle的学习心得和知识总结(三十一)| ODBC开放式数据库连接概述及应用程序开发-LMLPHP


总结一下,ODBC 体系结构有四个主要组成部分:

  1. 应用程序:执行处理并调用 ODBC 函数来提交 SQL 语句并检索结果
  2. 驱动程序管理器:代表应用程序加载和卸载驱动程序。 处理 ODBC 函数调用或将其传递给驱动程序
  3. 驱动程序:处理 ODBC 函数调用,将 SQL 请求提交到特定数据源,并将结果返回到应用程序。 如有必要,驱动程序会修改应用程序的请求,以便该请求符合关联的 DBMS 支持的语法
  4. 数据源:由用户想要访问的数据及其关联的操作系统、DBMS 和用于访问 DBMS 的网络平台(如果有)组成

如何开发应用程序

一个ODBC应用程序的编写,通常步骤如下所示:

Oracle的学习心得和知识总结(三十一)| ODBC开放式数据库连接概述及应用程序开发-LMLPHP

任何应用程序都不太可能完全按此顺序调用所有这些函数。但是,大多数应用程序使用这些步骤的一些变体。下图显示了基本的应用程序步骤:

Oracle的学习心得和知识总结(三十一)| ODBC开放式数据库连接概述及应用程序开发-LMLPHP

简单解释一下,例如:

  1. 步骤 1:连接数据源
  2. 步骤 2:初始化应用程序
  3. 步骤 3:生成并执行 SQL 语句
  4. 步骤 4a:提取结果;步骤 4b:提取行计数
  5. 步骤 5:提交事务
  6. 步骤 6:从数据源断开连接

建立 ODBC DSN

DSN(Data Source Name)是用于指定ODBC与相关的驱动程序相对应的一个入口,所有DSN的信息由系统进行管理。一般来讲当应用程序要使用ODBC访问数据库时,就需要指定一个DSN以便于连接到一个指定的ODBC驱动程序。


下面列出了 ODBC Driver for SQL Server 中提供的连接字符串和 DSN 的关键字以及 SQLSetConnectAttr 和 SQLGetConnectAttr 的连接属性:


我们这里以连接oracle数据库为例,编写一个odbc应用测试程序。作为服务端的Oracle数据库 机器1如下:

[oracle@dbserver ~]$ ifconfig 
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.14  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 2409:8a71:abc:ed70:250:56ff:fe2b:6b00  prefixlen 64  scopeid 0x0<global>
        inet6 fe80::250:56ff:fe2b:6b00  prefixlen 64  scopeid 0x20<link>
        ether 00:50:56:2b:6b:00  txqueuelen 1000  (Ethernet)
        RX packets 3711  bytes 2060244 (1.9 MiB)
        RX errors 0  dropped 5  overruns 0  frame 0
        TX packets 1374  bytes 168460 (164.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 415  bytes 64588 (63.0 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 415  bytes 64588 (63.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

virbr0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.168.122.1  netmask 255.255.255.0  broadcast 192.168.122.255
        ether 52:54:00:3b:26:0a  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[oracle@dbserver ~]$
[oracle@dbserver ~]$ sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Tue Dec 19 09:57:49 2023
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.

Connected to an idle instance.

SQL> startup;
ORACLE instance started.

Total System Global Area 1157624440 bytes
Fixed Size		    9134712 bytes
Variable Size		  352321536 bytes
Database Buffers	  788529152 bytes
Redo Buffers		    7639040 bytes
Database mounted.
Database opened.
SQL> 
SQL> select * from v$version;

BANNER									    BANNER_FULL 								BANNER_LEGACY	  CON_ID
--------------------------------------------------------------------------- --------------------------------------------------------------------------- --------------------------------------------------------------------------- ----------
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production	    Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production	Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production		     0
									    Version 19.3.0.0.0


SQL>

机器2(CentOS8)配置ODBC连接oracle,步骤如下:

一、安装oracle客户端及oracle ODBC驱动程序(这里一定要注意别下错了,32位还是64位)

// 下载oracle客户端包:

https://www.oracle.com/cn/database/technologies/instant-client/linux-x86-64-downloads.html
https://download.oracle.com/otn_software/linux/instantclient/1921000/oracle-instantclient19.21-basic-19.21.0.0.0-1.x86_64.rpm

https://download.oracle.com/otn_software/linux/instantclient/1921000/oracle-instantclient19.21-sqlplus-19.21.0.0.0-1.x86_64.rpm

https://download.oracle.com/otn_software/linux/instantclient/1921000/oracle-instantclient19.21-tools-19.21.0.0.0-1.x86_64.rpm

https://download.oracle.com/otn_software/linux/instantclient/1921000/oracle-instantclient19.21-devel-19.21.0.0.0-1.x86_64.rpm

https://download.oracle.com/otn_software/linux/instantclient/1921000/oracle-instantclient19.21-odbc-19.21.0.0.0-1.x86_64.rpm

提前安装相关依赖,如下:

[postgres@localhost:~/odbcloc]$ sudo yum install libaio*
...
[postgres@localhost:~/odbcloc]$ sudo yum install libnsl*
...
[postgres@localhost:~/odbcloc]$ sudo yum install libaio.so.1* -y
...
[postgres@localhost:~/odbcloc]$ sudo yum install libnsl.so.1* -y
...
[root@localhost odbc]# ll
total 54924
-rw-rw-r--. 1 postgres postgres 53832016 Dec 19 00:17 oracle-instantclient19.21-basic-19.21.0.0.0-1.x86_64.rpm
-rw-rw-r--. 1 postgres postgres   614204 Dec 19 00:17 oracle-instantclient19.21-devel-19.21.0.0.0-1.x86_64.rpm
-rw-rw-r--. 1 postgres postgres   246728 Dec 19 00:17 oracle-instantclient19.21-odbc-19.21.0.0.0-1.x86_64.rpm
-rw-rw-r--. 1 postgres postgres   703388 Dec 19 00:17 oracle-instantclient19.21-sqlplus-19.21.0.0.0-1.x86_64.rpm
-rw-rw-r--. 1 postgres postgres   837556 Dec 19 00:17 oracle-instantclient19.21-tools-19.21.0.0.0-1.x86_64.rpm
[root@localhost odbc]#
[root@localhost odbc]# rpm -ivh oracle-instantclient19.21-basic-19.21.0.0.0-1.x86_64.rpm 
Verifying...                          ################################# [100%]
Preparing...                          ################################# [100%]
Updating / installing...
   1:oracle-instantclient19.21-basic-1################################# [100%]
[root@localhost odbc]# 
[root@localhost odbc]# rpm -ivh oracle-instantclient19.21-sqlplus-19.21.0.0.0-1.x86_64.rpm 
Verifying...                          ################################# [100%]
Preparing...                          ################################# [100%]
Updating / installing...
   1:oracle-instantclient19.21-sqlplus################################# [100%]
[root@localhost odbc]# 
[root@localhost odbc]# rpm -ivh oracle-instantclient19.21-devel-19.21.0.0.0-1.x86_64.rpm 
Verifying...                          ################################# [100%]
Preparing...                          ################################# [100%]
Updating / installing...
   1:oracle-instantclient19.21-devel-1################################# [100%]
[root@localhost odbc]# 
[root@localhost odbc]# rpm -ivh oracle-instantclient19.21-tools-19.21.0.0.0-1.x86_64.rpm 
Verifying...                          ################################# [100%]
Preparing...                          ################################# [100%]
Updating / installing...
   1:oracle-instantclient19.21-tools-1################################# [100%]
[root@localhost odbc]# 
[root@localhost odbc]# rpm -ivh oracle-instantclient19.21-odbc-19.21.0.0.0-1.x86_64.rpm 
Verifying...                          ################################# [100%]
Preparing...                          ################################# [100%]
Updating / installing...
   1:oracle-instantclient19.21-odbc-19################################# [100%]
[root@localhost odbc]#

执行如下命令查看rpm安装后,生成文件及安装路径:

[root@localhost odbc]# rpm -qpl oracle-instantclient19.21-odbc-19.21.0.0.0-1.x86_64.rpm 
/usr/lib/oracle
/usr/lib/oracle/19.21
/usr/lib/oracle/19.21/client64
/usr/lib/oracle/19.21/client64/bin
/usr/lib/oracle/19.21/client64/bin/odbc_update_ini.sh
/usr/lib/oracle/19.21/client64/lib
/usr/lib/oracle/19.21/client64/lib/libsqora.so.19.1
/usr/share/oracle
/usr/share/oracle/19.21
/usr/share/oracle/19.21/client64
/usr/share/oracle/19.21/client64/doc
/usr/share/oracle/19.21/client64/doc/ODBC_LICENSE
/usr/share/oracle/19.21/client64/doc/ODBC_README
[root@localhost odbc]# 

配置环境变量,如下:

[root@localhost odbcloc]# vim /etc/profile
...
#根据实际安装路径配置
 export ORACLE_HOME=/usr/lib/oracle/19.21/client64
 export TNS_ADMIN=/usr/lib/oracle/19.21/client64/network/admin
 export LD_LIBRARY_PATH=/usr/lib/oracle/19.21/client64/lib
 export ORACLE_SID=orcl
 export PATH=$ORACLE_HOME:$PATH
 export PATH=$PATH:$HOME/bin:$ORACLE_HOME/bin

source /etc/profile 使其生效

配置监听配置文件,如下:

[postgres@localhost:~/odbc]$ sudo mkdir -p /usr/lib/oracle/19.21/client64/network/admin
[sudo] password for postgres: 
[postgres@localhost:~/odbc]$ 
[postgres@localhost:~/odbc]$ cd /usr/lib/oracle/19.21/client64/network/admin/
[postgres@localhost:/usr/lib/oracle/19.21/client64/network/admin]$ 
[postgres@localhost:/usr/lib/oracle/19.21/client64/network/admin]$ vim tnsnames.ora 
[postgres@localhost:/usr/lib/oracle/19.21/client64/network/admin]$ 
[postgres@localhost:/usr/lib/oracle/19.21/client64/network/admin]$ sudo vim tnsnames.ora 
[postgres@localhost:/usr/lib/oracle/19.21/client64/network/admin]$ 
[postgres@localhost:/usr/lib/oracle/19.21/client64/network/admin]$ cat tnsnames.ora 
ORCL =
(DESCRIPTION =
  (ADDRESS_LIST =
     (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.14)(PORT = 1521))
  )
  (CONNECT_DATA =
    (SERVICE_NAME = ORCL)
  )
)

[postgres@localhost:/usr/lib/oracle/19.21/client64/network/admin]$

二、安装 unixODBC 驱动程序管理器,如下:

[root@localhost admin]# yum install unixODBC unixODBC-devel -y
...

若是选择源码编译安装,则获取 unixODBC-2.3.1.tar.gz。 可以从 http://www.unixodbc.org 获取 unixODBC-2.3.1.tar.gz放到/usr/local下,然后运行下述命令:

注:如果自定义其他安装目录,需要配置环境变量,否则使用时可能会出现找不到库的问题

tar zxvf unixODBC-2.3.1.tar.gz
cd unixODBC-2.3.1 
./configure --prefix=/usr/local/unixODBC-2.3.1 --includedir=/usr/include 
--libdir=/usr/lib -bindir=/usr/bin --sysconfdir=/etc
make
make install

三、安装之后运行odbcinst -j,如下:

[postgres@localhost:~]$ odbcinst -j
unixODBC 2.3.7
DRIVERS............: /etc/odbcinst.ini
SYSTEM DATA SOURCES: /etc/odbc.ini
FILE DATA SOURCES..: /etc/ODBCDataSources
USER DATA SOURCES..: /home/postgres/.odbc.ini
SQLULEN Size.......: 8
SQLLEN Size........: 8
SQLSETPOSIROW Size.: 8
[postgres@localhost:~]$

根据odbcinst -j 显示的路径,配置 odbcinst.iniodbc.ini 文件

[postgres@localhost:~]$ sudo vim /etc/odbcinst.ini
[postgres@localhost:~]$ 
[postgres@localhost:~]$ cat /etc/odbcinst.ini
# Example driver definitions

# Driver from the postgresql-odbc package
# Setup from the unixODBC package
[PostgreSQL]
Description	= ODBC for PostgreSQL
Driver		= /usr/lib/psqlodbcw.so
Setup		= /usr/lib/libodbcpsqlS.so
Driver64	= /usr/lib64/psqlodbcw.so
Setup64		= /usr/lib64/libodbcpsqlS.so
FileUsage	= 1

[ORACLE]
Description = ODBC for Oracle
Driver      = /usr/lib/oracle/19.21/client64/lib/libsqora.so.19.1
FileUsage   = 1

[postgres@localhost:~]$

[postgres@localhost:~]$ ls /usr/lib/oracle/19.21/client64/lib/libsqora.so.19.1
/usr/lib/oracle/19.21/client64/lib/libsqora.so.19.1
[postgres@localhost:~]$ 
[postgres@localhost:~]$ ldd /usr/lib/oracle/19.21/client64/lib/libsqora.so.19.1
	linux-vdso.so.1 (0x00007ffc2750b000)
	libdl.so.2 => /lib64/libdl.so.2 (0x00007f0dec769000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f0dec3e7000)
	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f0dec1c7000)
	libnsl.so.1 => /lib64/libnsl.so.1 (0x00007f0debfae000)
	librt.so.1 => /lib64/librt.so.1 (0x00007f0debda6000)
	libaio.so.1 => /lib64/libaio.so.1 (0x00007f0debba3000)
	libresolv.so.2 => /lib64/libresolv.so.2 (0x00007f0deb98b000)
	libclntsh.so.19.1 => /usr/lib/oracle/19.21/client64/lib/libclntsh.so.19.1 (0x00007f0de777c000)
	libclntshcore.so.19.1 => /usr/lib/oracle/19.21/client64/lib/libclntshcore.so.19.1 (0x00007f0de71d7000)
	libodbcinst.so.2 => /lib64/libodbcinst.so.2 (0x00007f0de6fc1000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f0de6bfc000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f0decc27000)
	libnnz19.so => /usr/lib/oracle/19.21/client64/lib/libnnz19.so (0x00007f0de6583000)
	libltdl.so.7 => /lib64/libltdl.so.7 (0x00007f0de6379000)
[postgres@localhost:~]$

在机器2上先行验证一下,如下:

[postgres@localhost:~]$ sqlplus c##oracle/123456@192.168.1.14:1521/orcl

SQL*Plus: Release 19.0.0.0.0 - Production on Mon Dec 18 18:54:13 2023
Version 19.20.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.

Last Successful login time: Mon Dec 18 2023 18:53:33 -08:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> show user;         
USER is "C##ORACLE"
SQL> 
SQL> Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0
[postgres@localhost:~]$

[postgres@localhost:~]$ sudo vim /etc/odbc.ini
[postgres@localhost:~]$ 
[postgres@localhost:~]$ cat /etc/odbc.ini
[pg]
Description = Test to pg
Driver = PostgreSQL
Database = postgres
Servername = 127.0.0.1
UserName = postgres
Password = 1
Port = 5432
ReadOnly = 0
ConnSettings = set client_encoding to UTF8

[oracle_test]
driver = ORACLE
server = 192.168.1.14
port = 1521
ServerName = orcl
UserID = c##oracle
password = 123456

[postgres@localhost:~]$

连接测试

[postgres@localhost:~]$ isql -v oracle_test
+---------------------------------------+
| Connected!                            |
|                                       |
| sql-statement                         |
| help [tablename]                      |
| quit                                  |
|                                       |
+---------------------------------------+
SQL> create table t_a(id int, name varchar(64));
SQLRowCount returns -1
SQL> insert into t_a values(1, 'a');
SQLRowCount returns 1
SQL> insert into t_a values(2, 'b');
SQLRowCount returns 1
SQL> insert into t_a values(3, 'c');
SQLRowCount returns 1
SQL> insert into t_a values(4, 'd');
SQLRowCount returns 1
SQL> insert into t_a values(5, 'jj');
SQLRowCount returns 1
SQL> select * from t_a;
+-----------------------------------------+-----------------------------------------------------------------+
| ID                                      | NAME                                                            |
+-----------------------------------------+-----------------------------------------------------------------+
| 1                                       | a                                                               |
| 2                                       | b                                                               |
| 3                                       | c                                                               |
| 4                                       | d                                                               |
| 5                                       | jj                                                              |
+-----------------------------------------+-----------------------------------------------------------------+
SQLRowCount returns -1
5 rows fetched
SQL>

编写 ODBC 程序

#include <stdio.h>
#include <sqlext.h>

int main() {
    SQLHENV env; // ODBC环境句柄
    SQLHDBC dbc; // ODBC连接句柄
    SQLRETURN ret;

    // Step 1: 初始化环境
    ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
    ret = SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, SQL_IS_INTEGER);

    // Step 2: 分配连接句柄
    ret = SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc);

    // Step 3: 建立连接
    ret = SQLDriverConnect(dbc, NULL,
                           (SQLCHAR*)"DRIVER={Oracle};SERVER=<server>;DATABASE=<database>;UID=<username>;PWD=<password>;",
                           SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);

    if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
        printf("Connected to Oracle.\n");

        // Step 4: 执行SQL查询
        SQLHSTMT stmt; // SQL语句句柄
        ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt);

        ret = SQLExecDirect(stmt, (SQLCHAR*)"SELECT * FROM my_table", SQL_NTS);

        if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) {
            // 处理结果集
            // ...
            printf("SQL executed successfully.\n");
        } else {
            // 处理错误
            // ...
            printf("SQL execution failed.\n");
        }

        // Step 5: 释放语句句柄
        ret = SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    } else {
        // 处理连接失败
        // ...
        printf("Failed to connect to Oracle.\n");
    }

    // Step 6: 断开连接
    ret = SQLDisconnect(dbc);

    // Step 7: 释放连接句柄
    ret = SQLFreeHandle(SQL_HANDLE_DBC, dbc);

    // Step 8: 释放环境句柄
    ret = SQLFreeHandle(SQL_HANDLE_ENV, env);

    return 0;
}

要编译以上的代码,首先需要确保已经安装了ODBC驱动和相关的开发库。接下来,可以使用gcc或其他支持C编译的工具进行编译。例如,使用gcc命令编译该代码:

gcc -o odbc_demo odbc_demo.c -lodbc
gcc -o odbc_demo odbc_demo.c -L/odbc库的路径 -lodbc
#可通过下面的命令查找odbc库的路径
sudo find / -name "libodbc*"

其中,odbc_demo为输出的可执行文件名,odbc_demo.c为上述代码保存的文件名。-lodbc用于链接ODBC库。请注意,编译时可能需要配置包含路径和连接选项,具体取决于所使用的操作系统和ODBC驱动程序。


下面是一个示例,源码odbctest.c如下:

#include <stdio.h>

#include <sql.h>
#include <sqlext.h>

static void
test_SQLConnect()
{
	SQLRETURN ret;
    SQLHENV env;
    SQLHDBC conn;
    HSTMT hstmt = SQL_NULL_HSTMT;
    SQLSMALLINT sdwNative;
	SQLINTEGER swMsgLen;
	char buffer[256];
	char message[1000];

	SQLCHAR *dsn = (SQLCHAR *) "oracle_test";
    //CHAR* queryStr="create table t_odbc(id int);";

	SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);

	SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);

	SQLAllocHandle(SQL_HANDLE_DBC, env, &conn);

	printf("Connecting with SQLConnect...\n");

	ret = SQLConnect(conn, dsn, SQL_NTS, NULL, 0, NULL, 0);
	if (SQL_SUCCEEDED(ret)) {
		printf("connected success!\n");
	} else {
		printf("SQLConnect failed.\n");
		return;
	}

    ret = SQLSetConnectAttr(conn,
						   SQL_ATTR_AUTOCOMMIT,
						   (SQLPOINTER)SQL_AUTOCOMMIT_OFF,
						   SQL_IS_UINTEGER);
	if (SQL_SUCCEEDED(ret)) {
		printf("set autommit off success!\n");
	} else {
		printf("set autommit failed.\n");
		return;
	}

    ret = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);

    ret = SQLExecDirect(hstmt, (SQLCHAR *)"create table t_odbc(id int)", SQL_NTS);
    if (SQL_SUCCEEDED(ret)) {
		printf("create table success!\n");
	}
    else {
		printf("create table failed.\n");
		return;
	}

    ret = SQLExecDirect(hstmt, (SQLCHAR *) "INSERT INTO t_odbc VALUES (10000)", SQL_NTS);
	if (SQL_SUCCEEDED(ret)) {
		printf("insert table success!\n");
	}
    else {
		printf("insert table failed.\n");
		return;
	}

    SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);
    if (SQL_SUCCEEDED(ret)) {
		printf("SQLEndTran success!\n");
	}
    else {
		printf("SQLEndTran failed.\n");
		return;
	}

    ret = SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
    if (SQL_SUCCEEDED(ret)) {
		printf("SQLFreeHandle success!\n");
	}
    else {
		printf("SQLFreeHandle failed.\n");
		return;
	}

    ret = SQLDisconnect(conn);
	if (SQL_SUCCEEDED(ret)) {
		printf("SQLDisconnect success!\n");
	}
    else {
        printf("SQLDisconnect failed.\n");
        SQLError(env, conn, hstmt, buffer, &swMsgLen,
		message, 256, &sdwNative);
		printf("%s\n%s\n", buffer, message);
		return;
	}

	ret = SQLFreeHandle(SQL_HANDLE_DBC, conn);
	if (!SQL_SUCCEEDED(ret))
	{
		printf("SQLFreeHandle failed\n");
		return;
	}
	conn = NULL;

	ret = SQLFreeHandle(SQL_HANDLE_ENV, env);
	if (!SQL_SUCCEEDED(ret))
	{
		printf("SQLFreeHandle failed\n");
		return;
	}
	env = NULL;
}


int main(int argc, char **argv)
{
	test_SQLConnect();
	return 0;
}

通过以下命令进行编译,第一条命令用于将UnixODBC驱动库安装在系统路径下,第二条命令可用于源码编译安装时,将UnixODBC安装在自定义路径下,可找到对应的odbc动态库路径,通过-L指定库的路径,-I指定所需头文件的路径等等

gcc -o odbctest odbctest.c -lodbc -g -O0
gcc -o odbctest odbctest.c -L/usr/lib64/ -lodbc

执行可执行程序

[postgres@localhost:~/odbc]$ vim odbctest.c
[postgres@localhost:~/odbc]$ 
[postgres@localhost:~/odbc]$ gcc -o odbctest odbctest.c -lodbc -g -O0
[postgres@localhost:~/odbc]$ 
[postgres@localhost:~/odbc]$ ls
odbctest                                                  oracle-instantclient19.21-odbc-19.21.0.0.0-1.x86_64.rpm
odbctest.c                                                oracle-instantclient19.21-sqlplus-19.21.0.0.0-1.x86_64.rpm
oracle-instantclient19.21-basic-19.21.0.0.0-1.x86_64.rpm  oracle-instantclient19.21-tools-19.21.0.0.0-1.x86_64.rpm
oracle-instantclient19.21-devel-19.21.0.0.0-1.x86_64.rpm
[postgres@localhost:~/odbc]$ 
[postgres@localhost:~/odbc]$ ./odbctest 
Connecting with SQLConnect...
connected success!
set autommit off success!
create table success!
insert table success!
SQLEndTran success!
SQLFreeHandle success!
SQLDisconnect success!
[postgres@localhost:~/odbc]$

编写 OCI 程序

oci应用程序的编写步骤和odbc流程一致,只是调用的接口不同,示例 如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "oci.h" //引用OCI接口的头文件,因此编译时需要指定该头文件的路径
 
/*user name and password*/
static text* username=(text *)"c##oracle";
static text* password=(text *)"123456";
static text* oracle=(text *)"192.168.1.14/orcl";
 
/* Define SQL statements to be used in program. */
static text* SQL=(text *)"insert into t_oci(id1, id2) values (:1, :2)";
 
/*handle define*/
static OCIEnv             *p_env;                                        //OCI environment handle
static OCIError         *p_err;                                        //OCI error handle
static OCISvcCtx        *p_svc;                                        //OCI service context handel
static OCIServer        *p_ser;                                        //OCI server handle
static OCISession        *p_usr;                                        //OCI user session handle
static OCIStmt            *p_sql;                                        //OCI statement handle
static OCIDefine        *p_dfn = (OCIDefine *)NULL;            //OCI define handle
static OCIBind            *p_bnd = (OCIBind *)NULL;                //OCI bind handle
 
/*create OCI environment*/
int create_env()
{
    int swResult;            //Return value
    if(swResult = OCIEnvCreate(&p_env,OCI_DEFAULT,NULL,NULL,NULL,NULL,0,NULL))
	{
		printf("environment create error!\n\n");
		return -1;
	}
    else
	{
		printf("environment create success!\n\n");
		return 0;
	}
}
 
/*init handle*/
int init_handle()
{
    int swResult;
    if(swResult = OCIHandleAlloc(p_env,(dvoid *)&p_ser,OCI_HTYPE_SERVER,0,NULL))    //服务器句柄
	{
		printf("init server handle error!\n\n");
		return -1;
	}
    else
	{
		printf("init server handle success!\n\n");
	}
 
    if(swResult = OCIHandleAlloc(p_env,(dvoid *)&p_err,OCI_HTYPE_ERROR,0,NULL))    //错误句柄
	{
		printf("init error handle error!\n\n");
		return -1;
	}
    else
	{
		printf("init error handle success!\n\n");
	}
    
	if(swResult = OCIHandleAlloc(p_env,(dvoid *)&p_usr,OCI_HTYPE_SESSION,0,NULL))    //事务句柄
	{
		printf("init session handle error!\n\n");
		return -1;
	}
    else
	{
		printf("init session handle success!\n\n");
	}
    
	if(swResult = OCIHandleAlloc(p_env,(dvoid *)&p_svc,OCI_HTYPE_SVCCTX,0,NULL))    //上下文句柄
	{
		printf("init service context handle error!\n\n");
		return -1;
	}
    else
	{
		printf("init service context handel success!\n\n");
	}
    
	if(swResult = OCIHandleAlloc(p_env,(dvoid *)&p_sql,OCI_HTYPE_STMT,0,NULL))        //SQL语句句柄
	{
		printf("init statement handle error!\n\n");
		return -1;
	}
    else
	{
		printf("init statement handle success!\n\n");
	}
    return 0;
}
 
/*connect server*/
int conn_server()
{
    int swResult;
    if(swResult = OCILogon(p_env,p_err,&p_svc,(text *)username,strlen(username),(text *)password,strlen(password),(text *)oracle,strlen(oracle)))
	{
		printf("connect error!\n\n");
		return -1;
	}
    else
        printf("connect success!\n\n");
    return 0;
}
 
/*SQL statements*/
int oci_exec()
{
    int swResult;
	sb4 errcodep;
	ub4 recordno = 1;
	OraText bufp[1024];
	sword retcode = 0;

    //准备SQL语句
    if(swResult = OCIStmtPrepare(p_sql,p_err,SQL,strlen(SQL),OCI_NTV_SYNTAX,OCI_DEFAULT))
	{
		printf("prepare SQL statements error!\n\n");
	}
    else
	{
		printf("prepare SQL statements success!\n\n");
	}
 
    //设置绑定变量
    int getId1 ;
	int getId2;
    //char getName[10];
 
    OCIBind     *p_bndp1 = NULL;
    OCIBind     *p_bndp2 = NULL;
 
    printf("输入ID1,ID2:\n");
    scanf("%d %d",&getId1,&getId2);
 
    if(swResult = OCIBindByPos(p_sql,&p_bndp1,p_err,1,(dvoid *)&getId1,(sb4)sizeof(getId1),SQLT_INT,NULL,NULL,NULL,0,NULL,OCI_DEFAULT))
	{
		printf("Bind p1 error!\n\n");
		return -1;
	}
    else
	{
		printf("bind success!\n\n");
	}

	if(swResult = OCIBindByPos(p_sql,&p_bndp2,p_err,2,(dvoid *)&getId2,(sb4)sizeof(getId2),SQLT_INT,NULL,NULL,NULL,0,NULL,OCI_DEFAULT))
	{
		printf("Bind p1 error!\n\n");
		return -1;
	}
    else
	{
		printf("bind success!\n\n");
	}

	/*
    if(swResult = OCIBindByPos(p_sql,&p_bndp2,p_err,2,&getName,(sb4)sizeof(getName),SQLT_STR, (dvoid *) 0, (ub2 *) 0, (ub2 *) 0, (ub4) 0, (ub4 *) 0, OCI_DEFAULT))
	{
		printf("Bind p2 error!\n\n");
		return -1;
	}
    else
	{
		printf("bind success!\n\n");
	}*/
 
 
    //执行SQL statements
    if(swResult = OCIStmtExecute(p_svc,p_sql,p_err,1,0,NULL,NULL,OCI_DEFAULT))
	{
		//printf("execute SQL statement error!\n\n");
		if (OCIErrorGet(p_err, recordno++, NULL, &errcodep, (OraText *)bufp, sizeof(bufp), OCI_HTYPE_ERROR) == OCI_SUCCESS)
			printf("error msg:%s\n", bufp);
		return -1;
	}
    else
	{
		printf("execute SQL statement success!\n\n");
	}
    return 0;
}
 
 
/*quit server*/
void quit_server()
{
    OCILogoff(p_svc,p_err);
    printf("Quit success!\n");
}
 
/*free handle*/
void free_handle()
{
    OCIHandleFree(p_ser,OCI_HTYPE_SERVER);            //释放服务器句柄
    OCIHandleFree(p_err,OCI_HTYPE_ERROR);            //释放错误句柄
    OCIHandleFree(p_usr,OCI_HTYPE_SESSION);        //释放事务句柄
    OCIHandleFree(p_svc,OCI_HTYPE_SVCCTX);            //释放上下文句柄
    OCIHandleFree(p_sql,OCI_HTYPE_STMT);            //释放SQL语句句柄
}
 
int main()
{
    if(create_env() == -1)                //创建环境
        return -1;
    if(init_handle() == -1)                //初始化句柄
        return -1;
    if(conn_server() == -1)                //连接数据库
        return -1;
    if(oci_exec() == -1)
        return -1;
	/*
    quit_server();                            //退出数据库
    free_handle();                            //释放句柄 */
    return 0;
}

如上示例中可以看到调用OCI相关接口需要引用oci.h这个头文件,那么 这个头文件在哪?该头文件是在安装完oracle客户端之后,对应oracle-instantclient19.21-devel-19.21.0.0.0-1.x86_64.rpm软件包,可以通过如下命令查看其安装之后的路径,如下:

[postgres@localhost:~/odbc]$ rpm -qpl oracle-instantclient19.21-devel-19.21.0.0.0-1.x86_64.rpm
/usr/include/oracle
/usr/include/oracle/19.21
/usr/include/oracle/19.21/client64
/usr/include/oracle/19.21/client64/ldap.h
/usr/include/oracle/19.21/client64/nzerror.h
/usr/include/oracle/19.21/client64/nzt.h
/usr/include/oracle/19.21/client64/occi.h
/usr/include/oracle/19.21/client64/occiAQ.h
/usr/include/oracle/19.21/client64/occiCommon.h
/usr/include/oracle/19.21/client64/occiControl.h
/usr/include/oracle/19.21/client64/occiData.h
/usr/include/oracle/19.21/client64/occiObjects.h
/usr/include/oracle/19.21/client64/oci.h
/usr/include/oracle/19.21/client64/oci1.h
/usr/include/oracle/19.21/client64/oci8dp.h
/usr/include/oracle/19.21/client64/ociap.h
/usr/include/oracle/19.21/client64/ociapr.h
/usr/include/oracle/19.21/client64/ocidef.h
/usr/include/oracle/19.21/client64/ocidem.h
/usr/include/oracle/19.21/client64/ocidfn.h
/usr/include/oracle/19.21/client64/ociextp.h
/usr/include/oracle/19.21/client64/ocikpr.h
/usr/include/oracle/19.21/client64/ociver.h
/usr/include/oracle/19.21/client64/ocixmldb.h
/usr/include/oracle/19.21/client64/ocixstream.h
/usr/include/oracle/19.21/client64/odci.h
/usr/include/oracle/19.21/client64/oratypes.h
/usr/include/oracle/19.21/client64/ori.h
/usr/include/oracle/19.21/client64/orid.h
/usr/include/oracle/19.21/client64/orl.h
/usr/include/oracle/19.21/client64/oro.h
/usr/include/oracle/19.21/client64/ort.h
/usr/include/oracle/19.21/client64/xa.h
/usr/lib/oracle
/usr/lib/oracle/19.21
/usr/lib/oracle/19.21/client64
/usr/lib/oracle/19.21/client64/lib
/usr/lib/oracle/19.21/client64/lib/ottclasses.zip
/usr/share/oracle
/usr/share/oracle/19.21
/usr/share/oracle/19.21/client64
/usr/share/oracle/19.21/client64/admin
/usr/share/oracle/19.21/client64/admin/oraaccess.xsd
/usr/share/oracle/19.21/client64/demo
/usr/share/oracle/19.21/client64/demo/cdemo81.c
/usr/share/oracle/19.21/client64/demo/demo.mk
/usr/share/oracle/19.21/client64/demo/occidemo.sql
/usr/share/oracle/19.21/client64/demo/occidemod.sql
/usr/share/oracle/19.21/client64/demo/occidml.cpp
/usr/share/oracle/19.21/client64/demo/occiobj.cpp
/usr/share/oracle/19.21/client64/demo/occiobj.typ
/usr/share/oracle/19.21/client64/demo/oraaccess.xml
/usr/share/oracle/19.21/client64/demo/ott
/usr/share/oracle/19.21/client64/demo/setuporamysql.sh
/usr/share/oracle/19.21/client64/doc
/usr/share/oracle/19.21/client64/doc/SDK_LICENSE
/usr/share/oracle/19.21/client64/doc/SDK_README
[postgres@localhost:~/odbc]$

接下来编译上述样例:

[postgres@localhost:~/odbc]$ echo $ORACLE_HOME
/usr/lib/oracle/19.21/client64
[postgres@localhost:~/odbc]$ 
[postgres@localhost:~/odbc]$ gcc -o ocitest ocitest.c -I/usr/include/oracle/19.21/client64 -L${ORACLE_HOME}/lib -lclntsh -g -O0
[postgres@localhost:~/odbc]$

执行可执行程序,如下:

[postgres@localhost:~/odbc]$ sqlplus c##oracle/123456@192.168.1.14:1521/orcl

SQL*Plus: Release 19.0.0.0.0 - Production on Tue Dec 19 01:02:19 2023
Version 19.21.0.0.0

Copyright (c) 1982, 2022, Oracle.  All rights reserved.

Last Successful login time: Tue Dec 19 2023 01:00:31 -08:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> create table t_oci(id1 int, id2 int);

Table created.

SQL> Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0
[postgres@localhost:~/odbc]$ 
[postgres@localhost:~/odbc]$ ./ocitest
environment create success!

init server handle success!

init error handle success!

init session handle success!

init service context handel success!

init statement handle success!

connect success!

prepare SQL statements success!

输入ID1,ID2:
1 2  
bind success!

bind success!

execute SQL statement success!

[postgres@localhost:~/odbc]$
12-20 09:47