Project

General

Profile

« Previous | Next » 

Revision 2123

Added by darkviper about 9 years ago

! /framework/SqlImport modified for new export format of phpMyAdmin

View differences:

SqlImport.php
25 25
 * @copyright    Manuela v.d.Decken <manuela@isteam.de>
26 26
 * @author       Manuela v.d.Decken <manuela@isteam.de>
27 27
 * @license      http://www.gnu.org/licenses/gpl.html   GPL License
28
 * @version      0.0.1
28
 * @version      0.0.2
29 29
 * @revision     $Revision: 2070 $
30 30
 * @lastmodified $Date: 2014-01-03 02:21:42 +0100 (Fr, 03 Jan 2014) $
31 31
 * @since        File available since 30.11.2014
32 32
 * @description  can handle different actions with SQL-Import files
33 33
 */
34
class SqlImport {
35 34

  
35
class SqlImport
36
{
37

  
36 38
/** WbDatabase object */
37 39
    protected $oDb          = null;
38 40
/** valide structure file to use for import */
......
52 54
        'upgrade'
53 55
    );
54 56
/** collected additional replacements pairs */
55
    protected $aReplacements = array('key'=>array(), 'value'=>array());
57
    protected $aAdditionalReplacements = array('key'=>array(), 'value'=>array());
56 58
/** possible engines to use */
57 59
    protected $aEngineTypesAvail = array(
58
        'memory'     => 'MEMORY',
59 60
        'myisam'     => 'MyISAM',
60
        'innodb'     => 'InnoDB',
61
        'archive'    => 'ARCHIVE'
61
        'innodb'     => 'InnoDB'
62 62
    );
63 63
/** SQL objects witch can be handled */
64 64
    protected $aAvailSqlObjects = array(
......
70 70
        'TRIGGER',
71 71
        'EVENT'
72 72
    );
73
/** prepared batch lists for all SQL statements */
74
    protected $aBatchLists = array();
75
/* ****************************************************************** */
73 76
/**
74 77
 * Constructor
75 78
 * @param WbDatabase $oDb
......
79 82
    {
80 83
        $this->oDb = $oDb;
81 84
        $this->sTablePrefix = $oDb->TablePrefix;
82
        if (file_exists($sStructFile)) {
83
            $this->sStructFile = str_replace('\\', '/', $sStructFile);
84
        } else {
85
            $this->sStructFile = '';
86
            $this->aErrMsg[] = 'file with structure not available! ['.$sStructFile.']';
85
        $sStructFile = str_replace('\\', '/', $sStructFile);
86
        if (!file_exists($sStructFile)) {
87
            throw new SqlImportException('no existing import file defined');
87 88
        }
89
        $this->sStructFile = $sStructFile;
88 90
    }
91
// ---------------------------------------------------------------------
89 92
/**
93
 * Start and execute the import
94
 * @param string $sAction
95
 * @return boolean
96
 */
97
    public function doImport($sAction)
98
    {
99
        try{
100
            $sAction           = $this->getSanitizeAction($sAction);
101
            $aRawCommandSource = $this->getSorceFile($this->sStructFile);
102
            $aSqlCommandList   = $this->doNormalizeSource($aRawCommandSource);
103
            $aSqlCommandList   = $this->replacePlaceholders($aSqlCommandList);
104
            $this->splitStatementsListIntoBatchLists($aSqlCommandList, $sAction);
105
            unset($aRawCommandSource, $aSqlCommandList); // free never needed memory
106
            $this->executeBatchStack();
107
        } catch(SqlImportException $e) {
108
            $this->aErrMsg[] = (string)$e;
109
            return false;
110
        }
111
        return true;
112
    }
113
// ---------------------------------------------------------------------
114
/**
115
 * Sanitize Action and get a valid value
116
 * @param string $sAction simple command or filename/filepath
117
 * @return string valid command
118
 */
119
    protected function getSanitizeAction($sAction)
120
    {
121
        $sAction = strtolower( // extract command from given argument
122
            preg_replace(
123
                '/^.*?('.implode('|', $this->aActions).')(\.php)?$/i',
124
                '$1',
125
                $sAction
126
            )
127
        );
128
        // return 'install' if no command found
129
        return ($sAction == '' ? 'install' : $sAction);
130
    }
131
// ---------------------------------------------------------------------
132
/**
133
 * Get content of structure file
134
 * @param string $sStructFile filepath of the structure file
135
 * @return array raw list of commands (including comments)
136
 */
137
    protected function getSorceFile($sStructFile)
138
    {
139
        if (!$sStructFile) {
140
        // no file found so ignore import method
141
            throw new SqlImportException('no import file defined');
142
        }
143
        if (!is_readable($sStructFile)) {
144
        // file fond but it's not readable
145
            throw new SqlImportException('not readable file ['.$sStructFile.']');
146
        }
147
        if (!($aCommandSource = file($sStructFile, FILE_SKIP_EMPTY_LINES|FILE_IGNORE_NEW_LINES))) {
148
        // file fond but it's not readable
149
            throw new SqlImportException('unable to import file ['.$sStructFile.']');
150
        }
151
        return $aCommandSource;
152
    }
153
// ---------------------------------------------------------------------
154
/**
155
 * Normalize SQL statements from source
156
 * @param array $aSqlCommandSource array of all lines from source file
157
 * @return array list of complete SQL statements with placeholders
158
 * @description concate each Statement into one line; remove all comments; remove possible BOM
159
 */
160
    protected function doNormalizeSource(array $aRawCommandSource)
161
    {
162
        $aSqlCommandList = array();
163
        // remove possible ByteOrderMark
164
        $aRawCommandSource[0] = preg_replace('/^[\xAA-\xFF]{3}/', '', $aRawCommandSource[0]);
165
        // init empty statement line
166
        $sStatementLine = '';
167
        // iterate through all lines of the source
168
        foreach ($aRawCommandSource as $iKey => $sRawLine) {
169
            // remove trailing and leading whitespaces
170
            $sRawLine = trim($sRawLine);
171
            // skip line if it's a comment
172
            if (preg_match('/^--/', $sRawLine)) { continue; }
173
            // attach line to buffer
174
            $sStatementLine .= ' '.$sRawLine;
175
            // detect end of statement
176
            if ((substr($sStatementLine,-1,1) == ';')) {
177
                $aSqlCommandList[] = trim($sStatementLine);
178
                $sStatementLine = '';
179
            }
180
        }
181
        return $aSqlCommandList;
182
    }
183
// ---------------------------------------------------------------------
184
/**
185
 * Replace all Placeholders in all SQL statements
186
 * @param array $aSqlCommandList  list of SQL statements with placeholders
187
 * @return array finished list of SQL statements
188
 */
189
    protected function replacePlaceholders(array $aSqlCommandList)
190
    {
191
        // sanitize arguments and extract Charset from Collation
192
        $aTmp = preg_split('/_/', $this->sCollation, null, PREG_SPLIT_NO_EMPTY);
193
        $sCharset = $aTmp[0];
194
        // get from addReplacements
195
        $aSearch  = $this->aAdditionalReplacements['key'];
196
        /* ***  ATTENTION:: Do Not Change The Order Of Search-Replace Statements !! *** */
197
        // define basic array of searches
198
        $aSearch[] = '/\{TABLE_PREFIX\}/';                                        /* step 0 */
199
        $aSearch[] = '/\{FIELD_CHARSET\}/';                                       /* step 1 */
200
        $aSearch[] = '/\{FIELD_COLLATION\}/';                                     /* step 2 */
201
        $aSearch[] = '/\{TABLE_ENGINE\}/';                                        /* step 3 */
202
        $aSearch[] = '/\{TABLE_ENGINE=([a-zA-Z_0-9]*)\}/';                        /* step 4 */
203
        $aSearch[] = '/\{CHARSET\}/';                                             /* step 5 */
204
        $aSearch[] = '/\{COLLATION\}/';                                           /* step 6 */
205
        // get from addReplacements
206
        $aReplace = $this->aAdditionalReplacements['value'];
207
        // define basic array of replacements
208
        $aReplace[] = $this->sTablePrefix;                                        /* step 0 */
209
        $aReplace[] = ' CHARACTER SET {CHARSET}';                                 /* step 1 */
210
        $aReplace[] = ' COLLATE {COLLATION}';                                     /* step 2 */
211
        $aReplace[] = ' {TABLE_ENGINE='.$this->sEngine.'}';                       /* step 3 */
212
        $aReplace[] = ' ENGINE=$1 DEFAULT CHARSET={CHARSET} COLLATE={COLLATION}'; /* step 4 */
213
        $aReplace[] = $sCharset;                                                  /* step 5 */
214
        $aReplace[] = $this->sCollation;                                          /* step 6 */
215
        // replace placeholders in statement
216
        $aSqlCommandList = preg_replace($aSearch, $aReplace, $aSqlCommandList);
217
        return $aSqlCommandList;
218
    }
219
// ---------------------------------------------------------------------
220
/**
221
 * Split list of commands into a batch list structure
222
 * @param array $aSqlCommandList normalized list of SQL statements
223
 * @param string $sAction a valid name of an action
224
 *
225
 */
226
    protected function splitStatementsListIntoBatchLists(array $aSqlCommandList, $sAction)
227
    {
228
        // initialize lists
229
        $this->aBatchLists = array(
230
            'Set'    => array(
231
                            'Header' => array(), // all SET before DROP/CREATE/ALTER
232
                            'Footer' => array()  // all SET after DROP/CREATE/ALTER
233
                        ),
234
            'Drop'   => array(),
235
            'Create' => array(),
236
            'Alter'  => array()
237
        );
238
        $sSetType = 'Header'; // start with 'before'
239
        foreach ($aSqlCommandList as $sSqlCommand) {
240
            if (preg_match('/^\s*DROP\s*/si', $sSqlCommand)) {
241
                switch ($sAction) {
242
                    case 'uninstall':
243
                    case 'install';
244
                        $this->aBatchLists['Drop'][] = $sSqlCommand;
245
                        break;
246
                    default: // skip DROP TABLE
247
                        break;
248
                }
249
                $sSetType = 'Footer'; // switch to 'after'
250
            } elseif (preg_match('/^\s*ALTER\s*/si', $sSqlCommand)) {
251
                switch ($sAction) {
252
                    case 'upgrade':
253
                    case 'install';
254
                        $this->aBatchLists['Alter'][] = $sSqlCommand;
255
                        break;
256
                    default: // skip ALTER TABLE
257
                        break;
258
                }
259
                $sSetType = 'Footer'; // switch to 'after'
260
            } elseif (preg_match('/^\s*\/\*\![^\s]+\s+SET\s|^\s*SET\s*/si', $sSqlCommand)) {
261
                $this->aBatchLists['Set'][$sSetType][] = $sSqlCommand;
262
            } else {
263
                switch ($sAction) {
264
                    case 'upgrade':
265
                    case 'install';
266
                        $this->aBatchLists['Create'][] = $sSqlCommand;
267
                        break;
268
                    default: // skip CREATE TABLE
269
                        break;
270
                }
271
                $sSetType = 'Footer'; // switch to 'after'
272
            }
273
        }
274
    }
275
// ---------------------------------------------------------------------
276
/**
277
 * Scan database for all matching tables
278
 * @return array List of matchng tables
279
 */
280
    protected function getAllExistingTablenames()
281
    {
282
        $aRetval = array();
283
        $sql = 'SHOW TABLES LIKE \''.addcslashes($this->sTablePrefix, '_%').'%\'';
284
        if ((($oRecordSet = $this->oDb->doQuery($sql)))) {
285
            while (($aRecord = $oRecordSet->fetchArray(MYSQLI_NUM))) {
286
                $aRetval[] = $aRecord[0];
287
            }
288
        }
289
        return $aRetval;
290
    }
291
// ---------------------------------------------------------------------
292
/**
293
 * Execute a stack of batch lists with SQL statements
294
 * @param array $aAllowed  list of tables which are allowed to be altered
295
 * @return boolean
296
 */
297
    protected function executeBatchStack()
298
    {
299
        if ($this->aBatchLists['Set']['Header']) {
300
            $this->executeBatchList($this->aBatchLists['Set']['Header']);
301
        }
302
        if ($this->aBatchLists['Drop']) {
303
            $this->executeBatchList($this->aBatchLists['Drop']);
304
        }
305
        if ($this->aBatchLists['Create']) {
306
            $aTablesListBeforeCreate = $this->getAllExistingTablenames();
307
            $this->executeBatchList($this->aBatchLists['Create']);
308
            $aTablesListAfterCreate = $this->getAllExistingTablenames();
309
        }
310
        if ($this->aBatchLists['Alter']) {
311
            $this->executeBatchList(
312
                $this->aBatchLists['Alter'],
313
                array_diff($aTablesListAfterCreate, $aTablesListBeforeCreate)
314
            );
315
        }
316
        if ($this->aBatchLists['Set']['Footer']) {
317
            $this->executeBatchList($this->aBatchLists['Set']['Footer']);
318
        }
319
    }
320
// ---------------------------------------------------------------------
321
/**
322
 * Execute a list of SQL statements
323
 * @param array $aList the list to execute
324
 * @param array $aAllowedTables list with allowed tables
325
 * @description Execute a list of SQL statements. The list can be restricted
326
 *              using $aAllowedTables
327
 */
328
    protected function executeBatchList(array $aList, array $aAllowedTables = null)
329
    {
330
        foreach ($aList as $sSql) {
331
            if ($aAllowedTables) {
332
                // extract name of table
333
                $sTableName = preg_replace('/^[^\`]*?\`([^\`]*?)\`.*$/si', '$1', $sSql);
334
                if (!in_array($sTableName, $aAllowedTables)) {
335
                // if table is not in allowed list then use next statement
336
                    continue;
337
                }
338
            }
339
            // execute statement
340
            if (!$this->oDb->doQuery(trim($sSql))) {
341
                throw new SqlImportException($this->oDb->getError());
342
                break;
343
            }
344
        }
345
    }
346
// ---------------------------------------------------------------------
347
/**
90 348
 * Add Key-Value pairs for additional placeholders
91 349
 * @param string $sKey
92 350
 * @param mixed $sValue
......
94 352
    public function addReplacement($sKey, $sValue = '')
95 353
    {
96 354
        $sKey = strtoupper(preg_replace('/([a-z0-9])([A-Z])/', '\1_\2', ltrim($sKey, 'a..z')));
97
        $this->aReplacements['key'][]  = '/\{'.$sKey.'\}/';
98
        $this->aReplacements['value'][] = $sValue;
355
        $this->aAdditionalReplacements['key'][]  = '/\{'.$sKey.'\}/';
356
        $this->aAdditionalReplacements['value'][] = $sValue;
99 357
    }
358
// ---------------------------------------------------------------------
100 359
/**
101 360
 * define another collation then default utf8_unicode_ci
102 361
 * @param string $sCollation
......
116 375
        }
117 376
        return $bRetval;
118 377
    }
378
// ---------------------------------------------------------------------
119 379
/**
120
 * define another prefix then 'MyISAM'
380
 * define another default engine then 'MyISAM'
121 381
 * @param string $sEngine
122 382
 * @return boolean
123 383
 */
......
131 391
        }
132 392
        return $bRetval;
133 393
    }
394
// ---------------------------------------------------------------------
134 395
/**
135 396
 * define another prefix then default in Wbdatabase
136 397
 * @param string $sTablePrefix
......
150 411
        }
151 412
        return $bRetval;
152 413
    }
414
// ---------------------------------------------------------------------
153 415
/**
154
 * Start and execute the import
155
 * @param string $sAction
156
 * @return boolean
157
 */
158
    public function doImport($sAction)
159
    {
160
        if (! $this->sStructFile) {
161
        // no file found so ignore import method
162
            return true;
163
        }
164
        if (! is_readable($this->sStructFile)) {
165
        // file fond but it's not readable
166
            $this->aErrMsg[] = 'unable to read file ['.$this->sStructFile.']';
167
            return false;
168
        }
169
        $sAction = strtolower(
170
            preg_replace(
171
                '/^.*?('.implode('|', $this->aActions).')(\.php)?$/i',
172
                '$1',
173
                $sAction
174
            )
175
        );
176
        if ($this->importSql(($sAction == '' ? 'install' : $sAction))) {
177
            return true;
178
        } else {
179
            $this->aErrMsg[] = $this->oDb->getError();
180
            return false;
181
        }
182
    }
183
/**
184 416
 * return errorstrings, concatet by LF
185 417
 * @return string
186 418
 */
187 419
    public function getError()
188 420
    {
189
        return implode(PHP_EOL, $this->aErrMsg);
421
        return implode("\n", $this->aErrMsg);
190 422
    }
191
/**
192
 * Import an SQl-Dumpfile witch can include unlimited additional placeholders for values
193
 * @param string $sAction 'install' || 'uninstall' || 'upgrade'
194
 * @return bool  false on error
195
 */
196
    protected function importSql($sAction = 'install')
197
    {
198
        $retval = true;
199
        $this->error = '';
200
        // sanitize arguments
201
        $aTmp = preg_split('/_/', $this->sCollation, null, PREG_SPLIT_NO_EMPTY);
202
        $sCharset = $aTmp[0];
203
        // get from addReplacements
204
        $aSearch  = $this->aReplacements['key'];
205
        /* ***  ATTENTION:: Do Not Change The Order Of Search-Replace Statements !! *** */
206
        // define basic array of searches
207
        $aSearch[] = '/\{TABLE_PREFIX\}/';                                        /* step 0 */
208
        $aSearch[] = '/\{FIELD_CHARSET\}/';                                       /* step 1 */
209
        $aSearch[] = '/\{FIELD_COLLATION\}/';                                     /* step 2 */
210
        $aSearch[] = '/\{TABLE_ENGINE\}/';                                        /* step 3 */
211
        $aSearch[] = '/\{TABLE_ENGINE=([a-zA-Z_0-9]*)\}/';                        /* step 4 */
212
        $aSearch[] = '/\{CHARSET\}/';                                             /* step 5 */
213
        $aSearch[] = '/\{COLLATION\}/';                                           /* step 6 */
214
        // get from addReplacements
215
        $aReplace = $this->aReplacements['value'];
216
        // define basic array of replacements
217
        $aReplace[] = $this->sTablePrefix;                                        /* step 0 */
218
        $aReplace[] = ' CHARACTER SET {CHARSET}';                                 /* step 1 */
219
        $aReplace[] = ' COLLATE {COLLATION}';                                     /* step 2 */
220
        $aReplace[] = ' {TABLE_ENGINE='.$this->sEngine.'}';                       /* step 3 */
221
        $aReplace[] = ' ENGINE=$1 DEFAULT CHARSET={CHARSET} COLLATE={COLLATION}'; /* step 4 */
222
        $aReplace[] = $sCharset;                                                  /* step 5 */
223
        $aReplace[] = $this->sCollation;                                          /* step 6 */
224 423

  
225
        $sql = ''; // buffer for statements
226
        $aSql = file($this->sStructFile, FILE_SKIP_EMPTY_LINES|FILE_IGNORE_NEW_LINES);
227
        //  remove possible ByteOrderMark
228
        $aSql[0] = preg_replace('/^[\xAA-\xFF]{3}/', '', $aSql[0]);
229
    // iterate file line by line
230
        while (sizeof($aSql) > 0) {
231
            // remove trailing and leading whitespaces
232
            $sSqlLine = trim(array_shift($aSql));
233
            // skip line if it's a comment
234
            if (preg_match('/^--/', $sSqlLine)) { continue; }
235
            // attach line to buffer
236
            $sql = $sql.' '.$sSqlLine;
237
            // detect end of statement
238
            if ((substr($sql,-1,1) == ';')) {
239
                // replace placeholders in statement
240
                $sql = trim(preg_replace($aSearch, $aReplace, $sql));
241
                $sAvailSqlObjects = implode('|', $this->aAvailSqlObjects);
242
                switch ($sAction) {
243
                    case 'uninstall': // execute DROP - skip CREATE
244
                        if (preg_match('/^\s*CREATE ('.$sAvailSqlObjects.') /si', $sql)) {
245
                            $sql = ''; // clear buffer
246
                            continue 2; // read next statement
247
                        }
248
                        break;
249
                    case 'upgrade': // skip DROP; execute CREATE
250
                        if (preg_match('/^\s*DROP ('.$sAvailSqlObjects.') /si', $sql)) {
251
                            $sql = ''; // clear buffer
252
                            continue 2; // read next statement
253
                        }
254
                        break;
255
                    default: // install:  execute DROP; execute CREATE
256
                        break;
257
                }
258
                if (!$this->oDb->doQuery($sql)) {
259
                    $retval = false;
260
                    $this->aErrMsg[] = $this->oDb->getError();
261
                    unset($aSql);
262
                    break;
263
                }
264
                $sql = ''; // clear buffer
265
            }
266
        }
267
        return $retval;
268
    } // end of function importSql()
424
} // end of class SqlImport
425
// ---------------------------------------------------------------------
426
// ---------------------------------------------------------------------
269 427

  
270
}
428
class SqlImportException extends AppException {}
429

  

Also available in: Unified diff