This guide documents the specific pattern for fixing bugs where identifier-based predicates (like REGEXP_LIKE) are not properly recognized when wrapped in parentheses in boolean expressions.
Symptom: Parentheses around identifier-based boolean predicates cause syntax errors.
- Example:
SELECT 1 WHERE (REGEXP_LIKE('a', 'pattern'))fails to parse - Works:
SELECT 1 WHERE REGEXP_LIKE('a', 'pattern')(without parentheses)
Root Cause: The IsNextRuleBooleanParenthesis() function in TSql80ParserBaseInternal.cs only recognizes:
- Keyword-based predicates (tokens):
LIKE,BETWEEN,CONTAINS,EXISTS, etc. - One identifier-based predicate:
IIF - But doesn't recognize newer identifier-based predicates like
REGEXP_LIKE
This function determines whether parentheses contain a boolean expression vs. a scalar expression. It scans forward from a LeftParenthesis token looking for boolean operators or predicates.
Location: SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs
Key Logic:
case TSql80ParserInternal.Identifier:
// if identifier is IIF
if(NextTokenMatches(CodeGenerationSupporter.IIf))
{
++insideIIf;
}
// ADD NEW IDENTIFIER-BASED PREDICATES HERE
break;For identifier-based boolean predicates, add detection logic in the Identifier case:
case TSql80ParserInternal.Identifier:
// if identifier is IIF
if(NextTokenMatches(CodeGenerationSupporter.IIf))
{
++insideIIf;
}
// if identifier is REGEXP_LIKE
else if(NextTokenMatches(CodeGenerationSupporter.RegexpLike))
{
if (caseDepth == 0 && topmostSelect == 0 && insideIIf == 0)
{
matches = true;
loop = false;
}
}
break;Create a test case within the existing test framework to confirm the bug:
[TestMethod]
public void ReproduceParenthesesIssue()
{
var parser = new TSql170Parser(true);
var sql = "SELECT 1 WHERE (REGEXP_LIKE('a', 'pattern'));";
var result = parser.Parse(new StringReader(sql), out var errors);
// Should fail before fix, pass after fix
Assert.AreEqual(0, errors.Count, "Should parse without errors after fix");
}Only170SyntaxTests.cs, do not create a new test project.
Find the predicate identifier in CodeGenerationSupporter:
// In CodeGenerationSupporter.cs
public const string RegexpLike = "REGEXP_LIKE";Modify TSql80ParserBaseInternal.cs in the IsNextRuleBooleanParenthesis() method:
File: SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs
Method: IsNextRuleBooleanParenthesis()
Location: Around line 808, in the case TSql80ParserInternal.Identifier: block
Add the predicate detection logic following the pattern shown above.
Add test cases covering the parentheses scenario:
Test Script: Test/SqlDom/TestScripts/RegexpLikeTests170.sql
SELECT 1 WHERE (REGEXP_LIKE('a', '%pattern%'));Baseline: Test/SqlDom/Baselines170/RegexpLikeTests170.sql
SELECT 1
WHERE (REGEXP_LIKE ('a', '%pattern%'));Test Configuration: Update error counts in Only170SyntaxTests.cs if the new test cases affect older parser versions.
# Build the parser
dotnet build SqlScriptDom/Microsoft.SqlServer.TransactSql.ScriptDom.csproj -c Debug
# Run the specific test
dotnet test Test/SqlDom/UTSqlScriptDom.csproj --filter "FullyQualifiedName~SqlStudio.Tests.UTSqlScriptDom.SqlDomTests.TSql170SyntaxIn170ParserTest" -c DebugThis fix pattern applies when:
- Identifier-based predicates: The predicate is defined as an identifier (not a keyword token)
- Boolean context: The predicate returns a boolean value for use in WHERE clauses, CHECK constraints, etc.
- Parentheses fail: The predicate works without parentheses but fails with parentheses
- Already implemented: The predicate grammar and AST are already correctly implemented
REGEXP_LIKE(✅ Fixed)- Future identifier-based boolean functions
- Custom function predicates that return boolean values
This type of fix typically involves:
-
Core Parser Logic:
SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs- Main fix
-
Test Infrastructure:
Test/SqlDom/TestScripts/[TestName].sql- Input test casesTest/SqlDom/Baselines[Version]/[TestName].sql- Expected outputTest/SqlDom/Only[Version]SyntaxTests.cs- Test configuration
-
Potentially Affected:
Test/SqlDom/TestScripts/BooleanExpressionTests.sql- May need additional test casesTest/SqlDom/BaselinesCommon/BooleanExpressionTests.sql- Corresponding baselines
- Parentheses syntax parses without errors
- Non-parentheses syntax still works
- Test suite passes for target SQL version
- Older SQL versions have appropriate error counts
- Related boolean expression tests still pass
- IIF Special Handling:
IIFhas special logic (++insideIIf) because it's not a simple boolean predicate - Context Conditions: The fix includes conditions (
caseDepth == 0 && topmostSelect == 0 && insideIIf == 0) to ensure proper parsing context - Token vs Identifier: Keyword predicates are handled as tokens, identifier predicates need special detection
- Cross-Version Impact: Adding test cases may increase error counts for older SQL Server parsers
This pattern ensures that identifier-based boolean predicates work consistently with parentheses, maintaining parser compatibility across different syntactic contexts.