Q9.5: dbschema.pl
In order to use this script you must have Sybperl installed -- see Q9.4 for more information.
#!/usr/local/bin/perl -w
#
# @(#)dbschema.pl 1.16 04/09/97
#
# dbschema.pl A script to extract a database structure from
# a Sybase database
#
# Written by: Michael Peppler ([email protected])
# Substantially rewritten by David Whitmarsh from a partial
# System 10 implementation by Ashu Joglekar
# Last Mods: 9 April 1997
#
# Usage: dbschema.pl -d database -o script.name -t pattern -s server -v
# where database is self-explanatory (default: master)
# script.name is the output file (default: script.isql)
# pattern is the pattern of object names (in sysobjects)
# that we will look at (default: %), and server is
# the server to connect to (default, the value of $ENV{DSQUERY}).
#
# -v turns on a verbose switch.
#
# Changes: 11/18/93 - bpapp - Put in interactive SA password prompt
# 11/18/93 - bpapp - Get protection information for views and
# stored procedures.
# 02/22/94 - mpeppler - Merge bpapp's changes with itf version'
# 09/15/94 - mpeppler - Minor changes for use with Sybperl2
# alpha1
# 13/10/95 - Ashu Joglekar - System 10 w/o RI
# 11/11/96 - David Whitmarsh -
# Use Sybase::DBlib
# System 10 declarative RI
# constraints
# Eliminate key truncation problems
# Optional password command line
# Debugged and strictified
# Some index/key options
# 17/2/97 - Michael Peppler
# Fixed small ',' problem in printKeys()
# 11/3/97 - David Whitmarsh
# bug handling user defined types used as
# identity columns.
# addtype now has scale, prec
# removed spurious addtypes for nchar etc.
# null/not null/identity on types
# 12/3/97 - Michael Peppler
# Added -i switch to set an alternate interfaces
# file.
#
# If anyone knows a way to distinguish between key and reference
# declarations made at column and table level, let me know.
#------------------------------------------------------------------------------
use strict;
use Sybase::DBlib;
use Getopt::Std;
require 'ctime.pl';
sub getPerms;
sub getObj;
sub printKeys;
sub getComment;
sub PrintCols;
sub DumpTable;
my ($dbproc, @dat, $dat, $udflt, $urule, %udflt, %urule, %tables, @tabnames, @col);
my ($rule, $dflt, $date, $name);
select (STDOUT); $| = 1; # make unbuffered
getopts ('u:p:d:t:o:s:i:v');
$Getopt::Std::opt_u = `whoami` unless $Getopt::Std::opt_u;
$Getopt::Std::opt_d = 'master' unless $Getopt::Std::opt_d;
$Getopt::Std::opt_o = 'script.isql' unless $Getopt::Std::opt_o;
$Getopt::Std::opt_t = '%' unless $Getopt::Std::opt_t;
$Getopt::Std::opt_s = $ENV{DSQUERY} unless $Getopt::Std::opt_s;
open(SCRIPT, "> $Getopt::Std::opt_o") || die "Can't open $Getopt::Std::opt_o: $!\n";
open(LOG, "> $Getopt::Std::opt_o.log") || die "Can't open $Getopt::Std::opt_o.log: $!\n";
#
# Log us in to Sybase as '$Getopt::Std::opt_u' and prompt for password.
#
if (!$Getopt::Std::opt_p) {
print "\nPassword: ";
system("stty -echo");
chop($Getopt::Std::opt_p = <>);
system("stty echo");
}
if($Getopt::Std::opt_i) {
dbsetifile($Getopt::Std::opt_i);
}
$dbproc = new Sybase::DBlib ("$Getopt::Std::opt_u", $Getopt::Std::opt_p, $Getopt::Std::opt_s);
$dbproc->dbuse ($Getopt::Std::opt_d);
#
# Just in case you compiled with dbNullIsUndef defaulting to FALSE
# (Are you reading this, Ashu?)
#
$dbproc->{"dbNullIsUndef"} = TRUE;
$date = scalar(localtime);
print "dbschema.pl on Database $Getopt::Std::opt_d\n";
print LOG "Error log from dbschema.pl on Database $Getopt::Std::opt_d on $date\n\n";
print LOG "The following objects cannot be reliably created from the script in $Getopt::Std::opt_o.
Please correct the script to remove any inconsistencies.\n\n";
print SCRIPT
"/* This Isql script was generated by dbschema.pl on $date. */\n";
print SCRIPT "\nuse $Getopt::Std::opt_d\ngo\n"; # Change to the appropriate database
# first, Add the appropriate user data types:
#
print "Add user-defined data types...";
print SCRIPT
"/* Add user-defined data types: */\n\n";
$dbproc->dbcmd (< 100 and st.usertype < 100
and st.name not in ('intn', 'nvarchar', 'sysname', 'nchar')
SQLEND
);
$dbproc->dbsqlexec;
$dbproc->dbresults;
while((@dat = $dbproc->dbnextrow))
{
print SCRIPT "sp_addtype $dat[1], ";
($dat[2] =~ /char|binary/ and
print SCRIPT "'$dat[2]($dat[0])'")
or ($dat[2] =~ /numeric|decimal/ and
print SCRIPT "'$dat[2]($dat[5],$dat[6])'")
or print SCRIPT "$dat[2]";
(($dat[8] == 1) and print SCRIPT ", 'identity'")
or (($dat[7] == 1) and print SCRIPT ", 'null'")
or print SCRIPT ", 'not null'";
print SCRIPT "\ngo\n";
# Now remember the default & rule for later.
$urule{$dat[1]} = $dat[4] if defined($dat[4]);
$udflt{$dat[1]} = $dat[3] if defined($dat[3]);
}
print "Done\n";
print "Create rules...";
print SCRIPT
"\n/* Now we add the rules... */\n\n";
getObj('Rule', 'R');
print "Done\n";
print "Create defaults...";
print SCRIPT
"\n/* Now we add the defaults... */\n\n";
getObj('Default', 'D');
print "Done\n";
print "Bind rules & defaults to user data types...";
print SCRIPT "/* Bind rules & defaults to user data types... */\n\n";
while(($dat, $dflt)=each(%udflt))
{
print SCRIPT "sp_bindefault $dflt, $dat\ngo\n";
}
while(($dat, $rule) = each(%urule))
{
print SCRIPT "sp_bindrule $rule, $dat\ngo\n";
}
print "Done\n";
print "Create Tables & Indices...";
print "\n" if $Getopt::Std::opt_v;
# the fourth column set to 'N' becomes the indicator that this table has been
# printed
$dbproc->dbcmd (<dbsqlexec;
$dbproc->dbresults;
while((@dat = $dbproc->dbnextrow))
{
$tables{@dat[1] . "." . @dat[0]} = [ @dat ];
@tabnames = ( @tabnames, @dat[1] . "." . @dat[0] );
}
foreach $name (@tabnames) {
DumpTable ($tables{$name}, ());
}
print "Done\n";
#
# The key definitions - sp_primarykey etc, not constraints
# Primary keys first, then foreign and common
#
printKeys ();
#
# Now create any views that might exist
#
print "Create views...";
print SCRIPT
"\n/* Now we add the views... */\n\n";
getObj('View', 'V');
print "Done\n";
#
# Now create any stored procs that might exist
#
print "Create stored procs...";
print SCRIPT
"\n/* Now we add the stored procedures... */\n\n";
getObj('Stored Proc', 'P');
print "Done\n";
#
# Now create the triggers
#
print "Create triggers...";
print SCRIPT
"\n/* Now we add the triggers... */\n\n";
getObj('Trigger', 'TR');
print "Done\n";
print "\nLooks like I'm all done!\n";
close(SCRIPT);
close(LOG);
dbexit;
sub getPerms
{
my ($obj) = $_[0];
my ($ret, @dat, $act, $cnt);
$dbproc->dbcmd ("sp_helprotect '$obj'\n");
$dbproc->dbsqlexec;
$cnt = 0;
while(($ret = $dbproc->dbresults) != NO_MORE_RESULTS && $ret != FAIL)
{
while(@dat = $dbproc->dbnextrow)
{
$act = 'to';
$act = 'from' if $dat[0] =~ /Revoke/;
print SCRIPT "$dat[2] $dat[3] on $obj $act $dat[1]\n";
++$cnt;
}
}
$cnt;
}
sub getObj
{
my ($objname, $obj) = @_;
my (@dat, @items, @vi, $found, $text);
$dbproc->dbcmd (<dbsqlexec;
$dbproc->dbresults;
while((@dat = $dbproc->dbnextrow))
{
push (@items, [ @dat ]); # and save it in a list
}
foreach (@items)
{
@vi = @$_;
$found = 0;
$dbproc->dbcmd ("select text from dbo.syscomments where id = $vi[2]");
$dbproc->dbsqlexec;
$dbproc->dbresults;
print SCRIPT
"/* $objname $vi[0], owner $vi[1] */\n";
while(($text) = $dbproc->dbnextrow)
{
if(!$found && $vi[1] ne 'dbo')
{
++$found if($text =~ /$vi[1]/);
}
print SCRIPT $text;
}
print SCRIPT "\ngo\n";
if(!$found && $vi[1] ne 'dbo')
{
print "**Warning**\n$objname $vi[0] has owner $vi[1]\nbut this is not mentioned in the CREATE PROC statement!!\n";
print LOG "$objname $vi[0] (owner $vi[1])\n";
}
if ($obj eq 'V' || $obj eq 'P')
{
getPerms("$vi[0]") && print SCRIPT "go\n";
}
}
}
sub printKeys
{
print "Create sp_*key definitions...";
print SCRIPT "\n/* Now create the key definitions ...*/\n\n";
$dbproc->dbcmd (<dbsqlexec;
$dbproc->dbresults;
while((@dat = $dbproc->dbnextrow)) {
if ($dat[0] eq "primary") {
print SCRIPT "sp_primarykey $dat[1],";
PrintCols (@dat[3..10]);
print SCRIPT "\ngo\n";
}
if ($dat[0] eq "foreign") {
print SCRIPT "sp_foreignkey $dat[1], $dat[2],";
PrintCols (@dat[11..18]);
print SCRIPT "\ngo\n";
}
if ($dat[0] eq "common") {
print SCRIPT "sp_commonkey $dat[1], $dat[2],";
PrintCols (@dat[3..10]);
print SCRIPT "\ngo\n";
}
}
print "done\n"
}
sub getComment
{
my ($objid) = @_;
my ($line, $text);
$dbproc->dbcmd (
qq(select text from dbo.syscomments where id = $objid));
$dbproc->dbsqlexec;
$dbproc->dbresults;
$text = "";
while(($line) = $dbproc->dbnextrow)
{
$text = $text . $line;
}
return $text;
}
sub PrintCols
{
my ($col, $first);
$first = 1;
while ($col = shift (@_)) {
last if ($col eq '*');
print SCRIPT ", " if !$first;
$first = 0;
print SCRIPT "$col";
}
}
# Note: this is a recursive subroutine.
# If the current table references another that is in the list of
# tables to be dumped, and if that table has not yet been dumped,
# then DumpTable is called to dump it before proceeding
sub DumpTable
{
my ($tabref, @referers) = @_;
return if @$tabref[3] eq "Y";
my @nul = ('not null','null');
my (@dat, $dat, @col);
my (@refcols, @reflist, @field, $rule, $dflt, %rule, %dflt, $ddlrule, $ddldflt);
my ($refname, $first, $matchstring, $field, @constrids, $constrid);
my ($frgntabref);
my ($nultype);
# first, get any reference and ensure that dependent tables have already been
# created
$dbproc->dbcmd (<dbsqlexec;
$dbproc->dbresults;
while((@refcols = $dbproc->dbnextrow))
{
push (@reflist, [ @refcols ]);
}
foreach (@reflist) {
@refcols = @$_;
# if the foreign table is in a foreign database or is not in
# our table list, then don't do any more than add it to the list
next if $refcols[0] ne $Getopt::Std::opt_d;
$refname = $refcols[3] . "." . $refcols[2];
next if not defined ($tables{$refname});
$frgntabref = $tables{$refname};
# otherwise check if it's already been dumped, if so, continue
next if @$frgntabref[3] eq "Y";
# make sure we aren't in a refernce loop by checking to see if this table is
# already in the heirarchy of refering tables that led to the current invocation
grep ($refname, @referers)
&& print SCRIPT "/* WARNING: circular foreign key reference to $refname */\n"
&& print LOG "@$tabref[1].@$tabref[0] in circular foreign key reference to $refname\n";
# so dump the referenced tables first
DumpTable ($frgntabref, @referers, $refname);
}
print "Creating table @$tabref[0], owner @$tabref[1]\n" if $Getopt::Std::opt_v;
print SCRIPT "/* Start of description of table @$tabref[1].@$tabref[0] */\n\n";
$dbproc->dbcmd (<dbsqlexec;
$dbproc->dbresults;
undef(%rule);
undef(%dflt);
print SCRIPT "\n\nCREATE TABLE @$tabref[1].@$tabref[0] (\n";
$first = 1;
@col = ();
while (@field = $dbproc->dbnextrow)
{
push @col, [ @field ];
}
foreach (@col) {
@field = @$_;
print SCRIPT ",\n" if !$first; # add a , and a \n if not first field in table
# get the declarative rule and default (if set)
if ($field[9] != 0) {
$ddldflt = getComment ($field[11]);
} else {
$ddldflt = "";
}
if ($field[10] != 0) {
$ddlrule = getComment ($field[12]);
} else {
$ddlrule = "";
}
# Check if its an identity column
if ($field[8] == 1) {
$nultype = "identity";
} else {
$nultype = $nul[$field[5]];
}
print SCRIPT "\t$field[0] \t$field[1]";
print SCRIPT "($field[2])" if $field[1] =~ /char|bin/;
print SCRIPT "($field[3],$field[4])" if $field[1] =~ /numeric|decimal/;
print SCRIPT " $ddldflt $nultype $ddlrule";
if (defined ($field[7])
&& ((!defined ($urule{$field[1]})) || $urule{$field[1]} ne $field[7])
&& ($field[10] == 0)) {
$rule{"@$tabref[0].$field[0]"} = $field[7];
}
if (defined ($field[6])
&& ((!defined ($udflt{$field[1]})) || $udflt{$field[1]} ne $field[6])
&& ($field[9] == 0)) {
$dflt{"@$tabref[0].$field[0]"} = $field[6];
}
$first = 0 if $first;
}
# references
foreach (@reflist) {
@refcols = @$_;
print SCRIPT ",";
$refname = $refcols[3] . "." . $refcols[2];
if ($refcols[0] ne $Getopt::Std::opt_d) {
print SCRIPT "\n/* The following reference is in database
** $refcols[0], edit the script to create the reference manually
";
print LOG "Reference for @$tabref[1].@$tabref[0] in foreign database\n\t";
$refname = $refcols[0] . "." . $refname;
}
print SCRIPT "\n\t";
$matchstring = substr($refcols[1], 0, 8) . "[_0-9][_0-9]*";
$refcols[1] !~ /$matchstring/
&& print SCRIPT "CONSTRAINT $refcols[1] ";
print SCRIPT "FOREIGN KEY (";
PrintCols (@refcols[4..19]);
print SCRIPT ") REFERENCES $refname (";
PrintCols (@refcols[20..35]);
print SCRIPT ")";
if ($refcols[0] ne $Getopt::Std::opt_d) {
print SCRIPT "*/";
}
}
# now get the indexes and keys...
#
print "Indexes for table @$tabref[1].@$tabref[0]\n" if $Getopt::Std::opt_v;
$dbproc->dbcmd (<dbsqlexec;
$dbproc->dbresults;
@col = ();
while((@field = $dbproc->dbnextrow))
{
# if this is a key or unique constraint, print out the details
# otherwise buffer it up to print as an index afterwards
if ($field[3] & 2) {
print (SCRIPT ",\n\t");
print SCRIPT "CONSTRAINT $field[0] " unless ($field[3] & 8);
if ($field[2] & 2048) {
print SCRIPT "PRIMARY KEY ";
print SCRIPT "NONCLUSTERED " if ($field[1] != 1);
} else {
print SCRIPT "UNIQUE ";
print SCRIPT "CLUSTERED " if ($field[1] == 1);
}
print SCRIPT "(";
PrintCols (@field[4..19]);
print SCRIPT ")";
} else {
push @col, [ @field ];
}
}
# Now do the table level check constraints
@constrids = ();
$dbproc->dbcmd (<dbsqlexec;
$dbproc->dbresults;
while (@field = $dbproc->dbnextrow) {
@constrids = (@constrids, $field[0]);
}
foreach $constrid (@constrids) {
print SCRIPT ",\n\t" . getComment ($constrid);
}
print SCRIPT "\n)\ngo\n"; # end of CREATE TABLE
foreach (@col) { # now print the indexes
@field = @$_;
print SCRIPT "\nCREATE ";
print SCRIPT "UNIQUE " if $field[2] & 2;
print SCRIPT "CLUSTERED " if $field[1] == 1;
print SCRIPT "INDEX $field[0]\n";
print SCRIPT "ON @$tabref[1].@$tabref[0] (";
PrintCols (@field[4..19]);
print SCRIPT ")";
$first = 1;
if ($field[2] & 64) {
print SCRIPT " WITH ALLOW_DUP_ROW";
$first = 0;
}
if ($field[2] & 1) {
print SCRIPT (($first == 0) ? ", " : " WITH ") . "IGNORE_DUP_KEY";
$first = 0;
}
if ($field[2] & 4) {
print SCRIPT (($first == 0) ? ", " : " WITH ") . "IGNORE_DUP_ROW";
$first = 0;
}
print SCRIPT "\ngo\n";
}
getPerms("@$tabref[1].@$tabref[0]") && print SCRIPT "go\n";
print "Bind rules & defaults to columns...\n" if $Getopt::Std::opt_v;
print SCRIPT "/* Bind rules & defaults to columns... */\n\n";
if(@$tabref[1] ne 'dbo' && (keys(%dflt) || keys(%rule)))
{
print SCRIPT "/* The owner of the table is @$tabref[1].
* I can't bind the rules/defaults to a table of which I am not the owner.
* The procedures below will have to be run manualy by user @$tabref[1].
*/";
print LOG "Defaults/Rules for @$tabref[1].@$tabref[0] could not be bound\n";
}
while(($dat, $dflt)=each(%dflt))
{
print SCRIPT "/* " if @$tabref[1] ne 'dbo';
print SCRIPT "sp_bindefault $dflt, '$dat'";
if(@$tabref[1] ne 'dbo')
{
print SCRIPT " */\n";
}
else
{
print SCRIPT "\ngo\n";
}
}
while(($dat, $rule) = each(%rule))
{
print SCRIPT "/* " if @$tabref[1] ne 'dbo';
print SCRIPT "sp_bindrule $rule, '$dat'";
if(@$tabref[1] ne 'dbo')
{
print SCRIPT " */\n";
}
else
{
print SCRIPT "\ngo\n";
}
}
print SCRIPT "\n/* End of description of table @$tabref[1].@$tabref[0] */\n";
@$tabref[3] = "Y";
}