Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | More work on the backup API support. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | backupApi |
Files: | files | file ages | folders |
SHA1: |
6d5a1889b68e2004c3a9fb716bc5e568 |
User & Date: | mistachkin 2012-03-24 12:47:55 |
Context
2012-03-24
| ||
14:29 | Fix semantics of backup handle finalization. Add tests. check-in: 5522052e80 user: mistachkin tags: backupApi | |
12:47 | More work on the backup API support. check-in: 6d5a1889b6 user: mistachkin tags: backupApi | |
10:05 | Start of work on ticket [c71846ed57], supporting the SQLite online backup API. check-in: 6eac31ab88 user: mistachkin tags: backupApi | |
Changes
Changes to System.Data.SQLite/SQLite3.cs.
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
....
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
|
/// <summary> /// Creates a new SQLite backup object based on the provided destination /// database connection. The source database connection is the one /// associated with this object. The source and destination database /// connections cannot be the same. /// </summary> /// <param name="destCnn">The destination database connection handle.</param> /// <param name="destName">The destination database name.</param> /// <param name="sourceName">The source database name.</param> /// <returns>The newly created backup object.</returns> internal override SQLiteBackup InitializeBackup( SQLiteConnectionHandle destCnn, string destName, string sourceName ) { if (destCnn == null) throw new ArgumentNullException("destCnn"); if (destName == null) throw new ArgumentNullException("destName"); if (sourceName == null) throw new ArgumentNullException("sourceName"); byte[] zDestName = ToUTF8(destName); byte[] zSourceName = ToUTF8(sourceName); IntPtr backup = UnsafeNativeMethods.sqlite3_backup_init( destCnn, zDestName, _sql, zSourceName); if (backup == IntPtr.Zero) throw new SQLiteException(ResultCode(), SQLiteLastError()); return new SQLiteBackup(this, backup, destCnn, zDestName, _sql, zSourceName); } /// <summary> /// Copies up to N pages from the source database to the destination /// database associated with the specified backup object. /// </summary> /// <param name="backup">The backup object to use.</param> /// <param name="nPage"> /// The number of pages to copy, negative to copy all remaining pages. /// </param> /// <returns> /// True if there are more pages to be copied, false otherwise. /// </returns> internal override bool StepBackup( SQLiteBackup backup, int nPage ) { if (backup == null) throw new ArgumentNullException("backup"); SQLiteBackupHandle handle = backup._sqlite_backup; if (handle == null) throw new InvalidOperationException( "Backup object has an invalid handle."); int n = UnsafeNativeMethods.sqlite3_backup_step(handle, nPage); backup._stepResult = n; /* NOTE: Save for use by FinishBackup. */ if (n == (int)SQLiteErrorCode.Ok) return true; else if (n == (int)SQLiteErrorCode.Done) return false; else throw new SQLiteException(n, SQLiteLastError()); } /// <summary> /// Returns the number of pages remaining to be copied from the source /// database to the destination database associated with the specified /// backup object. /// </summary> ................................................................................ if (handle == null) throw new InvalidOperationException( "Backup object has an invalid handle."); int n = UnsafeNativeMethods.sqlite3_backup_finish(handle); if (n > 0 && n != backup._stepResult) throw new SQLiteException(n, SQLiteLastError()); } /////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Determines if the SQLite core library has been initialized for the |
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
....
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
|
/// <summary> /// Creates a new SQLite backup object based on the provided destination /// database connection. The source database connection is the one /// associated with this object. The source and destination database /// connections cannot be the same. /// </summary> /// <param name="destCnn">The destination database connection.</param> /// <param name="destName">The destination database name.</param> /// <param name="sourceName">The source database name.</param> /// <returns>The newly created backup object.</returns> internal override SQLiteBackup InitializeBackup( SQLiteConnection destCnn, string destName, string sourceName ) { if (destCnn == null) throw new ArgumentNullException("destCnn"); if (destName == null) throw new ArgumentNullException("destName"); if (sourceName == null) throw new ArgumentNullException("sourceName"); SQLite3 destSqlite3 = destCnn._sql as SQLite3; if (destSqlite3 == null) throw new ArgumentException( "Destination connection has no wrapper.", "destCnn"); SQLiteConnectionHandle destHandle = destSqlite3._sql; if (destHandle == null) throw new ArgumentException( "Destination connection has an invalid handle.", "destCnn"); SQLiteConnectionHandle sourceHandle = _sql; if (sourceHandle == null) throw new InvalidOperationException( "Source connection has an invalid handle."); byte[] zDestName = ToUTF8(destName); byte[] zSourceName = ToUTF8(sourceName); IntPtr backup = UnsafeNativeMethods.sqlite3_backup_init( destHandle, zDestName, sourceHandle, zSourceName); if (backup == IntPtr.Zero) throw new SQLiteException(ResultCode(), SQLiteLastError()); return new SQLiteBackup( this, backup, destHandle, zDestName, sourceHandle, zSourceName); } /// <summary> /// Copies up to N pages from the source database to the destination /// database associated with the specified backup object. /// </summary> /// <param name="backup">The backup object to use.</param> /// <param name="nPage"> /// The number of pages to copy, negative to copy all remaining pages. /// </param> /// <param name="retry"> /// Set to true if the operation needs to be retried due to database /// locking issues; otherwise, set to false. /// </param> /// <returns> /// True if there are more pages to be copied, false otherwise. /// </returns> internal override bool StepBackup( SQLiteBackup backup, int nPage, out bool retry ) { retry = false; if (backup == null) throw new ArgumentNullException("backup"); SQLiteBackupHandle handle = backup._sqlite_backup; if (handle == null) throw new InvalidOperationException( "Backup object has an invalid handle."); int n = UnsafeNativeMethods.sqlite3_backup_step(handle, nPage); backup._stepResult = n; /* NOTE: Save for use by FinishBackup. */ if (n == (int)SQLiteErrorCode.Ok) { return true; } else if (n == (int)SQLiteErrorCode.Busy) { retry = true; return true; } else if (n == (int)SQLiteErrorCode.Locked) { retry = true; return true; } else if (n == (int)SQLiteErrorCode.Done) { return false; } else { throw new SQLiteException(n, SQLiteLastError()); } } /// <summary> /// Returns the number of pages remaining to be copied from the source /// database to the destination database associated with the specified /// backup object. /// </summary> ................................................................................ if (handle == null) throw new InvalidOperationException( "Backup object has an invalid handle."); int n = UnsafeNativeMethods.sqlite3_backup_finish(handle); if ((n > 0) && (n != backup._stepResult)) throw new SQLiteException(n, SQLiteLastError()); } /////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Determines if the SQLite core library has been initialized for the |
Changes to System.Data.SQLite/SQLiteBase.cs.
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
...
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
|
/// <summary> /// Creates a new SQLite backup object based on the provided destination /// database connection. The source database connection is the one /// associated with this object. The source and destination database /// connections cannot be the same. /// </summary> /// <param name="destCnn">The destination database connection handle.</param> /// <param name="destName">The destination database name.</param> /// <param name="sourceName">The source database name.</param> /// <returns>The newly created backup object.</returns> internal abstract SQLiteBackup InitializeBackup( SQLiteConnectionHandle destCnn, string destName, string sourceName); /// <summary> /// Copies up to N pages from the source database to the destination /// database associated with the specified backup object. /// </summary> /// <param name="backup">The backup object to use.</param> /// <param name="nPage"> /// The number of pages to copy, negative to copy all remaining pages. /// </param> /// <returns> /// True if there are more pages to be copied, false otherwise. /// </returns> internal abstract bool StepBackup(SQLiteBackup backup, int nPage); /// <summary> /// Returns the number of pages remaining to be copied from the source /// database to the destination database associated with the specified /// backup object. /// </summary> /// <param name="backup">The backup object to check.</param> ................................................................................ /// <summary> /// Enable logging of all exceptions caught from user-provided /// managed code called from native code via delegates. /// </summary> LogCallbackException = 0x8, /// <summary> /// Enable all logging. /// </summary> LogAll = LogPrepare | LogPreBind | LogBind | LogCallbackException, /// <summary> /// The default extra flags for new connections. /// </summary> Default = LogCallbackException } |
|
|
|
>
>
>
>
|
>
>
>
>
>
|
>
|
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
...
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
|
/// <summary> /// Creates a new SQLite backup object based on the provided destination /// database connection. The source database connection is the one /// associated with this object. The source and destination database /// connections cannot be the same. /// </summary> /// <param name="destCnn">The destination database connection.</param> /// <param name="destName">The destination database name.</param> /// <param name="sourceName">The source database name.</param> /// <returns>The newly created backup object.</returns> internal abstract SQLiteBackup InitializeBackup( SQLiteConnection destCnn, string destName, string sourceName); /// <summary> /// Copies up to N pages from the source database to the destination /// database associated with the specified backup object. /// </summary> /// <param name="backup">The backup object to use.</param> /// <param name="nPage"> /// The number of pages to copy or negative to copy all remaining pages. /// </param> /// <param name="retry"> /// Set to true if the operation needs to be retried due to database /// locking issues. /// </param> /// <returns> /// True if there are more pages to be copied, false otherwise. /// </returns> internal abstract bool StepBackup(SQLiteBackup backup, int nPage, out bool retry); /// <summary> /// Returns the number of pages remaining to be copied from the source /// database to the destination database associated with the specified /// backup object. /// </summary> /// <param name="backup">The backup object to check.</param> ................................................................................ /// <summary> /// Enable logging of all exceptions caught from user-provided /// managed code called from native code via delegates. /// </summary> LogCallbackException = 0x8, /// <summary> /// Enable logging of backup API errors. /// </summary> LogBackup = 0x10, /// <summary> /// Enable all logging. /// </summary> LogAll = LogPrepare | LogPreBind | LogBind | LogCallbackException | LogBackup, /// <summary> /// The default extra flags for new connections. /// </summary> Default = LogCallbackException } |
Changes to System.Data.SQLite/SQLiteConnection.cs.
325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
cmd.ExecuteNonQuery(); } } } } } } /////////////////////////////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members private bool disposed; private void CheckDisposed() /* throw */ { |
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 |
cmd.ExecuteNonQuery(); } } } } } } /////////////////////////////////////////////////////////////////////////////////////////////// #region Backup API Members /// <summary> /// Raised between each backup step. /// </summary> /// <param name="source"> /// The source database connection. /// </param> /// <param name="destination"> /// The destination database connection. /// </param> /// <param name="remainingPages"> /// The number of pages remaining to be copied. /// </param> /// <param name="totalPages"> /// The total number of pages in the source database. /// </param> /// <param name="retry"> /// Set to true if the operation needs to be retried due to database /// locking issues; otherwise, set to false. /// </param> /// <returns> /// True to continue with the backup process or false to halt the backup /// process, rolling back any changes that have been made so far. /// </returns> public delegate bool SQLiteBackupCallback( SQLiteConnection source, SQLiteConnection destination, int remainingPages, int totalPages, bool retry); /////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Backs up the database, using the specified database connection as the /// destination. /// </summary> /// <param name="destination">The destination database connection.</param> /// <param name="destinationName">The destination database name.</param> /// <param name="sourceName">The source database name.</param> /// <param name="nPage"> /// The number of pages to copy or negative to copy all remaining pages. /// </param> /// <param name="callback"> /// The method to invoke between each step of the backup process. This /// parameter may be null (i.e. no callbacks will be performed). /// </param> /// <param name="retryMilliseconds"> /// The number of milliseconds to sleep after encountering a locking error /// during the backup process. A value less than zero means that no sleep /// should be performed. /// </param> public void BackupDatabase( SQLiteConnection destination, string destinationName, string sourceName, int nPage, SQLiteBackupCallback callback, int retryMilliseconds ) { CheckDisposed(); if (_connectionState != ConnectionState.Open) throw new InvalidOperationException( "Source database is not open."); if (destination == null) throw new ArgumentNullException("destination"); if (destination._connectionState != ConnectionState.Open) throw new ArgumentException( "Destination database is not open.", "destination"); if (destinationName == null) throw new ArgumentNullException("destinationName"); if (sourceName == null) throw new ArgumentNullException("sourceName"); SQLiteBase sqliteBase = _sql; if (sqliteBase == null) throw new InvalidOperationException( "Connection object has an invalid handle."); SQLiteBackup backup = null; try { backup = sqliteBase.InitializeBackup( destination, destinationName, sourceName); /* throw */ bool retry; while (sqliteBase.StepBackup(backup, nPage, out retry)) /* throw */ { // // NOTE: If a callback was supplied by our caller, call it. // If it returns false, halt the backup process. // if ((callback != null) && !callback(this, destination, sqliteBase.RemainingBackup(backup), sqliteBase.PageCountBackup(backup), retry)) { break; } // // NOTE: If we need to retry the previous operation, wait for // the number of milliseconds specified by our caller // unless the caller used a negative number, in that case // skip sleeping at all because we do not want to block // this thread forever. // if (retry && (retryMilliseconds >= 0)) System.Threading.Thread.Sleep(retryMilliseconds); } } catch (Exception e) { #if !PLATFORM_COMPACTFRAMEWORK if ((_flags & SQLiteConnectionFlags.LogBackup) == SQLiteConnectionFlags.LogBackup) { SQLiteLog.LogMessage(0, String.Format( "Caught exception while backing up database: {0}", e)); } #endif throw; } finally { if (backup != null) sqliteBase.FinishBackup(backup); /* throw */ } } #endregion /////////////////////////////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members private bool disposed; private void CheckDisposed() /* throw */ { |
Changes to Tests/common.eagle.
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
...
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
|
#
# NOTE: Evaluate the constructed [compileCSharp] command and return the
# result.
#
eval $command
}
proc setupDb {
fileName {mode ""} {dateTimeFormat ""} {dateTimeKind ""} {flags ""}
{extra ""} {delete true} {varName db} } {
#
# NOTE: For now, all test databases used by the test suite are placed into
# the temporary directory. Each database used by a test should be
# cleaned up by that test using the "cleanupDb" procedure, below.
#
set fileName [file join [getDatabaseDirectory] [file tail $fileName]]
#
# NOTE: By default, delete any pre-existing database with the same file
# name if it currently exists.
#
if {$delete && [file exists $fileName]} then {
#
# NOTE: Attempt to delete any pre-existing database with the same file
# name.
#
if {[catch {file delete $fileName} error]} then {
#
# NOTE: We somehow failed to delete the file, report why.
................................................................................
# NOTE: We somehow failed to close the database, report why.
#
tputs $::test_channel [appendArgs \
"==== WARNING: failed to close database \"" $db "\", error: " \
\n\t $error \n]
}
#
# NOTE: Build the full path to the database file name. For now, all test
# database files are stored in the temporary directory.
#
set fileName [file join [getDatabaseDirectory] [file tail $fileName]]
#
# NOTE: Check if the file still exists.
#
if {[file exists $fileName]} then {
#
# NOTE: Skip deleting database files if somebody sets the global
# variable to prevent it.
#
if {![info exists ::no(cleanupDb)]} then {
#
# NOTE: Attempt to delete the test database file now.
|
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
|
>
>
>
>
>
>
|
>
|
|
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
...
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
|
# # NOTE: Evaluate the constructed [compileCSharp] command and return the # result. # eval $command } proc isMemoryDb { fileName } { # # NOTE: Is the specified database file name really an in-memory database? # return [expr {$fileName eq ":memory:"}] } proc setupDb { fileName {mode ""} {dateTimeFormat ""} {dateTimeKind ""} {flags ""} {extra ""} {delete true} {varName db} } { # # NOTE: First, see if the caller has requested an in-memory database. # set isMemory [isMemoryDb $fileName] # # NOTE: For now, all test databases used by the test suite are placed into # the temporary directory. Each database used by a test should be # cleaned up by that test using the "cleanupDb" procedure, below. # if {!$isMemory} then { set fileName [file join [getDatabaseDirectory] [file tail $fileName]] } # # NOTE: By default, delete any pre-existing database with the same file # name if it currently exists. # if {!$isMemory && $delete && [file exists $fileName]} then { # # NOTE: Attempt to delete any pre-existing database with the same file # name. # if {[catch {file delete $fileName} error]} then { # # NOTE: We somehow failed to delete the file, report why. ................................................................................ # NOTE: We somehow failed to close the database, report why. # tputs $::test_channel [appendArgs \ "==== WARNING: failed to close database \"" $db "\", error: " \ \n\t $error \n] } # # NOTE: First, see if the caller has requested an in-memory database. # set isMemory [isMemoryDb $fileName] # # NOTE: Build the full path to the database file name. For now, all test # database files are stored in the temporary directory. # if {!$isMemory} then { set fileName [file join [getDatabaseDirectory] [file tail $fileName]] } # # NOTE: Check if the file still exists. # if {!$isMemory && [file exists $fileName]} then { # # NOTE: Skip deleting database files if somebody sets the global # variable to prevent it. # if {![info exists ::no(cleanupDb)]} then { # # NOTE: Attempt to delete the test database file now. |