/
Relational.php
691 lines (630 loc) · 20.9 KB
/
Relational.php
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Brent Cook |
// +----------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or |
// | modify it under the terms of the GNU Lesser General Public |
// | License as published by the Free Software Foundation; either |
// | version 2.1 of the License, or (at your option) any later version. |
// | |
// | This library is distributed in the hope that it will be useful, |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
// | Lesser General Public License for more details. |
// | |
// | You should have received a copy of the GNU Lesser General Public |
// | License along with this library; if not, write to the Free Software |
// | Foundation, Inc., 59 Temple Place, Suite 330,Boston,MA 02111-1307 USA|
// +----------------------------------------------------------------------+
// | Authors: Brent Cook <busterb@mail.utexas.edu> |
// | Jacob Lee <jacobswell4u@yahoo.com> |
// +----------------------------------------------------------------------+
//
// $Id$
require_once 'PEAR.php';
require_once 'DBA/Table.php';
require_once 'DBA/TempTable.php';
/**
* A relational database manager using DBA_Table as a storage object.
* DBA_Relational extends DBA_Table by providing uniform access to multiple
* tables, automatically opening, closing and locking tables as needed by
* various operations, and provides a join operation.
*
* @author Brent Cook <busterb@mail.utexas.edu>
* @package DBA
* @access public
* @version 0.19
*/
class DBA_Relational extends PEAR
{
// {{{ instance variables
/**
* Handles to table objects
* @access private
* @var array
*/
var $_tables=array();
/**
* Location of table data files
* @access private
* @var string
*/
var $_home;
/**
* Table driver to use
* @access private
* @var string
*/
var $_driver;
// }}}
// {{{ DBA_Relational($home = '', $driver = 'file')
/**
* Constructor
*
* @param string $home path where data files are stored
* @param string $driver DBA driver to use
*/
function DBA_Relational($home = '', $driver = 'file')
{
// call the base constructor
$this->PEAR();
// add trailing slash to home if not present
if (substr($home, -1) != '/') {
$home = $home.'/';
}
$this->_home = $home;
$this->_driver = $driver;
// create the _tables table. this keeps track of the tables to be used
// in DBA_Relational as well as which driver to use for each.
if (!$this->tableExists('_tables')) {
$this->createTable('_tables',
array('name'=>array('type'=>'varchar',
'primary_key'=>true,
'size'=>20),
'driver'=>array('type'=>'varchar'),
'description'=>array('type'=>'text')));
}
// create the _sequences table. this keeps track of named sequences
if (!$this->tableExists('_sequences')) {
$this->createTable('_sequences',
array('name'=>array('type'=>'varchar',
'primary_key'=>true,
'size'=>20),
'value'=>array('type'=>'integer'),
'increment'=>array('type'=>'integer')));
}
}
// }}}
// {{{ raiseError($message)
function raiseError($message) {
return PEAR::raiseError('DBA_Relational: '.$message);
}
// }}}
// {{{ close()
/**
* Closes all open tables
*
* @access public
*/
function close()
{
if (sizeof($this->_tables)) {
reset($this->_tables);
$this->_tables[key($this->_tables)]->close();
while(next($this->_tables)) {
$this->_tables[key($this->_tables)]->close();
}
}
}
// }}}
// {{{ _DBA_Relational()
/**
* PEAR emulated destructor calls close on PHP shutdown
* @access private
*/
function _DBA_Relational()
{
$this->close();
}
// }}}
// {{{ _openTable($tableName, $mode = 'r')
/**
* Opens a table, keeps it in the list of tables. Can also reopen tables
* to different file modes
*
* @access private
* @param string $tableName name of the table to open
* @param char $mode mode to open the table; one of r,w,c,n
* @return object PEAR_Error on failure
*/
function _openTable($tableName, $mode = 'r')
{
if (!isset($this->_tables[$tableName])) {
if (!$this->tableExists($tableName)) {
return $this->raiseError('table '.$tableName.' does not exist');
} else {
$this->_tables[$tableName] =& new DBA_Table($this->_driver);
}
}
if (!$this->_tables[$tableName]->isOpen()) {
return $this->_tables[$tableName]->open($this->_home.$tableName, $mode);
} else {
if (($mode == 'r') && !$this->_tables[$tableName]->isReadable()) {
// obtain a shared lock on the table
return $this->_tables[$tableName]->lockSh();
} elseif (($mode == 'w') &&
!$this->_tables[$tableName]->isWritable()){
// obtain an exclusive lock on the table
return $this->_tables[$tableName]->lockEx();
}
}
}
// }}}
// {{{ tableExists($tableName)
/**
* Returns whether the specified table exists in the db home
*
* @param string $tableName table to check existence of
* @return boolean true if the table exists, false if it doesn't
*/
function tableExists($tableName)
{
return DBA::db_exists($this->_home.$tableName, $this->_driver);
}
// }}}
// {{{ createTable
/**
* Creates a new table
*
* @access public
* @param string $tableName name of the table to create
* @param array $schema field schema for the table
* @param string $driver driver to use for this table
* @return object PEAR_Error on failure
*/
function createTable($tableName, $schema, $driver=null)
{
if (is_null($driver)) {
$driver = $this->_driver;
}
$this->insert('_tables', array($tableName, $driver));
return DBA_Table::create($this->_home.$tableName, $schema, $driver);
}
// }}}
// {{{ dropTable
/**
* Deletes a table permanently
*
* @access public
* @param string $tableName name of the table to delete
* @param string $driver driver that created the table
* @return object PEAR_Error on failure
*/
function dropTable($tableName, $driver=null)
{
if (is_null($driver)) {
$driver = $this->_driver;
}
if (isset($this->_tables[$tableName])) {
if (PEAR::isError($result = $this->_tables[$tableName]->close())) {
return $result;
}
unset($this->_tables[$tableName]);
}
return DBA::db_drop($tableName, $driver);
}
// }}}
// {{{ getSchema
/**
* Returns an array with the stored schema for the table
*
* @param string $tableName
* @return array
*/
function getSchema($tableName)
{
$result = $this->_openTable($tableName, 'w');
if (PEAR::isError($result)) {
return $result;
} else {
return $this->_tables[$tableName]->getSchema();
}
}
// }}}
// {{{ isOpen($tableName)
/**
* Returns the current read status for the database
*
* @return boolean
*/
function isOpen($tableName)
{
if (isset($this->_tables[$tableName])) {
return $this->_tables[$tableName]->isOpen();
} else {
return false;
}
}
// }}}
// {{{ insert($tableName, $data)
/**
* Inserts a new row in a table
*
* @param string $tableName table on which to operate
* @param array $data assoc array or ordered list of data to insert
* @return mixed PEAR_Error on failure, the row index on success
*/
function insert($tableName, $data)
{
$result = $this->_openTable($tableName, 'w');
if (PEAR::isError($result)) {
return $result;
} else {
return $this->_tables[$tableName]->insert($data);
}
}
// }}}
// {{{ replace($tableName, $rawQuery, $data, $rows=null)
/**
* Replaces rows that match $rawQuery
*
* @access public
* @param string $rawQuery query expression for performing the replace
* @param array $rows subset of rows to choose from
* @return object PEAR_Error on failure
*/
function replace($tableName, $rawQuery, $data, $rows=null)
{
$result = $this->_openTable($tableName, 'w');
if (PEAR::isError($result)) {
return $result;
} else {
return $this->_tables[$tableName]->replace($rawQuery, $data, $rows);
}
}
// }}}
// {{{ replaceKey($tableName, $key, $data)
/**
* Replaces an existing row in a table, inserts if the row does not exist
*
* @access public
* @param string $tableName table on which to operate
* @param string $key row id to replace
* @param array $data assoc array or ordered list of data to insert
* @return mixed PEAR_Error on failure, the row index on success
*/
function replaceKey($tableName, $key, $data)
{
$result = $this->_openTable($tableName, 'w');
if (PEAR::isError($result)) {
return $result;
} else {
return $this->_tables[$tableName]->replaceKey($key, $data);
}
}
// }}}
// {{{ remove($tableName, $rawQuery, $rows=null)
/**
* Removes rows that match $rawQuery with $
*
* @access public
* @param string $rawQuery query expression for performing the remove
* @param array $rows subset of rows to choose from
* @return object PEAR_Error on failure
*/
function remove($tableName, $rawQuery, $rows=null)
{
$result = $this->_openTable($tableName, 'w');
if (PEAR::isError($result)) {
return $result;
} else {
return $this->_tables[$tableName]->remove($rawQuery, $rows);
}
}
// }}}
// {{{ removeKey($tableName, $key)
/**
* Remove an existing row in a table
*
* @access public
* @param string $tableName table on which to operate
* @param string $key row id to remove
* @return object PEAR_Error on failure
*/
function removeKey($tableName, $key)
{
$result = $this->_openTable($tableName, 'w');
if (PEAR::isError($result)) {
return $result;
} else {
return $this->_tables[$tableName]->remove($key);
}
}
// }}}
// {{{ fetch($tableName, $key)
/**
* Fetches an existing row from a table
*
* @access public
* @param string $tableName table on which to operate
* @param string $key row id to fetch
* @return mixed PEAR_Error on failure, the row array on success
*/
function fetch($tableName, $key)
{
$result = $this->_openTable($tableName, 'r');
if (PEAR::isError($result)) {
return $result;
} else {
return $this->_tables[$tableName]->fetch($key);
}
}
// }}}
// {{{ sort($fields, $order='a', $rows)
/**
* Sorts rows by field in either ascending or descending order
* SQL analog: 'select * from rows, order by fields'
*
* @access public
* @param mixed $fields a string with the field name to sort by or an
* array of fields to sort by in order of preference
* @param string $order 'a' for ascending, 'd' for descending
* @param array $rows rows to sort, sorts the entire table if not
* specified
* @return mixed PEAR_Error on failure, the row array on success
*/
function sort($fields, $order='a', $rows)
{
return DBA_Table::sort($fields, $order, $rows);
}
// }}}
// {{{ project($fields, $rows)
/**
* Projects rows by field. This means that a subset of the possible fields
* are in the resulting rows. The SQL analog is 'select fields from table'
*
* @access public
* @param array $fields fields to project
* @param array $rows rows to project, projects entire table if not
* specified
* @return mixed PEAR_Error on failure, the row array on success
*/
function project($fields, $rows)
{
return DBA_Table::project($fields, $rows);
}
// }}}
// {{{ unique($rows)
/**
* Returns the unique rows from a set of rows
*
* @access public
* @param array $rows rows to process, uses entire table if not
* specified
* @return mixed PEAR_Error on failure, the row array on success
*/
function unique($rows)
{
return DBA_Table::unique($rows);
}
// }}}
// {{{ finalize($tableName, $rows=null)
/**
* Converts the results from any of the row operations to a 'finalized'
* display-ready form. That means that timestamps, sets and enums are
* converted into strings. This obviously has some consequences if you plan
* on chaining the results into another row operation, so don't call this
* unless it is the final operation.
*
* This function does not yet work reliably with the results of a join
* operation, due to a loss of metadata
*
* @access public
* @param string $tableName table on which to operate
* @param array $rows rows to finalize, if none are specified, returns
* the whole table
* @return mixed PEAR_Error on failure, the row array on success
*/
function finalize($tableName, $rows=null)
{
$result = $this->_openTable($tableName, 'r');
if (PEAR::isError($result)) {
return $result;
} else {
return $this->_tables[$tableName]->finalize($rows);
}
}
// }}}
// {{{ _validateTable(&$table, &$rows, &$fields, $altName)
/**
* Verifies that the fields submitted exist in $table
* @access private
*/
function _validateTable(&$table, &$rows, &$fields, $altName)
{
// validate query by checking for existence of fields
if (is_string($table) && !PEAR::isError($this->_openTable($table, 'r')))
{
$rows = $this->_tables[$table]->getRows();
$fields = $this->_tables[$table]->getFieldNames();
return true;
} elseif (is_array($table) && sizeof($table)) {
reset($table);
$rows = $table;
$fields = array_keys(current($table));
$table = $altName;
return true;
} elseif (is_null($table)) {
$fields = null;
$rows = null;
return true;
} else {
return false;
}
}
// }}}
// {{{ select($tableName, $query, $rows=null)
/**
* Performs a select on a table. This means that a subset of rows in a
* table are filtered and returned based on the query. Accepts any valid
* expression of the form '(field == field) || (field > 3)', etc. Using the
* expression '*' returns the entire table
* SQL analog: 'select * from rows where rawQuery'
*
* @access public
* @param string $tableName table on which to operate
* @param string $query query expression for performing the select
* @return mixed PEAR_Error on failure, the row array on success
*/
function select($tableName, $query='*', $rows=null)
{
$result = $this->_openTable($tableName,'r');
if (PEAR::isError($result)) {
return $result;
}
// use the table's select query parser
return $this->_tables[$tableName]->select($query, $rows);
}
// }}}
//{{{ &join($tableA,$tableB,$option=array())
/**
* Makes a new table joining 2 existing tables
*
* @access public
* @param $tableA mixed table name or object to join
* @param $tableB mixed table name or object to join
* @param string $query query expression for performing the select
* @param $type string 'inner','outer','right','left'
* @param array $option join options
* @return object PEAR_Error on failure, DBA_TempTable on success
*/
function &joinTables($tableA, $tableB, $clause=true, $type='inner')
{
if (!DBA_TempTable::isTempTable($tableA)) {
// Open table
$result = $this->_openTable($tableA, 'r');
if (PEAR::isError($result))
return $result;
// Makes a DBA_TempTable object
$objectA = new DBA_TempTable($this->_tables[$tableA], $option['alias'][0]);
} else {
$objectA = &$tableA;
}
if (!DBA_TempTable::isTempTable($tableB)) {
// Open table
$result = $this->_openTable($tableB, 'r');
if (PEAR::isError($result))
return $result;
// Makes a DBA_TempTable object
$objectB = new DBA_TempTable($this->_tables[$tableB], $option['alias'][1]);
} else {
$objectB = &$tableB;
}
if (isset($option['on'])) {
$onJoin = $this->_parsePHPQuery($option['on']);
if (PEAR::isError($onJoin))
return $onJoin;
} else {
$onJoin = 1;
}
if (isset($option['using'])) {
//not supported yet...
}
$PHPeval = '
// get each row from each table object and make a new row
$keyA = $objectA->firstRow();
while($keyA !== false) {
$rowA = $objectA->getRow($keyA);
$keyB = $objectB->firstRow();
while($keyB !== false) {
$rowB = $objectB->getRow($keyB);
//make a new row
$row = $rowA+$rowB;
if ('.$onJoin.') {
$rows[] = $row;
}
$keyB = $objectB->nextRow();
}
$keyA = $objectA->nextRow();
}
// If join type is outer
if ($type != "inner") {
// Makes blank rows;
$blankA = $objectA->blankRow();
$blankB = $objectB->blankRow();
if ($type != "right") {
$keyA = $objectA->firstRow();
while ($keyA !== false) {
$rowA = $objectA->getRow($keyA);
$row = $rowA + $blankB;
if ('.$onJoin.') {
$rows[] = $row;
}
$keyA = $objectA->nextRow();
}
}
if ($type != "left") {
$keyB = $objectB->firstRow();
while ($keyB !== false) {
$rowB = $objectB->getRow($keyB);
$row = $blankA + $rowB;
if ('.$onJoin.') {
$rows[] = $row;
}
$keyB = $objectB->nextRow();
}
}
}
';
eval($PHPeval);
// set rows to newly created object
$tblObject = new DBA_TempTable();
$tblObject->set($rows);
// return table object
return $tblObject;
}
// }}}
// {{{ join($tableA, $tableB, $rawQuery)
/**
* Joins rows between two tables based on a query.
*
* @access public
* @param string $tableA name of table to join
* @param string $tableB name of table to join
* @param string $rawQuery expression of how to join tableA and tableB
* @return mixed PEAR_Error on failure, the row array on success
*/
function join($tableA, $tableB, $rawQuery)
{
return $this->raiseError('TODO: merge new join()');
/*
// validate tables
if (!$this->_validateTable($tableA, $rowsA, $fieldsA, 'A')) {
return $this->raiseError("$tableA not in query");
}
if (!$this->_validateTable($tableB, $rowsB, $fieldsB, 'B')) {
return $this->raiseError("$tableA not in query");
}
// check for empty tables
if (is_null($rowsA) || is_null($rowsB)) {
return null;
}
// TODO Implement merge join, needs secondary indexes on tables
// build the join operation with nested loops
$query = $this->_parsePHPQuery($rawQuery, $fieldsA, $fieldsB,
$tableA, $tableB);
if (PEAR::isError($query)) {
return $query;
}
$results = array();
$PHPJoin = 'foreach ($rowsA as $rowA) foreach ($rowsB as $rowB) if ('.
$this->_parsePHPQuery($rawQuery, $fieldsA, $fieldsB, $tableA, $tableB)
.') $results[] = array_merge($rowA, $rowB);';
// evaluate the join
eval ($PHPJoin);
return $results;
*/
}
// }}}
}