i am running a MERGE
statement against an SQL Server 2008 R2 database (which is in 2008 compatibility mode).
The exact merge statement SQL is irrelevant, but here is an example of a MERGE statement:
MERGE Users
USING (VALUES
('{77410DC5-7A3E-4F1A-82C6-8EFB3068DE66}')
) AS rows(UserGUID)
ON Users.UserName = rows.UserName
WHEN NOT MATCHED BY SOURCE THEN
DELETE; --always end MERGE with semi-colon
When executing this statement from SSMS, or from a client PC running Windows 7, it executes correctly.
But when my software is running on a client PC that is Windows XP or Windows Server 2003 R2, the sql CommandText
is being altered before reaching the server. In the SQL Server Profiler, i can see the SQL being executed is:
exec MERGE Users
USING (VALUES
('{77410DC5-7A3E-4F1A-82C6-8EFB3068DE66}')
) AS rows(UserGUID)
ON Users.UserName = rows.UserName
WHEN NOT MATCHED BY SOURCE THEN
DELETE; --always end MERGE with semi-colon
Which is not valid SQL, and SQL Server throws the error:
Incorrect syntax near the keyword 'MERGE'
You can confirm that it is invalid SQL by trying to execute it against your SQL Server 2008 R2 database.
Peter Boulton reported the same issue on the Microsoft forums:
SQL MERGE syntax works with Win7 SP1 client but fails with earlier platforms
I believe ADO parses the SQL before it sends it to the server. The data access components were updated for Win7 SP1 and also WinServer 2008 R2. However, I believe XP SP3's data access components predate SQL Server 2008.
That's why the SQL works from Win7 SP1 but not from XP.
My 'solution' was to wrap the SQL in an
EXEC
so that ADO allows it through, as in:EXEC('MERGE....etc.')
His hack certainly works, change:
MERGE Users ...
into
EXEC('MERGE Users' ...)
but i'd like to come up with the real solution. I don't know who in the chain is responsible for altering my command text:
ADO -> OLEDB -> SQLOLEDB -> SQL Server
but i want them to stop.
How do i, through ADO, specify my command text, and have SQLOLEDB not modify it?
Right now my code is1:
String sqlCommandText = "MERGE Users" //snip;
int recordsAffected;
connection.Execute(
sqlCommandText,
out recordsAffected,
adCmdText | adExecuteNoRecords);
i do not see any ExecuteOptionEnum
, or CommandTypeEnum
to tell ADO, and underlying providers, to treat the text as raw.
For now the fixup hack is:
sql = "MERGE Users" ...
ExecuteNoRecords(connection, sql);
with the modified helper:
int ExecuteNoRecords(Connection connection, String sql)
{
//20130611: Fix bug in ADO that mangles/breaks SQL it doesn't understand (e.g. MERGE on Windows XP)
String obfuscatedCommandText = 'EXEC(' + QuotedStr(sql)+ ')';
int recordsAffected;
connection.Execute(obfuscatedCommandText, out recordsAffected, adCmdText | adExecuteNoRecords);
return recordsAffected;
}
Rather than wrapping the entire statement in EXEC(...)
, i found a safer trick to defeating ADO is to precede the statement with a comment. Even an empty comment will do:
--
MERGE Users
USING (VALUES ...
In reality you will want to have some text explaining that the empty comment line is critical to making the query work:
--Leading comment to thwart ADO from mangling MERGE on Windows XP/2003R2
MERGE Users
USING (VALUES ...
Since there is no solution, and ADO (while not dead) is done, the hack is the answer.
Never issue a MERGE
statement without a leading comment line:
--Dummy leading comment line to thwart ADO from mangling MERGE on Windows XP/2003R2
MERGE Users
USING (VALUES ...