1 import java.sql.*;
2 import java.util.*;
3 import java.io.*;
4 
5 /*
6  * Note:
7  * This provider invokes JNI in the following way: a C API calls some Java code through JNI, which in turn calls
8  * some C code as native method. To exchange pointers, the C pointers are converted to the "long" java type, which is mapped
9  * to the "jlong" C type which is always 64 bits long, refer to http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html
10  *
11  * In C code, when passing a C pointer to a method, it needs to be converted to a jlong, and the jni_cpointer_to_jlong()
12  * function must be used.
13  */
14 
15 /*
16  * This class will be instantiated once for each GdaJdbcProvider created.
17  */
18 class GdaJProvider {
19 	private static native void initIDs();
20 	private String driver;
21 
22 	// Constructor
23 	public GdaJProvider (String driver) {
24 		this.driver = driver;
25 	}
26 
27 	// Connection opening
28 	public GdaJConnection openConnection (String cnc_string, String username, String password) throws Exception {
29 		Connection cnc;
30 		try {
31 			Class.forName (driver);
32 			cnc = DriverManager.getConnection (cnc_string, username, password);
33 		}
34 		catch (Exception e) {
35 			throw e;
36 		}
37 		GdaJConnection jcnc = new GdaJConnection (cnc, driver);
38 		return jcnc;
39 	}
40 
41 	// Get a list of all the known drivers
42 	// See also http://www.devx.com/tips/Tip/28818 for a list of drivers
43 	public static String getDrivers () {
44 		try {
45 			Class driverClass = Class.forName ("org.hsqldb.jdbcDriver");
46 		}
47 		catch (Exception e) {
48 			// ignore exceptions, they mean some drivers are not available
49 		}
50 
51 		java.util.Enumeration e = DriverManager.getDrivers();
52 		String res = null;
53 		while (e.hasMoreElements ()) {
54 			Driver driver = (Driver) e.nextElement();
55 			if (res == null)
56 				res = driver.getClass().getName();
57 			else
58 				res = res + ":" + driver.getClass().getName();
59 			if (false)
60 				System.out.println ("FOUND DRIVER " + driver.getClass().getName());
61 
62 			// Properties list, for DSN spec creation.
63 			if (false) {
64 				System.out.println("===== DEBUG: Properties for " + driver.getClass().getName());
65 				try {
66 					Properties ep = new Properties();
67 					DriverPropertyInfo[] props = driver.getPropertyInfo ("", ep);
68 					if (props != null) {
69 						for (int i = 0; i < props.length; i++){
70 							DriverPropertyInfo prop = props[i];
71 							System.out.println ("  Prop name = "+prop.name);
72 							System.out.println ("     Prop description = "+prop.description);
73 							System.out.println ("     Prop value = "+prop.value);
74 							if (prop.choices != null) {
75 								for (int j = 0; j < prop.choices.length; j++) {
76 									System.out.println ("     prop choice "+j
77 											    +" = "+prop.choices[j]);
78 								}
79 							}
80 						}
81 					}
82 				}
83 				catch (Exception ex) {
84 					ex.printStackTrace ();
85 				}
86 			}
87 		}
88 		return res;
89 	}
90 
91 	public static Connection openConnection (String driver, String cnc_string) {
92 		System.out.println ("JAVA openConnection() requested");
93 		return null;
94 	}
95 
96 	// main(): in case someone tries to exetute the JAR
97 	public static void main (String args[]) {
98                 System.out.println ("This JAR is not intended to be executed directly, " +
99 				    "but used as Libgda's JDBC provider!");
100         }
101 
102 	// class initializer
103 	static {
104 		if (System.getProperty("os.name").indexOf ("Win") >= 0) {
105 			// If we are running on Windows, then we need to set the full library name
106 			// because our library is still called libgda-jdbc.dll
107 			System.loadLibrary ("libgda-jdbc");
108 		}
109 		else {
110 			System.loadLibrary ("gda-jdbc");
111 		}
112 		initIDs ();
113 
114 		try {
115 			/* dummy objects creation to force class loading */
116 			byte[] data = {'e'};
117 			GdaInputStream is = new GdaInputStream (data);
118 		}
119 		catch (Exception e) {
120 			System.out.println ("Coult not initialize some JAVA classes");
121 			e.printStackTrace ();
122 		}
123 	}
124 }
125 
126 
127 /*
128  * This class will be instantiated once for each GdaConnection object
129  */
130 class GdaJConnection {
131 	private static native void initIDs();
132 	public Connection cnc = null;
133 	private HashMap<String,Savepoint> svp_map = new HashMap<String,Savepoint>();
134 	private GdaJMeta jmeta = null;
135 	String driver;
136 
137 	// Constructor
138 	public GdaJConnection (Connection cnc, String driver) throws Exception {
139 		this.cnc = cnc;
140 		this.driver = driver;
141 	}
142 
143 	// Connection close
144 	public void close () throws Exception {
145 		this.cnc.close ();
146 		jmeta = null;
147 	}
148 
149 	// get server version
150 	public String getServerVersion () throws Exception {
151 		DatabaseMetaData meta_data;
152 		meta_data = cnc.getMetaData ();
153 		return meta_data.getDatabaseProductName() + " " + meta_data.getDatabaseProductVersion ();
154 	}
155 
156 	// prepare statement
157 	public GdaJPStmt prepareStatement (String sql) throws Exception {
158 		GdaJPStmt ps;
159 		PreparedStatement pstmt;
160 
161 		pstmt = cnc.prepareStatement (sql);
162 		ps = new GdaJPStmt (pstmt);
163 		return ps;
164 	}
165 
166 	// direct execution of a SELECT with no parameters
167 	public GdaJResultSet executeDirectSQL (String sql) throws Exception {
168 		Statement st;
169 		st = cnc.createStatement ();
170 		return new GdaJResultSet (st.executeQuery (sql));
171 	}
172 
173 	// Transaction management
174 	public void begin () throws Exception {
175 		if (cnc.getAutoCommit() == false) {
176 			throw new Exception ("Transaction already started");
177 		}
178 		cnc.setAutoCommit (false);
179 	}
180 
181 	public void rollback () throws Exception {
182 		if (cnc.getAutoCommit() == true) {
183 			throw new Exception ("No transaction started");
184 		}
185 		cnc.rollback ();
186 		cnc.setAutoCommit (true);
187 	}
188 
189 	public void commit () throws Exception {
190 		if (cnc.getAutoCommit() == true) {
191 			throw new Exception ("No transaction started");
192 		}
193 		cnc.commit ();
194 		cnc.setAutoCommit (true);
195 	}
196 
197 	public void addSavepoint (String svp_name) throws Exception {
198 		Savepoint svp;
199 		if (cnc.getAutoCommit() == true) {
200 			throw new Exception ("No transaction started");
201 		}
202 
203 		svp = svp_map.get (svp_name);
204 		if (svp != null) {
205 			throw new Exception ("Savepoint '" + svp_name + "' already exists");
206 		}
207 		svp = cnc.setSavepoint (svp_name);
208 		svp_map.put (svp_name, svp);
209 	}
210 
211 	public void rollbackSavepoint (String svp_name) throws Exception {
212 		Savepoint svp = null;
213 		if (cnc.getAutoCommit() == true) {
214 			throw new Exception ("No transaction started");
215 		}
216 		svp = svp_map.get (svp_name);
217 		if (svp == null) {
218 			throw new Exception ("No savepoint '" + svp_name + "' found");
219 		}
220 		cnc.rollback (svp);
221 		svp_map.remove (svp_name);
222 	}
223 
224 	public void releaseSavepoint (String svp_name) throws Exception {
225 		Savepoint svp = null;
226 		if (cnc.getAutoCommit() == true) {
227 			throw new Exception ("No transaction started");
228 		}
229 		svp = svp_map.get (svp_name);
230 		if (svp == null) {
231 			throw new Exception ("No savepoint '" + svp_name + "' found");
232 		}
233 		cnc.releaseSavepoint (svp);
234 		svp_map.remove (svp_name);
235 	}
236 
237 	// meta data retrieval
238 	public GdaJMeta getJMeta () throws Exception {
239 		if (jmeta == null) {
240 			String name= driver.replace (".", "_") + "Meta";
241 			try {
242 				Class<?> r = Class.forName (name);
243 				java.lang.reflect.Constructor c =
244 					r.getConstructor (new Class [] {Class.forName ("java.sql.Connection")});
245 				jmeta = (GdaJMeta) c.newInstance (new Object [] {cnc});
246 			}
247 			catch (Exception e) {
248 				if (driver.contains ("org.apache.derby.jdbc")) {
249 					try {
250 						name = "org_apache_derbyMeta";
251 						Class<?> r = Class.forName (name);
252 						java.lang.reflect.Constructor c =
253 							r.getConstructor (new Class [] {Class.forName ("java.sql.Connection")});
254 						jmeta = (GdaJMeta) c.newInstance (new Object [] {cnc});
255 					}
256 					catch (Exception e1) {
257 						// nothing
258 					}
259 				}
260 				else if (driver.contains ("sqlserver") ||
261 					 driver.contains ("com.ashna.jturbo.driver.Driver") ||
262 					 driver.contains ("com.inet.tds.TdsDriver")) {
263 					try {
264 						name = "sqlserverMeta";
265 						Class<?> r = Class.forName (name);
266 						java.lang.reflect.Constructor c =
267 							r.getConstructor (new Class [] {Class.forName ("java.sql.Connection")});
268 						jmeta = (GdaJMeta) c.newInstance (new Object [] {cnc});
269 					}
270 					catch (Exception e1) {
271 						// nothing
272 					}
273 				}
274 			}
275 			if (jmeta == null)
276 				jmeta = new GdaJMeta (cnc);
277 		}
278 		return jmeta;
279 	}
280 
281 	// class initializer
282 	static {
283 		initIDs ();
284 	}
285 }
286 
287 /*
288  * Represents a prepared statement
289  */
290 class GdaJPStmt {
291 	private static native void initIDs();
292 	private PreparedStatement ps;
293 	private Vector<GdaJValue> param_values;
294 
295 	// Constructor
296 	public GdaJPStmt (PreparedStatement ps) {
297 		this.ps = ps;
298 		param_values = new Vector<GdaJValue> (0);
299 	}
300 
301 	// declares the expected type for the parameters
302 	public void declareParamTypes (long cnc_c_pointer, byte types[]) throws Exception {
303 		int nparams = ps.getParameterMetaData().getParameterCount ();
304 		int i;
305 		if (types.length != nparams) {
306 			throw new Exception ("Number of types differs from number of parameters: got " + types.length +
307 					     " and expected " + nparams);
308 		}
309 
310 		for (i = 0; i < nparams; i++) {
311 			GdaJValue cv = GdaJValue.proto_type_to_jvalue (types[i]);
312 			cv.setGdaConnectionPointer (cnc_c_pointer);
313 			param_values.add (cv);
314 			//System.out.println ("JAVA: param " + i + " as type " + types[i]);
315 		}
316 	}
317 
318 	// sets a parameter's value
319 	// if c_value_pointer is 0, then the parameter should be set to NULL
320 	public void setParameterValue (int index, long c_value_pointer) throws Exception {
321 		GdaJValue cv = param_values.elementAt (index);
322 		cv.getCValue (ps, index, c_value_pointer);
323 	}
324 
325 	// Clear previous set parameters
326 	public void clearParameters () throws Exception {
327 		ps.clearParameters ();
328 	}
329 
330 	// Execute
331 	public boolean execute () throws Exception {
332 		return ps.execute ();
333 	}
334 
335 	// Fetch result set
336 	public GdaJResultSet getResultSet () throws Exception {
337 		return new GdaJResultSet (ps.getResultSet ());
338 	}
339 
340 	public int getImpactedRows () throws Exception {
341 		return ps.getUpdateCount ();
342 	}
343 
344 	// class initializer
345 	static {
346 		initIDs ();
347 	}
348 }
349 
350 
351 /*
352  * This class represents meta data information for a GdaJResultSet.
353  *
354  * All the fields are read-only
355  */
356 class GdaJResultSetInfos {
357 	private static native void initIDs();
358 	public Vector<GdaJColumnInfos> col_infos;
359 	private int ncols;
360 
361 	// Constructor
362 	public GdaJResultSetInfos (ResultSetMetaData md) throws Exception {
363 		int i;
364 		ncols = md.getColumnCount ();
365 		col_infos = new Vector<GdaJColumnInfos> (0);
366 		for (i = 0; i < ncols; i++) {
367 			GdaJColumnInfos ci;
368 			int rcol = i + 1;
369 			ci = new GdaJColumnInfos (md.getColumnName (rcol), md.getColumnLabel (rcol), md.getColumnType (rcol));
370 			col_infos.add (ci);
371 		}
372 	}
373 
374 	public GdaJResultSetInfos (Vector<GdaJColumnInfos> col_infos) {
375 		this.col_infos = col_infos;
376 		ncols = col_infos.size ();
377 	}
378 
379 	// describe a column, index starting at 0
380 	public GdaJColumnInfos describeColumn (int col) throws Exception {
381 		return (GdaJColumnInfos) col_infos.elementAt (col);
382 	}
383 
384 	// class initializer
385 	static {
386 		initIDs ();
387 	}
388 }
389 
390 class GdaJColumnInfos {
391 	private static native void initIDs();
392 	String col_name;
393 	String col_descr;
394 	int col_type;
395 
396 	public GdaJColumnInfos (String col_name, String col_descr, int col_type) {
397 		this.col_name = col_name;
398 		this.col_descr = col_descr;
399 		this.col_type = col_type;
400 	}
401 
402 	// class initializer
403 	static {
404 		initIDs ();
405 	}
406 }
407 
408 /*
409  * This class represents a result set: the result of a SELECT statement
410  */
411 class GdaJResultSet {
412 	private static native void initIDs();
413 	private ResultSet rs;
414 
415 	private int ncols;
416 	protected Vector<GdaJValue> col_values;
417 
418 	// Constructor
419 	public GdaJResultSet (ResultSet rs) throws Exception {
420 		this.rs = rs;
421 		if (rs != null) {
422 			ncols = rs.getMetaData().getColumnCount();
423 			col_values = new Vector<GdaJValue> (ncols);
424 		}
425 	}
426 
427 	protected GdaJResultSet (int ncols) {
428 		this.ncols = ncols;
429 		col_values = new Vector<GdaJValue> (ncols);
430 	}
431 
432 	// get result set's meta data
433 	public GdaJResultSetInfos getInfos () throws Exception {
434 		return new GdaJResultSetInfos (rs.getMetaData ());
435 	}
436 
437 	/*
438 	 * declares the expected type for the column
439 	 */
440 	public void declareColumnTypes (long cnc_c_pointer, byte types[]) throws Exception {
441 		int i;
442 		if (types.length != ncols) {
443 			throw new Exception ("Number of types differs from number of columns: expected " + ncols + " got " +
444 					     types.length);
445 		}
446 		for (i = 0; i < ncols; i++) {
447 			GdaJValue cv = GdaJValue.proto_type_to_jvalue (types[i]);
448 			cv.setGdaConnectionPointer (cnc_c_pointer);
449 			cv.setGdaRowColumn (i);
450 			col_values.add (cv);
451 		}
452 		columnTypesDeclared ();
453 	}
454 
455 	protected void columnTypesDeclared () {
456 		// do nothing here, let sub classes do it.
457 	}
458 
459 	/*
460 	 * move iterator to next row and fills row
461 	 * @c_pointer is a pointer to the new GdaRow to fill values
462 	 * Returns: false if no more row available
463 	 */
464 	public boolean fillNextRow (long c_pointer) throws Exception {
465 		int i;
466 		if (! rs.next ())
467 			return false;
468 		for (i = 0; i < ncols; i++) {
469 			GdaJValue cv = col_values.elementAt (i);
470 			cv.setCValue (rs, i, c_pointer);
471 		}
472 		return true;
473 	}
474 
475 	// class initializer
476 	static {
477 		initIDs ();
478 	}
479 }
480 
481 
482 /*
483  * Classes to represent value transport between the JAVA and C worlds:
484  * - JAVA -> C when reading through a resultset
485  * - C -> JAVA when setting the parameter's values of a prepared statement
486  */
487 abstract class GdaJValue {
488 	private static native void initIDs();
489 	protected long cnc_c_pointer = 0;
490 	protected int gda_row_column = -1;
491 	protected boolean convert_lc = false; // true if strings must be converted to lower case (ignored by non String)
492 	protected boolean no_null = false; // converts a NULL value to "" for a string (ignored by non String)
493 
494 	/*
495 	 * @c_pointer points to a GdaRow
496 	 * @col starts at 0
497 	 */
498 	native void setCString (long c_pointer, int col, String string);
499 	native void setCInt (long c_pointer, int col, int i);
500 	native void setCChar (long c_pointer, int col, byte b);
501 	native void setCDouble (long c_pointer, int col, double d);
502 	native void setCFloat (long c_pointer, int col, float f);
503 	native void setCLong (long c_pointer, int col, long l);
504 	native void setCShort (long c_pointer, int col, short s);
505 	native void setCBoolean (long c_pointer, int col, boolean b);
506 	native void setCDate (long c_pointer, int col, int year, int month, int day);
507 	native void setCTime (long c_pointer, int col, int hour, int min, int sec);
508 	native void setCTimestamp (long c_pointer, int col, int year, int month, int day, int hour, int min, int sec);
509 	native void setCBinary (long c_pointer, int col, byte[] data);
510 	native void setCBlob (long c_pointer, int col, long cnc_c_pointer, GdaJBlobOp blob);
511 	native void setCNumeric (long c_pointer, int col, String str, int precision, int scale);
512 
513 	/*
514 	 * @c_pointer points to a GValue, may NOT be 0 ( <=> NULL )
515 	 */
516 	native String getCString (long c_pointer);
517 	native int getCInt (long c_pointer);
518 	native byte getCChar (long c_pointer);
519 	native double getCDouble (long c_pointer);
520 	native float getCFloat (long c_pointer);
521 	native long getCLong (long c_pointer);
522 	native short getCShort (long c_pointer);
523 	native boolean getCBoolean (long c_pointer);
524 	native java.sql.Date getCDate (long c_pointer);
525 	native java.sql.Time getCTime (long c_pointer);
526 	native java.sql.Timestamp getCTimestamp (long c_pointer);
527 	native byte[] getCBinary (long c_pointer);
528 	native GdaInputStream getCBlob (long c_pointer);
529 	native java.math.BigDecimal getCNumeric (long c_pointer);
530 
531 	/*
532 	 * Sets the C GValue's value from @rs at column @col (@c_pointer points to the GdaRow
533 	 * which contains the GValues)
534 	 */
535 	abstract void setCValue (ResultSet rs, int col, long c_pointer) throws Exception; /* @col starts at 0 */
536 
537 	/*
538 	 * Set @ps's parameter at index @index from the C GValue pointed by @c_pointer (if @c_pointer is 0, then
539 	 * the parameter is set to NULL)
540 	 */
541 	abstract void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception; /* @index starts at 0 */
542 
543 	public void setGdaConnectionPointer (long cnc_c_pointer) {
544 		this.cnc_c_pointer = cnc_c_pointer;
545 	}
546 
547 	public void setGdaRowColumn (int col) {
548 		gda_row_column = col;
549 	}
550 
551 
552 	// (year, month, day) -> java.sql.Date
553 	public static java.sql.Date createDate (int year, int month, int day) {
554 		Calendar cal = GregorianCalendar.getInstance();
555 		cal.set (year, month, day);
556 		return new java.sql.Date (cal.getTimeInMillis ());
557 	}
558 
559 	// (hour, minute, sec) -> java.sql.Time
560 	public static java.sql.Time createTime (int hour, int min, int sec) {
561 		Calendar cal = GregorianCalendar.getInstance();
562 		cal.set (0, 0, 0, hour, min, sec);
563 		return new java.sql.Time (cal.getTimeInMillis ());
564 	}
565 
566 	// (year, month, day, hour, minute, sec) -> java.sql.Timestamp
567 	public static java.sql.Timestamp createTimestamp (int year, int month, int day, int hour, int min, int sec) {
568 		Calendar cal = GregorianCalendar.getInstance();
569 		cal.set (year, month, day, hour, min, sec);
570 		return new java.sql.Timestamp (cal.getTimeInMillis ());
571 	}
572 
573 	// types conversions
574 	public static GdaJValue proto_type_to_jvalue (byte type) throws Exception {
575 		GdaJValue cv;
576 		switch (type) {
577 		case 0: /* GDA_TYPE_NULL */
578 			cv = new GdaJNull ();
579 			break;
580 		case 1: /* G_TYPE_STRING */
581 			cv = new GdaJString ();
582 			break;
583 		case 2: /* G_TYPE_INT */
584 			cv = new GdaJInt ();
585 			break;
586 		case 3: /* G_TYPE_CHAR */
587 			cv = new GdaJChar ();
588 			break;
589 		case 4: /* G_TYPE_DOUBLE */
590 			cv = new GdaJDouble ();
591 			break;
592 		case 5: /* G_TYPE_FLOAT */
593 			cv = new GdaJFloat ();
594 			break;
595 		case 6: /* G_TYPE_BOOLEAN */
596 			cv = new GdaJBoolean ();
597 			break;
598 		case 7: /* G_TYPE_DATE */
599 			cv = new GdaJDate ();
600 			break;
601 		case 8: /* GDA_TYPE_TIME */
602 			cv = new GdaJTime ();
603 			break;
604 		case 9: /* GDA_TYPE_TIMESTAMP */
605 			cv = new GdaJTimestamp ();
606 			break;
607 		case 10: /* GDA_TYPE_BINARY */
608 			cv = new GdaJBinary ();
609 			break;
610 		case 11: /* GDA_TYPE_BLOB */
611 			cv = new GdaJBlob ();
612 			break;
613 		case 12: /* G_TYPE_INT64 */
614 			cv = new GdaJInt64 ();
615 			break;
616 		case 13: /* GDA_TYPE_SHORT */
617 			cv = new GdaJShort ();
618 			break;
619 		case 14: /* GDA_TYPE_NUMERIC */
620 			cv = new GdaJNumeric ();
621 			break;
622 
623 		default:
624 			throw new Exception ("Unhandled protocol type " + type);
625 		}
626 		return cv;
627 	}
628 
629 	// Same as gda-jdbc-recordset.c::jdbc_type_to_g_type
630 	// see http://docs.oracle.com/javase/6/docs/api/constant-values.html#java.sql.Types.ARRAY for reference
631 	public static String jdbc_type_to_g_type (int type) {
632 		switch (type) {
633 		case java.sql.Types.VARCHAR:
634 			return "gchararray";
635 		case java.sql.Types.ARRAY:
636 			return "GdaBinary";
637 		case java.sql.Types.BIGINT:
638 			return "gint64";
639 		case java.sql.Types.BINARY:
640 			return "GdaBinary";
641 		case java.sql.Types.BIT:
642 			return "gboolean";
643 		case java.sql.Types.BLOB:
644 			return "GdaBlob";
645 		case java.sql.Types.BOOLEAN:
646 			return "gboolean";
647 		case java.sql.Types.CHAR:
648 			return "gchararray";
649 		case java.sql.Types.CLOB:
650 		case java.sql.Types.DATALINK:
651 			return "GdaBinary";
652 		case java.sql.Types.DATE:
653 			return "GDate";
654 		case java.sql.Types.DECIMAL:
655 			return "GdaNumeric";
656 		case java.sql.Types.DISTINCT:
657 			return "GdaBinary";
658 		case java.sql.Types.DOUBLE:
659 			return "gdouble";
660 		case java.sql.Types.FLOAT:
661 			return "gfloat";
662 		case java.sql.Types.INTEGER:
663 			return "gint";
664 		case java.sql.Types.JAVA_OBJECT:
665 			return "GdaBinary";
666 		case java.sql.Types.LONGNVARCHAR:
667 			return "gchararray";
668 		case java.sql.Types.LONGVARBINARY:
669 			return "GdaBinary";
670 		case java.sql.Types.LONGVARCHAR:
671 			return "gchararray";
672 		case java.sql.Types.NCHAR:
673 			return "gchararray";
674 		case java.sql.Types.NCLOB:
675 			return "GdaBinary";
676 		case java.sql.Types.NULL:
677 			return "GdaNull";
678 		case java.sql.Types.NUMERIC:
679 			return "GdaNumeric";
680 		case java.sql.Types.NVARCHAR:
681 			return "gchararray";
682 		case java.sql.Types.OTHER:
683 			return "GdaBinary";
684 		case java.sql.Types.REAL:
685 			return "gfloat";
686 		case java.sql.Types.REF:
687 			return "GdaBinary";
688 		case java.sql.Types.ROWID:
689 			return "gchararray";
690 		case java.sql.Types.SMALLINT:
691 			return "GdaShort";
692 		case java.sql.Types.SQLXML:
693 			return "gchararray";
694 		case java.sql.Types.STRUCT:
695 			return "GdaBinary";
696 		case java.sql.Types.TIME:
697 			return "GdaTime";
698 		case java.sql.Types.TIMESTAMP:
699 			return "GdaTimestamp";
700 		case java.sql.Types.TINYINT:
701 			return "gchar";
702 		case java.sql.Types.VARBINARY:
703 		default:
704 			return "GdaBinary";
705 		}
706 	}
707 
708 	public static String toLower (String string) {
709 		String s2 = string.toUpperCase();
710 		if (s2 == string)
711 			return string.toLowerCase();
712 		else
713 			return string;
714 	}
715 
716 	// class initializer
717 	static {
718 		initIDs ();
719 	}
720 }
721 
722 class GdaJString extends GdaJValue {
723 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
724 		String string;
725 		string = rs.getString (col + 1);
726 		if (string == null) {
727 			if (no_null)
728 				setCString (c_pointer, gda_row_column, "");
729 		}
730 		else {
731 			if (convert_lc) {
732 				String s2 = string.toUpperCase();
733 				if (s2 == string)
734 					string = string.toLowerCase();
735 			}
736 			setCString (c_pointer, gda_row_column, string);
737 		}
738 	}
739 
740 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
741 		if (c_pointer == 0)
742 			ps.setNull (index + 1, java.sql.Types.VARCHAR);
743 		else {
744 			String string;
745 			string = getCString (c_pointer);
746 			ps.setString (index + 1, string);
747 		}
748 	}
749 }
750 
751 class GdaJInt extends GdaJValue {
752 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
753 		int i;
754 		i = rs.getInt (col + 1);
755 		if ((i != 0) || !rs.wasNull ())
756 			setCInt (c_pointer, gda_row_column, i);
757 	}
758 
759 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
760 		if (c_pointer == 0)
761 			ps.setNull (index + 1, java.sql.Types.INTEGER);
762 		else {
763 			int i;
764 			i = getCInt (c_pointer);
765 			ps.setInt (index + 1, i);
766 		}
767 	}
768 }
769 
770 class GdaJDouble extends GdaJValue {
771 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
772 		double d;
773 		d = rs.getDouble (col + 1);
774 		if ((d != 0) || !rs.wasNull ())
775 			setCDouble (c_pointer, gda_row_column, d);
776 	}
777 
778 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
779 		if (c_pointer == 0)
780 			ps.setNull (index + 1, java.sql.Types.DOUBLE);
781 		else {
782 			double d;
783 			d = getCDouble (c_pointer);
784 			ps.setDouble (index + 1, d);
785 		}
786 	}
787 }
788 
789 class GdaJFloat extends GdaJValue {
790 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
791 		float f;
792 		f = rs.getFloat (col + 1);
793 		if ((f != 0) || !rs.wasNull ())
794 			setCFloat (c_pointer, gda_row_column, f);
795 	}
796 
797 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
798 		if (c_pointer == 0)
799 			ps.setNull (index + 1, java.sql.Types.FLOAT);
800 		else {
801 			float f;
802 			f = getCFloat (c_pointer);
803 			ps.setFloat (index + 1, f);
804 		}
805 	}
806 }
807 
808 class GdaJBoolean extends GdaJValue {
809 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
810 		boolean b;
811 		b = rs.getBoolean (col + 1);
812 		if (b || !rs.wasNull ())
813 			setCBoolean (c_pointer, gda_row_column, b);
814 	}
815 
816 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
817 		if (c_pointer == 0)
818 			ps.setNull (index + 1, java.sql.Types.BOOLEAN);
819 		else {
820 			boolean b;
821 			b = getCBoolean (c_pointer);
822 			ps.setBoolean (index + 1, b);
823 		}
824 	}
825 }
826 
827 class GdaJDate extends GdaJValue {
828 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
829 		java.sql.Date date;
830 		date = rs.getDate (col + 1);
831 		if (date != null) {
832 			Calendar cal = GregorianCalendar.getInstance();
833 			cal.setTime (date);
834 			setCDate (c_pointer, gda_row_column, cal.get (Calendar.YEAR),
835 				  cal.get (Calendar.MONTH) + 1, cal.get (Calendar.DAY_OF_MONTH));
836 		}
837 	}
838 
839 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
840 		if (c_pointer == 0)
841 			ps.setNull (index + 1, java.sql.Types.DATE);
842 		else {
843 			java.sql.Date d;
844 			d = getCDate (c_pointer);
845 			ps.setDate (index + 1, d);
846 		}
847 	}
848 }
849 
850 class GdaJTime extends GdaJValue {
851 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
852 		java.sql.Time time;
853 		time = rs.getTime (col + 1);
854 		if (time != null) {
855 			Calendar cal = GregorianCalendar.getInstance();
856 			cal.setTime (time);
857 			setCTime (c_pointer, gda_row_column, cal.get (Calendar.HOUR_OF_DAY),
858 				  cal.get (Calendar.MINUTE) + 1, cal.get (Calendar.SECOND));
859 		}
860 	}
861 
862 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
863 		if (c_pointer == 0)
864 			ps.setNull (index + 1, java.sql.Types.TIME);
865 		else {
866 			java.sql.Time t;
867 			t = getCTime (c_pointer);
868 			ps.setTime (index + 1, t);
869 		}
870 	}
871 }
872 
873 class GdaJTimestamp extends GdaJValue {
874 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
875 		java.sql.Timestamp ts;
876 		ts = rs.getTimestamp (col + 1);
877 		if (ts != null) {
878 			Calendar cal = GregorianCalendar.getInstance();
879 			cal.setTime (ts);
880 			setCTimestamp (c_pointer, gda_row_column, cal.get (Calendar.YEAR),
881 				       cal.get (Calendar.MONTH) + 1, cal.get (Calendar.DAY_OF_MONTH),
882 				       cal.get (Calendar.HOUR_OF_DAY),
883 				       cal.get (Calendar.MINUTE) + 1, cal.get (Calendar.SECOND));
884 		}
885 	}
886 
887 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
888 		if (c_pointer == 0)
889 			ps.setNull (index + 1, java.sql.Types.TIMESTAMP);
890 		else {
891 			java.sql.Timestamp ts;
892 			ts = getCTimestamp (c_pointer);
893 			ps.setTimestamp (index + 1, ts);
894 		}
895 	}
896 }
897 
898 class GdaJBinary extends GdaJValue {
899 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
900 		byte[] bin;
901 		bin = rs.getBytes (col + 1);
902 		if (bin != null) {
903 			setCBinary (c_pointer, gda_row_column, bin);
904 		}
905 	}
906 
907 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
908 		if (c_pointer == 0)
909 			ps.setNull (index + 1, java.sql.Types.BINARY);
910 		else {
911 			byte[] bin;
912 			bin = getCBinary (c_pointer);
913 			ps.setBytes (index + 1, bin);
914 		}
915 	}
916 }
917 
918 class GdaJBlob extends GdaJValue {
919 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
920 		java.sql.Blob blob;
921 		blob = rs.getBlob (col + 1);
922 		if (blob != null) {
923 			setCBlob (c_pointer, gda_row_column, cnc_c_pointer, new GdaJBlobOp (blob));
924 		}
925 	}
926 
927 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
928 		if (c_pointer == 0)
929 			ps.setNull (index + 1, java.sql.Types.BLOB);
930 		else {
931 			GdaInputStream is;
932 			is = getCBlob (c_pointer);
933 			ps.setBinaryStream (index + 1, is, (int) is.size);
934 		}
935 	}
936 }
937 
938 class GdaJChar extends GdaJValue {
939 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
940 		Byte b;
941 		b = rs.getByte (col + 1);
942 		if ((b != 0) || !rs.wasNull ())
943 			setCChar (c_pointer, gda_row_column, b);
944 	}
945 
946 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
947 		if (c_pointer == 0)
948 			ps.setNull (index + 1, java.sql.Types.CHAR);
949 		else {
950 			byte b;
951 			b = getCChar (c_pointer);
952 			ps.setByte (index + 1, b);
953 		}
954 	}
955 }
956 
957 class GdaJInt64 extends GdaJValue {
958 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
959 		long l;
960 		l = rs.getLong (col + 1);
961 		if ((l != 0) || !rs.wasNull ())
962 			setCLong (c_pointer, gda_row_column, l);
963 	}
964 
965 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
966 		if (c_pointer == 0)
967 			ps.setNull (index + 1, java.sql.Types.BIGINT);
968 		else {
969 			long l;
970 			l = getCLong (c_pointer);
971 			ps.setLong (index + 1, l);
972 		}
973 	}
974 }
975 
976 class GdaJShort extends GdaJValue {
977 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
978 		short s;
979 		s = rs.getShort (col + 1);
980 		if ((s != 0) || !rs.wasNull ())
981 			setCShort (c_pointer, gda_row_column, s);
982 	}
983 
984 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
985 		if (c_pointer == 0)
986 			ps.setNull (index + 1, java.sql.Types.SMALLINT);
987 		else {
988 			short s;
989 			s = getCShort (c_pointer);
990 			ps.setShort (index + 1, s);
991 		}
992 	}
993 }
994 
995 class GdaJNumeric extends GdaJValue {
996 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
997 		java.math.BigDecimal bd;
998 
999 		bd = rs.getBigDecimal (col + 1);
1000 		if (bd != null) {
1001 			setCNumeric (c_pointer, gda_row_column, bd.toString(), bd.precision(), bd.scale());
1002 		}
1003 	}
1004 
1005 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
1006 		if (c_pointer == 0)
1007 			ps.setNull (index + 1, java.sql.Types.NUMERIC);
1008 		else {
1009 			java.math.BigDecimal bd;
1010 			bd = getCNumeric (c_pointer);
1011 			ps.setBigDecimal (index + 1, bd);
1012 		}
1013 	}
1014 }
1015 
1016 class GdaJNull extends GdaJValue {
1017 	void setCValue (ResultSet rs, int col, long c_pointer) throws Exception {
1018 		// nothing to do
1019 	}
1020 
1021 	void getCValue (PreparedStatement ps, int index, long c_pointer) throws Exception {
1022 		ParameterMetaData md;
1023 		int i = index + 1;
1024 		md = ps.getParameterMetaData ();
1025 		ps.setNull (i, md.getParameterType (i));
1026 	}
1027 }
1028 
1029 
1030 /*
1031  * This class represents a Blob, used by the GdaBlobOp object
1032  * to transfer data from JDBC -> C
1033  */
1034 class GdaJBlobOp {
1035 	private static native void initIDs();
1036 	private Blob blob;
1037 
1038 	// Constructor
1039 	public GdaJBlobOp (Blob blob) {
1040 		this.blob = blob;
1041 	}
1042 
1043 	// Get Blob's length
1044 	public long length () throws Exception {
1045 		return blob.length ();
1046 	}
1047 
1048 	// Read data
1049 	public byte[] read (long offset, int len) throws Exception {
1050 		return blob.getBytes (offset + 1, len);
1051 	}
1052 
1053 	// Write data
1054 	public int write (long offset, byte[] data) throws Exception {
1055 		int i;
1056 		i = blob.setBytes (offset, data);
1057 		if (i != data.length) {
1058 			throw new Exception ("Could not write the complete BLOB");
1059 		}
1060 		return i;
1061 	}
1062 
1063 	// class initializer
1064 	static {
1065 		initIDs ();
1066 	}
1067 }
1068 
1069 /*
1070  * an InputStream which reads from a GdaBlob, for
1071  * data transfer C -> JDBC
1072  *
1073  * Note: the @size attribute is not used here since the PreparedStatement.setBinaryStream() already
1074  * contains the blob's size and won't try to get more. If this InputStream were to be used
1075  * somewhere else, then @size would have to be handled correctly.
1076  */
1077 class GdaInputStream extends InputStream {
1078 	static int chunk_size = 65536; // 64kb chunks
1079 
1080 	private static native void initIDs();
1081 	private native byte[] readByteData (long gda_blob_pointer, long offset, long size);
1082 	public long size;
1083 	private long current_pos;
1084 	private long gda_blob_pointer;
1085 
1086 	private InputStream ist = null; // delegate
1087 
1088 	// Constructor
1089 	public GdaInputStream (long gda_blob_pointer, long size) {
1090 		current_pos = 0;
1091 		this.size = size;
1092 		this.gda_blob_pointer = gda_blob_pointer;
1093 	}
1094 
1095 	// Constructor
1096 	public GdaInputStream (byte[] data) {
1097 		this.gda_blob_pointer = 0;
1098 		ist = new ByteArrayInputStream (data);
1099 	}
1100 
1101 	// abstract class implementation
1102 	public int read() throws IOException {
1103 		int i = -1;
1104 		if (ist != null)
1105 			i = ist.read ();
1106 
1107 		if ((i == -1) && (gda_blob_pointer != 0)) {
1108 			byte[] data;
1109 			data = readByteData (gda_blob_pointer, current_pos, chunk_size);
1110 			current_pos += data.length;
1111 			ist = new ByteArrayInputStream (data);
1112 			i = ist.read ();
1113 		}
1114 		return i;
1115 	}
1116 
1117 	// class initializer
1118 	static {
1119 		initIDs ();
1120 	}
1121 }
1122