From: rbt Date: Wed, 12 May 2004 16:00:32 +0000 (+0000) Subject: Initial revision X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c2fa8282e5f39d9646dd5f6a4ccb714492f06ebd;p=postgresql-autodoc Initial revision --- c2fa8282e5f39d9646dd5f6a4ccb714492f06ebd diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..1075296 --- /dev/null +++ b/.cvsignore @@ -0,0 +1,6 @@ +.*.swp +ChangeLog.bak +postgresql_autodoc +config.status +config.mk +config.log diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..8e8b8c1 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,1065 @@ +2004-02-15 08:30 rtaylor02 + + * dot.tmpl, postgresql_autodoc.pl: Escape the variables for .dot + files which may include quotes. + +2004-02-14 15:37 rtaylor02 + + * postgresql_autodoc.pl: Last commit (bug fix) reported by + + Adrian Head Bytecomm P/L + +2004-02-14 15:29 rtaylor02 + + * postgresql_autodoc.pl: Remove quote_ident() calls that caused + postgresql keywords to be quoted. These were quite ugly and + actually broke the .dot output. + +2003-12-07 16:09 rtaylor02 + + * Makefile, zigzag.dia.tmpl: New template for dia + + In databases with many tables and foreign keys dia diagram + doesn't look very nice because uml-constraint is just a line (not + zigzagline). Attached is a patch to dia.tmpl which uses + dependancies instead of constraints. On the diagram they look in + the same way, but it is possible to have more segments.It is + tested on linux Suse 8.2, Postgresql 7.3.2, postgresql_autodoc + 1.20, Dia 0.90. + + Submitted by: Atanas Karashenski + +2003-12-02 09:11 rtaylor02 + + * postgresql_autodoc.pl: Fix permissions. They were picking up + characters in the granting users name. + +2003-11-28 19:07 rtaylor02 + + * ChangeLog: 1.21 release + +2003-11-14 09:28 rtaylor02 + + * ChangeLog: Sorry, 1.21 release + +2003-11-14 09:28 rtaylor02 + + * ChangeLog: Update Changelog for 1.20 changes + +2003-11-13 21:58 rtaylor02 + + * postgresql_autodoc.pl: Correct a bug with Indexes query for + databases we do not support indexes for (7.2 and prior). + +2003-11-07 21:15 rtaylor02 + + * configure: Goes with last commit.. + +2003-11-07 21:14 rtaylor02 + + * configure.ac: Do perl module test using the form [ ! ( expression + ) ] as the current test could fail on some versions of bash. + + Reported by: Chantal Ackermann + +2003-11-07 21:12 rtaylor02 + + * regressdatabase.sql: quantity_idx to become quantity_index for + spellcheck reasons. + +2003-11-02 17:33 rtaylor02 + + * ChangeLog: Changelogs for 1.20 + +2003-11-01 18:19 rtaylor02 + + * postgresql_autodoc.pl: Remove usage of upper() in favour of uc() + +2003-11-01 18:08 rtaylor02 + + * postgresql_autodoc.pl: Add the necessary ~ to upper (don't want + character count, want a character exchange) + + Bump version number to 1.20 + +2003-11-01 17:11 rtaylor02 + + * postgresql_autodoc.pl, xml.tmpl: Minor cleanups for spellchecker + +2003-10-28 17:58 rtaylor02 + + * regressdatabase.sql: Regress the index. + +2003-10-28 17:57 rtaylor02 + + * postgresql_autodoc.pl, xml.tmpl: Quick and dirty Index + documentation for Docbook. + +2003-08-25 23:35 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Rearrange / format help + + Correct a number of hrefs within the template to use the prepared + _sgmlid identifiers. + +2003-08-25 23:16 rtaylor02 + + * Makefile, html.tmpl, postgresql_autodoc.pl, xml.tmpl: Here's + another patch. This has a small addition to the makefile to make + the binary executable (useful in development). It also has code + to get the function data and display it in the HTML template. You + might think this needs more work. I have no knowledge of the dot + and dia forms, and not very much knowledge of docbook, so I + didn't do anything in those templates. Also, I have no knowledge + of what is required for earlier versions of Postgres, so I left + that alone. + + Andrew Dunstan + + Includes a correction of most HTML links, completes the above + function elements (pre 7.3, docbook). + + Rod Taylor + +2003-08-18 09:30 rtaylor02 + + * xml.tmpl: Add View: and Table: prefixes much like HTML output. + +2003-08-18 09:24 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Here's a first patch with 3 + things: 1. "View" heading for views in HTML 2. option to specify + template location (useful in development, for example) 3. option + to specify what sort of output you want, instead of getting them + all. + + Andrew Dunstan + +2003-08-12 10:03 rtaylor02 + + * Makefile, postgresql_autodoc.pl: Ensure that a single tmpl file + will be converted without an error. + + Change the make file to install a single template file at a time. + +2003-08-11 18:01 rtaylor02 + + * ChangeLog: Add release notes for 1.12 to ChangeLog + +2003-08-11 17:56 rtaylor02 + + * html.tmpl: Add a note about where the documentation originated + from + +2003-08-11 17:52 rtaylor02 + + * postgresql_autodoc.pl: Bump version to 1.12 + +2003-08-11 17:50 rtaylor02 + + * postgresql_autodoc.pl: Complain if the template files cannot be + found. + +2003-08-11 17:47 rtaylor02 + + * html.tmpl: Formatting corrections for compliance. + +2003-08-11 17:46 rtaylor02 + + * regressdatabase.sql: Add a couple of small functions. + +2003-08-01 20:49 rtaylor02 + + * ChangeLog, Makefile: Commit notes for 1.11 + +2003-08-01 20:48 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Rename various variables for + consistency -- discovered while documenting. + +2003-07-27 20:54 rtaylor02 + + * postgresql_autodoc.pl: Remove docbook markup for plain numbers. + +2003-07-26 15:04 rtaylor02 + + * .cvsignore: Ignore ChangeLog.bak + +2003-07-26 15:03 rtaylor02 + + * ChangeLog: Introduction of the changelog. + +2003-07-26 15:03 rtaylor02 + + * sgml.tmpl: Not as useful as xml.tmpl + +2003-07-26 14:58 rtaylor02 + + * dot.tmpl, html.tmpl, postgresql_autodoc.pl, sgml.tmpl, xml.tmpl: + Rename table_description to table_comment Skip views in GraphViz + output Rename column_null to column_constraint_notnull Reorder + column items to be alphabetical Rename column_description to + column_comment Remove tables.type + + Clean whitespace + +2003-07-19 20:57 rtaylor02 + + * regressdatabase.sql: 2 field CHECK constraint and a simple view. + +2003-07-19 19:38 rtaylor02 + + * xml.tmpl: Copy of sgml.tmpl + + The SGML version is no longer going to be maintained (by me + anyway), as the XML based version is much more useful. + +2003-07-18 20:10 rtaylor02 + + * .cvsignore: Ignore generated files. + +2003-07-18 20:09 rtaylor02 + + * regressdatabase.sql: A very simple regression database. + +2003-06-27 09:41 rtaylor02 + + * postgresql_autodoc.pl: Version bump + +2003-06-27 09:41 rtaylor02 + + * sgml.tmpl: Remove the schema prefix of the function title. + +2003-06-13 14:52 rtaylor02 + + * postgresql_autodoc.pl: Improve statistics message + +2003-06-13 14:44 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Clean up the statistics code to + function as expected. + +2003-06-13 14:11 rtaylor02 + + * Makefile: Add distclean target, short for distribution-clean + +2003-05-31 13:46 rtaylor02 + + * postgresql_autodoc.pl: Reformat help using spaces rather than + tabs. + + Inform the user on what to fix when --statistics is attempted on + old DB versions. + +2003-05-31 00:03 rtaylor02 + + * dot.tmpl, postgresql_autodoc.pl: Fix inter-schema connections in + the .dot output. + +2003-05-30 09:56 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Add back schema comments. + +2003-05-25 22:23 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Prototype most functions. + + Add triggerError function (prints error and exits) + + Change statistics to use pgstattuple in PostgreSQL 7.4 Add + --statistics flag to enable them. + +2003-05-25 21:35 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: When creating the list of Fkeys + referencing the current table, be sure to compare the schema, + table and column, not just the table and column. + + Noticed By: Arthur Ward + +2003-05-21 12:21 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Rudementry stats. + +2003-05-20 13:57 rtaylor02 + + * postgresql_autodoc.pl, sgml.tmpl: Correct the schema prefix to + function names. + +2003-05-20 13:34 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl, sgml.tmpl: Treat NOT NULL + properly. + + It wasn't showing in the standard case -- but we still wanted to + hide it in the primary key case. + +2003-05-20 11:40 rtaylor02 + + * sgml.tmpl: Re-shuffle statements. + +2003-05-20 11:30 rtaylor02 + + * sgml.tmpl: Ensure we test for foreign keys WITHIN the constraint + loop! + +2003-05-14 16:17 rtaylor02 + + * postgresql_autodoc.pl: Ensure we escape $'s in queries that + aren't a part of variables. + + Noticed By: Mike Stok + +2003-05-14 09:07 rtaylor02 + + * configure, configure.ac: Redirect PERL test output with 2>&1 + rather than >&. For some reason this messed up non-cli based + configures. + +2003-05-14 08:34 rtaylor02 + + * configure, configure.ac: Use ${PERL} rather than relying on the + paths. + +2003-05-12 15:31 rtaylor02 + + * Makefile: Fix up release creation. + + Fix up installation (depends on all, create directories first) + +2003-05-12 14:58 rtaylor02 + + * Makefile: Include $(RELEASE_FILES) in the release target (doH!) + +2003-05-12 14:57 rtaylor02 + + * Makefile: Add a simple make target for release that ensure + everything is all cleaned up and ready to go. + +2003-05-10 10:03 rtaylor02 + + * postgresql_autodoc.pl: Clean up comments, touch of formatting, + and remove some unneeded (commented out) debug code. + +2003-05-10 00:04 rtaylor02 + + * postgresql_autodoc.pl: Ensure we don't pull out the constraint + name if it has been auto-generated by postgresql. The $N style + naming doesn't have any useful information, per Arthur. + + UCASE() the various SQL keywords in queries, notably AS. + +2003-05-09 23:29 rtaylor02 + + * configure, configure.ac: Silence perls loud errors by redirecting + the 'if' tests output to /dev/null + +2003-05-09 22:45 rtaylor02 + + * configure.ac: Bump comment on perl modules tested + +2003-05-09 22:44 rtaylor02 + + * configure, configure.ac: Add tests for perl modules + +2003-05-09 22:42 rtaylor02 + + * postgresql_autodoc.pl: Comment out the Data::Dumper file. It's + unneeded. + +2003-05-09 20:50 rtaylor02 + + * Makefile: Don't be so secretive. + +2003-05-09 20:49 rtaylor02 + + * Makefile: Add distribution-clean, which should be good enough to + setup tarball. + + The difference between it and maintainer-clean, is that + distribution-clean will leave the configure file. + +2003-05-09 20:41 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Add the column name to the + foreign key link. + + Ignore the plpgsql_call_handler() function in >= Pg73 + + Ensure we show schemas (set number_of_schemas variable) for + column constraints. + +2003-05-09 20:39 rtaylor02 + + * sgml.tmpl: Correct spelling of REFERENCS + + Add a . between schema and table names in the section xreflabel + +2003-05-09 16:00 rtaylor02 + + * Makefile: Install with privileges + +2003-05-09 16:00 rtaylor02 + + * configure: Minor changes to header notes + +2003-05-09 15:59 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Fix column descriptions. + + Noticed by: Arthur Ward + +2003-05-09 15:23 rtaylor02 + + * Makefile, config.mk.in, configure, configure.ac, install-sh, + postgresql_autodoc.pl: Generate a make structure with + installation support. + +2003-05-09 10:23 rtaylor02 + + * postgresql_autodoc.pl: Ensure that 7.2 (and hopefully 7.1) + multikey foreign keys work the same as the newer versions of + PostgreSQL. + + Fiddle with the code in that area to reduce the amount of + duplication. + +2003-05-09 08:17 rtaylor02 + + * dot.tmpl, postgresql_autodoc.pl: Escape the foreign key name in + GraphViz .dot files. + +2003-05-09 07:20 rtaylor02 + + * dot.tmpl: Add a connection point to the table, and re-use \N for + the name of the node. + +2003-05-09 06:51 rtaylor02 + + * dot.tmpl: Replace \N with the $table name. + + Submitted By: rp at raphinou.com + +2003-05-09 06:44 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl, sgml.tmpl: Change foreign key + structure over to allow multi-key and single-key keys to work the + same way. + + This in turn allows them to be shown with #'s attached to them + (like UNIQUE). + + Suggested By (with reference patch): Arthur Ward + + Replace 4 leading spaces with a tab for indenting. + +2003-05-06 13:06 rtaylor02 + + * dia.tmpl: The leading comment broke Dia. + +2003-05-06 10:12 rtaylor02 + + * sgml.tmpl: Fix up the sgml output to deal with multiple + multi-column unique constraints. + + Used HTML output as reference. + +2003-05-05 23:18 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Rearrange constraints so that a + multi-key unique constraint can be associated with a group + number. + + That is, multikey unique constraints are referred to as UNIQUE#1, + UNQIUE#2, etc. + + Like #N's are a part of the same constraint. + + Suggested by: Arthur Ward + +2003-05-05 20:48 rtaylor02 + + * postgresql_autodoc.pl: Sprinkle comments. + + Ensure that the Information_Schema in 7.4 is NOT documented. + +2003-05-01 09:57 rtaylor02 + + * dia.tmpl, dot.tmpl, html.tmpl, sgml.tmpl: Toss in $Header: /cvsroot/autodoc/autodoc/ChangeLog,v 1.1 2004/05/12 16:00:33 rbt Exp $ where + appropriate + + Remove style entities from dot.tmpl. It's not a + flavour of SGML. + +2003-05-01 09:53 rtaylor02 + + * postgresql_autodoc.pl, sgml.tmpl: Ensure schemas are references + in the permission block for sgml template. + + Add user variables ass required for html output. + +2003-04-30 15:53 rtaylor02 + + * postgresql_autodoc.pl: Final round on permissions. Show all + permissions a user has, not just the first. + +2003-04-30 15:39 rtaylor02 + + * postgresql_autodoc.pl: Fix permissions to list off all + permissions in sgml output. + + Add a lower() function to convert strings to lowercase + + Change more spaces to tabs where appropriate. + +2003-04-30 15:07 rtaylor02 + + * postgresql_autodoc.pl: Toss the table and usernames into the + permission block. + +2003-04-30 14:46 rtaylor02 + + * postgresql_autodoc.pl: Spaces to tabs.. + +2003-04-30 14:40 rtaylor02 + + * postgresql_autodoc.pl: permisions -> permissions + +2003-04-29 15:53 rtaylor02 + + * sgml.tmpl: Close the literal earlier... Literals aren't variable + (duh!) + +2003-04-29 15:36 rtaylor02 + + * sgml.tmpl: Gotta use a prepared id for the ID attribute. + +2003-04-29 15:29 rtaylor02 + + * sgml.tmpl: Toss the View definition into a figure. + + Fix the function title ID definition. + +2003-04-29 14:13 rtaylor02 + + * sgml.tmpl: Chapter needs to be closed. + +2003-04-29 14:04 rtaylor02 + + * sgml.tmpl: Toss the book header onto a single line. + + It makes it easier for me to munge (oh well). + +2003-04-29 10:03 rtaylor02 + + * postgresql_autodoc.pl: Fix -f (bump ARGV) + +2003-04-24 11:49 rtaylor02 + + * sgml.tmpl: Loops are plural... schemas, tables, etc. + +2003-04-24 11:40 rtaylor02 + + * postgresql_autodoc.pl: Queries exist now. Drop the comments. + +2003-04-24 10:28 rtaylor02 + + * postgresql_autodoc.pl: Simplify cross table foreign key links + (use database provided schema rather than first matching table -- + fixes problems with linking to wrote schema too). + + Rename element FK to FKTABLE (better meaning) to match FKSCHEMA. + +2003-04-24 10:15 rtaylor02 + + * postgresql_autodoc.pl: Ensure that the table of referenced tables + (foreign key links) are only displayed once per table and not + once per key. + +2003-04-24 09:57 rtaylor02 + + * postgresql_autodoc.pl: Track multi-key indicies and single-key + indicies using a common method. Let the templates work it out. + +2003-04-24 09:56 rtaylor02 + + * html.tmpl: If a column is a primary key, don't bother marking as + NOT NULL as well. + +2003-04-23 14:47 rtaylor02 + + * dot.tmpl: Change the ratio to be 'auto' rather than 1.0. + + This helps prepare for multi-key primary and foreign keys + + Submitted by: Arthur Ward + +2003-04-23 12:42 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Fix Foreign key references + where multiple schemas are involved (schema prefix was missing) + +2003-04-23 12:33 rtaylor02 + + * html.tmpl: ESCAPE="???" rather than ESCAPE=??? + + Toss in a . between schema and table in permission blocks. + +2003-04-23 12:29 rtaylor02 + + * html.tmpl: Ensure the permissions segment is treated the same as + the others when a schema prefix is required. + +2003-04-23 12:20 rtaylor02 + + * postgresql_autodoc.pl: Correct ID links. object_id needed to be + incremented prior to recording the value -- not after (duh). + +2003-04-23 11:44 rtaylor02 + + * postgresql_autodoc.pl: Clean up a slew of formatting (spaces -> + tabs) + +2003-04-23 11:34 rtaylor02 + + * postgresql_autodoc.pl: Slight stylistic improvements and updated + (or added) comments. + +2003-04-23 11:24 rtaylor02 + + * postgresql_autodoc.pl: Alphabetize fk_links + +2003-04-23 11:17 rtaylor02 + + * dia.tmpl: Ensure all TMPL_VAR entries are escaped properly. + +2003-04-23 10:59 rtaylor02 + + * html.tmpl: Fix up the HTML template for formatting. + +2003-04-22 13:07 rtaylor02 + + * postgresql_autodoc.pl: Better mark the debug segment + (Data::Dumper) and clean up trailing tabs on some lines + +2003-04-22 12:08 rtaylor02 + + * sgml.tmpl: SGML cleanups for consistency. + +2003-04-22 12:07 rtaylor02 + + * html.tmpl: Remove second ESCAPE=HTML + +2003-04-22 11:43 rtaylor02 + + * dia.tmpl, html.tmpl, postgresql_autodoc.pl, sgml.tmpl: Clean up + formatting of dia.tmpl, html.tmpl. Ensure ESCAPE=? properties + are applied where appropriate + + Remove the sgml (docbook) code from postgresql_autodoc.pl, and + toss it into a template. + + Clean up help (add single schema note). + +2003-04-17 17:14 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Have HTML output deal with + schemas properly + + Use schema.table for links and references when multiple schemas + exist -- or where otherwise hidden from the user. + +2003-04-17 14:39 rtaylor02 + + * postgresql_autodoc.pl: Allow dumping the structure of a single + schema rather than the entire system. + +2003-04-16 14:34 rtaylor02 + + * dia.tmpl, dot.tmpl, html.tmpl, postgresql_autodoc.pl: Make dia + templates actually work -- used to crash Dia. + + Clean up formatting of HTML template (ucase tmpl commands). + + Fix postgresql_autodoc to drop the asxml items. Rely on ENCODING + by HTML::Template instead. + +2003-04-15 12:53 rtaylor02 + + * dia.tmpl, dot.tmpl, postgresql_autodoc.pl: Give proper connection + numbers for dot file -- dia is special, so rename those. + + DOT template now functions... + +2003-04-15 10:17 rtaylor02 + + * dia.tmpl, dot.tmpl, postgresql_autodoc.pl: pk_fk_links -> + fk_links + + Clean up dot template to insert less new lines. + +2003-04-15 10:01 rtaylor02 + + * postgresql_autodoc.pl: Array values must be separated by ,'s + +2003-04-14 20:48 rtaylor02 + + * postgresql_autodoc.pl: Clean .dot file support out of autodoc. + Template is the preferred method. + +2003-04-14 20:47 rtaylor02 + + * dia.tmpl: Stylistic improvements. + +2003-04-14 20:44 rtaylor02 + + * dot.tmpl: Dot file -- untested. + +2003-04-14 15:54 rtaylor02 + + * dia.tmpl: Beautification + +2003-04-13 17:04 rtaylor02 + + * .cvsignore: Ignore .swp files + +2003-04-13 17:00 rtaylor02 + + * dia.tmpl, html.tmpl, new.dia.tmpl, postgresql_autodoc.pl: Remove + the new.dia.tmpl file. Add dia.tmpl in its place. + + Update autodoc to generate dia via the templated method only (has + problems for dia). + + Fix html template for multiple schemas. + +2002-10-28 09:41 rtaylor02 + + * postgresql_autodoc.pl: Correct a bug in the sql_Columns query for + 7.1 databases. It was inappropriately attempting to use a + namespace. + +2002-09-18 10:40 rtaylor02 + + * postgresql_autodoc.pl: Clean up dirname / basename generation to + handle ./ + + Clean up template munging to manage templates in a different + directory than the executable. + +2002-09-18 10:26 rtaylor02 + + * postgresql_autodoc.pl: Add blank fields to Dia output to prevent + Dia from crashing. + +2002-09-17 16:58 rtaylor02 + + * postgresql_autodoc.pl: Wrap the table and column headers in the + sgml portion in and respectively. + + These could be replaced with database fields, but it doesn't seem + to buy anything. + +2002-09-16 16:52 rtaylor02 + + * postgresql_autodoc.pl: Add a few \n's + + Just prettyprint SQL stuff once. + +2002-09-16 15:45 rtaylor02 + + * postgresql_autodoc.pl: Add in titleabbrev. + + Give title and titleabbrev ids. + + Use 'join' where possible to generate names. + +2002-09-16 14:40 rtaylor02 + + * postgresql_autodoc.pl: Fix the default depth and indent values to + make things nicer for SGML output. + +2002-09-16 14:21 rtaylor02 + + * postgresql_autodoc.pl: Pretty Print the View definition for SGML + output as well. + +2002-09-16 14:10 rtaylor02 + + * html.tmpl, postgresql_autodoc.pl: Simple view support. + +2002-09-08 14:57 orangepeel + + * postgresql_autodoc.pl: change options that were index specific + (no-index etc) to no_templates set first object_id to 0 instead + of undef + +2002-09-08 08:58 orangepeel + + * new.dia.tmpl, postgresql_autodoc.pl: beginings of dia template + called new.dia.tmpl so as not to overwrite the standard dia + output file added object id and primary key to foriegn key for + connections in dia + +2002-09-06 10:46 orangepeel + + * html.tmpl, postgresql_autodoc.pl: use HTML::Template template for + html added write_index_structure replaced with version using html + template + +2002-09-05 23:22 rtaylor02 + + * postgresql_autodoc.pl: /usr/bin/env perl + + rather than + + /usr/bin/perl + +2002-09-05 23:10 rtaylor02 + + * postgresql_autodoc.pl: sgml, not xml for default docbook + extension. + +2002-09-03 12:24 rtaylor02 + + * postgresql_autodoc.pl: Whack load of changes to simplify, and + increase the exactness of the queries used. + +2002-08-30 19:56 rtaylor02 + + * postgresql_autodoc.pl: Nicer note about the website. + +2002-08-30 15:03 rtaylor02 + + * postgresql_autodoc.pl: Make hte sgml output filename sgml. + + Make the docbook output fully sgml (suggest sx to convert to + XML). + + Add the database commect above the Index. + + Add the date of the output to the HTML documentation. + + Correct a number of formatting issues. + +2002-08-29 19:54 rtaylor02 + + * postgresql_autodoc.pl: Output SGML rather than XML. + +2002-08-28 10:24 rtaylor02 + + * postgresql_autodoc.pl: Ran the file through perltidy, which has + helped clean it up quite a bit. + +2002-08-28 10:17 rtaylor02 + + * README.dia, postgresql_autodoc.pl: Clean up title printing for + docbook book titles. + + Clean up a few error messages. + + Drop README.dia as silly. + +2002-08-23 21:39 rtaylor02 + + * postgresql_autodoc.pl: Correct some formatting issues. + +2002-08-23 21:13 rtaylor02 + + * postgresql_autodoc.pl: Only display the schema for dia output if + there is more than one. + + Remove a good chunk of Dia XML which overrode user defaults. + v0.90 doesn't crash on these. + +2002-08-23 20:44 rtaylor02 + + * postgresql_autodoc.pl: Clean up internal docs. + + Don't group in Dia unless there is more than one schema. + +2002-08-23 17:07 rtaylor02 + + * postgresql_autodoc.pl: Fix the binary name in the help file. + + Remove references of --filelist. + +2002-08-23 11:08 rtaylor02 + + * postgresql_autodoc.pl: Remove the docbook DTD declaration. + Assume that the user will include these in their sgml buildlines. + +2002-08-23 09:18 rtaylor02 + + * postgresql_autodoc.pl: Correct the namespace field name. + +2002-08-22 16:43 rtaylor02 + + * postgresql_autodoc.pl: Ignore system schemas by variable, incase + they change in the future. + +2002-08-16 16:19 rtaylor02 + + * postgresql_autodoc.pl: Correct the spelling of structure. + +2002-08-12 14:30 rtaylor02 + + * postgresql_autodoc.pl: Block quotes from the sgml identifiers. + + Enable pulling non-system functions from the db in 7.2 or prior. + +2002-07-25 19:23 rtaylor02 + + * postgresql_autodoc.pl: Fix a few docbook tags. Remove the old + style tagfix stuff. Add an optional inclusiong of 'filelist.xml' + for docbook. + +2002-07-22 10:04 rtaylor02 + + * postgresql_autodoc.pl: Lets make our XRef labels a little more + consistent. + +2002-07-21 21:02 rtaylor02 + + * postgresql_autodoc.pl: Record Hans's name in the right place. + + Add database comments to the docbook output. + +2002-07-21 10:56 rtaylor02 + + * postgresql_autodoc.pl: Clean up our management of id tags. + Remove the database name -- as that is too easily changed. + + Drop -'s from the end of an id as well, to make it easier to + create xref's + +2002-07-20 23:37 rtaylor02 + + * postgresql_autodoc.pl: To prevent array and non-array types from + conflicting, sgml_safe_id will trade [] for ARRAY- rather than + --. + +2002-07-20 22:43 rtaylor02 + + * postgresql_autodoc.pl: Ensure to replace multiple --'s with a + single - in the sgml id. + +2002-07-20 14:47 rtaylor02 + + * postgresql_autodoc.pl: - Refactor docbook output to use lists + rather than tables. This removes warnings about columns being + too wide (as there are no columns). Looks better anyway. + + - Change permissions to list off the available perms for a user, + rather than a check. + + - Update column output in 7.3 to use format_type(). + + - Allow overriding tag cleanup by prepending the comment with + @DOCBOOK. Autodoc will remove the @DOCBOOK portion and use the + string as is. Otherwise it will replace <, >, &, etc. with SGML + safe characters. + +2002-07-19 15:17 rtaylor02 + + * postgresql_autodoc.pl: Allow users to enforce that special + characters in comment blocks are not escapted. The reason for + this is to allow custom markups in the comments (HTML or + DocBook). + +2002-07-18 22:47 rtaylor02 + + * postgresql_autodoc.pl: Clean up the Docbook output mechanism to + output a book (rather than an appendix). + + Docbook format outputs functions. + + Docbook format uses hardcoded system URI rather than public due + to openjade no longer using URLs. + +2002-07-17 14:35 rtaylor02 + + * postgresql_autodoc.pl: Center permission headers in HTML output. + +2002-07-17 14:20 rtaylor02 + + * postgresql_autodoc.pl: Be somewhat more cautious with variables. + Test whether they're defined before trying to pull a value. + + Setup 7.2 and prior DBs to create teh FKGROUP entries as + $system_schema. + +2002-07-17 10:25 rtaylor02 + + * postgresql_autodoc.pl: Make files overwrite instead of + complaining. + + Display basics about user created functions (HTML output only). + + Fix display of permissions and constraints. + + Ensure full 7.3 compatibility (at this point). + +2002-07-16 10:23 rtaylor02 + + * postgresql_autodoc.pl: Apply stylesheet and HTML fixes as + supplied by Hans Schou. + +2002-07-13 22:14 rtaylor02 + + * postgresql_autodoc.pl: Add rudementry support for pulling out the + API of a function set. + +2002-07-10 10:36 rtaylor02 + + * postgresql_autodoc.pl: Update the script to use schemas instead + of the previous grouping method. + + For pre 7.3 versions use 'public' as the schema, as this would be + what they upgrade to. + +2002-06-05 09:29 rtaylor02 + + * postgresql_autodoc.pl: Ensure we catch the trigger permission as + well as user PUBLIC when no other user or group was provided. + +2002-06-04 16:31 rtaylor02 + + * postgresql_autodoc.pl: Improved job of handling old DBD modules + which don't return bytea values -- but rather return strings in + their place. + + Basically means escaping the \ in \000 or not. + +2002-06-03 11:08 rtaylor02 + + * postgresql_autodoc.pl: Escape the backslash before NULL (\000) + for regular expression parsing. + + Appears in perl 5.6 it comes out as a string rather than as an + expression + +2002-04-23 11:15 rtaylor02 + + * postgresql_autodoc.pl: Use postgresql environment variables. + +2002-04-23 09:44 rtaylor02 + + * README.dia: Brief explanation about the usage of the .dia format. + +2002-04-23 09:38 rtaylor02 + + * postgresql_autodoc.pl: Fix -u to work like -U + + Bug reported by Neil Prockter + +2002-04-18 16:24 iggie + + * postgresql_autodoc.pl: Added directed graph output in the 'dot' + language used by GraphViz. Changed connections in Dia to connect + primary/foreign key columns rather than just tables. + +2002-04-03 15:32 rtaylor02 + + * postgresql_autodoc.pl: Initial import of the original. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7047ba8 --- /dev/null +++ b/Makefile @@ -0,0 +1,95 @@ +# $Header: /cvsroot/autodoc/autodoc/Makefile,v 1.1 2004/05/12 16:00:34 rbt Exp $ + +TEMPLATES = dia.tmpl dot.tmpl html.tmpl xml.tmpl zigzag.dia.tmpl +BINARY = postgresql_autodoc +CONFIGFILE = config.mk + +RELEASE_FILES = Makefile config.mk.in configure \ + configure.ac $(TEMPLATES) install-sh \ + postgresql_autodoc.pl + +cur-dir := $(shell basename ${PWD}) +REAL_RELEASE_FILES = $(addprefix $(cur-dir)/,$(RELEASE_FILES)) + +# Global dependencies +ALWAYS_DEPEND = Makefile configure $(CONFIGFILE) + + +#### +# Test to see if $(CONFIGFILE) has been generated. If so, include it. Otherwise we assume +# it will be created for us. +has_configmk := $(wildcard $(CONFIGFILE)) + +ifeq ($(has_configmk),$(CONFIGFILE)) +include $(CONFIGFILE) +endif + +#### +# ALL +.PHONY: all +all: $(ALWAYS_DEPEND) $(BINARY) + +#### +# Replace the /usr/bin/env perl with the supplied path +# chmod to make testing easier +$(BINARY): postgresql_autodoc.pl $(CONFIGFILE) + $(SED) -e "s,/usr/bin/env perl,$(PERL)," \ + -e "s,@@TEMPLATE-DIR@@,$(datadir)," \ + postgresql_autodoc.pl > $(BINARY) + -chmod +x $(BINARY) + +#### +# INSTALL Target +.PHONY: install +install: all $(ALWAYS_DEPEND) + $(INSTALL_SCRIPT) -d $(bindir) + $(INSTALL_SCRIPT) -d $(datadir) + $(INSTALL_SCRIPT) -m 755 $(BINARY) $(bindir) + for entry in $(TEMPLATES) ; \ + do $(INSTALL_SCRIPT) -m 644 $${entry} $(datadir) ; \ + done + +#### +# CLEAN / DISTRIBUTION-CLEAN / MAINTAINER-CLEAN Targets +.PHONY: clean +clean: $(ALWAYS_DEPEND) + $(RM) $(BINARY) + +.PHONY: distribution-clean distclean +distribution-clean distclean: clean + $(RM) $(CONFIGFILE) config.log config.status + $(RM) -r autom4te.cache + $(RM) $(patsubst %.tmpl,*.%,$(wildcard *.tmpl)) + +.PHONY: maintainer-clean +maintainer-clean: distribution-clean + $(RM) configure + +#### +# Build a release +# +# Clean +# Ensure configure is up to date +# Commit any pending elements +# Tar up the results +.PHONY: release +release: distribution-clean configure $(RELEASE_FILES) + @if [ -z ${VERSION} ] ; then \ + echo "-------------------------------------------"; \ + echo "VERSION needs to be specified for a release"; \ + echo "-------------------------------------------"; \ + false; \ + fi + cvs2cl + -cvs commit + cd ../ && tar -czvf postgresql_autodoc-${VERSION}.tar.gz $(REAL_RELEASE_FILES) + +#### +# Build and Run configure files when configure or a template is updated. +configure: configure.ac + autoconf + +# Fix my makefile, then execute myself +$(CONFIGFILE) : config.mk.in configure + ./configure + $(MAKE) $(MAKEFLAGS) diff --git a/config.mk.in b/config.mk.in new file mode 100644 index 0000000..ddfc225 --- /dev/null +++ b/config.mk.in @@ -0,0 +1,13 @@ +SHELL = /bin/sh +VPATH = @srcdir@ + +# Configure replacements +INSTALL_SCRIPT = @INSTALL@ +PERL = @PERL@ +SED = @SED@ + +# installation directories +prefix = @prefix@ +exec_prefix= @exec_prefix@ +bindir = @bindir@ +datadir = @datadir@ diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..c5a3436 --- /dev/null +++ b/configure.ac @@ -0,0 +1,55 @@ +AC_PREREQ(2.53) +AC_REVISION($Header: /cvsroot/autodoc/autodoc/Attic/configure.ac,v 1.1 2004/05/12 16:00:34 rbt Exp $) +AC_INIT(postgresql_autodoc.pl) + + +# Programs +AC_PROG_INSTALL + +AC_PATH_PROGS([SED], [sed], , + [$PATH:/bin:/usr/bin]) + +# Check for Perl +AC_ARG_WITH(perl-prefix, + [ --with-perl-prefix Location of Perl], + PERLPATH=$withval, + PERLPATH="") + +if ( test -n "$PERLPATH" -a "`echo $PERLPATH | cut -c-3`" = "../" ); then + PERLPATH="$PWD/$PERLPATH" +fi + +PERL="" +if ( test -n "$PERLPATH" ); then + AC_CHECK_FILE("$PERLPATH/bin/perl",PERL="$PERLPATH/bin/perl") +else + AC_PATH_PROGS([PERL], [perl], , + [$PATH:/usr/bin:/usr/local/bin:/usr/pkg/bin:/usr/local/perl/bin:/opt/sfw/bin]) +fi +if ( test -z "$PERL" ); then + AC_MSG_ERROR("Perl is required") +fi +AC_SUBST(PERL) + + +# Check that Perl Libraries are available: +# DBI +# DBD::Pg +# Fcntl +# HTML::Template +# +# Output of if test redirected to /dev/null to keep quiet +for module in DBI DBD::Pg Fcntl HTML::Template ; do + AC_MSG_CHECKING(${module}) + if [ ! (${PERL} -e "use ${module}" 2>&1 /dev/null ) ]; then + AC_MSG_RESULT(no) + AC_MSG_ERROR(Perl module ${module} is required) + else + AC_MSG_RESULT(yes) + fi +done + + +AC_OUTPUT([ +config.mk +]) diff --git a/dia.tmpl b/dia.tmpl new file mode 100644 index 0000000..8c8ea6f --- /dev/null +++ b/dia.tmpl @@ -0,0 +1,124 @@ + + + + + + + + + "> + + ## + + + + ## + + + + + + + + + + + + + + + + + + + #PK # + + + ## + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + ## + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "> + + ## + + + " connection=""/> + " connection=""/> + + + + + diff --git a/dot.tmpl b/dot.tmpl new file mode 100644 index 0000000..6723622 --- /dev/null +++ b/dot.tmpl @@ -0,0 +1,18 @@ +digraph g { +graph [ +rankdir = "LR", +concentrate = true, +ratio = auto +]; +node [ +fontsize = "10", +shape = record +]; +edge [ +]; + +"." [shape = record, label = " \N | : \l" ]; + + +"." -> "." [label=""]; +} diff --git a/html.tmpl b/html.tmpl new file mode 100644 index 0000000..f5fe161 --- /dev/null +++ b/html.tmpl @@ -0,0 +1,292 @@ + + + + + + Index for <!-- TMPL_VAR ESCAPE="HTML" name="database" --> + + + + + + + + + +



Dumped on

+

Index of database -

+ + + + --> + + +
+

Schema

+ +

+ + + + +
+

View:Table: + + ">."> +

+ +

+ + + + + + + + + + + + + tr0tr1"> + + + + + + +
. Structure
F-KeyNameTypeDescription
+ + + ">..# + + + + + PRIMARY KEY + + + + UNIQUE# + + + + NOT NULL + DEFAULT + +

+
+ + + +

 

+ + + + + + + + + + + + + + + + +
Statistics
Total Space (disk usage)Tuple CountActive SpaceDead SpaceFree Space
+ + + + + +

 

+ + + + + + + + tr0tr1"> + + + + +
. Constraints
NameConstraint
+ + + + +

Tables referencing this one via Foreign Key Constraints:

+ + + + + + + +
+ + + + +

 

+ + + + + + + + + + + + + + tr0tr1"> + + + + + + + + + + +
Permissions which apply to .
User
Select
Insert
Update
Delete
Reference
Rule
Trigger
+ + +

+ Index - + ">Schema +

+ + + + +
+

Function: + ">."> +

+

Returns:

+

Language:

+

+
+ + + +

Generated by PostgreSQL Autodoc

+

W3C HTML 4.01 Strict

+ diff --git a/install-sh b/install-sh new file mode 100755 index 0000000..398a88e --- /dev/null +++ b/install-sh @@ -0,0 +1,251 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5 (mit/util/scripts/install.sh). +# +# Copyright 1991 by the Massachusetts Institute of Technology +# +# Permission to use, copy, modify, distribute, and sell this software and its +# documentation for any purpose is hereby granted without fee, provided that +# the above copyright notice appear in all copies and that both that +# copyright notice and this permission notice appear in supporting +# documentation, and that the name of M.I.T. not be used in advertising or +# publicity pertaining to distribution of the software without specific, +# written prior permission. M.I.T. makes no representations about the +# suitability of this software for any purpose. It is provided "as is" +# without express or implied warranty. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. It can only install one file at a time, a restriction +# shared with many OS's install programs. + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +transformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + : +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + chmodcmd="" + else + instcmd=$mkdirprog + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + : + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + : + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + : + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' + ' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + : + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else : ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else : ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else : ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else : ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + : + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else :;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else :;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else :;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else :;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/postgresql_autodoc.pl b/postgresql_autodoc.pl new file mode 100755 index 0000000..18fc9ac --- /dev/null +++ b/postgresql_autodoc.pl @@ -0,0 +1,1763 @@ +#!/usr/bin/env perl +# -- # -*- Perl -*-w +# $Header: /cvsroot/autodoc/autodoc/postgresql_autodoc.pl,v 1.1 2004/05/12 16:00:36 rbt Exp $ +# Imported 1.22 2002/02/08 17:09:48 into sourceforge + +# Postgres Auto-Doc Version 1.22 + +# License +# ------- +# Copyright (c) 2001, Rod Taylor +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. 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. +# +# 3. Neither the name of the InQuent Technologies Inc. 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 FREEBSD +# PROJECT 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. + +# About Project +# ------------- +# Various details about the project and related items can be found at +# the website +# +# http://www.rbt.ca/autodoc/ + +use DBI; +use strict; + +# Allows file locking +use Fcntl; + +## Useful for debugging ## +#use Data::Dumper; + +# Allows file templates +use HTML::Template; + +# The templates path +# @@TEMPLATE-DIR@@ will be replaced by make in the build phase +my $template_path = '@@TEMPLATE-DIR@@'; + +# Setup the default connection variables based on the environment +my $dbuser = $ENV{'PGUSER'}; +$dbuser ||= $ENV{'USER'}; + +my $database = $ENV{'PGDATABASE'}; +$database ||= $dbuser; + +my $dbhost = $ENV{'PGHOST'}; +$dbhost ||= ""; + +my $dbport = $ENV{'PGPORT'}; +$dbport ||= ""; + +my $dbpass = ""; +my $output_filename_base = $database; + +# Tracking variables +my $dbisset = 0; +my $fileisset = 0; + +my $only_schema; + +my $wanted_output = undef; # means all types + +my $statistics = 0; + +# Fetch base and dirnames. Useful for Usage() +my $basename = $0; +my $dirname = $0; +$basename =~ s|^.*/([^/]+)$|$1|; +$dirname =~ s|^(.*)/[^/]+$|$1|; + +# If template_path isn't defined, lets set it ourselves +$template_path = $dirname if (!defined($template_path)); + +for ( my $i = 0 ; $i <= $#ARGV ; $i++ ) { + ARGPARSE: for ( $ARGV[$i] ) { + # Set the database + /^-d$/ && do { + $database = $ARGV[ ++$i ]; + $dbisset = 1; + if ( !$fileisset ) { + $output_filename_base = $database; + } + last; + }; + + # Set the user + /^-[uU]$/ && do { + $dbuser = $ARGV[ ++$i ]; + if ( !$dbisset ) { + $database = $dbuser; + if ( !$fileisset ) { + $output_filename_base = $database; + } + } + last; + }; + + # Set the hostname + /^-h$/ && do { $dbhost = $ARGV[ ++$i ]; last; }; + + # Set the Port + /^-p$/ && do { $dbport = $ARGV[ ++$i ]; last; }; + + # Set the users password + /^--password=/ && do { + $dbpass = $ARGV[$i]; + $dbpass =~ s/^--password=//g; + last; + }; + + # Set the base of the filename. The extensions pulled from the templates + # will be appended to this name + /^-f$/ && do { + $output_filename_base = $ARGV[++$i]; + $fileisset = 1; + last; + }; + + # Set the template directory explicitly + /^(-l|--library)$/ && do { + $template_path = $ARGV[++$i]; + last; + }; + + # Set the output type + /^(-t|--type)$/ && do { + $wanted_output = $ARGV[++$i]; + last; + }; + + # User has requested a single schema dump and provided a pattern + /^(-s|--schema)$/ && do { + $only_schema = $ARGV[++$i]; + last; + }; + + # Check to see if Statistics have been requested + /^--statistics$/ && do { + $statistics = 1; + last; + }; + + # Help is wanted, redirect user to usage() + /^-\?$/ && do { usage(); last; }; + /^--help$/ && do { usage(); last; }; + } +} + +# If no arguments have been provided, connect to the database anyway but +# inform the user of what we're doing. +if ( $#ARGV <= 0 ) { + print <connect( $dsn, $dbuser, $dbpass ); + +# Always disconnect from the database if a database handle is setup +END { + $dbh->disconnect() if $dbh; +} + +# PostgreSQL's version is used to determine what queries are required +# to retrieve a given information set. +my $sql_GetVersion = qq{ + SELECT cast(substr(version(), 12, 1) as integer) * 10000 + + cast(substr(version(), 14, 1) as integer) * 100 + as version; +}; + +my $sth_GetVersion = $dbh->prepare($sql_GetVersion); +$sth_GetVersion->execute(); +my $version = $sth_GetVersion->fetchrow_hashref; +my $pgversion = $version->{'version'}; + +# Ensure we only get information for the requested schemas. +# +# system_schema -> The primary system schema for a database. +# Public is used for verions prior to 7.3 +# +# system_schema_list -> The list of schemas which we are not supposed +# to gather information for. +# TODO: Merge with system_schema in array form. +# +# schemapattern -> The schema the user provided as a command +# line option. +my $schemapattern = '^'; +my $system_schema; +my $system_schema_list; +if ( $pgversion >= 70300 ) { + $system_schema = 'pg_catalog'; + $system_schema_list = 'pg_catalog|information_schema'; + if (defined($only_schema)) { + $schemapattern = '^'. $only_schema .'$'; + } +} +else { + $system_schema = 'public'; + $system_schema_list = $system_schema; +} + +# +# List of queries which are used to gather information from the +# database. The queries differ based on version but should +# provide similar output. At some point it should be safe to remove +# support for older database versions. +# +my $sql_Columns; +my $sql_Constraint; +my $sql_Database; +my $sql_Foreign_Keys; +my $sql_Foreign_Key_Arg; +my $sql_Function; +my $sql_FunctionArg; +my $sql_Indexes; +my $sql_Primary_Keys; +my $sql_Schema; +my $sql_Tables; +my $sql_Table_Statistics; + +# Pull out a list of tables, views and special structures. +if ( $pgversion >= 70300 ) { + $sql_Tables = qq{ + SELECT nspname as namespace + , relname as tablename + , pg_catalog.pg_get_userbyid(relowner) AS tableowner + , relhasindex as hasindexes + , relhasrules as hasrules + , reltriggers as hastriggers + , pg_class.oid + , pg_catalog.obj_description(pg_class.oid, 'pg_class') as table_description + , relacl + , CASE + WHEN relkind = 'r' THEN + 'table' + WHEN relkind = 's' THEN + 'special' + ELSE + 'view' + END as reltype + , CASE + WHEN relkind = 'v' THEN + pg_get_viewdef(pg_class.oid) + ELSE + NULL + END as view_definition + FROM pg_catalog.pg_class + JOIN pg_catalog.pg_namespace ON (relnamespace = pg_namespace.oid) + WHERE relkind IN ('r', 's', 'v') + AND nspname !~ '$system_schema_list' + AND nspname ~ '$schemapattern'; + }; + + # - uses pg_class.oid + $sql_Columns = qq{ + SELECT attname as column_name + , attlen as column_length + , CASE + WHEN pg_type.typname = 'int4' + AND EXISTS (SELECT TRUE + FROM pg_catalog.pg_depend + JOIN pg_catalog.pg_class ON (pg_class.oid = objid) + WHERE refobjsubid = attnum + AND refobjid = attrelid + AND relkind = 'S') THEN + 'serial' + WHEN pg_type.typname = 'int8' + AND EXISTS (SELECT TRUE + FROM pg_catalog.pg_depend + JOIN pg_catalog.pg_class ON (pg_class.oid = objid) + WHERE refobjsubid = attnum + AND refobjid = attrelid + AND relkind = 'S') THEN + 'bigserial' + ELSE + pg_catalog.format_type(atttypid, atttypmod) + END as column_type + , CASE + WHEN attnotnull THEN + cast('NOT NULL' as text) + ELSE + cast('' as text) + END as column_null + , CASE + WHEN pg_type.typname IN ('int4', 'int8') + AND EXISTS (SELECT TRUE + FROM pg_catalog.pg_depend + JOIN pg_catalog.pg_class ON (pg_class.oid = objid) + WHERE refobjsubid = attnum + AND refobjid = attrelid + AND relkind = 'S') THEN + NULL + ELSE + adsrc + END as column_default + , pg_catalog.col_description(attrelid, attnum) as column_description + , attnum + FROM pg_catalog.pg_attribute + JOIN pg_catalog.pg_type ON (pg_type.oid = atttypid) + LEFT OUTER JOIN pg_catalog.pg_attrdef ON ( attrelid = adrelid + AND attnum = adnum) + WHERE attnum > 0 + AND attisdropped IS FALSE + AND attrelid = ?; + }; + +} +elsif ( $pgversion >= 70200 ) { + $sql_Tables = qq{ + SELECT 'public' as namespace + , relname as tablename + , pg_get_userbyid(relowner) AS tableowner + , relhasindex as hasindexes + , relhasrules as hasrules + , reltriggers as hastriggers + , pg_class.oid + , obj_description(pg_class.oid, 'pg_class') as table_description + , relacl + , CASE + WHEN relkind = 'r' THEN + 'table' + WHEN relkind = 's' THEN + 'special' + ELSE + 'view' + END as reltype + , CASE + WHEN relkind = 'v' THEN + pg_get_viewdef(pg_class.relname) + ELSE + NULL + END as view_definition + FROM pg_class + WHERE relkind in ('r', 's', 'v') + AND relname NOT LIKE 'pg_%'; + }; + + # - uses pg_class.oid + $sql_Columns = qq{ + SELECT attname as column_name + , attlen as column_length + , CASE + WHEN pg_type.typname = 'int4' + AND adsrc LIKE 'nextval(%' THEN + 'serial' + WHEN pg_type.typname = 'int8' + AND adsrc LIKE 'nextval(%' THEN + 'bigserial' + ELSE + format_type(atttypid, atttypmod) + END as column_type + , CASE + WHEN attnotnull IS TRUE THEN + 'NOT NULL'::text + ELSE + ''::text + END as column_null + , CASE + WHEN pg_type.typname in ('int4', 'int8') + AND adsrc LIKE 'nextval(%' THEN + NULL + ELSE + adsrc + END as column_default + , col_description(attrelid, attnum) as column_description + , attnum + FROM pg_attribute + JOIN pg_type ON (pg_type.oid = pg_attribute.atttypid) + LEFT OUTER JOIN pg_attrdef ON ( pg_attribute.attrelid = pg_attrdef.adrelid + AND pg_attribute.attnum = pg_attrdef.adnum) + WHERE attnum > 0 + AND attrelid = ?; + }; + +} +else { + # 7.1 or earlier has a different description structure + + $sql_Tables = qq{ + SELECT 'public' as namespace + , relname as tablename + , pg_get_userbyid(relowner) AS tableowner + , relhasindex as hasindexes + , relhasrules as hasrules + , reltriggers as hastriggers + , pg_class.oid + , obj_description(pg_class.oid) as table_description + , 'table' as reltype + , NULL as view_definition + FROM pg_class + WHERE relkind IN ('r', 's') + AND relname NOT LIKE 'pg_%'; + }; + + # - uses pg_class.oid + $sql_Columns = qq{ + SELECT attname as column_name + , attlen as column_length + , CASE + WHEN pg_type.typname = 'int4' + AND adsrc LIKE 'nextval(%' THEN + 'serial' + WHEN pg_type.typname = 'int8' + AND adsrc LIKE 'nextval(%' THEN + 'bigserial' + ELSE + format_type(atttypid, atttypmod) + END as column_type + , CASE + WHEN attnotnull IS TRUE THEN + 'NOT NULL'::text + ELSE + ''::text + END as column_null + , CASE + WHEN pg_type.typname in ('int4', 'int8') + AND adsrc LIKE 'nextval(%' THEN + NULL + ELSE + adsrc + END as column_default + , description as column_description + , attnum + FROM pg_attribute + JOIN pg_type ON (pg_type.oid = pg_attribute.atttypid) + LEFT OUTER JOIN pg_attrdef ON ( pg_attribute.attrelid = pg_attrdef.adrelid + AND pg_attribute.attnum = pg_attrdef.adnum) + LEFT OUTER JOIN pg_description ON (pg_description.objoid = pg_attribute.oid) + WHERE attnum > 0 + AND attrelid = ?; + }; +} + +if ($statistics == 1) +{ + if ($pgversion <= 70300) { + triggerError("Table statistics supported on PostgreSQL 7.4 and later.\nRemove --statistics flag and try again."); + } + + $sql_Table_Statistics = qq{ + SELECT table_len + , tuple_count + , tuple_len + , CAST(tuple_percent AS numeric(20,2)) AS tuple_percent + , dead_tuple_count + , dead_tuple_len + , CAST(dead_tuple_percent AS numeric(20,2)) AS dead_tuple_percent + , CAST(free_space AS numeric(20,2)) AS free_space + , CAST(free_percent AS numeric(20,2)) AS free_percent + FROM pgstattuple(CAST(? AS oid)); + }; +} + +if ($pgversion >= 70300) +{ + $sql_Indexes = qq{ + SELECT schemaname + , tablename + , indexname + , substring( indexdef + FROM position('(' IN indexdef) + 1 + FOR length(indexdef) - position('(' IN indexdef) - 1 + ) AS indexdef + FROM pg_catalog.pg_indexes + WHERE substring(indexdef FROM 8 FOR 6) != 'UNIQUE' + AND schemaname = ? + AND tablename = ?; + }; +} else { + $sql_Indexes = qq{ + SELECT NULL AS schemaname + , NULL AS tablename + , NULL AS indexname + , NULL AS indexdef + WHERE TRUE = FALSE AND ? = ?; + }; +} + + +# Fetch the list of PRIMARY and UNIQUE keys +if ($pgversion >= 70300) +{ + $sql_Primary_Keys = qq{ + SELECT conname AS constraint_name + , pg_catalog.pg_get_indexdef(d.objid) AS constraint_definition + , CASE + WHEN contype = 'p' THEN + 'PRIMARY KEY' + ELSE + 'UNIQUE' + END as constraint_type + FROM pg_catalog.pg_constraint AS c + JOIN pg_catalog.pg_depend AS d ON (d.refobjid = c.oid) + WHERE contype IN ('p', 'u') + AND deptype = 'i' + AND conrelid = ?; + }; + +} else { + # - uses pg_class.oid + $sql_Primary_Keys = qq{ + SELECT i.relname AS constraint_name + , pg_get_indexdef(pg_index.indexrelid) AS constraint_definition + , CASE + WHEN indisprimary THEN + 'PRIMARY KEY' + ELSE + 'UNIQUE' + END as constraint_type + FROM pg_index + , pg_class as i + WHERE i.oid = pg_index.indexrelid + AND pg_index.indisunique + AND pg_index.indrelid = ?; + }; +} + +# FOREIGN KEY fetch +# +# Don't return the constraint name if it was automatically generated by +# PostgreSQL. The $N (where N is an integer) is not a descriptive enough +# piece of information to be worth while including in the various outputs. +if ( $pgversion >= 70300 ) { + $sql_Foreign_Keys = qq{ + SELECT pg_constraint.oid + , pg_namespace.nspname AS namespace + , CASE WHEN substring(pg_constraint.conname FROM 1 FOR 1) = '\$' THEN '' + ELSE pg_constraint.conname + END AS constraint_name + , conkey AS constraint_key + , confkey AS constraint_fkey + , confrelid AS foreignrelid + FROM pg_catalog.pg_constraint + JOIN pg_catalog.pg_class ON (pg_class.oid = conrelid) + JOIN pg_catalog.pg_class AS pc ON (pc.oid = confrelid) + JOIN pg_catalog.pg_namespace ON (pg_class.relnamespace = pg_namespace.oid) + JOIN pg_catalog.pg_namespace AS pn ON (pn.oid = pc.relnamespace) + WHERE contype = 'f' + AND conrelid = ? + AND pg_namespace.nspname ~ '$schemapattern' + AND pn.nspname ~ '$schemapattern'; + }; + + $sql_Foreign_Key_Arg = qq{ + SELECT attname AS attribute_name + , relname AS relation_name + , nspname AS namespace + FROM pg_catalog.pg_attribute + JOIN pg_catalog.pg_class ON (pg_class.oid = attrelid) + JOIN pg_catalog.pg_namespace ON (relnamespace = pg_namespace.oid) + WHERE attrelid = ? + AND attnum = ?; + }; +} +else { + # - uses pg_class.oid + $sql_Foreign_Keys = q{ + SELECT oid + , 'public' AS namespace + , CASE WHEN substring(tgname from 1 for 1) = '$' THEN '' + ELSE tgname + END AS constraint_name + , tgnargs AS number_args + , tgargs AS args + FROM pg_trigger + WHERE tgisconstraint = TRUE + AND tgtype = 21 + AND tgrelid = ?; + }; + + $sql_Foreign_Key_Arg = qq{SELECT TRUE WHERE ? = 0 and ? = 0;}; +} + +# Fetch CHECK constraints +if ( $pgversion >= 70300 ) { + $sql_Constraint = qq{ + SELECT 'CHECK ' || pg_catalog.substr(consrc, 2, length(consrc) - 2) AS constraint_source + , conname AS constraint_name + FROM pg_constraint + WHERE conrelid = ? + AND contype = 'c'; + }; +} +else { + $sql_Constraint = qq{ + SELECT 'CHECK ' || substr(rcsrc, 2, length(rcsrc) - 2) AS constraint_source + , rcname AS constraint_name + FROM pg_relcheck + WHERE rcrelid = ?; + }; +} + +# Query for function information +if ( $pgversion >= 70300 ) { + $sql_Function = qq{ + SELECT proname AS function_name + , nspname AS namespace + , lanname AS language_name + , pg_catalog.obj_description(pg_proc.oid, 'pg_proc') AS comment + , proargtypes AS function_args + , prosrc AS source_code + , proretset AS returns_set + , prorettype AS return_type + FROM pg_catalog.pg_proc + JOIN pg_catalog.pg_language ON (pg_language.oid = prolang) + JOIN pg_catalog.pg_namespace ON (pronamespace = pg_namespace.oid) + JOIN pg_catalog.pg_type ON (prorettype = pg_type.oid) + WHERE pg_namespace.nspname !~ '$system_schema_list' + AND pg_namespace.nspname ~ '$schemapattern' + AND proname != 'plpgsql_call_handler'; + }; + + $sql_FunctionArg = qq{ + SELECT nspname AS namespace + , pg_catalog.format_type(pg_type.oid, typtypmod) AS type_name + FROM pg_catalog.pg_type + JOIN pg_catalog.pg_namespace ON (pg_namespace.oid = typnamespace) + WHERE pg_type.oid = ?; + }; +} +else { + $sql_Function = qq{ + SELECT proname AS function_name + , 'public' AS namespace + , lanname AS language_name + , description AS comment + , proargtypes AS function_args + , prosrc AS source_code + , proretset AS returns_set + , prorettype AS return_type + FROM pg_proc + JOIN pg_language ON (pg_language.oid = prolang) + LEFT OUTER JOIN pg_description ON (objoid = pg_proc.oid) + WHERE pg_proc.oid > 16000 + AND proname != 'plpgsql_call_handler'; + }; + + $sql_FunctionArg = qq{ + SELECT 'public' AS namespace + , format_type(pg_type.oid, typtypmod) AS type_name + FROM pg_type + WHERE pg_type.oid = ?; + }; +} + +# Fetch schema information. +if ( $pgversion >= 70300 ) { + $sql_Schema = qq{ + SELECT pg_catalog.obj_description(oid, 'pg_namespace') AS comment + , nspname as namespace + FROM pg_catalog.pg_namespace; + }; +} +else { + # In PostgreSQL 7.2 and prior, schemas were not a part of the system. + # Dummy query returns no rows to prevent added logic later on. + $sql_Schema = qq{SELECT TRUE WHERE TRUE = FALSE;}; +} + +# Fetch the description of the database +if ($pgversion >= 70300) { + $sql_Database = qq{ + SELECT pg_catalog.obj_description(oid, 'pg_database') as comment + FROM pg_catalog.pg_database + WHERE datname = '$database'; + }; +} +elsif ($pgversion == 70200) { + $sql_Database = qq{ + SELECT obj_description(oid, 'pg_database') as comment + FROM pg_database + WHERE datname = '$database'; + }; +} +else { + # In PostgreSQL 7.1, the database did not have comment support + $sql_Database = qq{ SELECT TRUE as comment WHERE TRUE = FALSE;}; +} + +my $sth_Columns = $dbh->prepare($sql_Columns); +my $sth_Constraint = $dbh->prepare($sql_Constraint); +my $sth_Database = $dbh->prepare($sql_Database); +my $sth_Foreign_Keys = $dbh->prepare($sql_Foreign_Keys); +my $sth_Foreign_Key_Arg = $dbh->prepare($sql_Foreign_Key_Arg); +my $sth_Function = $dbh->prepare($sql_Function); +my $sth_FunctionArg = $dbh->prepare($sql_FunctionArg); +my $sth_Indexes = $dbh->prepare($sql_Indexes); +my $sth_Primary_Keys = $dbh->prepare($sql_Primary_Keys); +my $sth_Schema = $dbh->prepare($sql_Schema); +my $sth_Tables = $dbh->prepare($sql_Tables); +my $sth_Table_Statistics = $dbh->prepare($sql_Table_Statistics); + +my %structure; +my %struct; + +# Fetch Database info +$sth_Database->execute(); +my $dbinfo = $sth_Database->fetchrow_hashref; +if ( defined($dbinfo) ) { + $struct{'DATABASE'}{$database}{'COMMENT'} = $dbinfo->{'comment'}; +} + +# Fetch tables and all things bound to tables +$sth_Tables->execute(); +while ( my $tables = $sth_Tables->fetchrow_hashref ) { + my $reloid = $tables->{'oid'}; + my $relname = $tables->{'tablename'}; + + my $group = $tables->{'namespace'}; + + EXPRESSIONFOUND: + + # Store permissions + my $acl = $tables->{'relacl'}; + + # Empty acl groups cause serious issues. + $acl ||= ''; + + # Strip array forming 'junk'. + $acl =~ s/^{//g; + $acl =~ s/}$//g; + $acl =~ s/"//g; + + # Foreach acl + foreach ( split ( /\,/, $acl ) ) { + my ( $user, $raw_permissions ) = split ( /=/, $_ ); + + if ( defined($raw_permissions) ) { + if ( $user eq '' ) { + $user = 'PUBLIC'; + } + + # The section after the / is the user who granted the permissions + my ( $permissions, $granting_user) = split ( /\//, $raw_permissions ); + + # Break down permissions to individual flags + if ( $permissions =~ /a/ ) { + $structure{$group}{$relname}{'ACL'}{$user}{'INSERT'} = 1; + } + + if ( $permissions =~ /r/ ) { + $structure{$group}{$relname}{'ACL'}{$user}{'SELECT'} = 1; + } + + if ( $permissions =~ /w/ ) { + $structure{$group}{$relname}{'ACL'}{$user}{'UPDATE'} = 1; + } + + if ( $permissions =~ /d/ ) { + $structure{$group}{$relname}{'ACL'}{$user}{'DELETE'} = 1; + } + + if ( $permissions =~ /R/ ) { + $structure{$group}{$relname}{'ACL'}{$user}{'RULE'} = 1; + } + + if ( $permissions =~ /x/ ) { + $structure{$group}{$relname}{'ACL'}{$user}{'REFERENCES'} = 1; + } + + if ( $permissions =~ /t/ ) { + $structure{$group}{$relname}{'ACL'}{$user}{'TRIGGER'} = 1; + } + } + } + + # Primitive Stats, but only if requested + if ($statistics == 1) + { + $sth_Table_Statistics->execute($reloid); + + my $stats = $sth_Table_Statistics->fetchrow_hashref; + + $structure{$group}{$relname}{'TABLELEN'} = $stats->{'table_len'}; + $structure{$group}{$relname}{'TUPLECOUNT'} = $stats->{'tuple_count'}; + $structure{$group}{$relname}{'TUPLELEN'} = $stats->{'tuple_len'}; + $structure{$group}{$relname}{'DEADTUPLELEN'} = $stats->{'dead_tuple_len'}; + $structure{$group}{$relname}{'FREELEN'} = $stats->{'free_space'}; + } + + # Store the relation type + $structure{$group}{$relname}{'TYPE'} = $tables->{'reltype'}; + + # Store table description + $structure{$group}{$relname}{'DESCRIPTION'} = $tables->{'table_description'}; + + # Store the view definition + $structure{$group}{$relname}{'VIEW_DEF'} = $tables->{'view_definition'}; + + # Store constraints + $sth_Constraint->execute($reloid); + while ( my $cols = $sth_Constraint->fetchrow_hashref ) { + my $constraint_name = $cols->{'constraint_name'}; + $structure{$group}{$relname}{'CONSTRAINT'}{$constraint_name} = + $cols->{'constraint_source'}; + } + + $sth_Columns->execute($reloid); + my $i = 1; + while ( my $cols = $sth_Columns->fetchrow_hashref ) { + my $column_name = $cols->{'column_name'}; + $structure{$group}{$relname}{'COLUMN'}{$column_name}{'ORDER'} = + $cols->{'attnum'}; + $structure{$group}{$relname}{'COLUMN'}{$column_name}{'PRIMARY KEY'} = + 0; + $structure{$group}{$relname}{'COLUMN'}{$column_name}{'FKTABLE'} = ''; + $structure{$group}{$relname}{'COLUMN'}{$column_name}{'TYPE'} = + $cols->{'column_type'}; + $structure{$group}{$relname}{'COLUMN'}{$column_name}{'NULL'} = + $cols->{'column_null'}; + $structure{$group}{$relname}{'COLUMN'}{$column_name}{'DESCRIPTION'} = + $cols->{'column_description'}; + $structure{$group}{$relname}{'COLUMN'}{$column_name}{'DEFAULT'} = + $cols->{'column_default'}; + } + + # Pull out both PRIMARY and UNIQUE keys based on the supplied query + # and the relation OID. + # + # Since there may be multiple UNIQUE indexes on a table, we append a + # number to the end of the the UNIQUE keyword which shows that they + # are a part of a related definition. I.e UNIQUE_1 goes with UNIQUE_1 + # + $sth_Primary_Keys->execute($reloid); + my $unqgroup = 0; + while ( my $pricols = $sth_Primary_Keys->fetchrow_hashref ) { + my $index_type = $pricols->{'constraint_type'}; + my $con = $pricols->{'constraint_name'}; + my $indexdef = $pricols->{'constraint_definition'}; + + # Fetch the column list + my $column_list = $indexdef; + $column_list =~ s/.*\(([^)]+)\).*/$1/g; + + # Split our column list and deal with all PRIMARY KEY fields + my @collist = split(',', $column_list); + + # Store the column number in the indextype field. Anything > 0 indicates + # the column has this type of constraint applied to it. + my $column; + my $currentcol = $#collist + 1; + my $numcols = $#collist + 1; + + # Bump group number if there are two or more columns + if ($numcols >= 2 && $index_type eq 'UNIQUE') { + $unqgroup++; + } + + # Record the data to the structure. + while ($column = pop(@collist) ) { + $column =~ s/\s$//; + $column =~ s/^\s//; + + $structure{$group}{$relname}{'COLUMN'}{$column}{'CON'}{$con}{'TYPE'} = $index_type; + + $structure{$group}{$relname}{'COLUMN'}{$column}{'CON'}{$con}{'COLNUM'} = $currentcol--; + + # Record group number only when a multi-column constraint is involved + if ($numcols >= 2 && $index_type eq 'UNIQUE') { + $structure{$group}{$relname}{'COLUMN'}{$column}{'CON'}{$con}{'KEYGROUP'} = $unqgroup; + } + } + } + + # FOREIGN KEYS like UNIQUE indexes can appear several times in a table in multi-column + # format. We use the same trick to record a numeric association to the foreign key + # reference. + # + $sth_Foreign_Keys->execute($reloid); + my $fkgroup = 0; + while (my $forcols = $sth_Foreign_Keys->fetchrow_hashref) + { + my $column_oid = $forcols->{'oid'}; + my $con = $forcols->{'constraint_name'}; + + # Declare variables for dataload + my @keylist; + my @fkeylist; + my $fgroup; + my $ftable; + + if ($pgversion >= 70300) { + my $fkey = $forcols->{'constraint_fkey'}; + my $keys = $forcols->{'constraint_key'}; + my $frelid = $forcols->{'foreignrelid'}; + + # Since decent array support was not added to 7.4, and we want to support + # 7.3 as well, we parse the text version of the array by hand rather than + # combining this and Foreign_Key_Arg query into a single query. + $fkey =~ s/^{//g; + $fkey =~ s/}$//g; + $fkey =~ s/"//g; + + $keys =~ s/^{//g; + $keys =~ s/}$//g; + $keys =~ s/"//g; + + my @keyset = split (/,/, $keys); + my @fkeyset = split (/,/, $fkey); + + # Convert the list of column numbers into column names for the + # local side. + foreach my $k (@keyset) + { + $sth_Foreign_Key_Arg->execute($reloid, $k); + + my $row = $sth_Foreign_Key_Arg->fetchrow_hashref; + + push(@keylist, $row->{'attribute_name'}); + } + + # Convert the list of columns numbers into column names for the + # referenced side. Grab the table and namespace while we're here. + foreach my $k (@fkeyset) + { + $sth_Foreign_Key_Arg->execute($frelid, $k); + + my $row = $sth_Foreign_Key_Arg->fetchrow_hashref; + + push(@fkeylist, $row->{'attribute_name'}); + $fgroup = $row->{'namespace'}; + $ftable = $row->{'relation_name'}; + } + + # Deal with common catalog issues. + die "FKEY $con Broken -- fix your PostgreSQL installation" if $#keylist != $#fkeylist; + } + else { + my $keyname; # Throw away + my $table; # Throw away + my $unspecified; # Throw away + my @columns; + + my $nargs = $forcols->{'number_args'}; + my $args = $forcols->{'args'}; + + # This database doesn't support namespaces, so use the default + $fgroup = $system_schema; + + ($keyname, $table, $ftable, $unspecified, @columns) = split(/\000/, $args); + + # Account for old versions which don't handle NULL but instead return a string + # of the escape sequence + if (!defined($ftable)) { + ($keyname, $table, $ftable, $unspecified, @columns) = split (/\\000/, $args); + } + + # Push the column list stored into @columns into the key and fkey lists + while (my $column = pop (@columns) + and my $fcolumn = pop (@columns)) + { + push(@keylist, $column); + push(@fkeylist, $fcolumn); + } + } + + # + # Load up the array based on the information discovered using the information + # retrieval methods above. + # + my $numcols = $#keylist + 1; + my $currentcol = $#keylist + 1; + + # Bump group number if there are two or more columns involved + if ($numcols >= 2) { + $fkgroup++; + } + + # Record the foreign key to structure + while (my $column = pop(@keylist) + and my $fkey = pop(@fkeylist)) + { + $structure{$group}{$relname}{'COLUMN'}{$column}{'CON'}{$con}{'TYPE'} = 'FOREIGN KEY'; + + $structure{$group}{$relname}{'COLUMN'}{$column}{'CON'}{$con}{'COLNUM'} = $currentcol--; + + $structure{$group}{$relname}{'COLUMN'}{$column}{'CON'}{$con}{'FKTABLE'} = $ftable; + $structure{$group}{$relname}{'COLUMN'}{$column}{'CON'}{$con}{'FKSCHEMA'} = $fgroup; + $structure{$group}{$relname}{'COLUMN'}{$column}{'CON'}{$con}{'FK-COL NAME'} = $fkey; + + # Record group number only when a multi-column constraint is involved + if ($numcols >= 2) { + $structure{$group}{$relname}{'COLUMN'}{$column}{'CON'}{$con}{'KEYGROUP'} = $fkgroup; + } + } + } + + # Pull out index information + $sth_Indexes->execute($group, $relname); + while (my $idx = $sth_Indexes->fetchrow_hashref) + { + $structure{$group}{$relname}{'INDEX'}{$idx->{'indexname'}} = $idx->{'indexdef'}; + } +} + +# Function Handling +$sth_Function->execute(); +while ( my $functions = $sth_Function->fetchrow_hashref ) { + my $functionname = $functions->{'function_name'} . '( '; + my $group = $functions->{'namespace'}; + my $comment = $functions->{'comment'}; + my $functionargs = $functions->{'function_args'}; + + my @types = split ( ' ', $functionargs ); + my $count = 0; + + foreach my $type (@types) { + $sth_FunctionArg->execute($type); + + my $hash = $sth_FunctionArg->fetchrow_hashref; + + if ( $count > 0 ) { + $functionname .= ', '; + } + + if ( $hash->{'namespace'} ne $system_schema ) { + $functionname .= $hash->{'namespace'} . '.'; + } + $functionname .= $hash->{'type_name'}; + $count++; + } + $functionname .= ' )'; + + my $ret_type = $functions->{'returns_set'} ? 'SET OF ' : ''; + $sth_FunctionArg->execute($functions->{'return_type'}); + my $rhash = $sth_FunctionArg->fetchrow_hashref; + $ret_type .= $rhash->{'type_name'}; + + $struct{'FUNCTION'}{$group}{$functionname}{'COMMENT'} = $comment; + $struct{'FUNCTION'}{$group}{$functionname}{'SOURCE'} = $functions->{'source_code'}; + $struct{'FUNCTION'}{$group}{$functionname}{'LANGUAGE'} = $functions->{'language_name'}; + $struct{'FUNCTION'}{$group}{$functionname}{'RETURNS'} = $ret_type; +} + +# Deal with the Schema +$sth_Schema->execute(); +while ( my $schema = $sth_Schema->fetchrow_hashref ) { + my $comment = $schema->{'comment'}; + my $namespace = $schema->{'namespace'}; + + $struct{'SCHEMA'}{$namespace}{'COMMENT'} = $comment; +} + +# Write out *ALL* templates +&write_using_templates(); + + +##### +# write_using_templates +# Generate structure that HTML::Template requires out of the +# $structure for table related information, and $struct for +# the schema and function information +# +# TODO: Finish conversion of $structure format into $struct +sub write_using_templates +{ + my @schemas; + # Start at 0, increment to 1 prior to use. + my $object_id = 0; + my %tableids; + foreach my $schema ( sort keys %structure ) { + my @tables; + foreach my $table ( sort keys %{ $structure{$schema} } ) { + # Column List + my @columns; + foreach my $column ( + sort { + $structure{$schema}{$table}{'COLUMN'}{$a}{'ORDER'} <=> + $structure{$schema}{$table}{'COLUMN'}{$b}{'ORDER'} + } keys %{ $structure{$schema}{$table}{'COLUMN'} } + ) + { + my $inferrednotnull = 0; + + # Have a shorter default for places that require it + my $shortdefault = $structure{$schema}{$table}{'COLUMN'}{$column}{'DEFAULT'}; + $shortdefault =~ s/^(.{17}).{5,}(.{5})$/$1 ... $2/g; + + # Deal with column constraints + my @colconstraints; + foreach my $con + ( sort keys %{ $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'} }) + { + if ($structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'TYPE'} eq 'UNIQUE') { + my $unq = $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'TYPE'}; + my $unqcol = $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'COLNUM'}; + my $unqgroup = $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'KEYGROUP'}; + + push @colconstraints, { + column_unique => $unq, + column_unique_colnum => $unqcol, + column_unique_keygroup => $unqgroup, + }; + } elsif ($structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'TYPE'} eq 'PRIMARY KEY') { + $inferrednotnull = 1; + push @colconstraints, { + column_primary_key => 'PRIMARY KEY', + }; + } elsif ($structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'TYPE'} eq 'FOREIGN KEY') { + my $fksgmlid = sgml_safe_id( + join('.' + , $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'FKSCHEMA'} + , $structure{$schema}{$table}{'TYPE'} + , $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'FKTABLE'})); + + my $fkgroup = $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'KEYGROUP'}; + my $fktable = $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'FKTABLE'}; + my $fkcol = $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'FK-COL NAME'}; + my $fkschema = $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'FKSCHEMA'}; + + push @colconstraints, { + column_fk => 'FOREIGN KEY', + column_fk_colnum => $fkcol, + column_fk_keygroup => $fkgroup, + column_fk_schema => $fkschema, + column_fk_schema_dbk => docbook($fkschema), + column_fk_schema_dot => graphviz($fkschema), + column_fk_sgmlid => $fksgmlid, + column_fk_table => $fktable, + column_fk_table_dbk => docbook($fktable), + }; + + # only have the count if there is more than 1 schema + if (scalar(keys %structure) > 1) { + $colconstraints[-1]{"number_of_schemas"} = scalar(keys %structure); + } + } + } + + + # Generate the Column array + push @columns, { + column => $column, + column_dbk => docbook($column), + column_dot => graphviz($column), + column_default => $structure{$schema}{$table}{'COLUMN'}{$column}{'DEFAULT'}, + column_default_dbk => docbook($structure{$schema}{$table}{'COLUMN'}{$column}{'DEFAULT'}), + column_default_short => $shortdefault, + column_default_short_dbk => docbook($shortdefault), + + column_comment => $structure{$schema}{$table}{'COLUMN'}{$column}{'DESCRIPTION'}, + column_comment_dbk => docbook($structure{$schema}{$table}{'COLUMN'}{$column}{'DESCRIPTION'}), + + column_number => $structure{$schema}{$table}{'COLUMN'}{$column}{'ORDER'}, + + column_type => $structure{$schema}{$table}{'COLUMN'}{$column}{'TYPE'}, + column_type_dbk => docbook($structure{$schema}{$table}{'COLUMN'}{$column}{'TYPE'}), + + column_constraints => \@colconstraints, + }; + + if ($inferrednotnull == 0) { + $columns[-1]{"column_constraint_notnull"} = + $structure{$schema}{$table}{'COLUMN'}{$column}{'NULL'}; + } + } + + # Constraint List + my @constraints; + foreach my $constraint (sort keys %{$structure{$schema}{$table}{'CONSTRAINT'}}) { + my $shortcon = $structure{$schema}{$table}{'CONSTRAINT'}{$constraint}; + $shortcon =~ s/^(.{30}).{5,}(.{5})$/$1 ... $2/g; + push @constraints, { + constraint => $structure{$schema}{$table}{'CONSTRAINT'}{$constraint}, + constraint_dbk => docbook($structure{$schema}{$table}{'CONSTRAINT'}{$constraint}), + constraint_name => $constraint, + constraint_name_dbk => docbook($constraint), + constraint_short => $shortcon, + constraint_short_dbk => docbook($shortcon), + table => $table, + table_dbk => docbook($table), + table_dot => graphviz($table), + }; + } + + # Index List + my @indexes; + foreach my $index (sort keys %{$structure{$schema}{$table}{'INDEX'}}) { + push @indexes, { + index_definition => $structure{$schema}{$table}{'INDEX'}{$index}, + index_definition_dbk => docbook($structure{$schema}{$table}{'INDEX'}{$index}), + index_name => $index, + index_name_dbk => docbook($index), + table => $table, + table_dbk => docbook($table), + table_dot => graphviz($table), + schema => $schema, + schema_dbk => docbook($schema), + schema_dot => graphviz($schema), + }; + } + + # Foreign Key Discovery + # + # $lastmatch is used to ensure that we only supply a result a single time and not once + # for each link found. Since the loops are sorted, we only need to track the last + # element, and not all supplied elements. + my @fk_schemas; + my $lastmatch = ''; + foreach my $fk_schema ( sort keys %structure ) { + foreach my $fk_table ( sort keys %{ $structure{$fk_schema} } ) { + foreach my $fk_column ( + sort keys %{ $structure{$fk_schema}{$fk_table}{'COLUMN'} } ) + { + foreach my $con ( + sort keys %{$structure{$fk_schema}{$fk_table}{'COLUMN'}{$fk_column}{'CON'}} + ) { + if ( + $structure{$fk_schema}{$fk_table}{'COLUMN'}{$fk_column}{'CON'}{$con}{'TYPE'} eq 'FOREIGN KEY' + && $structure{$fk_schema}{$fk_table}{'COLUMN'}{$fk_column}{'CON'}{$con}{'FKTABLE'} eq $table + && $structure{$fk_schema}{$fk_table}{'COLUMN'}{$fk_column}{'CON'}{$con}{'FKSCHEMA'} eq $schema + && $lastmatch ne "$fk_schema$fk_table" + ) + { + my $fksgmlid = sgml_safe_id( + join('.',$fk_schema + , $structure{$fk_schema}{$fk_table}{'TYPE'} + , $fk_table)); + push @fk_schemas, { + fk_column_number => $structure{$fk_schema}{$fk_table}{'COLUMN'}{$fk_column}{'ORDER'}, + fk_sgmlid => $fksgmlid, + fk_schema => $fk_schema, + fk_schema_dbk => docbook($fk_schema), + fk_schema_dot => graphviz($fk_schema), + fk_table => $fk_table, + fk_table_dbk => docbook($fk_table), + fk_table_dot => graphviz($fk_table), + }; + + # only have the count if there is more than 1 schema + if (scalar(keys %structure) > 1) { + $fk_schemas[-1]{"number_of_schemas"} = scalar(keys %structure); + } + + $lastmatch = "$fk_schema$fk_table"; + } + } + } + } + } + + # List off permissions + my @permissions; + foreach my $user ( sort keys %{ $structure{$schema}{$table}{'ACL'} } ) { + push @permissions, { + schema => $schema, + schema_dbk => docbook($schema), + schema_dot => graphviz($schema), + table => $table, + table_dbk => docbook($table), + table_dot => graphviz($table), + user => $user, + user_dbk => docbook($user), + }; + + # only have the count if there is more than 1 schema + if (scalar(keys %structure) > 1) { + $permissions[-1]{"number_of_schemas"} = scalar(keys %structure); + } + + foreach my $perm ( keys %{ $structure{$schema}{$table}{'ACL'}{$user} } ) { + if ( $structure{$schema}{$table}{'ACL'}{$user}{$perm} == 1 ) { + $permissions[-1]{lower($perm)} = 1; + } + } + + } + + # Increment and record the object ID + $tableids{"$schema$table"} = ++$object_id; + my $viewdef = sql_prettyprint($structure{$schema}{$table}{'VIEW_DEF'}); + + push @tables, { + object_id => $object_id, + object_id_dbk => docbook($object_id), + + schema => $schema, + schema_dbk => docbook($schema), + schema_dot => graphviz($schema), + schema_sgmlid => sgml_safe_id($schema.".schema"), + + # Statistics + stats_enabled => $statistics, + stats_dead_bytes => useUnits($structure{$schema}{$table}{'DEADTUPLELEN'}), + stats_dead_bytes_dbk => docbook(useUnits($structure{$schema}{$table}{'DEADTUPLELEN'})), + stats_free_bytes => useUnits($structure{$schema}{$table}{'FREELEN'}), + stats_free_bytes_dbk => docbook(useUnits($structure{$schema}{$table}{'FREELEN'})), + stats_table_bytes => useUnits($structure{$schema}{$table}{'TABLELEN'}), + stats_table_bytes_dbk => docbook(useUnits($structure{$schema}{$table}{'TABLELEN'})), + stats_tuple_count => $structure{$schema}{$table}{'TUPLECOUNT'}, + stats_tuple_count_dbk => docbook($structure{$schema}{$table}{'TUPLECOUNT'}), + stats_tuple_bytes => useUnits($structure{$schema}{$table}{'TUPLELEN'}), + stats_tuple_bytes_dbk => docbook(useUnits($structure{$schema}{$table}{'TUPLELEN'})), + + table => $table, + table_dbk => docbook($table), + table_dot => graphviz($table), + table_sgmlid => sgml_safe_id(join('.', $schema, $structure{$schema}{$table}{'TYPE'}, $table)), + table_comment => $structure{$schema}{$table}{'DESCRIPTION'}, + table_comment_dbk => docbook($structure{$schema}{$table}{'DESCRIPTION'}), + view_definition => $viewdef, + view_definition_dbk => docbook($viewdef), + columns => \@columns, + constraints => \@constraints, + fk_schemas => \@fk_schemas, + indexes => \@indexes, + permissions => \@permissions, + }; + + # only have the count if there is more than 1 schema + if (scalar(keys %structure) > 1) { + $tables[-1]{"number_of_schemas"} = scalar(keys %structure); + } + } + + # Dump out list of functions + my @functions; + foreach my $function ( sort keys %{ $struct{'FUNCTION'}{$schema} } ) { + push @functions, { + function => $function, + function_dbk => docbook($function), + function_sgmlid => sgml_safe_id(join('.', $schema, 'function', $function)), + function_comment => $struct{'FUNCTION'}{$schema}{$function}{'COMMENT'}, + function_comment_dbk => docbook($struct{'FUNCTION'}{$schema}{$function}{'COMMENT'}), + function_language => uc($struct{'FUNCTION'}{$schema}{$function}{'LANGUAGE'}), + function_returns => $struct{'FUNCTION'}{$schema}{$function}{'RETURNS'}, + function_source => $struct{'FUNCTION'}{$schema}{$function}{'SOURCE'}, + schema => $schema, + schema_dbk => docbook($schema), + schema_dot => graphviz($schema), + schema_sgmlid => sgml_safe_id($schema.".schema"), + }; + + # only have the count if there is more than 1 schema + if (scalar(keys %structure) > 1) { + $functions[-1]{"number_of_schemas"} = scalar(keys %structure); + } + } + + push @schemas, { + schema => $schema, + schema_dbk => docbook($schema), + schema_dot => graphviz($schema), + schema_sgmlid => sgml_safe_id($schema.".schema"), + schema_comment => $struct{'SCHEMA'}{$schema}{'COMMENT'}, + schema_comment_dbk => docbook($struct{'SCHEMA'}{$schema}{'COMMENT'}), + functions => \@functions, + tables => \@tables, + }; + + # Build the array of schemas + if (scalar(keys %structure) > 1) { + $schemas[-1]{"number_of_schemas"} = scalar(keys %structure); + } + } + + # Link the various components together via the template. + my @fk_links; + my @fkeys; + foreach my $schema ( sort keys %structure ) { + foreach my $table ( sort keys %{ $structure{$schema} } ) { + foreach my $column ( + sort { + $structure{$schema}{$table}{'COLUMN'}{$a}{'ORDER'} <=> + $structure{$schema}{$table}{'COLUMN'}{$b}{'ORDER'} + } + keys %{ $structure{$schema}{$table}{'COLUMN'} } + ) { + foreach my $con ( + sort keys %{$structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}} + ) { + # To prevent a multi-column foreign key from appearing several times, we've opted + # to simply display the first column of any given key. Since column numbering + # always starts at 1 for foreign keys. + if ( $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'TYPE'} + eq 'FOREIGN KEY' + && $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'COLNUM'} + == 1 ) + { + # Pull out some of the longer keys + my $ref_table = $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'FKTABLE'}; + my $ref_schema = $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'FKSCHEMA'}; + my $ref_column = $structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'FK-COL NAME'}; + + # Default values cause these elements to attach to the bottom in Dia + # + # If a KEYGROUP is not defined, it's a single column. Modify the ref_con + # and key_con variables to attach the to the columns connection point + # directly. + my $ref_con = 0; + my $key_con = 0; + my $keycon_offset = 0; + if (!defined($structure{$schema}{$table}{'COLUMN'}{$column}{'CON'}{$con}{'KEYGROUP'})) { + $ref_con = $structure{$ref_schema}{$ref_table}{'COLUMN'}{$ref_column}{'ORDER'}; + $key_con = $structure{$schema}{$table}{'COLUMN'}{$column}{'ORDER'}; + $keycon_offset = 1; + } + + # Bump object_id + $object_id++; + + push @fk_links, { + fk_link_name => $con, + fk_link_name_dbk => docbook($con), + fk_link_name_dot => graphviz($con), + handle0_connection => $key_con, + handle0_connection_dbk => docbook($key_con), + handle0_connection_dia => 6 + ($key_con * 2), + handle0_name => $table, + handle0_name_dbk => docbook($table), + handle0_schema => $schema, + handle0_to => $tableids{"$schema$table"}, + handle0_to_dbk => docbook($tableids{"$schema$table"}), + handle1_connection => $ref_con, + handle1_connection_dbk => docbook($ref_con), + handle1_connection_dia => 6 + ($ref_con * 2) + $keycon_offset, + handle1_name => $ref_table, + handle1_name_dbk => docbook($ref_table), + handle1_schema => $ref_schema, + handle1_to => $tableids{"$ref_schema$ref_table"}, + handle1_to_dbk => docbook($tableids{"$ref_schema$ref_table"}), + object_id => $object_id, + object_id_dbk => docbook($object_id), + }; + + # Build the array of schemas + if (scalar(keys %structure) > 1) { + $fk_links[-1]{"number_of_schemas"} = scalar(keys %structure); + } + } + } + } + } + } + +### FOR DEBUGGING ### +# print Data::Dumper->Dump(\@schemas); + + # Make database level comment information + my @timestamp = localtime(); + my $dumped_on = sprintf("%04d-%02d-%02d", $timestamp[5]+1900, $timestamp[4]+1, $timestamp[3]); + my $database_comment = $struct{'DATABASE'}{$database}{'COMMENT'}; + + # Loop through each template found in the supplied path. Output the results of the template + # as . into the current working directory. + my @template_files = glob($template_path .'/*.tmpl'); + + # Ensure we've told the user if we don't find any files. + triggerError("Templates files not found in $template_path") + if ($#template_files < 0); + + # Process all found templates. + foreach my $template_file (@template_files) { + (my $file_extension = $template_file) =~ s/^(?:.*\/|)([^\/]+)\.tmpl$/$1/; + next if (defined($wanted_output) && $file_extension ne $wanted_output); + my $output_filename = "$output_filename_base.$file_extension"; + print "Producing $output_filename from $template_file\n"; + + my $template = HTML::Template->new( + filename => $template_file, + die_on_bad_params => 0, + global_vars => 0, + strict => 1, + loop_context_vars => 1 + ); + + $template->param( + database => $database, + database_dbk => docbook($database), + database_sgmlid => sgml_safe_id($database), + database_comment => $database_comment, + database_comment_dbk => docbook($database_comment), + dumped_on => $dumped_on, + dumped_on_dbk => docbook($dumped_on), + fk_links => \@fk_links, + schemas => \@schemas, + ); + + sysopen( FH, $output_filename, O_WRONLY | O_TRUNC | O_CREAT, 0644 ) + or die "Can't open $output_filename: $!"; + print FH $template->output(); + } +} + + +###### +# sgml_safe_id +# Safe SGML ID Character replacement +sub sgml_safe_id($) { + my $string = shift; + + # Lets use the keyword ARRAY in place of the square brackets + # to prevent duplicating a non-array equivelent + $string =~ s/\[\]/ARRAY-/g; + + # Brackets, spaces, commads, underscores are not valid 'id' characters + # replace with as few -'s as possible. + $string =~ s/[ "',)(_-]+/-/g; + + # Don't want a - at the end either. It looks silly. + $string =~ s/-$//g; + + return ($string); +} + +##### +# lower +# LowerCase the string +sub lower($) { + my $string = shift; + + $string =~ tr/A-Z/a-z/; + + return ($string); +} + +##### +# useUnits +# Tack on base 2 metric units +sub useUnits($) { + my $value = shift; + + my @units = ('Bytes', 'KiBytes', 'MiBytes', 'GiBytes', 'TiBytes'); + my $loop = 0; + + while ($value >= 1024) + { + $loop++; + + $value = $value / 1024; + } + + return(sprintf("%.2f %s", $value, $units[$loop])); +} + +##### +# docbook +# Docbook output is special in that we may or may not want to escape +# the characters inside the string depending on a string prefix. +sub docbook($) { + my $string = shift; + + if ( defined($string) ) { + if ( $string =~ /^\@DOCBOOK/ ) { + $string =~ s/^\@DOCBOOK//; + } + else { + $string =~ s/&(?!(amp|lt|gr|apos|quot);)/&/g; + $string =~ s//>/g; + $string =~ s/'/'/g; + $string =~ s/"/"/g; + } + } + else { + # Return an empty string when all else fails + $string = ''; + } + + return ($string); +} + +##### +# graphviz +# GraphViz output requires that special characters (like " and whitespace) must be preceeded +# by a \ when a part of a lable. +sub graphviz($) { + my $string = shift; + + # Ensure we don't return an least a empty string + $string = '' if (!defined($string)); + + $string =~ s/([\s"'])/\\$1/g; + + return ($string); +} + + +##### +# sql_prettyprint +# Clean up SQL into something presentable +sub sql_prettyprint($) +{ + my $string = shift; + + # If nothing has been sent in, return an empty string + if (!defined($string)) + { + return ''; + } + + # Initialize Result string + my $result = ''; + + # List of tokens to split on + my $tok = "SELECT|FROM|WHERE|HAVING|GROUP BY|ORDER BY|OR|AND|LEFT JOIN|RIGHT JOIN". + "|LEFT OUTER JOIN|LEFT INNER JOIN|INNER JOIN|RIGHT OUTER JOIN|RIGHT INNER JOIN". + "|JOIN|UNION ALL|UNION|EXCEPT|USING|ON|CAST|[\(\),]"; + + my $key = 0; + my $bracket = 0; + my $depth = 0; + my $indent = 6; + + # XXX: Split is wrong -- match would do + foreach my $elem (split(/(\"[^\"]*\"|'[^']*'|$tok)/, $string)) + { + my $format; + + # Skip junk tokens + if ($elem =~ /^[\s]?$/) + { + next; + } + + # NOTE: Should we drop leading spaces? + # $elem =~ s/^\s//; + + # Close brackets are special + # Bring depth in a level + if ($elem =~ /\)/) + { + $depth = $depth - $indent; + if ($key == 1 or $bracket == 1) + { + $format = "%s%s"; + } else + { + $format = "%s\n%". $depth ."s"; + } + + $key = 0; + $bracket = 0; + } + # Open brackets are special + # Bump depth out a level + elsif ($elem =~ /\(/) + { + if ($key == 1) + { + $format = "%s %s"; + } else + { + $format = "%s\n%". $depth ."s"; + } + $depth = $depth + $indent; + $bracket = 1; + $key = 0; + } + # Key element + # Token from our list -- format on left hand side of the equation + # when appropriate. + elsif ($elem =~ /$tok/) + { + if ($key == 1) + { + $format = "%s%s"; + } else + { + $format = "%s\n%". $depth ."s"; + } + + $key = 1; + $bracket = 0; + } + # Value + # Format for right hand side of the equation + else { + $format = "%s%s"; + + $key = 0; + } + + # Add the new format string to the result + $result = sprintf($format, $result, $elem); + } + + return $result; +} + +## +# triggerError +# Print out a supplied error message and exit the script. +sub triggerError($) +{ + my $error = shift; + + # Test error + if (!defined($error) || $error eq '') + { + triggerError("triggerError: Unknown error"); + } + printf("\n\n%s\n", $error); + + exit 2; +} + +##### +# usage +# Usage +sub usage() { + print < Specify database name to connect to (default: $database) + -f Specify output file prefix (default: $database) + -h Specify database server host (default: localhost) + -p Specify database server port (default: 5432) + -u Specify database username (default: $dbuser) + --password= Specify database password (default: blank) + + -l Path to the templates (default: @@TEMPLATE-DIR@@) + -t Type of output wanted (default: All in template library) + + -s Specify a specific schema to match. Technically this is a regular + expression but anything other than a specific name may have unusual + results. + + --statistics In 7.4 and later, with the contrib module pgstattuple installed we + can gather statistics on the tables in the database + (average size, free space, disk space used, dead tuple counts, etc.) + This is disk intensive on large databases as all pages must be visited. +USAGE + ; + exit 1; +} diff --git a/regressdatabase.sql b/regressdatabase.sql new file mode 100644 index 0000000..8d5890f --- /dev/null +++ b/regressdatabase.sql @@ -0,0 +1,115 @@ +-- +-- $Id: regressdatabase.sql,v 1.1 2004/05/12 16:00:37 rbt Exp $ +-- + +BEGIN; +-- +-- Foreign key'd structure, check constraints, primary keys +-- and duplicate table names in different schemas +-- +CREATE SCHEMA product + CREATE TABLE product + ( product_id SERIAL PRIMARY KEY + , product_code text NOT NULL UNIQUE + CHECK(product_code = upper(product_code)) + , product_description text + ); + +CREATE SCHEMA store + CREATE TABLE store + ( store_id SERIAL PRIMARY KEY + , store_code text NOT NULL UNIQUE + CHECK(store_code = upper(store_code)) + , store_description text + ) + + CREATE TABLE inventory + ( store_id integer REFERENCES store + ON UPDATE CASCADE ON DELETE RESTRICT + , product_id integer REFERENCES product.product + ON UPDATE CASCADE ON DELETE RESTRICT + , PRIMARY KEY(store_id, product_id) + , quantity integer NOT NULL CHECK(quantity > 0) + ); + +-- +-- Another schema with +-- +CREATE SCHEMA warehouse + CREATE TABLE warehouse + ( warehouse_id SERIAL PRIMARY KEY + , warehouse_code text NOT NULL UNIQUE + CHECK(warehouse_code = upper(warehouse_code)) + , warehouse_manager text NOT NULL + , warehouse_supervisor text UNIQUE + , warehouse_description text + , CHECK (upper(warehouse_manager) != upper(warehouse_supervisor)) + ) + CREATE TABLE inventory + ( warehouse_id integer REFERENCES warehouse + ON UPDATE CASCADE + ON DELETE RESTRICT + , product_id integer REFERENCES product.product + ON UPDATE CASCADE + ON DELETE RESTRICT + , PRIMARY KEY(warehouse_id, product_id) + , quantity integer NOT NULL + CHECK(quantity > 0) + ) + CREATE VIEW products AS + SELECT DISTINCT product.* + FROM inventory + JOIN product.product USING (product_id); + +-- Sample index +CREATE INDEX quantity_index ON warehouse.inventory (quantity); + +-- +-- Simple text comments +-- +--COMMENT ON DATABASE IS +--'This database has been created for the purpose of simple +-- tests on PostgreSQL Autodoc.'; + +COMMENT ON SCHEMA product IS +'This schema stores a list of products and information + about the product'; + +COMMENT ON SCHEMA warehouse IS +'A list of warehouses and information on warehouses'; + +COMMENT ON TABLE warehouse.inventory IS +'Warehouse inventory'; + +COMMENT ON TABLE store.inventory IS +'Store inventory'; + +COMMENT ON COLUMN warehouse.warehouse.warehouse_code IS +'Internal code which represents warehouses for + invoice purposes'; + +COMMENT ON COLUMN warehouse.warehouse.warehouse_supervisor IS +'Supervisors name for a warehouse when one + has been assigned. The same supervisor may not + be assigned to more than one warehouse, per company + policy XYZ.'; + +COMMENT ON COLUMN warehouse.warehouse.warehouse_manager IS +'Name of Warehouse Manager'; + +-- +-- A few simple functions +-- +CREATE FUNCTION product.worker(integer, integer) RETURNS integer AS +'SELECT $1 + $1;' LANGUAGE sql; + +CREATE FUNCTION warehouse.worker(integer, integer) RETURNS integer AS +'SELECT $1 * $1;' LANGUAGE sql; + +COMMENT ON FUNCTION product.worker(integer, integer) IS +'Worker function appropriate for products'; + +COMMENT ON FUNCTION warehouse.worker(integer, integer) IS +'Worker function appropriate for warehouses.'; + +END; diff --git a/xml.tmpl b/xml.tmpl new file mode 100644 index 0000000..13d0188 --- /dev/null +++ b/xml.tmpl @@ -0,0 +1,203 @@ + + +" xreflabel=" database schema"><!-- TMPL_VAR name="database_dbk" --> Model + + + + + + + " + xreflabel=""> + Schema <!-- TMPL_VAR name="schema_dbk" --> + + + +
" + xreflabel="."> + -title"> + <!-- TMPL_IF name="view_definition" -->View: + <!-- TMPL_ELSE -->Table: + <!-- /TMPL_IF name="view_definition" --> + <structname><!-- TMPL_VAR name="table_dbk" --></structname> + + + + + + + + + + + + Structure of <structname><!-- TMPL_VAR name="table_dbk" --></structname> + + + + + + + + + + PRIMARY KEY + + + + + UNIQUE# + + + + + NOT NULL + + + + DEFAULT + + + + + REFERENCES "/> + + + + + + + + + + + + + + + + + Constraints on <!-- TMPL_VAR name="table_dbk" --> + + + + + + + + + + + + + + Indexes on <!-- TMPL_VAR name="table_dbk" --> + + + + + + + + + + + + + + + Tables referencing <!-- TMPL_IF name="number_of_schemas" --><!-- TMPL_VAR ESCAPE="HTML" name="fk_schema_dbk" -->.<!-- /TMPL_IF name="number_of_schemas" --><!-- TMPL_VAR ESCAPE="HTML" name="fk_table_dbk" --> via Foreign Key Constraints + + + + + "/> + + + + + + + + +
+ Definition of view <!-- TMPL_VAR name="table_dbk" --> + +
+ + + + + Permissions on <!-- TMPL_IF name="number_of_schemas" --><!-- TMPL_VAR ESCAPE="HTML" name="schema" -->.<!-- /TMPL_IF name="number_of_schemas" --><!-- TMPL_VAR name="table_dbk" --> + + + + + + + + Select + + + Insert + + + Update + + + Delete + + + Rule + + + References + + + Trigger + + + + + + + + + + +
+
+ + + + --> +
" + xreflabel=""> + -title"> + <!-- TMPL_VAR name="function_dbk" --> + + -titleabbrev"> + + + + + + Function Properties + + Language + Return Type + + + + + + + + + +
+ +
+ +
+ diff --git a/zigzag.dia.tmpl b/zigzag.dia.tmpl new file mode 100644 index 0000000..70327aa --- /dev/null +++ b/zigzag.dia.tmpl @@ -0,0 +1,150 @@ + + + + + + + + + "> + + ## + + + + ## + + + + + + + + + + + + + + + + + + + #PK # + + + ## + + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + ## + + + + + + + + + + + + + + ## + + + ## + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "> + + + + + + + + + + + + + + + + + + + + + + ## + + + ## + + + + + + " connection=""/> + " connection=""/> + + + + +