@@ -277,46 +277,286 @@ final class NIOPostgresTests: XCTestCase {
277277 XCTAssertEqual ( error. code, . invalid_password)
278278 }
279279 }
280-
281- func testSelectPerformance ( ) throws {
280+
281+ func testRangeSelectDecodePerformance ( ) throws {
282282 // std deviation too high
283+ struct Series : Decodable {
284+ var num : Int
285+ }
286+
283287 let conn = try PostgresConnection . test ( on: eventLoop) . wait ( )
284288 defer { try ! conn. close ( ) . wait ( ) }
285289 measure {
286290 do {
287- _ = try conn. query ( " SELECT * FROM pg_type " ) . wait ( )
291+ for _ in 0 ..< 50 {
292+ try conn. query ( " SELECT * FROM generate_series(1, 10000) num " ) { row in
293+ _ = row. column ( " num " ) ? . int
294+ } . wait ( )
295+ }
288296 } catch {
289297 XCTFail ( " \( error) " )
290298 }
291299 }
292300 }
293-
294- func testRangeSelectPerformance( ) throws {
295- // std deviation too high
301+
302+ func testColumnsInJoin( ) throws {
303+ let conn = try PostgresConnection . test ( on: eventLoop) . wait ( )
304+ defer { try ! conn. close ( ) . wait ( ) }
305+
306+ let dateInTable1 = Date ( timeIntervalSince1970: 1234 )
307+ let dateInTable2 = Date ( timeIntervalSince1970: 5678 )
308+ _ = try conn. simpleQuery ( """
309+ CREATE TABLE table1 (
310+ " id " int8 NOT NULL,
311+ " table2_id " int8,
312+ " intValue " int8,
313+ " stringValue " text,
314+ " dateValue " timestamptz,
315+ PRIMARY KEY ( " id " )
316+ );
317+ """ ) . wait ( )
318+ defer { _ = try ! conn. simpleQuery ( " DROP TABLE \" table1 \" " ) . wait ( ) }
319+
320+ _ = try conn. simpleQuery ( """
321+ CREATE TABLE table2 (
322+ " id " int8 NOT NULL,
323+ " intValue " int8,
324+ " stringValue " text,
325+ " dateValue " timestamptz,
326+ PRIMARY KEY ( " id " )
327+ );
328+ """ ) . wait ( )
329+ defer { _ = try ! conn. simpleQuery ( " DROP TABLE \" table2 \" " ) . wait ( ) }
330+
331+ _ = try conn. simpleQuery ( " INSERT INTO table1 VALUES (12, 34, 56, 'stringInTable1', to_timestamp(1234)) " ) . wait ( )
332+ _ = try conn. simpleQuery ( " INSERT INTO table2 VALUES (34, 78, 'stringInTable2', to_timestamp(5678)) " ) . wait ( )
333+
334+ let tableNameToOID = Dictionary ( uniqueKeysWithValues: try conn. simpleQuery ( " SELECT relname, oid FROM pg_class WHERE relname in ('table1', 'table2') " )
335+ . wait ( )
336+ . map { row -> ( String , UInt32 ) in ( row. column ( " relname " ) !. string!, row. column ( " oid " ) !. uint32!) } )
337+
338+ let row = try conn. query ( " SELECT * FROM table1 INNER JOIN table2 ON table1.table2_id = table2.id " ) . wait ( ) . first!
339+ XCTAssertEqual ( 12 , row. column ( " id " , tableOID: tableNameToOID [ " table1 " ] !) ? . int)
340+ XCTAssertEqual ( 34 , row. column ( " table2_id " , tableOID: tableNameToOID [ " table1 " ] !) ? . int)
341+ XCTAssertEqual ( 56 , row. column ( " intValue " , tableOID: tableNameToOID [ " table1 " ] !) ? . int)
342+ XCTAssertEqual ( " stringInTable1 " , row. column ( " stringValue " , tableOID: tableNameToOID [ " table1 " ] !) ? . string)
343+ XCTAssertEqual ( dateInTable1, row. column ( " dateValue " , tableOID: tableNameToOID [ " table1 " ] !) ? . date)
344+ XCTAssertEqual ( 34 , row. column ( " id " , tableOID: tableNameToOID [ " table2 " ] !) ? . int)
345+ XCTAssertEqual ( 78 , row. column ( " intValue " , tableOID: tableNameToOID [ " table2 " ] !) ? . int)
346+ XCTAssertEqual ( " stringInTable2 " , row. column ( " stringValue " , tableOID: tableNameToOID [ " table2 " ] !) ? . string, " stringInTable2 " )
347+ XCTAssertEqual ( dateInTable2, row. column ( " dateValue " , tableOID: tableNameToOID [ " table2 " ] !) ? . date)
348+ }
349+
350+ private func prepareTableToMeasureSelectPerformance(
351+ rowCount: Int , batchSize: Int = 1_000 , schema: String , fixtureData: [ PostgresData ] ,
352+ file: StaticString = #file, line: UInt = #line) throws {
353+ XCTAssertEqual ( rowCount % batchSize, 0 , " `rowCount` must be a multiple of `batchSize` " , file: file, line: line)
296354 let conn = try PostgresConnection . test ( on: eventLoop) . wait ( )
297355 defer { try ! conn. close ( ) . wait ( ) }
356+
357+ _ = try conn. simpleQuery ( """
358+ CREATE TABLE " measureSelectPerformance " (
359+ " id " int8 NOT NULL,
360+ \( schema)
361+ PRIMARY KEY ( " id " )
362+ );
363+ """ ) . wait ( )
364+
365+ // Batch `batchSize` inserts into one for better insert performance.
366+ let totalArgumentsPerRow = fixtureData. count + 1
367+ let insertArgumentsPlaceholder = ( 0 ..< batchSize) . map { indexInBatch in
368+ " ( "
369+ + ( 0 ..< totalArgumentsPerRow) . map { argumentIndex in " $ \( indexInBatch * totalArgumentsPerRow + argumentIndex + 1 ) " }
370+ . joined ( separator: " , " )
371+ + " ) "
372+ } . joined ( separator: " , " )
373+ let insertQuery = " INSERT INTO \" measureSelectPerformance \" VALUES \( insertArgumentsPlaceholder) "
374+ var batchedFixtureData = Array ( repeating: [ PostgresData ( int: 0 ) ] + fixtureData, count: batchSize) . flatMap { $0 }
375+ for batchIndex in 0 ..< ( rowCount / batchSize) {
376+ for indexInBatch in 0 ..< batchSize {
377+ let rowIndex = batchIndex * batchSize + indexInBatch
378+ batchedFixtureData [ indexInBatch * totalArgumentsPerRow] = PostgresData ( int: rowIndex)
379+ }
380+ _ = try conn. query ( insertQuery, batchedFixtureData) . wait ( )
381+ }
382+ }
383+
384+ func testSelectTinyModel( ) throws {
385+ let conn = try PostgresConnection . test ( on: eventLoop) . wait ( )
386+ defer { try ! conn. close ( ) . wait ( ) }
387+
388+ let now = Date ( )
389+ let uuid = UUID ( )
390+ try prepareTableToMeasureSelectPerformance (
391+ rowCount: 300_000 , batchSize: 5_000 ,
392+ schema:
393+ """
394+ " int " int8,
395+ """ ,
396+ fixtureData: [ PostgresData ( int: 1234 ) ] )
397+ defer { _ = try ! conn. simpleQuery ( " DROP TABLE \" measureSelectPerformance \" " ) . wait ( ) }
398+
298399 measure {
299400 do {
300- _ = try conn. simpleQuery ( " SELECT * FROM generate_series(1, 10000) num " ) . wait ( )
401+ try conn. query ( " SELECT * FROM \" measureSelectPerformance \" " ) { row in
402+ _ = row. column ( " int " ) ? . int
403+ } . wait ( )
301404 } catch {
302405 XCTFail ( " \( error) " )
303406 }
304407 }
305408 }
306-
307- func testRangeSelectDecodePerformance( ) throws {
308- // std deviation too high
309- struct Series : Decodable {
310- var num : Int
409+
410+ func testSelectMediumModel( ) throws {
411+ let conn = try PostgresConnection . test ( on: eventLoop) . wait ( )
412+ defer { try ! conn. close ( ) . wait ( ) }
413+
414+ let now = Date ( )
415+ let uuid = UUID ( )
416+ try prepareTableToMeasureSelectPerformance (
417+ rowCount: 300_000 ,
418+ schema:
419+ // TODO: Also add a `Double` and a `Data` field to this performance test.
420+ """
421+ " string " text,
422+ " int " int8,
423+ " date " timestamptz,
424+ " uuid " uuid,
425+ """ ,
426+ fixtureData: [ PostgresData ( string: " foo " ) , PostgresData ( int: 0 ) ,
427+ now. postgresData!, PostgresData ( uuid: uuid) ] )
428+ defer { _ = try ! conn. simpleQuery ( " DROP TABLE \" measureSelectPerformance \" " ) . wait ( ) }
429+
430+ measure {
431+ do {
432+ try conn. query ( " SELECT * FROM \" measureSelectPerformance \" " ) { row in
433+ _ = row. column ( " id " ) ? . int
434+ _ = row. column ( " string " ) ? . string
435+ _ = row. column ( " int " ) ? . int
436+ _ = row. column ( " date " ) ? . date
437+ _ = row. column ( " uuid " ) ? . uuid
438+ } . wait ( )
439+ } catch {
440+ XCTFail ( " \( error) " )
441+ }
311442 }
312-
443+ }
444+
445+ func testSelectLargeModel( ) throws {
313446 let conn = try PostgresConnection . test ( on: eventLoop) . wait ( )
314447 defer { try ! conn. close ( ) . wait ( ) }
448+
449+ let now = Date ( )
450+ let uuid = UUID ( )
451+ try prepareTableToMeasureSelectPerformance (
452+ rowCount: 100_000 ,
453+ schema:
454+ // TODO: Also add `Double` and `Data` fields to this performance test.
455+ """
456+ " string1 " text,
457+ " string2 " text,
458+ " string3 " text,
459+ " string4 " text,
460+ " string5 " text,
461+ " int1 " int8,
462+ " int2 " int8,
463+ " int3 " int8,
464+ " int4 " int8,
465+ " int5 " int8,
466+ " date1 " timestamptz,
467+ " date2 " timestamptz,
468+ " date3 " timestamptz,
469+ " date4 " timestamptz,
470+ " date5 " timestamptz,
471+ " uuid1 " uuid,
472+ " uuid2 " uuid,
473+ " uuid3 " uuid,
474+ " uuid4 " uuid,
475+ " uuid5 " uuid,
476+ """ ,
477+ fixtureData: [ PostgresData ( string: " string1 " ) , PostgresData ( string: " string2 " ) , PostgresData ( string: " string3 " ) , PostgresData ( string: " string4 " ) , PostgresData ( string: " string5 " ) ,
478+ PostgresData ( int: 1 ) , PostgresData ( int: 2 ) , PostgresData ( int: 3 ) , PostgresData ( int: 4 ) , PostgresData ( int: 5 ) ,
479+ now. postgresData!, now. postgresData!, now. postgresData!, now. postgresData!, now. postgresData!,
480+ PostgresData ( uuid: uuid) , PostgresData ( uuid: uuid) , PostgresData ( uuid: uuid) , PostgresData ( uuid: uuid) , PostgresData ( uuid: uuid) ] )
481+ defer { _ = try ! conn. simpleQuery ( " DROP TABLE \" measureSelectPerformance \" " ) . wait ( ) }
482+
483+ measure {
484+ do {
485+ try conn. query ( " SELECT * FROM \" measureSelectPerformance \" " ) { row in
486+ _ = row. column ( " id " ) ? . int
487+ _ = row. column ( " string1 " ) ? . string
488+ _ = row. column ( " string2 " ) ? . string
489+ _ = row. column ( " string3 " ) ? . string
490+ _ = row. column ( " string4 " ) ? . string
491+ _ = row. column ( " string5 " ) ? . string
492+ _ = row. column ( " int1 " ) ? . int
493+ _ = row. column ( " int2 " ) ? . int
494+ _ = row. column ( " int3 " ) ? . int
495+ _ = row. column ( " int4 " ) ? . int
496+ _ = row. column ( " int5 " ) ? . int
497+ _ = row. column ( " date1 " ) ? . date
498+ _ = row. column ( " date2 " ) ? . date
499+ _ = row. column ( " date3 " ) ? . date
500+ _ = row. column ( " date4 " ) ? . date
501+ _ = row. column ( " date5 " ) ? . date
502+ _ = row. column ( " uuid1 " ) ? . uuid
503+ _ = row. column ( " uuid2 " ) ? . uuid
504+ _ = row. column ( " uuid3 " ) ? . uuid
505+ _ = row. column ( " uuid4 " ) ? . uuid
506+ _ = row. column ( " uuid5 " ) ? . uuid
507+ } . wait ( )
508+ } catch {
509+ XCTFail ( " \( error) " )
510+ }
511+ }
512+ }
513+
514+ func testSelectLargeModelWithLongFieldNames( ) throws {
515+ let conn = try PostgresConnection . test ( on: eventLoop) . wait ( )
516+ defer { try ! conn. close ( ) . wait ( ) }
517+
518+ let fieldIndices = Array ( 1 ... 20 )
519+ let fieldNames = fieldIndices. map { " veryLongFieldNameVeryLongFieldName \( $0) " }
520+ try prepareTableToMeasureSelectPerformance (
521+ rowCount: 50_000 , batchSize: 200 ,
522+ schema: fieldNames. map { " \" \( $0) \" int8 " } . joined ( separator: " , " ) + " , " ,
523+ fixtureData: fieldIndices. map { PostgresData ( int: $0) } )
524+ defer { _ = try ! conn. simpleQuery ( " DROP TABLE \" measureSelectPerformance \" " ) . wait ( ) }
525+
526+ measure {
527+ do {
528+ try conn. query ( " SELECT * FROM \" measureSelectPerformance \" " ) { row in
529+ _ = row. column ( " id " ) ? . int
530+ for fieldName in fieldNames {
531+ _ = row. column ( fieldName) ? . int
532+ }
533+ } . wait ( )
534+ } catch {
535+ XCTFail ( " \( error) " )
536+ }
537+ }
538+ }
539+
540+ func testSelectHugeModel( ) throws {
541+ let conn = try PostgresConnection . test ( on: eventLoop) . wait ( )
542+ defer { try ! conn. close ( ) . wait ( ) }
543+
544+ let fieldIndices = Array ( 1 ... 100 )
545+ let fieldNames = fieldIndices. map { " int \( $0) " }
546+ try prepareTableToMeasureSelectPerformance (
547+ rowCount: 10_000 , batchSize: 200 ,
548+ schema: fieldNames. map { " \" \( $0) \" int8 " } . joined ( separator: " , " ) + " , " ,
549+ fixtureData: fieldIndices. map { PostgresData ( int: $0) } )
550+ defer { _ = try ! conn. simpleQuery ( " DROP TABLE \" measureSelectPerformance \" " ) . wait ( ) }
551+
315552 measure {
316553 do {
317- try conn. query ( " SELECT * FROM generate_series(1, 10000) num " ) { row in
318- _ = row. column ( " num " ) ? . int
319- } . wait ( )
554+ try conn. query ( " SELECT * FROM \" measureSelectPerformance \" " ) { row in
555+ _ = row. column ( " id " ) ? . int
556+ for fieldName in fieldNames {
557+ _ = row. column ( fieldName) ? . int
558+ }
559+ } . wait ( )
320560 } catch {
321561 XCTFail ( " \( error) " )
322562 }
0 commit comments