|
30 | 30 | import java.util.Calendar;
|
31 | 31 | import java.util.Random;
|
32 | 32 | import java.util.UUID;
|
| 33 | +import java.util.logging.Handler; |
| 34 | +import java.util.logging.LogRecord; |
| 35 | +import java.util.logging.Logger; |
33 | 36 |
|
34 | 37 | import org.junit.jupiter.api.AfterAll;
|
35 |
| -import org.junit.jupiter.api.BeforeEach; |
36 | 38 | import org.junit.jupiter.api.BeforeAll;
|
| 39 | +import org.junit.jupiter.api.BeforeEach; |
37 | 40 | import org.junit.jupiter.api.Tag;
|
38 | 41 | import org.junit.jupiter.api.Test;
|
39 | 42 | import org.junit.platform.runner.JUnitPlatform;
|
@@ -1407,98 +1410,281 @@ private void getCreateTableTemporalSQL(String tableName) throws SQLException {
|
1407 | 1410 | }
|
1408 | 1411 | }
|
1409 | 1412 |
|
| 1413 | + class FallbackWatcherLogHandler extends Handler implements AutoCloseable { |
| 1414 | + |
| 1415 | + Logger stmtLogger = Logger.getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerStatement"); |
| 1416 | + boolean gotFallbackMessage = false; |
| 1417 | + |
| 1418 | + public FallbackWatcherLogHandler() { |
| 1419 | + stmtLogger.addHandler(this); |
| 1420 | + } |
| 1421 | + |
| 1422 | + @Override |
| 1423 | + public void publish(LogRecord record) { |
| 1424 | + if (record.getMessage().contains("Falling back to the original implementation for Batch Insert.")) { |
| 1425 | + gotFallbackMessage = true; |
| 1426 | + } |
| 1427 | + } |
| 1428 | + |
| 1429 | + @Override |
| 1430 | + public void flush() {} |
| 1431 | + |
| 1432 | + @Override |
| 1433 | + public void close() throws SecurityException { |
| 1434 | + stmtLogger.removeHandler(this); |
| 1435 | + } |
| 1436 | + } |
| 1437 | + |
1410 | 1438 | /**
|
1411 |
| - * Test batch insert using bulk copy with string values when setSendStringParametersAsUnicode is true. |
| 1439 | + * Test string values using prepared statement using accented and unicode characters. |
| 1440 | + * This test covers all combinations of useBulkCopyForBatchInsert and sendStringParametersAsUnicode. |
| 1441 | + * |
| 1442 | + * @throws Exception |
1412 | 1443 | */
|
1413 | 1444 | @Test
|
1414 |
| - public void testBulkInsertStringWhenSentAsUnicode() throws Exception { |
| 1445 | + public void testBulkInsertStringAllCombinations() throws Exception { |
| 1446 | + boolean[] bulkCopyOptions = { true, false }; |
| 1447 | + boolean[] unicodeOptions = { true, false }; |
| 1448 | + for (boolean useBulkCopy : bulkCopyOptions) { |
| 1449 | + for (boolean sendUnicode : unicodeOptions) { |
| 1450 | + runBulkInsertStringTest(useBulkCopy, sendUnicode); |
| 1451 | + runBulkInsertStringTestForceFallback(useBulkCopy, sendUnicode); |
| 1452 | + } |
| 1453 | + } |
| 1454 | + } |
| 1455 | + |
| 1456 | + /** |
| 1457 | + * Test batch insert using accented and unicode characters. |
| 1458 | + */ |
| 1459 | + public void runBulkInsertStringTest(boolean useBulkCopy, boolean sendUnicode) throws Exception { |
1415 | 1460 | String insertSQL = "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableNameBulkString)
|
1416 |
| - + " (charCol, varcharCol, longvarcharCol, ncharCol, nvarcharCol, longnvarcharCol) VALUES (?, ?, ?, ?, ?, ?)"; |
| 1461 | + + " (charCol, varcharCol, longvarcharCol, ncharCol1, nvarcharCol1, longnvarcharCol1, " |
| 1462 | + + "ncharCol2, nvarcharCol2, longnvarcharCol2) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; |
1417 | 1463 |
|
1418 |
| - String selectSQL = "SELECT charCol, varcharCol, longvarcharCol, ncharCol, nvarcharCol, longnvarcharCol FROM " |
| 1464 | + String selectSQL = "SELECT charCol, varcharCol, longvarcharCol, ncharCol1, nvarcharCol1, " |
| 1465 | + + "longnvarcharCol1, ncharCol2, nvarcharCol2, longnvarcharCol2 FROM " |
1419 | 1466 | + AbstractSQLGenerator.escapeIdentifier(tableNameBulkString);
|
1420 | 1467 |
|
1421 | 1468 | try (Connection connection = PrepUtil.getConnection(
|
1422 |
| - connectionString + ";useBulkCopyForBatchInsert=true;sendStringParametersAsUnicode=true;"); |
| 1469 | + connectionString + ";useBulkCopyForBatchInsert=" + useBulkCopy + ";sendStringParametersAsUnicode=" |
| 1470 | + + sendUnicode + ";"); |
1423 | 1471 | SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(insertSQL);
|
1424 | 1472 | Statement stmt = (SQLServerStatement) connection.createStatement()) {
|
1425 | 1473 |
|
1426 | 1474 | getCreateTableWithStringData();
|
1427 | 1475 |
|
1428 |
| - pstmt.setString(1, "CHAR_VAL"); |
1429 |
| - pstmt.setString(2, "VARCHAR_VALUE"); |
1430 |
| - pstmt.setString(3, "LONGVARCHAR_VALUE_WITH_MORE_TEXT"); |
1431 |
| - pstmt.setString(4, "NCHAR_VAL"); |
1432 |
| - pstmt.setString(5, "NVARCHAR_VALUE"); |
1433 |
| - pstmt.setString(6, "LONGNVARCHAR_VALUE_WITH_UNICODE_TEXT"); |
| 1476 | + String charValue = "Anaïs_Ni"; |
| 1477 | + String varcharValue = "café"; |
| 1478 | + String longVarcharValue = "Sørén Kierkégaard"; |
| 1479 | + String ncharValue1 = "José Müll"; |
| 1480 | + String nvarcharValue1 = "José Müller"; |
| 1481 | + String longNvarcharValue1 = "François Saldaña"; |
| 1482 | + String ncharValue2 = "Test1汉字😀"; |
| 1483 | + String nvarcharValue2 = "汉字"; |
| 1484 | + String longNvarcharValue2 = "日本語"; |
| 1485 | + |
| 1486 | + pstmt.setString(1, charValue); |
| 1487 | + pstmt.setString(2, varcharValue); |
| 1488 | + pstmt.setString(3, longVarcharValue); |
| 1489 | + pstmt.setString(4, ncharValue1); |
| 1490 | + pstmt.setString(5, nvarcharValue1); |
| 1491 | + pstmt.setString(6, longNvarcharValue1); |
| 1492 | + pstmt.setNString(7, ncharValue2); |
| 1493 | + pstmt.setNString(8, nvarcharValue2); |
| 1494 | + pstmt.setNString(9, longNvarcharValue2); |
1434 | 1495 | pstmt.addBatch();
|
1435 | 1496 | pstmt.executeBatch();
|
1436 | 1497 |
|
1437 | 1498 | // Validate inserted data
|
1438 | 1499 | try (ResultSet rs = stmt.executeQuery(selectSQL)) {
|
1439 | 1500 | assertTrue(rs.next(), "Expected at least one row in result set");
|
1440 |
| - assertEquals("CHAR_VAL", rs.getString("charCol")); |
1441 |
| - assertEquals("VARCHAR_VALUE", rs.getString("varcharCol")); |
1442 |
| - assertEquals("LONGVARCHAR_VALUE_WITH_MORE_TEXT", rs.getString("longvarcharCol")); |
1443 |
| - assertEquals("NCHAR_VAL", rs.getString("ncharCol")); |
1444 |
| - assertEquals("NVARCHAR_VALUE", rs.getString("nvarcharCol")); |
1445 |
| - assertEquals("LONGNVARCHAR_VALUE_WITH_UNICODE_TEXT", rs.getString("longnvarcharCol")); |
| 1501 | + assertEquals(charValue, rs.getString("charCol")); |
| 1502 | + assertEquals(varcharValue, rs.getString("varcharCol")); |
| 1503 | + assertEquals(longVarcharValue, rs.getString("longvarcharCol")); |
| 1504 | + assertEquals(ncharValue1, rs.getString("ncharCol1")); |
| 1505 | + assertEquals(nvarcharValue1, rs.getString("nvarcharCol1")); |
| 1506 | + assertEquals(longNvarcharValue1, rs.getString("longnvarcharCol1")); |
| 1507 | + assertEquals(ncharValue2, rs.getString("ncharCol2")); |
| 1508 | + assertEquals(nvarcharValue2, rs.getString("nvarcharCol2")); |
| 1509 | + assertEquals(longNvarcharValue2, rs.getString("longnvarcharCol2")); |
1446 | 1510 | assertFalse(rs.next());
|
1447 | 1511 | }
|
1448 | 1512 | }
|
1449 | 1513 | }
|
1450 | 1514 |
|
1451 | 1515 | /**
|
1452 |
| - * Test batch insert using bulk copy with string values when setSendStringParametersAsUnicode is false. |
| 1516 | + * Test batch insert using an unsupported statement (falls back to batch mode) with accented and Unicode characters. |
1453 | 1517 | */
|
1454 |
| - @Test |
1455 |
| - public void testBulkInsertStringWhenNotSentAsUnicode() throws Exception { |
| 1518 | + public void runBulkInsertStringTestForceFallback(boolean useBulkCopy, boolean sendUnicode) throws Exception { |
1456 | 1519 | String insertSQL = "INSERT INTO " + AbstractSQLGenerator.escapeIdentifier(tableNameBulkString)
|
1457 |
| - + " (charCol, varcharCol, longvarcharCol, ncharCol, nvarcharCol, longnvarcharCol) VALUES (?, ?, ?, ?, ?, ?)"; |
| 1520 | + + " (charCol, varcharCol, longvarcharCol, ncharCol1, nvarcharCol1, longnvarcharCol1, " |
| 1521 | + + "ncharCol2, nvarcharCol2, longnvarcharCol2) VALUES ('Anaïs_Ni', ?, ?, ?, ?, ?, ?, ?, ?)"; |
1458 | 1522 |
|
1459 |
| - String selectSQL = "SELECT charCol, varcharCol, longvarcharCol, ncharCol, nvarcharCol, longnvarcharCol FROM " |
| 1523 | + String selectSQL = "SELECT charCol, varcharCol, longvarcharCol, ncharCol1, nvarcharCol1, " |
| 1524 | + + "longnvarcharCol1, ncharCol2, nvarcharCol2, longnvarcharCol2 FROM " |
1460 | 1525 | + AbstractSQLGenerator.escapeIdentifier(tableNameBulkString);
|
1461 | 1526 |
|
1462 |
| - try (Connection connection = PrepUtil.getConnection( |
1463 |
| - connectionString + ";useBulkCopyForBatchInsert=true;sendStringParametersAsUnicode=false;"); |
| 1527 | + try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=" |
| 1528 | + + useBulkCopy + ";sendStringParametersAsUnicode=" + sendUnicode + ";"); |
1464 | 1529 | SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(insertSQL);
|
1465 | 1530 | Statement stmt = (SQLServerStatement) connection.createStatement()) {
|
1466 | 1531 |
|
1467 | 1532 | getCreateTableWithStringData();
|
1468 | 1533 |
|
1469 |
| - pstmt.setString(1, "CHAR_VAL"); |
1470 |
| - pstmt.setString(2, "VARCHAR_VALUE"); |
1471 |
| - pstmt.setString(3, "LONGVARCHAR_VALUE_WITH_MORE_TEXT"); |
1472 |
| - pstmt.setString(4, "NCHAR_VAL"); |
1473 |
| - pstmt.setString(5, "NVARCHAR_VALUE"); |
1474 |
| - pstmt.setString(6, "LONGNVARCHAR_VALUE_WITH_UNICODE_TEXT"); |
| 1534 | + String charValue = "Anaïs_Ni"; |
| 1535 | + String varcharValue = "café"; |
| 1536 | + String longVarcharValue = "Sørén Kierkégaard"; |
| 1537 | + String ncharValue1 = "José Müll"; |
| 1538 | + String nvarcharValue1 = "José Müller"; |
| 1539 | + String longNvarcharValue1 = "François Saldaña"; |
| 1540 | + String ncharValue2 = "Test1汉字😀"; |
| 1541 | + String nvarcharValue2 = "汉字"; |
| 1542 | + String longNvarcharValue2 = "日本語"; |
| 1543 | + |
| 1544 | + pstmt.setString(1, varcharValue); |
| 1545 | + pstmt.setString(2, longVarcharValue); |
| 1546 | + pstmt.setString(3, ncharValue1); |
| 1547 | + pstmt.setString(4, nvarcharValue1); |
| 1548 | + pstmt.setString(5, longNvarcharValue1); |
| 1549 | + pstmt.setNString(6, ncharValue2); |
| 1550 | + pstmt.setNString(7, nvarcharValue2); |
| 1551 | + pstmt.setNString(8, longNvarcharValue2); |
| 1552 | + pstmt.addBatch(); |
| 1553 | + |
| 1554 | + try (FallbackWatcherLogHandler handler = new FallbackWatcherLogHandler()) { |
| 1555 | + pstmt.executeBatch(); |
| 1556 | + if (useBulkCopy) { |
| 1557 | + assertTrue(handler.gotFallbackMessage); |
| 1558 | + } |
| 1559 | + } |
| 1560 | + |
| 1561 | + // Validate inserted data |
| 1562 | + try (ResultSet rs = stmt.executeQuery(selectSQL)) { |
| 1563 | + assertTrue(rs.next(), "Expected at least one row in result set"); |
| 1564 | + assertEquals(charValue, rs.getString("charCol")); |
| 1565 | + assertEquals(varcharValue, rs.getString("varcharCol")); |
| 1566 | + assertEquals(longVarcharValue, rs.getString("longvarcharCol")); |
| 1567 | + assertEquals(ncharValue1, rs.getString("ncharCol1")); |
| 1568 | + assertEquals(nvarcharValue1, rs.getString("nvarcharCol1")); |
| 1569 | + assertEquals(longNvarcharValue1, rs.getString("longnvarcharCol1")); |
| 1570 | + assertEquals(ncharValue2, rs.getString("ncharCol2")); |
| 1571 | + assertEquals(nvarcharValue2, rs.getString("nvarcharCol2")); |
| 1572 | + assertEquals(longNvarcharValue2, rs.getString("longnvarcharCol2")); |
| 1573 | + assertFalse(rs.next()); |
| 1574 | + } |
| 1575 | + } |
| 1576 | + } |
| 1577 | + |
| 1578 | + @Test |
| 1579 | + public void testIssue2669Repro() throws Exception { |
| 1580 | + // Original repro |
| 1581 | + testIssue2669Variation(false, true); |
| 1582 | + // Variations |
| 1583 | + testIssue2669Variation(false, false); |
| 1584 | + testIssue2669Variation(true, true); |
| 1585 | + testIssue2669Variation(true, false); |
| 1586 | + |
| 1587 | + // Test the same combos except falling back to batch insert |
| 1588 | + testIssue2669VariationForceFallback(false, true); |
| 1589 | + testIssue2669VariationForceFallback(false, false); |
| 1590 | + testIssue2669VariationForceFallback(true, true); |
| 1591 | + testIssue2669VariationForceFallback(true, false); |
| 1592 | + } |
| 1593 | + |
| 1594 | + public void testIssue2669Variation(boolean sendStringsAsUnicode, |
| 1595 | + boolean useBulkCopyForBatchInsert) throws Exception { |
| 1596 | + String statesTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("states")); |
| 1597 | + try (Statement stmt = connection.createStatement()) { |
| 1598 | + TestUtils.dropTableIfExists(statesTable, stmt); |
| 1599 | + String createTableSQL = "CREATE TABLE " + statesTable + " (" + "StateCode nvarchar(50) NOT NULL " + ")"; |
| 1600 | + |
| 1601 | + stmt.execute(createTableSQL); |
| 1602 | + } |
| 1603 | + |
| 1604 | + String insertSQL = "INSERT INTO " + statesTable + " (StateCode) VALUES (?)"; |
| 1605 | + |
| 1606 | + String selectSQL = "SELECT StateCode FROM " + statesTable; |
| 1607 | + |
| 1608 | + try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=" |
| 1609 | + + useBulkCopyForBatchInsert + ";sendStringParametersAsUnicode=" + sendStringsAsUnicode + ";"); |
| 1610 | + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(insertSQL); |
| 1611 | + Statement stmt = (SQLServerStatement) connection.createStatement()) { |
| 1612 | + |
| 1613 | + pstmt.setString(1, "OH"); |
1475 | 1614 | pstmt.addBatch();
|
1476 | 1615 | pstmt.executeBatch();
|
1477 | 1616 |
|
1478 | 1617 | // Validate inserted data
|
1479 | 1618 | try (ResultSet rs = stmt.executeQuery(selectSQL)) {
|
1480 | 1619 | assertTrue(rs.next(), "Expected at least one row in result set");
|
1481 |
| - assertEquals("CHAR_VAL", rs.getString("charCol")); |
1482 |
| - assertEquals("VARCHAR_VALUE", rs.getString("varcharCol")); |
1483 |
| - assertEquals("LONGVARCHAR_VALUE_WITH_MORE_TEXT", rs.getString("longvarcharCol")); |
1484 |
| - assertEquals("NCHAR_VAL", rs.getString("ncharCol")); |
1485 |
| - assertEquals("NVARCHAR_VALUE", rs.getString("nvarcharCol")); |
1486 |
| - assertEquals("LONGNVARCHAR_VALUE_WITH_UNICODE_TEXT", rs.getString("longnvarcharCol")); |
| 1620 | + assertEquals("OH", rs.getString("StateCode")); |
1487 | 1621 | assertFalse(rs.next());
|
1488 | 1622 | }
|
| 1623 | + } finally { |
| 1624 | + try (Statement stmt = connection.createStatement()) { |
| 1625 | + TestUtils.dropTableIfExists(statesTable, stmt); |
| 1626 | + } |
| 1627 | + } |
| 1628 | + } |
| 1629 | + |
| 1630 | + public void testIssue2669VariationForceFallback(boolean sendStringsAsUnicode, |
| 1631 | + boolean useBulkCopyForBatchInsert) throws SQLException { |
| 1632 | + String statesTable = AbstractSQLGenerator.escapeIdentifier(RandomUtil.getIdentifier("states")); |
| 1633 | + try (Statement stmt = connection.createStatement()) { |
| 1634 | + TestUtils.dropTableIfExists(statesTable, stmt); |
| 1635 | + String createTableSQL = "CREATE TABLE " + statesTable |
| 1636 | + + " (StateCode nvarchar(50), StateCode2 nvarchar(50) NOT NULL " + ")"; |
| 1637 | + |
| 1638 | + stmt.execute(createTableSQL); |
| 1639 | + } |
| 1640 | + |
| 1641 | + // Use an INSERT that forces fall back to plain batch insert |
| 1642 | + String insertSQL = "INSERT INTO " + statesTable + " (StateCode, StateCode2) VALUES ('NA', ?)"; |
| 1643 | + |
| 1644 | + String selectSQL = "SELECT StateCode, StateCode2 FROM " + statesTable; |
| 1645 | + |
| 1646 | + try (Connection connection = PrepUtil.getConnection(connectionString + ";useBulkCopyForBatchInsert=" |
| 1647 | + + useBulkCopyForBatchInsert + ";sendStringParametersAsUnicode=" + sendStringsAsUnicode + ";"); |
| 1648 | + SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement) connection.prepareStatement(insertSQL); |
| 1649 | + Statement stmt = (SQLServerStatement) connection.createStatement()) { |
| 1650 | + |
| 1651 | + pstmt.setString(1, "OH"); |
| 1652 | + pstmt.addBatch(); |
| 1653 | + |
| 1654 | + try (FallbackWatcherLogHandler handler = new FallbackWatcherLogHandler()) { |
| 1655 | + pstmt.executeBatch(); |
| 1656 | + if (useBulkCopyForBatchInsert) { |
| 1657 | + assertTrue(handler.gotFallbackMessage); |
| 1658 | + } |
| 1659 | + } |
| 1660 | + |
| 1661 | + // Validate inserted data |
| 1662 | + try (ResultSet rs = stmt.executeQuery(selectSQL)) { |
| 1663 | + assertTrue(rs.next(), "Expected at least one row in result set"); |
| 1664 | + assertEquals("NA", rs.getString("StateCode")); |
| 1665 | + assertEquals("OH", rs.getString("StateCode2")); |
| 1666 | + assertFalse(rs.next()); |
| 1667 | + } |
| 1668 | + } finally { |
| 1669 | + try (Statement stmt = connection.createStatement()) { |
| 1670 | + TestUtils.dropTableIfExists(statesTable, stmt); |
| 1671 | + } |
1489 | 1672 | }
|
1490 | 1673 | }
|
1491 | 1674 |
|
1492 | 1675 | private void getCreateTableWithStringData() throws SQLException {
|
1493 | 1676 | try (Statement stmt = connection.createStatement()) {
|
1494 | 1677 | TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableNameBulkString), stmt);
|
1495 | 1678 | String createTableSQL = "CREATE TABLE " + AbstractSQLGenerator.escapeIdentifier(tableNameBulkString) + " (" +
|
1496 |
| - "charCol CHAR(8) NOT NULL, " + |
1497 |
| - "varcharCol VARCHAR(50) NOT NULL, " + |
1498 |
| - "longvarcharCol VARCHAR(MAX) NOT NULL, " + |
1499 |
| - "ncharCol NCHAR(9) NOT NULL, " + |
1500 |
| - "nvarcharCol NVARCHAR(50) NOT NULL, " + |
1501 |
| - "longnvarcharCol NVARCHAR(MAX) NOT NULL" + ")"; |
| 1679 | + "charCol CHAR(8), " + |
| 1680 | + "varcharCol VARCHAR(50), " + |
| 1681 | + "longvarcharCol VARCHAR(MAX), " + |
| 1682 | + "ncharCol1 NCHAR(9), " + |
| 1683 | + "nvarcharCol1 NVARCHAR(50), " + |
| 1684 | + "longnvarcharCol1 NVARCHAR(MAX), " + |
| 1685 | + "ncharCol2 NCHAR(9), " + |
| 1686 | + "nvarcharCol2 NVARCHAR(50), " + |
| 1687 | + "longnvarcharCol2 NVARCHAR(MAX)" + ")"; |
1502 | 1688 |
|
1503 | 1689 | stmt.execute(createTableSQL);
|
1504 | 1690 | }
|
@@ -1534,6 +1720,7 @@ public static void terminateVariation() throws SQLException {
|
1534 | 1720 | TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(doubleQuoteTableName), stmt);
|
1535 | 1721 | TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(schemaTableName), stmt);
|
1536 | 1722 | TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableNameBulkString), stmt);
|
| 1723 | + TestUtils.dropTableIfExists(AbstractSQLGenerator.escapeIdentifier(tableNameBulkComputedCols), stmt); |
1537 | 1724 | }
|
1538 | 1725 | }
|
1539 | 1726 | }
|
0 commit comments