查找“裸”varchar

Mic*_*een 2 sql-server varchar ssms regex

很明显,省略 varchar 的长度是一件坏事。不幸的是,我现在正在使用发生这种情况的代码库。广泛地。我想纠正这一点。第一步是查找出现的情况。这就是我需要帮助的地方。

使用我能想到的所有同义词在各种网络引擎上进行搜索都没有返回权威答案。我要问的是

  • 我错过的其他测试用例
  • 一种全面、规范的查找无长度声明的方法

通常在 Windows 开发环境(SSMS、Powershell、.Net 等)上可用的任何技术都是好的。采用更多利基技术的答案对于更广泛的社区来说会很有趣,但对我个人来说就不那么有趣了。

测试

由于所讨论的四种数据类型 - char、nchar、varchar 和 nvarchar - 都以字符 CHAR 结尾,因此我在下面的测试中单独使用它。这可以避免列表变得臃肿,并使添加更多测试变得更简单。如果需要的话,复制-粘贴-替换会很容易。

-- These are all legal; the regex must not return these
char(9)
char (9)            -- with a space
char    (9)         -- with a tab
char         (9)    -- tab space tab space
char(max)
char
(9)                 -- a new line between type and length

character(9)
CAST(999 AS character(9))

char varying(9)
character varying(9)
CAST(999 AS char varying(9))
CAST(999 AS character varying(9))


-- These also are legal; ugly, but legal
[char](9)
[char] (9)          -- with a space
[char]  (9)         -- with a tab
[char]       (9)    -- tab space tab space
[char](max)
[char]
(9)                 -- a new line between type and length

-- The type can also be delimited by double-quote
"char"(9)
-- All the tests using square brackets should be duplicated with other delimiters.

[character](9)
CAST(999 AS [character](9))

-- SQL Server 2022 throws an error for [character varying]
-- Msg 243, Level 16, State 1, Line 15
-- Type character varying is not a defined system type.


-- These are business terms which the regex should not return
characteristic
charge
chart

-- These are valid SQL but missing the length. These are what the search should return
char;
char ;      -- a space
char    ;   -- a tab
char,
char ,
char = 'lorem'
cast(9 as char)
convert(char, 9)

[char];
[char] ;        -- a space
[char]  ;   -- a tab
[char],
[char] ,
[char] = 'lorem'
cast(9 as [char])
convert([char], 9)

character
CAST(999 AS character)

char varying
character varying
CAST(999 AS char varying)
CAST(999 AS character varying)

Run Code Online (Sandbox Code Playgroud)

Zik*_*ato 6

正则表达式不是解决此问题的正确方法。总会有不可能/极难发现的误报。例如,多行注释块

相反,我建议使用SqlScriptDOM,它是一个 .NET 库,用于解析 T-SQL 语句并与其 Microsoft 提供的抽象语法树进行交互。

然后,您可以使用 .NET 应用程序或 PowerShell 来准确识别丢失的字符大小。

您可以在源代码管理或sys.sql_modules中迭代代码库,并将内容传递给 ScriptDOM 函数。

我从Dan Guzman 的博客借用了大部分代码

# this is the script to parse
$script = @"
-- These are all legal; the regex must not return these

declare @test char(20)

SET @test = CAST (@test AS char     (15 )) /* CHAR( 15 ) is intentional */
DECLARE @test2 char
(
    100
)

RAISERROR ('Char(max) is not valid',16 ,1) WITH nowait
SELECT CHAR(64) AS EmailSeparator -- this char(64) returns @

CREATE TABLE #UglyLegal
(
    a [char](9)
    , b [char] (9)          -- with a space
    , c [char]  (9)         -- with a tab
    , d [char]       (9)    -- tab space tab space
    , e [char]
(9)   
    , characteristic int
    , charge bit
    , chart bit
)


-- These are errors that will be returned
declare @a char;
GO
declare @a char ;      -- a space
GO
declare @a char    ;   -- a tab
GO
declare @a char, @b bit
GO
declare @a char , @b bit
GO
declare @a char = 'lorem'
GO
SELECT cast(9 as char)
GO
SELECT CONVERT(char, 9)

CREATE TABLE #CharFamily
(
    a char(10)
    , b char
    , c nchar(10)
    , d nchar
    , e varchar(MAX)
    , f varchar
    , g nvarchar(10)
    , h nvarchar
)

/* these examples are courtesy of Paul White https://sql.kiwi/ */
DECLARE @foo char varying = 'aaron';
SELECT foo = @foo, lenfoo = LEN(@foo);
SELECT LEN(CAST(REPLICATE('a', 255) AS char varying));
GO
DECLARE @foo "varchar" = 'aaron';
SELECT foo = @foo, lenfoo = LEN(@foo);
SELECT LEN(CAST(REPLICATE('a', 255) AS "varchar"));
"@

try {

    class MyVisitor: Microsoft.SqlServer.TransactSql.ScriptDom.TSqlConcreteFragmentVisitor {

        [void]Visit ([Microsoft.SqlServer.TransactSql.ScriptDom.SqlDataTypeReference] $fragment) {
            # Write-Host "$($fragment.SqlDataTypeOption) - size $(($fragment.Parameters[0]).Value) found at line $($fragment.StartLine), column $($fragment.StartColumn), length $($fragment.FragmentLength)" -ForegroundColor Yellow

            if (!($fragment.Parameters[0].Value) -and $fragment.SqlDataTypeOption -in ('Char', 'NChar', 'VarChar', 'NVarChar')) {
                Write-Host "Data type $($fragment.SqlDataTypeOption) is missing size at line $($fragment.StartLine), column $($fragment.StartColumn), length $($fragment.FragmentLength)" -ForegroundColor Red
            }
        }

    }
    
    # Create trusted NuGet package source, if needed
    $packageSource = Get-PackageSource | Where-Object { ($_.Location -EQ "https://www.nuget.org/api/v2") -and ($_.ProviderName -eq "NuGet") -and ($_.IsTrusted -eq $true) }
    if ($packageSource -eq $null) {
        Register-PackageSource NuGetV2 https://www.nuget.org/api/v2 -ProviderName NuGet -Trusted
    }

    # Install package, if needed.
    $tSqlScriptDomPackage = Install-Package Microsoft.SqlServer.TransactSql.ScriptDom -Source ($packageSource.Name) -Scope CurrentUser
    # Get package
    $tSqlScriptDomPackage = Get-Package -Name Microsoft.SqlServer.TransactSql.ScriptDom

    # Load Microsoft.SqlServer.TransactSql.ScriptDom.dll .NET framework assembly into app domain for use in PS scripts
    $tSqlScriptDomPackageFolderPath = [System.IO.Path]::GetDirectoryName($tSqlScriptDomPackage.Source)
    Add-Type -LiteralPath "$tSqlScriptDomPackageFolderPath\lib\net462\Microsoft.SqlServer.TransactSql.ScriptDom.dll"
    $parser = New-Object Microsoft.SqlServer.TransactSql.ScriptDom.TSql150Parser($true) # Find the correct compatibility level https://learn.microsoft.com/en-us/sql/t-sql/statements/alter-database-transact-sql-compatibility-level?view=sql-server-ver16#compatibility_level--160--150--140--130--120--110--100--90--80- */

    # create an ParseError collection for any errors returned by parser
    $parseErrors = New-Object System.Collections.Generic.List[Microsoft.SqlServer.TransactSql.ScriptDom.ParseError]

    # create a StringReader for the script for parsing
    $stringReader = New-Object System.IO.StringReader($script)

    # parse the script
    $tSqlFragment = $parser.Parse($stringReader, [ref]$parseErrors)

    # raise an exception if any parsing errors occur
    if ($parseErrors.Count -gt 0) {
        throw "$($parseErrors.Count) parsing error(s): $(($parseErrors | ConvertTo-Json))"
    }
        
    $visitor = [MyVisitor]::new()
    $tSqlFragment.Accept($visitor)

}
catch {
    throw
} 
Run Code Online (Sandbox Code Playgroud)

这里我使用的是TSql150Parser,它根据兼容性级别表匹配 SQL Server 2019

这将是输出

Data type Char is missing size at line 29, column 12, length 4
Data type Char is missing size at line 31, column 12, length 4
Data type Char is missing size at line 33, column 12, length 4
Data type Char is missing size at line 35, column 12, length 4
Data type Char is missing size at line 37, column 12, length 4
Data type Char is missing size at line 39, column 12, length 4
Data type Char is missing size at line 41, column 18, length 4
Data type Char is missing size at line 43, column 16, length 4
Data type Char is missing size at line 48, column 9, length 4
Data type NChar is missing size at line 50, column 9, length 5
Data type VarChar is missing size at line 52, column 9, length 7
Data type NVarChar is missing size at line 54, column 9, length 8
Data type VarChar is missing size at line 58, column 14, length 12
Data type VarChar is missing size at line 60, column 40, length 12
Data type VarChar is missing size at line 62, column 14, length 9
Data type VarChar is missing size at line 64, column 40, length 9
Run Code Online (Sandbox Code Playgroud)

  • 是的,您可以添加任何有用的信息,这并不意味着是一个完整的解决方案。 (2认同)