Closed Tybion closed 5 months ago
The trails.sqlite file is unencrypted, so SQLCipher is not used.
import Foundation
import GRDB
import MapKit
import CSV
: :
// Table = 'trail'
struct Trail: Codable, FetchableRecord, PersistableRecord {
var id: Int
var title: String
var descr: String
var colour: String
}
// Table = 'coord'
struct Coord: Codable, FetchableRecord, PersistableRecord {
//var seqno: Int - auto-incremented primary key
var trailid: Int
var latitude: Double
var longitude: Double
var speed: Double
}
// Table = 'location'
struct Location: Codable, FetchableRecord, PersistableRecord {
var id: Int
var title: String
var descr: String
var colour: String
var longitude: Double
var latitude: Double
}
// If these 4 values are the same in 2 trails or 2 locations, then assume they are duplicates
// Although, theoretically the 2 trails could be different, it is exteremely unlikely
struct UniqFeature {
var title : String
var descr : String
var longitude : Double // first longitude in a trail
var latitude : Double // first latitude in a trail
}
// ***************************
init() throws {
let folder = MyUtils.getAppFolder("trails")
let filemgr = FileManager.default
var isDir = ObjCBool(true)
let exists = filemgr.fileExists(atPath: folder, isDirectory: &isDir)
if !exists {
// create folder and SQLite file
do {
try filemgr.createDirectory(atPath: folder, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
let str = "Unable to create trails directory: \(error.debugDescription)"
throw myError(str)
}
}
// Create SQLite file if it doesn't exist, else just connect ..
do {
let path = folder + "trails.sqlite"
//trailsDB = try Connection(folder + "trails.sqlite")
dbQueue = try DatabaseQueue(path: path)
MyUtils.Log(TAG, "Opened trails.sqlite (un-encrypted)")
} catch let error as NSError {
let str = "Unable to create trails SQLite file: \(error.debugDescription)"
throw myError(str)
}
// Create 'location' table if it doesn't exist
/*
let location = Table("location")
let id = Expression<Int>("id")
let title = Expression<String>("title")
let descr = Expression<String>("descr")
let colour = Expression<String>("colour")
let longitude = Expression<Double>("longitude")
let latitude = Expression<Double>("latitude")
*/
do {
try dbQueue!.write { db in
try db.create(table: "location", ifNotExists: true) { t in
t.column("id", .integer)
t.column("title", .text)
t.column("descr", .text)
t.column("longitude", .double)
t.column("latitude", .double)
t.primaryKey(["id"])
}
try db.execute(sql: "CREATE INDEX IF NOT EXISTS index_location_on_longitude_latitude ON location(longitude, latitude)")
}
} catch let error as NSError {
let str = "Unable to create 'location' table: \(error.debugDescription)"
throw myError(str)
}
// Add 'colour' table to hold RGB value
// Been tested on iPhone 7 - colour(TEXT) field is created.
do {
try dbQueue!.write { db in
try db.execute(sql: "ALTER TABLE location ADD COLUMN colour text")
}
} catch let error as NSError {
let str = "Unable to add 'colour' column to 'location' table: \(error.debugDescription)"
MyUtils.Log(TAG, str)
}
// Create 'trail' and 'coord' tables if they don't exist
/*
let trail = Table("trail")
let id = Expression<Int>("id")
let title = Expression<String>("title")
let descr = Expression<String>("descr")
*/
do {
try dbQueue!.write { db in
try db.create(table: "trail", ifNotExists: true) { t in
t.column("id", .integer)
t.column("title", .text)
t.column("descr", .text)
t.primaryKey(["id"])
}
}
/*
try trailsDB.run(trail.create(ifNotExists: true) { t in
t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL,
t.column(title)
t.column(descr)
}) */
} catch let error as NSError {
let str = "Unable to create 'trail' table: \(error.debugDescription)"
throw myError(str)
//return
}
// Add 'colour' to trail
do {
try dbQueue!.write { db in
try db.execute(sql: "ALTER TABLE trail ADD COLUMN colour text")
}
} catch let error as NSError {
let str = "Unable to add 'colour' column to 'trail' table: \(error.debugDescription)"
MyUtils.Log(TAG, str)
}
/*
let coord = Table("coord")
let seqno = Expression<Int>("seqno")
let trailid = Expression<Int>("trailid")
let speed = Expression<Double>("speed")
*/
do {
try dbQueue!.write { db in
try db.create(table: "coord", ifNotExists: true) { t in
t.autoIncrementedPrimaryKey("seqno")
t.column("trailid", .integer)
t.column("latitude", .double)
t.column("longitude", .double)
t.column("speed", .double)
}
try db.execute(sql: "CREATE INDEX IF NOT EXISTS index_coord_on_trailid ON coord(trailid)")
}
/*
try trailsDB.run(coord.create(ifNotExists: true) { t in
t.column(seqno, primaryKey: .autoincrement) // "seqno" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL
t.column(trailid)
t.column(latitude)
t.column(longitude)
t.column(speed)
})
try trailsDB.run(coord.createIndex(trailid, ifNotExists: true))
*/
} catch let error as NSError {
let str = "Unable to create 'coord' table: \(error.debugDescription)"
throw myError(str)
}
}
// ***************************
// ***************************
func readTrails(trails: inout [trailGPS], idStart: Int, idFinish: Int) throws -> Int {
// Read IDs between idStart and idFinish inclusive
if (dbQueue == nil) {return -1}
/*
let trail = Table("trail")
let id = Expression<Int>("id")
let coord = Table("coord")
let trailid = Expression<Int>("trailid")
let seqno = Expression<Int>("seqno")
let title = Expression<String>("title")
let descr = Expression<String>("descr")
let speed = Expression<Double>("speed")
let latitude = Expression<Double>("latitude")
let longitude = Expression<Double>("longitude")
*/
// Read data - one SELECT statement
var oldid: Int = 0
var coords: Int = 0
var trailGps: trailGPS!
do {
try dbQueue!.read { db in
// Fetch database rows - always build SQL in DB Browser for Sqlite
let rows = try Row.fetchCursor(db, sql: """
SELECT * FROM trail
INNER JOIN coord on trail.id = coord.trailid
WHERE id>=? AND id<=?
ORDER BY id, seqno
""", arguments: [idStart, idFinish])
while let row = try rows.next() {
// If CSV has 'colour' column, then get colour as TrailColour
var color = TrailColour.MAGENTA
if let colr = row["colour"] {
color = TrailColour(rawValue: colr as! String) ?? .MAGENTA
}
//print("id: \(user[id]), email: \(user[email]), name: \(user[name])")
if oldid == 0 || oldid != row["id"] {
if oldid != 0 {
// append complete trail to array
trails.append(trailGps)
}
// next trail ..
trailGps = trailGPS(trailid: row["id"])
trailGps.title = row["title"]
trailGps.descr = row["descr"]
trailGps.colour = color
}
coords += 1
trailGps.add(coord: CLLocationCoordinate2D(
latitude: row["latitude"], longitude: row["longitude"]),
speed: row["speed"])
oldid = row["id"]
}
}
// LAST trail read (unless database is empty) ..
if oldid != 0 {trails.append(trailGps)}
} catch let error as NSError {
let str = "Unable to read trails from SQLite: \(error.debugDescription)"
throw myError(str)
//return
}
MyUtils.getSQLiteTableCount(dbQueue: dbQueue!, tablename: "trail")
MyUtils.getSQLiteTableCount(dbQueue: dbQueue!, tablename: "coord")
return coords
}
// ***************************
I'm facing the same problem. My app crashes when I try to write data into db.
Hello @Tybion,
I'm sorry for those crashes.
There's insufficient information in your report:
Thanks, Gwendal.
Sorry, yes, I accidentally pasted the wrong crashlytics report. I have attached this one for the readTrails() crash - this is the function where I have pasted the code above.
I picked this because 13 users at this stage have experienced this, so is quite representative. Crashlytics says 92% of these are running iOS 17.4.1. (5% 17.2.1, 2% 17.3.0)
Line 576 is ..
try dbQueue!.read { db in
.. so the that is not very useful - just means somewhere in the closure.
I am really under pressure at the moment and have to travel over the next 24 hours, which is really bad timing. Sorry, I do not have huge skills in this low level stuff, and I am in a team of one. When I created this ticket, I thought maybe I had done something dumb that you could point out, but sounds more serious than that.
By the way, for a TestFlight app, I have upgraded to GRDB 6.24.1, but am still waiting for Apple to approve the build.
Something is strange in the stack trace:
Crashed: GRDB.DatabaseQueue
0 libsqlite3.dylib 0x9a10c sqlite3_sourceid + 36940
1 libsqlite3.dylib 0x52a7c sqlite3_column_type + 104
sqlite3_column_type
does not call sqlite3_sourceid
. And sqlite3_sourceid
returns a static C string: it can't crash. Either Crashlytics symbolication is wrong, either we're witnessing a big linker mess.
Could it be that your application is linked against two different and competing SQLite libraries? For example, did you introduce a new framework in your app? The problem would not be with GRDB, but with the way Xcode deals with the interaction of GRDB with this framework. I unfortunately don't have a straight answer to this kind of linker problem.
Thanks, I will look into that.
@Tybion please reopen if anything new happens.
What did you do?
Upgraded GRDB to the most current version 6 available in Cocoapods
What did you expect to happen?
GRDB code that has been running for at least 2 years. No warnings about code in the IDE. Worked fine on my own iPhone 14 running iOS 17.5 beta, so expected no problems
What happened instead?
So far, about 30 customers have reported (will be more to come as upgrade is propogated) that they can't run the app because it crashes on start up.
Environment
GRDB flavor(s): (GRDB, SQLCipher, Custom SQLite build?). GRDB.swift (highest version 5) SQLCipher (4.5.6) GRDB version: Installation method: (CocoaPods, SPM, manual?) Cocoapods Xcode version: 15.3 Swift version: 5 Platform(s) running GRDB: (iOS, macOS, watchOS?). iOS macOS version running Xcode: Sonoma 14.4.1
Demo Project
Can't do this yet
Crashlytics