@@ -1253,7 +1253,7 @@ describe('E2E traces with async/await', async () => {
12531253 } ) ;
12541254} ) ;
12551255
1256- describe ( " Negative case: database.run('SELECT 1p')" , async ( ) => {
1256+ describe ( ' Negative cases' , async ( ) => {
12571257 let server : grpc . Server ;
12581258 let spanner : Spanner ;
12591259 let database : Database ;
@@ -1266,6 +1266,10 @@ describe("Negative case: database.run('SELECT 1p')", async () => {
12661266 const messageBadSelect1p = `Missing whitespace between literal and alias [at 1:9]
12671267SELECT 1p
12681268 ^` ;
1269+ const insertAlreadyExistentDataSql =
1270+ "INSERT INTO Singers(firstName, SingerId) VALUES('Foo', 1)" ;
1271+ const messageBadInsertAlreadyExistent =
1272+ 'Failed to insert row with primary key ({pk#SingerId:1}) due to previously existing row' ;
12691273
12701274 beforeEach ( async ( ) => {
12711275 traceExporter = new InMemorySpanExporter ( ) ;
@@ -1292,6 +1296,15 @@ SELECT 1p
12921296 selectSql1p ,
12931297 mock . StatementResult . error ( serverErr )
12941298 ) ;
1299+
1300+ const insertAlreadyExistentErr = {
1301+ message : messageBadInsertAlreadyExistent ,
1302+ code : grpc . status . ALREADY_EXISTS ,
1303+ } as mock . MockError ;
1304+ spannerMock . putStatementResult (
1305+ insertAlreadyExistentDataSql ,
1306+ mock . StatementResult . error ( insertAlreadyExistentErr )
1307+ ) ;
12951308 } ) ;
12961309
12971310 afterEach ( async ( ) => {
@@ -1305,6 +1318,9 @@ SELECT 1p
13051318 function assertRunBadSyntaxExpectations ( ) {
13061319 traceExporter . forceFlush ( ) ;
13071320 const spans = traceExporter . getFinishedSpans ( ) ;
1321+ spans . sort ( ( spanA , spanB ) => {
1322+ return spanA . startTime < spanB . startTime ;
1323+ } ) ;
13081324
13091325 const actualSpanNames : string [ ] = [ ] ;
13101326 const actualEventNames : string [ ] = [ ] ;
@@ -1453,4 +1469,160 @@ SELECT 1p
14531469 done ( ) ;
14541470 } ) ;
14551471 } ) ;
1472+
1473+ function assertDatabaseRunPlusAwaitTransactionForAlreadyExistentData ( ) {
1474+ traceExporter . forceFlush ( ) ;
1475+ const spans = traceExporter . getFinishedSpans ( ) ;
1476+ spans . sort ( ( spanA , spanB ) => {
1477+ return spanA . startTime < spanB . startTime ;
1478+ } ) ;
1479+
1480+ const actualSpanNames : string [ ] = [ ] ;
1481+ const actualEventNames : string [ ] = [ ] ;
1482+ spans . forEach ( span => {
1483+ actualSpanNames . push ( span . name ) ;
1484+ span . events . forEach ( event => {
1485+ actualEventNames . push ( event . name ) ;
1486+ } ) ;
1487+ } ) ;
1488+
1489+ const expectedSpanNames = [
1490+ 'CloudSpanner.Database.batchCreateSessions' ,
1491+ 'CloudSpanner.SessionPool.createSessions' ,
1492+ 'CloudSpanner.Snapshot.runStream' ,
1493+ 'CloudSpanner.Snapshot.run' ,
1494+ 'CloudSpanner.Snapshot.begin' ,
1495+ 'CloudSpanner.Snapshot.begin' ,
1496+ 'CloudSpanner.Transaction.commit' ,
1497+ 'CloudSpanner.Transaction.commit' ,
1498+ 'CloudSpanner.Database.runTransaction' ,
1499+ ] ;
1500+ assert . deepStrictEqual (
1501+ expectedSpanNames ,
1502+ actualSpanNames ,
1503+ `span names mismatch:\n\tGot: ${ actualSpanNames } \n\tWant: ${ expectedSpanNames } `
1504+ ) ;
1505+
1506+ // We need to ensure a strict relationship between the spans.
1507+ // |-Database.runTransaction |-------------------------------------|
1508+ // |-Snapshot.run |------------------------|
1509+ // |-Snapshot.runStream |---------------------|
1510+ // |-Transaction.commit |--------|
1511+ // |-Snapshot.begin |------|
1512+ // |-Snapshot.commit |-----|
1513+ const spanDatabaseRunTransaction = spans [ spans . length - 1 ] ;
1514+ assert . deepStrictEqual (
1515+ spanDatabaseRunTransaction . name ,
1516+ 'CloudSpanner.Database.runTransaction' ,
1517+ `${ actualSpanNames } `
1518+ ) ;
1519+ const spanTransactionCommit0 = spans [ spans . length - 2 ] ;
1520+ assert . strictEqual (
1521+ spanTransactionCommit0 . name ,
1522+ 'CloudSpanner.Transaction.commit'
1523+ ) ;
1524+ assert . deepStrictEqual (
1525+ spanTransactionCommit0 . parentSpanId ,
1526+ spanDatabaseRunTransaction . spanContext ( ) . spanId ,
1527+ 'Expected that Database.runTransaction is the parent to Transaction.commmit'
1528+ ) ;
1529+ const spanSnapshotRun = spans [ 3 ] ;
1530+ assert . strictEqual ( spanSnapshotRun . name , 'CloudSpanner.Snapshot.run' ) ;
1531+ assert . deepStrictEqual (
1532+ spanSnapshotRun . parentSpanId ,
1533+ spanDatabaseRunTransaction . spanContext ( ) . spanId ,
1534+ 'Expected that Database.runTransaction is the parent to Snapshot.run'
1535+ ) ;
1536+ const wantSpanErr = '6 ALREADY_EXISTS: ' + messageBadInsertAlreadyExistent ;
1537+ assert . deepStrictEqual (
1538+ spanSnapshotRun . status . code ,
1539+ SpanStatusCode . ERROR ,
1540+ 'Unexpected status code'
1541+ ) ;
1542+ assert . deepStrictEqual (
1543+ spanSnapshotRun . status . message ,
1544+ wantSpanErr ,
1545+ 'Unexpexcted error message'
1546+ ) ;
1547+
1548+ const databaseBatchCreateSessionsSpan = spans [ 0 ] ;
1549+ assert . strictEqual (
1550+ databaseBatchCreateSessionsSpan . name ,
1551+ 'CloudSpanner.Database.batchCreateSessions'
1552+ ) ;
1553+ const sessionPoolCreateSessionsSpan = spans [ 1 ] ;
1554+ assert . strictEqual (
1555+ sessionPoolCreateSessionsSpan . name ,
1556+ 'CloudSpanner.SessionPool.createSessions'
1557+ ) ;
1558+ assert . ok (
1559+ sessionPoolCreateSessionsSpan . spanContext ( ) . traceId ,
1560+ 'Expecting a defined sessionPoolCreateSessions traceId'
1561+ ) ;
1562+ assert . deepStrictEqual (
1563+ sessionPoolCreateSessionsSpan . spanContext ( ) . traceId ,
1564+ databaseBatchCreateSessionsSpan . spanContext ( ) . traceId ,
1565+ 'Expected the same traceId'
1566+ ) ;
1567+ assert . deepStrictEqual (
1568+ databaseBatchCreateSessionsSpan . parentSpanId ,
1569+ sessionPoolCreateSessionsSpan . spanContext ( ) . spanId ,
1570+ 'Expected that sessionPool.createSessions is the parent to db.batchCreassionSessions'
1571+ ) ;
1572+
1573+ // Assert that despite all being exported, SessionPool.createSessions
1574+ // is not in the same trace as runStream, createSessions is invoked at
1575+ // Spanner Client instantiation, thus before database.run is invoked.
1576+ assert . notEqual (
1577+ sessionPoolCreateSessionsSpan . spanContext ( ) . traceId ,
1578+ spanDatabaseRunTransaction . spanContext ( ) . traceId ,
1579+ 'Did not expect the same traceId'
1580+ ) ;
1581+ // Finally check for the collective expected event names.
1582+ const expectedEventNames = [
1583+ 'Requesting 25 sessions' ,
1584+ 'Creating 25 sessions' ,
1585+ 'Requested for 25 sessions returned 25' ,
1586+ 'Begin Transaction' ,
1587+ 'Transaction Creation Done' ,
1588+ 'Begin Transaction' ,
1589+ 'Transaction Creation Done' ,
1590+ 'Starting Commit' ,
1591+ 'Commit Done' ,
1592+ 'Acquiring session' ,
1593+ 'Waiting for a session to become available' ,
1594+ 'Acquired session' ,
1595+ ] ;
1596+ assert . deepStrictEqual (
1597+ actualEventNames ,
1598+ expectedEventNames ,
1599+ `Unexpected events:\n\tGot: ${ actualEventNames } \n\tWant: ${ expectedEventNames } `
1600+ ) ;
1601+ }
1602+
1603+ it ( 'runTransaction with async/await for INSERT with existent data + transaction.commit' , done => {
1604+ const instance = spanner . instance ( 'instance' ) ;
1605+ const database = instance . database ( 'database' ) ;
1606+
1607+ const update = {
1608+ sql : insertAlreadyExistentDataSql ,
1609+ } ;
1610+ database . runTransaction ( async ( err , transaction ) => {
1611+ if ( err ) {
1612+ console . error ( err ) ;
1613+ return ;
1614+ }
1615+ try {
1616+ await transaction ! . run ( update ) ;
1617+ await new Promise ( resolve => setTimeout ( resolve , 400 ) ) ;
1618+ } catch ( err ) {
1619+ } finally {
1620+ await transaction ! . commit ( ) ;
1621+ await new Promise ( resolve => setTimeout ( resolve , 2800 ) ) ;
1622+ provider . forceFlush ( ) ;
1623+ assertDatabaseRunPlusAwaitTransactionForAlreadyExistentData ( ) ;
1624+ done ( ) ;
1625+ }
1626+ } ) ;
1627+ } ) ;
14561628} ) ;
0 commit comments