From fdd7bf1e20498a1e276b4b18ab4bda37a131f488 Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Mon, 18 Oct 2021 23:27:26 -0400 Subject: [PATCH 01/26] starting UndoTestDoublesTests.class.sql and related tests --- Source/Source.ssmssqlproj | 6 ++++++ Source/tSQLt.UndoTestDoubles.ssp.sql | 12 ++++++++++++ Tests/Tests.ssmssqlproj | 6 ++++++ Tests/UndoTestDoublesTests.class.sql | 22 ++++++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 Source/tSQLt.UndoTestDoubles.ssp.sql create mode 100644 Tests/UndoTestDoublesTests.class.sql diff --git a/Source/Source.ssmssqlproj b/Source/Source.ssmssqlproj index a618b037d..58740471f 100644 --- a/Source/Source.ssmssqlproj +++ b/Source/Source.ssmssqlproj @@ -636,6 +636,12 @@ tSQLt.Tests.view.sql + + + + + tSQLt.UndoTestDoubles.ssp.sql + diff --git a/Source/tSQLt.UndoTestDoubles.ssp.sql b/Source/tSQLt.UndoTestDoubles.ssp.sql new file mode 100644 index 000000000..23ed37043 --- /dev/null +++ b/Source/tSQLt.UndoTestDoubles.ssp.sql @@ -0,0 +1,12 @@ +IF OBJECT_ID('tSQLt.UndoTestDoubles') IS NOT NULL DROP PROCEDURE tSQLt.UndoTestDoubles; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.UndoTestDoubles +AS +BEGIN + RETURN; +END; +GO + + diff --git a/Tests/Tests.ssmssqlproj b/Tests/Tests.ssmssqlproj index 3a94750bf..2d4cb4b28 100644 --- a/Tests/Tests.ssmssqlproj +++ b/Tests/Tests.ssmssqlproj @@ -348,6 +348,12 @@ tSQLtclr_test.class.sql + + + + + UndoTestDoublesTests.class.sql + diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql new file mode 100644 index 000000000..dff9987db --- /dev/null +++ b/Tests/UndoTestDoublesTests.class.sql @@ -0,0 +1,22 @@ +EXEC tSQLt.NewTestClass 'UndoTestDoublesTests'; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a faked table] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable + ( + Id INT + ); + + DECLARE @OriginalObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleTable'); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + + EXEC tSQLt.UndoTestDoubles; + + DECLARE @RestoredObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleTable'); + EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; + +END; +GO + From 39e0f4344ab0d85c7c9e94d5e64b4d54a1dfb634 Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Mon, 18 Oct 2021 23:30:05 -0400 Subject: [PATCH 02/26] TODO! --- Tests/UndoTestDoublesTests.class.sql | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql index dff9987db..414bece67 100644 --- a/Tests/UndoTestDoublesTests.class.sql +++ b/Tests/UndoTestDoublesTests.class.sql @@ -19,4 +19,11 @@ BEGIN END; GO +/*-- +TODO +- ApplyConstraint +- ApplyTrigger +Also, just review all the code. + +--*/ From f4c4c91876c359a9c857c64316bea0c928506926 Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Wed, 20 Oct 2021 23:01:27 -0400 Subject: [PATCH 03/26] wrote a test which passes. ignored a test which fails. more work required. also BuildOrder.txt is yet again... important. --- Source/BuildOrder.txt | 3 ++- Source/tSQLt.UndoTestDoubles.ssp.sql | 6 ++++++ Tests/UndoTestDoublesTests.class.sql | 10 ++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Source/BuildOrder.txt b/Source/BuildOrder.txt index 301f92a2d..bd857f4ae 100644 --- a/Source/BuildOrder.txt +++ b/Source/BuildOrder.txt @@ -106,4 +106,5 @@ tSQLt.(at)tSQLt_RunOnlyOnHostPlatform.sfn.sql tSQLt.RemoveExternalAccessKey.ssp.sql tSQLt.InstallExternalAccessKey.ssp.sql tSQLt.Private_InstallationInfo.sfn.sql -tSQLt._Footer.sql +tSQLt.UndoTestDoubles.ssp.sql +tSQLt._Footer.sql \ No newline at end of file diff --git a/Source/tSQLt.UndoTestDoubles.ssp.sql b/Source/tSQLt.UndoTestDoubles.ssp.sql index 23ed37043..bc9746d69 100644 --- a/Source/tSQLt.UndoTestDoubles.ssp.sql +++ b/Source/tSQLt.UndoTestDoubles.ssp.sql @@ -5,6 +5,12 @@ GO CREATE PROCEDURE tSQLt.UndoTestDoubles AS BEGIN + SELECT * FROM tSQLt.Private_RenamedObjectLog + SELECT + QUOTENAME(SCHEMA_NAME(T.schema_id))+'.'+QUOTENAME(T.name) OriginalName, + OBJECT_SCHEMA_NAME(OTI.OrgTableObjectId), + OBJECT_NAME(OTI.OrgTableObjectId) + FROM sys.tables T CROSS APPLY tSQLt.Private_GetOriginalTableInfo(T.object_id) OTI RETURN; END; GO diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql index 414bece67..5e0bdd77f 100644 --- a/Tests/UndoTestDoublesTests.class.sql +++ b/Tests/UndoTestDoublesTests.class.sql @@ -1,5 +1,15 @@ EXEC tSQLt.NewTestClass 'UndoTestDoublesTests'; GO +CREATE PROCEDURE UndoTestDoublesTests.[test doesn't fail if there's no test double in the database] +AS +BEGIN + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.UndoTestDoubles; + +END; +GO CREATE PROCEDURE UndoTestDoublesTests.[test restores a faked table] AS BEGIN From 06fc8dc5a5d759876aa678c3e8d2048b69facdac Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Thu, 21 Oct 2021 22:43:26 -0400 Subject: [PATCH 04/26] some refactoring... --- Source/BuildOrder.txt | 2 ++ Source/tSQLt.Private_RenameObject.ssp.sql | 18 ++++++++++++++++++ ...Lt.Private_RenameObjectToUniqueName.ssp.sql | 10 +++------- Source/tSQLt.UndoSingleTestDouble.ssp.sql | 18 ++++++++++++++++++ Source/tSQLt.UndoTestDoubles.ssp.sql | 11 ++++++----- 5 files changed, 47 insertions(+), 12 deletions(-) create mode 100644 Source/tSQLt.Private_RenameObject.ssp.sql create mode 100644 Source/tSQLt.UndoSingleTestDouble.ssp.sql diff --git a/Source/BuildOrder.txt b/Source/BuildOrder.txt index bd857f4ae..1e60d5702 100644 --- a/Source/BuildOrder.txt +++ b/Source/BuildOrder.txt @@ -34,6 +34,7 @@ tSQLtCLR_CreateProcs.sql tSQLt.Private_PrepareFakeFunctionOutputTable.ssp.sql tSQLt.TableToText.ssp.sql tSQLt.Private_RenamedObjectLog.tbl.sql +tSQLt.Private_RenameObject.ssp.sql tSQLt.Private_MarkObjectBeforeRename.ssp.sql tSQLt.Private_RenameObjectToUniqueName.ssp.sql tSQLt.Private_RenameObjectToUniqueNameUsingObjectId.ssp.sql @@ -106,5 +107,6 @@ tSQLt.(at)tSQLt_RunOnlyOnHostPlatform.sfn.sql tSQLt.RemoveExternalAccessKey.ssp.sql tSQLt.InstallExternalAccessKey.ssp.sql tSQLt.Private_InstallationInfo.sfn.sql +tSQLt.UndoSingleTestDouble.ssp.sql tSQLt.UndoTestDoubles.ssp.sql tSQLt._Footer.sql \ No newline at end of file diff --git a/Source/tSQLt.Private_RenameObject.ssp.sql b/Source/tSQLt.Private_RenameObject.ssp.sql new file mode 100644 index 000000000..e464b7a52 --- /dev/null +++ b/Source/tSQLt.Private_RenameObject.ssp.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.Private_RenameObject') IS NOT NULL DROP PROCEDURE tSQLt.Private_RenameObject; +GO +---Build+ +CREATE PROCEDURE tSQLt.Private_RenameObject + @SchemaName NVARCHAR(MAX), + @ObjectName NVARCHAR(MAX), + @NewName NVARCHAR(MAX) +AS +BEGIN + DECLARE @RenameCmd NVARCHAR(MAX); + SET @RenameCmd = 'EXEC sp_rename ''' + + @SchemaName + '.' + @ObjectName + ''', ''' + + @NewName + ''',''OBJECT'';'; + + EXEC tSQLt.SuppressOutput @RenameCmd; +END; +---Build- +GO diff --git a/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql b/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql index 8b87f57ab..d34ec79fe 100644 --- a/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql +++ b/Source/tSQLt.Private_RenameObjectToUniqueName.ssp.sql @@ -8,16 +8,12 @@ CREATE PROCEDURE tSQLt.Private_RenameObjectToUniqueName AS BEGIN SET @NewName=tSQLt.Private::CreateUniqueObjectName(); - - DECLARE @RenameCmd NVARCHAR(MAX); - SET @RenameCmd = 'EXEC sp_rename ''' + - @SchemaName + '.' + @ObjectName + ''', ''' + - @NewName + ''',''OBJECT'';'; EXEC tSQLt.Private_MarkObjectBeforeRename @SchemaName, @ObjectName; - - EXEC tSQLt.SuppressOutput @RenameCmd; + EXEC tSQLt.Private_RenameObject @SchemaName, + @ObjectName, + @NewName; END; ---Build- diff --git a/Source/tSQLt.UndoSingleTestDouble.ssp.sql b/Source/tSQLt.UndoSingleTestDouble.ssp.sql new file mode 100644 index 000000000..281d8eeac --- /dev/null +++ b/Source/tSQLt.UndoSingleTestDouble.ssp.sql @@ -0,0 +1,18 @@ +IF OBJECT_ID('tSQLt.UndoSingleTestDouble') IS NOT NULL DROP PROCEDURE tSQLt.UndoSingleTestDouble; +GO +---Build+ +GO +CREATE PROCEDURE tSQLt.UndoSingleTestDouble + @SchemaName NVARCHAR(MAX), + @ObjectName NVARCHAR(MAX), + @OriginalName NVARCHAR(MAX) +AS +BEGIN + + + EXEC tSQLt.Private_RenameObject @SchemaName = @SchemaName, + @ObjectName = @ObjectName, + @NewName = @OriginalName; + +END; +GO diff --git a/Source/tSQLt.UndoTestDoubles.ssp.sql b/Source/tSQLt.UndoTestDoubles.ssp.sql index bc9746d69..d395b332f 100644 --- a/Source/tSQLt.UndoTestDoubles.ssp.sql +++ b/Source/tSQLt.UndoTestDoubles.ssp.sql @@ -5,12 +5,13 @@ GO CREATE PROCEDURE tSQLt.UndoTestDoubles AS BEGIN - SELECT * FROM tSQLt.Private_RenamedObjectLog SELECT - QUOTENAME(SCHEMA_NAME(T.schema_id))+'.'+QUOTENAME(T.name) OriginalName, - OBJECT_SCHEMA_NAME(OTI.OrgTableObjectId), - OBJECT_NAME(OTI.OrgTableObjectId) - FROM sys.tables T CROSS APPLY tSQLt.Private_GetOriginalTableInfo(T.object_id) OTI + Id, + ObjectId, + OBJECT_SCHEMA_NAME(L.ObjectId) SchemaName, + OBJECT_NAME(L.ObjectId) CurrentName, + OriginalName + FROM tSQLt.Private_RenamedObjectLog L RETURN; END; GO From d5547ed12732afeb8f4b224cd60cc29a76aa34dd Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Fri, 22 Oct 2021 23:07:55 -0400 Subject: [PATCH 05/26] add tests to DropClassTests.class.sql to ensure that all object types can be dropped. --> in progress, start here <-- --- Source/Source.ssmssqlproj | 12 +++++++ Source/tSQLt.DropClass.ssp.sql | 8 +++-- Tests/DropClassTests.class.sql | 63 ++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/Source/Source.ssmssqlproj b/Source/Source.ssmssqlproj index 58740471f..71d9d67ab 100644 --- a/Source/Source.ssmssqlproj +++ b/Source/Source.ssmssqlproj @@ -438,6 +438,12 @@ tSQLt.Private_RemoveSchemaBoundReferences.ssp.sql + + + + + tSQLt.Private_RenameObject.ssp.sql + @@ -636,6 +642,12 @@ tSQLt.Tests.view.sql + + + + + tSQLt.UndoSingleTestDouble.ssp.sql + diff --git a/Source/tSQLt.DropClass.ssp.sql b/Source/tSQLt.DropClass.ssp.sql index fdf99c496..4c75f5085 100644 --- a/Source/tSQLt.DropClass.ssp.sql +++ b/Source/tSQLt.DropClass.ssp.sql @@ -30,11 +30,11 @@ BEGIN SELECT 10, 'DROP ' + CASE type WHEN 'P' THEN 'PROCEDURE' - WHEN 'PC' THEN 'PROCEDURE' + --WHEN 'PC' THEN 'PROCEDURE' WHEN 'U' THEN 'TABLE' - WHEN 'IF' THEN 'FUNCTION' + --WHEN 'IF' THEN 'FUNCTION' WHEN 'TF' THEN 'FUNCTION' - WHEN 'FN' THEN 'FUNCTION' + --WHEN 'FN' THEN 'FUNCTION' WHEN 'FT' THEN 'FUNCTION' WHEN 'V' THEN 'VIEW' END + @@ -42,6 +42,7 @@ BEGIN name + ';' FROM ObjectInfo +/* UNION ALL SELECT 20, 'DROP TYPE ' + @@ -54,6 +55,7 @@ BEGIN name + ';' FROM XMLSchemaInfo +*/ UNION ALL SELECT 10000,'DROP SCHEMA ' + QUOTENAME(name) +';' FROM sys.schemas diff --git a/Tests/DropClassTests.class.sql b/Tests/DropClassTests.class.sql index acd8fd52e..738f9083e 100644 --- a/Tests/DropClassTests.class.sql +++ b/Tests/DropClassTests.class.sql @@ -127,6 +127,69 @@ CREATE FUNCTION MyTestClass.AClrTvf(@p1 NVARCHAR(MAX), @p2 NVARCHAR(MAX)) END END; GO + --CASE type *WHEN 'P' THEN 'PROCEDURE' + -- WHEN 'PC' THEN 'PROCEDURE' + -- *WHEN 'U' THEN 'TABLE' + -- WHEN 'IF' THEN 'FUNCTION' + -- *WHEN 'TF' THEN 'FUNCTION' + -- WHEN 'FN' THEN 'FUNCTION' + -- *WHEN 'FT' THEN 'FUNCTION' + -- *WHEN 'V' THEN 'VIEW' +/*-- +EXEC tSQLt.Run DropClassTests +--*/ +CREATE PROC DropClassTests.[test removes SSPs] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE PROC MyTestClass.P AS RETURN;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes VIEWs] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE VIEW MyTestClass.V AS SELECT 0 X;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes CLR SSPs] +AS +BEGIN + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE PROC MyTestClass.CLRProcedure @expectedCommand NVARCHAR(MAX), @actualCommand NVARCHAR(MAX) AS EXTERNAL NAME tSQLtCLR.[tSQLtCLR.StoredProcedures].AssertResultSetsHaveSameMetaData;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO + + From 646f3cac469ee549c631d979177b8c517e682ecd Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Sat, 23 Oct 2021 14:10:56 -0400 Subject: [PATCH 06/26] wrote enough tests for DropClassTests.class.sql --- Source/tSQLt.DropClass.ssp.sql | 8 +-- Tests/DropClassTests.class.sql | 117 ++++++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 7 deletions(-) diff --git a/Source/tSQLt.DropClass.ssp.sql b/Source/tSQLt.DropClass.ssp.sql index 4c75f5085..fdf99c496 100644 --- a/Source/tSQLt.DropClass.ssp.sql +++ b/Source/tSQLt.DropClass.ssp.sql @@ -30,11 +30,11 @@ BEGIN SELECT 10, 'DROP ' + CASE type WHEN 'P' THEN 'PROCEDURE' - --WHEN 'PC' THEN 'PROCEDURE' + WHEN 'PC' THEN 'PROCEDURE' WHEN 'U' THEN 'TABLE' - --WHEN 'IF' THEN 'FUNCTION' + WHEN 'IF' THEN 'FUNCTION' WHEN 'TF' THEN 'FUNCTION' - --WHEN 'FN' THEN 'FUNCTION' + WHEN 'FN' THEN 'FUNCTION' WHEN 'FT' THEN 'FUNCTION' WHEN 'V' THEN 'VIEW' END + @@ -42,7 +42,6 @@ BEGIN name + ';' FROM ObjectInfo -/* UNION ALL SELECT 20, 'DROP TYPE ' + @@ -55,7 +54,6 @@ BEGIN name + ';' FROM XMLSchemaInfo -*/ UNION ALL SELECT 10000,'DROP SCHEMA ' + QUOTENAME(name) +';' FROM sys.schemas diff --git a/Tests/DropClassTests.class.sql b/Tests/DropClassTests.class.sql index 738f9083e..15b9a3690 100644 --- a/Tests/DropClassTests.class.sql +++ b/Tests/DropClassTests.class.sql @@ -40,13 +40,15 @@ BEGIN END END; GO -CREATE PROC DropClassTests.[test removes UDDTs after tables] +CREATE PROC DropClassTests.[test removes UDDTs after the objects that use them] AS BEGIN EXEC('CREATE SCHEMA MyTestClass;'); EXEC('CREATE TYPE MyTestClass.UDT FROM INT;'); EXEC('CREATE TABLE MyTestClass.tbl(i MyTestClass.UDT);'); + EXEC('CREATE PROCEDURE MyTestClass.ssp @i MyTestClass.UDT AS RETURN;'); + EXEC('CREATE FUNCTION MyTestClass.[IF](@i MyTestClass.UDT) RETURNS TABLE AS RETURN SELECT 0 X;'); EXEC tSQLt.ExpectNoException; @@ -128,13 +130,57 @@ CREATE FUNCTION MyTestClass.AClrTvf(@p1 NVARCHAR(MAX), @p2 NVARCHAR(MAX)) END; GO --CASE type *WHEN 'P' THEN 'PROCEDURE' - -- WHEN 'PC' THEN 'PROCEDURE' + -- *WHEN 'PC' THEN 'PROCEDURE' -- *WHEN 'U' THEN 'TABLE' -- WHEN 'IF' THEN 'FUNCTION' -- *WHEN 'TF' THEN 'FUNCTION' -- WHEN 'FN' THEN 'FUNCTION' -- *WHEN 'FT' THEN 'FUNCTION' -- *WHEN 'V' THEN 'VIEW' + -- *XML + -- *UDDT +/* +Object type: + AF = Aggregate function (CLR) +- C = CHECK constraint +- D = DEFAULT (constraint or stand-alone) +- F = FOREIGN KEY constraint ++ FN = SQL scalar function + FS = Assembly (CLR) scalar-function ++ FT = Assembly (CLR) table-valued function ++ IF = SQL inline table-valued function + IT = Internal table ++ P = SQL Stored Procedure ++ PC = Assembly (CLR) stored-procedure +- PG = Plan guide +- PK = PRIMARY KEY constraint +? R = Rule (old-style, stand-alone) + RF = Replication-filter-procedure +- S = System base table + SN = Synonym + SO = Sequence object ++ U = Table (user-defined) ++ V = View +- EC = Edge constraint + +Applies to: SQL Server 2012 (11.x) and later. + SQ = Service queue + - TA = Assembly (CLR) DML trigger + + TF = SQL table-valued-function + - TR = SQL DML trigger + TT = Table type + - UQ = UNIQUE constraint + ? X = Extended stored procedure + +Applies to: SQL Server 2014 (12.x) and later, Azure SQL Database, Azure Synapse Analytics, Analytics Platform System (PDW). + ? ST = STATS_TREE + +Applies to: SQL Server 2016 (13.x) and later, Azure SQL Database, Azure Synapse Analytics, Analytics Platform System (PDW). + ET = External Table + +Also think about schema bound objects (an exercise in sorting?? because they need to be dropped in the correct order so that you don't drop parent objects before the child objects) +*/ + /*-- EXEC tSQLt.Run DropClassTests --*/ @@ -189,7 +235,74 @@ BEGIN END END; GO +CREATE PROC DropClassTests.[test removes Tables] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE TABLE MyTestClass.U(i INT);'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes Inline Table-Valued Functions] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE FUNCTION MyTestClass.[IF]() RETURNS TABLE AS RETURN SELECT 0 X;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes Multi-Statement Table-Valued Functions] +AS +BEGIN + + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE FUNCTION MyTestClass.[TF]() RETURNS @T TABLE (i INT) BEGIN RETURN; END;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO +CREATE PROC DropClassTests.[test removes Scalar Functions] +AS +BEGIN + EXEC('CREATE SCHEMA MyTestClass;'); + EXEC('CREATE FUNCTION MyTestClass.[FN]() RETURNS INT BEGIN RETURN 0; END;'); + + EXEC tSQLt.ExpectNoException; + + EXEC tSQLt.DropClass 'MyTestClass'; + + IF(SCHEMA_ID('MyTestClass') IS NOT NULL) + BEGIN + EXEC tSQLt.Fail 'DropClass did not drop MyTestClass'; + END +END; +GO From 6741b90a81c46f0147a7ecca9349f8c8e5c93461 Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Sat, 23 Oct 2021 23:28:16 -0400 Subject: [PATCH 07/26] refactored tSQLt.DropClass.ssp.sql to use tSQLt.Private_GetDropItemCmd.sfn.sql, but this breaks the facade build. We'll need to figure out how to inline the function. (tomorrow) --- Source/BuildOrder.txt | 1 + Source/Source.ssmssqlproj | 6 ++ Source/tSQLt.DropClass.ssp.sql | 63 ++++++++++----------- Source/tSQLt.Private_GetDropItemCmd.sfn.sql | 31 ++++++++++ Tests/DropClassTests.class.sql | 2 +- 5 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 Source/tSQLt.Private_GetDropItemCmd.sfn.sql diff --git a/Source/BuildOrder.txt b/Source/BuildOrder.txt index 1e60d5702..a30da0d31 100644 --- a/Source/BuildOrder.txt +++ b/Source/BuildOrder.txt @@ -2,6 +2,7 @@ tSQLt._Header.sql ../Build/temp/tSQLtBuild/TempDropClass.sql tSQLt.schema.sql tSQLt.TestClass.user.sql +tSQLt.Private_GetDropItemCmd.sfn.sql tSQLt.DropClass.ssp.sql tSQLt.Uninstall.ssp.sql tSQLt.TestClasses.view.sql diff --git a/Source/Source.ssmssqlproj b/Source/Source.ssmssqlproj index 71d9d67ab..30a1c9676 100644 --- a/Source/Source.ssmssqlproj +++ b/Source/Source.ssmssqlproj @@ -318,6 +318,12 @@ tSQLt.Private_GetDefaultConstraintDefinition.sfn.sql + + + + + tSQLt.Private_GetDropItemCmd.sfn.sql + diff --git a/Source/tSQLt.DropClass.ssp.sql b/Source/tSQLt.DropClass.ssp.sql index fdf99c496..132faec61 100644 --- a/Source/tSQLt.DropClass.ssp.sql +++ b/Source/tSQLt.DropClass.ssp.sql @@ -7,62 +7,57 @@ AS BEGIN DECLARE @Cmd NVARCHAR(MAX); - WITH ObjectInfo(name, type) AS + WITH ObjectInfo(FullName, ItemType) AS ( - SELECT QUOTENAME(SCHEMA_NAME(O.schema_id))+'.'+QUOTENAME(O.name) , O.type + SELECT + QUOTENAME(SCHEMA_NAME(O.schema_id))+'.'+QUOTENAME(O.name), + O.type FROM sys.objects AS O WHERE O.schema_id = SCHEMA_ID(@ClassName) ), - TypeInfo(name) AS + TypeInfo(FullName, ItemType) AS ( - SELECT QUOTENAME(SCHEMA_NAME(T.schema_id))+'.'+QUOTENAME(T.name) + SELECT + QUOTENAME(SCHEMA_NAME(T.schema_id))+'.'+QUOTENAME(T.name), + 'type' FROM sys.types AS T WHERE T.schema_id = SCHEMA_ID(@ClassName) ), - XMLSchemaInfo(name) AS + XMLSchemaInfo(FullName, ItemType) AS ( - SELECT QUOTENAME(SCHEMA_NAME(XSC.schema_id))+'.'+QUOTENAME(XSC.name) + SELECT + QUOTENAME(SCHEMA_NAME(XSC.schema_id))+'.'+QUOTENAME(XSC.name), + 'xml_schema_collections' FROM sys.xml_schema_collections AS XSC WHERE XSC.schema_id = SCHEMA_ID(@ClassName) ), - DropStatements(no,cmd) AS + SchemaInfo(FullName, ItemType) AS ( - SELECT 10, - 'DROP ' + - CASE type WHEN 'P' THEN 'PROCEDURE' - WHEN 'PC' THEN 'PROCEDURE' - WHEN 'U' THEN 'TABLE' - WHEN 'IF' THEN 'FUNCTION' - WHEN 'TF' THEN 'FUNCTION' - WHEN 'FN' THEN 'FUNCTION' - WHEN 'FT' THEN 'FUNCTION' - WHEN 'V' THEN 'VIEW' - END + - ' ' + - name + - ';' + SELECT + QUOTENAME(S.name), + 'schema' + FROM sys.schemas AS S + WHERE S.schema_id = SCHEMA_ID(PARSENAME(@ClassName,1)) + ), + DropStatements(no,FullName,ItemType) AS + ( + SELECT 10, FullName, ItemType FROM ObjectInfo UNION ALL - SELECT 20, - 'DROP TYPE ' + - name + - ';' + SELECT 20, FullName, ItemType FROM TypeInfo UNION ALL - SELECT 30, - 'DROP XML SCHEMA COLLECTION ' + - name + - ';' + SELECT 30, FullName, ItemType FROM XMLSchemaInfo UNION ALL - SELECT 10000,'DROP SCHEMA ' + QUOTENAME(name) +';' - FROM sys.schemas - WHERE schema_id = SCHEMA_ID(PARSENAME(@ClassName,1)) + SELECT 10000, FullName, ItemType + FROM SchemaInfo ), StatementBlob(xml)AS ( - SELECT cmd [text()] - FROM DropStatements + SELECT GDIC.cmd [text()] + FROM DropStatements DS + CROSS APPLY tSQLt.Private_GetDropItemCmd(FullName, ItemType) GDIC ORDER BY no FOR XML PATH(''), TYPE ) diff --git a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql new file mode 100644 index 000000000..6fed4d8e3 --- /dev/null +++ b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql @@ -0,0 +1,31 @@ +IF OBJECT_ID('tSQLt.Private_GetDropItemCmd') IS NOT NULL DROP PROCEDURE tSQLt.Private_GetDropItemCmd; +GO +---Build+ +GO +CREATE FUNCTION tSQLt.Private_GetDropItemCmd +( + @FullName NVARCHAR(MAX), + @ItemType NVARCHAR(MAX) +) +RETURNS TABLE +AS +RETURN +SELECT + 'DROP ' + + CASE @ItemType + WHEN 'P' THEN 'PROCEDURE' + WHEN 'PC' THEN 'PROCEDURE' + WHEN 'U' THEN 'TABLE' + WHEN 'IF' THEN 'FUNCTION' + WHEN 'TF' THEN 'FUNCTION' + WHEN 'FN' THEN 'FUNCTION' + WHEN 'FT' THEN 'FUNCTION' + WHEN 'V' THEN 'VIEW' + WHEN 'type' THEN 'TYPE' + WHEN 'xml_schema_collection' THEN 'XML SCHEMA COLLECTION' + WHEN 'schema' THEN 'SCHEMA' + END+ + ' ' + + @FullName + + ';' AS cmd +GO \ No newline at end of file diff --git a/Tests/DropClassTests.class.sql b/Tests/DropClassTests.class.sql index 15b9a3690..cafb40732 100644 --- a/Tests/DropClassTests.class.sql +++ b/Tests/DropClassTests.class.sql @@ -181,7 +181,7 @@ Applies to: SQL Server 2016 (13.x) and later, Azure SQL Database, Azure Synapse Also think about schema bound objects (an exercise in sorting?? because they need to be dropped in the correct order so that you don't drop parent objects before the child objects) */ -/*-- +/*-- random line because someone can't use ctrl-9 through zoom EXEC tSQLt.Run DropClassTests --*/ CREATE PROC DropClassTests.[test removes SSPs] From 7d33ded637b6c4c13bfd3dc06da163ebfc9d218a Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Sun, 24 Oct 2021 13:40:37 -0400 Subject: [PATCH 08/26] =?UTF-8?q?Preparing=20for=20a=20refactor=20with=20l?= =?UTF-8?q?ots=20of=20"suggestions"=20on=20how=20exactly=20I=20should=20do?= =?UTF-8?q?=20this...=20(=E2=97=94=5F=E2=97=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Build/tSQLt.build.xml | 32 +++++++++++++++++++++ Source/tSQLt.Private_GetDropItemCmd.sfn.sql | 2 ++ 2 files changed, 34 insertions(+) diff --git a/Build/tSQLt.build.xml b/Build/tSQLt.build.xml index bebdaa837..839a4e59a 100644 --- a/Build/tSQLt.build.xml +++ b/Build/tSQLt.build.xml @@ -214,6 +214,38 @@ + diff --git a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql index 6fed4d8e3..99a43ac66 100644 --- a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql +++ b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql @@ -10,6 +10,7 @@ CREATE FUNCTION tSQLt.Private_GetDropItemCmd RETURNS TABLE AS RETURN +/*START*/ SELECT 'DROP ' + CASE @ItemType @@ -28,4 +29,5 @@ SELECT ' ' + @FullName + ';' AS cmd +/*END*/ GO \ No newline at end of file From 4ec7a9ad5a7c51e7006123db7f0ea1e1b4df193d Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Mon, 25 Oct 2021 23:02:46 -0400 Subject: [PATCH 09/26] moving dropclass target to ps1. exploring multi-line regex in powershell without much joy. --- Build/CreateDropClassStatement.ps1 | 78 ++++++++++++++++++++++++++++++ Build/tSQLt.build.xml | 78 ++---------------------------- 2 files changed, 81 insertions(+), 75 deletions(-) create mode 100644 Build/CreateDropClassStatement.ps1 diff --git a/Build/CreateDropClassStatement.ps1 b/Build/CreateDropClassStatement.ps1 new file mode 100644 index 000000000..cb88bf4e9 --- /dev/null +++ b/Build/CreateDropClassStatement.ps1 @@ -0,0 +1,78 @@ +$scriptPath = $MyInvocation.MyCommand.Path; +$invocationDir = Split-Path $scriptPath; +$buildPath = $invocationDir +'/'; +$tempPath = $invocationDir + '/temp/tSQLtBuild/'; +$outputPath = $invocationDir + '/output/tSQLtBuild/'; +$sourcePath = $invocationDir + '/../Source/'; +$testUtilPath = $invocationDir + '/../TestUtil/'; + +.($buildPath+"CommonFunctionsAndMethods.ps1"); + +Log-Output '<#--=======================================================================-->' +Log-Output '' +Log-Output '<#--=======================================================================-->' + +.\BuildHelper.exe ($sourcePath+"tSQLtDropBuildOrder.txt") ($tempPath+"TempDropClass.sql") "---Build" +<# +TODO --> Test this: Empty File TempDropClass.sql file should throw an error +TODO --> Test this: If the $tempPath does not exist, BuildHelper.exe seems to currently throw an error, but does that stop the build? +TODO --> Test this: If the $sourcePath does not exist, BuildHelper.exe seems to currently throw an error, but does that stop the build? +#> +$fileContent = Get-Content -path ($tempPath+"TempDropClass.sql"); + +if($fileContent.length -eq 0) { + throw "Length of fileContent is zero"; +} + +$fileContent -replace '^(?:[\t ]*(?:\r?\n|\r))+', ''; + +<# +if ($LoginTrimmed -match '((.*[-]U)|(.*[-]P))+.*'){ + $AuthenticationString = $LoginTrimmed -replace '^((\s*([-]U\s+)(?\w+)\s*)|(\s*([-]P\s+)(?\S+)\s*))+$', 'User Id=${user};Password="${password}"' +} +#> + +<# We need to replace this target with a exec target which calls a ps1 to do this work. +In addition, we will add a replace which will "in line" the relevant text of tSQLt.Private_GetDropItemCmd. + +"suggested (◔_◔)" regex: tSQLt.Private_GetDropItemCmd(FullName, ItemType) +'tSQLt\s*.\s*Private_GetDropItemCmd\s*\(\s*([^,]*)\s*,\s*([^)]*)\s*\)' + +It should look like this: + +DECLARE @ClassName NVARCHAR(MAX) ='tSQLt';BEGIN DECLARE @Cmd NVARCHAR(MAX); WITH ObjectInfo(FullName, ItemType) AS ( SELECT QUOTENAME(SCHEMA_NAME(O.schema_id))+'.'+QUOTENAME(O.name), O.type FROM sys.objects AS O WHERE O.schema_id = SCHEMA_ID(@ClassName) ), TypeInfo(FullName, ItemType) AS ( SELECT QUOTENAME(SCHEMA_NAME(T.schema_id))+'.'+QUOTENAME(T.name), 'type' FROM sys.types AS T WHERE T.schema_id = SCHEMA_ID(@ClassName) ), XMLSchemaInfo(FullName, ItemType) AS ( SELECT QUOTENAME(SCHEMA_NAME(XSC.schema_id))+'.'+QUOTENAME(XSC.name), 'xml_schema_collections' FROM sys.xml_schema_collections AS XSC WHERE XSC.schema_id = SCHEMA_ID(@ClassName) ), SchemaInfo(FullName, ItemType) AS ( SELECT QUOTENAME(S.name), 'schema' FROM sys.schemas AS S WHERE S.schema_id = SCHEMA_ID(PARSENAME(@ClassName,1)) ), DropStatements(no,FullName,ItemType) AS ( SELECT 10, FullName, ItemType FROM ObjectInfo UNION ALL SELECT 20, FullName, ItemType FROM TypeInfo UNION ALL SELECT 30, FullName, ItemType FROM XMLSchemaInfo UNION ALL SELECT 10000, FullName, ItemType FROM SchemaInfo ), StatementBlob(xml)AS ( SELECT GDIC.cmd [text()] FROM DropStatements DS CROSS APPLY +( +SELECT + 'DROP ' + + CASE ItemType + WHEN 'P' THEN 'PROCEDURE' + WHEN 'PC' THEN 'PROCEDURE' + WHEN 'U' THEN 'TABLE' + WHEN 'IF' THEN 'FUNCTION' + WHEN 'TF' THEN 'FUNCTION' + WHEN 'FN' THEN 'FUNCTION' + WHEN 'FT' THEN 'FUNCTION' + WHEN 'V' THEN 'VIEW' + WHEN 'type' THEN 'TYPE' + WHEN 'xml_schema_collection' THEN 'XML SCHEMA COLLECTION' + WHEN 'schema' THEN 'SCHEMA' + END+ + ' ' + + FullName + + ';' AS cmd +) +GDIC ORDER BY no FOR XML PATH(''), TYPE ) SELECT @Cmd = xml.value('/', 'NVARCHAR(MAX)') FROM StatementBlob; EXEC(@Cmd); END; + +#> + + + + + + + + + Log-Output '<#--=======================================================================-->' + Log-Output '' + Log-Output '<#--=======================================================================-->' + \ No newline at end of file diff --git a/Build/tSQLt.build.xml b/Build/tSQLt.build.xml index 839a4e59a..b7541a9da 100644 --- a/Build/tSQLt.build.xml +++ b/Build/tSQLt.build.xml @@ -214,82 +214,10 @@ - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 6ebbfa538f661e9dd60b7befa9a1beb1ccd8affe Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Tue, 26 Oct 2021 23:12:51 -0400 Subject: [PATCH 10/26] fighting with powershell and regex (pl. regrets) --- Build/CommonFunctionsAndMethods.ps1 | 14 ++++-- Build/CreateDropClassStatement.ps1 | 53 ++++++++++++++++----- Source/tSQLt.DropClass.ssp.sql | 4 ++ Source/tSQLt.Private_GetDropItemCmd.sfn.sql | 6 ++- 4 files changed, 58 insertions(+), 19 deletions(-) diff --git a/Build/CommonFunctionsAndMethods.ps1 b/Build/CommonFunctionsAndMethods.ps1 index 5883e6683..7dac71135 100644 --- a/Build/CommonFunctionsAndMethods.ps1 +++ b/Build/CommonFunctionsAndMethods.ps1 @@ -201,16 +201,20 @@ Function Remove-ResourceGroup{ Function Get-SnipContent { [CmdletBinding()] param ( - [Parameter(Mandatory=$true)][AllowEmptyString()][String[]] $searchArray, + [Parameter(Mandatory=$true,ValueFromPipeline=$true)][AllowEmptyString()][string[]]$searchArray, [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $startSnipPattern, [Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()][String] $endSnipPattern ) - $outputOn = $false; - ( + begin { + $outputOn = $false; + }; + process { $searchArray | ForEach-Object { if($_ -eq $endSnipPattern) { $outputOn = $false }; if($outputOn) { $_ }; if($_ -eq $startSnipPattern) { $outputOn = $true }; - } - ); + } + }; + end { + }; } \ No newline at end of file diff --git a/Build/CreateDropClassStatement.ps1 b/Build/CreateDropClassStatement.ps1 index cb88bf4e9..a23d3f0af 100644 --- a/Build/CreateDropClassStatement.ps1 +++ b/Build/CreateDropClassStatement.ps1 @@ -12,19 +12,53 @@ Log-Output '<#--================================================================ Log-Output '' Log-Output '<#--=======================================================================-->' -.\BuildHelper.exe ($sourcePath+"tSQLtDropBuildOrder.txt") ($tempPath+"TempDropClass.sql") "---Build" +$DropClassFile = Get-Content -path ($sourcePath+"tSQLt.DropClass.ssp.sql"); +$GetDropItemCmdFile = Get-Content -path ($sourcePath+"tSQLt.Private_GetDropItemCmd.sfn.sql"); +$OutputFile = $tempPath+"TempDropClass.sql"; + +$DropClassSnip = ($DropClassFile | Get-SnipContent -startSnipPattern "/*SnipStart: CreateDropClassStatement.ps1*/" -endSnipPattern "/*SnipEnd: CreateDropClassStatement.ps1*/"); +$DropItemSnip = ($GetDropItemCmdFile | Get-SnipContent -startSnipPattern "/*SnipStart: CreateDropClassStatement.ps1*/" -endSnipPattern "/*SnipEnd: CreateDropClassStatement.ps1*/"); + +$CaptureName = $true; +$VariableNames = @(); +$DropItemSnip | ForEach-Object{ + if($CaptureName){ + if($_.trim() -match '^(@\w+).*'){ + $VariableNames += $Matches[1]; + + } + else { + $CaptureName = $false + } + } + else{ + $s=$_;for($i = 0;$i -lt $VariableNames.count;$i++){$s=$s -replace $VariableNamess[$i], ("$"+($i+1)) };$s + } +} + +$VariableNames; + <# TODO --> Test this: Empty File TempDropClass.sql file should throw an error TODO --> Test this: If the $tempPath does not exist, BuildHelper.exe seems to currently throw an error, but does that stop the build? TODO --> Test this: If the $sourcePath does not exist, BuildHelper.exe seems to currently throw an error, but does that stop the build? #> -$fileContent = Get-Content -path ($tempPath+"TempDropClass.sql"); +<# +# Read file and remove all empty lines +$fileContent = (Get-Content -path ($tempPath+"TempDropClass.sql")).trim() | Where-Object {$_ -ne "" -and $_ -notmatch "^GO(\s.*)?"}; + +# Make sure that the file is not empty if($fileContent.length -eq 0) { throw "Length of fileContent is zero"; } -$fileContent -replace '^(?:[\t ]*(?:\r?\n|\r))+', ''; +# trim lines and jam them all together with a space in between +$singleLineContent = $fileContent -join " "; + + + +#> <# if ($LoginTrimmed -match '((.*[-]U)|(.*[-]P))+.*'){ @@ -40,11 +74,13 @@ In addition, we will add a replace which will "in line" the relevant text of tSQ It should look like this: + + DECLARE @ClassName NVARCHAR(MAX) ='tSQLt';BEGIN DECLARE @Cmd NVARCHAR(MAX); WITH ObjectInfo(FullName, ItemType) AS ( SELECT QUOTENAME(SCHEMA_NAME(O.schema_id))+'.'+QUOTENAME(O.name), O.type FROM sys.objects AS O WHERE O.schema_id = SCHEMA_ID(@ClassName) ), TypeInfo(FullName, ItemType) AS ( SELECT QUOTENAME(SCHEMA_NAME(T.schema_id))+'.'+QUOTENAME(T.name), 'type' FROM sys.types AS T WHERE T.schema_id = SCHEMA_ID(@ClassName) ), XMLSchemaInfo(FullName, ItemType) AS ( SELECT QUOTENAME(SCHEMA_NAME(XSC.schema_id))+'.'+QUOTENAME(XSC.name), 'xml_schema_collections' FROM sys.xml_schema_collections AS XSC WHERE XSC.schema_id = SCHEMA_ID(@ClassName) ), SchemaInfo(FullName, ItemType) AS ( SELECT QUOTENAME(S.name), 'schema' FROM sys.schemas AS S WHERE S.schema_id = SCHEMA_ID(PARSENAME(@ClassName,1)) ), DropStatements(no,FullName,ItemType) AS ( SELECT 10, FullName, ItemType FROM ObjectInfo UNION ALL SELECT 20, FullName, ItemType FROM TypeInfo UNION ALL SELECT 30, FullName, ItemType FROM XMLSchemaInfo UNION ALL SELECT 10000, FullName, ItemType FROM SchemaInfo ), StatementBlob(xml)AS ( SELECT GDIC.cmd [text()] FROM DropStatements DS CROSS APPLY ( SELECT 'DROP ' + - CASE ItemType + CASE @ItemType WHEN 'P' THEN 'PROCEDURE' WHEN 'PC' THEN 'PROCEDURE' WHEN 'U' THEN 'TABLE' @@ -58,20 +94,13 @@ SELECT WHEN 'schema' THEN 'SCHEMA' END+ ' ' + - FullName + + @FullName + ';' AS cmd ) GDIC ORDER BY no FOR XML PATH(''), TYPE ) SELECT @Cmd = xml.value('/', 'NVARCHAR(MAX)') FROM StatementBlob; EXEC(@Cmd); END; #> - - - - - - - Log-Output '<#--=======================================================================-->' Log-Output '' Log-Output '<#--=======================================================================-->' diff --git a/Source/tSQLt.DropClass.ssp.sql b/Source/tSQLt.DropClass.ssp.sql index 132faec61..cf31f9ed8 100644 --- a/Source/tSQLt.DropClass.ssp.sql +++ b/Source/tSQLt.DropClass.ssp.sql @@ -5,6 +5,9 @@ CREATE PROCEDURE tSQLt.DropClass @ClassName NVARCHAR(MAX) AS BEGIN +---Build- +/*SnipStart: CreateDropClassStatement.ps1*/ +---Build+ DECLARE @Cmd NVARCHAR(MAX); WITH ObjectInfo(FullName, ItemType) AS @@ -67,4 +70,5 @@ BEGIN EXEC(@Cmd); END; ---Build- +/*SnipEnd: CreateDropClassStatement.ps1*/ GO diff --git a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql index 99a43ac66..5fcd8ed73 100644 --- a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql +++ b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql @@ -4,13 +4,15 @@ GO GO CREATE FUNCTION tSQLt.Private_GetDropItemCmd ( +/*SnipStart: CreateDropClassStatement.ps1*/ @FullName NVARCHAR(MAX), @ItemType NVARCHAR(MAX) +/*SnipEnd: CreateDropClassStatement.ps1*/ ) RETURNS TABLE AS RETURN -/*START*/ +/*SnipStart: CreateDropClassStatement.ps1*/ SELECT 'DROP ' + CASE @ItemType @@ -29,5 +31,5 @@ SELECT ' ' + @FullName + ';' AS cmd -/*END*/ +/*SnipEnd: CreateDropClassStatement.ps1*/ GO \ No newline at end of file From 5e152039b2be773c5a251bbc2daeaa947f1b9c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmbt1=E2=80=9D?= Date: Wed, 27 Oct 2021 00:23:27 -0400 Subject: [PATCH 11/26] more progress --- Build/CreateDropClassStatement.ps1 | 18 ++++++++---------- Source/tSQLt.DropClass.ssp.sql | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Build/CreateDropClassStatement.ps1 b/Build/CreateDropClassStatement.ps1 index a23d3f0af..5cc96bd5d 100644 --- a/Build/CreateDropClassStatement.ps1 +++ b/Build/CreateDropClassStatement.ps1 @@ -21,22 +21,20 @@ $DropItemSnip = ($GetDropItemCmdFile | Get-SnipContent -startSnipPattern "/*Snip $CaptureName = $true; $VariableNames = @(); +$DropItemSnipPrepared = "("+[System.Environment]::NewLine+(( $DropItemSnip | ForEach-Object{ - if($CaptureName){ - if($_.trim() -match '^(@\w+).*'){ - $VariableNames += $Matches[1]; - - } - else { - $CaptureName = $false - } + if($CaptureName -and $_.trim() -match '^(@\w+).*'){ + $VariableNames += $Matches[1]; } else{ - $s=$_;for($i = 0;$i -lt $VariableNames.count;$i++){$s=$s -replace $VariableNamess[$i], ("$"+($i+1)) };$s + $CaptureName = $false; + $s=$_;for($i = 0;$i -lt $VariableNames.count;$i++){$s=$s -replace $VariableNames[$i], ("($"+($i+1)+")") };$s } } +).trim() -join [System.Environment]::NewLine) + [System.Environment]::NewLine + ")" + [System.Environment]::NewLine; +$RawDropClassStatement = $DropClassSnip -replace 'tSQLt\s*.\s*Private_GetDropItemCmd\s*\(\s*([^,]*)\s*,\s*([^)]*)\s*\)',$DropItemSnipPrepared; -$VariableNames; +$RawDropClassStatement.trim()|Where-Object {$_ -ne "" -and $_ -notmatch "^GO(\s.*)?"}; <# TODO --> Test this: Empty File TempDropClass.sql file should throw an error diff --git a/Source/tSQLt.DropClass.ssp.sql b/Source/tSQLt.DropClass.ssp.sql index cf31f9ed8..13d76588a 100644 --- a/Source/tSQLt.DropClass.ssp.sql +++ b/Source/tSQLt.DropClass.ssp.sql @@ -60,7 +60,7 @@ BEGIN ( SELECT GDIC.cmd [text()] FROM DropStatements DS - CROSS APPLY tSQLt.Private_GetDropItemCmd(FullName, ItemType) GDIC + CROSS APPLY tSQLt.Private_GetDropItemCmd(DS.FullName, DS.ItemType) GDIC ORDER BY no FOR XML PATH(''), TYPE ) From a7afa7c566d82ac1753aa57fec40b98b10c0d3ae Mon Sep 17 00:00:00 2001 From: mbt1 Date: Wed, 27 Oct 2021 00:29:09 -0400 Subject: [PATCH 12/26] added a parsing challenge --- Experiments/Experiments.ssmssqlproj | 6 ++++++ Experiments/ParsingDisaster.sql | 17 +++++++++++++++++ tSQLt.ssmssln | 2 +- 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 Experiments/ParsingDisaster.sql diff --git a/Experiments/Experiments.ssmssqlproj b/Experiments/Experiments.ssmssqlproj index 44abda777..4bf7c06d0 100644 --- a/Experiments/Experiments.ssmssqlproj +++ b/Experiments/Experiments.ssmssqlproj @@ -66,6 +66,12 @@ NameResolutionResearch.sql + + + + + ParsingDisaster.sql + diff --git a/Experiments/ParsingDisaster.sql b/Experiments/ParsingDisaster.sql new file mode 100644 index 000000000..b1417590a --- /dev/null +++ b/Experiments/ParsingDisaster.sql @@ -0,0 +1,17 @@ +SELECT 1 AS [A /* +GO +*/ O] + +--/* <-- execute, then delete '--' and execute again +/*Comment*/ +SELECT 2 +,2.2 +--*/SELECT 3 /* +--*/,3.2,' +SELECT 4 +,4.2 +--' /* +--*/SELECT 5 /* +--*/,5.2 /* +--*/ +SELECT 6 diff --git a/tSQLt.ssmssln b/tSQLt.ssmssln index 30880a9f4..5a942feb5 100644 --- a/tSQLt.ssmssln +++ b/tSQLt.ssmssln @@ -38,6 +38,7 @@ Global Default|Default = Default|Default EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EBCB5200-E814-4995-8B2A-5F4B4328895F}.Default|Default.ActiveCfg = Default {E5760F57-8934-4790-8420-DA7032D58EB7}.Default|Default.ActiveCfg = Default {5FFBD87D-F3CF-43CF-BD60-ED175F5B0E92}.Default|Default.ActiveCfg = Default {161EC148-F99F-4D07-A8A0-FF7ECB9A1566}.Default|Default.ActiveCfg = Default @@ -52,7 +53,6 @@ Global {29C01CDC-4748-4118-ACF3-FFFE56A8A989}.Default|Default.ActiveCfg = Default {244CA5E2-1A9D-4569-A05D-5D0FE85C3F03}.Default|Default.ActiveCfg = Default {BE51C6D7-1DBA-4845-8512-A13A4BAF2F0D}.Default|Default.ActiveCfg = Default - {0E86CAC5-6E7F-4272-8E27-869E1A037717}.Default|Default.ActiveCfg = Default {2FC4D7CE-389E-4044-AE06-F999E036F6D6}.Default|Default.ActiveCfg = Default EndGlobalSection GlobalSection(SolutionProperties) = preSolution From b7ccbb4c16b03ee95e80f7d92b514167d3975b01 Mon Sep 17 00:00:00 2001 From: mbt1 Date: Wed, 27 Oct 2021 00:39:13 -0400 Subject: [PATCH 13/26] more parsing difficulties --- Experiments/ParsingDisaster.sql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Experiments/ParsingDisaster.sql b/Experiments/ParsingDisaster.sql index b1417590a..07e0b9447 100644 --- a/Experiments/ParsingDisaster.sql +++ b/Experiments/ParsingDisaster.sql @@ -1,6 +1,7 @@ -SELECT 1 AS [A /* +--execute, then comment out the line below and execute again +SELECT 1 AS [A GO -*/ O] +SELECT 0 AS [X] --/* <-- execute, then delete '--' and execute again /*Comment*/ From f46378a37041f591278c55adaec365be2bab1889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmbt1=E2=80=9D?= Date: Wed, 27 Oct 2021 20:40:10 -0400 Subject: [PATCH 14/26] DropClassStatement generation seems to be working --- Build/CreateDropClassStatement.ps1 | 51 +++++++++++---------- Source/tSQLt.DropClass.ssp.sql | 4 +- Source/tSQLt.Private_GetDropItemCmd.sfn.sql | 4 +- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/Build/CreateDropClassStatement.ps1 b/Build/CreateDropClassStatement.ps1 index 5cc96bd5d..910db7ad5 100644 --- a/Build/CreateDropClassStatement.ps1 +++ b/Build/CreateDropClassStatement.ps1 @@ -12,29 +12,34 @@ Log-Output '<#--================================================================ Log-Output '' Log-Output '<#--=======================================================================-->' -$DropClassFile = Get-Content -path ($sourcePath+"tSQLt.DropClass.ssp.sql"); -$GetDropItemCmdFile = Get-Content -path ($sourcePath+"tSQLt.Private_GetDropItemCmd.sfn.sql"); -$OutputFile = $tempPath+"TempDropClass.sql"; - -$DropClassSnip = ($DropClassFile | Get-SnipContent -startSnipPattern "/*SnipStart: CreateDropClassStatement.ps1*/" -endSnipPattern "/*SnipEnd: CreateDropClassStatement.ps1*/"); -$DropItemSnip = ($GetDropItemCmdFile | Get-SnipContent -startSnipPattern "/*SnipStart: CreateDropClassStatement.ps1*/" -endSnipPattern "/*SnipEnd: CreateDropClassStatement.ps1*/"); - -$CaptureName = $true; -$VariableNames = @(); -$DropItemSnipPrepared = "("+[System.Environment]::NewLine+(( -$DropItemSnip | ForEach-Object{ - if($CaptureName -and $_.trim() -match '^(@\w+).*'){ - $VariableNames += $Matches[1]; - } - else{ - $CaptureName = $false; - $s=$_;for($i = 0;$i -lt $VariableNames.count;$i++){$s=$s -replace $VariableNames[$i], ("($"+($i+1)+")") };$s - } -} -).trim() -join [System.Environment]::NewLine) + [System.Environment]::NewLine + ")" + [System.Environment]::NewLine; -$RawDropClassStatement = $DropClassSnip -replace 'tSQLt\s*.\s*Private_GetDropItemCmd\s*\(\s*([^,]*)\s*,\s*([^)]*)\s*\)',$DropItemSnipPrepared; +$DropClassFileContent = Get-Content -path ($sourcePath+"tSQLt.DropClass.ssp.sql"); +$GetDropItemCmdFileContent = Get-Content -path ($sourcePath+"tSQLt.Private_GetDropItemCmd.sfn.sql"); +$OutputFilePath = $tempPath+"TempDropClass.sql"; + +$DropClassSnip = ($DropClassFileContent | Get-SnipContent -startSnipPattern "/*SnipStart: CreateDropClassStatement.ps1*/" -endSnipPattern "/*SnipEnd: CreateDropClassStatement.ps1*/"); +$DropItemSnip = ($GetDropItemCmdFileContent | Get-SnipContent -startSnipPattern "/*SnipStart: CreateDropClassStatement.ps1*/" -endSnipPattern "/*SnipEnd: CreateDropClassStatement.ps1*/"); +$DropItemParamSnip = ($GetDropItemCmdFileContent | Get-SnipContent -startSnipPattern "/*SnipParamStart: CreateDropClassStatement.ps1*/" -endSnipPattern "/*SnipParamEnd: CreateDropClassStatement.ps1*/"); + +$VariablesString = ($DropItemParamSnip.trim() -join ' ') + +$VariableNames = (Select-String '@\S+' -input $VariablesString -AllMatches|ForEach-Object{$_.matches.Value}); +#$VariableNames + +$DISP1 = ($DropItemSnip | ForEach-Object{ + $s=$_; + for($i = 0;$i -lt $VariableNames.count;$i++){ + $s=$s -replace $VariableNames[$i], ("($"+($i+1)+")") + }; + $s; +}); +$DISP2 = $DISP1.trim() -join ' '; + +$DropItemSnipPrepared = "("+ $DISP2 + ")"; +$RawDropClassStatement = $DropClassSnip -replace 'tSQLt.Private_GetDropItemCmd\s*\(\s*([^,]*)\s*,\s*([^)]*)\s*\)',$DropItemSnipPrepared; + +$DropClassStatement = ($RawDropClassStatement.trim()|Where-Object {$_ -ne "" -and $_ -notmatch "^GO(\s.*)?"}) -join ' '; -$RawDropClassStatement.trim()|Where-Object {$_ -ne "" -and $_ -notmatch "^GO(\s.*)?"}; +Set-Content -Path $OutputFilePath -Value $DropClassStatement; <# TODO --> Test this: Empty File TempDropClass.sql file should throw an error @@ -67,7 +72,7 @@ if ($LoginTrimmed -match '((.*[-]U)|(.*[-]P))+.*'){ <# We need to replace this target with a exec target which calls a ps1 to do this work. In addition, we will add a replace which will "in line" the relevant text of tSQLt.Private_GetDropItemCmd. -"suggested (◔_◔)" regex: tSQLt.Private_GetDropItemCmd(FullName, ItemType) +"suggested (◔_◔)" regex: tSQLt.Private_GetDropItemCmd(Ds.FullName, Ds.ItemType) 'tSQLt\s*.\s*Private_GetDropItemCmd\s*\(\s*([^,]*)\s*,\s*([^)]*)\s*\)' It should look like this: diff --git a/Source/tSQLt.DropClass.ssp.sql b/Source/tSQLt.DropClass.ssp.sql index 13d76588a..a2e76cca0 100644 --- a/Source/tSQLt.DropClass.ssp.sql +++ b/Source/tSQLt.DropClass.ssp.sql @@ -5,9 +5,7 @@ CREATE PROCEDURE tSQLt.DropClass @ClassName NVARCHAR(MAX) AS BEGIN ----Build- /*SnipStart: CreateDropClassStatement.ps1*/ ----Build+ DECLARE @Cmd NVARCHAR(MAX); WITH ObjectInfo(FullName, ItemType) AS @@ -69,6 +67,6 @@ BEGIN EXEC(@Cmd); END; ----Build- /*SnipEnd: CreateDropClassStatement.ps1*/ +---Build- GO diff --git a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql index 5fcd8ed73..523e27bac 100644 --- a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql +++ b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql @@ -4,10 +4,10 @@ GO GO CREATE FUNCTION tSQLt.Private_GetDropItemCmd ( -/*SnipStart: CreateDropClassStatement.ps1*/ +/*SnipParamStart: CreateDropClassStatement.ps1*/ @FullName NVARCHAR(MAX), @ItemType NVARCHAR(MAX) -/*SnipEnd: CreateDropClassStatement.ps1*/ +/*SnipParamEnd: CreateDropClassStatement.ps1*/ ) RETURNS TABLE AS From 91d05b0ec6c7a2e4db0f5a6121b64ead4ebcc68d Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Wed, 27 Oct 2021 21:15:42 -0400 Subject: [PATCH 15/26] Fixed error'ing test, have one failing test. --- Source/tSQLt.DropClass.ssp.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/tSQLt.DropClass.ssp.sql b/Source/tSQLt.DropClass.ssp.sql index a2e76cca0..5c3af0989 100644 --- a/Source/tSQLt.DropClass.ssp.sql +++ b/Source/tSQLt.DropClass.ssp.sql @@ -28,7 +28,7 @@ BEGIN ( SELECT QUOTENAME(SCHEMA_NAME(XSC.schema_id))+'.'+QUOTENAME(XSC.name), - 'xml_schema_collections' + 'xml_schema_collection' FROM sys.xml_schema_collections AS XSC WHERE XSC.schema_id = SCHEMA_ID(@ClassName) ), From 99794687de821e3c94f4228d6c571724049ebc2f Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Wed, 27 Oct 2021 23:05:48 -0400 Subject: [PATCH 16/26] UndoTestDoubles is working. Check out the TODO in UndoTestDoublesTests.class.sql for next steps. --- Source/tSQLt.UndoTestDoubles.ssp.sql | 37 +++++++-- Tests/UndoTestDoublesTests.class.sql | 114 ++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 11 deletions(-) diff --git a/Source/tSQLt.UndoTestDoubles.ssp.sql b/Source/tSQLt.UndoTestDoubles.ssp.sql index d395b332f..6135e8453 100644 --- a/Source/tSQLt.UndoTestDoubles.ssp.sql +++ b/Source/tSQLt.UndoTestDoubles.ssp.sql @@ -5,14 +5,35 @@ GO CREATE PROCEDURE tSQLt.UndoTestDoubles AS BEGIN - SELECT - Id, - ObjectId, - OBJECT_SCHEMA_NAME(L.ObjectId) SchemaName, - OBJECT_NAME(L.ObjectId) CurrentName, - OriginalName - FROM tSQLt.Private_RenamedObjectLog L - RETURN; + DECLARE @cmd NVARCHAR(MAX); + WITH L AS + ( + SELECT + ROL.Id, + ParentROL.Id ParentId, + ISNULL(ParentROL.Id,ROL.Id) SortId, + ROL.ObjectId, + OBJECT_SCHEMA_NAME(ROL.ObjectId) SchemaName, + OBJECT_NAME(ROL.ObjectId) CurrentName, + PARSENAME(ROL.OriginalName,1) OriginalName, + O.type ObjectType + FROM tSQLt.Private_RenamedObjectLog ROL + JOIN sys.objects O + ON ROL.ObjectId = O.object_id + LEFT JOIN tSQLt.Private_RenamedObjectLog ParentROL + ON O.parent_object_id = ParentROL.ObjectId + ) + SELECT @cmd = + ( + SELECT + CASE WHEN L.ParentId IS NULL THEN DC.cmd ELSE '' END+ + ';EXEC tSQLt.Private_RenameObject '''+L.SchemaName+''','''+L.CurrentName+''','''+L.OriginalName+''';' + FROM L + CROSS APPLY tSQLt.Private_GetDropItemCmd(QUOTENAME(L.SchemaName)+'.'+QUOTENAME(L.OriginalName),L.ObjectType) DC + ORDER BY L.SortId DESC, L.Id ASC + FOR XML PATH(''),TYPE + ).value('.','NVARCHAR(MAX)') + EXEC(@cmd); END; GO diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql index 5e0bdd77f..d6ccfd76f 100644 --- a/Tests/UndoTestDoublesTests.class.sql +++ b/Tests/UndoTestDoublesTests.class.sql @@ -13,13 +13,95 @@ GO CREATE PROCEDURE UndoTestDoublesTests.[test restores a faked table] AS BEGIN - CREATE TABLE UndoTestDoublesTests.aSimpleTable + CREATE TABLE UndoTestDoublesTests.aSimpleTable ( Id INT ); + + DECLARE @OriginalObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleTable'); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + + EXEC tSQLt.UndoTestDoubles; + + DECLARE @RestoredObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleTable'); + EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test works with names in need of quotes] +AS +BEGIN + EXEC('CREATE SCHEMA [A Random Schema];'); + CREATE TABLE [A Random Schema].[A Simple Table] ( Id INT ); + DECLARE @OriginalObjectId INT = OBJECT_ID('[A Random Schema].[A Simple Table]'); + + EXEC tSQLt.FakeTable @TableName = '[A Random Schema].[A Simple Table]'; + + EXEC tSQLt.UndoTestDoubles; + + DECLARE @RestoredObjectId INT = OBJECT_ID('[A Random Schema].[A Simple Table]'); + EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores many faked tables] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable1 ( Id INT ); + CREATE TABLE UndoTestDoublesTests.aSimpleTable2 ( Id INT ); + CREATE TABLE UndoTestDoublesTests.aSimpleTable3 ( Id INT ); + + SELECT X.TableName, OBJECT_ID('UndoTestDoublesTests.'+X.TableName) ObjectId + INTO #OriginalObjectIds + FROM (VALUES('aSimpleTable1'),('aSimpleTable2'),('aSimpleTable3')) X (TableName); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable1'; + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable2'; + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable3'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT X.TableName, OBJECT_ID('UndoTestDoublesTests.'+X.TableName) ObjectId + INTO #RestoredObjectIds + FROM (VALUES('aSimpleTable1'),('aSimpleTable2'),('aSimpleTable3')) X (TableName); + + EXEC tSQLt.AssertEqualsTable @Expected = '#OriginalObjectIds', @Actual = '#RestoredObjectIds'; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a constraint] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable ( Id INT CONSTRAINT aSimpleTableConstraint CHECK(Id > 0)); + + SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + INTO #OriginalObjectIds + FROM (VALUES('aSimpleTableConstraint')) X (ObjectName); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + EXEC tSQLt.ApplyConstraint @TableName = 'UndoTestDoublesTests.aSimpleTable', @ConstraintName = 'aSimpleTableConstraint'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + INTO #RestoredObjectIds + FROM (VALUES('aSimpleTableConstraint')) X (ObjectName); + + EXEC tSQLt.AssertEqualsTable @Expected = '#OriginalObjectIds', @Actual = '#RestoredObjectIds'; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a table that has been faked multiple times] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable ( Id INT ); + DECLARE @OriginalObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleTable'); + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; EXEC tSQLt.UndoTestDoubles; @@ -27,12 +109,38 @@ BEGIN DECLARE @RestoredObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleTable'); EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a trigger] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable ( Id INT ); + EXEC('CREATE TRIGGER aSimpleTrigger ON UndoTestDoublesTests.aSimpleTable FOR INSERT AS RETURN;'); + + SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + INTO #OriginalObjectIds + FROM (VALUES('aSimpleTrigger')) X (ObjectName); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + EXEC tSQLt.ApplyTrigger @TableName = 'UndoTestDoublesTests.aSimpleTable', @TriggerName = 'aSimpleTrigger'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + INTO #RestoredObjectIds + FROM (VALUES('aSimpleTrigger')) X (ObjectName); + + EXEC tSQLt.AssertEqualsTable @Expected = '#OriginalObjectIds', @Actual = '#RestoredObjectIds'; + END; GO /*-- TODO -- ApplyConstraint -- ApplyTrigger +==> multiple constraints and triggers on a multiple tables, faked multiple times +- stored procedures +- views +- functions + Also, just review all the code. From 1448b34a493cae0e6d30ae679a38d3a9bf50d90e Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Wed, 27 Oct 2021 23:08:19 -0400 Subject: [PATCH 17/26] 123456789012345678901234567890123456789012345678901234567890 --- Build/tSQLt.validatebuild.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/tSQLt.validatebuild.xml b/Build/tSQLt.validatebuild.xml index 98055037a..1b37036af 100644 --- a/Build/tSQLt.validatebuild.xml +++ b/Build/tSQLt.validatebuild.xml @@ -1,5 +1,5 @@ - + From 0848c6d1302c43e30cc3ef4ecdfc8dcd59975d3a Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Wed, 27 Oct 2021 23:15:41 -0400 Subject: [PATCH 18/26] testing git.inputValidationLength lulz --- Build/tSQLt.validatebuild.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/tSQLt.validatebuild.xml b/Build/tSQLt.validatebuild.xml index 1b37036af..98055037a 100644 --- a/Build/tSQLt.validatebuild.xml +++ b/Build/tSQLt.validatebuild.xml @@ -1,5 +1,5 @@ - + From 2962bfda369ecc86b6628dd48337b3103528726e Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Thu, 28 Oct 2021 22:42:17 -0400 Subject: [PATCH 19/26] Added more Tests/UndoTestDoublesTests.class.sql to make sure that constraints and triggers work and added a test to check that that RenamedObjectLog table is empty after Undo. SSPs, Views, and Functions next. --- Source/tSQLt.UndoTestDoubles.ssp.sql | 10 +++- Tests/UndoTestDoublesTests.class.sql | 87 ++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/Source/tSQLt.UndoTestDoubles.ssp.sql b/Source/tSQLt.UndoTestDoubles.ssp.sql index 6135e8453..27c041256 100644 --- a/Source/tSQLt.UndoTestDoubles.ssp.sql +++ b/Source/tSQLt.UndoTestDoubles.ssp.sql @@ -6,6 +6,11 @@ CREATE PROCEDURE tSQLt.UndoTestDoubles AS BEGIN DECLARE @cmd NVARCHAR(MAX); + + SELECT TOP(0)A.* INTO #RenamedObjects FROM tSQLt.Private_RenamedObjectLog A RIGHT JOIN tSQLt.Private_RenamedObjectLog X ON 1=0; + + BEGIN TRAN; + DELETE FROM tSQLt.Private_RenamedObjectLog OUTPUT Deleted.* INTO #RenamedObjects; WITH L AS ( SELECT @@ -17,10 +22,10 @@ BEGIN OBJECT_NAME(ROL.ObjectId) CurrentName, PARSENAME(ROL.OriginalName,1) OriginalName, O.type ObjectType - FROM tSQLt.Private_RenamedObjectLog ROL + FROM #RenamedObjects ROL JOIN sys.objects O ON ROL.ObjectId = O.object_id - LEFT JOIN tSQLt.Private_RenamedObjectLog ParentROL + LEFT JOIN #RenamedObjects ParentROL ON O.parent_object_id = ParentROL.ObjectId ) SELECT @cmd = @@ -34,6 +39,7 @@ BEGIN FOR XML PATH(''),TYPE ).value('.','NVARCHAR(MAX)') EXEC(@cmd); + COMMIT; END; GO diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql index d6ccfd76f..89ffb87c6 100644 --- a/Tests/UndoTestDoublesTests.class.sql +++ b/Tests/UndoTestDoublesTests.class.sql @@ -134,14 +134,93 @@ BEGIN END; GO +CREATE PROCEDURE UndoTestDoublesTests.CreateTableWithTriggersAndConstraints + @Number NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + SELECT @cmd = 'CREATE TABLE UndoTestDoublesTests.aSimpleTable0 ( Id INT CONSTRAINT aSimpleTable0C1 CHECK(Id > 9) CONSTRAINT aSimpleTable0PK PRIMARY KEY);'; + SET @cmd = REPLACE(@cmd,'0',@Number);EXEC(@cmd); + + SET @cmd = 'CREATE TRIGGER aSimpleTrigger0i ON UndoTestDoublesTests.aSimpleTable0 FOR INSERT AS RETURN;'; + SET @cmd = REPLACE(@cmd,'0',@Number);EXEC(@cmd); + + SET @cmd = 'CREATE TRIGGER aSimpleTrigger0u ON UndoTestDoublesTests.aSimpleTable0 FOR UPDATE AS RETURN;'; + SET @cmd = REPLACE(@cmd,'0',@Number);EXEC(@cmd); + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints + @Number NVARCHAR(MAX) +AS +BEGIN + DECLARE @cmd NVARCHAR(MAX); + SELECT @cmd = ' + EXEC tSQLt.FakeTable @TableName=''UndoTestDoublesTests.aSimpleTable0''; + EXEC tSQLt.ApplyConstraint @TableName=''UndoTestDoublesTests.aSimpleTable0'', @ConstraintName = ''aSimpleTable0C1''; + EXEC tSQLt.ApplyConstraint @TableName=''UndoTestDoublesTests.aSimpleTable0'', @ConstraintName = ''aSimpleTable0PK''; + EXEC tSQLt.ApplyTrigger @TableName=''UndoTestDoublesTests.aSimpleTable0'', @TriggerName = ''aSimpleTrigger0i''; + EXEC tSQLt.ApplyTrigger @TableName=''UndoTestDoublesTests.aSimpleTable0'', @TriggerName = ''aSimpleTrigger0u''; + '; + SET @cmd = REPLACE(@cmd,'0',@Number);EXEC(@cmd); + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores multiple triggers and multiple constraints on multiple tables faked multiple times] +AS +BEGIN + EXEC UndoTestDoublesTests.CreateTableWithTriggersAndConstraints '1'; + EXEC UndoTestDoublesTests.CreateTableWithTriggersAndConstraints '2'; + EXEC UndoTestDoublesTests.CreateTableWithTriggersAndConstraints '3'; + + SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + INTO #OriginalObjectIds + FROM (VALUES('aSimpleTrigger1i'),('aSimpleTrigger1u'),('aSimpleTable1C1'),('aSimpleTable1PK'),('aSimpleTable1'), + ('aSimpleTrigger2i'),('aSimpleTrigger2u'),('aSimpleTable2C1'),('aSimpleTable2PK'),('aSimpleTable2'), + ('aSimpleTrigger3i'),('aSimpleTrigger3u'),('aSimpleTable3C1'),('aSimpleTable3PK'),('aSimpleTable3') + ) X (ObjectName); + + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '1'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '2'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '3'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '1'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '2'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '3'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '1'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '2'; + EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '3'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + INTO #RestoredObjectIds + FROM (VALUES('aSimpleTrigger1i'),('aSimpleTrigger1u'),('aSimpleTable1C1'),('aSimpleTable1PK'),('aSimpleTable1'), + ('aSimpleTrigger2i'),('aSimpleTrigger2u'),('aSimpleTable2C1'),('aSimpleTable2PK'),('aSimpleTable2'), + ('aSimpleTrigger3i'),('aSimpleTrigger3u'),('aSimpleTable3C1'),('aSimpleTable3PK'),('aSimpleTable3') + ) X (ObjectName); + + EXEC tSQLt.AssertEqualsTable @Expected = '#OriginalObjectIds', @Actual = '#RestoredObjectIds'; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test tSQLt.Private_RenamedObjectLog is empty after execution] +AS +BEGIN + CREATE TABLE UndoTestDoublesTests.aSimpleTable ( Id INT ); + + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleTable'; + + EXEC tSQLt.UndoTestDoubles; + + EXEC tSQLt.AssertEmptyTable @TableName = 'tSQLt.Private_RenamedObjectLog'; +END; +GO + /*-- TODO -==> multiple constraints and triggers on a multiple tables, faked multiple times - stored procedures - views - functions - - -Also, just review all the code. +- rename object to unique name --*/ From 8e861d23688416a7d79ae7bcde0ce7311a19f29b Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Fri, 29 Oct 2021 22:20:50 -0400 Subject: [PATCH 20/26] Added tests for ssps and views! --- Source/tSQLt.UndoTestDoubles.ssp.sql | 20 ++++++++++++++--- Tests/UndoTestDoublesTests.class.sql | 32 ++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/Source/tSQLt.UndoTestDoubles.ssp.sql b/Source/tSQLt.UndoTestDoubles.ssp.sql index 27c041256..07606ff92 100644 --- a/Source/tSQLt.UndoTestDoubles.ssp.sql +++ b/Source/tSQLt.UndoTestDoubles.ssp.sql @@ -11,7 +11,7 @@ BEGIN BEGIN TRAN; DELETE FROM tSQLt.Private_RenamedObjectLog OUTPUT Deleted.* INTO #RenamedObjects; - WITH L AS + WITH LL AS ( SELECT ROL.Id, @@ -20,13 +20,27 @@ BEGIN ROL.ObjectId, OBJECT_SCHEMA_NAME(ROL.ObjectId) SchemaName, OBJECT_NAME(ROL.ObjectId) CurrentName, - PARSENAME(ROL.OriginalName,1) OriginalName, - O.type ObjectType + PARSENAME(ROL.OriginalName,1) OriginalName FROM #RenamedObjects ROL JOIN sys.objects O ON ROL.ObjectId = O.object_id LEFT JOIN #RenamedObjects ParentROL ON O.parent_object_id = ParentROL.ObjectId + ), + L AS + ( + SELECT + LL.Id, + LL.ParentId, + LL.SortId, + LL.ObjectId, + LL.SchemaName, + LL.CurrentName, + LL.OriginalName, + FakeO.type ObjectType + FROM LL + JOIN sys.objects FakeO + ON FakeO.object_id = OBJECT_ID(QUOTENAME(LL.SchemaName)+'.'+QUOTENAME(LL.OriginalName)) ) SELECT @cmd = ( diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql index 89ffb87c6..9a9d38120 100644 --- a/Tests/UndoTestDoublesTests.class.sql +++ b/Tests/UndoTestDoublesTests.class.sql @@ -215,12 +215,40 @@ BEGIN EXEC tSQLt.AssertEmptyTable @TableName = 'tSQLt.Private_RenamedObjectLog'; END; GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a faked stored procedure] +AS +BEGIN + EXEC ('CREATE PROCEDURE UndoTestDoublesTests.aSimpleSSP @Id INT AS RETURN;'); + DECLARE @OriginalObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleSSP'); + EXEC tSQLt.SpyProcedure @ProcedureName = 'UndoTestDoublesTests.aSimpleSSP'; + + EXEC tSQLt.UndoTestDoubles; + + DECLARE @RestoredObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleSSP'); + EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; + +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a faked view] +AS +BEGIN + EXEC ('CREATE VIEW UndoTestDoublesTests.aSimpleView AS SELECT NULL X;'); + DECLARE @OriginalObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleView'); + EXEC tSQLt.FakeTable @TableName = 'UndoTestDoublesTests.aSimpleView'; + + EXEC tSQLt.UndoTestDoubles; + + DECLARE @RestoredObjectId INT = OBJECT_ID('UndoTestDoublesTests.aSimpleView'); + EXEC tSQLt.AssertEquals @Expected = @OriginalObjectId, @Actual = @RestoredObjectId; + +END; +GO /*-- TODO -- stored procedures -- views - functions - rename object to unique name +-- no replacement +-- random replacement --*/ From 9d4036f9a183fd2a20a4b4c4e554a59751ce167a Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Sat, 30 Oct 2021 21:43:00 -0400 Subject: [PATCH 21/26] Rewrote a test so that we make sure that we are deleting test doubles and nothing else; started writing the test for functions; discovered a ... not-feature for FakeFunction using FakeDataSource. --- Tests/UndoTestDoublesTests.class.sql | 59 ++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql index 9a9d38120..9efba08be 100644 --- a/Tests/UndoTestDoublesTests.class.sql +++ b/Tests/UndoTestDoublesTests.class.sql @@ -166,19 +166,16 @@ BEGIN END; GO -CREATE PROCEDURE UndoTestDoublesTests.[test restores multiple triggers and multiple constraints on multiple tables faked multiple times] +CREATE PROCEDURE UndoTestDoublesTests.[test restores objects after multiple fake actions and deletes test doubles] AS BEGIN EXEC UndoTestDoublesTests.CreateTableWithTriggersAndConstraints '1'; EXEC UndoTestDoublesTests.CreateTableWithTriggersAndConstraints '2'; EXEC UndoTestDoublesTests.CreateTableWithTriggersAndConstraints '3'; - SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name INTO #OriginalObjectIds - FROM (VALUES('aSimpleTrigger1i'),('aSimpleTrigger1u'),('aSimpleTable1C1'),('aSimpleTable1PK'),('aSimpleTable1'), - ('aSimpleTrigger2i'),('aSimpleTrigger2u'),('aSimpleTable2C1'),('aSimpleTable2PK'),('aSimpleTable2'), - ('aSimpleTrigger3i'),('aSimpleTrigger3u'),('aSimpleTable3C1'),('aSimpleTable3PK'),('aSimpleTable3') - ) X (ObjectName); + FROM sys.objects O; EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '1'; EXEC UndoTestDoublesTests.FakeTableAndApplyTriggersAndConstraints '2'; @@ -192,14 +189,18 @@ BEGIN EXEC tSQLt.UndoTestDoubles; - SELECT X.ObjectName, OBJECT_ID('UndoTestDoublesTests.'+X.ObjectName) ObjectId + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name INTO #RestoredObjectIds - FROM (VALUES('aSimpleTrigger1i'),('aSimpleTrigger1u'),('aSimpleTable1C1'),('aSimpleTable1PK'),('aSimpleTable1'), - ('aSimpleTrigger2i'),('aSimpleTrigger2u'),('aSimpleTable2C1'),('aSimpleTable2PK'),('aSimpleTable2'), - ('aSimpleTrigger3i'),('aSimpleTrigger3u'),('aSimpleTable3C1'),('aSimpleTable3PK'),('aSimpleTable3') - ) X (ObjectName); + FROM sys.objects O; - EXEC tSQLt.AssertEqualsTable @Expected = '#OriginalObjectIds', @Actual = '#RestoredObjectIds'; + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; END; GO @@ -243,12 +244,44 @@ BEGIN END; GO +CREATE PROCEDURE UndoTestDoublesTests.[test restores a faked function] +AS +BEGIN + EXEC ('CREATE FUNCTION UndoTestDoublesTests.aSimpleITVF() RETURNS TABLE AS RETURN SELECT NULL X;'); + EXEC ('CREATE FUNCTION UndoTestDoublesTests.aSimpleTVF() RETURNS @R TABLE(i INT) AS BEGIN RETURN; END;'); + EXEC ('CREATE FUNCTION UndoTestDoublesTests.aSimpleSVF() RETURNS INT AS BEGIN RETURN NULL; END;'); + EXEC ('CREATE FUNCTION UndoTestDoublesTests.aTempSVF() RETURNS INT AS BEGIN RETURN NULL; END;'); + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.FakeFunction @FunctionName = 'UndoTestDoublesTests.aSimpleITVF', @FakeDataSource = '(VALUES(NULL))X(X)'; + EXEC tSQLt.FakeFunction @FunctionName = 'UndoTestDoublesTests.aSimpleTVF', @FakeDataSource = '(VALUES(NULL))X(X)'; + EXEC tSQLt.FakeFunction @FunctionName = 'UndoTestDoublesTests.aSimpleSVF', @FakeFunctionName = 'UndoTestDoublesTests.aTempSVF'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO /*-- TODO - functions - rename object to unique name -- no replacement -- random replacement - +-- fakefunction should put new objects in the same schema +------->START HERE: Do we even need the fakefunction tempobject? --*/ From fe3ba0372d8b70aa896cabb7b52410bb81ef2e14 Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Sun, 31 Oct 2021 14:32:54 -0400 Subject: [PATCH 22/26] Do we make a snapshot or not for FakeFunctions with FakeDataSources? Tests required. --- Source/Source.ssmssqlproj | 6 ++++++ Tests/FakeFunctionTests.class.sql | 29 +++++++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/Source/Source.ssmssqlproj b/Source/Source.ssmssqlproj index 30a1c9676..01fff8716 100644 --- a/Source/Source.ssmssqlproj +++ b/Source/Source.ssmssqlproj @@ -420,6 +420,12 @@ tSQLt.Private_NullCellTable.tbl.sql + + + + + tSQLt.Private_PrepareFakeFunctionOutputTable.ssp.sql + diff --git a/Tests/FakeFunctionTests.class.sql b/Tests/FakeFunctionTests.class.sql index cfa138b19..9c85cd3d2 100644 --- a/Tests/FakeFunctionTests.class.sql +++ b/Tests/FakeFunctionTests.class.sql @@ -647,14 +647,31 @@ CREATE PROCEDURE FakeFunctionTests.[test Private_PrepareFakeFunctionOutputTable AS BEGIN - DECLARE @NewTable sysname; + DECLARE @NewTable NVARCHAR(MAX); - CREATE TABLE #Expected (a int); - INSERT INTO #Expected VALUES(1); - - EXEC tSQLt.Private_PrepareFakeFunctionOutputTable 'SELECT 1 AS a', @NewTable OUTPUT; + EXEC tSQLt.Private_PrepareFakeFunctionOutputTable 'SELECT 1013 AS a', @NewTable OUTPUT; - EXEC tSQLt.AssertEqualsTable '#Expected', @NewTable; + EXEC('SELECT TOP(0)A.* INTO FakeFunctionTests.Expected FROM '+@NewTable+' A RIGHT JOIN '+@NewTable+' ON 0=1;'); + INSERT INTO FakeFunctionTests.Expected VALUES(1013); + + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', @NewTable; +END; +GO +CREATE PROCEDURE FakeFunctionTests.[test Private_PrepareFakeFunctionOutputTable creates snapshot table in passed in schema] +AS +BEGIN + DECLARE @NewTable NVARCHAR(MAX); + + EXEC('CREATE SCHEMA [a random schema];'); + + EXEC tSQLt.Private_PrepareFakeFunctionOutputTable @FakeDataSource = 'SELECT 1013 AS a',@SchemaName = 'a random schema', @NewTableName = @NewTable OUT; + + SELECT SCHEMA_NAME(O.schema_id) SchemaName INTO #Actual FROM sys.objects O WHERE O.object_id = OBJECT_ID(@NewTable); + + SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; + INSERT INTO #Expected + VALUES('a random schema'); + EXEC tSQLt.AssertEqualsTable #Expected, #Actual END; GO From 5cfd5e37cd682958290a5c1fde7a0ed1f96a5f59 Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Sun, 31 Oct 2021 21:17:27 -0400 Subject: [PATCH 23/26] Removed snapshot'ing from FakeFunction. --- .../tSQLt.Private_CreateFakeFunction.ssp.sql | 25 ++- Tests/FakeFunctionTests.class.sql | 173 +++++++++++++----- 2 files changed, 146 insertions(+), 52 deletions(-) diff --git a/Source/tSQLt.Private_CreateFakeFunction.ssp.sql b/Source/tSQLt.Private_CreateFakeFunction.ssp.sql index f3470f881..41f601d91 100644 --- a/Source/tSQLt.Private_CreateFakeFunction.ssp.sql +++ b/Source/tSQLt.Private_CreateFakeFunction.ssp.sql @@ -45,15 +45,24 @@ BEGIN BEGIN EXEC('CREATE FUNCTION '+@FunctionName+'('+@ParameterList+') RETURNS '+@ReturnType+' AS BEGIN RETURN '+@FakeFunctionName+'('+@ParameterCallList+');END;'); END - ELSE IF (@FakeDataSource IS NOT NULL) + ELSE BEGIN - DECLARE @newTbleName NVARCHAR(MAX); - EXEC tSQLt.Private_PrepareFakeFunctionOutputTable @FakeDataSource, @newTbleName OUTPUT; - EXEC ('CREATE FUNCTION '+@FunctionName+'('+@ParameterList+') RETURNS TABLE AS RETURN ( SELECT * FROM '+@newTbleName+');'); - END - ELSE - BEGIN - EXEC('CREATE FUNCTION '+@FunctionName+'('+@ParameterList+') RETURNS TABLE AS RETURN SELECT * FROM '+@FakeFunctionName+'('+@ParameterCallList+');'); + DECLARE @cmd NVARCHAR(MAX); + IF (@FakeDataSource IS NOT NULL) + BEGIN + SET @cmd = + CASE + WHEN OBJECT_ID(@FakeDataSource) IS NOT NULL THEN 'SELECT * FROM '+@FakeDataSource + WHEN @FakeDataSource LIKE '(%)%(%)' THEN 'SELECT * FROM '+@FakeDataSource + ELSE @FakeDataSource + END; + END + ELSE + BEGIN + SET @cmd = 'SELECT * FROM '+@FakeFunctionName+'('+@ParameterCallList+')'; + END; + SET @cmd = 'CREATE FUNCTION '+@FunctionName+'('+@ParameterList+') RETURNS TABLE AS RETURN '+@cmd+';' + EXEC(@cmd); END; END; GO diff --git a/Tests/FakeFunctionTests.class.sql b/Tests/FakeFunctionTests.class.sql index 9c85cd3d2..8364016aa 100644 --- a/Tests/FakeFunctionTests.class.sql +++ b/Tests/FakeFunctionTests.class.sql @@ -464,49 +464,51 @@ BEGIN END; GO -CREATE PROCEDURE FakeFunctionTests.[test can fake Inline table function using a temp table as fake data source] +CREATE PROCEDURE FakeFunctionTests.[test can fake Inline table function using a table as fake data source] AS BEGIN EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS TABLE AS RETURN (SELECT 1 AS one);'); - CREATE TABLE #expected (a CHAR(1)); - INSERT INTO #expected VALUES('a'); + CREATE TABLE FakeFunctionTests.Expected (a CHAR(1)); + INSERT INTO FakeFunctionTests.Expected VALUES('a'); + SELECT TOP(0) A.* INTO FakeFunctionTests.Actual FROM FakeFunctionTests.Expected A RIGHT JOIN FakeFunctionTests.Expected X ON 1=0; - EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'FakeFunctionTests.Expected'; - SELECT * INTO #actual FROM FakeFunctionTests.AFunction(); + INSERT INTO FakeFunctionTests.Actual SELECT * FROM FakeFunctionTests.AFunction(); - EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', 'FakeFunctionTests.Actual'; END; GO -CREATE PROCEDURE FakeFunctionTests.[test can fake multi-statement table function using a temp table as fake data source] +CREATE PROCEDURE FakeFunctionTests.[test can fake multi-statement table function using a table as fake data source] AS BEGIN EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int) RETURNS @t TABLE (a int) AS BEGIN; INSERT INTO @t (a) VALUES (0) RETURN; END;'); - CREATE TABLE #expected (a CHAR(1)); - INSERT INTO #expected VALUES('a'); + CREATE TABLE FakeFunctionTests.Expected (a CHAR(1)); + INSERT INTO FakeFunctionTests.Expected VALUES('a'); + SELECT TOP(0) A.* INTO FakeFunctionTests.Actual FROM FakeFunctionTests.Expected A RIGHT JOIN FakeFunctionTests.Expected X ON 1=0; - EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'FakeFunctionTests.Expected'; - SELECT * INTO #actual FROM FakeFunctionTests.AFunction(123); + INSERT INTO FakeFunctionTests.Actual SELECT * FROM FakeFunctionTests.AFunction(123); - EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', 'FakeFunctionTests.Actual'; END; GO -CREATE PROCEDURE FakeFunctionTests.[test can fake CLR table function using a temp table as fake data source] +CREATE PROCEDURE FakeFunctionTests.[test can fake CLR table function using a table as fake data source] AS BEGIN + CREATE TABLE FakeFunctionTests.Expected (a CHAR(1)); + INSERT INTO FakeFunctionTests.Expected VALUES('a'); + SELECT TOP(0) A.* INTO FakeFunctionTests.Actual FROM FakeFunctionTests.Expected A RIGHT JOIN FakeFunctionTests.Expected X ON 1=0; + + EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt_testutil.AClrTvf', @FakeDataSource = 'FakeFunctionTests.Expected'; - CREATE TABLE #expected (a CHAR(1)); - INSERT INTO #expected VALUES('a'); + INSERT INTO FakeFunctionTests.Actual SELECT * FROM tSQLt_testutil.AClrTvf('', ''); - EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt_testutil.AClrTvf', @FakeDataSource = '#expected'; - - SELECT * INTO #actual FROM tSQLt_testutil.AClrTvf('', ''); - - EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', 'FakeFunctionTests.Actual'; END; GO CREATE PROCEDURE FakeFunctionTests.[test can fake function with one parameter] @@ -514,14 +516,15 @@ AS BEGIN EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int) RETURNS TABLE AS RETURN (SELECT @a AS one);'); - CREATE TABLE #expected (a INT); - INSERT INTO #expected VALUES(1); + CREATE TABLE FakeFunctionTests.Expected (a INT); + INSERT INTO FakeFunctionTests.Expected VALUES(1); + SELECT TOP(0) A.* INTO FakeFunctionTests.Actual FROM FakeFunctionTests.Expected A RIGHT JOIN FakeFunctionTests.Expected X ON 1=0; - EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'FakeFunctionTests.Expected'; - SELECT * INTO #actual FROM FakeFunctionTests.AFunction(123); + INSERT INTO FakeFunctionTests.Actual SELECT * FROM FakeFunctionTests.AFunction(123); - EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', 'FakeFunctionTests.Actual'; END; GO CREATE PROCEDURE FakeFunctionTests.[test can fake function with multiple parameters] @@ -529,14 +532,15 @@ AS BEGIN EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int, @b int, @c char(1)) RETURNS TABLE AS RETURN (SELECT @a AS one);'); - CREATE TABLE #expected (a INT); - INSERT INTO #expected VALUES(1); - - EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + CREATE TABLE FakeFunctionTests.Expected (a INT); + INSERT INTO FakeFunctionTests.Expected VALUES(1); + SELECT TOP(0) A.* INTO FakeFunctionTests.Actual FROM FakeFunctionTests.Expected A RIGHT JOIN FakeFunctionTests.Expected X ON 1=0; + + EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = 'FakeFunctionTests.Expected'; - SELECT * INTO #actual FROM FakeFunctionTests.AFunction(123, 321, 'a'); + INSERT INTO FakeFunctionTests.Actual SELECT * FROM FakeFunctionTests.AFunction(123, 321, 'a'); - EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; + EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', 'FakeFunctionTests.Actual'; END; GO CREATE PROCEDURE FakeFunctionTests.[test can fake function with VALUES clause as fake data source] @@ -657,21 +661,102 @@ BEGIN EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', @NewTable; END; GO -CREATE PROCEDURE FakeFunctionTests.[test Private_PrepareFakeFunctionOutputTable creates snapshot table in passed in schema] -AS -BEGIN - DECLARE @NewTable NVARCHAR(MAX); +-- THIS CODE FEELS UNNECESSARY +-- DECLARE @newTbleName NVARCHAR(MAX); +-- EXEC tSQLt.Private_PrepareFakeFunctionOutputTable @FakeDataSource, @newTbleName OUTPUT; - EXEC('CREATE SCHEMA [a random schema];'); +--THESE TESTS FAIL without a snapshot because they use temp tables +--CREATE PROCEDURE FakeFunctionTests.[test can fake function with multiple parameters] +--AS +--BEGIN +-- EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int, @b int, @c char(1)) RETURNS TABLE AS RETURN (SELECT @a AS one);'); - EXEC tSQLt.Private_PrepareFakeFunctionOutputTable @FakeDataSource = 'SELECT 1013 AS a',@SchemaName = 'a random schema', @NewTableName = @NewTable OUT; +-- CREATE TABLE #expected (a INT); +-- INSERT INTO #expected VALUES(1); - SELECT SCHEMA_NAME(O.schema_id) SchemaName INTO #Actual FROM sys.objects O WHERE O.object_id = OBJECT_ID(@NewTable); +-- EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; - SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; - INSERT INTO #Expected - VALUES('a random schema'); +-- SELECT * INTO #actual FROM FakeFunctionTests.AFunction(123, 321, 'a'); + +-- EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +--END; +--GO +--CREATE PROCEDURE FakeFunctionTests.[test can fake CLR table function using a temp table as fake data source] +--AS +--BEGIN + +-- CREATE TABLE #expected (a CHAR(1)); +-- INSERT INTO #expected VALUES('a'); + +-- EXEC tSQLt.FakeFunction @FunctionName = 'tSQLt_testutil.AClrTvf', @FakeDataSource = '#expected'; + +-- SELECT * INTO #actual FROM tSQLt_testutil.AClrTvf('', ''); + +-- EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +--END; +--GO +--CREATE PROCEDURE FakeFunctionTests.[test can fake function with one parameter] +--AS +--BEGIN +-- EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int) RETURNS TABLE AS RETURN (SELECT @a AS one);'); + +-- CREATE TABLE #expected (a INT); +-- INSERT INTO #expected VALUES(1); + +-- EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + +-- SELECT * INTO #actual FROM FakeFunctionTests.AFunction(123); + +-- EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +--END; +--GO +--CREATE PROCEDURE FakeFunctionTests.[test can fake Inline table function using a temp table as fake data source] +--AS +--BEGIN +-- EXEC('CREATE FUNCTION FakeFunctionTests.AFunction() RETURNS TABLE AS RETURN (SELECT 1 AS one);'); + +-- CREATE TABLE #expected (a CHAR(1)); +-- INSERT INTO #expected VALUES('a'); + +-- EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + +-- SELECT * INTO #actual FROM FakeFunctionTests.AFunction(); + +-- EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +--END; +--GO +--CREATE PROCEDURE FakeFunctionTests.[test can fake multi-statement table function using a temp table as fake data source] +--AS +--BEGIN +-- EXEC('CREATE FUNCTION FakeFunctionTests.AFunction(@a int) RETURNS @t TABLE (a int) AS BEGIN; +-- INSERT INTO @t (a) VALUES (0) RETURN; END;'); + +-- CREATE TABLE #expected (a CHAR(1)); +-- INSERT INTO #expected VALUES('a'); + +-- EXEC tSQLt.FakeFunction @FunctionName = 'FakeFunctionTests.AFunction', @FakeDataSource = '#expected'; + +-- SELECT * INTO #actual FROM FakeFunctionTests.AFunction(123); + +-- EXEC tSQLt.AssertEqualsTable '#expected', '#actual'; +--END; +--GO +--CREATE PROCEDURE FakeFunctionTests.[test Private_PrepareFakeFunctionOutputTable creates snapshot table in passed in schema] +--AS +--BEGIN +-- DECLARE @NewTable NVARCHAR(MAX); + +-- EXEC('CREATE SCHEMA [a random schema];'); + +-- EXEC tSQLt.Private_PrepareFakeFunctionOutputTable @FakeDataSource = 'SELECT 1013 AS a',@SchemaName = 'a random schema', @NewTableName = @NewTable OUT; + +-- SELECT SCHEMA_NAME(O.schema_id) SchemaName INTO #Actual FROM sys.objects O WHERE O.object_id = OBJECT_ID(@NewTable); + +-- SELECT TOP(0) A.* INTO #Expected FROM #Actual A RIGHT JOIN #Actual X ON 1=0; +-- INSERT INTO #Expected +-- VALUES('a random schema'); + +-- EXEC tSQLt.AssertEqualsTable #Expected, #Actual +--END; +--GO - EXEC tSQLt.AssertEqualsTable #Expected, #Actual -END; -GO From 82a0a7ee76d89313505a291adc0ae0b352ccdd40 Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Sun, 31 Oct 2021 22:33:36 -0400 Subject: [PATCH 24/26] wrote more tests, including one to catch potential future missing tests. --- Tests/UndoTestDoublesTests.class.sql | 69 ++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql index 9efba08be..9ecc1ec20 100644 --- a/Tests/UndoTestDoublesTests.class.sql +++ b/Tests/UndoTestDoublesTests.class.sql @@ -260,6 +260,70 @@ BEGIN EXEC tSQLt.FakeFunction @FunctionName = 'UndoTestDoublesTests.aSimpleSVF', @FakeFunctionName = 'UndoTestDoublesTests.aTempSVF'; + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test if FakeFunctionWithSnapshot exists we need to write tests] +AS +BEGIN + IF EXISTS (SELECT * FROM sys.objects WHERE UPPER(name) LIKE 'FAKEFUNCTION_%') + BEGIN + EXEC tSQLt.Fail 'More tests to be written!' + END; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test objects renamed by RemoveObject are restored if there is no other object of the same original name] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleTable'; + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test objects renamed by RemoveObject are restored and conflicting object are deleted] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleTable'; + EXEC ('CREATE PROCEDURE UndoTestDoublesTests.aSimpleTable AS PRINT ''Who came up with that name?'';'); + EXEC tSQLt.UndoTestDoubles; SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc @@ -278,10 +342,7 @@ END; GO /*-- TODO -- functions -- rename object to unique name +- Tests for the case in which people are writing their own test case doubles using both tSQLt.RemoveObject -- no replacement -- random replacement --- fakefunction should put new objects in the same schema -------->START HERE: Do we even need the fakefunction tempobject? --*/ From 8b181cc37ecc14a1868e52e8fdd72979ce8504d0 Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Mon, 1 Nov 2021 22:33:37 -0400 Subject: [PATCH 25/26] done with UndoTestDoubles for now. --- Source/tSQLt.Private_GetDropItemCmd.sfn.sql | 9 ++- Source/tSQLt.UndoTestDoubles.ssp.sql | 6 +- Tests/UndoTestDoublesTests.class.sql | 83 +++++++++++++++++---- 3 files changed, 76 insertions(+), 22 deletions(-) diff --git a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql index 523e27bac..ebbc8c539 100644 --- a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql +++ b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql @@ -1,4 +1,4 @@ -IF OBJECT_ID('tSQLt.Private_GetDropItemCmd') IS NOT NULL DROP PROCEDURE tSQLt.Private_GetDropItemCmd; +IF OBJECT_ID('tSQLt.Private_GetDropItemCmd') IS NOT NULL DROP FUNCTION tSQLt.Private_GetDropItemCmd; GO ---Build+ GO @@ -16,13 +16,14 @@ RETURN SELECT 'DROP ' + CASE @ItemType - WHEN 'P' THEN 'PROCEDURE' - WHEN 'PC' THEN 'PROCEDURE' - WHEN 'U' THEN 'TABLE' WHEN 'IF' THEN 'FUNCTION' WHEN 'TF' THEN 'FUNCTION' WHEN 'FN' THEN 'FUNCTION' WHEN 'FT' THEN 'FUNCTION' + WHEN 'P' THEN 'PROCEDURE' + WHEN 'PC' THEN 'PROCEDURE' + WHEN 'SN' THEN 'SYNONYM' + WHEN 'U' THEN 'TABLE' WHEN 'V' THEN 'VIEW' WHEN 'type' THEN 'TYPE' WHEN 'xml_schema_collection' THEN 'XML SCHEMA COLLECTION' diff --git a/Source/tSQLt.UndoTestDoubles.ssp.sql b/Source/tSQLt.UndoTestDoubles.ssp.sql index 07606ff92..81693da15 100644 --- a/Source/tSQLt.UndoTestDoubles.ssp.sql +++ b/Source/tSQLt.UndoTestDoubles.ssp.sql @@ -39,14 +39,14 @@ BEGIN LL.OriginalName, FakeO.type ObjectType FROM LL - JOIN sys.objects FakeO + LEFT JOIN sys.objects FakeO ON FakeO.object_id = OBJECT_ID(QUOTENAME(LL.SchemaName)+'.'+QUOTENAME(LL.OriginalName)) ) SELECT @cmd = ( SELECT - CASE WHEN L.ParentId IS NULL THEN DC.cmd ELSE '' END+ - ';EXEC tSQLt.Private_RenameObject '''+L.SchemaName+''','''+L.CurrentName+''','''+L.OriginalName+''';' + ISNULL(CASE WHEN L.ParentId IS NULL THEN DC.cmd+';' END,'')+ + 'EXEC tSQLt.Private_RenameObject '''+L.SchemaName+''','''+L.CurrentName+''','''+L.OriginalName+''';' FROM L CROSS APPLY tSQLt.Private_GetDropItemCmd(QUOTENAME(L.SchemaName)+'.'+QUOTENAME(L.OriginalName),L.ObjectType) DC ORDER BY L.SortId DESC, L.Id ASC diff --git a/Tests/UndoTestDoublesTests.class.sql b/Tests/UndoTestDoublesTests.class.sql index 9ecc1ec20..9eab7fe64 100644 --- a/Tests/UndoTestDoublesTests.class.sql +++ b/Tests/UndoTestDoublesTests.class.sql @@ -276,15 +276,6 @@ BEGIN EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; END; GO -CREATE PROCEDURE UndoTestDoublesTests.[test if FakeFunctionWithSnapshot exists we need to write tests] -AS -BEGIN - IF EXISTS (SELECT * FROM sys.objects WHERE UPPER(name) LIKE 'FAKEFUNCTION_%') - BEGIN - EXEC tSQLt.Fail 'More tests to be written!' - END; -END; -GO CREATE PROCEDURE UndoTestDoublesTests.[test objects renamed by RemoveObject are restored if there is no other object of the same original name] AS BEGIN @@ -340,9 +331,71 @@ BEGIN EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; END; GO -/*-- -TODO -- Tests for the case in which people are writing their own test case doubles using both tSQLt.RemoveObject --- no replacement --- random replacement ---*/ +--tSQLt.Run UndoTestDoublesTests +CREATE PROCEDURE UndoTestDoublesTests.[test synonyms are restored] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + EXEC ('CREATE SYNONYM UndoTestDoublesTests.aSimpleSynonym FOR UndoTestDoublesTests.aSimpleTable;'); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleSynonym'; + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleSynonym(i INT);'); + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test doubled synonyms are deleted] +AS +BEGIN + EXEC ('CREATE TABLE UndoTestDoublesTests.aSimpleTable(i INT);'); + EXEC ('CREATE VIEW UndoTestDoublesTests.aSimpleObject AS SELECT 1 X;'); + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #OriginalObjectIds + FROM sys.objects O; + + EXEC tSQLt.RemoveObject @ObjectName='UndoTestDoublesTests.aSimpleObject'; + EXEC ('CREATE SYNONYM UndoTestDoublesTests.aSimpleObject FOR UndoTestDoublesTests.aSimpleTable;'); + + EXEC tSQLt.UndoTestDoubles; + + SELECT O.object_id,SCHEMA_NAME(O.schema_id) schema_name, O.name object_name, O.type_desc + INTO #RestoredObjectIds + FROM sys.objects O; + + SELECT * INTO #ShouldBeEmpty + FROM + ( + SELECT 'Expected' T,* FROM (SELECT * FROM #OriginalObjectIds EXCEPT SELECT * FROM #RestoredObjectIds) E + UNION ALL + SELECT 'Actual' T,* FROM (SELECT * FROM #RestoredObjectIds EXCEPT SELECT * FROM #OriginalObjectIds) A + ) T; + EXEC tSQLt.AssertEmptyTable @TableName = '#ShouldBeEmpty'; +END; +GO +CREATE PROCEDURE UndoTestDoublesTests.[test if FakeFunctionWithSnapshot exists we need to write tests] +AS +BEGIN + IF EXISTS (SELECT * FROM sys.objects WHERE UPPER(name) LIKE 'FAKEFUNCTION_%') + BEGIN + EXEC tSQLt.Fail 'More tests to be written!' + -- Also remove tSQLt_TempObject_s + END; +END; +GO From 5fca51c5ba2e40085c0f4cd202bd14a67e7da51d Mon Sep 17 00:00:00 2001 From: Liz Baron <10554+lizbaron@users.noreply.github.com> Date: Mon, 1 Nov 2021 22:42:31 -0400 Subject: [PATCH 26/26] Clean up from Pull Request Review --- Build/CreateDropClassStatement.ps1 | 71 ++------------------- Source/tSQLt.Private_GetDropItemCmd.sfn.sql | 44 ++++++++++++- Tests/DropClassTests.class.sql | 55 ---------------- Tests/FakeFunctionTests.class.sql | 3 +- 4 files changed, 52 insertions(+), 121 deletions(-) diff --git a/Build/CreateDropClassStatement.ps1 b/Build/CreateDropClassStatement.ps1 index 910db7ad5..21f15af72 100644 --- a/Build/CreateDropClassStatement.ps1 +++ b/Build/CreateDropClassStatement.ps1 @@ -41,70 +41,13 @@ $DropClassStatement = ($RawDropClassStatement.trim()|Where-Object {$_ -ne "" -an Set-Content -Path $OutputFilePath -Value $DropClassStatement; -<# -TODO --> Test this: Empty File TempDropClass.sql file should throw an error -TODO --> Test this: If the $tempPath does not exist, BuildHelper.exe seems to currently throw an error, but does that stop the build? -TODO --> Test this: If the $sourcePath does not exist, BuildHelper.exe seems to currently throw an error, but does that stop the build? -#> - -<# -# Read file and remove all empty lines -$fileContent = (Get-Content -path ($tempPath+"TempDropClass.sql")).trim() | Where-Object {$_ -ne "" -and $_ -notmatch "^GO(\s.*)?"}; - -# Make sure that the file is not empty -if($fileContent.length -eq 0) { - throw "Length of fileContent is zero"; -} - -# trim lines and jam them all together with a space in between -$singleLineContent = $fileContent -join " "; - - - -#> - -<# -if ($LoginTrimmed -match '((.*[-]U)|(.*[-]P))+.*'){ - $AuthenticationString = $LoginTrimmed -replace '^((\s*([-]U\s+)(?\w+)\s*)|(\s*([-]P\s+)(?\S+)\s*))+$', 'User Id=${user};Password="${password}"' -} -#> - -<# We need to replace this target with a exec target which calls a ps1 to do this work. -In addition, we will add a replace which will "in line" the relevant text of tSQLt.Private_GetDropItemCmd. - -"suggested (◔_◔)" regex: tSQLt.Private_GetDropItemCmd(Ds.FullName, Ds.ItemType) -'tSQLt\s*.\s*Private_GetDropItemCmd\s*\(\s*([^,]*)\s*,\s*([^)]*)\s*\)' - -It should look like this: - - - -DECLARE @ClassName NVARCHAR(MAX) ='tSQLt';BEGIN DECLARE @Cmd NVARCHAR(MAX); WITH ObjectInfo(FullName, ItemType) AS ( SELECT QUOTENAME(SCHEMA_NAME(O.schema_id))+'.'+QUOTENAME(O.name), O.type FROM sys.objects AS O WHERE O.schema_id = SCHEMA_ID(@ClassName) ), TypeInfo(FullName, ItemType) AS ( SELECT QUOTENAME(SCHEMA_NAME(T.schema_id))+'.'+QUOTENAME(T.name), 'type' FROM sys.types AS T WHERE T.schema_id = SCHEMA_ID(@ClassName) ), XMLSchemaInfo(FullName, ItemType) AS ( SELECT QUOTENAME(SCHEMA_NAME(XSC.schema_id))+'.'+QUOTENAME(XSC.name), 'xml_schema_collections' FROM sys.xml_schema_collections AS XSC WHERE XSC.schema_id = SCHEMA_ID(@ClassName) ), SchemaInfo(FullName, ItemType) AS ( SELECT QUOTENAME(S.name), 'schema' FROM sys.schemas AS S WHERE S.schema_id = SCHEMA_ID(PARSENAME(@ClassName,1)) ), DropStatements(no,FullName,ItemType) AS ( SELECT 10, FullName, ItemType FROM ObjectInfo UNION ALL SELECT 20, FullName, ItemType FROM TypeInfo UNION ALL SELECT 30, FullName, ItemType FROM XMLSchemaInfo UNION ALL SELECT 10000, FullName, ItemType FROM SchemaInfo ), StatementBlob(xml)AS ( SELECT GDIC.cmd [text()] FROM DropStatements DS CROSS APPLY -( -SELECT - 'DROP ' + - CASE @ItemType - WHEN 'P' THEN 'PROCEDURE' - WHEN 'PC' THEN 'PROCEDURE' - WHEN 'U' THEN 'TABLE' - WHEN 'IF' THEN 'FUNCTION' - WHEN 'TF' THEN 'FUNCTION' - WHEN 'FN' THEN 'FUNCTION' - WHEN 'FT' THEN 'FUNCTION' - WHEN 'V' THEN 'VIEW' - WHEN 'type' THEN 'TYPE' - WHEN 'xml_schema_collection' THEN 'XML SCHEMA COLLECTION' - WHEN 'schema' THEN 'SCHEMA' - END+ - ' ' + - @FullName + - ';' AS cmd -) -GDIC ORDER BY no FOR XML PATH(''), TYPE ) SELECT @Cmd = xml.value('/', 'NVARCHAR(MAX)') FROM StatementBlob; EXEC(@Cmd); END; - -#> - Log-Output '<#--=======================================================================-->' Log-Output '' Log-Output '<#--=======================================================================-->' - \ No newline at end of file + + +<# TODO +--> Test this: Empty File TempDropClass.sql file should throw an error +--> Test this: If the $tempPath does not exist, BuildHelper.exe seems to currently throw an error, but does that stop the build? +--> Test this: If the $sourcePath does not exist, BuildHelper.exe seems to currently throw an error, but does that stop the build? +#> diff --git a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql index ebbc8c539..7148c9929 100644 --- a/Source/tSQLt.Private_GetDropItemCmd.sfn.sql +++ b/Source/tSQLt.Private_GetDropItemCmd.sfn.sql @@ -33,4 +33,46 @@ SELECT @FullName + ';' AS cmd /*SnipEnd: CreateDropClassStatement.ps1*/ -GO \ No newline at end of file +GO +---Build- +/* +Object type: + AF = Aggregate function (CLR) +- C = CHECK constraint +- D = DEFAULT (constraint or stand-alone) +- F = FOREIGN KEY constraint ++ FN = SQL scalar function + FS = Assembly (CLR) scalar-function ++ FT = Assembly (CLR) table-valued function ++ IF = SQL inline table-valued function + IT = Internal table ++ P = SQL Stored Procedure ++ PC = Assembly (CLR) stored-procedure +- PG = Plan guide +- PK = PRIMARY KEY constraint +? R = Rule (old-style, stand-alone) + RF = Replication-filter-procedure +- S = System base table + SN = Synonym + SO = Sequence object ++ U = Table (user-defined) ++ V = View +- EC = Edge constraint + +Applies to: SQL Server 2012 (11.x) and later. + SQ = Service queue + - TA = Assembly (CLR) DML trigger + + TF = SQL table-valued-function + - TR = SQL DML trigger + TT = Table type + - UQ = UNIQUE constraint + ? X = Extended stored procedure + +Applies to: SQL Server 2014 (12.x) and later, Azure SQL Database, Azure Synapse Analytics, Analytics Platform System (PDW). + ? ST = STATS_TREE + +Applies to: SQL Server 2016 (13.x) and later, Azure SQL Database, Azure Synapse Analytics, Analytics Platform System (PDW). + ET = External Table + +Also think about schema bound objects (an exercise in sorting?? because they need to be dropped in the correct order so that you don't drop parent objects before the child objects) +*/ diff --git a/Tests/DropClassTests.class.sql b/Tests/DropClassTests.class.sql index cafb40732..a397a6c5e 100644 --- a/Tests/DropClassTests.class.sql +++ b/Tests/DropClassTests.class.sql @@ -129,61 +129,6 @@ CREATE FUNCTION MyTestClass.AClrTvf(@p1 NVARCHAR(MAX), @p2 NVARCHAR(MAX)) END END; GO - --CASE type *WHEN 'P' THEN 'PROCEDURE' - -- *WHEN 'PC' THEN 'PROCEDURE' - -- *WHEN 'U' THEN 'TABLE' - -- WHEN 'IF' THEN 'FUNCTION' - -- *WHEN 'TF' THEN 'FUNCTION' - -- WHEN 'FN' THEN 'FUNCTION' - -- *WHEN 'FT' THEN 'FUNCTION' - -- *WHEN 'V' THEN 'VIEW' - -- *XML - -- *UDDT -/* -Object type: - AF = Aggregate function (CLR) -- C = CHECK constraint -- D = DEFAULT (constraint or stand-alone) -- F = FOREIGN KEY constraint -+ FN = SQL scalar function - FS = Assembly (CLR) scalar-function -+ FT = Assembly (CLR) table-valued function -+ IF = SQL inline table-valued function - IT = Internal table -+ P = SQL Stored Procedure -+ PC = Assembly (CLR) stored-procedure -- PG = Plan guide -- PK = PRIMARY KEY constraint -? R = Rule (old-style, stand-alone) - RF = Replication-filter-procedure -- S = System base table - SN = Synonym - SO = Sequence object -+ U = Table (user-defined) -+ V = View -- EC = Edge constraint - -Applies to: SQL Server 2012 (11.x) and later. - SQ = Service queue - - TA = Assembly (CLR) DML trigger - + TF = SQL table-valued-function - - TR = SQL DML trigger - TT = Table type - - UQ = UNIQUE constraint - ? X = Extended stored procedure - -Applies to: SQL Server 2014 (12.x) and later, Azure SQL Database, Azure Synapse Analytics, Analytics Platform System (PDW). - ? ST = STATS_TREE - -Applies to: SQL Server 2016 (13.x) and later, Azure SQL Database, Azure Synapse Analytics, Analytics Platform System (PDW). - ET = External Table - -Also think about schema bound objects (an exercise in sorting?? because they need to be dropped in the correct order so that you don't drop parent objects before the child objects) -*/ - -/*-- random line because someone can't use ctrl-9 through zoom -EXEC tSQLt.Run DropClassTests ---*/ CREATE PROC DropClassTests.[test removes SSPs] AS BEGIN diff --git a/Tests/FakeFunctionTests.class.sql b/Tests/FakeFunctionTests.class.sql index 8364016aa..9e7ab7a03 100644 --- a/Tests/FakeFunctionTests.class.sql +++ b/Tests/FakeFunctionTests.class.sql @@ -661,7 +661,8 @@ BEGIN EXEC tSQLt.AssertEqualsTable 'FakeFunctionTests.Expected', @NewTable; END; GO --- THIS CODE FEELS UNNECESSARY +-- THIS CODE CREATES THE SNAPSHOT. +-- When we revisit creating something like FakeFunctionWithSnapshot, we should refer back. -- DECLARE @newTbleName NVARCHAR(MAX); -- EXEC tSQLt.Private_PrepareFakeFunctionOutputTable @FakeDataSource, @newTbleName OUTPUT;