/** * Copyright (c) 2008-2010, Wave2 Limited. All rights reserved. * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Wave2 Limited nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.binarystor.postgresql; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.File; import java.io.FileWriter; import java.io.BufferedWriter; import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.HashMap; import org.kohsuke.args4j.*; /** * * @author Alan Snelson */ public class PostgreSQLDump { //Command Line Arguments @Option(name = "--help") private boolean help; @Option(name = "-h", usage = "PostgreSQL Server Hostname") private String hostname; @Option(name = "-p", usage = "PostgreSQL Server Port Number") private int port = 5432; @Option(name = "-U", usage = "PostgreSQL Username") private String username; @Option(name = "-W", usage = "PostgreSQL Password") private String password; @Option(name = "-v") private String database; //TODO remove prior to release private boolean verbose = true; // receives other command line parameters than options @Argument private List arguments = new ArrayList(); private static String version = "0.1"; private String schema = "public"; private Connection conn = null; private DatabaseMetaData databaseMetaData; private String databaseProductVersion = null; private int databaseProductMajorVersion = 0; private int databaseProductMinorVersion = 0; private String posgresqlVersion = null; //Privilege codes private static final HashMap pCodes = new HashMap() { { put("a", "INSERT"); put("c", "CONNECT"); put("C", "CREATE"); put("d", "DELETE"); put("D", "TRUNCATE"); put("r", "SELECT"); put("t", "TRIGGER"); put("T", "TEMPORARY"); put("U", "USAGE"); put("w", "UPDATE"); put("x", "REFERENCES"); put("X", "EXECUTE"); } }; /** * Default contructor for PostgreSQLDump. */ public PostgreSQLDump() { } /** * Create a new instance of PostgreSQLDump using default database (postgres). * * @param host PostgreSQL Server Hostname * @param username PostgreSQL Username * @param password PostgreSQL Password * @throws SQLException */ public PostgreSQLDump(String host, String username, String password) throws SQLException { try { connect(host, username, password, "postgres"); this.hostname = host; this.username = username; this.password = password; if (System.getenv("PGDATABASE") != null){ this.database = System.getenv("PGDATABASE"); } else { this.database = "postgres"; } } catch (SQLException se) { throw se; } } /** * Create a new instance of PostgreSQLDump using supplied database. * * @param host PostgreSQL Server Hostname * @param username PostgreSQL Username * @param password PostgreSQL Password * @param db Default database * @throws SQLException */ public PostgreSQLDump(String host, String username, String password, String db) throws SQLException { try { connect(host, username, password, db); this.hostname = host; this.username = username; this.password = password; this.database = db; } catch (SQLException se) { throw se; } } /** * Connect to PostgreSQL server * * @param host PostgreSQL Server Hostname * @param port * @param username PostgreSQL Username * @param password PostgreSQL Password * @param db Default database * @throws SQLException */ public void connect(String host, int port, String username, String password, String db) throws SQLException { try { Class.forName("org.postgresql.Driver").newInstance(); conn = DriverManager.getConnection("jdbc:postgresql://" + host + ":" + port + "/" + db, username, password); databaseMetaData = conn.getMetaData(); databaseProductVersion = databaseMetaData.getDatabaseProductVersion(); databaseProductMajorVersion = databaseMetaData.getDatabaseMajorVersion(); databaseProductMinorVersion = databaseMetaData.getDatabaseMinorVersion(); this.hostname = host; this.port = port; this.username = username; this.password = password; this.database = db; if (verbose) { System.out.println("PostgreSQL " + databaseProductMajorVersion + "." + databaseProductMinorVersion + " Database connection established"); } } catch (SQLException se) { throw se; } catch (Exception e) { System.err.println("Cannot connect to database server"); } } /** * Connect to PostgreSQL server using default port * * @param host PostgreSQL Server Hostname * @param username PostgreSQL Username * @param password PostgreSQL Password * @param db Default database * @throws SQLException */ public void connect(String host, String username, String password, String db) throws SQLException { connect(host, 5432, username, password, db); } /** * Parse permissions string and generate GRANT statements * * @param acl PostgreSQL acl string * @param name PostgreSQL name of object for grant statements * @param type PostgreSQL type of object for grant statements * @return */ public static String parseACL(String acl, String name, String type) { String aclCommands = ""; String[] acls = acl.substring(1, acl.length() - 1).split(","); HashMap> withGrant = new HashMap(); HashMap> withoutGrant = new HashMap(); //Function names require braces if (type.equals("FUNCTION")) { name = "\"" + name + "\"()"; } else { name = "\"" + name + "\""; } for (String priv : acls) { String user = priv.split("=")[0]; String privs = priv.split("=")[1].split("/")[0]; //Public Role? if (user.equals("")) { user = "public"; } //Schema privileges GRANT ALL? if (type.equals("DATABASE") && privs.contains("C") && privs.contains("T") && privs.contains("c")) { if (privs.contains("*")) { aclCommands += "GRANT ALL ON DATABASE " + name + " TO " + user + " WITH GRANT OPTION;\n"; } else { aclCommands += "GRANT ALL ON DATABASE " + name + " TO " + user + ";\n"; } } else if (type.equals("SCHEMA") && privs.contains("U") && privs.contains("C")) { if (privs.contains("*")) { aclCommands += "GRANT ALL ON SCHEMA " + name + " TO " + user + " WITH GRANT OPTION;\n"; } else { aclCommands += "GRANT ALL ON SCHEMA " + name + " TO " + user + ";\n"; } } else if (type.equals("SEQUENCE") && privs.contains("r") && privs.contains("w") && privs.contains("U")) { if (privs.contains("*")) { aclCommands += "GRANT ALL ON SEQUENCE " + name + " TO " + user + " WITH GRANT OPTION;\n"; } else { aclCommands += "GRANT ALL ON SEQUENCE " + name + " TO " + user + ";\n"; } } else if (type.equals("TABLE") && privs.contains("a") && privs.contains("r") && privs.contains("w") && privs.contains("d") && privs.contains("x") && privs.contains("t")) { if (privs.contains("*")) { aclCommands += "GRANT ALL ON TABLE " + name + " TO " + user + " WITH GRANT OPTION;\n"; } else { aclCommands += "GRANT ALL ON TABLE " + name + " TO " + user + ";\n"; } } else { //Determine privileges and check if GRANT OPTION set. for (char c : privs.toCharArray()) { if (pCodes.containsKey(Character.toString(c))) { //Is this the last character? if (privs.indexOf(c) != privs.length() - 1) { //Is the next character a GRANT if ('*' == privs.charAt(privs.indexOf(c) + 1)) { if (withGrant.containsKey(user)) { ArrayList userPrivs = withGrant.get(user); userPrivs.add(pCodes.get(Character.toString(c))); withGrant.put(user, userPrivs); } else { ArrayList userPrivs = new ArrayList(); userPrivs.add(pCodes.get(Character.toString(c))); withGrant.put(user, userPrivs); } } else { if (withoutGrant.containsKey(user)) { ArrayList userPrivs = withoutGrant.get(user); userPrivs.add(pCodes.get(Character.toString(c))); withoutGrant.put(user, userPrivs); } else { ArrayList userPrivs = new ArrayList(); userPrivs.add(pCodes.get(Character.toString(c))); withoutGrant.put(user, userPrivs); } } } else { if (withoutGrant.containsKey(user)) { ArrayList userPrivs = withoutGrant.get(user); userPrivs.add(pCodes.get(Character.toString(c))); withoutGrant.put(user, userPrivs); } else { ArrayList userPrivs = new ArrayList(); userPrivs.add(pCodes.get(Character.toString(c))); withoutGrant.put(user, userPrivs); } } } } } } //create GRANT statements without GRANT OPTION for (String user : withoutGrant.keySet()) { aclCommands += "GRANT "; for (String priv : withoutGrant.get(user)) { if (withoutGrant.get(user).get(withoutGrant.get(user).size() - 1).equals(priv)) { aclCommands += priv; } else { aclCommands += priv + ", "; } } aclCommands += " ON " + type + " " + name + " TO \"" + user + "\";\n"; } //create GRANT statements with GRANT OPTION for (String user : withGrant.keySet()) { aclCommands += "GRANT "; for (String priv : withGrant.get(user)) { if (withGrant.get(user).get(withGrant.get(user).size() - 1).equals(priv)) { aclCommands += priv; } else { aclCommands += priv + ", "; } } aclCommands += " ON " + type + " " + name + " TO \"" + user + "\" WITH GRANT OPTION;\n"; } return aclCommands; } /** * Obtain server parameters * * @return parameters */ public Map dumpServerParameters() { Map parameters = new TreeMap(); try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT name, setting, vartype FROM pg_catalog.pg_settings"); ResultSet rs = s.getResultSet(); while (rs.next()) { if (rs.getString("vartype").equals("string")) { parameters.put(rs.getString("name"), "'" + rs.getString("setting") + "'"); } parameters.put(rs.getString("name"), rs.getString("setting")); } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(parameters.toString()); } return parameters; } /** * Returns create statement for supplied role * * @param role PostgreSQL role name * @return */ public String dumpCreateRole(String role) { String createRole = null; try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT pg_catalog.pg_authid.oid, rolname, rolcanlogin, rolpassword, rolsuper, rolinherit, rolcreatedb, " + "rolcreaterole, rolconnlimit, rolvaliduntil, rolcatupdate, description FROM pg_catalog.pg_authid " + "LEFT OUTER JOIN pg_catalog.pg_shdescription " + "ON pg_catalog.pg_authid.oid = pg_catalog.pg_shdescription.objoid WHERE rolname = '" + role + "';"); ResultSet rs = s.getResultSet(); while (rs.next()) { createRole = "CREATE ROLE " + role; if (rs.getBoolean("rolcanlogin") == true) { createRole += " LOGIN"; } if (rs.getString("rolpassword") != null) { createRole += " ENCRYPTED PASSWORD '" + rs.getString("rolpassword") + "'"; } //Superuser? if (rs.getBoolean("rolsuper") == true) { createRole += " SUPERUSER"; } else { createRole += " NOSUPERUSER"; } //Inherit? if (rs.getBoolean("rolinherit") == true) { createRole += " INHERIT"; } else { createRole += " NOINHERIT"; } //CreateDB? if (rs.getBoolean("rolcreatedb") == true) { createRole += " CREATEDB"; } else { createRole += " NOCREATEDB"; } //CreateRole? if (rs.getBoolean("rolcreaterole") == true) { createRole += " CREATEROLE"; } else { createRole += " NOCREATEROLE"; } //Connection Limit? if (rs.getInt("rolconnlimit") != -1) { createRole += " CONNECTION LIMIT " + rs.getString("rolconnlimit"); } //Valid Until? if (rs.getString("rolvaliduntil") != null && !rs.getString("rolvaliduntil").equals("infinity")) { createRole += " VALID UNTIL '" + rs.getString("rolvaliduntil") + "'"; } createRole += ";\n"; //Update Catalog Direct? if (rs.getBoolean("rolsuper") == true) { if (rs.getBoolean("rolcatupdate") == false) { createRole += "UPDATE pg_catalog.pg_authid SET rolcatupdate=false WHERE rolname='" + role + "';\n"; } } //Role Membership? Statement rolestmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); rolestmt.executeQuery("SELECT rolname FROM pg_auth_members JOIN pg_catalog.pg_authid ON pg_catalog.pg_auth_members.roleid = pg_catalog.pg_authid.oid WHERE member = " + rs.getString("oid")); ResultSet rolers = rolestmt.getResultSet(); while (rolers.next()) { createRole += "GRANT \"" + rolers.getString("rolname") + "\" TO " + role + ";\n"; } if (rs.getString("description") != null) { createRole += "COMMENT ON ROLE " + role + " IS '" + rs.getString("description") + "';\n"; } } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(createRole); } return createRole; } /** * Returns create statement for supplied database * * @param database PostgreSQL database name * @return createDatabase */ public String dumpCreateDatabase(String database) { String createDatabase = null; try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); //PostgreSQL version 8.2+ if (databaseProductMajorVersion == 8 & databaseProductMinorVersion >= 2) { s.executeQuery("SELECT tableoid, oid, (SELECT rolname FROM pg_catalog.pg_roles " + "WHERE oid = datdba) AS dba, pg_encoding_to_char(encoding) AS encoding, " + "NULL AS datcollate, NULL AS datctype, datfrozenxid, " + "(SELECT spcname FROM pg_tablespace t WHERE t.oid = dattablespace) AS tablespace, " + "datconnlimit, shobj_description(oid, 'pg_database') AS description, datacl FROM pg_database " + "WHERE datname = '" + database + "';"); } ResultSet rs = s.getResultSet(); while (rs.next()) { createDatabase = "CREATE DATABASE " + database + " WITH OWNER = " + rs.getString("dba"); if (rs.getString("encoding") != null) { createDatabase += " ENCODING = '" + rs.getString("encoding") + "'"; } if (rs.getString("datcollate") != null) { createDatabase += " LC_COLLATE = '" + rs.getString("datcollate") + "'"; } if (rs.getString("datctype") != null) { createDatabase += " LC_CTYPE = '" + rs.getString("datctype") + "'"; } if (!rs.getString("tablespace").equals("pg_default")) { createDatabase += " TABLESPACE = " + rs.getString("tablespace"); } createDatabase += " CONNECTION LIMIT = " + rs.getString("datconnlimit") + ";\n"; if (rs.getString("datacl") != null) { createDatabase += parseACL(rs.getString("datacl"), database, "DATABASE"); } if (rs.getString("description") != null) { createDatabase += "COMMENT ON DATABASE " + database + " IS '" + rs.getString("description") + "';\n"; } } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(createDatabase); } return createDatabase; } /** * Returns create statement for supplied schema * * @param schema PostgreSQL role name * @return createSchema */ public String dumpCreateSchema(String schema) { String createSchema = null; try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT nspacl, description, rolname FROM pg_catalog.pg_namespace LEFT OUTER JOIN pg_catalog.pg_description ON pg_catalog.pg_namespace.oid=pg_catalog.pg_description.objoid JOIN pg_catalog.pg_roles ON pg_catalog.pg_roles.oid=pg_catalog.pg_namespace.nspowner WHERE nspname = '" + schema + "'"); ResultSet rs = s.getResultSet(); while (rs.next()) { createSchema = "CREATE SCHEMA \"" + schema + "\" AUTHORIZATION " + rs.getString("rolname") + ";\n"; if (rs.getString("nspacl") != null) { createSchema += parseACL(rs.getString("nspacl"), schema, "SCHEMA"); } if (rs.getString("description") != null) { createSchema += "COMMENT ON SCHEMA \"" + schema + "\" IS '" + rs.getString("description") + "';\n"; } } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(createSchema); } return createSchema; } /** * Returns create statement for supplied domain * * @param schema PostgreSQL schema * @param domain PostgreSQL domain name * @return createDomain */ public String dumpCreateDomain(String schema, String domain) { String createDomain = null; try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT (SELECT rolname FROM pg_catalog.pg_roles WHERE oid = typowner) as typowner, typnotnull, pg_catalog.format_type(typbasetype, typtypmod) AS typdefn, " + "pg_catalog.pg_get_expr(typdefaultbin, 'pg_catalog.pg_type'::pg_catalog.regclass) AS typdefaultbin, typdefault, description " + "FROM pg_catalog.pg_type LEFT OUTER JOIN pg_catalog.pg_description ON pg_catalog.pg_description.objoid = pg_catalog.pg_type.oid " + "WHERE pg_catalog.pg_type.oid = (SELECT oid FROM pg_catalog.pg_type WHERE typname='" + domain + "' AND typnamespace = (SELECT oid from pg_catalog.pg_namespace WHERE nspname = '" + schema + "'))::pg_catalog.oid"); ResultSet rs = s.getResultSet(); while (rs.next()) { createDomain = "CREATE DOMAIN \"" + domain + "\" AS " + rs.getString("typdefn"); //Default? if (rs.getString("typdefaultbin") != null) { createDomain += " DEFAULT " + rs.getString("typdefaultbin"); } if (rs.getBoolean("typnotnull") == true) { createDomain += " NOT NULL"; } createDomain += ";\n"; createDomain += "ALTER DOMAIN \"" + domain + "\" OWNER TO " + rs.getString("typowner") + ";\n"; //Comment? if (rs.getString("description") != null) { createDomain += "COMMENT ON DOMAIN \"" + domain + "\" IS '" + rs.getString("description") + "';\n"; } } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(createDomain); } return createDomain; } /** * Returns create statement for supplied sequence * * @param schema PostgreSQL schema * @param sequence PostgreSQL domain name * @return createSequence */ public String dumpCreateSequence(String schema, String sequence) { String createSequence = null; try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT increment_by, min_value, max_value, last_value, cache_value, " + "is_cycled, relacl, (SELECT rolname FROM pg_catalog.pg_roles WHERE oid = relowner) as relowner, " + "description FROM pg_catalog.pg_class LEFT OUTER JOIN pg_catalog.pg_description " + "ON pg_catalog.pg_description.objoid = pg_catalog.pg_class.oid " + "JOIN \"" + sequence + "\" ON pg_catalog.pg_class.relname=sequence_name " + "WHERE relkind = 'S' AND relname = '" + sequence + "'"); ResultSet rs = s.getResultSet(); while (rs.next()) { createSequence = "CREATE SEQUENCE \"" + sequence + "\" "; createSequence += "INCREMENT " + rs.getString("increment_by") + " "; createSequence += "MINVALUE " + rs.getString("min_value") + " "; createSequence += "MAXVALUE " + rs.getString("max_value") + " "; createSequence += "START " + rs.getString("last_value") + " "; createSequence += "CACHE " + rs.getString("cache_value"); if (rs.getBoolean("is_cycled") == true) { createSequence += " CYCLE"; } createSequence += ";\n"; createSequence += "ALTER TABLE \"" + sequence + "\" OWNER TO " + rs.getString("relowner") + ";\n"; //Privileges? if (rs.getString("relacl") != null) { createSequence += parseACL(rs.getString("relacl"), schema, "SEQUENCE"); } //Comment? if (rs.getString("description") != null) { createSequence += "COMMENT ON SEQUENCE \"" + sequence + "\" IS '" + rs.getString("description") + "';\n"; } } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(createSequence); } return createSequence; } /** * * @return */ public File dumpAllDatabases() { return null; } /** * * @param database * @return */ public File dumpDatabase(String database) { return null; } /** * * @param database * @return */ public File dumpAllTables(String database) { return null; } /** * Returns create statement for supplied table using property schema * * @param table PostgreSQL domain name * @return */ public String dumpCreateTable(String table) { return dumpCreateTable(schema, table); } /** * Returns create statement for supplied table * * @param schema PostgreSQL schema * @param table PostgreSQL domain name * @return createTable */ public String dumpCreateTable(String schema, String table) { String createTable = null; String comments = ""; HashMap columnNames = new HashMap(); try { Statement tablestmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); tablestmt.executeQuery("SELECT pg_catalog.pg_class.oid, relname, (SELECT rolname FROM pg_catalog.pg_roles WHERE oid = relowner) AS rolname, " + "relnatts, relhasoids, relchecks, reltriggers, " + "relhaspkey, relacl, description, spcname FROM pg_catalog.pg_class " + "LEFT OUTER JOIN pg_catalog.pg_tablespace ON pg_catalog.pg_class.reltablespace = pg_catalog.pg_tablespace.oid " + "LEFT OUTER JOIN pg_catalog.pg_description ON pg_catalog.pg_description.objoid = pg_catalog.pg_class.oid " + "WHERE relname = '" + table + "' AND relnamespace = (SELECT oid from pg_namespace " + "WHERE nspname = '" + schema + "')::pg_catalog.oid AND relkind = 'r' AND objsubid = 0"); ResultSet tablers = tablestmt.getResultSet(); while (tablers.next()) { createTable = "CREATE TABLE \"" + table + "\"\n(\n"; //Process Columns Statement columnstmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); columnstmt.executeQuery("SELECT a.attnum, a.attname, a.atttypmod, a.attstattarget, a.attstorage, " + "t.typstorage, a.attnotnull, a.atthasdef, a.attisdropped, a.attlen, a.attalign, " + "a.attislocal, pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, defs.adsrc, description " + "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t " + "ON a.atttypid = t.oid LEFT OUTER JOIN pg_catalog.pg_attrdef defs " + "ON defs.adrelid = a.attrelid AND defs.adnum = a.attnum " + "LEFT OUTER JOIN pg_catalog.pg_description ON pg_catalog.pg_description.objoid = " + "(SELECT oid FROM pg_class WHERE relname = '" + table + "' AND relnamespace = " + "(SELECT oid from pg_namespace WHERE nspname = '" + schema + "'))::pg_catalog.oid " + "AND pg_catalog.pg_description.objsubid = a.attnum WHERE a.attrelid = " + "(SELECT oid FROM pg_class WHERE relname = '" + table + "' AND " + "relnamespace = (SELECT oid from pg_namespace " + "WHERE nspname = '" + schema + "'))" + "::pg_catalog.oid " + "AND a.attnum > 0::pg_catalog.int2 AND attisdropped = FALSE ORDER BY a.attrelid, a.attnum"); ResultSet columnrs = columnstmt.getResultSet(); while (columnrs.next()) { columnNames.put(columnrs.getInt("attnum"), columnrs.getString("attname")); createTable += " \"" + columnrs.getString("attname") + "\" " + columnrs.getString("atttypname"); //NOT NULL? if (columnrs.getBoolean("attnotnull") == true) { createTable += " NOT NULL"; } //Default? if (columnrs.getBoolean("atthasdef") == true) { createTable += " DEFAULT " + columnrs.getString("adsrc"); } if (!columnrs.isLast() || tablers.getInt("relchecks") > 0) { createTable += ","; } //Comment? if (columnrs.getString("description") != null) { createTable += " -- " + columnrs.getString("description") + "\n"; comments += "COMMENT ON COLUMN \"" + table + "\".\"" + columnrs.getString("attname") + "\" IS '" + columnrs.getString("description") + "';\n"; } else { createTable += "\n"; } } //Constraints? Statement constraintstmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); constraintstmt.executeQuery("SELECT conname, contype, conkey, consrc, spcname FROM pg_catalog.pg_constraint " + "LEFT OUTER JOIN pg_catalog.pg_class ON pg_catalog.pg_constraint.conname = pg_catalog.pg_class.relname " + "LEFT OUTER JOIN pg_catalog.pg_tablespace ON pg_catalog.pg_class.reltablespace = pg_catalog.pg_tablespace.oid " + "WHERE connamespace = (SELECT oid from pg_namespace " + "WHERE nspname = 'public')::pg_catalog.oid AND conrelid = " + tablers.getString("oid")); ResultSet constraintrs = constraintstmt.getResultSet(); while (constraintrs.next()) { createTable += " CONSTRAINT \"" + constraintrs.getString("conname") + "\""; //Primary Key? if (constraintrs.getString("contype").equals("p")) { createTable += " PRIMARY KEY ("; Integer[] columns = (Integer[]) constraintrs.getArray("conkey").getArray(); for (int i = 0; i < columns.length; i++) { createTable += "\"" + columnNames.get(columns[i]) + "\""; if (i < columns.length - 1) { createTable += ","; } else { createTable += ")"; } } if (constraintrs.getString("spcname") != null) { createTable += " USING INDEX TABLESPACE \"" + constraintrs.getString("spcname") + "\""; } //Unique Constraint? } else if (constraintrs.getString("contype").equals("u")) { createTable += " UNIQUE ("; Integer[] columns = (Integer[]) constraintrs.getArray("conkey").getArray(); for (int i = 0; i < columns.length; i++) { createTable += "\"" + columnNames.get(columns[i]) + "\""; if (i < columns.length - 1) { createTable += ","; } else { createTable += ")"; } } if (constraintrs.getString("spcname") != null) { createTable += " USING INDEX TABLESPACE \"" + constraintrs.getString("spcname") + "\""; } //Check Constraints? } else if (constraintrs.getString("contype").equals("c")) { createTable += " CHECK " + constraintrs.getString("consrc"); } if (!constraintrs.isLast()) { createTable += ",\n"; } else { createTable += "\n"; } } createTable += ")\n"; //Table has OIDs? if (tablers.getBoolean("relhasoids") == true) { createTable += "WITH ( OIDS=TRUE )"; } else { createTable += "WITH ( OIDS=TRUE )"; } //Non default tablespace? if (tablers.getString("spcname") != null) { createTable += " TABLESPACE \"" + tablers.getString("spcname") + "\""; } createTable += ";\n"; //Table Owner createTable += "ALTER TABLE \"" + table + "\" OWNER TO " + tablers.getString("rolname") + ";\n"; //Privileges? if (tablers.getString("relacl") != null) { createTable += parseACL(tablers.getString("relacl"), table, "TABLE"); } //Table Comment? if (tablers.getString("description") != null) { createTable += "COMMENT ON TABLE \"" + table + "\" IS '" + tablers.getString("description") + "';\n"; } //Comments? if (!comments.equals("")) { createTable += comments; } } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(createTable); } return createTable; } /** * Returns create statement for supplied function * * @param schema PostgreSQL Schema * @param function PostgreSQL Function * @return createFunction */ public String dumpCreateFunction(String schema, String function) { String createFunction = null; try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT (SELECT rolname FROM pg_catalog.pg_roles WHERE oid = proowner) as owner, pg_catalog.format_type(prorettype::pg_catalog.oid,NULL) as prorettype, proallargtypes, proargmodes, proargnames, prosrc, lanname, provolatile, proisstrict, " + "prosecdef, procost, proretset, prorows, proacl, description FROM pg_catalog.pg_proc " + "LEFT OUTER JOIN pg_catalog.pg_description ON pg_catalog.pg_proc.oid=pg_catalog.pg_description.objoid " + "JOIN pg_language ON pg_catalog.pg_proc.prolang=pg_catalog.pg_language.oid " + "WHERE pronamespace = (SELECT oid from pg_namespace WHERE nspname = '" + schema + "')::pg_catalog.oid " + "AND proname = '" + function + "'"); ResultSet rs = s.getResultSet(); while (rs.next()) { createFunction = "CREATE OR REPLACE FUNCTION \"" + function + "\"("; //"SELECT pg_catalog.format_type('%u'::pg_catalog.oid, NULL)", if (rs.getArray("proargnames") != null) { Statement t = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); String[] argnames = (String[]) rs.getArray("proargnames").getArray(); Long[] argtypes = (Long[]) rs.getArray("proallargtypes").getArray(); String[] argmodes = (String[]) rs.getArray("proargmodes").getArray(); for (int i = 0; i < argnames.length; i++) { //IN or OUT or INOUT if (argmodes[i].equals("i")) { createFunction += "IN \"" + argnames[i] + "\" "; } else if (argmodes[i].equals("o")) { createFunction += "OUT \"" + argnames[i] + "\" "; } t.executeQuery("SELECT pg_catalog.format_type('" + argtypes[i] + "'::pg_catalog.oid, NULL) as typename"); ResultSet trs = t.getResultSet(); while (trs.next()) { createFunction += trs.getString("typename"); if (i != argnames.length - 1) { createFunction += ", "; } } } } createFunction += ") RETURNS "; if (rs.getBoolean("proretset")) { createFunction += "SETOF "; } createFunction += rs.getString("prorettype") + " AS\n"; //Single line or multi-line? if (rs.getString("prosrc").contains("\n")) { createFunction += "$$" + rs.getString("prosrc") + "$$\n"; } else { createFunction += "'" + rs.getString("prosrc") + "'\n"; } createFunction += "LANGUAGE '" + rs.getString("lanname") + "' "; if (rs.getString("provolatile").equals("s")) { createFunction += "STABLE "; } if (rs.getString("provolatile").equals("v")) { createFunction += "VOLATILE "; } if (rs.getString("provolatile").equals("i")) { createFunction += "IMMUTABLE "; } if (rs.getBoolean("proisstrict")) { createFunction += "STRICT "; } if (rs.getBoolean("prosecdef")) { createFunction += "SECURITY DEFINER "; } if (rs.getBoolean("proretset")) { createFunction += "COST " + rs.getString("procost") + " ROWS " + rs.getString("prorows") + ";\n"; } else { createFunction += "COST " + rs.getString("procost") + ";\n"; } createFunction += "ALTER FUNCTION \"" + function + "\"() OWNER TO " + rs.getString("owner") + ";\n"; if (rs.getString("proacl") != null) { createFunction += parseACL(rs.getString("proacl"), function, "FUNCTION"); } if (rs.getString("description") != null) { createFunction += "COMMENT ON FUNCTION \"" + function + "\"() IS '" + rs.getString("description") + "';\n"; } } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(createFunction); } return createFunction; } /** * Returns create statement for supplied trigger * * @param schema * @param trigger * @return createTrigger */ public String dumpCreateTrigger(String schema, String table, String trigger) { String createTrigger = null; try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT tgname, tgfoid::pg_catalog.regproc AS tgfname, tgtype, " + "tgnargs, tgargs, tgenabled, tgisconstraint, tgconstrname, tgdeferrable, " + "tgconstrrelid, tginitdeferred, t.tableoid, t.oid, " + "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname, description " + "FROM pg_catalog.pg_trigger t " + "LEFT OUTER JOIN pg_catalog.pg_description ON t.oid=pg_catalog.pg_description.objoid " + "WHERE tgrelid = (SELECT oid FROM pg_class WHERE relname = '" + table + "' " + "AND relnamespace = (SELECT oid from pg_namespace " + "WHERE nspname = '" + schema + "'))::pg_catalog.oid AND tgconstraint = 0 " + "AND tgname = '" + trigger + "'"); ResultSet rs = s.getResultSet(); while (rs.next()) { createTrigger = "CREATE TRIGGER \"" + trigger + "\"\n"; // Trigger type int findx = 0; if (((rs.getInt("tgtype")) & (1 << 1)) != 0) { createTrigger += " BEFORE"; } else { createTrigger += " AFTER"; } if (((rs.getInt("tgtype")) & (1 << 2)) != 0) { createTrigger += " INSERT"; findx++; } if (((rs.getInt("tgtype")) & (1 << 3)) != 0) { if (findx > 0) { createTrigger += " OR DELETE"; } else { createTrigger += " DELETE"; } findx++; } if (((rs.getInt("tgtype")) & (1 << 4)) != 0) { if (findx > 0) { createTrigger += " OR UPDATE"; } else { createTrigger += " UPDATE"; } } if (((rs.getInt("tgtype")) & (1 << 5)) != 0) { if (findx > 0) { createTrigger += " OR TRUNCATE"; } else { createTrigger += " TRUNCATE"; } } createTrigger += "\n ON \"" + table + "\"\n"; if (((rs.getInt("tgtype")) & (1 << 1)) != 0) { createTrigger += " FOR EACH ROW\n"; } else { createTrigger += " FOR EACH STATEMENT\n"; } createTrigger += " EXECUTE PROCEDURE " + rs.getString("tgfname") + ""; //Do we have function arguments? if (rs.getInt("tgnargs") > 0){ createTrigger += "(" + rs.getString("tgargs").substring(0, rs.getString("tgargs").length()-4).replace("\000", ",") + ");"; } else { createTrigger += "();"; } if (rs.getString("description") != null) { createTrigger += "\nCOMMENT ON TRIGGER \"" + trigger + "\" ON \"" + table + "\" IS '" + rs.getString("description") + "';"; } } } catch (SQLException e) { System.err.println(e.getMessage()); createTrigger = null; } if (verbose) { System.out.println(createTrigger); } return createTrigger; } /** * * @param out * @param table */ public void dumpTable(BufferedWriter out, String table) { try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT /*!40001 SQL_NO_CACHE */ * FROM " + table); ResultSet rs = s.getResultSet(); ResultSetMetaData rsMetaData = rs.getMetaData(); if (rs.last()) { out.write("--\n-- Dumping data for table `" + table + "`\n--\n\n"); rs.first(); } int columnCount = rsMetaData.getColumnCount(); String prefix = new String("INSERT INTO " + table + " ("); for (int i = 1; i <= columnCount; i++) { if (i == columnCount) { prefix += rsMetaData.getColumnName(i) + ") VALUES("; } else { prefix += rsMetaData.getColumnName(i) + ","; } } String postfix = new String(); int count = 0; while (rs.next()) { postfix = ""; for (int i = 1; i <= columnCount; i++) { if (i == columnCount) { //System.err.println(rs.getMetaData().getColumnClassName(i)); postfix += "'" + rs.getString(i) + "');\n"; } else { //System.err.println(rs.getMetaData().getColumnTypeName(i)); if (rs.getMetaData().getColumnTypeName(i).equalsIgnoreCase("LONGBLOB")) { try { postfix += "'" + escapeString(rs.getBytes(i)).toString() + "',"; } catch (Exception e) { postfix += "NULL,"; } } else { try { postfix += "'" + escapeString(rs.getBytes(i)).toString() + "',"; } catch (Exception e) { postfix += "NULL,"; } } } } out.write(prefix + postfix); ++count; } rs.close(); s.close(); } catch (IOException e) { System.err.println(e.getMessage()); } catch (SQLException e) { System.err.println(e.getMessage()); } } /** * * @param database * @return */ public File dumpAllViews(String database) { return null; } /** * * @param view * @return */ public String dumpCreateView(String view) { return null; } /** * * @param view * @return */ public File dumpView(String view) { return null; } /** * Return a list of databases managed by this instance * * @return databases */ public ArrayList listDatabases() { ArrayList databases = new ArrayList(); try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT * FROM pg_database WHERE datistemplate=false;"); ResultSet rs = s.getResultSet(); while (rs.next()) { databases.add(rs.getString("datname")); } } catch (SQLException e) { System.err.println(e.getMessage()); } return databases; } /** * Return a list of schemas within the connected database * * @return schemas */ public ArrayList listSchemas() { ArrayList schemas = new ArrayList(); try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT nspname FROM pg_catalog.pg_namespace WHERE " + "SUBSTRING(nspname FROM 1 FOR 3) != 'pg_' AND " + "nspname != 'information_schema'"); ResultSet rs = s.getResultSet(); while (rs.next()) { schemas.add(rs.getString("nspname")); } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(schemas.toString()); } return schemas; } /** * Return a list of user defined functions within the supplied schema * * @param schema * @return functions */ public ArrayList listFunctions(String schema) { ArrayList functions = new ArrayList(); try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT tableoid, oid, proname, prolang, pronargs, proargtypes, prorettype, " + "proacl, pronamespace FROM pg_proc WHERE NOT proisagg AND " + "pronamespace != (SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog') AND " + "pronamespace = (SELECT oid from pg_namespace WHERE nspname = '" + schema + "')::pg_catalog.oid"); ResultSet rs = s.getResultSet(); while (rs.next()) { functions.add(rs.getString("proname")); } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(functions.toString()); } return functions; } /** * Returns a list of triggers associated with the supplied schema / table * * @param table * @param schema * @return triggers */ public ArrayList listTriggers(String table, String schema) { ArrayList triggers = new ArrayList(); try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT tgname, tgfoid::pg_catalog.regproc AS tgfname, tgtype, tgnargs, tgargs, tgenabled, " + "tgisconstraint, tgconstrname, tgdeferrable, tgconstrrelid, tginitdeferred, tableoid, oid, " + "tgconstrrelid::pg_catalog.regclass AS tgconstrrelname FROM pg_catalog.pg_trigger t " + "WHERE tgrelid = (SELECT oid FROM pg_class WHERE relname = '" + table + "' AND relnamespace = (SELECT oid from pg_namespace " + "WHERE nspname = '" + schema + "'))::pg_catalog.oid::pg_catalog.oid AND tgconstraint = 0"); ResultSet rs = s.getResultSet(); while (rs.next()) { triggers.add(rs.getString(1)); } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(triggers.toString()); } return triggers; } /** * Returns a list of tables within the supplied schema * * @param schema * @return tables */ public ArrayList listTables(String schema) { ArrayList tables = new ArrayList(); try { Statement s = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); s.executeQuery("SELECT relname FROM pg_class WHERE relkind = 'r' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = '" + schema + "')::pg_catalog.oid"); ResultSet rs = s.getResultSet(); while (rs.next()) { tables.add(rs.getString(1)); } } catch (SQLException e) { System.err.println(e.getMessage()); } if (verbose) { System.out.println(tables.toString()); } return tables; } /** * * @return */ public String getDatabase() { return database; } /** * Switch Database - requires existing econnection. * * @param schema */ public void setDatabase(String database) { try{ conn.close(); connect(this.hostname, this.port, this.username, this.password, database); this.database = database; } catch (SQLException se) { System.err.println(se.getMessage()); } } /** * Return current Schema * * @return schema */ public String getSchema() { return schema; } /** * Set schema name * * @param schema */ public void setSchema(String schema) { this.schema = schema; } /** * Escape string ready for insert via pgsql client * * @param bIn String to be escaped passed in as byte array * @return bOut PostgreSQL compatible insert ready ByteArrayOutputStream */ private ByteArrayOutputStream escapeString(byte[] bIn) { int numBytes = bIn.length; ByteArrayOutputStream bOut = new ByteArrayOutputStream(numBytes + 2); for (int i = 0; i < numBytes; ++i) { byte b = bIn[i]; switch (b) { case 0: /* Must be escaped for 'mysql' */ bOut.write('\\'); bOut.write('0'); break; case '\n': /* Must be escaped for logs */ bOut.write('\\'); bOut.write('n'); break; case '\r': bOut.write('\\'); bOut.write('r'); break; case '\\': bOut.write('\\'); bOut.write('\\'); break; case '\'': bOut.write('\\'); bOut.write('\''); break; case '"': /* Better safe than sorry */ bOut.write('\\'); bOut.write('"'); break; case '\032': /* This gives problems on Win32 */ bOut.write('\\'); bOut.write('Z'); break; default: bOut.write(b); } } return bOut; } private String getHeader() { //return Dump Header return "-- BinaryStor PostgreSQL Dump " + version + "\n--\n-- Host: " + hostname + " " + "Database: " + schema + "\n-- ------------------------------------------------------\n-- Server Version: " + databaseProductVersion + "\n--"; } /** * Main entry point for PostgreSQLDump when run from command line * * @param args Command line arguments * @throws IOException */ public static void main(String[] args) throws IOException { new PostgreSQLDump().doMain(args); } /** * Parse command line arguments and run PostgreSQLDump * * @param args Command line arguments * @throws IOException */ public void doMain(String[] args) throws IOException { String usage = "Usage: java -jar PostgreSQLDump.jar [OPTIONS] database [tables]\nOR java -jar PostgreSQLDump.jar [OPTIONS] --databases [OPTIONS] DB1 [DB2 DB3...]\nOR java -jar PostgreSQLDump.jar [OPTIONS] --all-databases [OPTIONS]\nFor more options, use java -jar PostgreSQLDump.jar --help"; CmdLineParser parser = new CmdLineParser(this); // if you have a wider console, you could increase the value; // here 80 is also the default parser.setUsageWidth(80); try { // parse the arguments. parser.parseArgument(args); if (help) { throw new CmdLineException("Print Help"); } // after parsing arguments, you should check // if enough arguments are given. if (arguments.isEmpty()) { throw new CmdLineException("No argument is given"); } } catch (CmdLineException e) { if (e.getMessage().equalsIgnoreCase("Print Help")) { System.err.println("PostgreSQLDump.java Ver " + version + "\nThis software comes with ABSOLUTELY NO WARRANTY. This is free software,\nand you are welcome to modify and redistribute it under the BSD license" + "\n\n" + usage); return; } // if there's a problem in the command line, // you'll get this exception. this will report // an error message. System.err.println(e.getMessage()); // print usage. System.err.println(usage); return; } //Do we have a hostname? if not use localhost as default if (hostname == null) { hostname = "localhost"; } //First argument here should be database schema = arguments.remove(0); try { //Create temporary file to hold SQL output. File temp = File.createTempFile(schema, ".sql"); BufferedWriter out = new BufferedWriter(new FileWriter(temp)); this.connect(hostname, username, password, schema); out.write(getHeader()); for (String arg : arguments) { System.out.println(arg); out.write(dumpCreateTable(arg)); this.dumpTable(out, arg); } out.flush(); out.close(); this.cleanup(); } catch (SQLException se) { System.err.println(se.getMessage()); } } /** * * @return */ public int cleanup() { try { conn.close(); if (verbose) { System.out.println("Database connection terminated"); } } catch (Exception e) { /* ignore close errors */ } return 1; } }