Skip to content

Commit 1780fab

Browse files
committed
move to Kodus domain
1 parent 61cee30 commit 1780fab

6 files changed

Lines changed: 109 additions & 34 deletions

File tree

.travis.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
language: php
2+
3+
php:
4+
- 7.0
5+
- 7.1
6+
7+
before_script:
8+
- 'composer install --dev --prefer-source'
9+
10+
script: php test/test.php

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# `kodus/sql-split`
2+
3+
A simple parser to split SQL (and/or DDL) files into individual SQL queries and strip comments.
4+
5+
[![PHP Version](https://img.shields.io/badge/php-7.0%2B-blue.svg)](https://packagist.org/packages/kodus/sql-split)
6+
[![Build Status](https://travis-ci.org/kodus/sql-split.svg?branch=master)](https://travis-ci.org/kodus/sql-split)
7+
8+
### Install via Composer
9+
10+
composer require kodus/sql-split
11+
12+
### Features
13+
14+
I designed this for use with PDO and MySQL/PostgreSQL statements.
15+
16+
It uses a very simple recursive descent parser to minimally tokenize valid SQL - this approach ensures there
17+
is no ambiguity between quoted strings, keywords, comments, etc. but makes no attempt to validate SQL command
18+
structure or validity of the extracted statements.
19+
20+
It supports the following SQL/DDL features:
21+
22+
* SQL and DDL Queries
23+
* Stored procedures, functions, views, triggers, etc.
24+
* PostgreSQL dollar-tags (`$$` and `$mytag$` delimiters)
25+
* The MySQL `DELIMITER` command
26+
27+
## Usage
28+
29+
Just this:
30+
31+
```php
32+
$statements = Splitter::split(file_get_contents(...));
33+
```
34+
35+
This will split to individual SQL statements and (by default) strip comments.
36+
37+
Then just loop over your `$statements` and run them via `PDO`.
38+
39+
That's all.

composer.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,23 @@
11
{
2+
"name": "kodus/sql-split",
3+
"type": "library",
4+
"description": "A simple facility to split SQL files into individual queries - supports MySQL and PostgreSQL",
5+
"license": "MIT",
6+
"authors": [
7+
{
8+
"name": "Rasmus Schultz",
9+
"email": "rasmus@mindplay.dk"
10+
}
11+
],
212
"require": {
3-
"mindplay/testies": "^0.3.1"
13+
"php": "^7.0"
14+
},
15+
"require-dev": {
16+
"mindplay/testies": "^0.3.0"
417
},
518
"autoload": {
619
"psr-4": {
7-
"mindplay\\sql_parser\\": "src/"
20+
"Kodus\\SQL\\": "src/"
821
}
922
}
1023
}
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
<?php
22

3-
namespace mindplay\sql_parser;
3+
namespace Kodus\SQL;
44

5-
abstract class SQLSplitter
5+
/**
6+
* This class implements comment-stripping and kerjiggers the SQL tokens back into statements.
7+
*/
8+
abstract class Splitter
69
{
710
/**
811
* @param string $sql
@@ -12,7 +15,7 @@ abstract class SQLSplitter
1215
*/
1316
public static function split(string $sql, bool $strip_comments = true)
1417
{
15-
$tokens = SQLTokenizer::tokenize($sql);
18+
$tokens = Tokenizer::tokenize($sql);
1619

1720
if ($strip_comments) {
1821
$tokens = self::stripComments($tokens);
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
<?php
22

3-
namespace mindplay\sql_parser;
3+
namespace Kodus\SQL;
44

55
use RuntimeException;
66

7-
class SQLTokenizer
7+
/**
8+
* This class implements a simple recursive descent parser for minimal tokenization
9+
* of files containing one or more MySQL or PostgreSQL statements.
10+
*
11+
* It's used internally by {@see Splitter} which provides the main point of entry -
12+
* if you want to use the tokenizer for something else, have a look at the test-suite
13+
* which contains a specification demonstrating the very simple token output format.
14+
*
15+
* @see Splitter::split()
16+
*/
17+
class Tokenizer
818
{
919
/**
1020
* @var int

test/test.php

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,60 @@
11
<?php
22

3-
use mindplay\sql_parser\SQLSplitter;
4-
use mindplay\sql_parser\SQLTokenizer;
3+
use Kodus\SQL\Splitter;
4+
use Kodus\SQL\Tokenizer;
55

66
require dirname(__DIR__) . '/vendor/autoload.php';
77

88
test(
99
'single statement',
1010
function () {
11-
eq(SQLTokenizer::tokenize("SELECT 1"), [["SELECT", " ", "1"]], "unterminated");
12-
eq(SQLTokenizer::tokenize("SELECT 1"), [["SELECT", " ", "1"]], "terminated");
11+
eq(Tokenizer::tokenize("SELECT 1"), [["SELECT", " ", "1"]], "unterminated");
12+
eq(Tokenizer::tokenize("SELECT 1"), [["SELECT", " ", "1"]], "terminated");
1313
}
1414
);
1515

1616
test(
1717
'multiple statements',
1818
function () {
19-
eq(SQLTokenizer::tokenize("SELECT 1; SELECT 2"), [["SELECT", " ", "1"], ["SELECT", " ", "2"]], "separated statements");
20-
eq(SQLTokenizer::tokenize("SELECT 1; SELECT 2;"), [["SELECT", " ", "1"], ["SELECT", " ", "2"]], "terminated statements");
19+
eq(Tokenizer::tokenize("SELECT 1; SELECT 2"), [["SELECT", " ", "1"], ["SELECT", " ", "2"]], "separated statements");
20+
eq(Tokenizer::tokenize("SELECT 1; SELECT 2;"), [["SELECT", " ", "1"], ["SELECT", " ", "2"]], "terminated statements");
2121
}
2222
);
2323

2424
test(
2525
'various tokens',
2626
function () {
27-
eq(SQLTokenizer::tokenize("SELECT * FROM bar"), [["SELECT", " ", "*", " ", "FROM", " ", "bar"]]);
28-
eq(SQLTokenizer::tokenize("SELECT `foo` FROM `bar`"), [["SELECT", " ", "`foo`", " ", "FROM", " ", "`bar`"]]);
29-
eq(SQLTokenizer::tokenize("SELECT 'some\"quotes'"), [["SELECT", " ", "'some\"quotes'"]]);
30-
eq(SQLTokenizer::tokenize('SELECT "more quotes" AS `bar`'), [["SELECT", " ", '"more quotes"', " ", "AS", " ", "`bar`"]]);
31-
eq(SQLTokenizer::tokenize("SELECT :foo, :bat AS bar"), [["SELECT", " ", ":foo", ",", " ", ":bat", " ", "AS", " ", "bar"]]);
32-
eq(SQLTokenizer::tokenize("SELECT a*b+c-d FROM tbl"), [["SELECT", " ", "a", "*", "b", "+", "c", "-", "d", " ", "FROM", " ", "tbl"]]);
33-
eq(SQLTokenizer::tokenize("UPDATE foo (a, b) SET (1, 2)"), [["UPDATE", " ", "foo", " ", ["(", "a", ",", " ", "b", ")"], " ", "SET", " ", ["(", "1", ",", " ", "2", ")"]]]);
34-
eq(SQLTokenizer::tokenize("SELECT (({[1,2]}))"), [["SELECT", " ", ["(", ["(", ["{", ["[", "1", ",", "2", "]"], "}"], ")"], ")"]]], "nested brackets/braces");
35-
eq(SQLTokenizer::tokenize("SELECT ( [ 1 ] )"), [["SELECT", " ", ["(", " ", ["[", " ", "1", " ", "]"], " ", ")"]]], "nested brackets/braces");
36-
eq(SQLTokenizer::tokenize('CREATE FUNCTION foo AS $$RETURN $1$$;'), [["CREATE", " ", "FUNCTION", " ", "foo", " ", "AS", " ", '$$RETURN $1$$']], "stored procedure");
37-
eq(SQLTokenizer::tokenize('SELECT $$FOO$$; SELECT $$BAR$$'), [["SELECT", " ", '$$FOO$$'], ["SELECT", " ", '$$BAR$$']], "dollar-quoted strings");
38-
eq(SQLTokenizer::tokenize("SELECT 'foo\\'\\\\'"), [["SELECT", " ", "'foo\\'\\\\'"]]);
27+
eq(Tokenizer::tokenize("SELECT * FROM bar"), [["SELECT", " ", "*", " ", "FROM", " ", "bar"]]);
28+
eq(Tokenizer::tokenize("SELECT `foo` FROM `bar`"), [["SELECT", " ", "`foo`", " ", "FROM", " ", "`bar`"]]);
29+
eq(Tokenizer::tokenize("SELECT 'some\"quotes'"), [["SELECT", " ", "'some\"quotes'"]]);
30+
eq(Tokenizer::tokenize('SELECT "more quotes" AS `bar`'), [["SELECT", " ", '"more quotes"', " ", "AS", " ", "`bar`"]]);
31+
eq(Tokenizer::tokenize("SELECT :foo, :bat AS bar"), [["SELECT", " ", ":foo", ",", " ", ":bat", " ", "AS", " ", "bar"]]);
32+
eq(Tokenizer::tokenize("SELECT a*b+c-d FROM tbl"), [["SELECT", " ", "a", "*", "b", "+", "c", "-", "d", " ", "FROM", " ", "tbl"]]);
33+
eq(Tokenizer::tokenize("UPDATE foo (a, b) SET (1, 2)"), [["UPDATE", " ", "foo", " ", ["(", "a", ",", " ", "b", ")"], " ", "SET", " ", ["(", "1", ",", " ", "2", ")"]]]);
34+
eq(Tokenizer::tokenize("SELECT (({[1,2]}))"), [["SELECT", " ", ["(", ["(", ["{", ["[", "1", ",", "2", "]"], "}"], ")"], ")"]]], "nested brackets/braces");
35+
eq(Tokenizer::tokenize("SELECT ( [ 1 ] )"), [["SELECT", " ", ["(", " ", ["[", " ", "1", " ", "]"], " ", ")"]]], "nested brackets/braces");
36+
eq(Tokenizer::tokenize('CREATE FUNCTION foo AS $$RETURN $1$$;'), [["CREATE", " ", "FUNCTION", " ", "foo", " ", "AS", " ", '$$RETURN $1$$']], "stored procedure");
37+
eq(Tokenizer::tokenize('SELECT $$FOO$$; SELECT $$BAR$$'), [["SELECT", " ", '$$FOO$$'], ["SELECT", " ", '$$BAR$$']], "dollar-quoted strings");
38+
eq(Tokenizer::tokenize("SELECT 'foo\\'\\\\'"), [["SELECT", " ", "'foo\\'\\\\'"]]);
3939
}
4040
);
4141

4242
test(
4343
'comments',
4444
function () {
45-
eq(SQLTokenizer::tokenize("-- one\nSELECT -- two\n1; -- three\n-- four"), [["-- one", "\n", "SELECT", " ", "-- two", "\n", "1"], ["-- three", "\n", "-- four"]]);
46-
eq(SQLTokenizer::tokenize("/* one\ntwo */\nSELECT 1;/* three\nfour */\nSELECT 2;"), [["/* one\ntwo */", "\n", "SELECT", " ", "1"], ["/* three\nfour */", "\n", "SELECT", " ", "2"]]);
45+
eq(Tokenizer::tokenize("-- one\nSELECT -- two\n1; -- three\n-- four"), [["-- one", "\n", "SELECT", " ", "-- two", "\n", "1"], ["-- three", "\n", "-- four"]]);
46+
eq(Tokenizer::tokenize("/* one\ntwo */\nSELECT 1;/* three\nfour */\nSELECT 2;"), [["/* one\ntwo */", "\n", "SELECT", " ", "1"], ["/* three\nfour */", "\n", "SELECT", " ", "2"]]);
4747
}
4848
);
4949

5050
test(
5151
'split statements',
5252
function () {
53-
eq(SQLSplitter::split("SELECT 1; SELECT 2;"), ["SELECT 1", "SELECT 2"]);
54-
eq(SQLSplitter::split("-- one\nSELECT -- two\n1; -- three\n-- four"), ["SELECT \n1"]);
55-
eq(SQLSplitter::split("-- one\nSELECT -- two\n1; -- three\n-- four", false), ["-- one\nSELECT -- two\n1", "-- three\n-- four"]);
56-
eq(SQLSplitter::split("/* one\ntwo */\nSELECT 1;/* three\nfour */\nSELECT 2;"), ["SELECT 1", "SELECT 2"]);
57-
eq(SQLSplitter::split("/* one\ntwo */\nSELECT 1;\n/* three\nfour */\nSELECT 2;", false), ["/* one\ntwo */\nSELECT 1", "/* three\nfour */\nSELECT 2"]);
53+
eq(Splitter::split("SELECT 1; SELECT 2;"), ["SELECT 1", "SELECT 2"]);
54+
eq(Splitter::split("-- one\nSELECT -- two\n1; -- three\n-- four"), ["SELECT \n1"]);
55+
eq(Splitter::split("-- one\nSELECT -- two\n1; -- three\n-- four", false), ["-- one\nSELECT -- two\n1", "-- three\n-- four"]);
56+
eq(Splitter::split("/* one\ntwo */\nSELECT 1;/* three\nfour */\nSELECT 2;"), ["SELECT 1", "SELECT 2"]);
57+
eq(Splitter::split("/* one\ntwo */\nSELECT 1;\n/* three\nfour */\nSELECT 2;", false), ["/* one\ntwo */\nSELECT 1", "/* three\nfour */\nSELECT 2"]);
5858
}
5959
);
6060

@@ -212,7 +212,7 @@ function (string $sql) {
212212

213213
$sql = str_replace("-----", "\n", $sql_template);
214214

215-
foreach (SQLSplitter::split($sql, false) as $index => $statement) {
215+
foreach (Splitter::split($sql, false) as $index => $statement) {
216216
eq($statement, $expected_statements[$index]);
217217
}
218218
});
@@ -247,7 +247,7 @@ function (string $sql) {
247247
SQL;
248248

249249
test("mysql use-case", function () use ($mysql_script, $mysql_statements) {
250-
eq(SQLSplitter::split($mysql_script), array_map("trim", explode("-----", $mysql_statements)));
250+
eq(Splitter::split($mysql_script), array_map("trim", explode("-----", $mysql_statements)));
251251
});
252252

253253
exit(run());

0 commit comments

Comments
 (0)