From d3fe6d82d6de468b006c8899c6b7aeb276b3a0a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kuthan?= Date: Fri, 12 Jul 2019 14:33:38 +0200 Subject: [PATCH] SqlsrvDriver: Correct escaping of special characters (N prefix) (#332) In case nvarchar type is used and e.g. chinese have to be saved, there have to be N in front of the value, this escaping works fine for varchar columns as well. --- src/Dibi/Drivers/SqlsrvDriver.php | 2 +- src/Dibi/Drivers/SqlsrvReflector.php | 2 +- tests/dibi/Fluent.select.phpt | 5 +- tests/dibi/Translator.conditions.phpt | 13 ++- tests/dibi/Translator.phpt | 121 +++++++++++++++++++++----- 5 files changed, 118 insertions(+), 25 deletions(-) diff --git a/src/Dibi/Drivers/SqlsrvDriver.php b/src/Dibi/Drivers/SqlsrvDriver.php index 853c63d9e..3e7c7136f 100644 --- a/src/Dibi/Drivers/SqlsrvDriver.php +++ b/src/Dibi/Drivers/SqlsrvDriver.php @@ -196,7 +196,7 @@ public function createResultDriver($resource): SqlsrvResult */ public function escapeText(string $value): string { - return "'" . str_replace("'", "''", $value) . "'"; + return "N'" . str_replace("'", "''", $value) . "'"; } diff --git a/src/Dibi/Drivers/SqlsrvReflector.php b/src/Dibi/Drivers/SqlsrvReflector.php index 5eda90b32..28049537e 100644 --- a/src/Dibi/Drivers/SqlsrvReflector.php +++ b/src/Dibi/Drivers/SqlsrvReflector.php @@ -100,7 +100,7 @@ public function getColumns(string $table): array */ public function getIndexes(string $table): array { - $keyUsagesRes = $this->driver->query(sprintf('EXEC [sys].[sp_helpindex] @objname = N%s', $this->driver->escapeText($table))); + $keyUsagesRes = $this->driver->query(sprintf('EXEC [sys].[sp_helpindex] @objname = %s', $this->driver->escapeText($table))); $keyUsages = []; while ($row = $keyUsagesRes->fetch(true)) { $keyUsages[$row['index_name']] = explode(',', $row['index_keys']); diff --git a/tests/dibi/Fluent.select.phpt b/tests/dibi/Fluent.select.phpt index 723cc2cbd..ee0366213 100644 --- a/tests/dibi/Fluent.select.phpt +++ b/tests/dibi/Fluent.select.phpt @@ -134,7 +134,10 @@ $fluent = $conn->select('*') ->where(['x' => 'a', 'b', 'c']); Assert::same( - reformat('SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = \'a\') AND (b) AND (c)'), + reformat([ + 'sqlsrv' => "SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = N'a') AND (b) AND (c)", + "SELECT * FROM [me] AS [t] WHERE col > 10 AND ([x] = 'a') AND (b) AND (c)", + ]), (string) $fluent ); diff --git a/tests/dibi/Translator.conditions.phpt b/tests/dibi/Translator.conditions.phpt index 4af754d64..1a552700d 100644 --- a/tests/dibi/Translator.conditions.phpt +++ b/tests/dibi/Translator.conditions.phpt @@ -60,13 +60,22 @@ WHERE [id] > 0 // nested condition Assert::match( - reformat(" + reformat([ + 'sqlsrv' => " +SELECT * +FROM [customers] +WHERE + [name] LIKE N'xxx' + /* AND ...=1 */ + /* 1 LIMIT 10 */", + " SELECT * FROM [customers] WHERE [name] LIKE 'xxx' /* AND ...=1 */ - /* 1 LIMIT 10 */"), + /* 1 LIMIT 10 */", + ]), $conn->translate(' SELECT * diff --git a/tests/dibi/Translator.phpt b/tests/dibi/Translator.phpt index 3b7e4328e..74a290c04 100644 --- a/tests/dibi/Translator.phpt +++ b/tests/dibi/Translator.phpt @@ -16,7 +16,10 @@ $conn = new Dibi\Connection($config + ['formatDateTime' => "'Y-m-d H:i:s.u'", 'f // Dibi detects INSERT or REPLACE command & booleans Assert::same( - reformat("REPLACE INTO [products] ([title], [price]) VALUES ('Drticka', 318)"), + reformat([ + 'sqlsrv' => "REPLACE INTO [products] ([title], [price]) VALUES (N'Drticka', 318)", + "REPLACE INTO [products] ([title], [price]) VALUES ('Drticka', 318)", + ]), $conn->translate('REPLACE INTO [products]', [ 'title' => 'Drticka', 'price' => 318, @@ -31,7 +34,10 @@ $array = [ 'brand' => null, ]; Assert::same( - reformat('INSERT INTO [products] ([title], [price], [brand]) VALUES (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL) , (\'Super Product\', 12, NULL)'), + reformat([ + 'sqlsrv' => "INSERT INTO [products] ([title], [price], [brand]) VALUES (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL) , (N'Super Product', 12, NULL)", + "INSERT INTO [products] ([title], [price], [brand]) VALUES ('Super Product', 12, NULL) , ('Super Product', 12, NULL) , ('Super Product', 12, NULL)", + ]), $conn->translate('INSERT INTO [products]', $array, $array, $array) ); @@ -43,14 +49,20 @@ $array = [ ['pole' => 'hodnota3', 'bit' => 1], ]; Assert::same( - reformat('INSERT INTO [products] ([pole], [bit]) VALUES (\'hodnota1\', 1) , (\'hodnota2\', 1) , (\'hodnota3\', 1)'), + reformat([ + 'sqlsrv' => "INSERT INTO [products] ([pole], [bit]) VALUES (N'hodnota1', 1) , (N'hodnota2', 1) , (N'hodnota3', 1)", + "INSERT INTO [products] ([pole], [bit]) VALUES ('hodnota1', 1) , ('hodnota2', 1) , ('hodnota3', 1)", + ]), $conn->translate('INSERT INTO [products] %ex', $array) ); // Dibi detects UPDATE command Assert::same( - reformat("UPDATE [colors] SET [color]='blue', [order]=12 WHERE [id]=123"), + reformat([ + 'sqlsrv' => "UPDATE [colors] SET [color]=N'blue', [order]=12 WHERE [id]=123", + "UPDATE [colors] SET [color]='blue', [order]=12 WHERE [id]=123", + ]), $conn->translate('UPDATE [colors] SET', [ 'color' => 'blue', 'order' => 12, @@ -85,17 +97,26 @@ $e = Assert::exception(function () use ($conn) { Assert::same('SELECT **Invalid combination of type stdClass and modifier %s** , **Unknown or unexpected modifier %m**', $e->getSql()); Assert::same( - reformat('SELECT * FROM [table] WHERE id=10 AND name=\'ahoj\''), + reformat([ + 'sqlsrv' => "SELECT * FROM [table] WHERE id=10 AND name=N'ahoj'", + "SELECT * FROM [table] WHERE id=10 AND name='ahoj'", + ]), $conn->translate('SELECT * FROM [table] WHERE id=%i AND name=%s', 10, 'ahoj') ); Assert::same( - reformat('TEST ([cond] > 2) OR ([cond2] = \'3\') OR (cond3 < RAND())'), + reformat([ + 'sqlsrv' => "TEST ([cond] > 2) OR ([cond2] = N'3') OR (cond3 < RAND())", + "TEST ([cond] > 2) OR ([cond2] = '3') OR (cond3 < RAND())", + ]), $conn->translate('TEST %or', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()']) ); Assert::same( - reformat('TEST ([cond] > 2) AND ([cond2] = \'3\') AND (cond3 < RAND())'), + reformat([ + 'sqlsrv' => "TEST ([cond] > 2) AND ([cond2] = N'3') AND (cond3 < RAND())", + "TEST ([cond] > 2) AND ([cond2] = '3') AND (cond3 < RAND())", + ]), $conn->translate('TEST %and', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()']) ); @@ -114,7 +135,10 @@ $where['age'] = null; $where['email'] = 'ahoj'; $where['id%l'] = [10, 20, 30]; Assert::same( - reformat('SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = \'ahoj\') AND ([id] IN (10, 20, 30))'), + reformat([ + 'sqlsrv' => "SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = N'ahoj') AND ([id] IN (10, 20, 30))", + "SELECT * FROM [table] WHERE ([age] IS NULL) AND ([email] = 'ahoj') AND ([id] IN (10, 20, 30))", + ]), $conn->translate('SELECT * FROM [table] WHERE %and', $where) ); @@ -259,7 +283,13 @@ INTO OUTFILE '/tmp/result\'.txt' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\\\"' LINES TERMINATED BY '\\\\n' ", - "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT + 'sqlsrv' => "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT +CONCAT(last_name, N', ', first_name) AS full_name +GROUP BY [user] +HAVING MAX(salary) > %i 123 +INTO OUTFILE N'/tmp/result''.txt' +FIELDS TERMINATED BY N',' OPTIONALLY ENCLOSED BY N'\"' +LINES TERMINATED BY N'\\n'", "SELECT DISTINCT HIGH_PRIORITY SQL_BUFFER_RESULT CONCAT(last_name, ', ', first_name) AS full_name GROUP BY [user] HAVING MAX(salary) > %i 123 @@ -321,6 +351,27 @@ WHERE (`test`.`a` LIKE '1995-03-01' OR `false`= 0 OR `str_null`=NULL OR `str_not_null`='hello' +LIMIT 10", + 'sqlsrv' => "SELECT * +FROM [db].[table] +WHERE ([test].[a] LIKE '1995-03-01' + OR [b1] IN ( 1, 2, 3 ) + OR [b2] IN (N'1', N'2', N'3' ) + OR [b3] IN ( ) + OR [b4] IN ( N'one', N'two', N'three' ) + OR [b5] IN ([col1] AS [one], [col2] AS [two], [col3] AS [thr.ee] ) + OR [b6] IN (N'one', N'two', N'thr.ee') + OR [b7] IN (NULL) + OR [b8] IN (RAND() [col1] > [col2] ) + OR [b9] IN (RAND(), [col1] > [col2] ) + OR [b10] IN ( ) + AND [c] = N'embedded '' string' + OR [d]=10 + OR [e]=NULL + OR [true]= 1 + OR [false]= 0 + OR [str_null]=NULL + OR [str_not_null]=N'hello' LIMIT 10", 'postgre' => 'SELECT * FROM "db"."table" @@ -412,7 +463,10 @@ LIMIT 10') Assert::same( - reformat('TEST [cond] > 2 [cond2] = \'3\' cond3 < RAND() 123'), + reformat([ + 'sqlsrv' => "TEST [cond] > 2 [cond2] = N'3' cond3 < RAND() 123", + "TEST [cond] > 2 [cond2] = '3' cond3 < RAND() 123", + ]), $conn->translate('TEST %ex', ['[cond] > 2', '[cond2] = "3"', 'cond3 < RAND()'], 123) ); @@ -430,7 +484,10 @@ Assert::same( Assert::same( - reformat('TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE \'string\')'), + reformat([ + 'sqlsrv' => "TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE N'string')", + "TEST ([cond1] 3) OR ([cond2] RAND()) OR ([cond3] LIKE 'string')", + ]), $conn->translate('TEST %or', ['cond1%ex' => 3, 'cond2%ex' => 'RAND()', 'cond3%ex' => ['LIKE %s', 'string']]) ); @@ -438,7 +495,7 @@ Assert::same( Assert::same( reformat([ 'odbc' => 'SELECT TOP 10 * FROM (SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' ) t', - 'sqlsrv' => 'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', + 'sqlsrv' => 'SELECT * FROM [test] WHERE [id] LIKE N\'%d%t\' OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY', 'SELECT * FROM [test] WHERE [id] LIKE \'%d%t\' LIMIT 10', ]), $conn->translate("SELECT * FROM [test] WHERE %n LIKE '%d%t' %lmt", 'id', 10) @@ -460,18 +517,27 @@ Assert::same( ); Assert::same( - reformat('SELECT \'%i\''), + reformat([ + 'sqlsrv' => "SELECT N'%i'", + "SELECT '%i'", + ]), $conn->translate("SELECT '%i'") ); Assert::same( - reformat('SELECT \'%i\''), + reformat([ + 'sqlsrv' => "SELECT N'%i'", + "SELECT '%i'", + ]), $conn->translate('SELECT "%i"') ); Assert::same( - reformat('INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1(\'Test product\')) , (1, SHA1(\'Test product\'))'), + reformat([ + 'sqlsrv' => "INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1(N'Test product')) , (1, SHA1(N'Test product'))", + "INSERT INTO [products] ([product_id], [title]) VALUES (1, SHA1('Test product')) , (1, SHA1('Test product'))", + ]), $conn->translate('INSERT INTO [products]', [ 'product_id' => 1, 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), @@ -482,7 +548,10 @@ Assert::same( ); Assert::same( - reformat('UPDATE [products] [product_id]=1, [title]=SHA1(\'Test product\')'), + reformat([ + 'sqlsrv' => "UPDATE [products] [product_id]=1, [title]=SHA1(N'Test product')", + "UPDATE [products] [product_id]=1, [title]=SHA1('Test product')", + ]), $conn->translate('UPDATE [products]', [ 'product_id' => 1, 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), @@ -490,7 +559,10 @@ Assert::same( ); Assert::same( - reformat('UPDATE [products] [product_id]=1, [title]=SHA1(\'Test product\')'), + reformat([ + 'sqlsrv' => "UPDATE [products] [product_id]=1, [title]=SHA1(N'Test product')", + "UPDATE [products] [product_id]=1, [title]=SHA1('Test product')", + ]), $conn->translate('UPDATE [products]', [ 'product_id' => 1, 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), @@ -498,7 +570,10 @@ Assert::same( ); Assert::same( - reformat('SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1(\'Test product\')'), + reformat([ + 'sqlsrv' => "SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1(N'Test product')", + "SELECT * FROM [products] WHERE [product_id]=1, [title]=SHA1('Test product')", + ]), $conn->translate('SELECT * FROM [products] WHERE', [ 'product_id' => 1, 'title' => new Dibi\Expression('SHA1(%s)', 'Test product'), @@ -535,7 +610,10 @@ $array6 = [ ]; Assert::same( - reformat('INSERT INTO test ([id], [text], [num]) VALUES (1, \'ahoj\', 1), (2, \'jak\', -1), (3, \'se\', 10), (4, SUM(5), 1)'), + reformat([ + 'sqlsrv' => "INSERT INTO test ([id], [text], [num]) VALUES (1, N'ahoj', 1), (2, N'jak', -1), (3, N'se', 10), (4, SUM(5), 1)", + "INSERT INTO test ([id], [text], [num]) VALUES (1, 'ahoj', 1), (2, 'jak', -1), (3, 'se', 10), (4, SUM(5), 1)", + ]), $conn->translate('INSERT INTO test %m', $array6) ); @@ -589,7 +667,10 @@ Assert::same( setlocale(LC_ALL, 'czech'); Assert::same( - reformat("UPDATE [colors] SET [color]='blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5"), + reformat([ + 'sqlsrv' => "UPDATE [colors] SET [color]=N'blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5", + "UPDATE [colors] SET [color]='blue', [price]=-12.4, [spec]=-9E-005, [spec2]=1000, [spec3]=10000, [spec4]=10000 WHERE [price]=123.5", + ]), $conn->translate('UPDATE [colors] SET', [ 'color' => 'blue',