1SQL::Statement::Embed(3U)ser Contributed Perl DocumentatiSoQnL::Statement::Embed(3)
2
3
4
6 SQL::Statement::Embed - embed a SQL engine in a DBD or module
7
10 SQL::Statement is designed to be easy to embed in other modules and to
11 be especially easy to embed in DBI drivers. It provides a SQL Engine
12 and the other module needs to then provide a data source and a storage
13 mechanism. For example, the DBD::CSV module uses SQL::Statement as an
14 embedded SQL engine by implementing a file-based data source and by
15 using DBI as the user interface. Similarly DBD::Amazon uses
16 SQL::Statement as its SQL engine, provides its own extensions to the
17 supported SQL syntax, and uses on-the-fly searches of Amazon.com as its
18 data source.
19
20 SQL::Statement is the basis for eight existing DBDs (DBI database
21 drivers). If you have a new data source, you too can create a DBD
22 without having to reinvent the SQL wheel. It is fun and easy so become
23 a DBD author today!
24
25 SQL::Statement can be also be embedded without DBI. We will explore
26 that first since developing a DBD uses most of the same methods and
27 techniques.
28
30 SQL::Statement provides a SQL parsing and execution engine. It does
31 not provide a data source or storage mechanism other than in-memory
32 tables. The DBD::File module is a subclass of SQL::Statement that
33 provides access to file-based storage mechanisms. It is quite possible
34 to use things other than files as data souces, in which case you would
35 not use DBD::File, instead you would replace DBD::File's methods with
36 your own. In the examples below, we use DBD::File, replacing only a
37 few methods.
38
39 SQL::Statement provides SQL parsing and evaluation and DBD::File
40 provides file-based storage. The only thing missing is a data source -
41 what we actually want to store and query. As an example suppose we
42 are going to create a subclass called 'Foo' that will provide as a data
43 source a simple file similar to a passwd file - one record per line,
44 fields separated by colons, with only three fields "username, uid,
45 gid".
46
47 Consider what needs to happen to perform a SELECT query on our 'Foo'
48 data:
49
50 * receive a SQL string
51 * parse the SQL string into a request structure
52 * open the table(s) specified in the request
53 * define column names and positions for the table
54 * read rows from the table
55 * convert the rows from colon-separated format into perl arrays
56 * match the columns and rows against the requested selection criteria
57 * return requested rows and columns to the user
58
59 To perform operations like INSERT and DELETE, we also need to:
60
61 * convert rows from perl arrays into colon-separated format
62 * write rows
63 * delete rows
64
65 SQL::Statement takes care of all of the SQL parsing and evaluation.
66 DBD::File takes care of file opening, reading, writing, and deleting.
67 So the only things 'Foo' is really responsible for are:
68
69 * define column names and positions for the table
70 * convert rows from colon-separated format into perl arrays
71 * convert rows from perl arrays into colon-separated format
72
73 In SQL::Statement subclasses these responsibilities are assigned to two
74 objects, a ::Statement object is responsible for opening the table,
75 defining the column names and positions, and for creating new ::Table
76 objects. A ::Table object is responsible for reading, converting,
77 writing, and deleting data.
78
80 A subclass of SQL::Statement must provide at least one method called
81 open_table(). The method should open a new Table object and define the
82 table's columns. For our 'Foo' module, here is the complete object
83 definition:
84
85 package Foo;
86
87 package Foo::Statement;
88 use DBD::File;
89 use base qw(DBD::File::Statement);
90
91 sub open_table {
92 my $self = shift @_;
93 my $data = shift @_;
94 $data->{Database}->{f_dir} = './';
95 my $tbl = $self->SUPER::open_table($data,@_);
96 $tbl->{col_names} = [qw(username uid gid)];
97 $tbl->{col_nums} = {username=>0,uid=>1,gid=>2};
98 return $tbl;
99 }
100
101 Since 'Foo' is a file-based data source, we subclass SQL::Statement
102 indirectly through DBD::File::Statement. The open_table() method lets
103 DBD::File do the actual table opening. All we do is define the files
104 directory (f_dir), the names of the columns (col_names) and the
105 positions of the columns (col_nums). DBD::File creates and returns a
106 $tbl object. It names that object according to the module that calls
107 it, so in our case the object will be a Foo::Table object.
108
110 Table objects are responsible for reading, converting, writing, and
111 deleting data. Since DBD::File provides most of those services, our
112 'Foo' subclass only needs to define three methods - fetch_row() to read
113 data, push_row() to write data, and push_names() to store column names.
114 We will leave deleting to DBD::File, since deleting a record in the
115 'Foo' format is the same process as deleting a record in any other
116 simple file-based format. Here is the complete object defintion:
117
118 package Foo::Table;
119 use base qw(DBD::File::Table);
120
121 sub fetch_row {
122 my($self, $data) = @_;
123 my $fieldstr = $self->{fh}->getline;
124 return undef unless $fieldstr;
125 chomp $fieldstr;
126 my @fields = split /:/,$fieldstr;
127 $self->{row} = (@fields ? \@fields : undef);
128 }
129 sub push_row {
130 my($self, $data, $fields) = @_;
131 my $str = join ':', map { defined $_ ? $_ : '' } @$fields;
132 $self->{fh}->print( $str."\n");
133 1;
134 }
135 sub push_names {}
136 1;
137
138 The fetch_row() method uses DBD::File's getline() method to physically
139 read a row of data, then we convert it from native colon-separated
140 format into a perl arrayref.
141
142 The push_row() method converts from a perl arrayref back to colon-
143 separated format then uses DBD::File's print() method to print it to
144 file.
145
146 The push_names method does nothing because it's purpose is to store
147 column names in a file and in our 'Foo' subclass, we are defining the
148 column names ourselves, not storing them in a file.
149
151 Here is a script which should create and query a file in our 'Foo'
152 format. It assumes you have saved the Foo, Foo::Statement, and
153 Foo::Table classes shown above into a file called Foo.pm.
154
155 #!perl -w
156 use strict;
157 use Foo;
158 my $parser = SQL::Parser->new();
159 $parser->{RaiseError}=1;
160 $parser->{PrintError}=0;
161 for my $sql(split /\n/,
162 " DROP TABLE IF EXISTS group_id
163 CREATE TABLE group_id (username CHAR,uid INT, gid INT)
164 INSERT INTO group_id VALUES('joe',1,1)
165 INSERT INTO group_id VALUES('sue',2,1)
166 INSERT INTO group_id VALUES('bob',3,2)
167 SELECT * FROM group_id "
168 ){
169 my $stmt = Foo::Statement->new($sql,$parser);
170 $stmt->execute;
171 next unless $stmt->command eq 'SELECT';
172 while (my $row=$stmt->fetch) {
173 print "@$row\n";
174 }
175 }
176
177 This is the same script as shown in the section on executing and
178 fetching in SQL::Statement::Structure except that instead of
179 SQL::Statement->new(), we are using Foo::Statement->new(). The other
180 difference is that the execute/fetch example was using in-memory
181 storage while this script is using file-based storage and the 'Foo'
182 format we defined. When you run this script, you will be creating a
183 file called "group_id" and it will contain the specified data in colon-
184 separated format.
185
187 Moving from a subclass to a DBD
188 A DBD based on SQL::Statement uses the same two subclasses that are
189 shown above. They should be called DBD::Foo::Statement and
190 DBD::Foo::Table, but would otherwise be identical to the non-DBD
191 subclass illustrated above. To turn it into a full DBD, you have to
192 sublass DBD::File, DBD::File::dr, DBD::File::db, and DBD::File::st. In
193 many cases a simple subclass with few or no methods overridden is
194 sufficient.
195
196 Here is a working DBD::Foo:
197
198 package DBD::Foo;
199 use base qw(DBD::File);
200
201 package DBD::Foo::dr;
202 $DBD::Foo::dr::imp_data_size = 0;
203 use base qw(DBD::File::dr);
204
205 package DBD::Foo::db;
206 $DBD::Foo::db::imp_data_size = 0;
207 use base qw(DBD::File::db);
208
209 package DBD::Foo::st;
210 $DBD::Foo::st::imp_data_size = 0;
211 use base qw(DBD::File::st);
212
213 package DBD::Foo::Statement;
214 use base qw(DBD::File::Statement);
215
216 sub open_table {
217 my $self = shift @_;
218 my $data = shift @_;
219 $data->{Database}->{f_dir} = './';
220 my $tbl = $self->SUPER::open_table($data,@_);
221 $tbl->{col_names} = [qw(username uid gid)];
222 $tbl->{col_nums} = {username=>0,uid=>1,gid=>2};
223 return $tbl;
224 }
225
226 package DBD::Foo::Table;
227 use base qw(DBD::File::Table);
228
229 sub fetch_row {
230 my($self, $data) = @_;
231 my $fieldstr = $self->{fh}->getline;
232 return undef unless $fieldstr;
233 chomp $fieldstr;
234 my @fields = split /:/,$fieldstr;
235 $self->{row} = (@fields ? \@fields : undef);
236 }
237 sub push_row {
238 my($self, $data, $fields) = @_;
239 my $str = join ':', map { defined $_ ? $_ : '' } @$fields;
240 $self->{fh}->print( $str."\n");
241 1;
242 }
243 sub push_names {}
244 1;
245
246 A sample script to test our new DBD
247 Assuming you saved the DBD::Foo shown above as a file called "Foo.pm"
248 in a directory called "DBD", this script will work, so will most other
249 DBI methods such as selectall_arrayref, fetchrow_hashref, etc.
250
251 #!perl -w
252 use strict;
253 use lib qw(/home/jeff/data/module/lib); # or wherever you stored DBD::Foo
254 use DBI;
255 my $dbh=DBI->connect('dbi:Foo:');
256 $dbh->{RaiseError}=1;
257 $dbh->{PrintError}=0;
258 for my $sql(split /\n/,
259 " DROP TABLE IF EXISTS group_id
260 CREATE TABLE group_id (username CHAR,uid INT, gid INT)
261 INSERT INTO group_id VALUES('joe',1,1)
262 INSERT INTO group_id VALUES('sue',2,1)
263 INSERT INTO group_id VALUES('bob',3,2)
264 SELECT * FROM group_id "
265 ){
266 my $stmt = $dbh->prepare($sql);
267 $stmt->execute;
268 next unless $stmt->{NUM_OF_FIELDS};
269 while (my $row=$stmt->fetch) {
270 print "@$row\n";
271 }
272 }
273
275 Now that we have a basic DBD operational, there are several directions
276 for expansion. In the first place, we might want to override some or
277 all of DBD::File::Table to provide alternate means of reading, writing,
278 and deleting from our data source. We might want to override the
279 open_table() method to provide a different means of identifying column
280 names (e.g. reading them from the file itself) or to provide other
281 kinds of metadata. See SQL::Eval for documentation of the API for
282 ::Table objects and see DBD::File for an example subclass.
283
284 We might want to create extensions to the SQL syntax specific to our
285 DBD. See the section on extending SQL syntax in SQL::Statement::Syntax.
286
287 We might want to provide a completely different kind of data source.
288 See DBD::DBM (whose source code includes documentation on subclassing
289 SQL::Statement and DBD::File), and other DBD::File subclasses such as
290 DBD::CSV.
291
292 We might also want to provide a completely different storage mechanism,
293 something not based on files at all. See DBD::Amazon and DBD::AnyData.
294
295 And we will almost certainly want to fine-tune the DBI interface, see
296 DBI::DBD.
297
299 The dbi-devATperl.org mailing list should be your first stop in
300 creating a new DBD. Tim Bunce, the author of DBI and many DBD authors
301 hang out there. Tell us what you are planning and we will offer
302 suggestions about similar modules or other people working on similar
303 issues, or on how to proceed.
304
306 Copyright (c) 2005, Jeff Zucker <jzuckerATcpan.org>, all rights
307 reserved.
308
309 This document may be freely modified and distributed under the same
310 terms as Perl itself.
311
312
313
314perl v5.12.1 2010-07-12 SQL::Statement::Embed(3)