5. Sequoia Driver

5.1. Overview

The Sequoia driver is a generic JDBC driver that is designed to replace any database specific JDBC driver that could be used by a client. The client only has to know on which node the Sequoia controller is running and the name of the database to access. The Sequoia driver implements most of the JDBC 3.0 interface.

Users reported successful usage of Sequoia with the following RDBMS: Oracle®, PostgreSQL, MySQL, Apache Derby, IBM DB2®, Sybase®, SAP DB (MySQL MaxDB), HyperSonic SQL, Firebird, MS SQL Server and InstantDB.

5.2. Loading the Driver

The Sequoia driver can be loaded as any standard JDBC driver from the client program using:

 
        Class.forName("org.continuent.sequoia.driver.Driver"); 
      
[Note]Note

The sequoia-driver.jar file must be in the client classpath else the driver will fail to load.

5.3. Sequoia JDBC URL

The JDBC URL expected for the use with Sequoia is the following: jdbc:sequoia://host1:port1,host2:port2/database.

host is the machine name (or IP address) where the Sequoia controller is running, port is the port the controller is listening for client connections.

At least one host must be specified but a list of comma separated hosts can be specified. If several hosts are given, one is picked up randomly from the list. If the currently selected controller fails, another one is automatically picked up from the list.

The port is optional is the URL and the default port number is 25322 if it is omitted. Those two examples are equivalent:

 
        DriverManager.getConnection("jdbc:sequoia://localhost/tpcw"); 
        DriverManager.getConnection("jdbc:sequoia://localhost:25322/tpcw"); 
        

Examples using two controllers for fault tolerance:

 
        DriverManager.getConnection("jdbc:sequoia://c1.continuent.org,c2.objectweb.org/tpcw"); 
        DriverManager.getConnection("jdbc:sequoia://localhost,remote.continuent.org:2048/tpcw"); 
        DriverManager.getConnection("jdbc:sequoia://smpnode.com:25322,smpnode.com:1098/tpcw"); 
        

5.3.1. URL options

The Sequoia driver accepts additional options to override the default behavior of the driver. The options are appended at the end of the Sequoia URL after a question mark followed by a list of ampersands separated options. Here is an example:

 
        DriverManager.getConnection("jdbc:sequoia://host/db?user=me&password=secret")

Another option is to use semicolons to delimit the start of options and options themselves. Example:

 
        DriverManager.getConnection("jdbc:sequoia://host/db;user=me;password=secret")

The recognized options are:

  • connectionPooling: By default the Sequoia driver does transparent connection pooling on your behalf meaning that when connection.close() is called, the connection is not physically closed but rather put in a pool for reuse within the next 5 seconds. Set this to false if you do not want the driver to perform transparent connection pooling.

  • debugLevel: Debug level that can be set to 'debug', 'info' or 'off' to display driver related information on the standard output. Default is off.

  • escapeBackslash: Set this to false if you don't want to escape backslashes when performing escape processing of PreparedStatements, default is true.

  • escapeSingleQuote: Set this to false if you don't want to escape single quotes (') when performing escape processing of PreparedStatements, default is true

  • escapeCharacter: Character to prepend and append to the String values when performing escape processing of PreparedStatements, default is a single quote.

  • user: user login

  • password: user password

  • preferredController: defines the strategy to use to choose a preferred controller to connect to.

    jdbc:sequoia://node1,node2,node3/myDB?preferredController=ordered : Always connect to node1, and if not available then try to node2 and finally if none are available try node3.

    jdbc:sequoia://node1,node2,node3/myDB?preferredController=random: Pickup a controller node randomly (default strategy)

    jdbc:sequoia://node1,node2:25343,node3/myDB?preferredController=node2:25343,node3 : Round-robin between node2 and node3, fallback to node1 if none of node2 and node3 is available.

    jdbc:sequoia://node1,node2,node3/myDB?preferredController=roundRobin: Round robin starting with first node in URL.

  • retryIntervalInMs: once a controller has died, the driver will try to reconnect to this controller every retryIntervalInMs to see if the backend is back online. The default is 5000 (5 seconds).

5.4. Getting a connection using a data source

Another way to use the Sequoia driver is to use its DataSource implementation. Data sources have been introduced in JDBC 2.0 Standard Extension API and are also a part of JDBC 3.0. They use the Java Naming and Directory Interface (JNDI) to break the application dependence on the JDBC driver configuration (i.e., driver class name, machine name, port number, etc.). With a data source, the only thing an application has to know is the name assigned to the DataSource object in the jdbc naming subcontext of the JNDI namespace.

The example below registers a data source object with a JNDI naming service. It is typically used by an application server.

      import org.continuent.sequoia.driver.DataSource;
      import javax.naming.Context;
      import javax.naming.InitialContext;
      import javax.naming.NamingException;
      ...
      private final static String NAME = "jdbc/sequoia";
      private final static String URL = "jdbc:sequoia://localhost:25322/mysql";
      
      // Initializing data source
      DataSource ds = new DataSource();
      ds.setUrl(URL);

      // Get initial context
      Context ctx;
      try {
        ctx = new InitialContext();
      } catch (javax.naming.NamingException _e) {
        ... // Naming exception
      }
		
      // Bind data source to a JNDI name
      try {
        ctx.bind(NAME, ds);
      } catch (javax.naming.NamingException _e) {
        ... // Naming exception
      }
      

The org.continuent.sequoia.driver.DataSource class implements the javax.sql.DataSource JDBC 3.0 interface. The setUrl line initializes the data source properties (the URL in this case). The data source object is bound to a logical JNDI name by calling ctx.bind(). In the example above, the JNDI name specifies a "jdbc" subcontext and a "sequoia" logical name within this subcontext.

Once a data source object is registered to JNDI, it can be used by an application. The example below gets the data source using the JNDI naming service. Such a piece of code is typically a part of an application that uses JDBC.

      import javax.naming.Context;
      import javax.naming.InitialContext;
      import javax.naming.NamingException;
      import java.sql.Connection;
      import javax.sql.DataSource;
      ...
      private final static String NAME = "jdbc/sequoia";

      // Lookup for the data source object
      try {
        Context ctx = new InitialContext();
        Object obj = ctx.lookup(NAME);
        if (null == obj) {
          ... // Something wrong: NAME not found
        }
        ctx.close( );
      } catch (javax.naming.NamingException _e) {
        ... // Naming exception
      }
      
      // Get a new JDBC connection
      try {
        DataSource ds = (DataSource) obj;
        Connection conn = ds.getConnection("user", "sequoia");
        ... // Use of the connection retrieved
        ...
      } catch (SQLException _e) {
        ... // SQL exception
      }
      

The ctx.lookup() line in the example uses the retrieved initial JNDI naming context to do a lookup using the data source logical name. The method returns a reference to a Java object which is then narrowed to a javax.sql.DataSource object. Such an object can be then used to open a new JDBC connection by invoking one of its getConnection() methods. The application code is completely independent of the driver details, such as the Driver class name, URL, etc. (the user name and password used by the connection can be also set by the application server - look at the Sequoia javadoc documentation for more details). The only information a JDBC application has to know is the logical name of the data source object to use.

[Note]Note

The URL used for the Sequoia data source is the same as for the Driver described in the previous section.

5.5. Stored procedures

Stored procedures are supported by Sequoia since version 1.0b6. Note that Sequoia only support calls in the form {call <procedure-name>[<arg1>,<arg2>, ...]} but does not support {? = call <procedure-name>[<arg1>,<arg2>, ...]}.

A call to a stored procedure is systematically broadcasted to all backends since there is no way to know if the stored procedure will update the database or not. Therefore, the query cache (see Section 10.6.3, “Request Cache”), is completely flushed on every stored procedure call. To prevent cache flushing, the user can force the connection to read-only before calling the stored procedure. But never set a connection to read-only when calling a stored procedure that updates the database. If Sequoia detects a read-only connection, it will not flush the cache. However, the call will still be broadcasted to all nodes resulting in duplicated jobs on each backend. Here is an example on how to prevent cache flushing when calling a stored procedure that does only read-only:

 
        ... 
        CallableStatement cs = connection.prepareCall("{call myproc(?)}"); 
        cs.setString(1,"parameter1");
        // Force no cache flush  
        connection.setReadOny(true); 
        // Call the stored procedure without flushing the cache ... 
        ResultSet rs = cs.executeQuery(); 
      

In the case of horizontal scalability, only read-only stored procedures are not broadcasted. All other stored procedures returning an int or a ResultSet are executed by all backends at all controllers.

[Note]Note

It is not allowed to set a connection to read-only in the middle of a transaction. If you need to set a connection to read-only, you must do so before starting the transaction.

5.6. Blobs: Binary Large Objects

You should not have to change your code for storing blobs into your database. Sequoia will transparently encode the blob in the protocol and forward it to your database driver.

[Note]Note
  • The column type used to store large objects with MySQL is text.

  • The column type used to store large objects with PostgreSQL is bytea.

Please refer to the following lines of code for storing and retrieving of large objects:

        // In the code below:
        // The signature of the readBinary method is:
        // byte[] readBinary(File file) throws IOException
        // it just read a file, and convert its content into an array of bytes
        
        // Store file in database 
        File fis = new File(storeFile); 
        query = "insert into ... values(...,?)"; 
        ps1 = con.prepareStatement(query); 
        if (callBlobMethods) 
        {
          org.continuent.sequoia.common.protocol.ByteArrayBlob bob = 
            new org.continuent.sequoia.common.protocol.ByteArrayBlob(readBinary(fis)); 
          ps1.setBlob(1, bob); 
        } 
        else 
        { 
          ps1.setBytes(1, readBinary(fis)); 
        } 
        ps1.executeUpdate();
        // Read File from database 
        query = "select * from ... where id=..."; 
        ps1 = con.prepareStatement(query); 
        ResultSet rs = ps1.executeQuery(); 
        rs.first(); 
        byte[] lisette; 
        if (callBlobMethods) 
        { 
          Blob blisette = rs.getBlob("blobcolumnname"); 
          lisette = blisette.getBytes((long) 1, (int) blisette.length());
        }
        else 
        {
          lisette = rs.getBytes("blobcolumnname"); 
        } 
      

5.7. Clobs: Character Large Objects

CLOB is a built-in type that stores a Character Large Object as a column value in a row of a database table. By default drivers implement Clob using an SQL locator (CLOB), which means that a Clob object contains a logical pointer to the SQL CLOB data rather than the data itself. A Clob object is valid for the duration of the transaction in which it was created.

Clobs in Sequoia are handled like strings. You can refer to the section of code below to make good usage of clobs. This code is part of the Sequoia test suite.

 
        String clob = "I am a clob"; 
        ps = con.prepareStatement("insert into ... values(...,?)"); 
        ps.setString(1, clob); 
        ps.executeUpdate(); 
        
        // Test retrieval 
        String ret; 
        ps = con.prepareStatement("Select * from ... where id=..."); 
        rs = ps.executeQuery(); 
        rs.first(); 
        clob = rs.getClob("name"); 
        ret = clob.getSubString((long) 0, (int) clob.length()); 
      

5.8. ResultSet streaming

In its default mode, when a query is executed on a backend, Sequoia makes a copy of the backend's native ResultSet into a Sequoia serializable ResultSet. If the result contains many rows or very large objects, the controller might run out of memory when trying to copy the whole ResultSet.

It is possible to fetch ResultSets by blocks using the Statement.setFetchSize(int rows) method. In this case, the ResultSet will be copied by block of rows and returned when needed by the client. Note that the current implementation only allows to fetch forward streamable ResultSet, which basically means that you are only allowed to call ResultSet.next() on a streamable ResultSet.

Sequoia will try to call setFetchSize() on the backend's driver to let the backend driver also perform the necessary optimizations. However, some driver requires a prior call to setCursorName() in which case you will also have to call setCursorName() on Sequoia to pass it to the backend's driver.

A typical usage of the ResultSet streaming feature is as follows:

...        
Connection con = getSEQUOIAConnection();
con.setAutocommit(false);
Statement s = con.createStatement();
s.setCursorName("cursor name");
s.setFetchSize(10);
rs = s.executeQuery(sql);
while (rs.next())
{ // Every 10 calls, Sequoia will transfer a new block of rows
  XXX o = rs.getXXX("some column name");
}
...
con.commit();
      
[Note]Note

Streamable ResultSets are not cacheable. The result cache automatically detects this kind of ResultSet and does not keep them in the cache. However, as database specific ResultSets are copied into Sequoia ResultSets, the memory footprint of the fetched blocks will be twice the one obtained without Sequoia. If you have memory restrictions, you can reduce your fetch size by half to reduce the memory footprint of streamed ResultSets.

Streamable ResultSets do not work properly in autocommit mode as the connection used for retrieving the ResultSet is handed back to the pool. The workaround is to always encapsulate the query in a transaction. Note that databases such as PostgreSQL do not support streamable ResultSets in autocommit mode as well.

5.9. Current Limitations

The Sequoia driver currently does not support the following features:

  • java.sql.Array and java.sql.Ref types,

  • Custom type mapping using java.sql.Connection.setTypeMap(java.util.Map map),

  • XAConnections (look at the XAPool project for XA support with Sequoia),

  • Streamable ResultSets do not work in autocommit mode.