Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/*
3
 *  $Id: CreoleSQLExecTask.php 266 2007-10-25 01:32:38Z hans $
4
 *
5
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16
 *
17
 * This software consists of voluntary contributions made by many individuals
18
 * and is licensed under the LGPL. For more information please see
19
 * <http://phing.info>.
20
 */
21
 
22
require_once 'phing/tasks/ext/creole/CreoleTask.php';
23
include_once 'phing/system/io/StringReader.php';
24
 
25
/**
26
 * Executes a series of SQL statements on a database using Creole.
27
 *
28
 * <p>Statements can
29
 * either be read in from a text file using the <i>src</i> attribute or from
30
 * between the enclosing SQL tags.</p>
31
 *
32
 * <p>Multiple statements can be provided, separated by semicolons (or the
33
 * defined <i>delimiter</i>). Individual lines within the statements can be
34
 * commented using either --, // or REM at the start of the line.</p>
35
 *
36
 * <p>The <i>autocommit</i> attribute specifies whether auto-commit should be
37
 * turned on or off whilst executing the statements. If auto-commit is turned
38
 * on each statement will be executed and committed. If it is turned off the
39
 * statements will all be executed as one transaction.</p>
40
 *
41
 * <p>The <i>onerror</i> attribute specifies how to proceed when an error occurs
42
 * during the execution of one of the statements.
43
 * The possible values are: <b>continue</b> execution, only show the error;
44
 * <b>stop</b> execution and commit transaction;
45
 * and <b>abort</b> execution and transaction and fail task.</p>
46
 *
47
 * @author    Hans Lellelid <hans@xmpl.org> (Phing)
48
 * @author    Jeff Martin <jeff@custommonkey.org> (Ant)
49
 * @author    Michael McCallum <gholam@xtra.co.nz> (Ant)
50
 * @author    Tim Stephenson <tim.stephenson@sybase.com> (Ant)
51
 * @package   phing.tasks.ext
52
 * @version   $Revision: 1.21 $
53
 */
54
class CreoleSQLExecTask extends CreoleTask {
55
 
56
    private $goodSql = 0;
57
    private $totalSql = 0;
58
 
59
    const DELIM_ROW = "row";
60
    const DELIM_NORMAL = "normal";
61
 
62
    /**
63
     * Database connection
64
     */
65
    private $conn = null;
66
 
67
    /**
68
     * files to load
69
     */
70
    private $filesets = array();
71
 
72
    /**
73
     * all filterchains objects assigned to this task
74
     */
75
    private $filterChains  = array();
76
 
77
    /**
78
     * SQL statement
79
     */
80
    private $statement = null;
81
 
82
    /**
83
     * SQL input file
84
     */
85
    private $srcFile = null;
86
 
87
    /**
88
     * SQL input command
89
     */
90
    private $sqlCommand = "";
91
 
92
    /**
93
     * SQL transactions to perform
94
     */
95
    private $transactions = array();
96
 
97
    /**
98
     * SQL Statement delimiter
99
     */
100
    private $delimiter = ";";
101
 
102
    /**
103
     * The delimiter type indicating whether the delimiter will
104
     * only be recognized on a line by itself
105
     */
106
    private $delimiterType = "normal"; // can't use constant just defined
107
 
108
    /**
109
     * Print SQL results.
110
     */
111
    private $print = false;
112
 
113
    /**
114
     * Print header columns.
115
     */
116
    private $showheaders = true;
117
 
118
    /**
119
     * Results Output file.
120
     */
121
    private $output = null;
122
 
123
 
124
    /**
125
     * Action to perform if an error is found
126
     **/
127
    private $onError = "abort";
128
 
129
    /**
130
     * Encoding to use when reading SQL statements from a file
131
     */
132
    private $encoding = null;
133
 
134
    /**
135
     * Append to an existing file or overwrite it?
136
     */
137
    private $append = false;
138
 
139
    /**
140
     * Set the name of the SQL file to be run.
141
     * Required unless statements are enclosed in the build file
142
     */
143
    public function setSrc(PhingFile $srcFile) {
144
        $this->srcFile = $srcFile;
145
    }
146
 
147
    /**
148
     * Set an inline SQL command to execute.
149
     * NB: Properties are not expanded in this text.
150
     */
151
    public function addText($sql) {
152
        $this->sqlCommand .= $sql;
153
    }
154
 
155
    /**
156
     * Adds a set of files (nested fileset attribute).
157
     */
158
    public function addFileset(FileSet $set) {
159
        $this->filesets[] = $set;
160
    }
161
 
162
    /**
163
     * Creates a filterchain
164
     *
165
     * @access public
166
     * @return  object  The created filterchain object
167
     */
168
    function createFilterChain() {
169
        $num = array_push($this->filterChains, new FilterChain($this->project));
170
        return $this->filterChains[$num-1];
171
    }
172
 
173
    /**
174
     * Add a SQL transaction to execute
175
     */
176
    public function createTransaction() {
177
        $t = new SQLExecTransaction($this);
178
        $this->transactions[] = $t;
179
        return $t;
180
    }
181
 
182
    /**
183
     * Set the file encoding to use on the SQL files read in
184
     *
185
     * @param encoding the encoding to use on the files
186
     */
187
    public function setEncoding($encoding) {
188
        $this->encoding = $encoding;
189
    }
190
 
191
    /**
192
     * Set the statement delimiter.
193
     *
194
     * <p>For example, set this to "go" and delimitertype to "ROW" for
195
     * Sybase ASE or MS SQL Server.</p>
196
     *
197
     * @param delimiter
198
     */
199
    public function setDelimiter($delimiter)
200
    {
201
        $this->delimiter = $delimiter;
202
    }
203
 
204
    /**
205
     * Set the Delimiter type for this sql task. The delimiter type takes two
206
     * values - normal and row. Normal means that any occurence of the delimiter
207
     * terminate the SQL command whereas with row, only a line containing just
208
     * the delimiter is recognized as the end of the command.
209
     *
210
     * @param string $delimiterType
211
     */
212
    public function setDelimiterType($delimiterType)
213
    {
214
        $this->delimiterType = $delimiterType;
215
    }
216
 
217
    /**
218
     * Set the print flag.
219
     *
220
     * @param boolean $print
221
     */
222
    public function setPrint($print)
223
    {
224
        $this->print = (boolean) $print;
225
    }
226
 
227
    /**
228
     * Print headers for result sets from the
229
     * statements; optional, default true.
230
     * @param boolean $showheaders
231
     */
232
    public function setShowheaders($showheaders) {
233
        $this->showheaders = (boolean) $showheaders;
234
    }
235
 
236
    /**
237
     * Set the output file;
238
     * optional, defaults to the console.
239
     * @param PhingFile $output
240
     */
241
    public function setOutput(PhingFile $output) {
242
        $this->output = $output;
243
    }
244
 
245
    /**
246
     * whether output should be appended to or overwrite
247
     * an existing file.  Defaults to false.
248
     * @param $append
249
     */
250
    public function setAppend($append) {
251
        $this->append = (boolean) $append;
252
    }
253
 
254
 
255
    /**
256
     * Action to perform when statement fails: continue, stop, or abort
257
     * optional; default &quot;abort&quot;
258
     */
259
    public function setOnerror($action) {
260
        $this->onError = $action;
261
    }
262
 
263
    /**
264
     * Load the sql file and then execute it
265
     * @throws BuildException
266
     */
267
    public function main()  {
268
 
269
        $savedTransaction = array();
270
        for($i=0,$size=count($this->transactions); $i < $size; $i++) {
271
            $savedTransaction[] = clone $this->transactions[$i];
272
        }
273
 
274
        $savedSqlCommand = $this->sqlCommand;
275
 
276
        $this->sqlCommand = trim($this->sqlCommand);
277
 
278
        try {
279
            if ($this->srcFile === null && $this->sqlCommand === ""
280
                && empty($this->filesets)) {
281
                if (count($this->transactions) === 0) {
282
                    throw new BuildException("Source file or fileset, "
283
                                             . "transactions or sql statement "
284
                                             . "must be set!", $this->location);
285
                }
286
            }
287
 
288
            if ($this->srcFile !== null && !$this->srcFile->exists()) {
289
                throw new BuildException("Source file does not exist!", $this->location);
290
            }
291
 
292
            // deal with the filesets
293
            for ($i = 0,$size=count($this->filesets); $i < $size; $i++) {
294
                $fs = $this->filesets[$i];
295
                $ds = $fs->getDirectoryScanner($this->project);
296
                $srcDir = $fs->getDir($this->project);
297
 
298
                $srcFiles = $ds->getIncludedFiles();
299
 
300
                // Make a transaction for each file
301
                for ($j=0, $size=count($srcFiles); $j < $size; $j++) {
302
                    $t = $this->createTransaction();
303
                    $t->setSrc(new PhingFile($srcDir, $srcFiles[$j]));
304
                }
305
            }
306
 
307
            // Make a transaction group for the outer command
308
            $t = $this->createTransaction();
309
            if ($this->srcFile) $t->setSrc($this->srcFile);
310
            $t->addText($this->sqlCommand);
311
            $this->conn = $this->getConnection();
312
 
313
            try {
314
 
315
                $this->statement = $this->conn->createStatement();
316
 
317
                $out = null;
318
 
319
                try {
320
 
321
                    if ($this->output !== null) {
322
                        $this->log("Opening output file " . $this->output, Project::MSG_VERBOSE);
323
                        $out = new BufferedWriter(new FileWriter($this->output->getAbsolutePath(), $this->append));
324
                    }
325
 
326
                    // Process all transactions
327
                    for ($i=0,$size=count($this->transactions); $i < $size; $i++) {
328
                        $this->transactions[$i]->runTransaction($out);
329
                        if (!$this->isAutocommit()) {
330
                            $this->log("Commiting transaction", Project::MSG_VERBOSE);
331
                            $this->conn->commit();
332
                        }
333
                    }
334
                    if ($out) $out->close();
335
                } catch (Exception $e) {
336
                    if ($out) $out->close();
337
                    throw $e;
338
                }
339
            } catch (IOException $e) {
340
                if (!$this->isAutocommit() && $this->conn !== null && $this->onError == "abort") {
341
                    try {
342
                        $this->conn->rollback();
343
                    } catch (SQLException $ex) {}
344
                }
345
                throw new BuildException($e->getMessage(), $this->location);
346
            } catch (SQLException $e){
347
                if (!$this->isAutocommit() && $this->conn !== null && $this->onError == "abort") {
348
                    try {
349
                        $this->conn->rollback();
350
                    } catch (SQLException $ex) {}
351
                }
352
                throw new BuildException($e->getMessage(), $this->location);
353
            }
354
 
355
            $this->log($this->goodSql . " of " . $this->totalSql .
356
                " SQL statements executed successfully");
357
        } catch (Exception $e) {
358
            $this->transactions = $savedTransaction;
359
            $this->sqlCommand = $savedSqlCommand;
360
            throw $e;
361
        }
362
        // finally {
363
        $this->transactions = $savedTransaction;
364
        $this->sqlCommand = $savedSqlCommand;
365
 
366
    }
367
 
368
 
369
    /**
370
     * read in lines and execute them
371
     * @throws SQLException, IOException
372
     */
373
    public function runStatements(Reader $reader, $out = null) {
374
        $sql = "";
375
        $line = "";
376
 
377
		$buffer = '';
378
 
379
        if ((is_array($this->filterChains)) && (!empty($this->filterChains))) {
380
            $in = FileUtils::getChainedReader(new BufferedReader($reader), $this->filterChains, $this->getProject());
381
			while(-1 !== ($read = $in->read())) { // -1 indicates EOF
382
				   $buffer .= $read;
383
            }
384
            $lines = explode("\n", $buffer);
385
        } else {
386
	        $in = new BufferedReader($reader);
387
 
388
            while (($line = $in->readLine()) !== null) {
389
				$lines[] = $line;
390
			}
391
		}
392
 
393
        try {
394
			foreach ($lines as $line) {
395
                $line = trim($line);
396
                $line = ProjectConfigurator::replaceProperties($this->project, $line,
397
                        $this->project->getProperties());
398
 
399
                if (StringHelper::startsWith("//", $line) ||
400
                    StringHelper::startsWith("--", $line) ||
401
                    StringHelper::startsWith("#", $line)) {
402
                    continue;
403
                }
404
 
405
                if (strlen($line) > 4
406
                        && strtoupper(substr($line,0, 4)) == "REM ") {
407
                    continue;
408
                }
409
 
410
                $sql .= " " . $line;
411
                $sql = trim($sql);
412
 
413
                // SQL defines "--" as a comment to EOL
414
                // and in Oracle it may contain a hint
415
                // so we cannot just remove it, instead we must end it
416
                if (strpos($line, "--") !== false) {
417
                    $sql .= "\n";
418
                }
419
 
420
                if ($this->delimiterType == self::DELIM_NORMAL
421
                        && StringHelper::endsWith($this->delimiter, $sql)
422
                        || $this->delimiterType == self::DELIM_ROW
423
                        && $line == $this->delimiter) {
424
                    $this->log("SQL: " . $sql, Project::MSG_VERBOSE);
425
                    $this->execSQL(StringHelper::substring($sql, 0, strlen($sql) - strlen($this->delimiter)), $out);
426
                    $sql = "";
427
                }
428
            }
429
 
430
            // Catch any statements not followed by ;
431
            if ($sql !== "") {
432
                $this->execSQL($sql, $out);
433
            }
434
        } catch (SQLException $e) {
435
            throw new BuildException("Error running statements", $e);
436
        }
437
    }
438
 
439
 
440
    /**
441
     * Exec the sql statement.
442
     * @throws SQLException
443
     */
444
    protected function execSQL($sql, $out = null) {
445
        // Check and ignore empty statements
446
        if (trim($sql) == "") {
447
            return;
448
        }
449
 
450
        try {
451
            $this->totalSql++;
452
            if (!$this->statement->execute($sql)) {
453
                $this->log($this->statement->getUpdateCount() . " rows affected", Project::MSG_VERBOSE);
454
            } else {
455
                if ($this->print) {
456
                    $this->printResults($out);
457
                }
458
            }
459
 
460
            $this->goodSql++;
461
 
462
        } catch (SQLException $e) {
463
            $this->log("Failed to execute: " . $sql, Project::MSG_ERR);
464
            if ($this->onError != "continue") {
465
                throw new BuildException("Failed to execute SQL", $e);
466
            }
467
            $this->log($e->getMessage(), Project::MSG_ERR);
468
        }
469
    }
470
 
471
    /**
472
     * print any results in the statement.
473
     * @throw SQLException
474
     */
475
    protected function printResults($out = null) {
476
 
477
        $rs = null;
478
        do {
479
            $rs = $this->statement->getResultSet();
480
 
481
            if ($rs !== null) {
482
 
483
                $this->log("Processing new result set.", Project::MSG_VERBOSE);
484
 
485
                $line = "";
486
 
487
                $colsprinted = false;
488
 
489
                while ($rs->next()) {
490
                    $fields = $rs->getRow();
491
 
492
                    if (!$colsprinted && $this->showheaders) {
493
                        $first = true;
494
                        foreach($fields as $fieldName => $ignore) {
495
                            if ($first) $first = false; else $line .= ",";
496
                            $line .= $fieldName;
497
                        }
498
                        if ($out !== null) {
499
                            $out->write($line);
500
                            $out->newLine();
501
                        } else {
502
                            print($line.PHP_EOL);
503
                        }
504
                        $line = "";
505
                        $colsprinted = true;
506
                    } // if show headers
507
 
508
                    $first = true;
509
                    foreach($fields as $columnValue) {
510
 
511
                        if ($columnValue != null) {
512
                            $columnValue = trim($columnValue);
513
                        }
514
 
515
                        if ($first) {
516
                            $first = false;
517
                        } else {
518
                            $line .= ",";
519
                        }
520
                        $line .= $columnValue;
521
                    }
522
 
523
                    if ($out !== null) {
524
                        $out->write($line);
525
                        $out->newLine();
526
                    } else {
527
                        print($line . PHP_EOL);
528
                    }
529
                    $line = "";
530
 
531
                } // while rs->next()
532
            }
533
        } while ($this->statement->getMoreResults());
534
        print(PHP_EOL);
535
        if ($out !== null) $out->newLine();
536
    }
537
}
538
 
539
 
540
/**
541
 * "Inner" class that contains the definition of a new transaction element.
542
 * Transactions allow several files or blocks of statements
543
 * to be executed using the same JDBC connection and commit
544
 * operation in between.
545
 */
546
class SQLExecTransaction {
547
 
548
    private $tSrcFile = null;
549
    private $tSqlCommand = "";
550
    private $parent;
551
 
552
    function __construct($parent)
553
    {
554
        // Parent is required so that we can log things ...
555
        $this->parent = $parent;
556
    }
557
 
558
    public function setSrc(PhingFile $src)
559
    {
560
        $this->tSrcFile = $src;
561
    }
562
 
563
    public function addText($sql)
564
    {
565
        $this->tSqlCommand .= $sql;
566
    }
567
 
568
    /**
569
     * @throws IOException, SQLException
570
     */
571
    public function runTransaction($out = null)
572
    {
573
        if (!empty($this->tSqlCommand)) {
574
            $this->parent->log("Executing commands", Project::MSG_INFO);
575
            $this->parent->runStatements(new StringReader($this->tSqlCommand), $out);
576
        }
577
 
578
        if ($this->tSrcFile !== null) {
579
            $this->parent->log("Executing file: " . $this->tSrcFile->getAbsolutePath(),
580
                Project::MSG_INFO);
581
 
582
            $reader = new FileReader($this->tSrcFile);
583
 
584
            $this->parent->runStatements($reader, $out);
585
            $reader->close();
586
        }
587
    }
588
}
589
 
590