Skip to content

Commit 80c9a50

Browse files
campbellhardingdeasonshokurovdroyad
authored
fix: #680 PostgreSQL statements split implemented (#19)
* fix: #680 PostgreSQL statements split implemented Co-authored-by: shokurov <egor.shokurov@gmail.com> * Updated approval file --------- Co-authored-by: shokurov <egor.shokurov@gmail.com> Co-authored-by: Robert Wagner <robert@wagner.id.au>
1 parent b33151c commit 80c9a50

4 files changed

Lines changed: 448 additions & 3 deletions

File tree

src/Tests/ApprovalFiles/NoPublicApiChanges.Run.approved.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public PostgresqlConnectionManager(string connectionString) { }
2525
public PostgresqlConnectionManager(Npgsql.NpgsqlDataSource datasource) { }
2626
public PostgresqlConnectionManager(string connectionString, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { }
2727
public PostgresqlConnectionManager(string connectionString, DbUp.Postgresql.PostgresqlConnectionOptions connectionOptions) { }
28+
public bool StandardConformingStrings { get; set; }
2829
public override System.Collections.Generic.IEnumerable<string> SplitScriptIntoCommands(string scriptContents) { }
2930
}
3031
public class PostgresqlConnectionOptions
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using Xunit;
4+
5+
namespace DbUp.Postgresql.Tests;
6+
7+
public class PostgresqlQueryParserTests
8+
{
9+
[Theory]
10+
[InlineData("SELECT 1\n;\nSELECT 2", 2, "SELECT 1", "SELECT 2")]
11+
[InlineData(";;SELECT 1", 1, "SELECT 1")]
12+
[InlineData("SELECT 1;", 1, "SELECT 1")]
13+
[InlineData("", 0)]
14+
[InlineData("CREATE OR REPLACE RULE test AS ON UPDATE TO test DO (SELECT 1; SELECT 1)",
15+
1,
16+
"CREATE OR REPLACE RULE test AS ON UPDATE TO test DO (SELECT 1; SELECT 1)")]
17+
[InlineData("CREATE OR REPLACE RULE test AS ON UPDATE TO test DO (SELECT 1); SELECT 2",
18+
2,
19+
"CREATE OR REPLACE RULE test AS ON UPDATE TO test DO (SELECT 1)", "SELECT 2")]
20+
[InlineData("SELECT 1 /* block comment; */", 1, "SELECT 1 /* block comment; */")]
21+
[InlineData(
22+
"""
23+
SELECT 1;
24+
-- Line comment; with semicolon
25+
SELECT 2;
26+
""", 2,
27+
"SELECT 1",
28+
"""
29+
-- Line comment; with semicolon
30+
SELECT 2
31+
""")]
32+
[InlineData("SELECT 'string with; semicolon'", 1, "SELECT 'string with; semicolon'")]
33+
[InlineData("SELECT 'string with'' quote and; semicolon'", 1, "SELECT 'string with'' quote and; semicolon'")]
34+
[InlineData("""
35+
CREATE FUNCTION TXT()
36+
LANGUAGE PLPGSQL AS
37+
$BODY$
38+
BEGIN
39+
SELECT 'string with'' quote and; semicolon';
40+
END
41+
$BODY$
42+
""", 1)]
43+
[InlineData("SELECT 1 as \"QUOTED;IDENT\"", 1)]
44+
[InlineData("SELECT E'\\041'; SELECT '1'", 2, "SELECT E'\\041'", "SELECT '1'")]
45+
[InlineData("""
46+
SELECT 'some'
47+
'text';
48+
SELECT '1'
49+
""", 2)]
50+
public void split_into_statements(string sql, int statementCount, params string[] expected)
51+
{
52+
var results = ParseCommand(sql);
53+
Assert.Equal(statementCount, results.Count);
54+
if (expected.Length > 0)
55+
Assert.Equal(expected, results);
56+
}
57+
58+
[Fact]
59+
public void split_into_statements_non_sql_standard()
60+
{
61+
const string sql = "SELECT 'string with\\' quote and; semicolon'";
62+
var results = ParseCommand(sql, false);
63+
Assert.Single(results);
64+
Assert.Equal(sql, results[0]);
65+
}
66+
67+
private List<string> ParseCommand(string sql)
68+
=> ParseCommand(sql, true);
69+
70+
private static List<string> ParseCommand(string sql, bool standardConformingStrings)
71+
{
72+
var manager = new PostgresqlConnectionManager("") { StandardConformingStrings = standardConformingStrings };
73+
var commands = manager.SplitScriptIntoCommands(sql);
74+
return commands.ToList();
75+
}
76+
}

src/dbup-postgresql/PostgresqlConnectionManager.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.Collections.Generic;
22
using System.Linq;
33
using System.Security.Cryptography.X509Certificates;
4-
using System.Text.RegularExpressions;
54
using DbUp.Engine.Transactions;
65
using Npgsql;
76

@@ -12,6 +11,11 @@ namespace DbUp.Postgresql
1211
/// </summary>
1312
public class PostgresqlConnectionManager : DatabaseConnectionManager
1413
{
14+
/// <summary>
15+
/// Disallow single quotes to be escaped with a backslash (\')
16+
/// </summary>
17+
public bool StandardConformingStrings { get; set; } = true;
18+
1519
/// <summary>
1620
/// Creates a new PostgreSQL database connection.
1721
/// </summary>
@@ -47,7 +51,7 @@ public PostgresqlConnectionManager(string connectionString, PostgresqlConnection
4751

4852
return databaseConnection;
4953
}
50-
))
54+
))
5155
{
5256
}
5357

@@ -67,7 +71,7 @@ public PostgresqlConnectionManager(NpgsqlDataSource datasource)
6771
public override IEnumerable<string> SplitScriptIntoCommands(string scriptContents)
6872
{
6973
var scriptStatements =
70-
Regex.Split(scriptContents, "^\\s*;\\s*$", RegexOptions.IgnoreCase | RegexOptions.Multiline)
74+
PostgresqlQueryParser.ParseRawQuery(scriptContents, StandardConformingStrings)
7175
.Select(x => x.Trim())
7276
.Where(x => x.Length > 0)
7377
.ToArray();

0 commit comments

Comments
 (0)