Dynamic SQL and Code Generation IN THIS CHAPTER Executing dynamic SQL Parameterized queries The risk of SQL injection Generating stored procedures Alternatives to dynamic SQL Folks laugh
Trang 1managers understand that in the long run it will save money — and their job.
Trang 2Dynamic SQL and Code Generation
IN THIS CHAPTER
Executing dynamic SQL Parameterized queries The risk of SQL injection Generating stored procedures Alternatives to dynamic SQL
Folks laugh when they hear that my favorite project is based on the
notion that T-SQL is a great language for code generation Nordic (New
Object/Relational Design) is essentially a code-generation tool that uses
dynamic SQL to create tables, stored procedures, and views T-SQL works rather
well for code generation, thank you
The term dynamic SQL has a couple of differing definitions Some say it describes
any SQL query submitted by a client other than a stored procedure That’s not
true SQL submitted from the client is better known as ad-hoc SQL
It’s more accurate to say that dynamic SQL describes any SQL DML statement
assembled dynamically at runtime as a string and then submitted
Dynamic SQL is very useful for several tasks:
■ Multiple possible query criteria can be dynamically assembled into
customFROM,WHERE, andORDER BYclauses for flexible queries
■ Code can respond to the schema of the database and generate
appropri-ate triggers, CRUD stored procedures, and views
■ Dynamic code can auto-generate very consistent stored procedures
However, note the following issues when developing dynamic SQL:
■ Dynamic SQL that includes user entries inWHEREclauses can be open
to SQL injection attacks
■ Poorly written dynamic SQL queries often include extra table references
and perform poorly
■ T-SQL code that generates T-SQL code can be tricky to debug
Trang 3EXEC[UTE] (’T-SQL batch’);
For example, the followingEXECcommand executes a simpleSELECTstatement:
USE Family;
EXEC (’SELECT LastName FROM Person WHERE PersonID = 12;’);
Result:
LastName -Halloway
The security context of executing code should be considered when working with theEXECUTE
command You can control which user account the Database Engine uses to validate permissions on
objects that are referenced by the module The following code uses theEXECUTE ASsyntax to execute
the query as the user Joe:
Use OBXKites EXECUTE AS ‘Joe’ select * from Products
Another aspect of theEXECUTEcommand is the capability to execute the code at a linked server,
instead of at the local server The code is submitted to the linked server and the results are returned to
the local server:
EXECUTE (’Code’) AT MAUI/SYDNEY;
sp_executeSQL
Another method of executing dynamic SQL is to use thesp_executesqlsystem stored procedure
It offers greater compatibility with complex SQL queries than the straightEXECUTEcommand In
several situations I have found that theEXECUTEcommand failed to execute the dynamic SQL, but
sp_executesqlworked flawlessly:
EXEC sp_Executesql
‘T-SQL query’,
‘Parameters Definition’, Parameter, Parameter ;
Parameterized queries
Sometimes it’s easier to create queries based on a number of parameters because it usually avoids the
need for concatenating strings The query and the definition must be Unicode strings
Parameters provide optimization If the T-SQL query has the same parameters for each execution, then
these parameters can be passed tosp_executesqlso the SQL query plan can be stored, and future
executions will be optimized The following example executes the same query from thePersontable
Trang 4in theFamilydatabase, but this example uses parameters (theNbefore the parameters is necessary
becausesp_executesqlrequires Unicode strings):
EXEC sp_executesql
N’SELECT LastName
FROM Person
WHERE PersonID = @PersonSelect;’,
N’@PersonSelect INT’,
@PersonSelect = 12;
Result:
LastName
-Halloway
Developing dynamic SQL code
Building a dynamic SQL string usually entails combining aSELECTcolumn’s literal string with a
more fluidFROMclause andWHEREclause While any part of the query can be dynamic, normally the
SELECT@columnsis not
Once the SQL string is complete, the SQL statement is executed by means of thesp_executesql
command The example that follows builds both customFROMandWHEREclauses based on the user’s
requirements
Within the batch, theNeedsAndbit variable tracks the need for anAndseparator betweenWHERE
clause conditions If the product category is specified, then the initial portion of theSELECTstatement
includes the required joins to fetch theProductCategorytable TheWHEREclause portion of the
batch examines each possible user criterion If the user has specified a criterion for that column, then
the column, with its criterion, is added to the @SQLWherestring
Real-world dynamic SQL sometimes includes dozens of complex options The following code listing uses
three possible columns for optional user criteria:
USE OBXKites;
DECLARE
@SQL NVARCHAR(1024),
@SQLWhere NVARCHAR(1024),
@NeedsAnd BIT,
User Parameters
@ProductName VARCHAR(50),
@ProductCode VARCHAR(10),
@ProductCategory VARCHAR(50);
Initialize Variables
SET @NeedsAnd = 0;
SET @SQLWhere = ‘’;
Trang 5Set up initial SQL Select
IF @ProductCategory IS NULL SET @SQL = ‘Select ProductName from Product’;
ELSE SET @SQL = ‘Select ProductName
from Product Join ProductCategory
on Product.ProductCategoryID
= ProductCategory.ProductCategoryID’;
Build the Dynamic Where Clause
IF @ProductName IS NOT NULL BEGIN;
SET @SQLWhere = ‘ProductName = ’ + @ProductName;
SET @NeedsAnd = 1;
END;
IF @ProductCode IS NOT NULL BEGIN;
IF @NeedsAnd = 1 SET @SQLWhere = @SQLWhere + ‘ and ’;
SET @SQLWhere = ‘Code = ’ + @ProductCode;
SET @NeedsAnd = 1;
END;
IF @ProductCategory IS NOT NULL BEGIN;
IF @NeedsAnd = 1 SET @SQLWhere = @SQLWhere + ‘ and ’;
SET @SQLWhere = ‘ProductCategory = ’ + @ProductCategory;
SET @NeedsAnd = 1;
END;
Assemble the select and the where portions of the dynamic SQL
IF @SQLWhere <> ‘’
SET @SQL = @SQL + ‘ where ’ + @SQLWhere + ‘;’;
∼∼Use this for testing and debug use only
PRINT @SQL;
EXEC sp_executesql @SQL
Trang 6The results shown are both the printed text of the dynamic SQL and the data returned from the
execu-tion of the dynamic SQL statement:
Select ProductName from Product where Code = 1001;
Name
-Basic Box Kite 21 inch
Code generation
The following example may seem a bit complex, but it’s a great real-world demonstration of T-SQL
code generation It’s from the Nordic database and this is the piece of code that actually generates the
stored procedures and views for each class This procedure is called every time a new class or attribute
is added or changed
A few points in the code worth noting:
■ + CHAR(13) + CHAR(10)are added to the generated code to make it more readable
■ The columns (attributes) are built up in the@SQLStrvariable first, then the dynamicFROM
clause is added
■ A cursor is used to iterate through the columns and tables to build up the custom stored
procedure and view
■ The@SQLStris assembled once and the@GenStris modified to first create the view and
then create the stored procedure
■ The dynamic SQL variables@SQLStrand@GenStrare both declared asNVARCHAR(MAX)
■ Any custom columns are automatically enclosed in square brackets to avoid any syntax errors
Here’s the code:
Gen Class
CREATE
alter
PROC dbo.GenClass
(@ClassName NVARCHAR(50))
AS
SET NoCount ON
EXEC IncVersion ‘Class Design’
DECLARE @GenStr NVARCHAR(MAX),
@SQLStr NVARCHAR(MAX),
@ClassStr NVARCHAR(MAX),
@CurrentClass CHAR(100),
Trang 7+ CHAR(13) + CHAR(10) + ‘ sc.ClassName + ’‘:’‘ + StateName as [State],’
+ CHAR(13) + CHAR(10) + ‘ ObjectCode as [Object:ObjectCode], Name1 as [Object:Name1], Name2 as [Object:Name2],’
+ CHAR(13) + CHAR(10) + ‘ NULL as [Object:Description],’ + CHAR(13) + CHAR(10)
+ ‘ o.Created as [Object:Created], o.Modified as [Object:Modified], o.Version as [Object:Version] ’
+ CHAR(13) + CHAR(10)
Walk through custom attributes DECLARE cAttributes CURSOR FAST_FORWARD FOR SELECT REPLACE(ClassName, ‘ ’, ‘’), AttributeName FROM dbo.Attributes(@ClassName)
OPEN cAttributes prime the cursor FETCH cAttributes INTO @CurrentClass, @CurrentAttrib WHILE @@Fetch_Status = 0
BEGIN SET @SQLStr = @SQLStr + ‘ , CC’ + RTRIM(@CurrentClass) + ‘.’
+ RTRIM(@CurrentAttrib) + ‘ as [’ + RTRIM(@CurrentClass) + ‘:’
+ RTRIM(@CurrentAttrib) + ‘]’ + CHAR(13) + CHAR(10) fetch next
FETCH cAttributes INTO @CurrentClass, @CurrentAttrib END
CLOSE cAttributes DEALLOCATE cAttributes
FROM base metadata tables SET @SQLStr = @SQLStr + ‘ FROM dbo.Object AS o’ + CHAR(13) + CHAR(10) + ‘ JOIN dbo.Class AS c’ + CHAR(13) + CHAR(10)
+ ‘ ON o.ClassID = c.ClassID’ + CHAR(13) + CHAR(10) + ‘ LEFT JOIN dbo.State AS s’ + CHAR(13) + CHAR(10) + ‘ ON o.StateID = s.StateID’ + CHAR(13) + CHAR(10) + ‘ LEFT JOIN dbo.Class AS sc’ + CHAR(13) + CHAR(10) + ‘ ON s.ClassID = sc.ClassID’ + CHAR(13) + CHAR(10)
FROM dynamic classes DECLARE cClasses CURSOR FAST_FORWARD
Set Difference Query FOR SELECT REPLACE(ClassName, ‘ ’, ‘’)
Trang 8FROM SuperClasses(dbo.GetClassID(@ClassName))
ORDER BY ClassID DESC
OPEN cClasses
FETCH cClasses INTO @CurrentClass prime the cursor
WHILE @@Fetch_Status = 0
BEGIN
SET @SQLStr = @SQLStr + ‘ JOIN dbo.Obj’
+ RTRIM(@CurrentClass) + ‘ CC’
+ RTRIM(@CurrentClass) + CHAR(13) + CHAR(10)
+ ‘ ON o.ObjectID = CC’
+ RTRIM(@CurrentClass) + ‘.ObjectID’ + CHAR(13) + CHAR(10)
FETCH cClasses INTO @CurrentClass fetch next
END
CLOSE cClasses
DEALLOCATE cClasses
Drop and Create View
SET @GenStr = ‘IF OBJECT_ID(’’v’ + RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
+ ‘’’)’ + ‘ IS NOT NULL DROP VIEW dbo.v’
+ RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
EXEC sp_executesql @GenStr
SET @GenStr = ‘CREATE VIEW dbo.v’
+ RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
+ CHAR(13) + CHAR(10)
+ ‘ AS ’ + CHAR(13) + CHAR(10) + @SQLStr
EXEC sp_executesql @GenStr
Standard Where Clause
SET @SQLStr = @SQLStr + CHAR(13) + CHAR(10)
+ ‘ WHERE o.ObjectID = @ObjectID weirdness aboundeth’
Drop and Create Proc
SET @GenStr = ‘IF OBJECT_ID(’’p’ + RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
+ ‘’’)’ + ‘ IS NOT NULL DROP PROC dbo.p’
+ RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
EXEC sp_executesql @GenStr
SET @GenStr = ‘CREATE PROC dbo.p’
+ RTRIM(REPLACE(@ClassName, ‘ ’, ‘’))
+ ‘ (@ObjectID INT) AS SET NoCount ON ’ + @SQLStr
EXEC sp_executesql @GenStr
RETURN
Trang 9SQL injection is a hacker technique that appends SQL code to a parameter that is later executed as
dynamic SQL What makes SQL injection so dangerous is that anyone with access to the organization’s
website who can enter data into a text field can attempt an SQL injection attack There are several
malicious techniques that involve appending code or modifying theWHEREclause Before learning how
to prevent it, it’s important to understand how it works, as the following sections explain
Appending malicious code
Adding a statement terminator, another SQL command, and a comment, a hacker can pass code into the
execute string For example, if the parameter passed in is
123’; Delete OrderDetail
the parameter, including thedeleteDDL command, placed within a dynamic SQL string would
execute as a batch:
SELECT * FROM Customers
WHERE CustomerID = ‘123’; Delete OrderDetail ’
The statement terminator ends the intended code and the delete command looks to SQL Server like
nothing more than the second line in the batch The quotes would normally cause a syntax error, but
the comment line solves that problem for the hacker The result? An emptyOrderDetailtable
Other popular appended commands include runningxp_commandshellor setting thesapassword
Or 1 =1
Another SQL injection technique is to modify theWHEREclause so that more rows are selected than
intended
If the user enters the following string into the user text box:
123’ or 1=1
then the1=1(always true) condition is injected into theWHEREclause The injected hyphens comment
out the closing quote:
SELECT * FROM Customers
WHERE CustomerID = ‘123’ or 1=1 ’
With every row selected by the SQL statement, what happens next depends on how the rest of the
sys-tem handles multiple rows Regardless, it’s not what should happen
Trang 10Password? What password?
Another creative use of SQL injection is to comment out part of the intended code Suppose the user
enters the following in the web form:
UserName: Joe’
Password : who cares
The resulting SQL statement might read as follows:
SELECT USerID
FROM Users
WHERE UserName = ‘Joe’ ’ AND Password = ‘who cares’
The comment in the username causes SQL Server to ignore the rest of theWHEREclause, including the
password condition
Preventing SQL Server injection attacks
Several development techniques can prevent SQL injection:
■ UseEXECUTE ASand carefully define the roles so that statements don’t have permission to
drop tables
■ Use DRI referential integrity to prevent deleting primary table rows with dependent secondary
table rows
■ Never let user input mixed with dynamic SQL in a web form execute as submitted SQL
Always pass all parameters through a stored procedure
■ Check for and reject parameters that include statement terminators, comments, orxp_
■ Test your database using the SQL injection techniques described above
SQL injection is a real threat If your application is exposed to entry from the Internet and you haven’t
taken steps to prevent SQL injection, it’s only a matter of time before your database is attacked
Best Practice
For a flexible search procedure in Nordic, I’ve started using a new practice of parsing and joining Rather
than build a dynamic SQL WHERE clause, I allow the application to pass in a multiple-word search string
This is parsed into a table with each word becoming a row The search table is then joined with the various
data points that can be searched For an example of this technique, turn back to Chapter 28, ‘‘Building Out
the Data Abstraction Layer,’’ or download the latest version of Nordic from CodePlex.com