diff --git a/src/interfaces/jdbc/Makefile b/src/interfaces/jdbc/Makefile index 928aafae5ef51781fd534837e9d5b848b549cba4..1065dcec4fbd93b15c1f244b5eb3b696ce983e0b 100644 --- a/src/interfaces/jdbc/Makefile +++ b/src/interfaces/jdbc/Makefile @@ -4,7 +4,7 @@ # Makefile for Java JDBC interface # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/interfaces/jdbc/Attic/Makefile,v 1.3 1998/01/11 21:14:29 scrappy Exp $ +# $Header: /cvsroot/pgsql/src/interfaces/jdbc/Attic/Makefile,v 1.4 1998/01/13 02:19:10 scrappy Exp $ # #------------------------------------------------------------------------- diff --git a/src/interfaces/jdbc/example/ImageViewer.java b/src/interfaces/jdbc/example/ImageViewer.java new file mode 100644 index 0000000000000000000000000000000000000000..9e93d83c7c3a4356195f0907d2d043bb9aa5836a --- /dev/null +++ b/src/interfaces/jdbc/example/ImageViewer.java @@ -0,0 +1,377 @@ +package example; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.sql.*; +import postgresql.largeobject.*; + +/** + * This example is a small application that stores and displays images + * held on a postgresql database. + * + * Before running this application, you need to create a database, and + * on the first time you run it, select "Initialise" in the "PostgreSQL" + * menu. + * + * Important note: You will notice we import the postgresql.largeobject + * package, but don't import the postgresql package. The reason for this is + * that importing postgresql can confuse javac (we have conflicting class names + * in postgresql.* and java.sql.*). This doesn't cause any problems, as long + * as no code imports postgresql. + * + * Under normal circumstances, code using any jdbc driver only needs to import + * java.sql, so this isn't a problem. + * + * It's only if you use the non jdbc facilities, do you have to take this into + * account. + * + */ + +public class ImageViewer implements ItemListener +{ + Connection db; + Statement stat; + LargeObjectManager lom; + Frame frame; + Label label; // Label used to display the current name + List list; // The list of available images + imageCanvas canvas; // Canvas used to display the image + String currentImage; // The current images name + + // This is a simple component to display our image + public class imageCanvas extends Canvas + { + private Image image; + + public imageCanvas() + { + image=null; + } + + public void setImage(Image img) + { + image=img; + repaint(); + } + + // This defines our minimum size + public Dimension getMinimumSize() + { + return new Dimension(400,400); + } + + public Dimension getPreferedSize() + { + return getMinimumSize(); + } + + public void update(Graphics g) + { + paint(g); + } + + public void paint(Graphics g) + { + g.setColor(Color.gray); + g.fillRect(0,0,getSize().width,getSize().height); + + if(image!=null) + g.drawImage(image,0,0,this); + } + } + + public ImageViewer(Frame f,String url,String user,String password) throws ClassNotFoundException, FileNotFoundException, IOException, SQLException + { + frame = f; + + MenuBar mb = new MenuBar(); + Menu m; + MenuItem i; + + f.setMenuBar(mb); + mb.add(m = new Menu("PostgreSQL")); + m.add(i= new MenuItem("Initialise")); + i.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ImageViewer.this.init(); + } + }); + + m.add(i= new MenuItem("Exit")); + ActionListener exitListener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + ImageViewer.this.close(); + } + }; + m.addActionListener(exitListener); + + mb.add(m = new Menu("Image")); + m.add(i= new MenuItem("Import")); + ActionListener importListener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + ImageViewer.this.importImage(); + } + }; + i.addActionListener(importListener); + + m.add(i= new MenuItem("Remove")); + ActionListener removeListener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + ImageViewer.this.removeImage(); + } + }; + i.addActionListener(removeListener); + + // To the north is a label used to display the current images name + f.add("North",label = new Label()); + + // We have a panel to the south of the frame containing the controls + Panel p = new Panel(); + p.setLayout(new FlowLayout()); + Button b; + p.add(b=new Button("Refresh List")); + b.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + ImageViewer.this.refreshList(); + } + }); + p.add(b=new Button("Import new image")); + b.addActionListener(importListener); + p.add(b=new Button("Remove image")); + b.addActionListener(removeListener); + p.add(b=new Button("Quit")); + b.addActionListener(exitListener); + f.add("South",p); + + // And a panel to the west containing the list of available images + f.add("West",list=new List()); + list.addItemListener(this); + + // Finally the centre contains our image + f.add("Center",canvas = new imageCanvas()); + + // Load the driver + Class.forName("postgresql.Driver"); + + // Connect to database + System.out.println("Connecting to Database URL = " + url); + db = DriverManager.getConnection(url, user, password); + + // Create a statement + stat = db.createStatement(); + + // Also, get the LargeObjectManager for this connection + lom = ((postgresql.Connection)db).getLargeObjectAPI(); + + // Now refresh the image selection list + refreshList(); + } + + + /** + * This method initialises the database by creating a table that contains + * the image names, and Large Object OID's + */ + public void init() + { + try { + stat.executeUpdate("create table images (imgname name,imgoid oid)"); + label.setText("Initialised database"); + } catch(SQLException ex) { + label.setText(ex.toString()); + } + } + + /** + * This closes the connection, and ends the application + */ + public void close() + { + try { + db.close(); + } catch(SQLException ex) { + System.err.println(ex.toString()); + } + System.exit(0); + } + + /** + * This imports an image into the database. + * + * This is the most efficient method, using the large object extension. + */ + public void importImage() + { + FileDialog d = new FileDialog(frame,"Import Image",FileDialog.LOAD); + d.setVisible(true); + String name = d.getFile(); + String dir = d.getDirectory(); + d.dispose(); + + // Now the real import stuff + if(name!=null && dir!=null) { + try { + System.out.println("Importing file"); + // A temporary buffer - this can be as large as you like + byte buf[] = new byte[2048]; + + // Open the file + System.out.println("Opening file "+dir+"/"+name); + FileInputStream fis = new FileInputStream(new File(dir,name)); + + // Gain access to large objects + System.out.println("Gaining LOAPI"); + + // Now create the large object + System.out.println("creating blob"); + int oid = lom.create(); + + System.out.println("Opening "+oid); + LargeObject blob = lom.open(oid); + + // Now copy the file into the object. + // + // Note: we dont use write(buf), as the last block is rarely the same + // size as our buffer, so we have to use the amount read. + System.out.println("Importing file"); + int s,t=0; + while((s=fis.read(buf,0,buf.length))>0) { + System.out.println("Block s="+s+" t="+t);t+=s; + blob.write(buf,0,s); + } + + // Close the object + System.out.println("Closing blob"); + blob.close(); + + // Now store the entry into the table + stat.executeUpdate("insert into images values ('"+name+"',"+oid+")"); + stat.close(); + + // Finally refresh the names list, and display the current image + refreshList(); + displayImage(name); + } catch(Exception ex) { + label.setText(ex.toString()); + } + } + } + + /** + * This refreshes the list of available images + */ + public void refreshList() + { + try { + // First, we'll run a query, retrieving all of the image names + ResultSet rs = stat.executeQuery("select imgname from images order by imgname"); + if(rs!=null) { + list.removeAll(); + while(rs.next()) + list.addItem(rs.getString(1)); + rs.close(); + } + } catch(SQLException ex) { + label.setText(ex.toString()+" Have you initialised the database?"); + } + } + + /** + * This removes an image from the database + * + * Note: With postgresql, this is the only way of deleting a large object + * using Java. + */ + public void removeImage() + { + try { + // Delete any large objects for the current name + ResultSet rs = stat.executeQuery("select imgoid from images where imgname='"+currentImage+"'"); + if(rs!=null) { + // Even though there should only be one image, we still have to + // cycle through the ResultSet + while(rs.next()) { + System.out.println("Got oid "+rs.getInt(1)); + lom.delete(rs.getInt(1)); + System.out.println("Import complete"); + } + } + rs.close(); + + // Finally delete any entries for that name + stat.executeUpdate("delete from images where imgname='"+currentImage+"'"); + + label.setText(currentImage+" deleted"); + currentImage=null; + refreshList(); + } catch(SQLException ex) { + label.setText(ex.toString()); + } + } + + /** + * This displays an image from the database. + * + * For images, this is the easiest method. + */ + public void displayImage(String name) + { + try { + System.out.println("Selecting oid for "+name); + ResultSet rs = stat.executeQuery("select imgoid from images where imgname='"+name+"'"); + if(rs!=null) { + // Even though there should only be one image, we still have to + // cycle through the ResultSet + while(rs.next()) { + System.out.println("Got oid "+rs.getInt(1)); + canvas.setImage(canvas.getToolkit().createImage(rs.getBytes(1))); + System.out.println("Import complete"); + label.setText(currentImage = name); + } + } + rs.close(); + } catch(SQLException ex) { + label.setText(ex.toString()); + } + } + + public void itemStateChanged(ItemEvent e) { + displayImage(list.getItem(((Integer)e.getItem()).intValue())); + } + + /** + * This is the command line instructions + */ + public static void instructions() + { + System.err.println("java example.ImageViewer jdbc-url user password"); + System.err.println("\nExamples:\n"); + System.err.println("java -Djdbc.driver=postgresql.Driver example.ImageViewer jdbc:postgresql:test postgres password\n"); + + System.err.println("This example tests the binary large object api of the driver.\nBasically, it will allow you to store and view images held in the database."); + System.err.println("Note: If you are running this for the first time on a particular database,\nyou have to select \"Initialise\" in the \"PostgreSQL\" menu.\nThis will create a table used to store image names."); + } + + /** + * This is the application entry point + */ + public static void main(String args[]) + { + if(args.length!=3) { + instructions(); + System.exit(1); + } + + try { + Frame frame = new Frame("PostgreSQL ImageViewer v6.3 rev 1"); + frame.setLayout(new BorderLayout()); + ImageViewer viewer = new ImageViewer(frame,args[0],args[1],args[2]); + frame.pack(); + frame.setVisible(true); + } catch(Exception ex) { + System.err.println("Exception caught.\n"+ex); + ex.printStackTrace(); + } + } +} diff --git a/src/interfaces/jdbc/example/basic.java b/src/interfaces/jdbc/example/basic.java new file mode 100644 index 0000000000000000000000000000000000000000..1ea20d1d932ff5a5911b6279ba45c40b81ed8893 --- /dev/null +++ b/src/interfaces/jdbc/example/basic.java @@ -0,0 +1,172 @@ +package example; + +import java.io.*; +import java.sql.*; +import java.text.*; + +/** + * This example tests the basic components of the JDBC driver, and shows + * how even the simplest of queries can be implemented. + * + * To use this example, you need a database to be in existence. This example + * will create a table called basic. + * + */ + +public class basic +{ + Connection db; // The connection to the database + Statement st; // Our statement to run queries with + + public basic(String args[]) throws ClassNotFoundException, FileNotFoundException, IOException, SQLException + { + String url = args[0]; + String usr = args[1]; + String pwd = args[2]; + + // Load the driver + Class.forName("postgresql.Driver"); + + // Connect to database + System.out.println("Connecting to Database URL = " + url); + db = DriverManager.getConnection(url, usr, pwd); + + System.out.println("Connected...Now creating a statement"); + st = db.createStatement(); + + // Clean up the database (in case we failed earlier) then initialise + cleanup(); + + // Now run tests using JDBC methods + doexample(); + + // Clean up the database + cleanup(); + + // Finally close the database + System.out.println("Now closing the connection"); + st.close(); + db.close(); + + } + + /** + * This drops the table (if it existed). No errors are reported. + */ + public void cleanup() + { + try { + st.executeUpdate("drop table basic"); + } catch(Exception ex) { + // We ignore any errors here + } + } + + /** + * This performs the example + */ + public void doexample() throws SQLException + { + System.out.println("\nRunning tests:"); + + // First we need a table to store data in + st.executeUpdate("create table basic (a int2, b int2)"); + + // Now insert some data, using the Statement + st.executeUpdate("insert into basic values (1,1)"); + st.executeUpdate("insert into basic values (2,1)"); + st.executeUpdate("insert into basic values (3,1)"); + + // For large inserts, a PreparedStatement is more efficient, because it + // supports the idea of precompiling the SQL statement, and to store + // directly, a Java object into any column. PostgreSQL doesnt support + // precompiling, but does support setting a column to the value of a + // Java object (like Date, String, etc). + // + // Also, this is the only way of writing dates in a datestyle independent + // manner. (DateStyles are PostgreSQL's way of handling different methods + // of representing dates in the Date data type.) + PreparedStatement ps = db.prepareStatement("insert into basic values (?,?)"); + for(int i=2;i<5;i++) { + ps.setInt(1,4); // "column a" = 5 + ps.setInt(2,i); // "column b" = i + ps.executeUpdate(); // executeUpdate because insert returns no data + } + ps.close(); // Always close when we are done with it + + // Finally perform a query on the table + System.out.println("performing a query"); + ResultSet rs = st.executeQuery("select a, b from basic"); + if(rs!=null) { + // Now we run through the result set, printing out the result. + // Note, we must call .next() before attempting to read any results + while(rs.next()) { + int a = rs.getInt("a"); // This shows how to get the value by name + int b = rs.getInt(2); // This shows how to get the value by column + System.out.println(" a="+a+" b="+b); + } + rs.close(); // again, you must close the result when done + } + + // Now run the query again, showing a more efficient way of getting the + // result if you don't know what column number a value is in + System.out.println("performing another query"); + rs = st.executeQuery("select * from basic where b>1"); + if(rs!=null) { + // First find out the column numbers. + // + // It's best to do this here, as calling the methods with the column + // numbers actually performs this call each time they are called. This + // really speeds things up on large queries. + // + int col_a = rs.findColumn("a"); + int col_b = rs.findColumn("b"); + + // Now we run through the result set, printing out the result. + // Again, we must call .next() before attempting to read any results + while(rs.next()) { + int a = rs.getInt(col_a); // This shows how to get the value by name + int b = rs.getInt(col_b); // This shows how to get the value by column + System.out.println(" a="+a+" b="+b); + } + rs.close(); // again, you must close the result when done + } + + // The last thing to do is to drop the table. This is done in the + // cleanup() method. + } + + /** + * Display some instructions on how to run the example + */ + public static void instructions() + { + System.out.println("\nThis example tests the basic components of the JDBC driver, demonstrating\nhow to build simple queries in java.\n"); + System.out.println("Useage:\n java example.basic jdbc:postgresql:database user password [debug]\n\nThe debug field can be anything. It's presence will enable DriverManager's\ndebug trace. Unless you want to see screens of items, don't put anything in\nhere."); + System.exit(1); + } + + /** + * This little lot starts the test + */ + public static void main(String args[]) + { + System.out.println("PostgreSQL basic test v6.3 rev 1\n"); + + if(args.length<3) + instructions(); + + // This line outputs debug information to stderr. To enable this, simply + // add an extra parameter to the command line + if(args.length>3) + DriverManager.setLogStream(System.err); + + // Now run the tests + try { + basic test = new basic(args); + } catch(Exception ex) { + System.err.println("Exception caught.\n"+ex); + ex.printStackTrace(); + } + } +} diff --git a/src/interfaces/jdbc/example/blobtest.java b/src/interfaces/jdbc/example/blobtest.java new file mode 100644 index 0000000000000000000000000000000000000000..9ef64183ef7768450651e33a748ec5a52c4befbb --- /dev/null +++ b/src/interfaces/jdbc/example/blobtest.java @@ -0,0 +1,194 @@ +package example; + +import java.io.*; +import java.sql.*; +import postgresql.largeobject.*; + +/** + * This test attempts to create a blob in the database, then to read + * it back. + * + * Important note: You will notice we import the postgresql.largeobject + * package, but don't import the postgresql package. The reason for this is + * that importing postgresql can confuse javac (we have conflicting class names + * in postgresql.* and java.sql.*). This doesn't cause any problems, as long + * as no code imports postgresql. + * + * Under normal circumstances, code using any jdbc driver only needs to import + * java.sql, so this isn't a problem. + * + * It's only if you use the non jdbc facilities, do you have to take this into + * account. + * + */ + +public class blobtest +{ + Connection db; + Statement s; + LargeObjectManager lobj; + + public blobtest(String args[]) throws ClassNotFoundException, FileNotFoundException, IOException, SQLException + { + String url = args[0]; + String usr = args[1]; + String pwd = args[2]; + + // Load the driver + Class.forName("postgresql.Driver"); + + // Connect to database + System.out.println("Connecting to Database URL = " + url); + db = DriverManager.getConnection(url, usr, pwd); + System.out.println("Connected...Now creating a statement"); + s = db.createStatement(); + + // Now run tests using postgresql's own Large object api + // NOTE: The methods shown in this example are _NOT_ JDBC, but are + // an implementation of the calls found in libpq. Unless you need to + // use this functionality, look at the jdbc tests on how to access blobs. + ownapi(); + + // Now run tests using JDBC methods + //jdbcapi(db,s); + + // Finally close the database + System.out.println("Now closing the connection"); + s.close(); + db.close(); + + } + + /** + * Now this is an extension to JDBC, unique to postgresql. Here we fetch + * an PGlobj object, which provides us with access to postgresql's + * large object api. + */ + public void ownapi() throws FileNotFoundException, IOException, SQLException + { + System.out.println("\n----------------------------------------------------------------------\nTesting postgresql large object api\n"); + + // Internally, the driver provides JDBC compliant methods to access large + // objects, however the unique methods available to postgresql makes things + System.out.println("Gaining access to large object api"); + lobj = ((postgresql.Connection)db).getLargeObjectAPI(); + + int oid = ownapi_test1(); + ownapi_test2(oid); + //ownapi_test3(oid); + System.out.println("\n\nOID="+oid); + } + + private int ownapi_test1() throws FileNotFoundException, IOException, SQLException + { + System.out.println("Test 1 Creating a large object\n"); + + // Ok, test 1 is to create a large object. To do this, we use the create + // method. + System.out.println("Creating a large object"); + int oid = lobj.create(LargeObjectManager.READ|LargeObjectManager.WRITE); + DriverManager.println("got large object oid="+oid); + + LargeObject obj = lobj.open(oid,LargeObjectManager.WRITE); + DriverManager.println("got large object obj="+obj); + + // Now open a test file - this class will do + System.out.println("Opening test source object"); + FileInputStream fis = new FileInputStream("example/blobtest.java"); + + // copy the data + System.out.println("Copying file to large object"); + byte buf[] = new byte[2048]; + int s,tl=0; + while((s=fis.read(buf,0,2048))>0) { + System.out.println("Block size="+s+" offset="+tl); + //System.out.write(buf); + obj.write(buf,0,s); + tl+=s; + } + DriverManager.println("Copied "+tl+" bytes"); + + // Close the object + System.out.println("Closing object"); + obj.close(); + + return oid; + } + + private void ownapi_test2(int oid) throws FileNotFoundException, IOException, SQLException + { + System.out.println("Test 2 Reading a large object and save as a file\n"); + + // Now open the large object + System.out.println("Opening large object "+oid); + LargeObject obj = lobj.open(oid,LargeObjectManager.READ); + DriverManager.println("got obj="+obj); + + // Now open a test file - this class will do + System.out.println("Opening test destination object"); + FileOutputStream fos = new FileOutputStream("blob_testoutput"); + + // copy the data + System.out.println("Copying large object to file"); + byte buf[] = new byte[512]; + int s=obj.size(); + int tl=0; + while(s>0) { + int rs = buf.length; + if(s3) + DriverManager.setLogStream(System.err); + + // Now run the tests + try { + blobtest test = new blobtest(args); + } catch(Exception ex) { + System.err.println("Exception caught.\n"+ex); + ex.printStackTrace(); + } + } +} diff --git a/src/interfaces/jdbc/example/datestyle.java b/src/interfaces/jdbc/example/datestyle.java new file mode 100644 index 0000000000000000000000000000000000000000..eb412a7bd8916cc8275f076b277ccc17347dbc97 --- /dev/null +++ b/src/interfaces/jdbc/example/datestyle.java @@ -0,0 +1,181 @@ +package example; + +import java.io.*; +import java.sql.*; +import java.text.*; + +/** + * This example tests the various date styles that are available to postgresql. + * + * To use this example, you need a database to be in existence. This example + * will create a table called datestyle. + * + */ + +public class datestyle +{ + Connection db; // The connection to the database + Statement st; // Our statement to run queries with + + // This is our standard to compare results with. + java.sql.Date standard; + + // This is a list of the available date styles including variants. + // These have to match what the "set datestyle" statement accepts. + String styles[] = { + "postgres,european", + "postgres,us", + "iso", // iso has no variants - us/european has no affect + "sql,european", + "sql,us", + "german" // german has no variants - us/european has no affect + }; + + public datestyle(String args[]) throws ClassNotFoundException, FileNotFoundException, IOException, SQLException + { + String url = args[0]; + String usr = args[1]; + String pwd = args[2]; + + // Load the driver + Class.forName("postgresql.Driver"); + + // Connect to database + System.out.println("Connecting to Database URL = " + url); + db = DriverManager.getConnection(url, usr, pwd); + + System.out.println("Connected...Now creating a statement"); + st = db.createStatement(); + + // Clean up the database (in case we failed earlier) then initialise + cleanup(); + init(); + + // Now run tests using JDBC methods + doexample(); + + // Clean up the database + cleanup(); + + // Finally close the database + System.out.println("Now closing the connection"); + st.close(); + db.close(); + + } + + /** + * This drops the table (if it existed). No errors are reported. + */ + public void cleanup() + { + try { + st.executeUpdate("drop table datestyle"); + } catch(Exception ex) { + // We ignore any errors here + } + } + + /** + * This initialises the database for this example + */ + public void init() throws SQLException + { + // Create a table holding a single date + st.executeUpdate("create table datestyle (dt date)"); + + // Now create our standard date for the test. + // + // NB: each component of the date should be different, otherwise the tests + // will not be valid. + // + // NB: January = 0 here + // + standard = new java.sql.Date(98,0,8); + + // Now store the result. + // + // This is an example of how to set a date in a date style independent way. + // The only way of doing this is by using a PreparedStatement. + // + PreparedStatement ps = db.prepareStatement("insert into datestyle values (?)"); + ps.setDate(1,standard); + ps.executeUpdate(); + ps.close(); + + } + + /** + * This performs the example + */ + public void doexample() throws SQLException + { + System.out.println("\nRunning tests:"); + + for(int i=0;i3) + DriverManager.setLogStream(System.err); + + // Now run the tests + try { + datestyle test = new datestyle(args); + } catch(Exception ex) { + System.err.println("Exception caught.\n"+ex); + ex.printStackTrace(); + } + } +} diff --git a/src/interfaces/jdbc/example/psql.java b/src/interfaces/jdbc/example/psql.java new file mode 100644 index 0000000000000000000000000000000000000000..adad24f4d08e4eedeec8c0eadf3febc76a9af3b1 --- /dev/null +++ b/src/interfaces/jdbc/example/psql.java @@ -0,0 +1,210 @@ +package example; + +import java.io.*; +import java.sql.*; +import java.text.*; + +/** + * This example application demonstrates some of the drivers other features + * by implementing a simple psql replacement in Java. + * + */ + +public class psql +{ + Connection db; // The connection to the database + Statement st; // Our statement to run queries with + DatabaseMetaData dbmd; // This defines the structure of the database + + public psql(String args[]) throws ClassNotFoundException, FileNotFoundException, IOException, SQLException + { + String url = args[0]; + String usr = args[1]; + String pwd = args[2]; + + // Load the driver + Class.forName("postgresql.Driver"); + + // Connect to database + System.out.println("Connecting to Database URL = " + url); + db = DriverManager.getConnection(url, usr, pwd); + + dbmd = db.getMetaData(); + st = db.createStatement(); + + // This prints the backend's version + System.out.println("Connected to "+dbmd.getDatabaseProductName()+" "+dbmd.getDatabaseProductVersion()); + + System.out.println(); + + // This provides us the means of reading from stdin + StreamTokenizer input = new StreamTokenizer(new InputStreamReader(System.in)); + input.resetSyntax(); + input.slashSlashComments(true); // allow // as a comment delimiter + input.eolIsSignificant(false); // treat eol's as spaces + input.wordChars(32,126); + input.whitespaceChars(59,59); + input.quoteChar(39); + + // Now the main loop. + int tt=0,lineno=1; + while(tt!=StreamTokenizer.TT_EOF) { + System.out.print("["+lineno+"] "); + System.out.flush(); + + // Here, we trap SQLException so they don't terminate the application + try { + if((tt=input.nextToken())==StreamTokenizer.TT_WORD) { + processLine(input.sval); + lineno++; + } + } catch(SQLException ex) { + System.out.println(ex.getMessage()); + } + } + + System.out.println("Now closing the connection"); + st.close(); + db.close(); + + } + + /** + * This processes a statement + */ + public void processLine(String line) throws SQLException + { + if(line.startsWith("\\")) { + processSlashCommand(line); + return; + } + + boolean type = st.execute(line); + boolean loop=true; + while(loop) { + if(type) { + // A ResultSet was returned + ResultSet rs=st.getResultSet(); + displayResult(rs); + } else { + int count = st.getUpdateCount(); + + if(count==-1) { + // This indicates nothing left + loop=false; + } else { + // An update count was returned + System.out.println("Updated "+st.getUpdateCount()+" rows"); + } + } + + if(loop) + type = st.getMoreResults(); + } + } + + /** + * This displays a result set. + * Note: it closes the result once complete. + */ + public void displayResult(ResultSet rs) throws SQLException + { + ResultSetMetaData rsmd = rs.getMetaData(); + + // Print the result column names + int cols = rsmd.getColumnCount(); + for(int i=1;i<=cols;i++) + System.out.print(rsmd.getColumnLabel(i)+(i3) + DriverManager.setLogStream(System.err); + + // Now run the tests + try { + psql test = new psql(args); + } catch(Exception ex) { + System.err.println("Exception caught.\n"+ex); + ex.printStackTrace(); + } + } +} diff --git a/src/interfaces/jdbc/postgresql/fastpath/Fastpath.java b/src/interfaces/jdbc/postgresql/fastpath/Fastpath.java new file mode 100644 index 0000000000000000000000000000000000000000..ce278acef715e4a7ef5dabd870ccd08f29744211 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/fastpath/Fastpath.java @@ -0,0 +1,291 @@ +package postgresql.fastpath; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.sql.*; +import postgresql.util.*; + +/** + * This class implements the Fastpath api. + * + *

This is a means of executing functions imbeded in the postgresql backend + * from within a java application. + * + *

It is based around the file src/interfaces/libpq/fe-exec.c + * + * + *

Implementation notes: + * + *

Network protocol: + * + *

The code within the backend reads integers in reverse. + * + *

There is work in progress to convert all of the protocol to + * network order but it may not be there for v6.3 + * + *

When fastpath switches, simply replace SendIntegerReverse() with + * SendInteger() + * + * @see postgresql.FastpathFastpathArg + * @see postgresql.LargeObject + */ +public class Fastpath +{ + // This maps the functions names to their id's (possible unique just + // to a connection). + protected Hashtable func = new Hashtable(); + + protected postgresql.Connection conn; // our connection + protected postgresql.PG_Stream stream; // the network stream + + /** + * Initialises the fastpath system + * + *

Important Notice + *
This is called from postgresql.Connection, and should not be called + * from client code. + * + * @param conn postgresql.Connection to attach to + * @param stream The network stream to the backend + */ + public Fastpath(postgresql.Connection conn,postgresql.PG_Stream stream) + { + this.conn=conn; + this.stream=stream; + DriverManager.println("Fastpath initialised"); + } + + /** + * Send a function call to the PostgreSQL backend + * + * @param fnid Function id + * @param resulttype True if the result is an integer, false for other results + * @param args FastpathArguments to pass to fastpath + * @return null if no data, Integer if an integer result, or byte[] otherwise + * @exception SQLException if a database-access error occurs. + */ + public Object fastpath(int fnid,boolean resulttype,FastpathArg[] args) throws SQLException + { + // send the function call + try { + // 70 is 'F' in ASCII. Note: don't use SendChar() here as it adds padding + // that confuses the backend. The 0 terminates the command line. + stream.SendInteger(70,1); + stream.SendInteger(0,1); + + stream.SendIntegerReverse(fnid,4); + stream.SendIntegerReverse(args.length,4); + + for(int i=0;iUser code should use the addFunctions method, which is based upon a + * query, rather than hard coding the oid. The oid for a function is not + * guaranteed to remain static, even on different servers of the same + * version. + * + * @param name Function name + * @param fnid Function id + */ + public void addFunction(String name,int fnid) + { + func.put(name,new Integer(fnid)); + } + + /** + * This takes a ResultSet containing two columns. Column 1 contains the + * function name, Column 2 the oid. + * + *

It reads the entire ResultSet, loading the values into the function + * table. + * + *

REMEMBER to close() the resultset after calling this!! + * + *

Implementation note about function name lookups: + * + *

PostgreSQL stores the function id's and their corresponding names in + * the pg_proc table. To speed things up locally, instead of querying each + * function from that table when required, a Hashtable is used. Also, only + * the function's required are entered into this table, keeping connection + * times as fast as possible. + * + *

The postgresql.LargeObject class performs a query upon it's startup, + * and passes the returned ResultSet to the addFunctions() method here. + * + *

Once this has been done, the LargeObject api refers to the functions by + * name. + * + *

Dont think that manually converting them to the oid's will work. Ok, + * they will for now, but they can change during development (there was some + * discussion about this for V7.0), so this is implemented to prevent any + * unwarranted headaches in the future. + * + * @param rs ResultSet + * @exception SQLException if a database-access error occurs. + * @see postgresql.LargeObjectManager + */ + public void addFunctions(ResultSet rs) throws SQLException + { + while(rs.next()) { + func.put(rs.getString(1),new Integer(rs.getInt(2))); + } + } + + /** + * This returns the function id associated by its name + * + *

If addFunction() or addFunctions() have not been called for this name, + * then an SQLException is thrown. + * + * @param name Function name to lookup + * @return Function ID for fastpath call + * @exception SQLException is function is unknown. + */ + public int getID(String name) throws SQLException + { + Integer id = (Integer)func.get(name); + + // may be we could add a lookup to the database here, and store the result + // in our lookup table, throwing the exception if that fails. + // We must, however, ensure that if we do, any existing ResultSet is + // unaffected, otherwise we could break user code. + // + // so, until we know we can do this (needs testing, on the TODO list) + // for now, we throw the exception and do no lookups. + if(id==null) + throw new SQLException("Fastpath: function "+name+" is unknown"); + + return id.intValue(); + } +} + diff --git a/src/interfaces/jdbc/postgresql/fastpath/FastpathArg.java b/src/interfaces/jdbc/postgresql/fastpath/FastpathArg.java new file mode 100644 index 0000000000000000000000000000000000000000..9b800073c223f2c83a6ff8bd0e75fb2121f475ee --- /dev/null +++ b/src/interfaces/jdbc/postgresql/fastpath/FastpathArg.java @@ -0,0 +1,106 @@ +package postgresql.fastpath; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.sql.*; +import postgresql.util.*; + +/** + * Each fastpath call requires an array of arguments, the number and type + * dependent on the function being called. + * + *

This class implements methods needed to provide this capability. + * + *

For an example on how to use this, refer to the postgresql.largeobject + * package + * + * @see postgresql.fastpath.Fastpath + * @see postgresql.largeobject.LargeObjectManager + * @see postgresql.largeobject.LargeObject + */ +public class FastpathArg +{ + /** + * Type of argument, true=integer, false=byte[] + */ + public boolean type; + + /** + * Integer value if type=true + */ + public int value; + + /** + * Byte value if type=false; + */ + public byte[] bytes; + + /** + * Constructs an argument that consists of an integer value + * @param value int value to set + */ + public FastpathArg(int value) + { + type=true; + this.value=value; + } + + /** + * Constructs an argument that consists of an array of bytes + * @param bytes array to store + */ + public FastpathArg(byte bytes[]) + { + type=false; + this.bytes=bytes; + } + + /** + * Constructs an argument that consists of part of a byte array + * @param buf source array + * @param off offset within array + * @param len length of data to include + */ + public FastpathArg(byte buf[],int off,int len) + { + type=false; + bytes = new byte[len]; + System.arraycopy(buf,off,bytes,0,len); + } + + /** + * Constructs an argument that consists of a String. + * @param s String to store + */ + public FastpathArg(String s) + { + this(s.getBytes()); + } + + /** + * This sends this argument down the network stream. + * + *

The stream sent consists of the length.int4 then the contents. + * + *

Note: This is called from Fastpath, and cannot be called from + * client code. + * + * @param s output stream + * @exception IOException if something failed on the network stream + */ + protected void send(postgresql.PG_Stream s) throws IOException + { + if(type) { + // argument is an integer + s.SendIntegerReverse(4,4); // size of an integer + s.SendIntegerReverse(value,4); // integer value of argument + } else { + // argument is a byte array + s.SendIntegerReverse(bytes.length,4); // size of array + s.Send(bytes); + } + } +} + diff --git a/src/interfaces/jdbc/postgresql/geometric/PGbox.java b/src/interfaces/jdbc/postgresql/geometric/PGbox.java new file mode 100644 index 0000000000000000000000000000000000000000..18148326907203939cf25044b8ead705e3f69966 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/geometric/PGbox.java @@ -0,0 +1,105 @@ +package postgresql.geometric; + +import java.io.*; +import java.sql.*; +import postgresql.util.*; + +/** + * This represents the box datatype within postgresql. + */ +public class PGbox extends PGobject implements Serializable,Cloneable +{ + /** + * These are the two points. + */ + public PGpoint point[] = new PGpoint[2]; + + /** + * @param x1 first x coordinate + * @param y1 first y coordinate + * @param x2 second x coordinate + * @param y2 second y coordinate + */ + public PGbox(double x1,double y1,double x2,double y2) + { + this(); + this.point[0] = new PGpoint(x1,y1); + this.point[1] = new PGpoint(x2,y2); + } + + /** + * @param p1 first point + * @param p2 second point + */ + public PGbox(PGpoint p1,PGpoint p2) + { + this(); + this.point[0] = p1; + this.point[1] = p2; + } + + /** + * @param s Box definition in PostgreSQL syntax + * @exception SQLException if definition is invalid + */ + public PGbox(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * Required constructor + */ + public PGbox() + { + setType("box"); + } + + /** + * This method sets the value of this object. It should be overidden, + * but still called by subclasses. + * + * @param value a string representation of the value of the object + * @exception SQLException thrown if value is invalid for this type + */ + public void setValue(String value) throws SQLException + { + PGtokenizer t = new PGtokenizer(value,','); + if(t.getSize() != 2) + throw new SQLException("conversion of box failed - "+value); + + point[0] = new PGpoint(t.getToken(0)); + point[1] = new PGpoint(t.getToken(1)); + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGbox) { + PGbox p = (PGbox)obj; + return (p.point[0].equals(point[0]) && p.point[1].equals(point[1])) || + (p.point[0].equals(point[1]) && p.point[1].equals(point[0])); + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + return new PGbox((PGpoint)point[0].clone(),(PGpoint)point[1].clone()); + } + + /** + * @return the PGbox in the syntax expected by postgresql + */ + public String getValue() + { + return point[0].toString()+","+point[1].toString(); + } +} diff --git a/src/interfaces/jdbc/postgresql/geometric/PGcircle.java b/src/interfaces/jdbc/postgresql/geometric/PGcircle.java new file mode 100644 index 0000000000000000000000000000000000000000..105ed91a2f07d509bf2d0a30fdd64a2cf23f50f7 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/geometric/PGcircle.java @@ -0,0 +1,108 @@ +package postgresql.geometric; + +import java.io.*; +import java.sql.*; +import postgresql.util.*; + +/** + * This represents postgresql's circle datatype, consisting of a point and + * a radius + */ +public class PGcircle extends PGobject implements Serializable,Cloneable +{ + /** + * This is the centre point + */ + public PGpoint center; + + /** + * This is the radius + */ + double radius; + + /** + * @param x coordinate of centre + * @param y coordinate of centre + * @param r radius of circle + */ + public PGcircle(double x,double y,double r) + { + this(new PGpoint(x,y),r); + } + + /** + * @param c PGpoint describing the circle's centre + * @param r radius of circle + */ + public PGcircle(PGpoint c,double r) + { + this(); + this.center = c; + this.radius = r; + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public PGcircle(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * This constructor is used by the driver. + */ + public PGcircle() + { + setType("circle"); + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + PGtokenizer t = new PGtokenizer(PGtokenizer.removeAngle(s),','); + if(t.getSize() != 2) + throw new SQLException("conversion of circle failed - "+s); + + try { + center = new PGpoint(t.getToken(0)); + radius = Double.valueOf(t.getToken(1)).doubleValue(); + } catch(NumberFormatException e) { + throw new SQLException("conversion of circle failed - "+s+" - +"+e.toString()); + } + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGcircle) { + PGcircle p = (PGcircle)obj; + return p.center.equals(center) && p.radius==radius; + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + return new PGcircle((PGpoint)center.clone(),radius); + } + + /** + * @return the PGcircle in the syntax expected by postgresql + */ + public String getValue() + { + return "<"+center+","+radius+">"; + } +} diff --git a/src/interfaces/jdbc/postgresql/geometric/PGlseg.java b/src/interfaces/jdbc/postgresql/geometric/PGlseg.java new file mode 100644 index 0000000000000000000000000000000000000000..9daddfc91acca43133812037f88f859182d00da6 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/geometric/PGlseg.java @@ -0,0 +1,100 @@ +package postgresql.geometric; + +import java.io.*; +import java.sql.*; +import postgresql.util.*; + +/** + * This implements a lseg (line segment) consisting of two points + */ +public class PGlseg extends PGobject implements Serializable,Cloneable +{ + /** + * These are the two points. + */ + public PGpoint point[] = new PGpoint[2]; + + /** + * @param x1 coordinate for first point + * @param y1 coordinate for first point + * @param x2 coordinate for second point + * @param y2 coordinate for second point + */ + public PGlseg(double x1,double y1,double x2,double y2) + { + this(new PGpoint(x1,y1),new PGpoint(x2,y2)); + } + + /** + * @param p1 first point + * @param p2 second point + */ + public PGlseg(PGpoint p1,PGpoint p2) + { + this(); + this.point[0] = p1; + this.point[1] = p2; + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public PGlseg(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * reuired by the driver + */ + public PGlseg() + { + setType("lseg"); + } + + /** + * @param s Definition of the line segment in PostgreSQL's syntax + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + PGtokenizer t = new PGtokenizer(PGtokenizer.removeBox(s),','); + if(t.getSize() != 2) + throw new SQLException("conversion of lseg failed - "+s); + + point[0] = new PGpoint(t.getToken(0)); + point[1] = new PGpoint(t.getToken(1)); + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGlseg) { + PGlseg p = (PGlseg)obj; + return (p.point[0].equals(point[0]) && p.point[1].equals(point[1])) || + (p.point[0].equals(point[1]) && p.point[1].equals(point[0])); + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + return new PGlseg((PGpoint)point[0].clone(),(PGpoint)point[1].clone()); + } + + /** + * @return the PGlseg in the syntax expected by postgresql + */ + public String getValue() + { + return "["+point[0]+","+point[1]+"]"; + } +} diff --git a/src/interfaces/jdbc/postgresql/geometric/PGpath.java b/src/interfaces/jdbc/postgresql/geometric/PGpath.java new file mode 100644 index 0000000000000000000000000000000000000000..45c162ac8aa74b1cb183e91d4f31af7a3229e2d2 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/geometric/PGpath.java @@ -0,0 +1,145 @@ +package postgresql.geometric; + +import java.io.*; +import java.sql.*; +import postgresql.util.*; + +/** + * This implements a path (a multiple segmented line, which may be closed) + */ +public class PGpath extends PGobject implements Serializable,Cloneable +{ + /** + * True if the path is open, false if closed + */ + public boolean open; + + /** + * The points defining this path + */ + public PGpoint points[]; + + /** + * @param points the PGpoints that define the path + * @param open True if the path is open, false if closed + */ + public PGpath(PGpoint[] points,boolean open) + { + this(); + this.points = points; + this.open = open; + } + + /** + * Required by the driver + */ + public PGpath() + { + setType("path"); + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public PGpath(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * @param s Definition of the path in PostgreSQL's syntax + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + // First test to see if were open + if(s.startsWith("[") && s.endsWith("]")) { + open = true; + s = PGtokenizer.removeBox(s); + } else if(s.startsWith("(") && s.endsWith(")")) { + open = false; + s = PGtokenizer.removePara(s); + } else + throw new SQLException("cannot tell if path is open or closed"); + + PGtokenizer t = new PGtokenizer(s,','); + int npoints = t.getSize(); + points = new PGpoint[npoints]; + for(int p=0;p0) b.append(","); + b.append(points[p].toString()); + } + b.append(open?"]":")"); + + return b.toString(); + } + + public boolean isOpen() + { + return open; + } + + public boolean isClosed() + { + return !open; + } + + public void closePath() + { + open = false; + } + + public void openPath() + { + open = true; + } + +} diff --git a/src/interfaces/jdbc/postgresql/geometric/PGpoint.java b/src/interfaces/jdbc/postgresql/geometric/PGpoint.java new file mode 100644 index 0000000000000000000000000000000000000000..29f2c753460697d20ab11e4c8933070d2130423b --- /dev/null +++ b/src/interfaces/jdbc/postgresql/geometric/PGpoint.java @@ -0,0 +1,167 @@ +package postgresql.geometric; + +import java.awt.Point; +import java.io.*; +import java.sql.*; + +import postgresql.util.*; + +/** + * This implements a version of java.awt.Point, except it uses double + * to represent the coordinates. + * + *

It maps to the point datatype in postgresql. + */ +public class PGpoint extends PGobject implements Serializable,Cloneable +{ + /** + * The X coordinate of the point + */ + public double x; + + /** + * The Y coordinate of the point + */ + public double y; + + /** + * @param x coordinate + * @param y coordinate + */ + public PGpoint(double x,double y) + { + this(); + this.x = x; + this.y = y; + } + + /** + * This is called mainly from the other geometric types, when a + * point is imbeded within their definition. + * + * @param value Definition of this point in PostgreSQL's syntax + */ + public PGpoint(String value) throws SQLException + { + this(); + setValue(value); + } + + /** + * Required by the driver + */ + public PGpoint() + { + setType("point"); + } + + /** + * @param s Definition of this point in PostgreSQL's syntax + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + PGtokenizer t = new PGtokenizer(PGtokenizer.removePara(s),','); + try { + x = Double.valueOf(t.getToken(0)).doubleValue(); + y = Double.valueOf(t.getToken(1)).doubleValue(); + } catch(NumberFormatException e) { + throw new SQLException("conversion of point failed - "+e.toString()); + } + } + + /** + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGpoint) { + PGpoint p = (PGpoint)obj; + return x == p.x && y == p.y; + } + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + return new PGpoint(x,y); + } + + /** + * @return the PGpoint in the syntax expected by postgresql + */ + public String getValue() + { + return "("+x+","+y+")"; + } + + /** + * Translate the point with the supplied amount. + * @param x integer amount to add on the x axis + * @param y integer amount to add on the y axis + */ + public void translate(int x,int y) + { + translate((double)x,(double)y); + } + + /** + * Translate the point with the supplied amount. + * @param x double amount to add on the x axis + * @param y double amount to add on the y axis + */ + public void translate(double x,double y) + { + this.x += x; + this.y += y; + } + + /** + * Moves the point to the supplied coordinates. + * @param x integer coordinate + * @param y integer coordinate + */ + public void move(int x,int y) + { + setLocation(x,y); + } + + /** + * Moves the point to the supplied coordinates. + * @param x double coordinate + * @param y double coordinate + */ + public void move(double x,double y) + { + this.x = x; + this.y = y; + } + + /** + * Moves the point to the supplied coordinates. + * refer to java.awt.Point for description of this + * @param x integer coordinate + * @param y integer coordinate + * @see java.awt.Point + */ + public void setLocation(int x,int y) + { + move((double)x,(double)y); + } + + /** + * Moves the point to the supplied java.awt.Point + * refer to java.awt.Point for description of this + * @param p Point to move to + * @see java.awt.Point + */ + public void setLocation(Point p) + { + setLocation(p.x,p.y); + } + +} diff --git a/src/interfaces/jdbc/postgresql/geometric/PGpolygon.java b/src/interfaces/jdbc/postgresql/geometric/PGpolygon.java new file mode 100644 index 0000000000000000000000000000000000000000..b60c16e8ce558b7483b71398cf76e79a31d2c873 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/geometric/PGpolygon.java @@ -0,0 +1,105 @@ +package postgresql.geometric; + +import java.io.*; +import java.sql.*; +import postgresql.util.*; + +/** + * This implements the polygon datatype within PostgreSQL. + */ +public class PGpolygon extends PGobject implements Serializable,Cloneable +{ + /** + * The points defining the polygon + */ + public PGpoint points[]; + + /** + * Creates a polygon using an array of PGpoints + * + * @param points the points defining the polygon + */ + public PGpolygon(PGpoint[] points) + { + this(); + this.points = points; + } + + /** + * @param s definition of the circle in PostgreSQL's syntax. + * @exception SQLException on conversion failure + */ + public PGpolygon(String s) throws SQLException + { + this(); + setValue(s); + } + + /** + * Required by the driver + */ + public PGpolygon() + { + setType("polygon"); + } + + /** + * @param s Definition of the polygon in PostgreSQL's syntax + * @exception SQLException on conversion failure + */ + public void setValue(String s) throws SQLException + { + PGtokenizer t = new PGtokenizer(PGtokenizer.removePara(s),','); + int npoints = t.getSize(); + points = new PGpoint[npoints]; + for(int p=0;p0) b.append(","); + b.append(points[p].toString()); + } + b.append(")"); + return b.toString(); + } +} diff --git a/src/interfaces/jdbc/postgresql/largeobject/LargeObject.java b/src/interfaces/jdbc/postgresql/largeobject/LargeObject.java new file mode 100644 index 0000000000000000000000000000000000000000..0cab20f8e337f2eb317a690ac874aee845d8c737 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/largeobject/LargeObject.java @@ -0,0 +1,253 @@ +package postgresql.largeobject; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.sql.*; + +import postgresql.fastpath.*; + +/** + * This class implements the large object interface to postgresql. + * + *

It provides the basic methods required to run the interface, plus + * a pair of methods that provide InputStream and OutputStream classes + * for this object. + * + *

Normally, client code would use the getAsciiStream, getBinaryStream, + * or getUnicodeStream methods in ResultSet, or setAsciiStream, + * setBinaryStream, or setUnicodeStream methods in PreparedStatement to + * access Large Objects. + * + *

However, sometimes lower level access to Large Objects are required, + * that are not supported by the JDBC specification. + * + *

Refer to postgresql.largeobject.LargeObjectManager on how to gain access + * to a Large Object, or how to create one. + * + * @see postgresql.largeobject.LargeObjectManager + * @see postgresql.ResultSet#getAsciiStream + * @see postgresql.ResultSet#getBinaryStream + * @see postgresql.ResultSet#getUnicodeStream + * @see postgresql.PreparedStatement#setAsciiStream + * @see postgresql.PreparedStatement#setBinaryStream + * @see postgresql.PreparedStatement#setUnicodeStream + * @see java.sql.ResultSet#getAsciiStream + * @see java.sql.ResultSet#getBinaryStream + * @see java.sql.ResultSet#getUnicodeStream + * @see java.sql.PreparedStatement#setAsciiStream + * @see java.sql.PreparedStatement#setBinaryStream + * @see java.sql.PreparedStatement#setUnicodeStream + * + */ +public class LargeObject +{ + /** + * Indicates a seek from the begining of a file + */ + public static final int SEEK_SET = 0; + + /** + * Indicates a seek from the current position + */ + public static final int SEEK_CUR = 1; + + /** + * Indicates a seek from the end of a file + */ + public static final int SEEK_END = 2; + + private Fastpath fp; // Fastpath API to use + private int oid; // OID of this object + private int fd; // the descriptor of the open large object + + /** + * This opens a large object. + * + *

If the object does not exist, then an SQLException is thrown. + * + * @param fp FastPath API for the connection to use + * @param oid of the Large Object to open + * @param mode Mode of opening the large object + * (defined in LargeObjectManager) + * @exception SQLException if a database-access error occurs. + * @see postgresql.largeobject.LargeObjectManager + */ + protected LargeObject(Fastpath fp,int oid,int mode) throws SQLException + { + this.fp = fp; + this.oid = oid; + + FastpathArg args[] = new FastpathArg[2]; + args[0] = new FastpathArg(oid); + args[1] = new FastpathArg(mode); + this.fd = fp.getInteger("lo_open",args); + } + + /** + * @return the OID of this LargeObject + */ + public int getOID() + { + return oid; + } + + /** + * This method closes the object. You must not call methods in this + * object after this is called. + * @exception SQLException if a database-access error occurs. + */ + public void close() throws SQLException + { + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(fd); + fp.fastpath("lo_close",false,args); // true here as we dont care!! + } + + /** + * Reads some data from the object, and return as a byte[] array + * + * @param len number of bytes to read + * @return byte[] array containing data read + * @exception SQLException if a database-access error occurs. + */ + public byte[] read(int len) throws SQLException + { + FastpathArg args[] = new FastpathArg[2]; + args[0] = new FastpathArg(fd); + args[1] = new FastpathArg(len); + return fp.getData("loread",args); + } + + /** + * Reads some data from the object into an existing array + * + * @param buf destination array + * @param off offset within array + * @param len number of bytes to read + * @exception SQLException if a database-access error occurs. + */ + public void read(byte buf[],int off,int len) throws SQLException + { + System.arraycopy(read(len),0,buf,off,len); + } + + /** + * Writes an array to the object + * + * @param buf array to write + * @exception SQLException if a database-access error occurs. + */ + public void write(byte buf[]) throws SQLException + { + FastpathArg args[] = new FastpathArg[2]; + args[0] = new FastpathArg(fd); + args[1] = new FastpathArg(buf); + fp.fastpath("lowrite",false,args); + } + + /** + * Writes some data from an array to the object + * + * @param buf destination array + * @param off offset within array + * @param len number of bytes to write + * @exception SQLException if a database-access error occurs. + */ + public void write(byte buf[],int off,int len) throws SQLException + { + byte data[] = new byte[len]; + System.arraycopy(buf,off,data,0,len); + write(data); + } + + /** + * Sets the current position within the object. + * + *

This is similar to the fseek() call in the standard C library. It + * allows you to have random access to the large object. + * + * @param pos position within object + * @param ref Either SEEK_SET, SEEK_CUR or SEEK_END + * @exception SQLException if a database-access error occurs. + */ + public void seek(int pos,int ref) throws SQLException + { + FastpathArg args[] = new FastpathArg[3]; + args[0] = new FastpathArg(fd); + args[1] = new FastpathArg(pos); + args[2] = new FastpathArg(ref); + fp.fastpath("lo_lseek",false,args); + } + + /** + * Sets the current position within the object. + * + *

This is similar to the fseek() call in the standard C library. It + * allows you to have random access to the large object. + * + * @param pos position within object from begining + * @exception SQLException if a database-access error occurs. + */ + public void seek(int pos) throws SQLException + { + seek(pos,SEEK_SET); + } + + /** + * @return the current position within the object + * @exception SQLException if a database-access error occurs. + */ + public int tell() throws SQLException + { + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(fd); + return fp.getInteger("lo_tell",args); + } + + /** + * This method is inefficient, as the only way to find out the size of + * the object is to seek to the end, record the current position, then + * return to the original position. + * + *

A better method will be found in the future. + * + * @return the size of the large object + * @exception SQLException if a database-access error occurs. + */ + public int size() throws SQLException + { + int cp = tell(); + seek(0,SEEK_END); + int sz = tell(); + seek(cp,SEEK_SET); + return sz; + } + + /** + * Returns an InputStream from this object. + * + *

This InputStream can then be used in any method that requires an + * InputStream. + * + * @exception SQLException if a database-access error occurs. + */ + public InputStream getInputStream() throws SQLException + { + throw new SQLException("LargeObject:getInputStream not implemented"); + } + + /** + * Returns an OutputStream to this object + * + *

This OutputStream can then be used in any method that requires an + * OutputStream. + * + * @exception SQLException if a database-access error occurs. + */ + public OutputStream getOutputStream() throws SQLException + { + throw new SQLException("LargeObject:getOutputStream not implemented"); + } +} diff --git a/src/interfaces/jdbc/postgresql/largeobject/LargeObjectManager.java b/src/interfaces/jdbc/postgresql/largeobject/LargeObjectManager.java new file mode 100644 index 0000000000000000000000000000000000000000..c7798d15a12608ca2c67aa3f2983da2c91020247 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/largeobject/LargeObjectManager.java @@ -0,0 +1,205 @@ +package postgresql.largeobject; + +import java.io.*; +import java.lang.*; +import java.net.*; +import java.util.*; +import java.sql.*; + +import postgresql.fastpath.*; + +/** + * This class implements the large object interface to postgresql. + * + *

It provides methods that allow client code to create, open and delete + * large objects from the database. When opening an object, an instance of + * postgresql.largeobject.LargeObject is returned, and its methods then allow + * access to the object. + * + *

This class can only be created by postgresql.Connection + * + *

To get access to this class, use the following segment of code: + *

+ * import postgresql.largeobject.*;
+ *
+ * Connection  conn;
+ * LargeObjectManager lobj;
+ *
+ * ... code that opens a connection ...
+ *
+ * lobj = ((postgresql.Connection)myconn).getLargeObjectAPI();
+ * 
+ * + *

Normally, client code would use the getAsciiStream, getBinaryStream, + * or getUnicodeStream methods in ResultSet, or setAsciiStream, + * setBinaryStream, or setUnicodeStream methods in PreparedStatement to + * access Large Objects. + * + *

However, sometimes lower level access to Large Objects are required, + * that are not supported by the JDBC specification. + * + *

Refer to postgresql.largeobject.LargeObject on how to manipulate the + * contents of a Large Object. + * + * @see postgresql.largeobject.LargeObject + * @see postgresql.ResultSet#getAsciiStream + * @see postgresql.ResultSet#getBinaryStream + * @see postgresql.ResultSet#getUnicodeStream + * @see postgresql.PreparedStatement#setAsciiStream + * @see postgresql.PreparedStatement#setBinaryStream + * @see postgresql.PreparedStatement#setUnicodeStream + * @see java.sql.ResultSet#getAsciiStream + * @see java.sql.ResultSet#getBinaryStream + * @see java.sql.ResultSet#getUnicodeStream + * @see java.sql.PreparedStatement#setAsciiStream + * @see java.sql.PreparedStatement#setBinaryStream + * @see java.sql.PreparedStatement#setUnicodeStream + */ +public class LargeObjectManager +{ + // the fastpath api for this connection + private Fastpath fp; + + /** + * This mode indicates we want to write to an object + */ + public static final int WRITE = 0x00020000; + + /** + * This mode indicates we want to read an object + */ + public static final int READ = 0x00040000; + + /** + * This mode is the default. It indicates we want read and write access to + * a large object + */ + public static final int READWRITE = READ | WRITE; + + /** + * This prevents us being created by mere mortals + */ + private LargeObjectManager() + { + } + + /** + * Constructs the LargeObject API. + * + *

Important Notice + *
This method should only be called by postgresql.Connection + * + *

There should only be one LargeObjectManager per Connection. The + * postgresql.Connection class keeps track of the various extension API's + * and it's advised you use those to gain access, and not going direct. + */ + public LargeObjectManager(postgresql.Connection conn) throws SQLException + { + // We need Fastpath to do anything + this.fp = conn.getFastpathAPI(); + + // Now get the function oid's for the api + // + // This is an example of Fastpath.addFunctions(); + // + ResultSet res = (postgresql.ResultSet)conn.createStatement().executeQuery("select proname, oid from pg_proc" + + " where proname = 'lo_open'" + + " or proname = 'lo_close'" + + " or proname = 'lo_creat'" + + " or proname = 'lo_unlink'" + + " or proname = 'lo_lseek'" + + " or proname = 'lo_tell'" + + " or proname = 'loread'" + + " or proname = 'lowrite'"); + + if(res==null) + throw new SQLException("failed to initialise LargeObject API"); + + fp.addFunctions(res); + res.close(); + DriverManager.println("Large Object initialised"); + } + + /** + * This opens an existing large object, based on its OID. This method + * assumes that READ and WRITE access is required (the default). + * + * @param oid of large object + * @return LargeObject instance providing access to the object + * @exception SQLException on error + */ + public LargeObject open(int oid) throws SQLException + { + return new LargeObject(fp,oid,READWRITE); + } + + /** + * This opens an existing large object, based on its OID + * + * @param oid of large object + * @param mode mode of open + * @return LargeObject instance providing access to the object + * @exception SQLException on error + */ + public LargeObject open(int oid,int mode) throws SQLException + { + return new LargeObject(fp,oid,mode); + } + + /** + * This creates a large object, returning its OID. + * + *

It defaults to READWRITE for the new object's attributes. + * + * @return oid of new object + * @exception SQLException on error + */ + public int create() throws SQLException + { + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(READWRITE); + return fp.getInteger("lo_creat",args); + } + + /** + * This creates a large object, returning its OID + * + * @param mode a bitmask describing different attributes of the new object + * @return oid of new object + * @exception SQLException on error + */ + public int create(int mode) throws SQLException + { + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(mode); + return fp.getInteger("lo_creat",args); + } + + /** + * This deletes a large object. + * + * @param oid describing object to delete + * @exception SQLException on error + */ + public void delete(int oid) throws SQLException + { + FastpathArg args[] = new FastpathArg[1]; + args[0] = new FastpathArg(oid); + fp.fastpath("lo_unlink",false,args); + } + + /** + * This deletes a large object. + * + *

It is identical to the delete method, and is supplied as the C API uses + * unlink. + * + * @param oid describing object to delete + * @exception SQLException on error + */ + public void unlink(int oid) throws SQLException + { + delete(oid); + } + +} diff --git a/src/interfaces/jdbc/postgresql/util/PGobject.java b/src/interfaces/jdbc/postgresql/util/PGobject.java new file mode 100644 index 0000000000000000000000000000000000000000..62b3d55f5ef491047ca7fb7d90e85df645d4517b --- /dev/null +++ b/src/interfaces/jdbc/postgresql/util/PGobject.java @@ -0,0 +1,102 @@ +package postgresql.util; + +import java.io.*; +import java.lang.*; +import java.sql.*; +import java.util.*; + +/** + * postgresql.PG_Object is a class used to describe unknown types + * An unknown type is any type that is unknown by JDBC Standards + * + *

As of PostgreSQL 6.3, this allows user code to add their own + * handlers via a call to postgresql.Connection. These handlers + * must extend this class. + */ +public class PGobject implements Serializable,Cloneable +{ + protected String type; + protected String value; + + /** + * This is called by postgresql.Connection.getObject() to create the + * object. + */ + public PGobject() + { + } + + /** + * This method sets the type of this object. + * + *

It should not be extended by subclasses, hence its final + * + * @param type a string describing the type of the object + */ + public final void setType(String type) + { + this.type = type; + } + + /** + * This method sets the value of this object. It must be overidden. + * + * @param value a string representation of the value of the object + * @exception SQLException thrown if value is invalid for this type + */ + public void setValue(String value) throws SQLException + { + this.value = value; + } + + /** + * As this cannot change during the life of the object, it's final. + * @return the type name of this object + */ + public final String getType() + { + return type; + } + + /** + * This must be overidden, to return the value of the object, in the + * form required by postgresql. + * @return the value of this object + */ + public String getValue() + { + return value; + } + + /** + * This must be overidden to allow comparisons of objects + * @param obj Object to compare with + * @return true if the two boxes are identical + */ + public boolean equals(Object obj) + { + if(obj instanceof PGobject) + return ((PGobject)obj).getValue().equals(getValue()); + return false; + } + + /** + * This must be overidden to allow the object to be cloned + */ + public Object clone() + { + PGobject obj = new PGobject(); + obj.type=type; + obj.value=value; + return obj; + } + + /** + * This is defined here, so user code need not overide it. + * @return the value of this object, in the syntax expected by postgresql + */ + public String toString() + { + return value; + } +} diff --git a/src/interfaces/jdbc/postgresql/util/PGtokenizer.java b/src/interfaces/jdbc/postgresql/util/PGtokenizer.java new file mode 100644 index 0000000000000000000000000000000000000000..0d0759689169d3541788a516dc154ccbe3b10a22 --- /dev/null +++ b/src/interfaces/jdbc/postgresql/util/PGtokenizer.java @@ -0,0 +1,197 @@ +package postgresql.util; + +import java.sql.*; +import java.util.*; + +/** + * This class is used to tokenize the text output of postgres. + * + *

It's mainly used by the geometric classes, but is useful in parsing any + * output from custom data types output from postgresql. + * + * @see postgresql.geometric.PGbox + * @see postgresql.geometric.PGcircle + * @see postgresql.geometric.PGlseg + * @see postgresql.geometric.PGpath + * @see postgresql.geometric.PGpoint + * @see postgresql.geometric.PGpolygon + */ +public class PGtokenizer +{ + // Our tokens + protected Vector tokens; + + /** + * Create a tokeniser. + * + *

We could have used StringTokenizer to do this, however, we needed to + * handle nesting of '(' ')' '[' ']' '<' and '>' as these are used + * by the geometric data types. + * + * @param string containing tokens + * @param delim single character to split the tokens + */ + public PGtokenizer(String string,char delim) + { + tokenize(string,delim); + } + + /** + * This resets this tokenizer with a new string and/or delimiter. + * + * @param string containing tokens + * @param delim single character to split the tokens + */ + public int tokenize(String string,char delim) + { + tokens = new Vector(); + + // nest holds how many levels we are in the current token. + // if this is > 0 then we don't split a token when delim is matched. + // + // The Geometric datatypes use this, because often a type may have others + // (usualls PGpoint) imbedded within a token. + // + // Peter 1998 Jan 6 - Added < and > to the nesting rules + int nest=0,p,s; + + for(p=0,s=0;p') + nest--; + + if(nest==0 && c==delim) { + tokens.addElement(string.substring(s,p)); + s=p+1; // +1 to skip the delimiter + } + + } + + // Don't forget the last token ;-) + if(s"); + } + + /** + * Removes < and > from the beginning and end of all tokens + * @return String without the < or > + */ + public void removeAngle() + { + remove("<",">"); + } +}