Book Home Perl for System AdministrationSearch this book

7.4. Server Documentation

A great deal of time and energy goes into the configuration of an SQL server and the objects that reside on it. Having a way to document this sort of information can come in handy in a number of situations. If a database gets corrupted and there's no backup, you may be called upon to recreate all of its tables. You may have to migrate data from one server to another; knowing the source and destination configuration can be important. Even for your own database programming, being able to see a table map can be very helpful.

To give you a flavor of the nonportable nature of database administration, let me show you an example of the same simple task as written for three different SQL servers using both DBI and ODBC. Each of these programs does the exact same thing: print out a listing of all of the databases on a server, their tables, and the basic structure of each table. These scripts could easily be expanded to show more information about each object. For instance, it might be useful to show which columns in a table had NULL or NOT NULL set. The output of all three programs looks roughly like this:

---sysadm---
        hosts
                name [char(30)]
                ipaddr [char(15)]
                aliases [char(50)]
                owner [char(40)]
                dept [char(15)]
                bldg [char(10)]
                room [char(4)]
                manuf [char(10)]
                model [char(10)]
---hpotter---
        customers
                cid [char(4)]
                cname [varchar(13)]
                city [varchar(20)]
                discnt [real(7)]
        agents
                aid [char(3)]
                aname [varchar(13)]
                city [varchar(20)]
                percent [int(10)]
        products
                pid [char(3)]
                pname [varchar(13)]
                city [varchar(20)]
                quantity [int(10)]
                price [real(7)]
        orders
                ordno [int(10)]
                month [char(3)]
                cid [char(4)]
                aid [char(3)]
                pid [char(3)]
                qty [int(10)]
                dollars [real(7)]
...

7.4.1. MySQL Server via DBI

Here's a DBI way of pulling this information from a MySQL server. MySQL's addition of the SHOW command makes this task pretty easy:

use DBI;

print "Enter user for connect: ";
chomp($user = <STDIN>);
print "Enter passwd for $user: ";
chomp($pw = <STDIN>);

$start= "mysql"; # connect initially to this database

# connect to the start MySQL database
$dbh = DBI->connect("DBI:mysql:$start",$user,$pw);
die "Unable to connect: ".$DBI::errstr."\n" unless (defined $dbh);

# find the databases on the server 
$sth=$dbh->prepare(q{SHOW DATABASES}) or
  die "Unable to prepare show databases: ". $dbh->errstr."\n";
$sth->execute or
  die "Unable to exec show databases: ". $dbh->errstr."\n";
while ($aref = $sth->fetchrow_arrayref) {
    push(@dbs,$aref->[0]);
} 
$sth->finish;

# find the tables in each database
foreach $db (@dbs) {
    print "---$db---\n";
    
    $sth=$dbh->prepare(qq{SHOW TABLES FROM $db}) or
     die "Unable to prepare show tables: ". $dbh->errstr."\n";
    $sth->execute or
      die "Unable to exec show tables: ". $dbh->errstr."\n";

    @tables=(  );
    while ($aref = $sth->fetchrow_arrayref) {
	    push(@tables,$aref->[0]);
    } 

    $sth->finish;
    
   # find the column info for each table
   foreach $table (@tables) {
	    print "\t$table\n";

	    $sth=$dbh->prepare(qq{SHOW COLUMNS FROM $table FROM $db}) or
          die "Unable to prepare show columns: ". $dbh->errstr."\n";
	    $sth->execute or
          die "Unable to exec show columns: ". $dbh->errstr."\n";

	    while ($aref = $sth->fetchrow_arrayref) {
	        print "\t\t",$aref->[0]," [",$aref->[1],"]\n";
	    } 

       $sth->finish;
   }
}
$dbh->disconnect;

A few quick comments about this code:

7.4.2. Sybase Server via DBI

Here's the Sybase equivalent. Peruse the code and then we'll talk about a few salient points:

use DBI;

print "Enter user for connect: ";
chomp($user = <STDIN>);
print "Enter passwd for $user: ";
chomp($pw = <STDIN>);

$dbh = DBI->connect('dbi:Sybase:',$user,$pw);
die "Unable to connect: $DBI::errstr\n" 
  unless (defined $dbh);

# find the databases on the server 
$sth = $dbh->prepare(q{SELECT name from master.dbo.sysdatabases}) or
  die "Unable to prepare sysdatabases query: ".$dbh->errstr."\n";
$sth->execute or
  die "Unable to execute sysdatabases query: ".$dbh->errstr."\n";

while ($aref = $sth->fetchrow_arrayref) {
    push(@dbs, $aref->[0]);
}
$sth->finish;

foreach $db (@dbs) {
    $dbh->do("USE $db") or
      die "Unable to use $db: ".$dbh->errstr."\n";
    print "---$db---\n";

    # find the tables in each database
    $sth=$dbh->prepare(q{SELECT name FROM sysobjects WHERE type="U"}) or
      die "Unable to prepare sysobjects query: ".$dbh->errstr."\n";
    $sth->execute or
      die "Unable to exec sysobjects query: ".$dbh->errstr."\n";

    @tables=(  );
    while ($aref = $sth->fetchrow_arrayref) {
        push(@tables,$aref->[0]);
    } 
    $sth->finish;

    # we need to be "in" the database for the next step
    $dbh->do("use $db") or
          die "Unable to change to $db: ".$dbh->errstr."\n";
  
    # find the column info for each table
    foreach $table (@tables) {
	    print "\t$table\n";

       $sth=$dbh->prepare(qq{EXEC sp_columns $table}) or
	       die "Unable to prepare sp_columns query: ".$dbh->errstr."\n";
	    $sth->execute or
	       die "Unable to execute sp_columns query: ".$dbh->errstr."\n";

	    while ($aref = $sth->fetchrow_arrayref) {
	        print "\t\t",$aref->[3]," [",$aref->[5],"(",
                  $aref->[6],")]\n";
	    } 
	    $sth->finish;
    }
}
$dbh->disconnect or
  warn "Unable to disconnect: ".$dbh->errstr."\n";

Here are the promised salient points:

7.4.3. MS-SQL Server via ODBC

Finally, here's the code for pulling the same information from a MS-SQL server via ODBC. You'll notice that the actual SQL needed is almost identical to the previous example thanks to the Sybase/MS-SQL connection. The interesting changes between this example and the last are:

Here's the code:

use Win32::ODBC;

print "Enter user for connect: ";
chomp($user = <STDIN>);
print "Enter passwd for $user: ";
chomp($pw = <STDIN>);

$dsn="sysadm"; # name of the DSN we will be using

# find the available DSNs, creating $dsn if it doesn't exist already
die "Unable to query available DSN's".Win32::ODBC::Error(  )."\n" 
  unless (%dsnavail = Win32::ODBC::DataSources(  ));
if (!defined $dsnavail{$dsn}) {
    die "unable to create DSN:".Win32::ODBC::Error(  )."\n"
      unless (Win32::ODBC::ConfigDSN(ODBC_ADD_DSN, 
				  "SQL Server", 
				  ("DSN=$dsn", 
				   "DESCRIPTION=DSN for PerlSysAdm", 
				   "SERVER=mssql.happy.edu",
				   "DATABASE=master",
				   "NETWORK=DBMSSOCN", # TCP/IP Socket Lib
				  )));
}

# connect to the master database
$dbh = new Win32::ODBC("DSN=$dsn;UID=$user;PWD=$pw;");
die "Unable to connect to DSN $dsn:".Win32::ODBC::Error(  )."\n" 
    unless (defined $dbh);

# find the databases on the server
if (defined $dbh->Sql(q{SELECT name from sysdatabases})){
    die "Unable to query databases:".Win32::ODBC::Error(  )."\n";
}

while ($dbh->FetchRow(  )){
    push(@dbs, $dbh->Data("name"));
}
$dbh->DropCursor(  );

# find the user tables in each database
foreach $db (@dbs) {
    if (defined $dbh->Sql("use $db")){
	     die "Unable to change to database $db:" . 
             Win32::ODBC::Error(  ) . "\n";
    }
    print "---$db---\n";
    @tables=(  );
    if (defined $dbh->Sql(q{SELECT name from sysobjects 
                            WHERE type="U"})){
	    die "Unable to query tables in $db:" . 
            Win32::ODBC::Error(  ) . "\n";
    }
    while ($dbh->FetchRow(  )) {
	    push(@tables,$dbh->Data("name"));
    } 
    $dbh->DropCursor(  );

    # find the column info for each table
    foreach $table (@tables) {
  	    print "\t$table\n";
  	    if (defined $dbh->Sql(" {call sp_columns (\'$table\')} ")){
	        die "Unable to query columns in 
                $table:".Win32::ODBC::Error(  ) . "\n";
	    }
	    while ($dbh->FetchRow(  )) {
	        @cols=(  );
	        @cols=$dbh->Data("COLUMN_NAME","TYPE_NAME","PRECISION");
	        print "\t\t",$cols[0]," [",$cols[1],"(",$cols[2],")]\n";
	    } 
	    $dbh->DropCursor(  );
    }
}
$dbh->Close(  );

die "Unable to delete DSN:".Win32::ODBC::Error(  )."\n"
    unless (Win32::ODBC::ConfigDSN(ODBC_REMOVE_DSN,
                                "SQL Server","DSN=$dsn"));





Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.