Skip to content

Commit

Permalink
PostgreSql statement parser implemented!
Browse files Browse the repository at this point in the history
  • Loading branch information
avraampiperidis committed Apr 2, 2020
1 parent 54b3b43 commit 78e1972
Show file tree
Hide file tree
Showing 11 changed files with 849 additions and 109 deletions.
18 changes: 12 additions & 6 deletions SqlStatementParser.Tests/PostgreSqlProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class PostgreSqlProvider
{
public static IEnumerable postgreSqlstatementProvider()
{
/* yield return new TestCaseData(@"
yield return new TestCaseData(@"
SELECT * FROM TABLE1;
DO $$
<<first_block>>
Expand All @@ -22,17 +22,23 @@ public static IEnumerable postgreSqlstatementProvider()
END first_block $$", 2);
yield return new TestCaseData(@"
SELECT * FROM TABLE1;
DO $$
DO $$
<<first_block>>
DECLARE
counter integer := 0;
BEGIN
counter := counter + 1;
RAISE NOTICE 'The current value of counter is %', counter;
END first_block $$ select 1;", 3);
yield return new TestCaseData(@"select 1;$$ my block of code; $$", 2);
yield return new TestCaseData(@"$$ my block of code; $$;select 1", 2);*/
yield return new TestCaseData(@"$f$ c; $f$", 1);
END first_block ;$$Language sql;
select 2;", 3);
yield return new TestCaseData(@"select 1;$$ my block of code; $$;select 2", 3);
yield return new TestCaseData(@"$$ my block of code; $$ select 1;", 1);
yield return new TestCaseData(@"DO BEGIN $func$ c; $func$ Language sql", 1);
yield return new TestCaseData(@"$$ c; $$", 1);
yield return new TestCaseData(@"commit$$ commit; $$", 1);
yield return new TestCaseData(@"commit$func$ $inner$ select 1;$inner$; $func$", 1);
yield return new TestCaseData(@"DO $block$ $ sad $$$ s$bloccc$ #$434// #$`` 'CODE' $block$commit;", 1);
yield return new TestCaseData(@"DO $block$ $ sad $$$ s$bloccc$ #$434// #$`` 'CODE' $block$;commit;", 2);
}
}
}
20 changes: 13 additions & 7 deletions SqlStatementParser.Tests/TestPostgreSqlStatementParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@ public TestPostgreSqlStatementParser(T t)
this.dbType = (DbType)(object)t;
}

/*[Test, TestCaseSource(typeof(SqlProvider), "statementProvider")]
[Test, TestCaseSource(typeof(SqlProvider), "statementProvider")]
public void TestParser(string sql, int expectedStatements)
{
Assert.AreEqual(expectedStatements, new SqlStatementParserWrapper(sql, dbType).Parse().Count);
}*/
}

[Test, TestCaseSource(typeof(PostgreSqlProvider), "postgreSqlstatementProvider")]
public void TestMysqlParser(string sql, int expectedStatements)
public void TestPostgreSqlParser(string sql, int expectedStatements)
{
Assert.AreEqual(expectedStatements, new SqlStatementParserWrapper(sql, dbType).Parse().Count);
List<StatementRange> ranges = new SqlStatementParserWrapper(sql, dbType).Parse();
Assert.AreEqual(expectedStatements, ranges.Count);
List<string> list = SqlStatementParserWrapper.convert(sql, ranges);
foreach(string s in list)
{
Console.WriteLine("Query:"+s);
}
}
/*

[Test, TestCaseSource(typeof(SqlProvider), "statementProvider")]
public void TestConvert(string sql, int expectedStatements)
{
Expand All @@ -35,10 +41,10 @@ public void TestConvert(string sql, int expectedStatements)
}

[Test, TestCaseSource(typeof(PostgreSqlProvider), "postgreSqlstatementProvider")]
public void TestMySqlConvert(string sql, int expectedStatements)
public void TestPostgreSqlConvert(string sql, int expectedStatements)
{
SqlStatementParserWrapper parser = new SqlStatementParserWrapper(sql, dbType);
Assert.AreEqual(expectedStatements, SqlStatementParserWrapper.convert(parser.sql, parser.Parse()).Count);
}*/
}
}
}
228 changes: 228 additions & 0 deletions SqlStatementParser/Db2StatementParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace com.protectsoft.SqlStatementParser
{
public class Db2StatementParser : SqlStatementParser
{
public Db2StatementParser(string sql):base(sql)
{
}

unsafe internal override void determineStatementRanges(char* sql, int length, string initial_delimiter, List<StatementRange> ranges, string line_break)
{
char[] delimiterarr = { 'D', 'E', 'L', 'I', 'M', 'I', 'T', 'E', 'R' };
bool _stop = false;
string delimiter = string.IsNullOrEmpty(initial_delimiter) ? ";" : initial_delimiter;
char* delimiter_head;
fixed (char* delimiter_head_alloc = delimiter)
{
delimiter_head = delimiter_head_alloc;
}
char* start = (char*)(sql);
char* head = sql;
char* tail = head;
char* end = head + length;
char* new_line;
fixed (char* new_line_alloc = line_break)
{
new_line = new_line_alloc;
}
bool have_content = false; // Set when anything else but comments were found for the current statement.
int statementStart = 0;
int currentLine = 0;
while (!_stop && tail < end)
{
switch (*tail)
{
case '/': // Possible multi line comment or hidden (conditional) command.
if (*(tail + 1) == '*')
{
tail += 2;
bool is_hidden_command = (*tail == '!');
while (true)
{
while (tail < end && *tail != '*')
tail++;
if (tail == end) // Unfinished comment.
break;
else
{
if (*++tail == '/')
{
tail++; // Skip the slash too.
break;
}
}
}

if (!is_hidden_command && !have_content)
head = tail; // Skip over the comment.
}
else
tail++;

break;

case '-': // Possible single line comment.
{
char* end_char = tail + 2;
if (*(tail + 1) == '-' && (*end_char == ' ' || *end_char == '\t' || isLineBreak(end_char, new_line)))
{
// Skip everything until the end of the line.
tail += 2;
while (tail < end && !isLineBreak(tail, new_line))
tail++;
if (!have_content)
head = tail;
}
else
tail++;

break;
}

case '#': // MySQL single line comment.
while (tail < end && !isLineBreak(tail, new_line))
tail++;
if (!have_content)
head = tail;
break;

case '"':
case '\'':
case '`': // Quoted string/id. Skip this in a local loop.
{
have_content = true;
char quote = *tail++;
while (tail < end && *tail != quote)
{
// Skip any escaped character too.
if (*tail == '\\')
tail++;
tail++;
}
if (*tail == quote)
tail++; // Skip trailing quote char to if one was there.

break;
}
case 'd':
case 'D':
{
have_content = true;
// Possible start of the keyword DELIMITER. Must be at the start of the text or a character,
char* run = tail;
bool isDelimiter = true;
for (int i = 0; i < delimiterarr.Length; i++)
{
if (char.ToLower(delimiterarr[i]) == char.ToLower(*run))
++run;
else
isDelimiter = false;
}
if (*run == ' ' && isDelimiter)
{
// Delimiter keyword found. Get the new delimiter (everything until the end of the line).
StringBuilder delimiterBuilder = new StringBuilder();
while (run < end && *run != '\n' && *run != '\0')
{
if (*run != ' ' && *run != 13)
delimiterBuilder.Append(*run);
run++;
}
delimiter = delimiterBuilder.ToString();
fixed (char* dhead = delimiter)
{
delimiter_head = dhead;
}
while (isLineBreak(run, new_line))
{
++currentLine;
++run;
}
tail = run;
head = tail;
statementStart = currentLine;
}
else
++tail;
break;
}
default:
if (isLineBreak(tail, new_line))
{
++currentLine;
if (!have_content)
++statementStart;
}
if (*tail > ' ')
have_content = true;
tail++;
break;
}

if (*tail == *delimiter_head)
{
// Found possible start of the delimiter. Check if it really is.
int count = delimiter.Length;
if (count == 1)
{
// Most common case. Trim the statement and check if it is not empty before adding the range.
head = skip_leading_whitespace(head, tail);
if (head < tail)
{
long startT = head - (char*)sql;
long endT = tail - head;
if (includeInRange(startT, endT))
{
ranges.Add(new StatementRange(startT, endT));
}
}
head = ++tail;
have_content = false;
}
else
{
char* run = tail + 1;
char* del = delimiter_head + 1;
while (count-- > 1 && (*run++ == *del++))
;
if (count == 0)
{
// Multi char delimiter is complete. Tail still points to the start of the delimiter.
// Run points to the first character after the delimiter.
head = skip_leading_whitespace(head, tail);
if (head < tail)
{
long startT = head - (char*)sql;
long endT = tail - head;
if (includeInRange(startT, endT))
{
ranges.Add(new StatementRange(startT, endT));
}
}
tail = run;
head = run;
have_content = false;
}
}
}
}

// Add remaining text to the range list.
head = skip_leading_whitespace(head, tail);
if (head < tail)
{
long startT = head - (char*)sql;
long endT = tail - head;
if (includeInRange(startT, endT))
{
ranges.Add(new StatementRange(startT, endT));
}
}
}

}
}
3 changes: 2 additions & 1 deletion SqlStatementParser/DbType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum DbType : int
ORACLE = 2,
POSTGRES = 3,
SQLSERVER = 4,
SQLITE = 5
SQLITE = 5,
DB2 = 6
}
}
Loading

0 comments on commit 78e1972

Please sign in to comment.