Cover V14, i07

Article
Table 1

jul2005.tar

FreeTDS for Database Connectivity

Kevin M. Lyons

It has often been said that the world would be a nicer place for programmers and administrators if everyone used the same operating system, if only one database were needed, only one programming language... Let us pause for a moment while you stop laughing.

Sooner or later in your career, you will most likely need to handle communication between a Unix system and a Windows system, particularly in the world of databases. Indeed, Oracle and MySQL get quite a bit of press, but what about systems such as Sybase and Microsoft's SQL Server? There is a solution in the form of FreeTDS, the open source emulation of the Tabular Data Stream (TDS) protocol.

Our office has successfully used the FreeTDS libraries for C/C++, Perl, and PHP connectivity to MSSQL systems for Web-based applications since 1999. We discovered the project, which was begun by Brian Bruns, while doing work for the Nebraska Secretary of State's office and have continued using it in a variety of other systems including the State Electrical Division's online licensing and permitting.

FreeTDS for MSSQL/Sybase connectivity has gained popularity on a wide variety of platforms. So, in an effort to shed some light on these less-used, but still important, database systems, the following is a practical guide to the use of FreeTDS.

What Is TDS/FreeTDS?

The TDS protocol, which FreeTDS was built to emulate, is the underlying protocol used by both Sybase (the original developers) and Microsoft SQL Server for remote data transfer. For some information on the protocol itself, you can check out some basic documentation at:

http://www.freetds.org/tds.html
FreeTDS is a set of open source libraries that implement the TDS protocol and the two common APIs that sit on top of TDS. Those APIs are CT, which is only supported by Sybase, and DB, which is the preferred API for use with both Sybase and MSSQL. As FreeTDS has evolved, support for multiple languages has been added, as well as ODBC support for both iODBC and unixODBC.

Why Use FreeTDS?

The first and biggest question is whether FreeTDS is right for your project. The answer, as in most cases, is ... maybe.

Especially when dealing with applications based on C/C++, FreeTDS is a strong, well supported, and free library that allows easy connectivity to MSSQL and Sybase systems on any *nix type of platform. It's been tested across Solaris, Linux, HP, AIX, and several other platforms with few reliability issues; problems have been noted with the build process, however, especially on AIX and HP systems. Perl and PHP support are also well maintained and have no significant stability issues as of the writing of this article.

The major issues you may encounter will come from servers requiring TDS protocol 4.2 or 5.0, such as older Sybase and MSSQL systems. If you are dealing with a situation where upgrade is not an option, you may wish to consider an alternate solution, although FreeTDS can handle the job.

The primary concern is with endianness, or specifically how improper configuration on either end can cause misreported data in date, time, and money fields. These are especially widespread when dealing with Microsoft's SQL Server 7.0, which can mishandle endian issues unless Service Pack 3 or higher is installed.

Additionally through the years, there have been issues with MSSQL breaking and re-breaking how it handles endianness, so if you are going to be working in an environment where the database server is frequently upgraded (not just patched), you may be in for a bumpy ride. Fortunately, TDS protocol versions 7.0 and higher support automatic little-endian emulation, although older systems may have some serious problems.

The other main concern with 4.2 and 5.0 systems comes from varchar fields with a length of more than 255. The TDS protocol inherently limits varchars to 255 characters, so the fields, while correct on the MSSQL end, will not be correctly reported unless they are converted to text fields instead.

As a smaller, lesser problem, there is a conflict when using FreeTDS and the DBM library at the same time. Both define a dbopen() function, which can confuse things, especially in C/C++ and PHP. This can be worked around, as described later, but has caused confusion and irritation in some projects.

Starting Out with FreeTDS

If you are ready to work with the FreeTDS package, then you first need to grab the source from the FreeTDS Web site at:

http://www.freetds.org/
and use the "Software" link at the top of the page. Or, you can just head straight to:

http://www.freetds.org/software.html
This is the base C library install package on which the Perl and PHP implementations are built. A native (type 4) JDBC driver is also available courtesy of Craig Spanning and CDS Networks, however, that topic is outside the scope of this article.

The package install itself is the standard ./configure && make && make install type of scenario with the usual options. Note that if you wish to use the PHP install, run configure with the --enable-msdblib option. If you do not, you may have problems, especially with PHP systems compiled with DBM support, because both DBM and the DB layer of TDS define a dbopen() function. Once installation is complete, just recompile PHP using the --with-mssql=/path/to/freetds option and support will be enabled.

If you wish to use the Perl DBI implementation, it can be downloaded and installed with the usual perl Makefile.pl or obtained via CPAN with the module name of DBI::Sybase.

Configuration

FreeTDS uses a freetds.conf file for basic configuration options, with a fairly basic layout. You begin with setting your global configuration for all connections. The default setup for this should work, with one exception. Check for the following line under the [global] heading:

tds version = 4.2
The 4.2 protocol is rather outdated and not the preferred method for use on most systems. Table 1 provides a reference guide on which type of system supports which type of connection. It is also helpful to set up connection information about specific servers in this file. Options specified in a section for a specific server will override options in the global; thus, if you are like me and need to connect to several different types of servers, you can do it all within a single configuration file.

For example, say you wanted to connect to the database at foo.com and their new testing system at bar.com using an updated version of Sybase -- you could define these hosts in your freetds.conf as follows:

[foo]
  host = db.foo.com
  port = 1433
  tds version = 7.0

[bar]
  host = 10.0.0.1
  port = 1444
  tds version = 8.0
Then, when establishing connections, you could simply call your connect to foo or bar and have the appropriate information in place, and it could be updated as needed without code changes.

Support is also available for multiple freetds.conf files. When using the DB layer, you can use the dbsetconf() function to set a configuration file. If set, this takes priority and supersedes all other conf files. Otherwise the following steps are used (in this order) to determine which conf file to use:

1. Check for a FREETDSCONF environment variable.

2. Look for $HOME/.freetds.conf.

3. Look for /path/to/freetds/etc/freetds.conf.

For further details on this, you can find a sample configuration file, with associated documentation, showing setups to multiple types of connections with the source for this article (available from the Sys Admin Web site).

Connections

Now that the configuration is complete, we can review how to open connections to the TDS systems via applications.

In the C/C++ venue, there are two separate abstraction layers you can work with -- the CT and DB layers. ODBC connectivity is available as well, although it is outside of the scope of this article. The DB layer is the most robust and well-supported layer and is the preferred API according to the FreeTDS staff.

When connecting using the DB layer, you must first create your login record using the LOGINREC struct. A variety of macros have been provided to allow for easy setup. For example, the following code will establish a connection to the "foo" database listed in our previous example configuration:

LOGINREC *login;
DBPROCESS *dbproc;

/* insert various code here */
dbinit();

/* more code comes here */

login = dblogin();
DBSETLUSER(login, "mylogin");
DBSETLPWD(login, "mypasswd");   
dbproc = dbopen(login, "foo");

/* more code comes here */
To trace this code a little, the call to dbinit sets up the FreeTDS library accordingly, loading information from your configuration, etc. The call to dblogin() then allocates memory and initializes the login structure, while the DBSETLXXX() macros (see FreeTDS documentation for a complete listing) will establish which login parameters you want to use. Finally, the call to dbopen performs the actual login.

In Perl, you would use the normal DBI interface, like so:

use DBI;

# -- insert various code here

my $dbh = \
  DBI->connect("dbi:Sybase:server=foo;", $user, $pass);
if (!$dbh) {
                 # -- error handle here
}

# -- insert various code here
And finally, in PHP, you can use the native mssql_ functions to connect:

// insert various code here

putenv("FREETDSCONF='/path/to/freetds/conf'");

$dbh = mssql_connect("foo", $user, $pass);
if (!$dbh) {
                 // error handle here
}

// insert various code here
Note that for PHP you have the option of using mssql_pconnect for persistent connections as well. It's also advisable to note that if you want to actually use the freetds.conf file setup, you will need to put the location in the environment variable FREETDSCONF. Otherwise, it may or may not load depending on which version of PHP you are using.

Queries and Statements

When doing something like a simple select, you can use the dbcmd and dbsqlexec functions to submit the query, as follows (assume Col1 is an int, Col2 is a varchar, and Col3 is text):

       DBINT Col1;
       char  Col2[MAX_ROW2_LEN];
char  Col3[MAX_ROW3_LEN];

/* insert code here */

dbcmd(dbproc, "select Col1, Col2, Col3 from MyTable");
dbsqlexec(dbproc);

if (dbresults(dbproc) != SUCCEED) {
  /* no result set for the query, something bad happened */
} else {
                        if (dbbind(dbproc, 1, INTBIND, -1, \
                           (BYTE*)&Col1) != SUCCESS) {
                                         /* error handle here */
  }
  if (dbbind(dbproc, 2, STRINGBIND, -1, (BYTE*)Col2) != SUCCESS) {
    /* error handle here */
  }
  if (dbbind(dbproc, 3, STRINGBIND, -1, (BYTE*)Col3) != SUCCESS) {
    /* error handle here */
  }
  while (REG_ROW == dbnextrow(dbproc)) {
    printf("Col1 = %s, Col2 = %s, Col3 = %s", (int)Col1, Col2, Col3);
    fflush(stdout);
  }
       }

               /* insert code here */
In this code, the command is prepared with dbcmd (which checks for any SQL errors, etc.), then it is executed with dbsqlexec. We check whether we actually got a result set from the query with dbresults (and, yes -- "no rows" is a valid result set!).

Once all this is done, we "bind" each of the columns to their specific type, which is what the calls to dbbind handle. This means that you're interpreting data from a specific column in a specific fashion.

Finally, to loop through the results, we use dbnextrow. If that doesn't equal REG_ROW, then we don't have a row and we can exit.

In Perl, we accomplish the same thing with the following code:

# -- insert code here

my $stmt = $dbh->prepare("select Col1, Col2, Col3 from MyTable");
if (!$stmt) {
# -- print error here via $DBI::errstr
}

if (!$stmt->execute) {
# -- print error here via $DBI::errstr
}

my @row;
while (@row = $stmt->fetchrow()) {
                 foreach my $val (@row) { print "$val "; }
                 print "\n";
}

# -- insert code here
And in PHP, we accomplish the same task with the following code:

// insert code here

$res = mssql_query("select Col1, Col2, Col3 from MyTable");

for($i=0; $i < mssql_num_rows($res); $i++) {
$line = mssql_fetch_row($res);
  echo("Col1 = $line[0] Col2 = $line[1] Col3 = $line[3]");
}

// insert code here
For further coding information, complete with comments, check the example source that accompanies this article.

Troubleshooting

As with any library, there are a number of problems you can run across when using FreeTDS. Fortunately, the fine FreeTDS team has provided a number of different ways to help you resolve issues. Both the online documentation at:

http://www.freetds.org/userguide/troubleshooting.htm
and the mailing list are great tools for resolving most problems you may have.

Helpful Extras

The FreeTDS library on its own doesn't provide everything that is useful in development. However, there are associated tools that can make life working with Sybase/MSSQL systems much easier.

The first, and foremost, among these is SQSH, the SQL Shell project. Started by Scott C. Gray and now maintained by Michael Peppler (author of the DBI::Sybase Perl module), this is a command-line query tool much like MySQL's mysql client or Oracle's sqlplus. SQSH is easy to use and both stable and reliable. You can find SQSH at:

http://www.sqsh.org/
although the project is housed by SourceForge at this time.

FreeTDS also comes with Qt support in the form of SybSQL. If you have Qt 2.3.1 or higher installed and you want something more graphical than SQSH, this is a good option. Again, this project is maintained by Michael Peppler.

Source Code

Included with this article is the following source code, which can be downloaded from the Sys Admin magazine Web site (http://www.sysadminmag.com/code/):

FreeTDS/tdsexample.c -- C-based example of using FreeTDS

FreeTDS/tdsexample.p -- Perl example of using DBI::Sybase

FreeTDS/tdsexample.php -- PHP example of using mssql_XXX functions

FreeTDS/sample_freetds.conf -- Example FreeTDS configuration file

Kevin Lyons is an admin and developer for Nebraska Interactive LLC, the company behind Nebrask@ Online and a division of NIC. He graduated from Nebraska Wesleyan University with a degree in Computer Science and has been working with a wide variety of Unix flavors since then. He can be reached at: kevin@nol.org.