├── .travis.yml ├── composer.json ├── phpunit.xml ├── readme.md ├── src ├── Console │ └── SqlCommand.php ├── SqlFormatter.php ├── SqlGeneratorServiceProvider.php ├── migrations │ └── 2016_12_14_085908_test_sql_generator_table.php └── sql_generator.php └── tests ├── SqlGeneratorTest.php ├── TestCase.php └── sql └── database.sql /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | - 7.0 6 | 7 | sudo: false 8 | 9 | cache: 10 | directories: 11 | - laravel 12 | 13 | services: mysql 14 | 15 | script: 16 | - vendor/bin/phpunit -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "froiden/sql-generator", 3 | "description": "convert Laravel migrations to raw SQL scripts", 4 | "authors": [ 5 | { 6 | "name": "Jayant Soni", 7 | "email": "jayant@froiden.com" 8 | } 9 | ], 10 | "autoload": { 11 | "psr-4": { 12 | "Froiden\\SqlGenerator\\": "src", 13 | "Froiden\\SqlGenerator\\Tests\\": "tests" 14 | } 15 | }, 16 | "require": { 17 | "laravel/framework": ">= 5.3" 18 | }, 19 | "license": "MIT" 20 | } 21 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests 15 | 16 | 17 | 18 | 19 | ./src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Packagist License](https://poser.pugx.org/froiden/sql-generator/license.png)]() 2 | [![Total Downloads](https://poser.pugx.org/froiden/sql-generator/d/total.png)](https://packagist.org/packages/froiden/sql-generator) 3 | 4 | 5 | # LARAVEL SQL GENERATOR 6 | Convert Laravel migrations to raw SQL scripts 7 | 8 | 9 | ## Usage 10 | 11 | ### Step 1: Install Through Composer 12 | 13 | ``` 14 | composer require "froiden/sql-generator:dev-master" 15 | ``` 16 | 17 | ### Step 2: Add the Service Provider 18 | Now add the following to the providers array in your config/app.php 19 | 20 | ```php 21 | \Froiden\SqlGenerator\SqlGeneratorServiceProvider::class, 22 | ``` 23 | ### Step 3: Now publish the vendor 24 | ```bash 25 | php artisan vendor:publish 26 | ``` 27 | 28 | 29 | ### Step 4: Run command 30 | Then you will need to run these commands in the terminal 31 | 32 | ```bash 33 | php artisan sql:generate 34 | ``` 35 | 36 | This Will Generate "database.sql" in 'database/sql' directory 37 | If you want change path directory go to 'config/sql_generator.php' change value 'defaultDirectory' 38 | -------------------------------------------------------------------------------- /src/Console/SqlCommand.php: -------------------------------------------------------------------------------- 1 | resolveConnection(null); 24 | $migrator->requireFiles($migrations = $migrator->getMigrationFiles(base_path().'/database/migrations')); 25 | // 26 | $sql = "-- convert Laravel migrations to raw SQL scripts --\n"; 27 | foreach($migrations as $migration) { 28 | // First we will resolve a "real" instance of the migration class from this 29 | // migration file name. Once we have the instances we can run the actual 30 | // command such as "up" or "down", or we can just simulate the action. 31 | $migration_name = $migrator->getMigrationName($migration); 32 | $migration = $migrator->resolve($migration_name); 33 | $name = ""; 34 | foreach($db->pretend(function() use ($migration) { $migration->up(); }) as $query){ 35 | if($name != $migration_name){ 36 | $name = $migration_name; 37 | $sql .="\n-- migration:".$name." --\n"; 38 | } 39 | if(substr( $query['query'], 0, 11 ) === "insert into"){ 40 | $sql .= "-- insert data -- \n"; 41 | foreach ($query['bindings'] as $item) { 42 | $query['query'] = str_replace_first("?", "'".$item."'" ,$query['query']); 43 | } 44 | } 45 | $query['query'] = SqlFormatter::format($query['query'],false); 46 | $sql .= $query['query'].";\n"; 47 | } 48 | } 49 | $dir = Config::get('sql_generator.defaultDirectory'); 50 | //Check directory exit or not 51 | if( is_dir($dir) === false ) 52 | { 53 | // Make directory in database folder 54 | mkdir($dir); 55 | } 56 | // Pull query in sql file 57 | File::put($dir.'/database.sql', $sql); 58 | $this->comment("Sql script create successfully"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/SqlFormatter.php: -------------------------------------------------------------------------------- 1 | ', '+', '-', '*', '/', '!', '^', '%', '|', '&', '#'); 95 | // For HTML syntax highlighting 96 | // Styles applied to different token types 97 | public static $quote_attributes = 'style="color: blue;"'; 98 | public static $backtick_quote_attributes = 'style="color: purple;"'; 99 | public static $reserved_attributes = 'style="font-weight:bold;"'; 100 | public static $boundary_attributes = ''; 101 | public static $number_attributes = 'style="color: green;"'; 102 | public static $word_attributes = 'style="color: #333;"'; 103 | public static $error_attributes = 'style="background-color: red;"'; 104 | public static $comment_attributes = 'style="color: #aaa;"'; 105 | public static $variable_attributes = 'style="color: orange;"'; 106 | public static $pre_attributes = 'style="color: black; background-color: white;"'; 107 | // Boolean - whether or not the current environment is the CLI 108 | // This affects the type of syntax highlighting 109 | // If not defined, it will be determined automatically 110 | public static $cli; 111 | // For CLI syntax highlighting 112 | public static $cli_quote = "\x1b[34;1m"; 113 | public static $cli_backtick_quote = "\x1b[35;1m"; 114 | public static $cli_reserved = "\x1b[37m"; 115 | public static $cli_boundary = ""; 116 | public static $cli_number = "\x1b[32;1m"; 117 | public static $cli_word = ""; 118 | public static $cli_error = "\x1b[31;1;7m"; 119 | public static $cli_comment = "\x1b[30;1m"; 120 | public static $cli_functions = "\x1b[37m"; 121 | public static $cli_variable = "\x1b[36;1m"; 122 | // The tab character to use when formatting SQL 123 | public static $tab = ' '; 124 | // This flag tells us if queries need to be enclosed in
 tags
125 |     public static $use_pre = true;
126 |     // This flag tells us if SqlFormatted has been initialized
127 |     protected static $init;
128 |     // Regular expressions for tokenizing
129 |     protected static $regex_boundaries;
130 |     protected static $regex_reserved;
131 |     protected static $regex_reserved_newline;
132 |     protected static $regex_reserved_toplevel;
133 |     protected static $regex_function;
134 |     // Cache variables
135 |     // Only tokens shorter than this size will be cached.  Somewhere between 10 and 20 seems to work well for most cases.
136 |     public static $max_cachekey_size = 15;
137 |     protected static $token_cache = array();
138 |     protected static $cache_hits = 0;
139 |     protected static $cache_misses = 0;
140 |     /**
141 |      * Get stats about the token cache
142 |      * @return Array An array containing the keys 'hits', 'misses', 'entries', and 'size' in bytes
143 |      */
144 |     public static function getCacheStats()
145 |     {
146 |         return array(
147 |             'hits'=>self::$cache_hits,
148 |             'misses'=>self::$cache_misses,
149 |             'entries'=>count(self::$token_cache),
150 |             'size'=>strlen(serialize(self::$token_cache))
151 |         );
152 |     }
153 |     /**
154 |      * Stuff that only needs to be done once.  Builds regular expressions and sorts the reserved words.
155 |      */
156 |     protected static function init()
157 |     {
158 |         if (self::$init) return;
159 |         // Sort reserved word list from longest word to shortest, 3x faster than usort
160 |         $reservedMap = array_combine(self::$reserved, array_map('strlen', self::$reserved));
161 |         arsort($reservedMap);
162 |         self::$reserved = array_keys($reservedMap);
163 |         // Set up regular expressions
164 |         self::$regex_boundaries = '('.implode('|',array_map(array(__CLASS__, 'quote_regex'),self::$boundaries)).')';
165 |         self::$regex_reserved = '('.implode('|',array_map(array(__CLASS__, 'quote_regex'),self::$reserved)).')';
166 |         self::$regex_reserved_toplevel = str_replace(' ','\\s+','('.implode('|',array_map(array(__CLASS__, 'quote_regex'),self::$reserved_toplevel)).')');
167 |         self::$regex_reserved_newline = str_replace(' ','\\s+','('.implode('|',array_map(array(__CLASS__, 'quote_regex'),self::$reserved_newline)).')');
168 |         self::$regex_function = '('.implode('|',array_map(array(__CLASS__, 'quote_regex'),self::$functions)).')';
169 |         self::$init = true;
170 |     }
171 |     /**
172 |      * Return the next token and token type in a SQL string.
173 |      * Quoted strings, comments, reserved words, whitespace, and punctuation are all their own tokens.
174 |      *
175 |      * @param String $string   The SQL string
176 |      * @param array  $previous The result of the previous getNextToken() call
177 |      *
178 |      * @return Array An associative array containing the type and value of the token.
179 |      */
180 |     protected static function getNextToken($string, $previous = null)
181 |     {
182 |         // Whitespace
183 |         if (preg_match('/^\s+/',$string,$matches)) {
184 |             return array(
185 |                 self::TOKEN_VALUE => $matches[0],
186 |                 self::TOKEN_TYPE=>self::TOKEN_TYPE_WHITESPACE
187 |             );
188 |         }
189 |         // Comment
190 |         if ($string[0] === '#' || (isset($string[1])&&($string[0]==='-'&&$string[1]==='-') || ($string[0]==='/'&&$string[1]==='*'))) {
191 |             // Comment until end of line
192 |             if ($string[0] === '-' || $string[0] === '#') {
193 |                 $last = strpos($string, "\n");
194 |                 $type = self::TOKEN_TYPE_COMMENT;
195 |             } else { // Comment until closing comment tag
196 |                 $last = strpos($string, "*/", 2) + 2;
197 |                 $type = self::TOKEN_TYPE_BLOCK_COMMENT;
198 |             }
199 |             if ($last === false) {
200 |                 $last = strlen($string);
201 |             }
202 |             return array(
203 |                 self::TOKEN_VALUE => substr($string, 0, $last),
204 |                 self::TOKEN_TYPE  => $type
205 |             );
206 |         }
207 |         // Quoted String
208 |         if ($string[0]==='"' || $string[0]==='\'' || $string[0]==='`' || $string[0]==='[') {
209 |             $return = array(
210 |                 self::TOKEN_TYPE => (($string[0]==='`' || $string[0]==='[')? self::TOKEN_TYPE_BACKTICK_QUOTE : self::TOKEN_TYPE_QUOTE),
211 |                 self::TOKEN_VALUE => self::getQuotedString($string)
212 |             );
213 |             return $return;
214 |         }
215 |         // User-defined Variable
216 |         if (($string[0] === '@' || $string[0] === ':') && isset($string[1])) {
217 |             $ret = array(
218 |                 self::TOKEN_VALUE => null,
219 |                 self::TOKEN_TYPE => self::TOKEN_TYPE_VARIABLE
220 |             );
221 | 
222 |             // If the variable name is quoted
223 |             if ($string[1]==='"' || $string[1]==='\'' || $string[1]==='`') {
224 |                 $ret[self::TOKEN_VALUE] = $string[0].self::getQuotedString(substr($string,1));
225 |             }
226 |             // Non-quoted variable name
227 |             else {
228 |                 preg_match('/^('.$string[0].'[a-zA-Z0-9\._\$]+)/',$string,$matches);
229 |                 if ($matches) {
230 |                     $ret[self::TOKEN_VALUE] = $matches[1];
231 |                 }
232 |             }
233 | 
234 |             if($ret[self::TOKEN_VALUE] !== null) return $ret;
235 |         }
236 |         // Number (decimal, binary, or hex)
237 |         if (preg_match('/^([0-9]+(\.[0-9]+)?|0x[0-9a-fA-F]+|0b[01]+)($|\s|"\'`|'.self::$regex_boundaries.')/',$string,$matches)) {
238 |             return array(
239 |                 self::TOKEN_VALUE => $matches[1],
240 |                 self::TOKEN_TYPE=>self::TOKEN_TYPE_NUMBER
241 |             );
242 |         }
243 |         // Boundary Character (punctuation and symbols)
244 |         if (preg_match('/^('.self::$regex_boundaries.')/',$string,$matches)) {
245 |             return array(
246 |                 self::TOKEN_VALUE => $matches[1],
247 |                 self::TOKEN_TYPE  => self::TOKEN_TYPE_BOUNDARY
248 |             );
249 |         }
250 |         // A reserved word cannot be preceded by a '.'
251 |         // this makes it so in "mytable.from", "from" is not considered a reserved word
252 |         if (!$previous || !isset($previous[self::TOKEN_VALUE]) || $previous[self::TOKEN_VALUE] !== '.') {
253 |             $upper = strtoupper($string);
254 |             // Top Level Reserved Word
255 |             if (preg_match('/^('.self::$regex_reserved_toplevel.')($|\s|'.self::$regex_boundaries.')/', $upper,$matches)) {
256 |                 return array(
257 |                     self::TOKEN_TYPE=>self::TOKEN_TYPE_RESERVED_TOPLEVEL,
258 |                     self::TOKEN_VALUE=>substr($string,0,strlen($matches[1]))
259 |                 );
260 |             }
261 |             // Newline Reserved Word
262 |             if (preg_match('/^('.self::$regex_reserved_newline.')($|\s|'.self::$regex_boundaries.')/', $upper,$matches)) {
263 |                 return array(
264 |                     self::TOKEN_TYPE=>self::TOKEN_TYPE_RESERVED_NEWLINE,
265 |                     self::TOKEN_VALUE=>substr($string,0,strlen($matches[1]))
266 |                 );
267 |             }
268 |             // Other Reserved Word
269 |             if (preg_match('/^('.self::$regex_reserved.')($|\s|'.self::$regex_boundaries.')/', $upper,$matches)) {
270 |                 return array(
271 |                     self::TOKEN_TYPE=>self::TOKEN_TYPE_RESERVED,
272 |                     self::TOKEN_VALUE=>substr($string,0,strlen($matches[1]))
273 |                 );
274 |             }
275 |         }
276 |         // A function must be suceeded by '('
277 |         // this makes it so "count(" is considered a function, but "count" alone is not
278 |         $upper = strtoupper($string);
279 |         // function
280 |         if (preg_match('/^('.self::$regex_function.'[(]|\s|[)])/', $upper,$matches)) {
281 |             return array(
282 |                 self::TOKEN_TYPE=>self::TOKEN_TYPE_RESERVED,
283 |                 self::TOKEN_VALUE=>substr($string,0,strlen($matches[1])-1)
284 |             );
285 |         }
286 |         // Non reserved word
287 |         preg_match('/^(.*?)($|\s|["\'`]|'.self::$regex_boundaries.')/',$string,$matches);
288 |         return array(
289 |             self::TOKEN_VALUE => $matches[1],
290 |             self::TOKEN_TYPE  => self::TOKEN_TYPE_WORD
291 |         );
292 |     }
293 |     protected static function getQuotedString($string)
294 |     {
295 |         $ret = null;
296 | 
297 |         // This checks for the following patterns:
298 |         // 1. backtick quoted string using `` to escape
299 |         // 2. square bracket quoted string (SQL Server) using ]] to escape
300 |         // 3. double quoted string using "" or \" to escape
301 |         // 4. single quoted string using '' or \' to escape
302 |         if ( preg_match('/^(((`[^`]*($|`))+)|((\[[^\]]*($|\]))(\][^\]]*($|\]))*)|(("[^"\\\\]*(?:\\\\.[^"\\\\]*)*("|$))+)|((\'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*(\'|$))+))/s', $string, $matches)) {
303 |             $ret = $matches[1];
304 |         }
305 | 
306 |         return $ret;
307 |     }
308 |     /**
309 |      * Takes a SQL string and breaks it into tokens.
310 |      * Each token is an associative array with type and value.
311 |      *
312 |      * @param String $string The SQL string
313 |      *
314 |      * @return Array An array of tokens.
315 |      */
316 |     protected static function tokenize($string)
317 |     {
318 |         self::init();
319 |         $tokens = array();
320 |         // Used for debugging if there is an error while tokenizing the string
321 |         $original_length = strlen($string);
322 |         // Used to make sure the string keeps shrinking on each iteration
323 |         $old_string_len = strlen($string) + 1;
324 |         $token = null;
325 |         $current_length = strlen($string);
326 |         // Keep processing the string until it is empty
327 |         while ($current_length) {
328 |             // If the string stopped shrinking, there was a problem
329 |             if ($old_string_len <= $current_length) {
330 |                 $tokens[] = array(
331 |                     self::TOKEN_VALUE=>$string,
332 |                     self::TOKEN_TYPE=>self::TOKEN_TYPE_ERROR
333 |                 );
334 |                 return $tokens;
335 |             }
336 |             $old_string_len =  $current_length;
337 |             // Determine if we can use caching
338 |             if ($current_length >= self::$max_cachekey_size) {
339 |                 $cacheKey = substr($string,0,self::$max_cachekey_size);
340 |             } else {
341 |                 $cacheKey = false;
342 |             }
343 |             // See if the token is already cached
344 |             if ($cacheKey && isset(self::$token_cache[$cacheKey])) {
345 |                 // Retrieve from cache
346 |                 $token = self::$token_cache[$cacheKey];
347 |                 $token_length = strlen($token[self::TOKEN_VALUE]);
348 |                 self::$cache_hits++;
349 |             } else {
350 |                 // Get the next token and the token type
351 |                 $token = self::getNextToken($string, $token);
352 |                 $token_length = strlen($token[self::TOKEN_VALUE]);
353 |                 self::$cache_misses++;
354 |                 // If the token is shorter than the max length, store it in cache
355 |                 if ($cacheKey && $token_length < self::$max_cachekey_size) {
356 |                     self::$token_cache[$cacheKey] = $token;
357 |                 }
358 |             }
359 |             $tokens[] = $token;
360 |             // Advance the string
361 |             $string = substr($string, $token_length);
362 |             $current_length -= $token_length;
363 |         }
364 |         return $tokens;
365 |     }
366 |     /**
367 |      * Format the whitespace in a SQL string to make it easier to read.
368 |      *
369 |      * @param String  $string    The SQL string
370 |      * @param boolean $highlight If true, syntax highlighting will also be performed
371 |      *
372 |      * @return String The SQL string with HTML styles and formatting wrapped in a 
 tag
373 |      */
374 |     public static function format($string, $highlight=true)
375 |     {
376 |         // This variable will be populated with formatted html
377 |         $return = '';
378 |         // Use an actual tab while formatting and then switch out with self::$tab at the end
379 |         $tab = "\t";
380 |         $indent_level = 0;
381 |         $newline = false;
382 |         $inline_parentheses = false;
383 |         $increase_special_indent = false;
384 |         $increase_block_indent = false;
385 |         $indent_types = array();
386 |         $added_newline = false;
387 |         $inline_count = 0;
388 |         $inline_indented = false;
389 |         $clause_limit = false;
390 |         // Tokenize String
391 |         $original_tokens = self::tokenize($string);
392 |         // Remove existing whitespace
393 |         $tokens = array();
394 |         foreach ($original_tokens as $i=>$token) {
395 |             if ($token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_WHITESPACE) {
396 |                 $token['i'] = $i;
397 |                 $tokens[] = $token;
398 |             }
399 |         }
400 |         // Format token by token
401 |         foreach ($tokens as $i=>$token) {
402 |             // Get highlighted token if doing syntax highlighting
403 |             if ($highlight) {
404 |                 $highlighted = self::highlightToken($token);
405 |             } else { // If returning raw text
406 |                 $highlighted = $token[self::TOKEN_VALUE];
407 |             }
408 |             // If we are increasing the special indent level now
409 |             if ($increase_special_indent) {
410 |                 $indent_level++;
411 |                 $increase_special_indent = false;
412 |                 array_unshift($indent_types,'special');
413 |             }
414 |             // If we are increasing the block indent level now
415 |             if ($increase_block_indent) {
416 |                 $indent_level++;
417 |                 $increase_block_indent = false;
418 |                 array_unshift($indent_types,'block');
419 |             }
420 |             // If we need a new line before the token
421 |             if ($newline) {
422 |                 $return .= "\n" . str_repeat($tab, $indent_level);
423 |                 $newline = false;
424 |                 $added_newline = true;
425 |             } else {
426 |                 $added_newline = false;
427 |             }
428 |             // Display comments directly where they appear in the source
429 |             if ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_COMMENT || $token[self::TOKEN_TYPE] === self::TOKEN_TYPE_BLOCK_COMMENT) {
430 |                 if ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_BLOCK_COMMENT) {
431 |                     $indent = str_repeat($tab,$indent_level);
432 |                     $return .= "\n" . $indent;
433 |                     $highlighted = str_replace("\n","\n".$indent,$highlighted);
434 |                 }
435 |                 $return .= $highlighted;
436 |                 $newline = true;
437 |                 continue;
438 |             }
439 |             if ($inline_parentheses) {
440 |                 // End of inline parentheses
441 |                 if ($token[self::TOKEN_VALUE] === ')') {
442 |                     $return = rtrim($return,' ');
443 |                     if ($inline_indented) {
444 |                         array_shift($indent_types);
445 |                         $indent_level --;
446 |                         $return .= "\n" . str_repeat($tab, $indent_level);
447 |                     }
448 |                     $inline_parentheses = false;
449 |                     $return .= $highlighted . ' ';
450 |                     continue;
451 |                 }
452 |                 if ($token[self::TOKEN_VALUE] === ',') {
453 |                     if ($inline_count >= 30) {
454 |                         $inline_count = 0;
455 |                         $newline = true;
456 |                     }
457 |                 }
458 |                 $inline_count += strlen($token[self::TOKEN_VALUE]);
459 |             }
460 |             // Opening parentheses increase the block indent level and start a new line
461 |             if ($token[self::TOKEN_VALUE] === '(') {
462 |                 // First check if this should be an inline parentheses block
463 |                 // Examples are "NOW()", "COUNT(*)", "int(10)", key(`somecolumn`), DECIMAL(7,2)
464 |                 // Allow up to 3 non-whitespace tokens inside inline parentheses
465 |                 $length = 0;
466 |                 for ($j=1;$j<=250;$j++) {
467 |                     // Reached end of string
468 |                     if (!isset($tokens[$i+$j])) break;
469 |                     $next = $tokens[$i+$j];
470 |                     // Reached closing parentheses, able to inline it
471 |                     if ($next[self::TOKEN_VALUE] === ')') {
472 |                         $inline_parentheses = true;
473 |                         $inline_count = 0;
474 |                         $inline_indented = false;
475 |                         break;
476 |                     }
477 |                     // Reached an invalid token for inline parentheses
478 |                     if ($next[self::TOKEN_VALUE]===';' || $next[self::TOKEN_VALUE]==='(') {
479 |                         break;
480 |                     }
481 |                     // Reached an invalid token type for inline parentheses
482 |                     if ($next[self::TOKEN_TYPE]===self::TOKEN_TYPE_RESERVED_TOPLEVEL || $next[self::TOKEN_TYPE]===self::TOKEN_TYPE_RESERVED_NEWLINE || $next[self::TOKEN_TYPE]===self::TOKEN_TYPE_COMMENT || $next[self::TOKEN_TYPE]===self::TOKEN_TYPE_BLOCK_COMMENT) {
483 |                         break;
484 |                     }
485 |                     $length += strlen($next[self::TOKEN_VALUE]);
486 |                 }
487 |                 if ($inline_parentheses && $length > 30) {
488 |                     $increase_block_indent = true;
489 |                     $inline_indented = true;
490 |                     $newline = true;
491 |                 }
492 |                 // Take out the preceding space unless there was whitespace there in the original query
493 |                 if (isset($original_tokens[$token['i']-1]) && $original_tokens[$token['i']-1][self::TOKEN_TYPE] !== self::TOKEN_TYPE_WHITESPACE) {
494 |                     $return = rtrim($return,' ');
495 |                 }
496 |                 if (!$inline_parentheses) {
497 |                     $increase_block_indent = true;
498 |                     // Add a newline after the parentheses
499 |                     $newline = true;
500 |                 }
501 |             }
502 |             // Closing parentheses decrease the block indent level
503 |             elseif ($token[self::TOKEN_VALUE] === ')') {
504 |                 // Remove whitespace before the closing parentheses
505 |                 $return = rtrim($return,' ');
506 |                 $indent_level--;
507 |                 // Reset indent level
508 |                 while ($j=array_shift($indent_types)) {
509 |                     if ($j==='special') {
510 |                         $indent_level--;
511 |                     } else {
512 |                         break;
513 |                     }
514 |                 }
515 |                 if ($indent_level < 0) {
516 |                     // This is an error
517 |                     $indent_level = 0;
518 |                     if ($highlight) {
519 |                         $return .= "\n".self::highlightError($token[self::TOKEN_VALUE]);
520 |                         continue;
521 |                     }
522 |                 }
523 |                 // Add a newline before the closing parentheses (if not already added)
524 |                 if (!$added_newline) {
525 |                     $return .= "\n" . str_repeat($tab, $indent_level);
526 |                 }
527 |             }
528 |             // Top level reserved words start a new line and increase the special indent level
529 |             elseif ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_RESERVED_TOPLEVEL) {
530 |                 $increase_special_indent = true;
531 |                 // If the last indent type was 'special', decrease the special indent for this round
532 |                 reset($indent_types);
533 |                 if (current($indent_types)==='special') {
534 |                     $indent_level--;
535 |                     array_shift($indent_types);
536 |                 }
537 |                 // Add a newline after the top level reserved word
538 |                 $newline = true;
539 |                 // Add a newline before the top level reserved word (if not already added)
540 |                 if (!$added_newline) {
541 |                     $return .= "\n" . str_repeat($tab, $indent_level);
542 |                 }
543 |                 // If we already added a newline, redo the indentation since it may be different now
544 |                 else {
545 |                     $return = rtrim($return,$tab).str_repeat($tab, $indent_level);
546 |                 }
547 |                 // If the token may have extra whitespace
548 |                 if (strpos($token[self::TOKEN_VALUE],' ')!==false || strpos($token[self::TOKEN_VALUE],"\n")!==false || strpos($token[self::TOKEN_VALUE],"\t")!==false) {
549 |                     $highlighted = preg_replace('/\s+/',' ',$highlighted);
550 |                 }
551 |                 //if SQL 'LIMIT' clause, start variable to reset newline
552 |                 if ($token[self::TOKEN_VALUE] === 'LIMIT' && !$inline_parentheses) {
553 |                     $clause_limit = true;
554 |                 }
555 |             }
556 |             // Checks if we are out of the limit clause
557 |             elseif ($clause_limit && $token[self::TOKEN_VALUE] !== "," && $token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_NUMBER && $token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_WHITESPACE) {
558 |                 $clause_limit = false;
559 |             }
560 |             // Commas start a new line (unless within inline parentheses or SQL 'LIMIT' clause)
561 |             elseif ($token[self::TOKEN_VALUE] === ',' && !$inline_parentheses) {
562 |                 //If the previous TOKEN_VALUE is 'LIMIT', resets new line
563 |                 if ($clause_limit === true) {
564 |                     $newline = false;
565 |                     $clause_limit = false;
566 |                 }
567 |                 // All other cases of commas
568 |                 else {
569 |                     $newline = true;
570 |                 }
571 |             }
572 |             // Newline reserved words start a new line
573 |             elseif ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_RESERVED_NEWLINE) {
574 |                 // Add a newline before the reserved word (if not already added)
575 |                 if (!$added_newline) {
576 |                     $return .= "\n" . str_repeat($tab, $indent_level);
577 |                 }
578 |                 // If the token may have extra whitespace
579 |                 if (strpos($token[self::TOKEN_VALUE],' ')!==false || strpos($token[self::TOKEN_VALUE],"\n")!==false || strpos($token[self::TOKEN_VALUE],"\t")!==false) {
580 |                     $highlighted = preg_replace('/\s+/',' ',$highlighted);
581 |                 }
582 |             }
583 |             // Multiple boundary characters in a row should not have spaces between them (not including parentheses)
584 |             elseif ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_BOUNDARY) {
585 |                 if (isset($tokens[$i-1]) && $tokens[$i-1][self::TOKEN_TYPE] === self::TOKEN_TYPE_BOUNDARY) {
586 |                     if (isset($original_tokens[$token['i']-1]) && $original_tokens[$token['i']-1][self::TOKEN_TYPE] !== self::TOKEN_TYPE_WHITESPACE) {
587 |                         $return = rtrim($return,' ');
588 |                     }
589 |                 }
590 |             }
591 |             // If the token shouldn't have a space before it
592 |             if ($token[self::TOKEN_VALUE] === '.' || $token[self::TOKEN_VALUE] === ',' || $token[self::TOKEN_VALUE] === ';') {
593 |                 $return = rtrim($return, ' ');
594 |             }
595 |             $return .= $highlighted.' ';
596 |             // If the token shouldn't have a space after it
597 |             if ($token[self::TOKEN_VALUE] === '(' || $token[self::TOKEN_VALUE] === '.') {
598 |                 $return = rtrim($return,' ');
599 |             }
600 | 
601 |             // If this is the "-" of a negative number, it shouldn't have a space after it
602 |             if($token[self::TOKEN_VALUE] === '-' && isset($tokens[$i+1]) && $tokens[$i+1][self::TOKEN_TYPE] === self::TOKEN_TYPE_NUMBER && isset($tokens[$i-1])) {
603 |                 $prev = $tokens[$i-1][self::TOKEN_TYPE];
604 |                 if($prev !== self::TOKEN_TYPE_QUOTE && $prev !== self::TOKEN_TYPE_BACKTICK_QUOTE && $prev !== self::TOKEN_TYPE_WORD && $prev !== self::TOKEN_TYPE_NUMBER) {
605 |                     $return = rtrim($return,' ');
606 |                 }
607 |             }
608 |         }
609 |         // If there are unmatched parentheses
610 |         if ($highlight && array_search('block',$indent_types) !== false) {
611 |             $return .= "\n".self::highlightError("WARNING: unclosed parentheses or section");
612 |         }
613 |         // Replace tab characters with the configuration tab character
614 |         $return = trim(str_replace("\t",self::$tab,$return));
615 |         if ($highlight) {
616 |             $return = self::output($return);
617 |         }
618 |         return $return;
619 |     }
620 |     /**
621 |      * Add syntax highlighting to a SQL string
622 |      *
623 |      * @param String $string The SQL string
624 |      *
625 |      * @return String The SQL string with HTML styles applied
626 |      */
627 |     public static function highlight($string)
628 |     {
629 |         $tokens = self::tokenize($string);
630 |         $return = '';
631 |         foreach ($tokens as $token) {
632 |             $return .= self::highlightToken($token);
633 |         }
634 |         return self::output($return);
635 |     }
636 |     /**
637 |      * Split a SQL string into multiple queries.
638 |      * Uses ";" as a query delimiter.
639 |      *
640 |      * @param String $string The SQL string
641 |      *
642 |      * @return Array An array of individual query strings without trailing semicolons
643 |      */
644 |     public static function splitQuery($string)
645 |     {
646 |         $queries = array();
647 |         $current_query = '';
648 |         $empty = true;
649 |         $tokens = self::tokenize($string);
650 |         foreach ($tokens as $token) {
651 |             // If this is a query separator
652 |             if ($token[self::TOKEN_VALUE] === ';') {
653 |                 if (!$empty) {
654 |                     $queries[] = $current_query.';';
655 |                 }
656 |                 $current_query = '';
657 |                 $empty = true;
658 |                 continue;
659 |             }
660 |             // If this is a non-empty character
661 |             if ($token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_WHITESPACE && $token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_COMMENT && $token[self::TOKEN_TYPE] !== self::TOKEN_TYPE_BLOCK_COMMENT) {
662 |                 $empty = false;
663 |             }
664 |             $current_query .= $token[self::TOKEN_VALUE];
665 |         }
666 |         if (!$empty) {
667 |             $queries[] = trim($current_query);
668 |         }
669 |         return $queries;
670 |     }
671 |     /**
672 |      * Remove all comments from a SQL string
673 |      *
674 |      * @param String $string The SQL string
675 |      *
676 |      * @return String The SQL string without comments
677 |      */
678 |     public static function removeComments($string)
679 |     {
680 |         $result = '';
681 |         $tokens = self::tokenize($string);
682 |         foreach ($tokens as $token) {
683 |             // Skip comment tokens
684 |             if ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_COMMENT || $token[self::TOKEN_TYPE] === self::TOKEN_TYPE_BLOCK_COMMENT) {
685 |                 continue;
686 |             }
687 |             $result .= $token[self::TOKEN_VALUE];
688 |         }
689 |         $result = self::format( $result,false);
690 |         return $result;
691 |     }
692 |     /**
693 |      * Compress a query by collapsing white space and removing comments
694 |      *
695 |      * @param String $string The SQL string
696 |      *
697 |      * @return String The SQL string without comments
698 |      */
699 |     public static function compress($string)
700 |     {
701 |         $result = '';
702 |         $tokens = self::tokenize($string);
703 |         $whitespace = true;
704 |         foreach ($tokens as $token) {
705 |             // Skip comment tokens
706 |             if ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_COMMENT || $token[self::TOKEN_TYPE] === self::TOKEN_TYPE_BLOCK_COMMENT) {
707 |                 continue;
708 |             }
709 |             // Remove extra whitespace in reserved words (e.g "OUTER     JOIN" becomes "OUTER JOIN")
710 |             elseif ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_RESERVED || $token[self::TOKEN_TYPE] === self::TOKEN_TYPE_RESERVED_NEWLINE || $token[self::TOKEN_TYPE] === self::TOKEN_TYPE_RESERVED_TOPLEVEL) {
711 |                 $token[self::TOKEN_VALUE] = preg_replace('/\s+/',' ',$token[self::TOKEN_VALUE]);
712 |             }
713 |             if ($token[self::TOKEN_TYPE] === self::TOKEN_TYPE_WHITESPACE) {
714 |                 // If the last token was whitespace, don't add another one
715 |                 if ($whitespace) {
716 |                     continue;
717 |                 } else {
718 |                     $whitespace = true;
719 |                     // Convert all whitespace to a single space
720 |                     $token[self::TOKEN_VALUE] = ' ';
721 |                 }
722 |             } else {
723 |                 $whitespace = false;
724 |             }
725 |             $result .= $token[self::TOKEN_VALUE];
726 |         }
727 |         return rtrim($result);
728 |     }
729 |     /**
730 |      * Highlights a token depending on its type.
731 |      *
732 |      * @param Array $token An associative array containing type and value.
733 |      *
734 |      * @return String HTML code of the highlighted token.
735 |      */
736 |     protected static function highlightToken($token)
737 |     {
738 |         $type = $token[self::TOKEN_TYPE];
739 |         if (self::is_cli()) {
740 |             $token = $token[self::TOKEN_VALUE];
741 |         } else {
742 |             if (defined('ENT_IGNORE')) {
743 |                 $token = htmlentities($token[self::TOKEN_VALUE],ENT_COMPAT | ENT_IGNORE ,'UTF-8');
744 |             } else {
745 |                 $token = htmlentities($token[self::TOKEN_VALUE],ENT_COMPAT,'UTF-8');
746 |             }
747 |         }
748 |         if ($type===self::TOKEN_TYPE_BOUNDARY) {
749 |             return self::highlightBoundary($token);
750 |         } elseif ($type===self::TOKEN_TYPE_WORD) {
751 |             return self::highlightWord($token);
752 |         } elseif ($type===self::TOKEN_TYPE_BACKTICK_QUOTE) {
753 |             return self::highlightBacktickQuote($token);
754 |         } elseif ($type===self::TOKEN_TYPE_QUOTE) {
755 |             return self::highlightQuote($token);
756 |         } elseif ($type===self::TOKEN_TYPE_RESERVED) {
757 |             return self::highlightReservedWord($token);
758 |         } elseif ($type===self::TOKEN_TYPE_RESERVED_TOPLEVEL) {
759 |             return self::highlightReservedWord($token);
760 |         } elseif ($type===self::TOKEN_TYPE_RESERVED_NEWLINE) {
761 |             return self::highlightReservedWord($token);
762 |         } elseif ($type===self::TOKEN_TYPE_NUMBER) {
763 |             return self::highlightNumber($token);
764 |         } elseif ($type===self::TOKEN_TYPE_VARIABLE) {
765 |             return self::highlightVariable($token);
766 |         } elseif ($type===self::TOKEN_TYPE_COMMENT || $type===self::TOKEN_TYPE_BLOCK_COMMENT) {
767 |             return self::highlightComment($token);
768 |         }
769 |         return $token;
770 |     }
771 |     /**
772 |      * Highlights a quoted string
773 |      *
774 |      * @param String $value The token's value
775 |      *
776 |      * @return String HTML code of the highlighted token.
777 |      */
778 |     protected static function highlightQuote($value)
779 |     {
780 |         if (self::is_cli()) {
781 |             return self::$cli_quote . $value . "\x1b[0m";
782 |         } else {
783 |             return '' . $value . '';
784 |         }
785 |     }
786 |     /**
787 |      * Highlights a backtick quoted string
788 |      *
789 |      * @param String $value The token's value
790 |      *
791 |      * @return String HTML code of the highlighted token.
792 |      */
793 |     protected static function highlightBacktickQuote($value)
794 |     {
795 |         if (self::is_cli()) {
796 |             return self::$cli_backtick_quote . $value . "\x1b[0m";
797 |         } else {
798 |             return '' . $value . '';
799 |         }
800 |     }
801 |     /**
802 |      * Highlights a reserved word
803 |      *
804 |      * @param String $value The token's value
805 |      *
806 |      * @return String HTML code of the highlighted token.
807 |      */
808 |     protected static function highlightReservedWord($value)
809 |     {
810 |         if (self::is_cli()) {
811 |             return self::$cli_reserved . $value . "\x1b[0m";
812 |         } else {
813 |             return '' . $value . '';
814 |         }
815 |     }
816 |     /**
817 |      * Highlights a boundary token
818 |      *
819 |      * @param String $value The token's value
820 |      *
821 |      * @return String HTML code of the highlighted token.
822 |      */
823 |     protected static function highlightBoundary($value)
824 |     {
825 |         if ($value==='(' || $value===')') return $value;
826 |         if (self::is_cli()) {
827 |             return self::$cli_boundary . $value . "\x1b[0m";
828 |         } else {
829 |             return '' . $value . '';
830 |         }
831 |     }
832 |     /**
833 |      * Highlights a number
834 |      *
835 |      * @param String $value The token's value
836 |      *
837 |      * @return String HTML code of the highlighted token.
838 |      */
839 |     protected static function highlightNumber($value)
840 |     {
841 |         if (self::is_cli()) {
842 |             return self::$cli_number . $value . "\x1b[0m";
843 |         } else {
844 |             return '' . $value . '';
845 |         }
846 |     }
847 |     /**
848 |      * Highlights an error
849 |      *
850 |      * @param String $value The token's value
851 |      *
852 |      * @return String HTML code of the highlighted token.
853 |      */
854 |     protected static function highlightError($value)
855 |     {
856 |         if (self::is_cli()) {
857 |             return self::$cli_error . $value . "\x1b[0m";
858 |         } else {
859 |             return '' . $value . '';
860 |         }
861 |     }
862 |     /**
863 |      * Highlights a comment
864 |      *
865 |      * @param String $value The token's value
866 |      *
867 |      * @return String HTML code of the highlighted token.
868 |      */
869 |     protected static function highlightComment($value)
870 |     {
871 |         if (self::is_cli()) {
872 |             return self::$cli_comment . $value . "\x1b[0m";
873 |         } else {
874 |             return '' . $value . '';
875 |         }
876 |     }
877 |     /**
878 |      * Highlights a word token
879 |      *
880 |      * @param String $value The token's value
881 |      *
882 |      * @return String HTML code of the highlighted token.
883 |      */
884 |     protected static function highlightWord($value)
885 |     {
886 |         if (self::is_cli()) {
887 |             return self::$cli_word . $value . "\x1b[0m";
888 |         } else {
889 |             return '' . $value . '';
890 |         }
891 |     }
892 |     /**
893 |      * Highlights a variable token
894 |      *
895 |      * @param String $value The token's value
896 |      *
897 |      * @return String HTML code of the highlighted token.
898 |      */
899 |     protected static function highlightVariable($value)
900 |     {
901 |         if (self::is_cli()) {
902 |             return self::$cli_variable . $value . "\x1b[0m";
903 |         } else {
904 |             return '' . $value . '';
905 |         }
906 |     }
907 |     /**
908 |      * Helper function for building regular expressions for reserved words and boundary characters
909 |      *
910 |      * @param String $a The string to be quoted
911 |      *
912 |      * @return String The quoted string
913 |      */
914 |     private static function quote_regex($a)
915 |     {
916 |         return preg_quote($a,'/');
917 |     }
918 |     /**
919 |      * Helper function for building string output
920 |      *
921 |      * @param String $string The string to be quoted
922 |      *
923 |      * @return String The quoted string
924 |      */
925 |     private static function output($string)
926 |     {
927 |         if (self::is_cli()) {
928 |             return $string."\n";
929 |         } else {
930 |             $string=trim($string);
931 |             if (!self::$use_pre) {
932 |                 return $string;
933 |             }
934 |             return '
' . $string . '
'; 935 | } 936 | } 937 | private static function is_cli() 938 | { 939 | if (isset(self::$cli)) return self::$cli; 940 | else return php_sapi_name() === 'cli'; 941 | } 942 | } -------------------------------------------------------------------------------- /src/SqlGeneratorServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 19 | __DIR__.'/sql_generator.php' => config_path("sql_generator.php"), 20 | ]); 21 | } 22 | 23 | /** 24 | * Register the application services. 25 | * 26 | * @return void 27 | */ 28 | public function register() 29 | { 30 | $this->registerCommands(); 31 | } 32 | 33 | /** 34 | * Register all of sql generator command. 35 | * 36 | * @return void 37 | */ 38 | protected function registerCommands() 39 | { 40 | $this->commands('command.generate.sql'); 41 | 42 | $this->registerInstallCommand(); 43 | } 44 | 45 | /** 46 | * @return void 47 | */ 48 | protected function registerInstallCommand() 49 | { 50 | $this->app->singleton('command.generate.sql', function($app) { 51 | 52 | return new SqlCommand(); 53 | }); 54 | } 55 | 56 | /** 57 | * @return void 58 | */ 59 | public function provides() 60 | { 61 | return [ 62 | 'command.generate.sql' 63 | ]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/migrations/2016_12_14_085908_test_sql_generator_table.php: -------------------------------------------------------------------------------- 1 | increments('test_id'); 18 | $table->string('test_name'); 19 | $table->string('test_email')->unique(); 20 | $table->enum('test_gender', ['male', 'female']); 21 | $table->string('test_password'); 22 | $table->rememberToken(); 23 | $table->timestamps(); 24 | }); 25 | // 26 | Schema::create('test_test_sql_blogs', function (Blueprint $table) { 27 | $table->increments('test_id'); 28 | $table->integer('test_userId')->unsigned(); 29 | $table->string('test_userName'); 30 | $table->string('test_title')->unique(); 31 | $table->text('test_blogMsg'); 32 | $table->timestamps(); 33 | }); 34 | Schema::table('test_test_sql_blogs', function($table) { 35 | $table->foreign('test_userId')->references('test_id')->on('test_sql_generator_')->onDelete('cascade'); 36 | }); 37 | 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | * 43 | * @return void 44 | */ 45 | public function down() 46 | { 47 | // 48 | Schema::drop('test_sql_generator'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/sql_generator.php: -------------------------------------------------------------------------------- 1 | database_path('sql'), 9 | 10 | ]; -------------------------------------------------------------------------------- /tests/SqlGeneratorTest.php: -------------------------------------------------------------------------------- 1 | copyMigration(); 14 | $this->artisan('sql:generate'); 15 | $this->deleteMigration(); 16 | 17 | $this->assertTrue(true); 18 | $this->assertDirectoryExists($this->directory); 19 | $this->assertFileExists($this->directory.'/database.sql'); 20 | } 21 | 22 | public function testSqlExitOrNot() 23 | { 24 | $content = file_get_contents($this->directory.'/database.sql'); 25 | $queries = \Froiden\SqlGenerator\SqlFormatter::splitQuery($content); 26 | foreach ($queries as $query) { 27 | 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | __DIR__.'/sql']); 26 | $this->directory = __DIR__."/sql"; 27 | 28 | } 29 | 30 | public function copyMigration() 31 | { 32 | $fileName = '/2016_12_14_085908_test_sql_generator_table.php'; 33 | $source = 'src/migrations'.$fileName; 34 | $destination = database_path('migrations'.$fileName); 35 | 36 | if(file_exists($destination)){ 37 | unlink($destination); 38 | } 39 | copy($source,$destination); 40 | } 41 | 42 | public function deleteMigration(){ 43 | $fileName = '/2016_12_14_085908_test_sql_generator_table.php'; 44 | $destination = database_path('migrations'.$fileName); 45 | 46 | if(file_exists($destination)){ 47 | unlink($destination); 48 | } 49 | } 50 | 51 | 52 | /** 53 | * Creates the application. 54 | * 55 | * @return \Illuminate\Foundation\Application 56 | */ 57 | public function createApplication() 58 | { 59 | $app = require __DIR__.'/../../../../bootstrap/app.php'; 60 | $app->make(\Illuminate\Contracts\Console\Kernel::class)->bootstrap(); 61 | return $app; 62 | } 63 | 64 | 65 | 66 | } -------------------------------------------------------------------------------- /tests/sql/database.sql: -------------------------------------------------------------------------------- 1 | -- convert Laravel migrations to raw SQL scripts -- 2 | 3 | -- migration:2016_12_14_085908_test_sql_generator_table -- 4 | create table `test_test_sql_generator_users` ( 5 | `test_id` int unsigned not null auto_increment primary key, 6 | `test_name` varchar(255) not null, 7 | `test_email` varchar(255) not null, 8 | `test_gender` enum('male', 'female') not null, 9 | `test_password` varchar(255) not null, 10 | `remember_token` varchar(100) null, 11 | `created_at` timestamp null, 12 | `updated_at` timestamp null 13 | ) default character set utf8 collate utf8_unicode_ci; 14 | alter table 15 | `test_test_sql_generator_users` 16 | add 17 | unique `test_test_sql_generator_users_test_email_unique`(`test_email`); 18 | create table `test_test_sql_blogs` ( 19 | `test_id` int unsigned not null auto_increment primary key, 20 | `test_userId` int unsigned not null, 21 | `test_userName` varchar(255) not null, 22 | `test_title` varchar(255) not null, 23 | `test_blogMsg` text not null, 24 | `created_at` timestamp null, 25 | `updated_at` timestamp null 26 | ) default character set utf8 collate utf8_unicode_ci; 27 | alter table 28 | `test_test_sql_blogs` 29 | add 30 | unique `test_test_sql_blogs_test_title_unique`(`test_title`); 31 | alter table 32 | `test_test_sql_blogs` 33 | add 34 | constraint `test_test_sql_blogs_test_userid_foreign` foreign key (`test_userId`) references `test_sql_generator_` (`test_id`) on delete cascade; 35 | --------------------------------------------------------------------------------