aboutsummaryrefslogtreecommitdiff
path: root/content/posts/2005-03-11-three-language-showdown.html
blob: f374d648f88852817f85b952a792d1e8dc79176d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
---
date: "2005-03-11T04:13:57Z"
title: Three Language Showdown
---

<p>
Earlier this evening I wrote an example of how to read a list of field values to delete from an input file.  That's not particularly interesting.  The fact that I wrote it in three different languages is, though.  So, without any further ado, the world's simplest database application, written in <a href='http://ruby-lang.org/'>Ruby</a>, <a href='http://perl.org/'>Perl</a>, and <a href='http://php.net/'>PHP</a>, for your viewing (comparing, contrasting, commenting, etc) pleasure:
</p>

<p>
<b>Ruby:</b>
</p>

<div style='font-size: 75%; background-color: #eee; padding: 10px;
margin: 10px 30px 10px 30px; border: 1px solid black;'>
<pre><code>  #!/usr/bin/env ruby

  # load the mysql library
  require 'mysql'

  # all our options go here
  opt = {
    :host =&gt; 'localhost', # server to connect to (usually localhost)
    :user =&gt; 'USER',      # replace USER with username
    :pass =&gt; 'PASSWORD',  # replace PASSWORD with password
    :db   =&gt; 'DATABASE',  # replace DATABASE with database name
    :tbl  =&gt; 'TABLE',     # replace TABLE with table name
    :fld  =&gt; 'FIELD',     # name of field to match against
  }

  # check first command-line argument for the filename
  unless path = ARGV.shift
    $stderr.puts "ERROR: missing filename"
    exit -1
  end

  # check to make sure the specified file exists and is readable
  unless File.exists?(path) &amp;&amp; File.readable?(path)
    $stderr.puts "ERROR: missing file or unreadable file: #{path}"
    exit -1
  end

  # read a list of IDs from the file, excluding blank lines
  ids = File.readlines(path).map { |id| id.strip }.grep(/[^\s]/)

  # connect to server, select database
  db = Mysql::connect(opt[:host], opt[:user], opt[:pass])
  db.select_db(opt[:db])

  # build a big friggin list of ids for the query below
  ids_str = ids.map { |id| "'#{id.gsub(/'/, "''")}'" }.join(',')

  # build query
  query = "DELETE FROM #{opt[:tbl]} WHERE #{opt[:fld]} IN (#{id_str})"

  # print the query out on the screen
  puts query
  
  # check to make sure this really what we want to do 
  puts "Execute this query? (y/N)"
  if gets =~ /^y/
    # user entered "y" or "Y", so execute the query
    db.query(query)
    puts "Done."
  else
    puts "Cancelled."
  end
</code></pre></div>

<p>
<b>Perl:</b>
</p>

<div style='font-size: 75%; background-color: #eee; padding: 10px;
margin: 10px 30px 10px 30px; border: 1px solid black;'>
<pre><code>  #!/usr/bin/perl

  # turn on warnings and strict interpretation
  use warnings;
  use strict;

  # load DBI module (see [2] below)
  use DBI;

  my %opt = {
    'host' =&gt; 'localhost',  # server to connect to (in our case, localhost)
    'user' =&gt; 'USER',       # replace USER with username
    'pass' =&gt; 'PASSWORD',   # replace PASSWORD with password
    'db'   =&gt; 'DATABASE',   # replace DATABASE with database
    'tbl'  =&gt; 'TABLE',      # replace TABLE with table name
    'fld'  =&gt; 'FIELD',      # replace FIELD with name of field
  };

  # declare vars
  my ($path, $fh, @ids, $id_str, $dsn, $db, $query, $response, $a);

  # get filename
  $path = shift @ARGV;

  # check path to make sure it's legit
  die "ERROR: Missing filename\n" unless $path;
  die "Missing or unreadable file: '$path'\n" unless (-e $path &amp;&amp; -r $path);

  # read ids from file, stripping out blank lines
  open $fh, $path or die "Couldn't open file: $!\n";
  @ids = grep { /[^\s]/ } &lt;$fh&gt;;
  close $fh;
  chomp @ids;

  # build DSN string, connect to database
  $dsn = "dbi:mysql:database=" . $opt{'db'} . ";host=" . $opt{'host'};
  $db = DBI-&gt;connect($dsn, $opt{'user'}, $opt{'pass'});
  
  # build id list string
  $id_str = join(',', map { $a = $_; $a =~ s/'/''/g; "'$a'" } @ids);

  # build query string
  $query = "DELETE FROM " . $opt{'tbl'} .
           " WHERE " . $opt{'fld'} . " IN ($id_str)";

  # print query out on screen
  print "$query\n";

  # check to make sure user really wants to execute query
  print "Execute this query? (y/N)";
  $response = &lt;STDIN&gt;;

  # check response
  if ($response =~ /^y/i) {
    # user said 'y' or 'Y', execute query
    $db-&gt;do($query);
    print "Done.\n";
  } else {
    print "Cancelled...\n";
  }
</code></pre></div>

<p>
<b>PHP:</b>
</p>

<div style='font-size: 75%; background-color: #eee; padding: 10px;
margin: 10px 30px 10px 30px; border: 1px solid black;'>
<pre><code>  &lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1.dtd"&gt;
  &lt;?php 
  require_once 'DB.php';

  echo "&lt;?xml version='1.0' encoding='iso-8859-1'?&gt;\n"; 

  # options
  $opt = array(
    'host' =&gt; 'localhost',  # database server (in our case, localhost)
    'user' =&gt; 'USERNAME',   # replace USERNAME with user
    'pass' =&gt; 'PASSWORD',   # replace PASSWORD with password
    'db'   =&gt; 'DATABASE',   # replace DATABASE with database
    'tbl'  =&gt; 'TABLE',      # replace TABLE with table name
    'fld'  =&gt; 'FIELD',      # replace FIELD with match field
  );

  # set page title
  $page_title = 'PHP MySQL Sample Code';
  ?&gt;
  &lt;html lang='en'&gt;
    &lt;head&gt;
      &lt;title&gt;&lt;?php echo $page_title; ?&gt;&lt;/title&gt;
    &lt;/head&gt;

    &lt;body&gt;
      &lt;form method='post' action='&lt;?php echo $PHP_SELF; ?&gt;'
            enctype='multipart/form-data'&gt; 
         &lt;!-- PHP needs this nonsense :/ --&gt;
         &lt;input type='hidden' name='MAX_FILE_SIZE' value='30000' /&gt;
         File: &lt;input type='file' name='csvfile' /&gt;
         &lt;input type='submit' value='Delete IDs from file' /&gt;
      &lt;/form&gt;

      &lt;?php
        # check to see if the file was uploaded
        if ($file = $_FILES['csvfile']) {
          echo "&lt;hr /&gt;\nResults:&lt;br /&gt;";
          handle_file($file);
        }
      ?&gt;
    &lt;/body&gt;
  &lt;/html&gt;
  &lt;?php

  #####################
  # UTILITY FUNCTIONS #
  #####################
  
  # 
  # handle file upload
  #
  function handle_file($file) {
    global $opt;

    # check to see if there was an error:
    if ($file['error'])
      die(__LINE__ . ": Error handling file upload: {$file['error']}");

    # no error, go ahead and read the list of IDs
    $ids = array_map('trim', file($file['tmp_name']));

    # build ID string
    $id_str = join(',', array_map('quote_str_cb', $ids));

    # build DSN string
    $dsn = "mysql://{$opt['user']}:{$opt['pass']}@{$opt['host']}/{$opt['db']}";

    # connect to database
    $db = DB::connect($dsn);
    if (DB::isError($db))
      die(__LINE__ . ": Couldn't connect to database: " . $db-&gt;getMessage());

    # build/print query string
    $query = "DELETE FROM {$opt['tbl']} WHERE {$opt['fld']} IN ($id_str)";
    echo "$query";

    # execute query
    $err = $db-&gt;query($query);
    if (DB::isError($err))
      die(__LINE__ . ": Couldn't query database: " . $err-&gt;getMessage());
  }

  #
  # escape quotes in string, and return quoted version of string
  #
  function quote_str_cb($str) {
      return "'" . str_replace("'", "''", $str) . "'";
  }

  ?&gt;
</code></pre></div>

<p>
Now before the pedant geek armada nails me to the wall: Yes, I know the
<em>new</em> way of calling <code>DBI-&gt;connect</code> includes passing <code>{ 
AutoCommit =&gt; 1 }</code> as a fourth parameter, and yes, I know that I should be 
passing the <acronym title='Data Source Name'>DSN</acronym> an
<code>array</code> to <code>DB::connect</code>, and yes, I know that <a
href='http:/ruby-lang.org/'>Ruby</a> has <acronym title='Database Interface'>DBI</acronym> too!  And finally, yes, I
know a dependency on <a href='http://mysql.com/'>MySQL</a> is totally
2001, but I wrote these examples to address a specific problem.  The
original email includes all sorts of extra stuff, including explanations
of differences between <a href='http://mysql.com/'>MySQL</a> and <a
href='http://postgresql.org/'>Postgres</a>, and the nuances of their
respective regex engines.  So bugger off :D.
</p>