|
|
@@ -12,7 +12,7 @@ @implementation FMDatabase
|
|
|
@synthesize cachedStatements=_cachedStatements;
|
|
|
@synthesize logsErrors=_logsErrors;
|
|
|
@synthesize crashOnErrors=_crashOnErrors;
|
|
|
-@synthesize busyRetryTimeout=_busyRetryTimeout;
|
|
|
+@synthesize busyTimeout=_busyTimeout;
|
|
|
@synthesize checkedOut=_checkedOut;
|
|
|
@synthesize traceExecution=_traceExecution;
|
|
|
|
|
|
@@ -41,7 +41,7 @@ - (instancetype)initWithPath:(NSString*)aPath {
|
|
|
_db = nil;
|
|
|
_logsErrors = YES;
|
|
|
_crashOnErrors = NO;
|
|
|
- _busyRetryTimeout = 0;
|
|
|
+ _busyTimeout = 0;
|
|
|
}
|
|
|
|
|
|
return self;
|
|
|
@@ -98,6 +98,11 @@ - (BOOL)open {
|
|
|
return NO;
|
|
|
}
|
|
|
|
|
|
+ if (_busyTimeout > 0.0) {
|
|
|
+ sqlite3_busy_timeout(_db, (int)(_busyTimeout * 1000));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
return YES;
|
|
|
}
|
|
|
|
|
|
@@ -108,6 +113,11 @@ - (BOOL)openWithFlags:(int)flags {
|
|
|
NSLog(@"error opening!: %d", err);
|
|
|
return NO;
|
|
|
}
|
|
|
+
|
|
|
+ if (_busyTimeout > 0.0) {
|
|
|
+ sqlite3_busy_timeout(_db, (int)(_busyTimeout * 1000));
|
|
|
+ }
|
|
|
+
|
|
|
return YES;
|
|
|
}
|
|
|
#endif
|
|
|
@@ -124,30 +134,19 @@ - (BOOL)close {
|
|
|
|
|
|
int rc;
|
|
|
BOOL retry;
|
|
|
- int numberOfRetries = 0;
|
|
|
BOOL triedFinalizingOpenStatements = NO;
|
|
|
|
|
|
do {
|
|
|
retry = NO;
|
|
|
rc = sqlite3_close(_db);
|
|
|
-
|
|
|
if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
|
|
|
-
|
|
|
- retry = YES;
|
|
|
- usleep(FMDatabaseSQLiteBusyMicrosecondsTimeout);
|
|
|
-
|
|
|
- if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
|
|
|
- NSLog(@"%s:%d", __FUNCTION__, __LINE__);
|
|
|
- NSLog(@"Database busy, unable to close");
|
|
|
- return NO;
|
|
|
- }
|
|
|
-
|
|
|
if (!triedFinalizingOpenStatements) {
|
|
|
triedFinalizingOpenStatements = YES;
|
|
|
sqlite3_stmt *pStmt;
|
|
|
- while ((pStmt = sqlite3_next_stmt(_db, 0x00)) !=0) {
|
|
|
+ while ((pStmt = sqlite3_next_stmt(_db, nil)) !=0) {
|
|
|
NSLog(@"Closing leaked statement");
|
|
|
sqlite3_finalize(pStmt);
|
|
|
+ retry = YES;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -161,6 +160,36 @@ - (BOOL)close {
|
|
|
return YES;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+- (void)setRetryTimeout:(NSTimeInterval)timeout {
|
|
|
+ _busyTimeout = timeout;
|
|
|
+ if (_db) {
|
|
|
+ sqlite3_busy_timeout(_db, (int)(timeout * 1000));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+- (NSTimeInterval)retryTimeout {
|
|
|
+ return _busyTimeout;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// we no longer make busyRetryTimeout public
|
|
|
+// but for folks who don't bother noticing that the interface to FMDatabase changed,
|
|
|
+// we'll still implement the method so they don't get suprise crashes
|
|
|
+- (int)busyRetryTimeout {
|
|
|
+ NSLog(@"%s:%d", __FUNCTION__, __LINE__);
|
|
|
+ NSLog(@"FMDB: busyRetryTimeout no longer works, please use retryTimeout");
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+- (void)setBusyRetryTimeout:(int)i {
|
|
|
+ NSLog(@"%s:%d", __FUNCTION__, __LINE__);
|
|
|
+ NSLog(@"FMDB: setBusyRetryTimeout does nothing, please use setRetryTimeout:");
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
- (void)clearCachedStatements {
|
|
|
|
|
|
for (NSMutableSet *statements in [_cachedStatements objectEnumerator]) {
|
|
|
@@ -605,46 +634,28 @@ - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arr
|
|
|
[statement reset];
|
|
|
}
|
|
|
|
|
|
- int numberOfRetries = 0;
|
|
|
- BOOL retry = NO;
|
|
|
-
|
|
|
if (!pStmt) {
|
|
|
- do {
|
|
|
- retry = NO;
|
|
|
- rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
|
|
|
+
|
|
|
+ rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
|
|
|
+
|
|
|
+ if (SQLITE_OK != rc) {
|
|
|
|
|
|
- if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
|
|
|
- retry = YES;
|
|
|
- usleep(FMDatabaseSQLiteBusyMicrosecondsTimeout);
|
|
|
-
|
|
|
- if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
|
|
|
- NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
|
|
|
- NSLog(@"Database busy");
|
|
|
- sqlite3_finalize(pStmt);
|
|
|
- _isExecutingStatement = NO;
|
|
|
- return nil;
|
|
|
- }
|
|
|
- }
|
|
|
- else if (SQLITE_OK != rc) {
|
|
|
-
|
|
|
- if (_logsErrors) {
|
|
|
- NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
|
|
|
- NSLog(@"DB Query: %@", sql);
|
|
|
- NSLog(@"DB Path: %@", _databasePath);
|
|
|
+ if (_logsErrors) {
|
|
|
+ NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
|
|
|
+ NSLog(@"DB Query: %@", sql);
|
|
|
+ NSLog(@"DB Path: %@", _databasePath);
|
|
|
#ifndef NS_BLOCK_ASSERTIONS
|
|
|
- if (_crashOnErrors) {
|
|
|
- abort();
|
|
|
- NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
|
|
|
- }
|
|
|
-#endif
|
|
|
+ if (_crashOnErrors) {
|
|
|
+ abort();
|
|
|
+ NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
|
|
|
}
|
|
|
-
|
|
|
- sqlite3_finalize(pStmt);
|
|
|
- _isExecutingStatement = NO;
|
|
|
- return nil;
|
|
|
+#endif
|
|
|
}
|
|
|
+
|
|
|
+ sqlite3_finalize(pStmt);
|
|
|
+ _isExecutingStatement = NO;
|
|
|
+ return nil;
|
|
|
}
|
|
|
- while (retry);
|
|
|
}
|
|
|
|
|
|
id obj;
|
|
|
@@ -797,51 +808,32 @@ - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArra
|
|
|
[cachedStmt reset];
|
|
|
}
|
|
|
|
|
|
- int numberOfRetries = 0;
|
|
|
- BOOL retry = NO;
|
|
|
-
|
|
|
if (!pStmt) {
|
|
|
+ rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
|
|
|
|
|
|
- do {
|
|
|
- retry = NO;
|
|
|
- rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0);
|
|
|
- if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
|
|
|
- retry = YES;
|
|
|
- usleep(FMDatabaseSQLiteBusyMicrosecondsTimeout);
|
|
|
-
|
|
|
- if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
|
|
|
- NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
|
|
|
- NSLog(@"Database busy");
|
|
|
- sqlite3_finalize(pStmt);
|
|
|
- _isExecutingStatement = NO;
|
|
|
- return NO;
|
|
|
- }
|
|
|
- }
|
|
|
- else if (SQLITE_OK != rc) {
|
|
|
-
|
|
|
- if (_logsErrors) {
|
|
|
- NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
|
|
|
- NSLog(@"DB Query: %@", sql);
|
|
|
- NSLog(@"DB Path: %@", _databasePath);
|
|
|
+ if (SQLITE_OK != rc) {
|
|
|
+
|
|
|
+ if (_logsErrors) {
|
|
|
+ NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
|
|
|
+ NSLog(@"DB Query: %@", sql);
|
|
|
+ NSLog(@"DB Path: %@", _databasePath);
|
|
|
#ifndef NS_BLOCK_ASSERTIONS
|
|
|
- if (_crashOnErrors) {
|
|
|
- abort();
|
|
|
- NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
|
|
|
- }
|
|
|
-#endif
|
|
|
- }
|
|
|
-
|
|
|
- sqlite3_finalize(pStmt);
|
|
|
-
|
|
|
- if (outErr) {
|
|
|
- *outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
|
|
|
+ if (_crashOnErrors) {
|
|
|
+ abort();
|
|
|
+ NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]);
|
|
|
}
|
|
|
-
|
|
|
- _isExecutingStatement = NO;
|
|
|
- return NO;
|
|
|
+#endif
|
|
|
+ }
|
|
|
+
|
|
|
+ sqlite3_finalize(pStmt);
|
|
|
+
|
|
|
+ if (outErr) {
|
|
|
+ *outErr = [self errorWithMessage:[NSString stringWithUTF8String:sqlite3_errmsg(_db)]];
|
|
|
}
|
|
|
+
|
|
|
+ _isExecutingStatement = NO;
|
|
|
+ return NO;
|
|
|
}
|
|
|
- while (retry);
|
|
|
}
|
|
|
|
|
|
id obj;
|
|
|
@@ -914,55 +906,32 @@ - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArra
|
|
|
/* Call sqlite3_step() to run the virtual machine. Since the SQL being
|
|
|
** executed is not a SELECT statement, we assume no data will be returned.
|
|
|
*/
|
|
|
- numberOfRetries = 0;
|
|
|
|
|
|
- do {
|
|
|
- rc = sqlite3_step(pStmt);
|
|
|
- retry = NO;
|
|
|
-
|
|
|
- if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
|
|
|
- // this will happen if the db is locked, like if we are doing an update or insert.
|
|
|
- // in that case, retry the step... and maybe wait just 10 milliseconds.
|
|
|
- retry = YES;
|
|
|
- if (SQLITE_LOCKED == rc) {
|
|
|
- rc = sqlite3_reset(pStmt);
|
|
|
- if (rc != SQLITE_LOCKED) {
|
|
|
- NSLog(@"Unexpected result from sqlite3_reset (%d) eu", rc);
|
|
|
- }
|
|
|
- }
|
|
|
- usleep(FMDatabaseSQLiteBusyMicrosecondsTimeout);
|
|
|
-
|
|
|
- if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) {
|
|
|
- NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]);
|
|
|
- NSLog(@"Database busy");
|
|
|
- retry = NO;
|
|
|
- }
|
|
|
- }
|
|
|
- else if (SQLITE_DONE == rc) {
|
|
|
- // all is well, let's return.
|
|
|
- }
|
|
|
- else if (SQLITE_ERROR == rc) {
|
|
|
- if (_logsErrors) {
|
|
|
- NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db));
|
|
|
- NSLog(@"DB Query: %@", sql);
|
|
|
- }
|
|
|
+ rc = sqlite3_step(pStmt);
|
|
|
+
|
|
|
+ if (SQLITE_DONE == rc) {
|
|
|
+ // all is well, let's return.
|
|
|
+ }
|
|
|
+ else if (SQLITE_ERROR == rc) {
|
|
|
+ if (_logsErrors) {
|
|
|
+ NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db));
|
|
|
+ NSLog(@"DB Query: %@", sql);
|
|
|
}
|
|
|
- else if (SQLITE_MISUSE == rc) {
|
|
|
- // uh oh.
|
|
|
- if (_logsErrors) {
|
|
|
- NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db));
|
|
|
- NSLog(@"DB Query: %@", sql);
|
|
|
- }
|
|
|
+ }
|
|
|
+ else if (SQLITE_MISUSE == rc) {
|
|
|
+ // uh oh.
|
|
|
+ if (_logsErrors) {
|
|
|
+ NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db));
|
|
|
+ NSLog(@"DB Query: %@", sql);
|
|
|
}
|
|
|
- else {
|
|
|
- // wtf?
|
|
|
- if (_logsErrors) {
|
|
|
- NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db));
|
|
|
- NSLog(@"DB Query: %@", sql);
|
|
|
- }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // wtf?
|
|
|
+ if (_logsErrors) {
|
|
|
+ NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db));
|
|
|
+ NSLog(@"DB Query: %@", sql);
|
|
|
}
|
|
|
-
|
|
|
- } while (retry);
|
|
|
+ }
|
|
|
|
|
|
if (rc == SQLITE_ROW) {
|
|
|
NSAssert1(NO, @"A executeUpdate is being called with a query string '%@'", sql);
|