JSuder-xx / TSqlStrong

T-Sql type checker that detects improper joins, invalid null value operations, enumeration check constraint violations, incorrect use of temporary table at call site, more. On-line demo available.

Home Page:http://tsqlstrongdemo.azurewebsites.net/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Overview

T-SQL Strong is a T-SQL type checker/verifier that will

  • Verify the correctness of T-Sql code before it executes against a database.
  • Advanced type checking features include
    • Key Column Comparison - Protects against incorrect joins.
    • Null Type Checking - Guard against run-time errors of assigning a null value to a non-null column.
    • Check Constraints As Enumerations - Ensure only valid literals are assigned or compared against check constraint columns.
    • Insert Into Select Alias Matching - Protect against positional mistakes made with insert-select where you have many columns.
    • Verify the correctness of Temporary Table structure usage between different stored procedures.
  • Flow Typing. TSqlStrong can learn more about types (refinement) by analyzing flow control (IF/ELSEIF, CASE).

Try it out online

Examples

Key Column Comparison

create table Parent (
    id int not null, 
    constraint PK_Parent primary key clustered (id)
); 

create table Child (
    id int not null, 
    Parent_id int not null foreign key references Parent(id), 
    constraint PK_Child primary key clustered (id)
);

-- OK 
select * from Parent inner join Child on Child.Parent_ID = Parent.ID
-- Error
select * from Parent inner join Child on Child.ID = Parent.ID

Null Type Checking

declare @tableWithNulls table (val int null);
declare @tableWithoutNulls table (val int not null);

-- OK
insert into @tableWithNulls
select val from @tableWithoutNulls;

-- TypeError
insert into @tableWithoutNulls
select val from @tableWithNulls;

-- OK
insert into @tableWithoutNulls
select COALESCE(val, 0) from @tableWithNulls;

-- when declaring a variable and immediately setting it to a non-null
-- TSqlStrong knows the value is not null.
declare @someInt int = 10;

-- OK. The value was initialized to not null.
insert into @tableWithoutNulls values (@someInt);

declare @someOtherInt int;

-- ERROR. TSqlStrong saw that @someInt became null.
insert into @tableWithoutNulls values (@someOtherInt);

if (@someOtherInt is not null)
begin
    -- OK. TSqlStrong knows that @someOtherInt is not null at this point thanks to the check.
    insert into @tableWithoutNulls values (@someInt);
end;

Check Constraints Understood as Enumerations

Literal Values

create table Person
(
    gender varchar(40) 
        constraint ck_Person_Gender
        check (
            (gender = 'male')
            or (gender = 'female')
            or (gender = 'other') 
            or (gender = 'unknown')
        )
)

-- OK
insert into Person (gender)
values 
    ('female'),
    ('other'),
    ('male');

-- Type Error
insert into Person (gender)
values ('hello');

Case Determination

TSqlStrong can figure out all possible values returned by a CASE expression and can check that against your check constraint columns.

create table Person
(
    gender varchar(40) 
        constraint ck_Person_Gender
        check (
            (gender = 'male')
            or (gender = 'female')
            or (gender = 'other') 
            or (gender = 'unknown')
        )
)
GO

declare @someInt int;

-- OK
insert into Person (gender)
values
    (case 
        when @someInt is null then 'unknown'
        when @someInt > 0 then 'female'
        else 'male'                
    end);

-- Type Error due to 'apple' 
insert into Person (gender)
values
    (case 
        when @someInt is null then 'unknown'
        when @someInt > 0 then 'apple'
        else 'male'                
    end);

Insert Into Select - Alias Matching

-- Type Error: 
insert into TableOfManyColumns (A, B, C, D /*,...Z*/)
select
    1 as A,
    'Hi' as B,
    13 as C,
    'Bye' as E, -- Error: Expected D in this position    
    /* ...Z */
from
    SomeOtherWideTable

Temporary Table Structure Checking

create procedure dbo.SelectFirstAndLastNameFromTemp 
as
begin
    select firstName, lastName from #Temp;
end;
GO

create procedure dbo.CallsSelectFirstAndLastNameFromTemp
as
begin
    exec dbo.SelectFirstAndLastNameFromTemp; 
end;
GO

create procedure dbo.CreateWithFullNameAndExecuteIndirect
as
begin
    create table #Temp (fullName varchar(200));

    -- ERROR: When called from here the temp table does not have the correct structure.
    exec dbo.CallsSelectFirstAndLastNameFromTemp; 
end;
GO

create procedure dbo.CreateWithFirstAndLastNameAndExecuteIndirect
as
begin
    create table #Temp (firstName varchar(200), lastName varchar(200));
    
    -- OK: When called from here it is just fine.
    exec dbo.CallsSelectFirstAndLastNameFromTemp; 
end;
GO

Common Table Expressions

with Recurse (Num)
as
(
    select 0

    union all

    -- ERROR: Num is an Int from the base case
    select Num + 'apples' 
    from Recurse 
)
select top 10 Num from Recurse;

with Recurse (Num)
as
(
    select 0

    union all

    -- ERROR: NumX is not in the list of Recurse
    select NumX
    from Recurse 
)
select top 10 Num from Recurse;

-- ERROR: Column count mismatch between CTE and union.
with Recurse (Num)
as
(
    select 0, 1

    union all

    select Num, 20
    from Recurse 
)
select top 10 Num from Recurse;

-- GOOD!
with Recurse (Num)
as
(
    select 0

    union all

    select Num + 1
    from Recurse 
)
select top 10 Num from Recurse;

with NonRecursive (Num)
as
(
    select 0
    union all
    select 1
    union all
    select 2
)
select * 
from 
    NonRecursive 
where 
    -- ERROR: TSqlStrong figured out Num can only be 0, 1, or 2 so this comparison check makes no sense.
    Num = 4;

-- GOOD
with NonRecursive (Num)
as
(
    select 0
    union all
    select 1
    union all
    select 2
)
select * 
from 
    NonRecursive 
where 
    Num = 2;

Flow Typing

Null Checking

create table Person (weight int not null)
GO

declare @someInt int; -- NOTE: All variables are nullable by default.

insert into Person select @someInt; -- ERROR: Possible nullable assignment to non-null.
insert into Person select Coalesce(@someInt, 1); -- OK. 
insert into Person select IsNull(@someInt, 1); -- OK. 

if (@someInt is not null)
begin
    insert into Person select @someInt; -- OK
end
else 
begin
    insert into Person select @someInt; -- TYPE ERROR
end;

Enumerations

create table Person
(
    gender varchar(40) 
        not null
        constraint ck_Person_Gender
        check (
            (gender = 'male')
            or (gender = 'female')
            or (gender = 'other') 
            or (gender = 'unknown')
        )
)
GO

declare @randomText varchar(100);

insert into Person select coalesce(@randomText, 'unknown'); -- TYPE ERROR

if (@randomText = 'male') or (@randomText = 'female') or (@randomText = 'other')
begin
    -- OK. TSqlStrong knows randomText must be 'male', 'female', 'other'.
    insert into Person select @randomText; 
end;

Usage

The project is currently a proof of concept. See ROADMAP for where it is heading.

  • Fork or download the source from GitHub and build. TSqlStrong was developed in Visual Studio 2017 Community Edition.
  • Sql text can be verified using the command line interface TSqlStrongCli.
  • Example Sql can be found in \TSqlStrongDemoWebApp\wwwroot\sql
  • A build task for Visual Studio Code can be found in \TSqlStrongCli\VSCodeIntegration.

About

T-Sql type checker that detects improper joins, invalid null value operations, enumeration check constraint violations, incorrect use of temporary table at call site, more. On-line demo available.

http://tsqlstrongdemo.azurewebsites.net/

License:MIT License


Languages

Language:C# 88.3%Language:TypeScript 7.1%Language:PLSQL 1.8%Language:JavaScript 1.7%Language:PLpgSQL 1.0%Language:SQLPL 0.1%Language:CSS 0.0%