Skip to content

Commit

Permalink
Add process_identifier to SpanRecord (#248)
Browse files Browse the repository at this point in the history
* Creates 'AddProcessIdentifierToSpanRecordMigration', adds to current

Updates SpanRecord to have processIdentifier property

Adds Migration+Helpers to help run migrations 'upTo' a certain one.

* Update migration to inject list of migrations

Needed to make testing specific migrations possible
  • Loading branch information
atreat authored Jun 4, 2024
1 parent 77f45aa commit c47d221
Show file tree
Hide file tree
Showing 12 changed files with 366 additions and 34 deletions.
10 changes: 5 additions & 5 deletions Sources/EmbraceStorage/EmbraceStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ public class EmbraceStorage: Storage {
/// - resetIfError: If true and the migrations fail the DB will be reset entirely.
public func performMigration(
resetIfError: Bool = true,
service: MigrationServiceProtocol = MigrationService()
migrations: [Migration] = .current
) throws {
do {
try service.perform(dbQueue, migrations: .current)
try MigrationService().perform(dbQueue, migrations: migrations)
} catch let error {
if resetIfError {
ConsoleLog.error("Error performing migrations, resetting EmbraceStorage: \(error)")
try reset(migrationService: service)
try reset(migrations: migrations)
} else {
ConsoleLog.error("Error performing migrations. Reset not enabled: \(error)")
throw error // re-throw error if auto-recover is not enabled
Expand All @@ -43,13 +43,13 @@ public class EmbraceStorage: Storage {
}

/// Deletes the database and recreates it from scratch
func reset(migrationService: MigrationServiceProtocol = MigrationService()) throws {
func reset(migrations: [Migration] = .current) throws {
if let fileURL = options.fileURL {
try FileManager.default.removeItem(at: fileURL)
}

dbQueue = try Self.createDBQueue(options: options)
try performMigration(resetIfError: false, service: migrationService) // Do not perpetuate loop
try performMigration(resetIfError: false, migrations: migrations) // Do not perpetuate loop
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/EmbraceStorage/Migration/Migration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ extension Migration {
}

extension Migration {
static var foreignKeyChecks: DatabaseMigrator.ForeignKeyChecks { .immediate }
public static var foreignKeyChecks: DatabaseMigrator.ForeignKeyChecks { .immediate }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// Copyright © 2023 Embrace Mobile, Inc. All rights reserved.
//

import GRDB

struct AddProcessIdentifierToSpanRecordMigration: Migration {
static var identifier = "AddProcessIdentifierToSpanRecord" // DEV: Must not change

private static var tempSpansTableName = "spans_temp"

func perform(_ db: GRDB.Database) throws {

// create copy of `spans` table in `spans_temp`
// include new column 'process_identifier'
try db.create(table: Self.tempSpansTableName) { t in

t.column(SpanRecord.Schema.id.name, .text).notNull()
t.column(SpanRecord.Schema.traceId.name, .text).notNull()
t.primaryKey([SpanRecord.Schema.traceId.name, SpanRecord.Schema.id.name])

t.column(SpanRecord.Schema.name.name, .text).notNull()
t.column(SpanRecord.Schema.type.name, .text).notNull()
t.column(SpanRecord.Schema.startTime.name, .datetime).notNull()
t.column(SpanRecord.Schema.endTime.name, .datetime)
t.column(SpanRecord.Schema.data.name, .blob).notNull()

// include new column into `spans_temp` table
t.column(SpanRecord.Schema.processIdentifier.name, .text).notNull()
}

// copy all existing data into temp table
// include default value for `process_identifier`
try db.execute(literal: """
INSERT INTO 'spans_temp' (
'id',
'trace_id',
'name',
'type',
'start_time',
'end_time',
'data',
'process_identifier'
) SELECT
id,
trace_id,
name,
type,
start_time,
end_time,
data,
'c0ffee'
FROM 'spans'
""")

// drop original table
try db.drop(table: SpanRecord.databaseTableName)

// rename temp table to be original table
try db.rename(table: Self.tempSpansTableName, to: SpanRecord.databaseTableName)

// Create Trigger on new spans table to prevent endTime from being modified on SpanRecord
try db.execute(sql:
"""
CREATE TRIGGER IF NOT EXISTS prevent_closed_span_modification
BEFORE UPDATE ON \(SpanRecord.databaseTableName)
WHEN OLD.end_time IS NOT NULL
BEGIN
SELECT RAISE(ABORT,'Attempted to modify an already closed span.');
END;
""" )
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import Foundation

extension Array where Element == Migration {

static var current: [Migration] {
public static var current: [Migration] {
return [
// register migrations here
// order matters
AddSpanRecordMigration(),
AddSessionRecordMigration(),
AddMetadataRecordMigration(),
AddLogRecordMigration()
AddLogRecordMigration(),
AddProcessIdentifierToSpanRecordMigration()
]
}
}
6 changes: 5 additions & 1 deletion Sources/EmbraceStorage/Records/SpanRecord.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public struct SpanRecord: Codable {
public var data: Data
public var startTime: Date
public var endTime: Date?
public var processIdentifier: ProcessIdentifier

public init(
id: String,
Expand All @@ -23,7 +24,8 @@ public struct SpanRecord: Codable {
type: SpanType,
data: Data,
startTime: Date,
endTime: Date? = nil
endTime: Date? = nil,
processIdentifier: ProcessIdentifier = .current
) {
self.id = id
self.traceId = traceId
Expand All @@ -32,6 +34,7 @@ public struct SpanRecord: Codable {
self.startTime = startTime
self.endTime = endTime
self.name = name
self.processIdentifier = processIdentifier
}
}

Expand All @@ -44,6 +47,7 @@ extension SpanRecord {
static var startTime: Column { Column("start_time") }
static var endTime: Column { Column("end_time") }
static var name: Column { Column("name") }
static var processIdentifier: Column { Column("process_identifier") }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ final class StorageSpanExporterTests: XCTestCase {
let exportedSpans: [SpanRecord] = try storage.fetchAll()
XCTAssertTrue(exportedSpans.count == 1)

let exportedSpan = exportedSpans.first
XCTAssertEqual(exportedSpan?.traceId, traceId.hexString)
XCTAssertEqual(exportedSpan?.id, spanId.hexString)
XCTAssertEqual(exportedSpan!.endTime!.timeIntervalSince1970, endTime.timeIntervalSince1970, accuracy: 0.01)
let exportedSpan = try XCTUnwrap(exportedSpans.first)
XCTAssertEqual(exportedSpan.traceId, traceId.hexString)
XCTAssertEqual(exportedSpan.id, spanId.hexString)
XCTAssertEqual(exportedSpan.endTime!.timeIntervalSince1970, endTime.timeIntervalSince1970, accuracy: 0.01)
}

func test_DB_allowsOpenSpan_toUpdateAttributes() throws {
Expand Down
21 changes: 9 additions & 12 deletions Tests/EmbraceStorageTests/EmbraceStorageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,8 @@ class EmbraceStorageTests: XCTestCase {
func test_performMigration_ifResetsIfErrorTrue_resetsDB() throws {
storage = try EmbraceStorage.createInMemoryDb()

let service = ThrowingMigrationService(performToThrow: 1)
try storage.performMigration(
resetIfError: true,
service: service
)
let migration = ThrowingMigration(performToThrow: 1)
try storage.performMigration(resetIfError: true, migrations: .current + [migration])

try storage.dbQueue.read { db in
XCTAssertTrue(try db.tableExists(SessionRecord.databaseTableName))
Expand All @@ -64,34 +61,34 @@ class EmbraceStorageTests: XCTestCase {
XCTAssertTrue(try db.tableExists(LogRecord.databaseTableName))
}

XCTAssertEqual(service.currentPerformCount, 2)
XCTAssertEqual(migration.currentPerformCount, 2)
}

func test_performMigration_ifResetsIfErrorTrue_andMigrationFailsTwice_rethrowsError() throws {
storage = try EmbraceStorage.createInMemoryDb()

let service = ThrowingMigrationService(performsToThrow: [1, 2])
let migration = ThrowingMigration(performsToThrow: [1, 2])
XCTAssertThrowsError(
try storage.performMigration(
resetIfError: true,
service: service
migrations: [migration]
)
)

XCTAssertEqual(service.currentPerformCount, 2)
XCTAssertEqual(migration.currentPerformCount, 2)
}

func test_performMigration_ifResetsIfErrorFalse_rethrowsError() throws {
storage = try EmbraceStorage.createInMemoryDb()
let service = ThrowingMigrationService(performToThrow: 1)
let migration = ThrowingMigration(performToThrow: 1)

XCTAssertThrowsError(
try storage.performMigration(
resetIfError: false,
service: service
migrations: [migration]
)
)
XCTAssertEqual(service.currentPerformCount, 1)
XCTAssertEqual(migration.currentPerformCount, 1)
}

func test_reset_remakesDB() throws {
Expand Down
Loading

0 comments on commit c47d221

Please sign in to comment.