Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion EmbeddedSQLTester/EmbeddedSQLTester.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.2.0" />
<PackageReference Include="TSQL.Parser" Version="2.6.0" />
<PackageReference Include="Microsoft.SqlServer.TransactSql.ScriptDom" Version="180.18.1" />
<None Include="README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters;
using TSQL;
using TSQL.Statements;
using EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ClauseProcessors;
using EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ExpressionProcessors;
using Microsoft.SqlServer.TransactSql.ScriptDom;

namespace EmbeddedSQLTester.SQLitePlatformConversion
{
Expand All @@ -13,33 +15,47 @@ public class SQLServerToOrmliteSQLiteDialectConverter

public string ConvertToOrmliteSQLiteSQL(string sqlInput)
{
var statements = TSQLStatementReader.ParseStatements(sqlInput);
var parser = new TSql150Parser(false);
using (var reader = new StringReader(sqlInput))
{
var fragment = parser.Parse(reader, out var errors);
var script = (TSqlScript)fragment;

var statement = statements[0];
if (script.Batches.Count > 0 && script.Batches[0].Statements.Count > 0)
{
// AST parsing succeeded — use structured approach
var statement = script.Batches[0].Statements[0];

if (statement.GetType() == typeof(TSQLSelectStatement))
{
var selectStatement = (TSQLSelectStatement)statement;
var converter = new SelectStatementConverter();
_clauseConverters = converter.GetClauseProcessorList(selectStatement);
}
else if (statement.GetType() == typeof(TSQLUpdateStatement))
{
var updateStatement = (TSQLUpdateStatement)statement;
var converter = new UpdateStatementConverter();
_clauseConverters = converter.GetClauseProcessorList(updateStatement);
}
else if (statement.GetType() == typeof(TSQLInsertStatement))
{
var insertStatement = (TSQLInsertStatement)statement;
var converter = new InsertStatementConverter();
_clauseConverters = converter.GetClauseProcessorList(insertStatement);
}
else if (statement.GetType() == typeof(TSQLDeleteStatement))
{
var deleteStatement = (TSQLDeleteStatement)statement;
var converter = new DeleteStatementConverter();
_clauseConverters = converter.GetClauseProcessorList(deleteStatement);
if (statement is SelectStatement selectStatement)
{
var converter = new SelectStatementConverter();
_clauseConverters = converter.GetClauseProcessorList(selectStatement);
}
else if (statement is UpdateStatement updateStatement)
{
var converter = new UpdateStatementConverter();
_clauseConverters = converter.GetClauseProcessorList(updateStatement);
}
else if (statement is InsertStatement insertStatement)
{
var converter = new InsertStatementConverter();
_clauseConverters = converter.GetClauseProcessorList(insertStatement);
}
else if (statement is DeleteStatement deleteStatement)
{
var converter = new DeleteStatementConverter();
_clauseConverters = converter.GetClauseProcessorList(deleteStatement);
}
}
else
{
// AST parsing failed — fall back to token-based clause splitting
var stream = fragment.ScriptTokenStream;
var stmtType = TokenBasedStatementParser.DetectStatementType(stream);
var clauses = TokenBasedStatementParser.SplitIntoClauses(stream, stmtType);

_clauseConverters = BuildConvertersFromClauses(stmtType, clauses);
}
}

var sb = new StringBuilder();
Expand All @@ -48,6 +64,47 @@ public string ConvertToOrmliteSQLiteSQL(string sqlInput)
sb.Append(clauseConverter.Convert());

return sb.ToString();
}
}

private List<ConverterBase> BuildConvertersFromClauses(string stmtType,
Dictionary<string, List<SqlToken>> clauses)
{
switch (stmtType)
{
case "SELECT":
return new List<ConverterBase>
{
new GeneralClauseConverter(clauses.ContainsKey("SELECT") ? clauses["SELECT"] : null),
new FromClauseConverter(clauses.ContainsKey("FROM") ? clauses["FROM"] : null),
new WhereClauseConverter(clauses.ContainsKey("WHERE") ? clauses["WHERE"] : null),
new GeneralClauseConverter(clauses.ContainsKey("GROUPBY") ? clauses["GROUPBY"] : null),
new OrderByClauseConverter(clauses.ContainsKey("ORDERBY") ? clauses["ORDERBY"] : null),
new LimitStatementConverter(clauses.ContainsKey("SELECT") ? clauses["SELECT"] : null),
};
case "UPDATE":
return new List<ConverterBase>
{
new UpdateClauseConverter(clauses.ContainsKey("UPDATE") ? clauses["UPDATE"] : null),
new GeneralClauseConverter(clauses.ContainsKey("SET") ? clauses["SET"] : null),
new FromClauseConverter(clauses.ContainsKey("FROM") ? clauses["FROM"] : null),
new WhereClauseConverter(clauses.ContainsKey("WHERE") ? clauses["WHERE"] : null),
};
case "INSERT":
return new List<ConverterBase>
{
new InsertClauseConverter(clauses.ContainsKey("INSERT") ? clauses["INSERT"] : null),
new ValuesExpressionConverter(clauses.ContainsKey("VALUES") ? clauses["VALUES"] : null),
};
case "DELETE":
return new List<ConverterBase>
{
new GeneralClauseConverter(clauses.ContainsKey("DELETE") ? clauses["DELETE"] : null),
new FromClauseConverter(clauses.ContainsKey("FROM") ? clauses["FROM"] : null),
new WhereClauseConverter(clauses.ContainsKey("WHERE") ? clauses["WHERE"] : null),
};
default:
return new List<ConverterBase>();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
using System.Collections.Generic;
using System.Text;
using TSQL.Clauses;
using TSQL.Tokens;

namespace EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ClauseProcessors
{
internal abstract class ClauseConverterBase : ConverterBase
{
private readonly TSQLClause _clause;
private readonly List<SqlToken> _tokens;
private StringBuilder _stringBuilder;
protected List<TSQLToken> Tokens;
protected List<SqlToken> Tokens;

protected ClauseConverterBase(TSQLClause clauseParameter)
protected ClauseConverterBase(List<SqlToken> tokens)
{
_clause = clauseParameter;
_tokens = tokens;
}

public override string Convert()
{
_stringBuilder = new StringBuilder();

if (_clause != null)
if (_tokens != null)
{
Tokens = _clause.Tokens;
Tokens = _tokens;
ConvertHelper();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using TSQL.Clauses;
using System.Collections.Generic;

namespace EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ClauseProcessors
{
internal class FromClauseConverter : ClauseConverterBase
{
private bool _insideOnClause;

public FromClauseConverter(TSQLClause clause) : base(clause)
public FromClauseConverter(List<SqlToken> tokens) : base(tokens)
{
_insideOnClause = false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System;
using System.Net.Mime;
using System.Collections.Generic;
using System.Text;
using TSQL.Clauses;

namespace EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ClauseProcessors
{
internal class GeneralClauseConverter : ClauseConverterBase
{
public GeneralClauseConverter(TSQLClause clause) : base(clause)
public GeneralClauseConverter(List<SqlToken> tokens) : base(tokens)
{
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using System.Text;
using TSQL.Clauses;
using System.Collections.Generic;

namespace EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ClauseProcessors
{
internal class InsertClauseConverter : ClauseConverterBase
{
public InsertClauseConverter(TSQLClause clause) : base(clause)
public InsertClauseConverter(List<SqlToken> tokens) : base(tokens)
{
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.Text;
using TSQL.Clauses;

namespace EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ClauseProcessors
{
internal class OrderByClauseConverter : ClauseConverterBase
{
public OrderByClauseConverter(TSQLClause clauseParameter) : base(clauseParameter)
public OrderByClauseConverter(List<SqlToken> tokens) : base(tokens)
{
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using TSQL.Clauses;
using System.Collections.Generic;

namespace EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ClauseProcessors
{
internal class UpdateClauseConverter : ClauseConverterBase
{
public UpdateClauseConverter(TSQLClause clauseParameter) : base(clauseParameter)
public UpdateClauseConverter(List<SqlToken> tokens) : base(tokens)
{
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.Text;
using TSQL.Clauses;

namespace EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ClauseProcessors
{
internal class WhereClauseConverter : ClauseConverterBase
{
public WhereClauseConverter(TSQLClause clause) : base(clause)
public WhereClauseConverter(List<SqlToken> tokens) : base(tokens)
{
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,87 @@
using System.Collections.Generic;
using EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ClauseProcessors;
using TSQL.Statements;
using Microsoft.SqlServer.TransactSql.ScriptDom;

namespace EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters
{
internal class DeleteStatementConverter
{
public List<ConverterBase> GetClauseProcessorList(TSQLDeleteStatement statement)
public List<ConverterBase> GetClauseProcessorList(DeleteStatement statement)
{
var spec = statement.DeleteSpecification;

// DELETE keyword tokens (just "DELETE")
var deleteTokens = ExtractDeleteKeywordTokens(statement);
var fromTokens = ExtractFromTokens(spec);
var whereTokens = SqlToken.ExtractTokens(spec.WhereClause);

return new List<ConverterBase>
{
new GeneralClauseConverter(statement.Delete),
new FromClauseConverter(statement.From),
new WhereClauseConverter(statement.Where),
new GeneralClauseConverter(deleteTokens),
new FromClauseConverter(fromTokens),
new WhereClauseConverter(whereTokens),
};
}

private List<SqlToken> ExtractDeleteKeywordTokens(DeleteStatement statement)
{
var tokens = new List<SqlToken>();
var stream = statement.ScriptTokenStream;

// Just the DELETE keyword
for (int i = statement.FirstTokenIndex; i <= statement.LastTokenIndex; i++)
{
var t = stream[i];
if (t.TokenType == TSqlTokenType.WhiteSpace) continue;
if (t.TokenType == TSqlTokenType.Delete)
{
tokens.Add(new SqlToken
{
Text = t.Text,
BeginPosition = t.Offset,
EndPosition = t.Offset + t.Text.Length - 1
});
break;
}
}

return tokens;
}

private List<SqlToken> ExtractFromTokens(DeleteSpecification spec)
{
// For DELETE FROM, the FromClause on DeleteSpecification may be null
// but the target has the table. We need FROM + target.
var tokens = new List<SqlToken>();
var stream = spec.ScriptTokenStream;

// Find FROM keyword and everything up to WHERE (or end)
int startIndex = spec.Target.FirstTokenIndex;
int endIndex = spec.WhereClause != null ? spec.WhereClause.FirstTokenIndex - 1 : spec.LastTokenIndex;

// Look for FROM keyword before the target
for (int i = startIndex - 1; i >= spec.FirstTokenIndex; i--)
{
if (stream[i].TokenType == TSqlTokenType.From)
{
startIndex = i;
break;
}
}

for (int i = startIndex; i <= endIndex; i++)
{
var t = stream[i];
if (t.TokenType == TSqlTokenType.WhiteSpace) continue;
tokens.Add(new SqlToken
{
Text = t.Text,
BeginPosition = t.Offset,
EndPosition = t.Offset + t.Text.Length - 1
});
}

return tokens;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
using System.Collections.Generic;
using System.Text;
using EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ClauseProcessors;
using TSQL.Elements;
using TSQL.Expressions;
using TSQL.Tokens;

namespace EmbeddedSQLTester.SQLitePlatformConversion.StatementConverters.ExpressionProcessors
{
internal abstract class ExpressionConverterBase : ConverterBase
{
private readonly TSQLValues _expression;
private readonly List<SqlToken> _tokens;
private StringBuilder _stringBuilder;
protected List<TSQLToken> Tokens;
protected List<SqlToken> Tokens;

protected ExpressionConverterBase(TSQLValues expression)
protected ExpressionConverterBase(List<SqlToken> tokens)
{
_expression = expression;
_tokens = tokens;
}

public override string Convert()
{
_stringBuilder = new StringBuilder();

if (_expression != null)
if (_tokens != null)
{
Tokens = _expression.Tokens;
Tokens = _tokens;
ConvertHelper();
}

Expand Down
Loading