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 .Assertions ;
36
- import org .junit .jupiter .api .BeforeEach ;
37
38
import org .junit .jupiter .api .BeforeAll ;
39
+ import org .junit .jupiter .api .BeforeEach ;
38
40
import org .junit .jupiter .api .Tag ;
39
41
import org .junit .jupiter .api .Test ;
40
42
import org .junit .platform .runner .JUnitPlatform ;
@@ -1408,51 +1410,28 @@ private void getCreateTableTemporalSQL(String tableName) throws SQLException {
1408
1410
}
1409
1411
}
1410
1412
1411
- /**
1412
- * Test insert-select fallback to normal execution for bulk copy API
1413
- */
1414
- @ Test
1415
- public void testInsertSelectFallbackToNormalExecution () throws Exception {
1416
- String tableNameSource = AbstractSQLGenerator .escapeIdentifier ("SourceTable" );
1417
- String tableNameDestination = AbstractSQLGenerator .escapeIdentifier ("DestinationTable" );
1418
-
1419
- String connectStringUrl = connectionString
1420
- + ";useBulkCopyForBatchInsert=true;sendStringParametersAsUnicode=false;" ;
1421
-
1422
- try (Connection connection = PrepUtil .getConnection (connectStringUrl );
1423
- Statement stmt = connection .createStatement ()) {
1413
+ class FallbackWatcherLogHandler extends Handler implements AutoCloseable {
1424
1414
1425
- TestUtils .dropTableIfExists (tableNameSource , stmt );
1426
- String createSourceTableSQL = "CREATE TABLE " + tableNameSource + " (id INT, value VARCHAR(50))" ;
1427
- stmt .execute (createSourceTableSQL );
1415
+ Logger stmtLogger = Logger .getLogger ("com.microsoft.sqlserver.jdbc.internals.SQLServerStatement" );
1416
+ boolean gotFallbackMessage = false ;
1428
1417
1429
- String insertSourceDataSQL = "INSERT INTO " + tableNameSource + " VALUES (1, 'TestValue1'), (2, 'TestValue2')" ;
1430
- stmt .execute (insertSourceDataSQL );
1431
-
1432
- TestUtils .dropTableIfExists (tableNameDestination , stmt );
1433
- String createDestinationTableSQL = "CREATE TABLE " + tableNameDestination + " (id INT, value VARCHAR(50))" ;
1434
- stmt .execute (createDestinationTableSQL );
1435
-
1436
- // Attempt unsupported INSERT-SELECT query for bulk copy api
1437
- String insertSelectSQL = "INSERT INTO " + tableNameDestination + " SELECT * FROM " + tableNameSource
1438
- + " WHERE value = ?" ;
1418
+ public FallbackWatcherLogHandler () {
1419
+ stmtLogger .addHandler (this );
1420
+ }
1439
1421
1440
- try (SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement ) connection
1441
- .prepareStatement (insertSelectSQL )) {
1442
- pstmt .setString (1 , "TestValue1" );
1443
- pstmt .addBatch ();
1444
- pstmt .executeBatch (); // This should fall back to normal execution flow
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 ;
1445
1426
}
1427
+ }
1446
1428
1447
- // Validate inserted data in destination table
1448
- String selectSQL = "SELECT * FROM " + tableNameDestination ;
1449
- try (ResultSet rs = stmt .executeQuery (selectSQL )) {
1450
- assertTrue (rs .next (), "Expected at least one row in result set" );
1451
- assertEquals (1 , rs .getInt ("id" ));
1452
- assertEquals ("TestValue1" , rs .getString ("value" ));
1429
+ @ Override
1430
+ public void flush () {}
1453
1431
1454
- Assertions .assertFalse (rs .next (), "No more rows expected" );
1455
- }
1432
+ @ Override
1433
+ public void close () throws SecurityException {
1434
+ stmtLogger .removeHandler (this );
1456
1435
}
1457
1436
}
1458
1437
@@ -1469,6 +1448,7 @@ public void testBulkInsertStringAllCombinations() throws Exception {
1469
1448
for (boolean useBulkCopy : bulkCopyOptions ) {
1470
1449
for (boolean sendUnicode : unicodeOptions ) {
1471
1450
runBulkInsertStringTest (useBulkCopy , sendUnicode );
1451
+ runBulkInsertStringTestForceFallback (useBulkCopy , sendUnicode );
1472
1452
}
1473
1453
}
1474
1454
}
@@ -1532,6 +1512,166 @@ public void runBulkInsertStringTest(boolean useBulkCopy, boolean sendUnicode) th
1532
1512
}
1533
1513
}
1534
1514
1515
+ /**
1516
+ * Test batch insert using an unsupported statement (falls back to batch mode) with accented and Unicode characters.
1517
+ */
1518
+ public void runBulkInsertStringTestForceFallback (boolean useBulkCopy , boolean sendUnicode ) throws Exception {
1519
+ String insertSQL = "INSERT INTO " + AbstractSQLGenerator .escapeIdentifier (tableNameBulkString )
1520
+ + " (charCol, varcharCol, longvarcharCol, ncharCol1, nvarcharCol1, longnvarcharCol1, "
1521
+ + "ncharCol2, nvarcharCol2, longnvarcharCol2) VALUES ('Anaïs_Ni', ?, ?, ?, ?, ?, ?, ?, ?)" ;
1522
+
1523
+ String selectSQL = "SELECT charCol, varcharCol, longvarcharCol, ncharCol1, nvarcharCol1, "
1524
+ + "longnvarcharCol1, ncharCol2, nvarcharCol2, longnvarcharCol2 FROM "
1525
+ + AbstractSQLGenerator .escapeIdentifier (tableNameBulkString );
1526
+
1527
+ try (Connection connection = PrepUtil .getConnection (connectionString + ";useBulkCopyForBatchInsert="
1528
+ + useBulkCopy + ";sendStringParametersAsUnicode=" + sendUnicode + ";" );
1529
+ SQLServerPreparedStatement pstmt = (SQLServerPreparedStatement ) connection .prepareStatement (insertSQL );
1530
+ Statement stmt = (SQLServerStatement ) connection .createStatement ()) {
1531
+
1532
+ getCreateTableWithStringData ();
1533
+
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" );
1614
+ pstmt .addBatch ();
1615
+ pstmt .executeBatch ();
1616
+
1617
+ // Validate inserted data
1618
+ try (ResultSet rs = stmt .executeQuery (selectSQL )) {
1619
+ assertTrue (rs .next (), "Expected at least one row in result set" );
1620
+ assertEquals ("OH" , rs .getString ("StateCode" ));
1621
+ assertFalse (rs .next ());
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
+ }
1672
+ }
1673
+ }
1674
+
1535
1675
private void getCreateTableWithStringData () throws SQLException {
1536
1676
try (Statement stmt = connection .createStatement ()) {
1537
1677
TestUtils .dropTableIfExists (AbstractSQLGenerator .escapeIdentifier (tableNameBulkString ), stmt );
@@ -1580,6 +1720,7 @@ public static void terminateVariation() throws SQLException {
1580
1720
TestUtils .dropTableIfExists (AbstractSQLGenerator .escapeIdentifier (doubleQuoteTableName ), stmt );
1581
1721
TestUtils .dropTableIfExists (AbstractSQLGenerator .escapeIdentifier (schemaTableName ), stmt );
1582
1722
TestUtils .dropTableIfExists (AbstractSQLGenerator .escapeIdentifier (tableNameBulkString ), stmt );
1723
+ TestUtils .dropTableIfExists (AbstractSQLGenerator .escapeIdentifier (tableNameBulkComputedCols ), stmt );
1583
1724
}
1584
1725
}
1585
1726
}
0 commit comments