Skip to content

Commit 54d4237

Browse files
committed
Handle backticks in stored procedure names. Fixes #1029
- Fix bug that didn't unescape backticks when getting stored procedure information. - Update documentation to call this out as a change from Connector/NET. - Note a Connnector/NET bug found during testing.
1 parent 67dc064 commit 54d4237

File tree

7 files changed

+56
-5
lines changed

7 files changed

+56
-5
lines changed

docs/content/api/MySqlConnector/MySqlCommand/CommandText.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ title: MySqlCommand.CommandText property
44

55
# MySqlCommand.CommandText property
66

7+
Gets or sets the command text to execute.
8+
79
```csharp
810
public override string CommandText { get; set; }
911
```
1012

13+
## Remarks
14+
15+
If [`CommandType`](../CommandType/) is Text, this is one or more SQL statements to execute. If [`CommandType`](../CommandType/) is StoredProcedure, this is the name of the stored procedure; any special characters in the stored procedure name must be quoted or escaped.
16+
1117
## See Also
1218

1319
* class [MySqlCommand](../../MySqlCommandType/)

docs/content/api/MySqlConnector/MySqlCommandType.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public sealed class MySqlCommand : DbCommand, ICloneable
1616
| --- | --- |
1717
| [MySqlCommand](../MySqlCommand/MySqlCommand/)() | Initializes a new instance of the [`MySqlCommand`](../MySqlCommandType/) class. |
1818
| [MySqlCommand](../MySqlCommand/MySqlCommand/)(…) | Initializes a new instance of the [`MySqlCommand`](../MySqlCommandType/) class, setting [`CommandText`](../MySqlCommand/CommandText/) to *commandText*. (4 constructors) |
19-
| override [CommandText](../MySqlCommand/CommandText/) { get; set; } | |
19+
| override [CommandText](../MySqlCommand/CommandText/) { get; set; } | Gets or sets the command text to execute. |
2020
| override [CommandTimeout](../MySqlCommand/CommandTimeout/) { getset; } | |
2121
| override [CommandType](../MySqlCommand/CommandType/) { getset; } | |
2222
| [Connection](../MySqlCommand/Connection/) { getset; } | |

docs/content/tutorials/migrating-from-connector-net.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ property doesn’t reference the active transaction. This fixes <a href="https:/
148148
To disable this strict validation, set <code>IgnoreCommandTransaction=true</code>
149149
in the connection string. See [Transaction Usage](/troubleshooting/transaction-usage/) for more details.
150150

151+
If `MySqlCommand.CommandType` is `CommandType.StoredProcedure`, the stored procedure name assigned to `MySqlCommand.CommandText` must have any special characters escaped or quoted. Connector/NET will automatically quote some characters (such as spaces); MySqlConnector leaves this up to the developer.
152+
151153
### MySqlDataAdapter
152154

153155
Connector/NET provides `MySqlDataAdapter.FillAsync`, `FillSchemaAsync`, and `UpdateAsync` methods, but these methods
@@ -284,3 +286,4 @@ The following bugs in Connector/NET are fixed by switching to MySqlConnector. (~
284286
* [#103390](https://bugs.mysql.com/bug.php?id=103390): Can't query `CHAR(36)` column if `MySqlCommand` is prepared
285287
* [#103801](https://bugs.mysql.com/bug.php?id=103801): `TimeSpan` parameters lose microseconds with prepared statement
286288
* [#103819](https://bugs.mysql.com/bug.php?id=103819): Can't use `StringBuilder` containing non-BMP characters as `MySqlParameter.Value`
289+
* [#104913](https://bugs.mysql.com/bug.php?id=104913): Cannot execute stored procedure with backtick in name

src/MySqlConnector/Core/NormalizedSchema.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ public NormalizedSchema(string name, string? defaultSchema = null)
2929
if (match.Success)
3030
{
3131
if (match.Groups[3].Success)
32-
Component = match.Groups[3].Value.Trim();
32+
Component = match.Groups[3].Value.Replace("``", "`").Trim();
3333
else if (match.Groups[4].Success)
3434
Component = match.Groups[4].Value.Trim();
3535

3636
string firstGroup = "";
3737
if (match.Groups[1].Success)
38-
firstGroup = match.Groups[1].Value.Trim();
38+
firstGroup = match.Groups[1].Value.Replace("``", "`").Trim();
3939
else if (match.Groups[2].Success)
4040
firstGroup = match.Groups[2].Value.Trim();
4141
if (Component is null)

src/MySqlConnector/MySqlCommand.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,12 @@ private bool NeedsPrepare(out Exception? exception)
173173
return Connection.Session.TryGetPreparedStatement(CommandText!) is null;
174174
}
175175

176-
/// <inheritdoc/>
176+
/// <summary>
177+
/// Gets or sets the command text to execute.
178+
/// </summary>
179+
/// <remarks>If <see cref="CommandType"/> is <see cref="CommandType.Text"/>, this is one or more SQL statements to execute.
180+
/// If <see cref="CommandType"/> is <see cref="CommandType.StoredProcedure"/>, this is the name of the stored procedure; any
181+
/// special characters in the stored procedure name must be quoted or escaped.</remarks>
177182
[AllowNull]
178183
public override string CommandText
179184
{

tests/MySqlConnector.Tests/NormalizeTests.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,19 @@ public class NormalizeTests
1212
[InlineData(" mysql . data ", "mysql", "data")]
1313
[InlineData("`mysql`.data", "mysql", "data")]
1414
[InlineData("mysql.`data`", "mysql", "data")]
15-
[InlineData("`my``sql`.`da``ta`", "my``sql", "da``ta")]
15+
[InlineData("`my``sql`.`da``ta`", "my`sql", "da`ta")]
1616
[InlineData("`mysql.data`", null, "mysql.data")]
1717
[InlineData("mysqldata", null, "mysqldata")]
1818
[InlineData("my`sql.data", null, null)]
1919
[InlineData("mysql.da`ta", null, null)]
20+
[InlineData("sproc_name", null, "sproc_name")]
21+
[InlineData("`sproc name`", null, "sproc name")]
22+
[InlineData("db_name.sproc_name", "db_name", "sproc_name")]
23+
[InlineData("`db name`.sproc_name", "db name", "sproc_name")]
24+
[InlineData("db_name.`sproc name`", "db_name", "sproc name")]
25+
[InlineData("`db name`.`sproc name`", "db name", "sproc name")]
26+
[InlineData("`db``name`.sproc_name", "db`name", "sproc_name")]
27+
[InlineData("db_name.`sproc``name`", "db_name", "sproc`name")]
2028
public void NormalizeSchema(string input, string expectedSchema, string expectedComponent)
2129
{
2230
var normalized = new NormalizedSchema(input);

tests/SideBySide/StoredProcedureTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,35 @@ public void EnumProcedure(bool prepare)
787787
Assert.False(reader.Read());
788788
}
789789

790+
[SkippableTheory(Baseline = "https://bugs.mysql.com/bug.php?id=104913")]
791+
[InlineData("`a b`")]
792+
[InlineData("`a.b`")]
793+
[InlineData("`a``b`")]
794+
[InlineData("`a b.c ``d`")]
795+
public void SprocNameSpecialCharacters(string sprocName)
796+
{
797+
using var connection = CreateOpenConnection();
798+
799+
using (var command = new MySqlCommand($@"DROP PROCEDURE IF EXISTS {sprocName};
800+
CREATE PROCEDURE {sprocName} ()
801+
BEGIN
802+
SELECT 'test' AS Result;
803+
END;", connection))
804+
{
805+
command.ExecuteNonQuery();
806+
}
807+
808+
using (var command = new MySqlCommand(sprocName, connection))
809+
{
810+
command.CommandType = CommandType.StoredProcedure;
811+
812+
using var reader = command.ExecuteReader();
813+
Assert.True(reader.Read());
814+
Assert.Equal("test", reader.GetString(0));
815+
Assert.False(reader.Read());
816+
}
817+
}
818+
790819
private static string NormalizeSpaces(string input)
791820
{
792821
input = input.Replace('\r', ' ');

0 commit comments

Comments
 (0)