I get a lot of queries to my personal/CPAN email address with connection problems with DBD::ODBC. I've tried to describe the process at http://www.martin-evans.me.uk/node/70 which you may find useful.
I know some people are a little cagey these days about self promotion of web sites. If you are one of these, don't bother clicking on the link above. On the other hand I'd welcome feedback and my intention is to add this document to DBD::ODBC which would put it in the public domain (further to the Creative Commons license my personal web site uses). In addition I reproduce here (although it is possible it will exceed the limits on postings on this list). A great deal of issues arise in the dbi-users mailing list and to me personally concerning ODBC connection issues so I thought I'd describe the ODBC connection process here (with a slight Perl angle since I look after DBD::ODBC). The information here should be of general interest to anyone using ODBC. The first thing to note is that in ODBC 3 there are two connection APIs; SQLConnect (the first and older API) and SQLDriverConnect (the new ODBC 3 API). DBD::ODBC uses both but more on that later. Applications link to an ODBC Driver Manager and some of the driver manager's responsibilities are to manage the connect APIs, locate the ODBC Driver to load and pass information to the ODBC Driver to enable it to connect you to a database usually as a specific user. In addition the ODBC Driver Manager provides APIs to the ODBC Driver to retrieve data source attributes (e.g., SQLPrivateProfileString). First we have to cover some background information. If you think you know this or just want to skip it scroll down to "The Connection Process". <h2>Data Source Names (DSNs)</h2> The key to the SQLConnect/SQLDriverConnect APIs is the Data Source Name (DSN). A DSN is a way of defining various attributes for an ODBC database connection under one name. Typically a DSN defines the name of the ODBC Driver to use and other attributes like the database name, host name, database username/password etc but in the main it only really has to define the ODBC Driver and all the other attributes are optional. In Windows the DSN is stored in a registry key and ODBC Drivers in another registry key and the ODBC Driver Manager provides the GUI ODBC Administrator to create/edit DSNs. When ODBC Drivers are installed an entry is added to the registry key defining ODBC Drivers and another key defines the DLL which is used to create DSNs for that ODBC Driver. On non-Windows platforms DSNs are stored in the odbc.ini (or .odbc.ini file for user DSNs) and ODBC Drivers in the odbcinst.ini file and although ODBC Driver Managers like unixODBC provide a GUI application (and the odbcinst command) to create/edit installed ODBC Drivers and DSNs but most people simply edit the files. Actually, unixODBC works just like the ODBC Driver Manager in Windows in that when an ODBC Driver is installed it can add a Setup attribute to the driver entry in the odbcinst.ini file and then unixODBC can load that shared object to create/edit DSNs but the GUI part of unixODBC is written in QT and there are few drivers which provide a QT shared object for creating/editing DSNs. <h2>USER/SYSTEM DSNs</h2> The first thing you need to realise is there are USER and SYSTEM DSNs. USER DSNs are only available to the logged in user and SYSTEM DSNs are available to anyone. In reality, in Windows this is achieved by putting USER/SYSTEM DSNs in different registry keys and in UNIX by putting them in different files (usually /etc/odbc.ini or /usr/local/odbc.ini for SYSTEM DSNs and ~/.odbc.ini for USER DSNs). This in itself is a common source of problems since people define USER DSNs when logged in a user "fred" then run their application (e.g., Apache, IAS etc) as user "dave" and wonder why they get a DSN not found error. <h2>SQLConnect vs SQLDriverConnect</h2> The process in the driver manager is pretty much the same whether you call SQLConnect or SQLDriverConnect (with some exceptions). The SQLConnect API takes ServerName, UserName and Authentication strings. Generally these are the DSN name, database username and database password. Beyond that it is fairly inflexible and even the ServerName argument has a maximum length which is usually 32 characters. The SQLDriverConnect API is far more flexible in that it takes arguments of a WindowHandle, InConnectionString, OutConnectionString, and DriverCompletion. The InConnectionString is a ';' separated list of attributes which may include DSN, UID (username), PWD (password), DRIVER (driver name), FILEDSN (pointer to a file containing a DSN), SAVEDSN (file to write the completed connection string) and any others the ODBC Driver cares to define. The OutConnectionString returns a completed connection string which may be used to connect to the database again (more later). The WindowHandle allows the ODBC Driver Manager to use the ODBC Driver Setup DLL/shared object to display a GUI dialogue to create/edit DSNs (this is another area where unixODBC and QT have some issues). The DriverCompletion argument allows the application to say how it would like the ODBC Driver to behave when the attributes passed in InConnectionString are insufficient (or not) to connect to a specific database: SQL_DRIVER_PROMPT SQL_DRIVER_COMPLETE SQL_DRIVER_COMPLETE_REQUIRED SQL_DRIVER_NOPROMPT (See below for the meanings of these attributes). For the sake of this discussion I am assuming the ODBC Driver manager maps calls to SQLConnect to SQLDriverConnect where ServerName, Username and Authentication arguments are mapped to SQLDriverConnect's InConnectionString as DSN/UID/PWD attributes in a ';' delimited string and DriverCompletion is SQL_DRIVER_COMPLETE. i.e., if someone calls: SQLConnect("mydsnname", "myusername", "mypassword") it ends is SQLDriverConnect with an InConnectionString of: DSN=mydsnname;UID=myusername;PWD=mypassword and DriverCompletion of SQL_DRIVER_COMPLETE. >From here on the discussion is SQLDriverConnect focussed. <h2>ODBC defined connection attributes</h2> The SQLDriverConnect API has an InConnectionString argument which is a ';' delimited string of attributes. ODBC defines some attributes: DRIVER - the name of the ODBC Driver There are some extra rules for defining the location and name of the DLL or shared object for the driver which require the filespec to be enclosed in {}. DSN - a data source name UID - a database username PWD - UID's password FILEDSN - the name of a file containing the attributes for the DSN Few non-Windows drivers support FILEDSN although all Easysoft drivers do. SAVEFILE - file to save completed connection string Few non-Windows drivers support SAVEFILE although all Easysoft drivers do. For DRIVER see "DSN-less Connections". If a DSN is supplied it is simply a name used to lookup the other attributes in the DSN (in the registry or odbc.ini file). If UID/PWD are supplied they override any UID/PWD in the DSN. FILEDSN defines the name of a file containing all the other attributes a driver may need to connect to the database. FILEDSN is rarely used but is useful if you want to distribute a DSN with your application. <h2>ODBC Driver defined attributes</h2> An ODBC Driver may define whatever attributes it requires. Often these are attributes like "database" (database to connect to), "servername" (server where database resides), port (port to connect to), but there is no universal standard. If an ODBC Driver has a GUI setup DLL/shared object to create/edit DSNs it will allow you to enter the values for these attributes and store them in the DSN. Otherwise, you just have to consult the ODBC Driver documentation to find out what attributes it requires/defines. <h2>DSN-less Connections</h2> DSN-less connections are obviously those which do not specify a DSN. That is possible because instead you specify the DRIVER attribute and optionally all the other attributes the driver needs to connect to the database (or the driver prompts for the missing attributes). The DRIVER attribute tells the ODBC Driver Manager which driver to load and pass the InConnectionString on to. After that it is up to the ODBC Driver to find any other attributes it needs in the InConnectionString or by prompting the user for additional attributes (this depends on the DriverCompletion argument). <h2>The DriverCompletion argument</h2> The DriverCompletion argument may be: SQL_DRIVER_PROMPT The driver displays a dialog box, using the values from the connection string and system information (if any) as initial values. When the user exits the dialog box, the driver connects to the data source. It also constructs a connection string from the value of the DSN or DRIVER keyword in InConnectionString and the information returned from the dialog box. It places this connection string in the OutConnectionString buffer. There are very few ODBC Drivers on non-Windows platforms that can handle this - the Easysoft ODBC-ODBC Bridge is the only one I know of. SQL_DRIVER_COMPLETE SQL_DRIVER_COMPLETE_REQUIRED If the connection string contains enough information, and that information is correct, the driver connects to the data source and copies InConnectionString to OutConnectionString. If any information is missing or incorrect, the driver takes the same actions as it does when DriverCompletion is SQL_DRIVER_PROMPT, except that if DriverCompletion is SQL_DRIVER_COMPLETE_REQUIRED, the driver disables the controls for any information not required to connect to the data source. This is the most flexible option on non-Windows platforms or on Windows if the application is not a GUI as it tells the ODBC driver to fill in the missing pieces as it can. SQL_DRIVER_NOPROMPT If the connection string contains enough information, the driver connects to the data source and copies InConnectionString to OutConnectionString. Otherwise, the driver returns SQL_ERROR for SQLDriverConnect. This is pretty inflexible in all environments as it means that if insufficient attributes are found for a connect the SQLDriverConnect will fail (even if it could find the needed attributes in a DSN). <h2>DEFAULT DSNs</h2> ODBC defines a DEFAULT DSN which will be used when no DRIVER or DSN attribute is defined. The DEFAULT DSN is named "DEFAULT" and works like any other DSN except the ODBC Driver Manager knows to look for it if no Driver/DSN attributes are specified. <h2>How do drivers "complete" a connection string?</h2> When an ODBC Driver is handed a connection string it parses it into separate attributes and then determines if it has sufficient attributes to connect to the database. Depending on what you pass in the DriverCompletion argument the driver may consult the DSN to obtain other attributes it needs to connect, throw a dialogue to obtain the remaining attributes or error. Attributes passed in the InConnectionString override those in any DSN or dialogue (greyed out in dialogues if SQL_DRIVER_COMPLETE_REQUIRED). Assuming the ODBC Driver has insufficient attributes to connect to the database and a DSN is named (and SQL_DRIVER_COMPLETE|SQL_DRIVER_COMPLETE_REQUIRED is specified) it can use the ODBC Driver Manager API SQLGetPrivateProfileString to obtain any remaining attributes. It sets the DSN name then requests attributes by name and the driver manager will look them up in the DSN and return them to the driver. In this way the ODBC Driver does not need to know where the additional attributes are held, only their name. It uses this method to "complete" the connection string and on connection the full OutConnectionString to reconnect to this database will be returned (applications like MS Access use this to store the full connection string so it does not need the driver to prompt the next time you connect). This works well in Windows but falls down on non-Windows platforms where some drivers do not use the SQLGetPrivateProfileString API or store connection information in other files (e.g., freeTDS can store database and protocol information in the freetds.conf file). Worse, some drivers do use SQLPrivateProfileString but also have a driver-specific configuration file outside of ODBC and use different names for the attributes in each and define different orders for where they look first. If you are working on an ODBC Driver or have input to the development of one then I strongly suggest you encourage the use of SQLGetPrivateProfileString and linking with an ODBC Driver Manager. <h2>The Connection Process</h2> If you have not read the above I hope you know your ODBC API. This discussion assumes the application is linked with an ODBC Driver Manager which provides SQLDriverConnect (or SQLDriverConnectW) and SQLGetPrivateProfileString. The application calls SQLDriverConnect with a connection string and DriverCompletion. The connection string needs to define an attribute which allows the driver manager to locate the ODBC Driver (DRIVER or DSN). If the connection string names a DRIVER the driver manager loads it. If the InConnectionString contains a DSN/FILEDSN the driver manager looks up the DSN to find the driver and loads the driver. If the driver cannot be found this way you end up up with an error saying something like "Datasource not found and no DEFAULT defined" (although see DEFAULT DSNs above). At this stage the ODBC Driver manager needs to allocate an environment handle (SQLAllocHandle for SQL_HANDLE_ENV), set the ODBC version (SQLSetConnectAttr for SQL_ODBC_VERSION) and allocate a connection handle (SQLAllocHandle for SQL_HANDLE_DBC). In particular the SQLAllocHandle for an environment can fail at this stage if the ODBC Driver requires an environment variable to be set (e.g., DB2INSTANCE, ORACLE_HOME). After this the InConnectionString is passed to the ODBC Drivers SQLDriverConnect. >From this point on what happens is down to the ODBC Driver. It parses the InConnectionString and extracts all attributes and values. It then needs to ascertain if it has sufficient attributes to connect to the database although if SQL_DRIVER_PROMPT is defined it must throw a dialogue anyway. If it is short of attributes to connect it may look up additional attributes via the FILEDSN/DSN attributes and SQLPrivateProfileString so long as DriverCompletion allows. As explained above this is where things deviate for non-Windows as some drivers do not use SQLPrivateProfileString and/or use a separate file to define connections to databases. At this stage and depending on DriverCompletion the ODBC Driver will either complete the missing attributes (see "How do drivers "complete" a connection string" above), throw a dialogue or error. <h2>Perl DBD::ODBC</h2> Perl's DBD::ODBC module has a long history and attempts to be compatible with as many ODBC Drivers as possible. This means it aims to support ODBC 1/2 drivers and more modern ODBC 3 drivers. As a result, any call to DBI's connect method which does not mention a DRIVER or DSN attribute first results in a call to SQLConnect. e.g., DBI->connect("dbi:ODBC:fred", "uid", "pwd"); results in a call to SQLConnect(ServerName="fred","uid","pwd) and only if this fails does it result in: SQLDriverConnect(InConnectionString="DSN=fred;UID=uid;PWD=pwd") On the other hand, if the call to DBI's connect method contains a DSN or DRIVER the call to SQLConnect is bypassed and a direct call to SQLDriverConnect is made: DBI->connect("dbi:ODBC:DSN=fred") results in: SQLDriverConnect(InConnectionString="DSN=fred"); DBI->connect("dbi:ODBC:DSN=fred;something=somethingelse") results in: SQLDriverConnect(InConnectionString="DSN=fred;something=esomethingelse"); DBI->connect("dbi:ODBC:DSN=fred", "uid", "pwd") results in: SQLDriverConnect(InConnectionString="DSN=fred;UID=uid;PWD=pwd"); DBI->connect("dbi:ODBC:DRIVER={fred};something=somethingelse") results in SQLDriverConnect(InConnectionString="DRIVER={fred};something=somethingelse"); and in addition UID=x and PWD=y will be added it you passed a username/password to DBI->Connect. The upshot of this is that if you are passing a DSN name to DBI->connect then prefix it with "DSN=" especially if you've added other attributes or you could waste time calling SQLConnect with too long a ServerName attribute to SQLConnect which will only fail then call SQLDriverConnect (wasting time). In addition, if the connection string passed to DBI->connect contains unicode strings (the UTF8 flag is set) DBD::ODBC will call SQLDriverConnectW assuming it was built for unicode (see -u argument to perl Makefile.pl).