Programming Comparisons: Practical DB Functionality


Problem: give the neatest possible complete program which does the given database select using the first_name given on the command line and outputs the table in the following format: first line: column names, sorted, tab separated, followed by one line per row of the matching data, tab separated. Brevity is good. The DB is on the local host and the connection parameters are:

 user: foo
 pass: bar
 db:   common
 type: MySQL
The SQL select is: SELECT * FROM person p, email e, company c, operating_system o WHERE p.first_name = ? AND e.person_id = p.id AND c.person_id = p.id and o.person_id = p.id ORDER BY p.last_name LIMIT 10
Example:
> ./pracdb Joe | cat -t
company_name^Iemail^Ifirst_name^Iid^Ilast_name^Ioperating_system^Iperson_id
Jones, Inc.^Ijjones@example.com^IJoe^I2^IJones^IMacOS^I2
Smith, Inc.^Ijsmith@example.com^IJoe^I1^ISmith^IWindows^I1

Languages given alphabetical order:
C
#include <mysql/mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int strcmp_wrapper(void *a, void *b) {
  return strcmp(*((char**)a), *((char**)b));
}
 
int main(int argc, char *argv[]) {
  MYSQL dbh;
  MYSQL_RES *result;
  MYSQL_ROW row;
  MYSQL_FIELD *fields;
  char *sql, *first_name, ***field_names;
  size_t len;
  unsigned int i, j, num_fields, *col_order;
 
  /* connect to the database */
  mysql_init(&dbh);
  mysql_real_connect(&dbh, NULL, "foo", "bar", "common", 0, NULL, 0);
 
  /* build ourselves a properly escaped sql query */
  len = strlen(argv[1]);
  first_name = (char*) malloc(sizeof(char) * (len * 2 + 1));
  mysql_real_escape_string(&dbh, first_name, argv[1], (unsigned long) len);
  asprintf(&sql, " \ 
      SELECT * \ 
        FROM person p, email e, company c, operating_system o \ 
      WHERE p.first_name = '%s' \ 
        AND e.person_id = p.id AND c.person_id = p.id and o.person_id = p.id \ 
    ORDER BY p.last_name \ 
      LIMIT 10 \ 
  ", first_name);
  free(first_name);
 
  /* perform the query */
  mysql_query(&dbh, sql);
  free(sql);
  result = mysql_store_result(&dbh);
  num_fields = mysql_num_fields(result);
 
  /* get list of column headers */
  fields = mysql_fetch_fields(result);
  field_names = (char***) malloc(sizeof(char**) * num_fields);
  for (i = 0; i < num_fields; i++) {
    field_names[i] = &fields[i].name;
  }
 
  /* sort list of column headers */
  qsort(field_names, num_fields, sizeof(char*), strcmp_wrapper);
 
  /* generate column order array and output sorted headers */
  col_order = (unsigned int*) malloc(sizeof(unsigned int) * num_fields);
  for (i = 0; i < num_fields; i++) {
    for (j = 0; j < num_fields; j++) {
      if (fields[i].name == *field_names[j]) {
        col_order[i] = j;
        continue;
      }
    }
    fputs(*field_names[i], stdout);
    putchar((i == (num_fields-1)) ? '\n' : '\t');
  }
  free(field_names);
 
  /* output data */
  while ((row = mysql_fetch_row(result))) {
    for (i = 0; i < num_fields; i++) {
      fputs(row[col_order[i]], stdout);
      putchar((i == (num_fields-1)) ? '\n' : '\t');
    }
  }
 
  /* cleanup */
  free(col_order);
  free(fields);
  mysql_free_result(result);
  exit(0);
}
Haskell
import List
import Data.List
import Database.HDBC
import Database.HDBC.ODBC
import System
import Maybe
 
main = do
  -- connect to DB; HDBC only supports MySQL via ODBC
  dbh <- connectODBC "DSN=people"
 
  -- form the query
  sth <- prepare dbh
    "    SELECT *                                                  \
    \      FROM person p, email e, company c, operating_system o   \
    \     WHERE p.first_name = ? AND e.person_id = p.id            \
    \       AND c.person_id = p.id and o.person_id = p.id          \
    \  ORDER BY p.last_name                                        \
    \     LIMIT 10                                                 "
 
  -- run the SQL with the first command line arg
  args <- getArgs
  execute sth [toSql $ head args]
 
  -- get the results as an array of association lists
  rows <- fetchAllRowsAL' sth
 
  -- extract the sorted column names from the first row and output w/ tabs
  let cols = sort $ map fst $ head rows
  putStrLn $ intercalate "\t" cols
 
  -- sort the rows' columns based on "cols", then ouput each row
  let sortRow row = map (\col -> fromSql $ fromJust $ lookup col row) cols
  mapM_ putStrLn $ map (intercalate "\t" . sortRow) rows
Perl
use DBI;
 
my $dbh = DBI->connect('DBI:mysql:common', 'foo', 'bar', { RaiseError => 1 });
 
my @rows = @{$dbh->selectall_arrayref(q{
    SELECT * 
      FROM person p, email e, company c, operating_system o 
     WHERE p.first_name = ? 
       AND e.person_id = p.id AND c.person_id = p.id and o.person_id = p.id 
  ORDER BY p.last_name 
     LIMIT 10
}, { Columns => {} }, $ARGV[0])};
 
my @cols = sort keys %{$rows[0]};
print(join("\t", @cols), "\n");
print(join("\t", @$_{@cols}), "\n") for @rows;
PHP
<?php
  require_once 'DB.php';
 
  $db = DB::connect('mysql://foo:bar@localhost/common');
 
  $rows = $db->getAll('
      SELECT * 
	FROM person p, email e, company c, operating_system o 
       WHERE p.first_name = ? 
	 AND e.person_id = p.id AND c.person_id = p.id and o.person_id = p.id 
    ORDER BY p.last_name 
       LIMIT 10
  ', array($_SERVER['argv'][1]), DB_FETCHMODE_ASSOC);
 
  $cols = array_keys($rows[0]);
  sort($cols);
  print join("\t", $cols) . "\n";
 
  foreach ($rows as $row) {
    $line = array();
    foreach ($cols as $col) $line[] = $row[$col];
    print join("\t", $line) . "\n";
  }
?>
Python
import sys
import MySQLdb
 
db = MySQLdb.connect(user='foo',passwd='bar',db='common',host='localhost')
 
c = db.cursor(MySQLdb.cursors.DictCursor)
 
c.execute('''
    SELECT *
      FROM person p, email e, company c, operating_system o
     WHERE p.first_name = %s
       AND e.person_id = p.id AND c.person_id = p.id and o.person_id = p.id
  ORDER BY p.last_name
     LIMIT 10
''', sys.argv[1])
 
rows = c.fetchall()
cols = rows[0].keys()
cols.sort()
 
print '\t'.join(cols)
for r in rows:
  print '\t'.join([ str(r[i]) for i in cols ])
Ruby
require 'dbi'
 
dbh = DBI.connect('dbi:mysql:common', 'foo', 'bar')
 
rows = dbh.select_all(%q{
    SELECT * 
      FROM person p, email e, company c, operating_system o 
     WHERE p.first_name = ? 
       AND e.person_id = p.id AND c.person_id = p.id and o.person_id = p.id 
  ORDER BY p.last_name 
     LIMIT 10
}, ARGV[0])
 
cols = rows[0].column_names.sort
puts cols.join("\t")
rows.each {|row| puts row[cols].join("\t") }