Skip to content
Merged
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
27 changes: 0 additions & 27 deletions src/grate.core/Infrastructure/BatchSplitterReplacer.cs

This file was deleted.

9 changes: 9 additions & 0 deletions src/grate.core/Infrastructure/IStatementSplitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace grate.Infrastructure;

/// <summary>
/// Splits a batch of SQL text into individual statements to be run separately.
/// </summary>
public interface IStatementSplitter
{
IEnumerable<string> Split(string statement);
}
2 changes: 1 addition & 1 deletion src/grate.core/Infrastructure/ISyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/// </summary>
public interface ISyntax
{
string StatementSeparatorRegex { get; }
IStatementSplitter StatementSplitter { get; }
string CurrentDatabase { get; }
string ListDatabases { get; }
string CreateDatabase(string databaseName, string? password);
Expand Down
59 changes: 59 additions & 0 deletions src/grate.core/Infrastructure/RegexStatementSplitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Text.RegularExpressions;
using static System.Text.RegularExpressions.RegexOptions;

namespace grate.Infrastructure;

/// <summary>
/// Base class for splitting SQL batches into statements using a dialect-specific regex.
/// The regex must tag the parts to keep as KEEP1/KEEP2 groups, and the separator to remove as BATCHSPLITTER.
/// </summary>
public abstract class RegexStatementSplitter : IStatementSplitter
{
private const string BatchTerminatorReplacementString = @" |{[_REMOVE_]}| ";

protected abstract string StringsRegex { get; }
protected abstract string DashCommentsRegex { get; }
protected abstract string StarCommentsRegex { get; }
protected abstract string SeparatorRegex { get; }

/// <summary>
/// Additional regex fragments to be inserted between the strings and comments patterns (e.g. for extra string forms).
/// </summary>
protected virtual IEnumerable<string> AdditionalRegexes => Enumerable.Empty<string>();

private Regex? _regex;
private Regex Regex => _regex ??= new Regex(Pattern, IgnoreCase | Multiline);

private string Pattern =>
string.Join("|", new[] { StringsRegex }
.Concat(AdditionalRegexes)
.Append(DashCommentsRegex)
.Append(StarCommentsRegex)
.Append(SeparatorRegex));

public IEnumerable<string> Split(string statement)
{
var replaced = Replace(statement);

var statements = replaced.Split(BatchTerminatorReplacementString);
return statements.Where(HasScriptsToRun);
}

private string Replace(string text) => Regex.Replace(text, ReplaceBatchSeparator);

private static string ReplaceBatchSeparator(Match match)
{
var groups = match.Groups;
var replacement = groups["BATCHSPLITTER"].Success ? BatchTerminatorReplacementString : string.Empty;
return groups["KEEP1"].Value + replacement + groups["KEEP2"].Value;
}

private static bool HasScriptsToRun(string sqlStatement)
{
var trimmedStatement = sqlStatement.Replace(BatchTerminatorReplacementString, string.Empty, StringComparison.InvariantCultureIgnoreCase);
return !string.IsNullOrEmpty(trimmedStatement.ToLower()
.Replace(Environment.NewLine, string.Empty)
.Replace("\n", string.Empty) // This is necessary to make script with unix-style line endings work on grate on Windows
.Replace(" ", string.Empty));
}
}
33 changes: 0 additions & 33 deletions src/grate.core/Infrastructure/StatementSplitter.cs

This file was deleted.

5 changes: 1 addition & 4 deletions src/grate.core/Migration/AnsiSqlDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ protected AnsiSqlDatabase(ILogger logger, ISyntax syntax)
{
Logger = logger;
_syntax = syntax;
StatementSplitter = new StatementSplitter(syntax);
}

public string ServerName => Connection.DataSource;
Expand All @@ -54,15 +53,13 @@ protected AnsiSqlDatabase(ILogger logger, ISyntax syntax)
public abstract bool SupportsSchemas { get; }

public virtual bool SplitBatchStatements => true;
private StatementSplitter StatementSplitter { get; }
private IStatementSplitter StatementSplitter => _syntax.StatementSplitter;

public virtual IEnumerable<string> GetStatements(string sql)
=> SplitBatchStatements ? this.StatementSplitter.Split(sql) : new[] { sql };

public abstract void ThrowScriptFailed(MigrationsFolder folder, string file, string? scriptText, Exception exception);

public string StatementSeparatorRegex => _syntax.StatementSeparatorRegex;

public string ScriptsRunTable => _syntax.TableWithSchema(SchemaName, ScriptsRunTableName);
public string ScriptsRunErrorsTable => _syntax.TableWithSchema(SchemaName, ScriptsRunErrorsTableName);
public string VersionTable => _syntax.TableWithSchema(SchemaName, VersionTableName);
Expand Down
11 changes: 11 additions & 0 deletions src/grate.mariadb/Infrastructure/MariaDbStatementSplitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using grate.Infrastructure;

namespace grate.MariaDb.Infrastructure;

public class MariaDbStatementSplitter : RegexStatementSplitter
{
protected override string StringsRegex => @"(?<KEEP1>'[^']*')";
protected override string DashCommentsRegex => @"(?<KEEP1>--.*$)";
protected override string StarCommentsRegex => @"(?<KEEP1>/\*[\S\s]*?\*/)";
protected override string SeparatorRegex => @"(?<KEEP1>^|\s)(?<BATCHSPLITTER>GO)(?<KEEP2>\s|;|$)";
}
13 changes: 2 additions & 11 deletions src/grate.mariadb/Infrastructure/MariaDbSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,8 @@ namespace grate.MariaDb.Infrastructure;

public readonly struct MariaDbSyntax : ISyntax
{
public string StatementSeparatorRegex
{
get
{
const string strings = @"(?<KEEP1>'[^']*')";
const string dashComments = @"(?<KEEP1>--.*$)";
const string starComments = @"(?<KEEP1>/\*[\S\s]*?\*/)";
const string separator = @"(?<KEEP1>^|\s)(?<BATCHSPLITTER>GO)(?<KEEP2>\s|;|$)";
return strings + "|" + dashComments + "|" + starComments + "|" + separator;
}
}
private static readonly IStatementSplitter _statementSplitter = new MariaDbStatementSplitter();
public IStatementSplitter StatementSplitter => _statementSplitter;

public string CurrentDatabase => "SELECT DATABASE()";
public string ListDatabases => "SHOW DATABASES";
Expand Down
11 changes: 11 additions & 0 deletions src/grate.oracle/Infrastructure/OracleStatementSplitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using grate.Infrastructure;

namespace grate.Oracle.Infrastructure;

public class OracleStatementSplitter : RegexStatementSplitter
{
protected override string StringsRegex => @"(?<KEEP1>'[^']*')";
protected override string DashCommentsRegex => @"(?<KEEP1>--.*$)";
protected override string StarCommentsRegex => @"(?<KEEP1>/\*[\S\s]*?\*/)";
protected override string SeparatorRegex => @"(?<KEEP1>^|\s)(?<BATCHSPLITTER>/)(?<KEEP2>\s|;|$)";
}
13 changes: 2 additions & 11 deletions src/grate.oracle/Infrastructure/OracleSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,8 @@ namespace grate.Oracle.Infrastructure;

public readonly struct OracleSyntax : ISyntax
{
public string StatementSeparatorRegex
{
get
{
const string strings = @"(?<KEEP1>'[^']*')";
const string dashComments = @"(?<KEEP1>--.*$)";
const string starComments = @"(?<KEEP1>/\*[\S\s]*?\*/)";
const string separator = @"(?<KEEP1>^|\s)(?<BATCHSPLITTER>/)(?<KEEP2>\s|;|$)";
return strings + "|" + dashComments + "|" + starComments + "|" + separator;
}
}
private static readonly IStatementSplitter _statementSplitter = new OracleStatementSplitter();
public IStatementSplitter StatementSplitter => _statementSplitter;

public string CurrentDatabase => "select user from dual";
public string ListDatabases => "SELECT * FROM all_users";
Expand Down
22 changes: 22 additions & 0 deletions src/grate.postgresql/Infrastructure/PostgreSqlStatementSplitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using grate.Infrastructure;

namespace grate.PostgreSql.Infrastructure;

public class PostgreSqlStatementSplitter : RegexStatementSplitter
{
protected override string StringsRegex => @"(?<KEEP1>'([^']|\'\')*')";
protected override string DashCommentsRegex => "(?<KEEP1>--.*$)";
protected override string StarCommentsRegex => @"(?<KEEP1>/\*[\S\s]*?\*/)";
protected override string SeparatorRegex => "(?<KEEP1>.*)(?<BATCHSPLITTER>(;)(?=(?:[^']|'[^']*')*$))(?<KEEP2>.*)";

protected override IEnumerable<string> AdditionalRegexes
{
get
{
const string backslashEscapedStrings = @"(?<KEEP1>E(?<!\\)('[\S\s]*?(?<!\\)'))";
const string dollarQuotedStrings = @"(?<KEEP1>\$(?'tag'\w*)\$[\S\s]*?\$\k'tag'\$)";
yield return backslashEscapedStrings;
yield return dollarQuotedStrings;
}
}
}
15 changes: 2 additions & 13 deletions src/grate.postgresql/Infrastructure/PostgreSqlSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,8 @@ namespace grate.PostgreSql.Infrastructure;

public readonly struct PostgreSqlSyntax : ISyntax
{
public string StatementSeparatorRegex
{
get
{
const string strings = @"(?<KEEP1>'([^']|\'\')*')";
const string backslashEscapedStrings = @"(?<KEEP1>E(?<!\\)('[\S\s]*?(?<!\\)'))";
const string dollarQuotedStrings = @"(?<KEEP1>\$(?'tag'\w*)\$[\S\s]*?\$\k'tag'\$)";
const string dashComments = "(?<KEEP1>--.*$)";
const string starComments = @"(?<KEEP1>/\*[\S\s]*?\*/)";
const string separator = "(?<KEEP1>.*)(?<BATCHSPLITTER>(;)(?=(?:[^']|'[^']*')*$))(?<KEEP2>.*)";
return strings + "|" + backslashEscapedStrings + "|" + dollarQuotedStrings + "|" + dashComments + "|" + starComments + "|" + separator;
}
}
private static readonly IStatementSplitter _statementSplitter = new PostgreSqlStatementSplitter();
public IStatementSplitter StatementSplitter => _statementSplitter;

public string CurrentDatabase => "SELECT current_database()";
public string ListDatabases => "SELECT datname FROM pg_database";
Expand Down
11 changes: 11 additions & 0 deletions src/grate.sqlite/Infrastructure/SqliteStatementSplitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using grate.Infrastructure;

namespace grate.Sqlite.Infrastructure;

public class SqliteStatementSplitter : RegexStatementSplitter
{
protected override string StringsRegex => @"(?<KEEP1>'[^']*')";
protected override string DashCommentsRegex => @"(?<KEEP1>--.*$)";
protected override string StarCommentsRegex => @"(?<KEEP1>/\*[\S\s]*?\*/)";
protected override string SeparatorRegex => @"(?<KEEP1>^|\s)(?<BATCHSPLITTER>GO)(?<KEEP2>\s|;|$)";
}
13 changes: 2 additions & 11 deletions src/grate.sqlite/Infrastructure/SqliteSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,8 @@ namespace grate.Sqlite.Infrastructure;

public readonly struct SqliteSyntax : ISyntax
{
public string StatementSeparatorRegex
{
get
{
const string strings = @"(?<KEEP1>'[^']*')";
const string dashComments = @"(?<KEEP1>--.*$)";
const string starComments = @"(?<KEEP1>/\*[\S\s]*?\*/)";
const string separator = @"(?<KEEP1>^|\s)(?<BATCHSPLITTER>GO)(?<KEEP2>\s|;|$)";
return strings + "|" + dashComments + "|" + starComments + "|" + separator;
}
}
private static readonly IStatementSplitter _statementSplitter = new SqliteStatementSplitter();
public IStatementSplitter StatementSplitter => _statementSplitter;

public string CurrentDatabase => "SELECT name FROM pragma_database_list ORDER BY seq DESC LIMIT 1";
public string ListDatabases => "select name from pragma_database_list";
Expand Down
Loading
Loading