Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial NanoStore 2.0 release

  • Loading branch information...
commit 0a58f266511de6862f96034336df821c36658235 0 parents
@tciuro authored
Showing with 44,938 additions and 0 deletions.
  1. +554 −0 Classes/Advanced/NSFNanoEngine.h
  2. +1,819 −0 Classes/Advanced/NSFNanoEngine.m
  3. +146 −0 Classes/Advanced/NSFNanoResult.h
  4. +259 −0 Classes/Advanced/NSFNanoResult.m
  5. +43 −0 Classes/Private/NSFNanoBag_Private.h
  6. +69 −0 Classes/Private/NSFNanoEngine_Private.h
  7. +35 −0 Classes/Private/NSFNanoExpression_Private.h
  8. +96 −0 Classes/Private/NSFNanoGlobals_Private.h
  9. +37 −0 Classes/Private/NSFNanoObject_Private.h
  10. +35 −0 Classes/Private/NSFNanoPredicate_Private.h
  11. +43 −0 Classes/Private/NSFNanoResult_Private.h
  12. +50 −0 Classes/Private/NSFNanoSearch_Private.h
  13. +59 −0 Classes/Private/NSFNanoStore_Private.h
  14. +37 −0 Classes/Private/NanoStore_Private.h
  15. +280 −0 Classes/Public/NSFNanoBag.h
  16. +438 −0 Classes/Public/NSFNanoBag.m
  17. +131 −0 Classes/Public/NSFNanoExpression.h
  18. +107 −0 Classes/Public/NSFNanoExpression.m
  19. +323 −0 Classes/Public/NSFNanoGlobals.h
  20. +127 −0 Classes/Public/NSFNanoGlobals.m
  21. +303 −0 Classes/Public/NSFNanoObject.h
  22. +191 −0 Classes/Public/NSFNanoObject.m
  23. +80 −0 Classes/Public/NSFNanoObjectProtocol.h
  24. +128 −0 Classes/Public/NSFNanoPredicate.h
  25. +137 −0 Classes/Public/NSFNanoPredicate.m
  26. +389 −0 Classes/Public/NSFNanoSearch.h
  27. +876 −0 Classes/Public/NSFNanoSearch.m
  28. +459 −0 Classes/Public/NSFNanoStore.h
  29. +1,331 −0 Classes/Public/NSFNanoStore.m
  30. +355 −0 Classes/Public/NanoStore.h
  31. +2 −0  English.lproj/InfoPlist.strings
  32. +18 −0 Examples/iPhoneTest/Classes/RootViewController.h
  33. +228 −0 Examples/iPhoneTest/Classes/RootViewController.m
  34. +21 −0 Examples/iPhoneTest/Classes/iPhoneTestAppDelegate.h
  35. +90 −0 Examples/iPhoneTest/Classes/iPhoneTestAppDelegate.m
  36. +542 −0 Examples/iPhoneTest/MainWindow.xib
  37. +384 −0 Examples/iPhoneTest/RootViewController.xib
  38. +30 −0 Examples/iPhoneTest/iPhoneTest-Info.plist
  39. +374 −0 Examples/iPhoneTest/iPhoneTest.xcodeproj/project.pbxproj
  40. +580 −0 Examples/iPhoneTest/iPhoneTest.xcodeproj/tciuro.pbxuser
  41. +1,520 −0 Examples/iPhoneTest/iPhoneTest.xcodeproj/tciuro.perspectivev3
  42. +14 −0 Examples/iPhoneTest/iPhoneTest_Prefix.pch
  43. +17 −0 Examples/iPhoneTest/main.m
  44. +79 −0 Examples/iTunesImporter/iTunesImporter.1
  45. +90 −0 Examples/iTunesImporter/iTunesImporter.m
  46. +343 −0 Examples/iTunesImporter/iTunesImporter.xcodeproj/project.pbxproj
  47. +7 −0 Examples/iTunesImporter/iTunesImporter.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  48. +2,141 −0 ...r/iTunesImporter.xcodeproj/project.xcworkspace/xcuserdata/tciuro.xcuserdatad/UserInterfaceState.xcuserstate
  49. +256 −0 Examples/iTunesImporter/iTunesImporter.xcodeproj/tciuro.pbxuser
  50. +1,502 −0 Examples/iTunesImporter/iTunesImporter.xcodeproj/tciuro.perspectivev3
  51. +19 −0 ...les/iTunesImporter/iTunesImporter.xcodeproj/xcuserdata/tciuro.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist
  52. +76 −0 ...les/iTunesImporter/iTunesImporter.xcodeproj/xcuserdata/tciuro.xcuserdatad/xcschemes/iTunesImporter.xcscheme
  53. +22 −0 ...es/iTunesImporter/iTunesImporter.xcodeproj/xcuserdata/tciuro.xcuserdatad/xcschemes/xcschememanagement.plist
  54. +7 −0 Examples/iTunesImporter/iTunesImporter_Prefix.pch
  55. BIN  Images/NanoStore_Logo.png
  56. +28 −0 Info.plist
  57. BIN  LICENSE.pdf
  58. +694 −0 NanoStore.xcodeproj/project.pbxproj
  59. +7 −0 NanoStore.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  60. +14,748 −0 NanoStore.xcodeproj/project.xcworkspace/xcuserdata/tciuro.xcuserdatad/UserInterfaceState.xcuserstate
  61. +18 −0 NanoStore.xcodeproj/project.xcworkspace/xcuserdata/tciuro.xcuserdatad/WorkspaceSettings.xcsettings
  62. +6,157 −0 NanoStore.xcodeproj/project.xcworkspace/xcuserdata/tciuro.xcuserdatad/WorkspaceState.xcuserstate
  63. +1,047 −0 NanoStore.xcodeproj/tciuro.pbxuser
  64. +1,578 −0 NanoStore.xcodeproj/tciuro.perspectivev3
  65. +14 −0 NanoStore.xcodeproj/xcuserdata/tciuro.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist
  66. +55 −0 NanoStore.xcodeproj/xcuserdata/tciuro.xcuserdatad/xcschemes/NanoStore.xcscheme
  67. +68 −0 NanoStore.xcodeproj/xcuserdata/tciuro.xcuserdatad/xcschemes/UnitTests.xcscheme
  68. +32 −0 NanoStore.xcodeproj/xcuserdata/tciuro.xcuserdatad/xcschemes/xcschememanagement.plist
  69. +7 −0 NanoStore_Prefix.pch
  70. 0  README
  71. +21 −0 Read Me.txt
  72. +14 −0 Tests/EasyDebugging/NanoStore.m
  73. +18 −0 Tests/EasyDebugging/NanoStoreTester.h
  74. +101 −0 Tests/EasyDebugging/NanoStoreTester.m
  75. +40 −0 Tests/OCUnitExtensions/OCUnitExtensions.h
  76. +119 −0 Tests/OCUnitExtensions/OCUnitExtensions.m
  77. BIN  Tests/Resources/Icon-Fail.tiff
  78. BIN  Tests/Resources/Icon-Pass.tiff
  79. +16 −0 Tests/UnitTests/NanoStore/NanoEngineTests.h
  80. +52 −0 Tests/UnitTests/NanoStore/NanoEngineTests.m
  81. +16 −0 Tests/UnitTests/NanoStore/NanoStoreBagTests.h
  82. +719 −0 Tests/UnitTests/NanoStore/NanoStoreBagTests.m
  83. +16 −0 Tests/UnitTests/NanoStore/NanoStoreExpressionTests.h
  84. +253 −0 Tests/UnitTests/NanoStore/NanoStoreExpressionTests.m
  85. +16 −0 Tests/UnitTests/NanoStore/NanoStoreObjectTests.h
  86. +138 −0 Tests/UnitTests/NanoStore/NanoStoreObjectTests.m
  87. +16 −0 Tests/UnitTests/NanoStore/NanoStoreResultTests.h
  88. +50 −0 Tests/UnitTests/NanoStore/NanoStoreResultTests.m
  89. +16 −0 Tests/UnitTests/NanoStore/NanoStoreSearchTests.h
  90. +788 −0 Tests/UnitTests/NanoStore/NanoStoreSearchTests.m
  91. +16 −0 Tests/UnitTests/NanoStore/NanoStoreTests.h
  92. +739 −0 Tests/UnitTests/NanoStore/NanoStoreTests.m
  93. +22 −0 UnitTests-Info.plist
554 Classes/Advanced/NSFNanoEngine.h
@@ -0,0 +1,554 @@
+/*
+ NSFNanoBag.h
+ NanoStore
+
+ Copyright (c) 2010 Webbo, L.L.C. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification, are permitted
+ provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions
+ and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
+ and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the name of Webbo nor the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+ */
+
+/*! @file NSFNanoEngine.h
+ @brief A wrapper around SQLite, it provides convenience methods as well as "raw" access to the database.
+ */
+
+/** @class NSFNanoEngine
+ * A wrapper around SQLite, it provides convenience methods as well as "raw" access to the database.
+ */
+
+#import "sqlite3.h"
+
+#import "NSFNanoGlobals.h"
+#import "NSFNanoGlobals.h"
+
+@class NSFNanoResult;
+
+@interface NSFNanoEngine : NSObject
+{
+ @protected
+ sqlite3 *sqlite;
+ NSString *path;
+ NSFCacheMethod cacheMethod;
+
+ /** \cond */
+ NSMutableDictionary *schema;
+ BOOL willCommitChangeSchema;
+ unsigned int busyTimeout;
+ /** \endcond */
+}
+
+/** * A reference to the SQLite database. */
+@property (assign, readonly) sqlite3 *sqlite;
+/** * The file path where the database is located. */
+@property (copy, readonly) NSString *path;
+/** * The cache mechanism being used. */
+@property (assign, readwrite) NSFCacheMethod cacheMethod;
+
+/** @name Creating and Initializing NanoEngine
+ */
+
+//@{
+
+/** Creates and returns an engine object at a given file path.
+ * @param thePath the file path where the document store will be created. Must not be nil.
+ * @return An engine object upon success, nil otherwise.
+ * @note To manipulate the document store, you must first open it.
+ * @see - (id)initWithPath:(NSString *)thePath;
+ * @see - (BOOL)openWithCacheMethod:(NSFCacheMethod)theCacheMethod useFastMode:(BOOL)useFastMode;
+ */
+
++ (id)databaseWithPath:(NSString *)thePath;
+
+/** Initializes a newly allocated document store at a given file path.
+ * @param thePath the file path where the document store will be created. Must not be nil.
+ * @return An engine object upon success, nil otherwise.
+ * @note To manipulate the document store, you must first open it.
+ * @see + (id)databaseWithPath:(NSString *)thePath;
+ * @see - (BOOL)openWithCacheMethod:(NSFCacheMethod)theCacheMethod useFastMode:(BOOL)useFastMode;
+ */
+
+- (id)initWithPath:(NSString *)thePath;
+
+//@}
+
+/** @name Opening and Closing
+ */
+
+//@{
+
+/** Opens the engine, making it ready for manipulation.
+ * @param theCacheMethod allows to specify hwo the data will be read from the database:. This setting incurs a tradeoff between speed and memory usage.
+ * @param useFastMode if set to YES, the document store is opened with all performance turned on (more risky in case of failure). Setting it to NO is slower, but safer. See the note below for more information.
+ * @return YES upon success, NO otherwise.
+ * @note
+ * When FastMode is activated NanoStore continues without pausing as soon as it has handed data off to the operating system.
+ * If the application running NanoStore crashes, the data will be safe, but the database might become corrupted if the operating system crashes
+ * or the computer loses power before that data has been written to the disk surface.
+ * On the other hand, some operations are as much as 50 or more times faster with FastMode activated.
+ *
+ * @par
+ * If FastMode is deactivated, NanoStore will pause at critical moments to make sure that data has actually been written to the disk surface
+ * before continuing. This ensures that if the operating system crashes or if there is a power failure, the database will be uncorrupted after rebooting.
+ * Deactivating FastMode is very safe, but it is also slower.
+ */
+
+- (BOOL)openWithCacheMethod:(NSFCacheMethod)theCacheMethod useFastMode:(BOOL)useFastMode;
+
+/** Closes the database.
+ * @return YES upon success, NO otherwise.
+ */
+
+- (BOOL)close;
+
+//@}
+
+/** @name Accessors
+ */
+
+//@{
+
+/** Checks whether the document store is open or closed.
+ * @see - (void)close;
+ */
+
+- (BOOL)isDatabaseOpen;
+
+/** Checks whether a transaction is currently active.
+ * @return YES if a transaction is currently active, NO otherwise.
+ */
+
+- (BOOL)isTransactionActive;
+
+/** Sets the busy timeout.
+ * @param theTimeout is number of milliseconds that SQLite will wait to retry a busy operation.
+ * @note The acceptable range is between 100 and 5000 milliseconds. If the value is out of range, the 250 millisecond default timeout will be set instead.
+ * @see - (unsigned int)busyTimeout;
+ */
+
+- (void)setBusyTimeout:(unsigned int)theTimeout;
+
+/** Returns the current busy timeout.
+ * @see - (void)setBusyTimeout:(unsigned int)theTimeout;
+ */
+
+- (unsigned int)busyTimeout;
+
+/** Returns the recommended cache size based on the system resources available.
+ * @return The recommended cache size in number of pages.
+ */
+
++ (NSUInteger)recommendedCacheSize;
+
+/** Sets the cache size.
+ * @param numberOfPages is the number of pages.
+ * @return YES upon success, NO otherwise.
+ * @see + (NSUInteger)recommendedCacheSize;
+ * @see - (NSUInteger)cacheSize;
+ */
+
+- (BOOL)setCacheSize:(NSUInteger)numberOfPages;
+
+/** Returns the cache size.
+ * @return The current cache size.
+ * @see + (NSUInteger)recommendedCacheSize;
+ * @see - (BOOL)setCacheSize:(NSUInteger)numberOfPages;
+ */
+
+- (NSUInteger)cacheSize;
+
+/** Returns the system's page size
+ */
+
++ (NSInteger)systemPageSize;
+
+/** Sets the page size.
+ * @param numberOfBytes is the size of the page.
+ * @return YES upon success, NO otherwise.
+ * @see + (NSInteger)systemPageSize;
+ * @see - (NSUInteger)pageSize;
+ */
+
+- (BOOL)setPageSize:(NSUInteger)numberOfBytes;
+
+/** Returns the page size.
+ * @return The current page size.
+ * @see + (NSInteger)systemPageSize;
+ * @see - (BOOL)setPageSize:(NSUInteger)numberOfBytes;
+ */
+
+- (NSUInteger)pageSize;
+
+/** Sets the text encoding type.
+ * @param theEncodingType is the encoding type. Can be NSFEncodingUTF8 or NSFEncodingUTF16.
+ * @return YES upon success, NO otherwise.
+ * @see - (NSFEncodingType)encoding;
+ */
+
+- (BOOL)setEncodingType:(NSFEncodingType)theEncodingType;
+
+/** Returns the encoding type.
+ * @return The current encoding type.
+ * @see - (BOOL)setEncodingType:(NSFEncodingType)theEncodingType;
+ */
+
+- (NSFEncodingType)encoding;
+
+/** Returns the encoding type from its string equivalent.
+ * @return The encoding type if successful, NSFEncodingUnknown otherwise.
+ * @see + (NSString *)NSFEncodingTypeToNSString:(NSFEncodingType)value;
+ */
+
++ (NSFEncodingType)NSStringToNSFEncodingType:(NSString *)value;
+
+/** Returns the string equivalent of an encoding type.
+ * @return The string equivalent if successful, nil otherwise.
+ * @see + (NSFEncodingType)NSStringToNSFEncodingType:(NSString *)value;
+ */
+
++ (NSString *)NSFEncodingTypeToNSString:(NSFEncodingType)value;
+
+/** Sets the synchronous mode.
+ * @param theSynchronousMode is the synchronous mode. Can be SynchronousModeOff, SynchronousModeNormal or SynchronousModeFull.
+ * @see - (NSFSynchronousMode)synchronousMode;
+ */
+
+- (void)setSynchronousMode:(NSFSynchronousMode)theSynchronousMode;
+
+/** Returns the synchronous mode.
+ * @return The current synchronous mode.
+ * @see - (void)setSynchronousMode:(NSFSynchronousMode)theSynchronousMode;
+ */
+
+- (NSFSynchronousMode)synchronousMode;
+
+/** Sets the temporary storage mode.
+ * @param theTempStoreMode is the temporary storage mode. Can be TempStoreModeDefault, TempStoreModeFile or TempStoreModeMemory.
+ * @see - (NSFTempStoreMode)tempStoreMode;
+ */
+
+- (void)setTempStoreMode:(NSFTempStoreMode)theTempStoreMode;
+
+/** Returns the temporary storage mode.
+ * @return The current temporary storage mode.
+ * @see - (void)setTempStoreMode:(NSFTempStoreMode)theTempStoreMode;
+ */
+
+- (NSFTempStoreMode)tempStoreMode;
+
+/** * Journal mode.
+ * These values represent the options used by SQLite to the the journal mode for databases associated with the current database connection.
+
+ @par
+ The <b>DELETE</b> journaling mode is the normal behavior. In the <b>DELETE</b> mode, the rollback journal is deleted at the conclusion
+ of each transaction. Indeed, the delete operation is the action that causes the transaction to commit. (See the document titled
+ Atomic Commit In SQLite for additional detail.)
+
+ @par
+ The <b>TRUNCATE</b> journaling mode commits transactions by truncating the rollback journal to zero-length instead of deleting it.
+ On many systems, truncating a file is much faster than deleting the file since the containing directory does not need to be changed.
+
+ @par
+ The <b>PERSIST</b> journaling mode prevents the rollback journal from being deleted at the end of each transaction. Instead, the header
+ of the journal is overwritten with zeros. This will prevent other database connections from rolling the journal back. The <b>PERSIST</b>
+ journaling mode is useful as an optimization on platforms where deleting or truncating a file is much more expensive than overwriting
+ the first block of a file with zeros.
+
+ @par
+ The <b>MEMORY</b> journaling mode stores the rollback journal in volatile RAM. This saves disk I/O but at the expense of database safety
+ and integrity. If the application using SQLite crashes in the middle of a transaction when the <b>MEMORY</b> journaling mode is set, then
+ the database file will very likely go corrupt.
+
+ @par
+ The <b>WAL</b> journaling mode uses a write-ahead log instead of a rollback journal to implement transactions. The <b>WAL</b> journaling mode is
+ persistent; after being set it stays in effect across multiple database connections and after closing and reopening the database. A database
+ in <b>WAL</b> journaling mode can only be accessed by SQLite version 3.7.0 or later.
+
+ @par
+ The <b>OFF</b> journaling mode disables the rollback journal completely. No rollback journal is ever created and hence there is never a
+ rollback journal to delete. The <b>OFF</b> journaling mode disables the atomic commit and rollback capabilities of SQLite. The <b>ROLLBACK</b> command
+ no longer works; it behaves in an undefined way. Applications must avoid using the <b>ROLLBACK</b> command when the journal mode is <b>OFF</b>.
+ If the application crashes in the middle of a transaction when the <b>OFF</b> journaling mode is set, then the database file will very likely go corrupt.
+
+ @note
+ The journal_mode for an in-memory database is either <b>MEMORY</b> or <b>OFF</b> and can not be changed to a different value. An attempt to change
+ the journal_mode of an in-memory database to any setting other than <b>MEMORY</b> or <b>OFF</b> is ignored. Note also that the journal_mode cannot be changed
+ while a transaction is active.
+
+ @see NSFNanoEngine
+ */
+
+- (NSFJournalModeMode)journalModeAndReturnError:(out NSError **)outError;
+
+/** Returns the journal mode.
+ * @return The current journal mode.
+ * @see - (NSFJournalModeMode)journalModeAndReturnError:(out NSError **)outError;
+ */
+- (BOOL)setJournalMode:(NSFJournalModeMode)theMode;
+
+/** Returns a new array containing the datatypes recognized by NanoStore.
+ * @return A new array containing the datatypes recognized by NanoStore.
+ */
+
++ (NSSet *)sharedNanoStoreEngineDatatypes;
+
+/** Returns the NanoStore engine version.
+ * @return The NanoStore engine version.
+ */
+
++ (NSString *)nanoStoreEngineVersion;
+
+/** Returns the SQLite version.
+ * @return The SQLite version.
+ */
+
++ (NSString *)sqliteVersion;
+
+//@}
+
+/** @name Transactions
+ */
+
+//@{
+
+/** Starts a transaction.
+ * @return YES upon success, NO otherwise.
+ * @see - (BOOL)beginTransaction;
+ * @see - (BOOL)beginDeferredTransaction;
+ * @see - (BOOL)commitTransaction;
+ * @see - (BOOL)rollbackTransaction;
+ * @see - (BOOL)isTransactionActive;
+ */
+
+- (BOOL)beginTransaction;
+
+/** Starts a deferred transaction.
+ * @return YES upon success, NO otherwise.
+ * @see - (BOOL)beginTransaction;
+ * @see - (BOOL)commitTransaction;
+ * @see - (BOOL)rollbackTransaction;
+ * @see - (BOOL)isTransactionActive;
+ */
+
+- (BOOL)beginDeferredTransaction;
+
+/** Commits a transaction.
+ * @return YES upon success, NO otherwise.
+ * @see - (BOOL)beginTransaction;
+ * @see - (BOOL)beginDeferredTransaction;
+ * @see - (BOOL)rollbackTransaction;
+ * @see - (BOOL)isTransactionActive;
+ */
+
+- (BOOL)commitTransaction;
+
+/** Rolls back a transaction.
+ * @return YES upon success, NO otherwise.
+ * @see - (BOOL)beginTransaction;
+ * @see - (BOOL)beginDeferredTransaction;
+ * @see - (BOOL)commitTransaction;
+ * @see - (BOOL)isTransactionActive;
+ */
+
+- (BOOL)rollbackTransaction;
+
+//@}
+
+/** @name Everything About Tables
+ */
+
+//@{
+
+/** Creates a table.
+ * @param theTable the name of the table. Must not be nil.
+ * @param theColumns the names of the columns. Must not be nil.
+ * @param theDatatypes the datatypes of the columns. Must not be nil.
+ * @see - (BOOL)dropTable:(NSString *)theTable;
+ * @return YES upon success, NO otherwise.
+ * @note
+ * Allowed datatypes: NSFNanoTypeRowUID, NSFNanoTypeString, NSFNanoTypeData, NSFNanoTypeDate and NSFNanoTypeNumber.
+ * @throws NSFUnexpectedParameterException is thrown if any of the parameters are nil.
+ * @throws NSFUnexpectedParameterException is thrown if the number of columns and datatypes are not equal.
+ */
+
+- (BOOL)createTable:(NSString *)theTable withColumns:(NSArray *)theColumns datatypes:(NSArray *)theDatatypes;
+
+/** Returns a new array containing the tables found in the main document store.
+ * @return A new array containing the tables in the main document store, or an empty array if none is found.
+ * @see - (NSDictionary *)allTables;
+ * @see - (NSArray *)temporaryTables;
+ */
+
+- (NSArray *)tables;
+
+/** Returns a new array containing the tables found in the main and attached document stores.
+ * @return A new array containing the tables in the main and attached document stores, or an empty array if none is found.
+ * @note
+ * The dictionary key is the document store name and its value, an array of the tables associated with that document store.
+ * @see - (NSArray *)tables;
+ * @see - (NSArray *)temporaryTables;
+ */
+
+- (NSDictionary *)allTables;
+
+/** Returns a new array containing the columns for a given table.
+ * @param theTable is the name of the table.
+ * @return A new array containing the columns for a given table, or an empty array if none is found.
+ */
+
+- (NSArray *)columnsForTable:(NSString *)theTable;
+
+/** Returns a new array containing the temporary tables found in the main document store.
+ * @return A new array containing the temporary tables in the main document store, or an empty array if none is found.
+ * @see - (NSArray *)tables;
+ * @see - (NSDictionary *)allTables;
+ */
+
+- (NSArray *)temporaryTables;
+
+/** Returns a new array containing the datatypes for a given table.
+ * @param theTable is the name of the table.
+ * @return A new array containing the datatypes for a given table, or an empty array if none is found.
+ */
+
+- (NSArray *)datatypesForTable:(NSString *)theTable;
+
+/** Removes the table from the document store.
+ * @param theTable is the name of the table.
+ * @return YES upon success, NO otherwise.
+ * @see - (BOOL)createTable:(NSString *)theTable withColumns:(NSArray *)theColumns datatypes:(NSArray *)theDatatypes;
+ */
+
+- (BOOL)dropTable:(NSString *)theTable;
+
+//@}
+
+/** @name Everything about Indexes
+ */
+
+//@{
+
+/** Creates an index.
+ * @param theColumn is the name of the column.
+ * @param theTable is the name of the table.
+ * @param isUnique whether the index should be unique or allow duplicates.
+ * @return YES upon success, NO otherwise.
+ * @see - (void)dropIndex:(NSString *)indexName;
+ */
+
+- (BOOL)createIndexForColumn:(NSString *)theColumn table:(NSString *)theTable isUnique:(BOOL)isUnique;
+
+/** Returns a new array containing the indexes found in the main document store.
+ * @return A new array containing the indexes in the main document store, or an empty array if none is found.
+ */
+
+- (NSArray *)indexes;
+
+/** Returns a new array containing the indexes found for a given table.
+ * @return A new array containing the indexes for a given table, or an empty array if none is found.
+ */
+
+- (NSArray *)indexedColumnsForTable:(NSString *)theTable;
+
+/** Removes an index.
+ * @param theIndex is the name of the index to be removed.
+ * @see - (BOOL)createIndexForColumn:(NSString *)theColumn table:(NSString *)theTable isUnique:(BOOL)isUnique;
+ */
+
+- (void)dropIndex:(NSString *)theIndex;
+
+//@}
+
+/** @name Database Maintenance
+ */
+
+//@{
+
+/** Compacts the database, attempting to reclaim unused space.
+ * @return YES upon success, NO otherwise.
+ * @note If a transaction is open, the operation will not proceed and NO will be returned instead.
+ */
+
+- (BOOL)compact;
+
+/** Performs an integrity check on the database.
+ * @return YES upon success, NO otherwise.
+ * @note If a transaction is open, the operation will not proceed and NO will be returned instead.
+ */
+
+- (BOOL)integrityCheck;
+
+//@}
+
+/** @name Searching and Retrieving
+ */
+
+//@{
+
+/** Executes a SQL statement.
+ * @param theSQLStatement is the SQL statement to be executed. Must not be nil or an empty string.
+ * @return Returns a NSFNanoResult.
+ * @throws NSFUnexpectedParameterException is thrown if the statement is nil or an empty string.
+ * @attention Check NSFNanoResult's error property to find out if there was a problem executing the statement.
+ * @note The result set will always contain string values. If you need to obtain NanoObjects instead, use the NSFNanoSearch class.
+ * @see NSFNanoSearch
+ */
+
+- (NSFNanoResult *)executeSQL:(NSString *)theSQLStatement;
+
+/** Returns the largest ROWUID for a given table.
+ * @param theTable is the table from which to obtain the largest ROWUID. Must not be nil.
+ * @return The largest ROWUID in use.
+ * @throws NSFUnexpectedParameterException is thrown if the table is nil.
+ */
+
+- (long long)maxRowUIDForTable:(NSString *)theTable;
+
+//@}
+
+/** @name Miscellaneous
+ */
+
+//@{
+
+/** Returns a string containing the base 64 representation of a data element.
+ * @return A string encoded in base 64 format.
+ */
+
++ (NSString *)encodeDataToBase64:(NSData *)theData;
+
+/** Returns a data element containing from a base 64 formatted string.
+ * @return A data element.
+ */
+
++ (NSData *)decodeDataFromBase64:(NSString *)theEncodedData;
+
+/** Returns a UUID string
+ * @return A string containing a representation of a UUID.
+ */
+
++ (NSString *)stringWithUUID;
+
+/** Returns a string representation of the engine.
+ */
+
+- (NSString *)description;
+
+//@}
+
+@end
1,819 Classes/Advanced/NSFNanoEngine.m
@@ -0,0 +1,1819 @@
+/*
+ NSFNanoEngine.m
+ NanoStore
+
+ Copyright (c) 2010 Webbo, L.L.C. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without modification, are permitted
+ provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions
+ and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
+ and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the name of Webbo nor the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+ */
+
+#import "NanoStore.h"
+#import "NanoStore_Private.h"
+
+#import <stdio.h>
+#import <stdlib.h>
+#import <unistd.h>
+
+#pragma mark// ==================================
+#pragma mark// NSFNanoEngine C Declarations
+#pragma mark// ==================================
+
+int NSFP_commitCallback(void* nsfdb);
+
+static char __NSFP_base64Table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static NSArray *__NSFP_SQLCommandsReturningData = nil;
+static NSArray *__NSFPSharedROWIDKeywords = nil;
+static NSSet *__NSFPSharedNanoStoreEngineDatatypes = nil;
+
+#pragma mark -
+
+@implementation NSFNanoEngine
+
+@synthesize sqlite;
+@synthesize path;
+@synthesize cacheMethod;
+
+#pragma mark -
+
+#pragma mark// ==================================
+#pragma mark// Initialization/Cleanup Methods
+#pragma mark// ==================================
+
++ (id)databaseWithPath:(NSString *)thePath
+{
+ if (nil == thePath)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: thePath is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ return [[[self alloc]initWithPath:thePath]autorelease];
+}
+
+- (id)initWithPath:(NSString *)thePath
+{
+ if (nil == thePath)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: thePath is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if ((self = [self init])) {
+ path = thePath;
+ }
+
+ return self;
+}
+
+/** \cond */
+
++ (void)initialize
+{
+ __NSFP_SQLCommandsReturningData = [[NSArray alloc]initWithObjects:@"SELECT", @"PRAGMA", @"EXPLAIN", nil];
+}
+
+- (id)init
+{
+ if ((self = [super init])) {
+ path = nil;
+ schema = nil;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [self close];
+
+ [schema release];
+ path = nil;
+
+ [super dealloc];
+}
+
+/** \endcond */
+
+- (NSString*)description
+{
+ return [self NSFP_nestedDescriptionWithPrefixedSpace:@""];
+}
+
+#pragma mark// ==================================
+#pragma mark// Opening & Closing Methods
+#pragma mark// ==================================
+
+- (BOOL)openWithCacheMethod:(NSFCacheMethod)theCacheMethod useFastMode:(BOOL)useFastMode
+{
+ int status = sqlite3_open_v2( [path UTF8String], &sqlite,
+ SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_AUTOPROXY | SQLITE_OPEN_FULLMUTEX, NULL);
+
+ // Set NanoStoreEngine's page size to match the system current page size
+ if (0 == [[self tables]count]) {
+ NSUInteger systemPageSize = [NSFNanoEngine systemPageSize];
+ [self setPageSize:systemPageSize];
+ }
+
+ // Since we're operating with extended result code support, extract the bits
+ // and obtain the regular result code
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
+
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
+
+ if ((SQLITE_OK != status) || (sqlite3_extended_result_codes(self.sqlite, 1) != SQLITE_OK))
+ return NO;
+
+ if ([[path lowercaseString]isEqualToString:NSFMemoryDatabase] == YES) {
+
+ sqlite3_exec(self.sqlite, "PRAGMA fullfsync = OFF;", NULL, NULL, NULL);
+ sqlite3_exec(self.sqlite, "PRAGMA temp_store = MEMORY", NULL, NULL, NULL);
+ sqlite3_exec(self.sqlite, "PRAGMA synchronous = OFF;", NULL, NULL, NULL);
+ sqlite3_exec(self.sqlite, "PRAGMA journal_mode = MEMORY;", NULL, NULL, NULL);
+ sqlite3_exec(self.sqlite, "PRAGMA temp_store = MEMORY", NULL, NULL, NULL);
+
+ } else {
+
+ // Set FastMode accordingly...
+ if (YES == useFastMode) {
+ sqlite3_exec(self.sqlite, "PRAGMA fullfsync = OFF;", NULL, NULL, NULL);
+ sqlite3_exec(self.sqlite, "PRAGMA synchronous = OFF;", NULL, NULL, NULL);
+ sqlite3_exec(self.sqlite, "PRAGMA journal_mode = MEMORY;", NULL, NULL, NULL);
+ sqlite3_exec(self.sqlite, "PRAGMA temp_store = MEMORY", NULL, NULL, NULL);
+ } else {
+ sqlite3_exec(self.sqlite, "PRAGMA fullfsync = OFF;", NULL, NULL, NULL);
+ sqlite3_exec(self.sqlite, "PRAGMA synchronous = FULL;", NULL, NULL, NULL);
+ sqlite3_exec(self.sqlite, "PRAGMA journal_mode = DELETE", NULL, NULL, NULL);
+ sqlite3_exec(self.sqlite, "PRAGMA temp_store = DEFAULT", NULL, NULL, NULL);
+ }
+
+ }
+
+ // Save whether we want data to be fetched lazily
+ cacheMethod = theCacheMethod;
+
+ [self setBusyTimeout:250];
+
+ // Refresh the schema cache
+ [self NSFP_rebuildDatatypeCache];
+
+ [self NSFP_installCommitCallback];
+
+ return YES;
+}
+
+
+- (BOOL)close
+{
+ if (NO == self.sqlite) {
+ return NO;
+ }
+
+ if (YES == [self isTransactionActive]) {
+ [self rollbackTransaction];
+ }
+
+ // Make sure we clear the temporary data from schema
+ NSArray *tempTables = [self temporaryTables];
+
+ if ([tempTables count] > 0) {
+ [self beginTransaction];
+
+ for (NSString *table in tempTables) {
+ [self dropTable:table];
+ }
+
+ [self commitTransaction];
+ }
+
+ int status = sqlite3_close(self.sqlite);
+ sqlite = NULL;
+
+ // Since we're operating with extended result code support, extract the bits
+ // and obtain the regular result code
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
+
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
+
+ return (SQLITE_OK == status);
+}
+
+- (BOOL)isDatabaseOpen
+{
+ return (NULL != self.sqlite);
+}
+
+#pragma mark Transaction Methods
+
+- (BOOL)beginTransaction
+{
+ if (YES == [self isTransactionActive])
+ return NO;
+
+ willCommitChangeSchema = NO;
+
+ return [self beginDeferredTransaction];
+}
+
+- (BOOL)beginDeferredTransaction
+{
+ if (YES == [self isTransactionActive])
+ return NO;
+
+ willCommitChangeSchema = NO;
+
+ return [self NSFP_beginTransactionMode:@"BEGIN DEFERRED TRANSACTION;"];
+}
+
+- (BOOL)commitTransaction
+{
+ if (NO == [self isTransactionActive]) {
+ willCommitChangeSchema = NO;
+ return NO;
+ }
+
+ if (NO == willCommitChangeSchema)
+ [self NSFP_uninstallCommitCallback];
+
+ BOOL success = (nil == [[self executeSQL:@"COMMIT TRANSACTION;"]error]);
+
+ if (NO == willCommitChangeSchema)
+ [self NSFP_installCommitCallback];
+
+ willCommitChangeSchema = NO;
+
+ return success;
+}
+
+- (BOOL)rollbackTransaction
+{
+ if ([self isTransactionActive] == NO) {
+ willCommitChangeSchema = NO;
+ return NO;
+ }
+
+ BOOL success = (nil == [[self executeSQL:@"ROLLBACK TRANSACTION;"]error]);
+
+ willCommitChangeSchema = NO;
+
+ return success;
+}
+
+- (BOOL)isTransactionActive
+{
+ sqlite3* myDB = self.sqlite;
+
+ int status = sqlite3_get_autocommit(myDB);
+
+ // Since we're operating with extended result code support, extract the bits
+ // and obtain the regular result code
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
+
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
+
+ return (0 == status);
+}
+
+#pragma mark// ==================================
+#pragma mark// Utility Methods
+#pragma mark// ==================================
+
++ (NSString *)stringWithUUID
+{
+ CFUUIDRef uuid;
+ NSString *uidString;
+ uuid = CFMakeCollectable(CFUUIDCreate(NULL));
+ uidString = NSMakeCollectable(CFUUIDCreateString(NULL,uuid));
+ [(id)uuid release];
+ return [uidString autorelease];
+
+}
+
+- (BOOL)compact
+{
+ if (NO == [self isTransactionActive])
+ return (nil == [[self executeSQL:@"VACUUM;"]error]);
+
+ return NO;
+}
+
+- (BOOL)integrityCheck
+{
+ if (NO == [self isTransactionActive]) {
+ NSFNanoResult* result = [self executeSQL:@"PRAGMA integrity_check"];
+
+ // SQLite returns the status as 'ok'. Let's code defensively and lowercase the result.
+ return ([[[[result valuesForColumn:@"integrity_check"]lastObject]lowercaseString]isEqualToString:@"ok"]);
+ }
+
+ return NO;
+}
+
++ (NSString *)nanoStoreEngineVersion
+{
+ return NSFVersionKey;
+}
+
++ (NSString *)sqliteVersion
+{
+ return [NSString stringWithUTF8String: sqlite3_libversion()];
+}
+
++ (NSSet*)sharedNanoStoreEngineDatatypes
+{
+ if (nil == __NSFPSharedNanoStoreEngineDatatypes)
+ __NSFPSharedNanoStoreEngineDatatypes = [[NSSet alloc]initWithObjects:NSFStringFromNanoDataType(NSFNanoTypeRowUID),
+ NSFStringFromNanoDataType(NSFNanoTypeString),
+ NSFStringFromNanoDataType(NSFNanoTypeData),
+ NSFStringFromNanoDataType(NSFNanoTypeDate),
+ NSFStringFromNanoDataType(NSFNanoTypeNumber),
+ nil];
+
+ return __NSFPSharedNanoStoreEngineDatatypes;
+}
+
+#pragma mark// ==================================
+#pragma mark// Table and Introspection Methods
+#pragma mark// ==================================
+
+- (BOOL)createTable:(NSString *)table withColumns:(NSArray *)columns datatypes:(NSArray *)datatypes
+{
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == columns)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: columns is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == datatypes)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: datatypes is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ return [self NSFP_createTable:table withColumns:columns datatypes:datatypes isTemporary:NO];
+}
+
+- (BOOL)dropTable:(NSString *)table
+{
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ BOOL transactionSetHere = NO;
+ if ([self isTransactionActive] == NO)
+ transactionSetHere = [self beginTransaction];
+
+ NSString *theSQLStatement = [[NSString alloc]initWithFormat:@"DROP TABLE %@;", table];
+ BOOL everythingIsFine = (nil == [[self executeSQL:theSQLStatement]error]);
+ [theSQLStatement release];
+
+ if (everythingIsFine) {
+ theSQLStatement = [[NSString alloc]initWithFormat:@"DELETE FROM %@ WHERE %@ = '%@';", NSFP_SchemaTable, NSFP_TableIdentifier, table];
+ everythingIsFine = (nil == [[self executeSQL:theSQLStatement]error]);
+ [theSQLStatement release];
+ }
+
+ if (transactionSetHere) {
+ if (everythingIsFine)
+ [self commitTransaction];
+ else
+ [self rollbackTransaction];
+ }
+
+ if (everythingIsFine)
+ [self NSFP_rebuildDatatypeCache];
+
+ return everythingIsFine;
+}
+
+- (BOOL)createIndexForColumn:(NSString *)column table:(NSString *)table isUnique:(BOOL)flag
+{
+ if (nil == column)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: column is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSString *theSQLStatement = nil;
+
+ if (flag)
+ theSQLStatement = [[NSString alloc]initWithFormat:@"CREATE UNIQUE INDEX %@_%@_IDX ON %@ (%@);", table, column, table, column];
+ else
+ theSQLStatement = [[NSString alloc]initWithFormat:@"CREATE INDEX %@_%@_IDX ON %@ (%@);", table, column, table, column];
+
+ BOOL indexWasCreated = (nil == [[self executeSQL:theSQLStatement]error]);
+
+ // Cleanup
+ [theSQLStatement release];
+
+ return indexWasCreated;
+}
+
+- (void)dropIndex:(NSString *)indexName
+{
+ if (nil == indexName)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: indexName is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSString *theSQLStatement = [[NSString alloc]initWithFormat:@"DROP INDEX %@;", indexName];
+
+ [self executeSQL:theSQLStatement];
+
+ // Cleanup
+ [theSQLStatement release];
+}
+
+- (NSArray *)tables
+{
+ NSArray *allTables = [self NSFP_flattenAllTables];
+ if ([allTables count] == 0)
+ return allTables;
+
+ // Remove NSF's private table
+ NSMutableArray *tempTables = [NSMutableArray arrayWithArray:allTables];
+ [tempTables removeObject:NSFP_SchemaTable];
+
+ return tempTables;
+}
+
+- (NSDictionary *)allTables
+{
+ NSMutableDictionary *allTables = [NSMutableDictionary dictionary];
+
+ // Make sure we obtain full column names
+ [self NSFP_setFullColumnNamesEnabled];
+
+ NSFNanoResult *databasesResult = [self executeSQL:@"PRAGMA database_list"];
+ NSArray *databases = [databasesResult valuesForColumn:@"name"];
+
+ for (NSString *database in databases) {
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT * FROM %@.sqlite_master;", database];
+ NSFNanoResult* result = [self executeSQL:theSQLStatement];
+ if (nil == [result error]) {
+ // Get all tables in the database
+ NSArray *databaseTables = [result valuesForColumn:@"sqlite_master.tbl_name"];
+ NSSet *tablesPerDatabase = [NSSet setWithArray:databaseTables];
+ [allTables setObject: [tablesPerDatabase allObjects] forKey: database];
+ }
+ }
+
+ return allTables;
+}
+
+- (NSArray *)columnsForTable:(NSString *)table
+{
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSString *theSQLStatement = nil;
+ NSString *database = [self NSFP_prefixWithDotDelimiter:table];
+
+ if ([database isEqualToString:table] == NO) {
+ database = [NSString stringWithFormat:@"%@.", database];
+ table = [self NSFP_suffixWithDotDelimiter:table];
+ theSQLStatement = [NSString stringWithFormat:@"PRAGMA %@.table_info ('%@');", database, table];
+ } else {
+ theSQLStatement = [NSString stringWithFormat:@"PRAGMA table_info ('%@');", table];
+ }
+
+ NSFNanoResult *result = [self executeSQL:theSQLStatement];
+
+ return [result valuesForColumn:@"name"];
+}
+
+- (NSArray *)datatypesForTable:(NSString *)table
+{
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSString *theSQLStatement = nil;
+ NSString *database = [self NSFP_prefixWithDotDelimiter:table];
+
+ if ([database isEqualToString:table] == NO) {
+ database = [NSString stringWithFormat:@"%@.", database];
+ table = [self NSFP_suffixWithDotDelimiter:table];
+ theSQLStatement = [NSString stringWithFormat:@"PRAGMA %@.table_info ('%@');", database, table];
+ } else {
+ theSQLStatement = [NSString stringWithFormat:@"PRAGMA table_info ('%@');", table];
+ }
+
+ NSFNanoResult *result = [self executeSQL:theSQLStatement];
+
+ return [result valuesForColumn:@"type"];
+}
+
+- (NSArray *)indexes
+{
+ NSFNanoResult* result = [self executeSQL:@"SELECT name FROM sqlite_master WHERE type='index' ORDER BY name"];
+
+ return [result valuesForColumn:@"sqlite_master.name"];
+}
+
+- (NSArray *)indexedColumnsForTable:(NSString *)table
+{
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSFNanoResult* result = [self executeSQL:[NSString stringWithFormat:@"SELECT sqlite_master.name FROM sqlite_master WHERE type = 'index' AND sqlite_master.tbl_name = '%@';", table]];
+ if ([result numberOfRows] == 0) {
+ result = [self executeSQL:[NSString stringWithFormat:@"SELECT sqlite_temp_master.name FROM sqlite_temp_master WHERE type = 'index' AND sqlite_temp_master.tbl_name = '%@';", table]];
+ return [result valuesForColumn:@"sqlite_temp_master.name"];
+ }
+
+ return [result valuesForColumn:@"sqlite_master.name"];
+}
+
+- (NSArray *)temporaryTables
+{
+ NSFNanoResult* result = [self executeSQL:@"SELECT * FROM sqlite_temp_master"];
+ return [[NSSet setWithArray:[result valuesForColumn:@"sqlite_temp_master.tbl_name"]]allObjects];
+}
+
+- (NSFNanoResult *)executeSQL:(NSString *)theSQLStatement
+{
+ if (nil == theSQLStatement)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if ([theSQLStatement length] == 0)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is empty.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ // Check whether we will need to return a dictionary with results
+ sqlite3 *sqliteStore = self.sqlite;
+ NSMutableDictionary *info = [NSMutableDictionary dictionary];
+ BOOL returnInfo = NO;
+
+ for (NSString *sqlCommand in __NSFP_SQLCommandsReturningData) {
+ if ([theSQLStatement compare:sqlCommand options:NSCaseInsensitiveSearch range:NSMakeRange(0, [sqlCommand length])] == NSOrderedSame) {
+ returnInfo = YES;
+ break;
+ }
+ }
+
+ int status = SQLITE_OK;
+ char *errorMessage = NULL;
+
+ if (returnInfo) {
+ sqlite3_stmt *theSQLiteStatement = NULL;
+
+ status = sqlite3_prepare_v2 (sqliteStore, [theSQLStatement UTF8String], -1, &theSQLiteStatement, NULL );
+
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
+
+ if (SQLITE_OK == status) {
+ info = [NSMutableDictionary dictionary];
+ int columnIndex, numColumns = sqlite3_column_count (theSQLiteStatement);
+
+ while (SQLITE_ROW == sqlite3_step (theSQLiteStatement)) {
+ for (columnIndex = 0; columnIndex < numColumns; columnIndex++) {
+ // Safety check: obtain the column and value. If the column is NULL, skip the iteration.
+ char *columnUTF8 = (char *)sqlite3_column_name (theSQLiteStatement, columnIndex);
+ if (NULL == columnUTF8) {
+ continue;
+ }
+ NSString *column = [[NSString alloc]initWithUTF8String:columnUTF8];
+
+ // Sanity check: some queries return NULL, which would cause a crash below.
+ char *valueUTF8 = (char *)sqlite3_column_text (theSQLiteStatement, columnIndex);
+ NSString *value = nil;
+ if (NULL != valueUTF8) {
+ value = [[NSString alloc]initWithUTF8String:valueUTF8];
+ } else {
+ value = [[[NSNull null]description]retain];
+ }
+
+ // Obtain the array to collect the values. If the array doesn't exist, create it.
+ NSMutableArray *values = [[info objectForKey:column]retain];
+ if (nil == values) {
+ values = [NSMutableArray new];
+ }
+ [values addObject:value];
+ [info setObject:values forKey:column];
+
+ // Let's cleanup. This will keep the memory footprint low...
+ [column release];
+ [value release];
+ [values release];
+ }
+
+ }
+
+ sqlite3_finalize (theSQLiteStatement);
+ }
+ } else {
+ status = sqlite3_exec(sqliteStore, [theSQLStatement UTF8String], NULL, NULL, &errorMessage);
+
+ // Since we're operating with extended result code support, extract the bits
+ // and obtain the regular result code
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
+
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
+ }
+
+ NSFNanoResult *result = nil;
+
+ if (SQLITE_OK != status) {
+ NSString *msg = (NULL != errorMessage) ? [NSString stringWithUTF8String:errorMessage] : [NSString stringWithFormat:@"SQLite error ID: %ld", status];
+ result = [NSFNanoResult resultWithError:[NSError errorWithDomain:NSFDomainKey
+ code:NSFNanoStoreErrorKey
+ userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"*** -[%@ %s]: %@", [self class], _cmd, msg]
+ forKey:NSLocalizedFailureReasonErrorKey]]];
+ } else {
+ result = [NSFNanoResult resultWithDictionary:info];
+ }
+
+ // Cleanup
+ if (NULL != errorMessage) {
+ sqlite3_free (errorMessage);
+ }
+
+ return result;
+}
+
+- (long long)maxRowUIDForTable:(NSString *)table
+{
+ if (nil == table) {
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+ }
+
+ NSString *sql = [[NSString alloc]initWithFormat:@"SELECT max(ROWID) FROM %@", table];
+
+ NSFNanoResult *result = [self executeSQL:sql];
+
+ [sql release];
+
+ return [[result firstValue]longLongValue];
+}
+
+#pragma mark// ==================================
+#pragma mark// SQLite Tunning Methods
+#pragma mark// ==================================
+
+- (void)setBusyTimeout:(unsigned int)theTimeout
+{
+ // If the timeout is out-of-range, default the value
+ if ((theTimeout < 100) || (theTimeout > 5 * 1000)) {
+ theTimeout = 250;
+ }
+
+ busyTimeout = theTimeout;
+
+ sqlite3_busy_timeout(self.sqlite, busyTimeout);
+}
+
+- (unsigned int)busyTimeout
+{
+ return busyTimeout;
+}
+
++ (NSInteger)systemPageSize
+{
+ static NSUInteger __sSystemPageSize = NSNotFound;
+
+ if (NSNotFound == __sSystemPageSize) {
+ __sSystemPageSize = getpagesize();
+ }
+
+ return __sSystemPageSize;
+}
+
++ (NSUInteger)recommendedCacheSize
+{
+ static NSUInteger __sRecommendedCacheSize = NSNotFound;
+
+ if (NSNotFound == __sRecommendedCacheSize) {
+ NSUInteger defaultNanoStoreEngineCacheSize = 10000;
+ unsigned long long physMem = [[NSProcessInfo processInfo] physicalMemory];
+
+ physMem = physMem / (512 * 1024 * 1000);
+ __sRecommendedCacheSize = (NSInteger)physMem * defaultNanoStoreEngineCacheSize;
+ if (0 == __sRecommendedCacheSize) {
+ __sRecommendedCacheSize = defaultNanoStoreEngineCacheSize;
+ }
+ }
+
+ return __sRecommendedCacheSize;
+}
+
+- (BOOL)setCacheSize:(NSUInteger)numberOfPages
+{
+ if (numberOfPages < 1000)
+ numberOfPages = 1000;
+
+ [self executeSQL:[NSString stringWithFormat:@"PRAGMA cache_size = %ld", numberOfPages]];
+ NSUInteger cacheSize = [self cacheSize];
+ return (cacheSize == numberOfPages);
+}
+
+- (NSUInteger)cacheSize
+{
+ NSFNanoResult *result = [self executeSQL:@"PRAGMA cache_size;"];
+ NSString *value = [result firstValue];
+ return [value integerValue];
+}
+
+- (BOOL)setPageSize:(NSUInteger)numberOfBytes
+{
+ [self executeSQL:[NSString stringWithFormat:@"PRAGMA page_size = %ld", numberOfBytes]];
+ NSUInteger pageSize = [self pageSize];
+ return (pageSize == numberOfBytes);
+}
+
+- (NSUInteger)pageSize
+{
+ NSFNanoResult *result = [self executeSQL:@"PRAGMA page_size;"];
+ NSString *value = [result firstValue];
+ return [value integerValue];
+}
+
+- (BOOL)setEncodingType:(NSFEncodingType)theEncodingType
+{
+ BOOL success = NO;
+
+ if (NSFEncodingUTF8 == theEncodingType) {
+ [self executeSQL:@"PRAGMA encoding = \"UTF-8\";"];
+ NSFEncodingType encoding = [self encoding];
+ success = (NSFEncodingUTF8 == encoding);
+ } else if (NSFEncodingUTF16 == theEncodingType) {
+ [self executeSQL:@"PRAGMA encoding = \"UTF-16\";"];
+ NSFEncodingType encoding = [self encoding];
+ success = (NSFEncodingUTF16 == encoding);
+ }
+
+ return success;
+}
+
+- (NSFEncodingType)encoding
+{
+ NSFNanoResult *result = [self executeSQL:@"pragma encoding;"];
+ NSString *value = [result firstValue];
+ return [NSFNanoEngine NSStringToNSFEncodingType:value];
+}
+
++ (NSFEncodingType)NSStringToNSFEncodingType:(NSString *)value
+{
+ NSFEncodingType convertedValue = NSFEncodingUnknown;
+
+ if (YES == [value isEqualToString:@"UTF-8"]) {
+ convertedValue = NSFEncodingUTF8;
+ } else if (YES == [value isEqualToString:@"UTF-16"]) {
+ convertedValue = NSFEncodingUTF16;
+ }
+
+ return convertedValue;
+}
+
++ (NSString *)NSFEncodingTypeToNSString:(NSFEncodingType)value
+{
+ NSString *convertedValue = nil;
+
+ if (NSFEncodingUTF8 == value) {
+ convertedValue = @"UTF-8";
+ } else if (NSFEncodingUTF16 == value) {
+ convertedValue = @"UTF-16";
+ }
+
+ return convertedValue;
+}
+
+- (NSFSynchronousMode)synchronousMode
+{
+ NSFNanoResult* result = [self executeSQL:@"PRAGMA synchronous"];
+ return [[[result valuesForColumn:@"synchronous"]lastObject]intValue];
+}
+
+- (void)setSynchronousMode:(NSFSynchronousMode)mode
+{
+ switch (mode) {
+ case SynchronousModeOff:
+ [self executeSQL:@"PRAGMA synchronous = OFF;"];
+ break;
+ case SynchronousModeFull:
+ [self executeSQL:@"PRAGMA synchronous = FULL;"];
+ break;
+ default:
+ [self executeSQL:@"PRAGMA synchronous = NORMAL;"];
+ break;
+ }
+}
+
+- (NSFTempStoreMode)tempStoreMode
+{
+ NSFNanoResult* result = [self executeSQL:@"PRAGMA temp_store"];
+
+ return [[[result valuesForColumn:@"temp_store"]lastObject]intValue];
+}
+
+- (void)setTempStoreMode:(NSFTempStoreMode)mode
+{
+ switch (mode) {
+ case TempStoreModeFile:
+ [self executeSQL:@"PRAGMA temp_store = FILE"];
+ break;
+ case TempStoreModeMemory:
+ [self executeSQL:@"PRAGMA temp_store = MEMORY"];
+ break;
+ default:
+ [self executeSQL:@"PRAGMA temp_store = DEFAULT"];
+ break;
+ }
+}
+
+- (NSFJournalModeMode)journalModeAndReturnError:(out NSError **)outError
+{
+ NSFNanoResult *result = [self executeSQL:@"PRAGMA journal_mode; "];
+ if (nil != [result error]) {
+ if (nil != outError) {
+ *outError = [[[result error]copy]autorelease];
+ return JournalModeDelete;
+ }
+ }
+
+ NSString *journalModeString = [result firstValue];
+ if ([journalModeString isEqualToString:@"TRUNCATE"]) return JournalModeDelete;
+ else if ([journalModeString isEqualToString:@"TRUNCATE"]) return JournalModeTruncate;
+ else if ([journalModeString isEqualToString:@"PERSIST"]) return JournalModePersist;
+ else if ([journalModeString isEqualToString:@"MEMORY"]) return JournalModeMemory;
+ else if ([journalModeString isEqualToString:@"WAL"]) return JournalModeWAL;
+ else return JournalModeOFF;
+}
+
+- (BOOL)setJournalMode:(NSFJournalModeMode)theMode
+{
+ if (YES == [self isTransactionActive]) {
+ return NO;
+ }
+
+ NSString *theModeString = nil;
+
+ switch (theMode) {
+ case JournalModeTruncate:
+ theModeString = @"TRUNCATE";
+ break;
+ case JournalModePersist:
+ theModeString = @"PERSIST";
+ break;
+ case JournalModeMemory:
+ theModeString = @"MEMORY";
+ break;
+ case JournalModeWAL:
+ theModeString = @"WAL";
+ break;
+ case JournalModeOFF:
+ theModeString = @"OFF";
+ break;
+ default:
+ theModeString = @"DELETE";
+ break;
+ }
+
+ [self executeSQL:[NSString stringWithFormat:@"PRAGMA journal_mode = %@", theModeString]];
+
+ NSFJournalModeMode verificationMode = [self journalModeAndReturnError:nil];
+
+ return (verificationMode == theMode);
+}
+
+#pragma mark// ==================================
+#pragma mark// Binary Data Methods
+#pragma mark// ==================================
+
++ (NSString *)encodeDataToBase64:(NSData*)data
+{
+ if (nil == data)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: data is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSInteger decodedDataSize = [data length];
+ unsigned char *bytes = (unsigned char *)malloc(decodedDataSize);
+
+ // Extract the bytes
+ [data getBytes:bytes];
+
+ unsigned char inBuffer[3];
+ unsigned char outBuffer[4];
+ NSInteger i;
+ NSInteger segments;
+ char *outputBuffer;
+ char *base64Buffer;
+
+ base64Buffer = outputBuffer = (char *)malloc (decodedDataSize * 4 / 3 + 4);
+ if (NULL == outputBuffer) {
+ free (bytes);
+ return nil;
+ }
+
+ while (decodedDataSize > 0) {
+ for (i = segments = 0; i < 3; i++) {
+ if (decodedDataSize > 0) {
+ segments++;
+ inBuffer[i] = *(bytes + i);
+ decodedDataSize--;
+ } else
+ inBuffer[i] = 0;
+ }
+
+ outBuffer [0] = (inBuffer [0] & 0xFC) >> 2;
+ outBuffer [1] = ((inBuffer [0] & 0x03) << 4) | ((inBuffer [1] & 0xF0) >> 4);
+ outBuffer [2] = ((inBuffer [1] & 0x0F) << 2) | ((inBuffer [2] & 0xC0) >> 6);
+ outBuffer [3] = inBuffer [2] & 0x3F;
+
+ switch (segments) {
+ case 1:
+ sprintf(outputBuffer, "%c%c==",
+ __NSFP_base64Table[outBuffer[0]],
+ __NSFP_base64Table[outBuffer[1]]);
+ break;
+ case 2:
+ sprintf(outputBuffer, "%c%c%c=",
+ __NSFP_base64Table[outBuffer[0]],
+ __NSFP_base64Table[outBuffer[1]],
+ __NSFP_base64Table[outBuffer[2]]);
+ break;
+ default:
+ sprintf(outputBuffer, "%c%c%c%c",
+ __NSFP_base64Table[outBuffer[0]],
+ __NSFP_base64Table[outBuffer[1]],
+ __NSFP_base64Table[outBuffer[2]],
+ __NSFP_base64Table[outBuffer[3]] );
+ break;
+ }
+
+ outputBuffer += 4;
+ }
+
+ *outputBuffer = 0;
+
+ NSString *myBase64Data = [NSString stringWithUTF8String:base64Buffer];
+
+ free (base64Buffer);
+ free (bytes);
+
+ return myBase64Data;
+}
+
++ (NSData*)decodeDataFromBase64:(NSString *)encodedData
+{
+ if (nil == encodedData)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: encodedData is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ const char* source = [encodedData UTF8String];
+ NSUInteger sourceLength = strlen(source);
+ char* destination = (char *)malloc(sourceLength * 3/4 + 8);
+ char* destinationPtr = destination;
+
+ NSInteger length = 0;
+ NSInteger pivot = 0;
+ NSInteger i;
+ NSInteger numSegments;
+ unsigned char lastSegment[3];
+ NSUInteger decodedLength = 0;
+
+ while ((source[length] != '=') && source[length])
+ length++;
+ while (source[length+pivot] == '=')
+ pivot++;
+
+ numSegments = (length + pivot) / 4;
+
+ decodedLength = (numSegments * 3) - pivot;
+
+ for (i = 0; i < numSegments - 1; i++) {
+ [self NSFP_decodeQuantum:(unsigned char *)destination andSource:source];
+ destination += 3;
+ source += 4;
+ }
+
+ [self NSFP_decodeQuantum:lastSegment andSource:source];
+
+ for (i = 0; i < 3 - pivot; i++)
+ destination[i] = lastSegment[i];
+
+ // Construct a NSData with the decoded data
+ NSData* myDummyData = [NSData dataWithBytes:destinationPtr length:decodedLength];
+
+ // Cleanup
+ free (destinationPtr);
+
+ return myDummyData;
+}
+
+#pragma mark// ==================================
+#pragma mark// NSFNanoEngine Private Methods
+#pragma mark// ==================================
+
+/** \cond */
+
++ (NSArray *)NSFP_sharedROWIDKeywords
+{
+ if (nil == __NSFPSharedROWIDKeywords)
+ __NSFPSharedROWIDKeywords = [[NSArray alloc]initWithObjects:@"ROWID", @"OID", @"_ROWID_", nil];
+
+ return __NSFPSharedROWIDKeywords;
+}
+
+- (NSString *)NSFP_cacheMethodToString
+{
+ switch (cacheMethod) {
+ case CacheAllData:
+ return @"Cache all data";
+ break;
+ case CacheDataOnDemand:
+ return @"Cache data on demand";
+ break;
+ default:
+ return @"Do not cache data";
+ break;
+ }
+
+ return @"<unknown cache method";
+}
+
++ (int)NSFP_stripBitsFromExtendedResultCode:(int)extendedResult
+{
+ return (extendedResult & 0x00FF);
+}
+
+- (NSString*)NSFP_nestedDescriptionWithPrefixedSpace:(NSString *)prefixedSpace
+{
+ if (nil == prefixedSpace) {
+ prefixedSpace = @"";
+ }
+
+ NSMutableString *description = [NSMutableString string];
+ [description appendString:@"\n"];
+ [description appendString:[NSString stringWithFormat:@"%@SQLite address : 0x%x\n", prefixedSpace, self.sqlite]];
+ [description appendString:[NSString stringWithFormat:@"%@Database path : %@\n", prefixedSpace, path]];
+ [description appendString:[NSString stringWithFormat:@"%@Cache method : %@\n", prefixedSpace, [self NSFP_cacheMethodToString]]];
+
+ return description;
+}
+
++ (NSDictionary *)_plistToDictionary:(NSString *)aPlist
+{
+ if (nil == aPlist)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: aPlist is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if ([aPlist length] == 0)
+ return nil;
+
+ // Some sanity check...
+ if ([NSPropertyListSerialization propertyList:aPlist isValidForFormat:NSPropertyListXMLFormat_v1_0] == NO)
+ return nil;
+
+ NSString *errorString = nil;
+ NSPropertyListFormat *format = nil;
+ NSData *data = [aPlist dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
+ NSDictionary *dict = [NSPropertyListSerialization propertyListFromData:data mutabilityOption:NSPropertyListImmutable format:format errorDescription:&errorString];
+
+ if (nil == dict) {
+ NSLog(@"*** -[%@ %@]: [NSPropertyListSerialization propertyListFromData] failure. %@", [self class], NSStringFromSelector(_cmd), errorString);
+ NSLog(@" Plist data: %@", aPlist);
+ return nil;
+ }
+
+ return dict;
+}
+
++ (void)NSFP_decodeQuantum:(unsigned char*)dest andSource:(const char *)src
+{
+ if (nil == dest)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: dest is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == src)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: src is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSUInteger x = 0;
+ NSInteger i;
+ for (i = 0; i < 4; i++) {
+ if (src[i] >= 'A' && src[i] <= 'Z')
+ x = (x << 6) + (NSUInteger)(src[i] - 'A' + 0);
+ else if (src[i] >= 'a' && src[i] <= 'z')
+ x = (x << 6) + (NSUInteger)(src[i] - 'a' + 26);
+ else if (src[i] >= '0' && src[i] <= '9')
+ x = (x << 6) + (NSUInteger)(src[i] - '0' + 52);
+ else if (src[i] == '+')
+ x = (x << 6) + 62;
+ else if (src[i] == '/')
+ x = (x << 6) + 63;
+ else if (src[i] == '=')
+ x = (x << 6);
+ }
+
+ dest[2] = (unsigned char)(x & 255);
+ x >>= 8;
+ dest[1] = (unsigned char)(x & 255);
+ x >>= 8;
+ dest[0] = (unsigned char)(x & 255);
+}
+
+- (NSFNanoDatatype)NSFP_datatypeForColumn:(NSString *)tableAndColumn
+{
+ if (nil == tableAndColumn)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tableAndColumn is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSString *table = [self NSFP_prefixWithDotDelimiter:tableAndColumn];
+ NSString *column = [self NSFP_suffixWithDotDelimiter:tableAndColumn];
+
+ return [self NSFP_datatypeForTable:(NSString *)table column:(NSString *)column];
+}
+
+- (NSFNanoDatatype)NSFP_datatypeForTable:(NSString *)table column:(NSString *)column
+{
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == column)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: column is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSString *datatype = nil;
+
+ // Check to see if the schema has been cached; take advantage of it if possible...
+ if (nil != schema) {
+ datatype = [[schema objectForKey:table]objectForKey:column];
+ if (nil == datatype) datatype = NSFStringFromNanoDataType(NSFNanoTypeUnknown);
+ } else {
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT %@ from %@ WHERE %@ = '%@' AND %@ = '%@';", NSFP_DatatypeIdentifier, NSFP_SchemaTable, NSFP_TableIdentifier, table, NSFP_ColumnIdentifier, column];
+
+ NSFNanoResult* result = [self executeSQL:theSQLStatement];
+
+ datatype = [[result valuesForColumn:NSFP_FullDatatypeIdentifier]lastObject];
+
+ if (nil == datatype) datatype = NSFStringFromNanoDataType(NSFNanoTypeUnknown);
+
+ NSMutableDictionary *tempSchema = [schema objectForKey:table];
+ if (nil != tempSchema)
+ tempSchema = [[NSMutableDictionary alloc]init];
+ else
+ [tempSchema retain];
+
+ [tempSchema setObject:datatype forKey:column];
+ [schema setObject:tempSchema forKey:table];
+
+ [tempSchema release];
+ tempSchema = nil;
+ }
+
+ return NSFNanoDatatypeFromString(datatype);
+}
+
+- (void)NSFP_setFullColumnNamesEnabled
+{
+ [self executeSQL:@"PRAGMA short_column_names = OFF;"];
+ [self executeSQL:@"PRAGMA full_column_names = ON;"];
+}
+
+- (NSArray *)NSFP_flattenAllTables
+{
+ NSMutableSet *flattenedTables = [[NSMutableSet alloc]init];
+ NSDictionary *allTables = [self allTables];
+ NSEnumerator *enumerator = [allTables keyEnumerator];
+ NSString *database;
+ BOOL addPrefix = ([allTables count] > 1);
+
+ while ((database = [enumerator nextObject])) {
+ NSArray *databaseTables = [allTables objectForKey:database];
+
+ if ((YES == addPrefix) && ([database hasPrefix:@"main"] == NO)) {
+ for (NSString *table in databaseTables) {
+ [flattenedTables addObject:[NSString stringWithFormat:@"%@.%@", database, table]];
+ }
+ } else {
+ [flattenedTables addObjectsFromArray:databaseTables];
+ }
+ }
+
+ NSArray *immutableValues = [flattenedTables allObjects];
+
+ [flattenedTables release];
+ flattenedTables = nil;
+
+ return immutableValues;
+}
+
+- (NSInteger)NSFP_prepareSQLite3Statement:(sqlite3_stmt **)aStatement theSQLStatement:(NSString *)aSQLQuery
+{
+ if (nil == aSQLQuery)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: aSQLQuery is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ // Prepare SQLite's VM. It's placed here so we can speed up stores...
+ int status = SQLITE_OK;
+ BOOL continueLooping = YES;
+ const char *query = [aSQLQuery UTF8String];
+
+ do {
+ status = sqlite3_prepare_v2(self.sqlite, query, -1, aStatement, NULL);
+
+ // Since we're operating with extended result code support, extract the bits
+ // and obtain the regular result code
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
+
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
+
+ continueLooping = ((SQLITE_LOCKED == status) || (SQLITE_BUSY == status));
+ } while (continueLooping);
+
+ return status;
+}
+
+- (BOOL)NSFP_beginTransactionMode:(NSString *)theSQLStatement
+{
+ if (nil == theSQLStatement)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if ([self isTransactionActive] == NO) {
+ sqlite3_stmt *NSF_sqliteVM;
+ const char *query_tail = [theSQLStatement UTF8String];
+
+ int status = sqlite3_prepare_v2(self.sqlite, query_tail, -1, &NSF_sqliteVM, &query_tail);
+
+ // Since we're operating with extended result code support, extract the bits
+ // and obtain the regular result code
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
+
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
+
+ if (SQLITE_OK == status) {
+ BOOL continueTrying = YES;
+
+ do {
+ status = sqlite3_step(NSF_sqliteVM);
+
+ // Since we're operating with extended result code support, extract the bits
+ // and obtain the regular result code
+ // For more info check: http://www.sqlite.org/c3ref/c_ioerr_access.html
+
+ status = [NSFNanoEngine NSFP_stripBitsFromExtendedResultCode:status];
+
+ switch (status) {
+ case SQLITE_OK:
+ case SQLITE_DONE:
+ continueTrying = NO;
+ break;
+ case SQLITE_BUSY:
+ [self rollbackTransaction];
+ sqlite3_reset(NSF_sqliteVM);
+ continueTrying = YES;
+ break;
+ default:
+ [self rollbackTransaction];
+ continueTrying = NO;
+ break;
+ }
+ } while (continueTrying);
+
+ return (SQLITE_OK == sqlite3_finalize(NSF_sqliteVM));
+ }
+ }
+
+ return NO;
+}
+
+- (BOOL)NSFP_createTable:(NSString *)table withColumns:(NSArray *)tableColumns datatypes:(NSArray *)tableDatatypes isTemporary:(BOOL)isTemporaryFlag
+{
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == tableColumns)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tableColumns is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == tableDatatypes)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tableDatatypes is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if ([tableColumns count] != [tableDatatypes count])
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: number of columns and datatypes mismatch.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSSet *allowedDatatypes = [NSFNanoEngine sharedNanoStoreEngineDatatypes];
+ NSSet *specifiedDatatypes = [NSSet setWithArray:tableDatatypes];
+
+ if (NO == [specifiedDatatypes isSubsetOfSet:allowedDatatypes])
+ return NO;
+
+ // Make sure we have specified ROWID in the group of columns
+ NSMutableArray *revisedColumns = [[NSMutableArray alloc]initWithArray:tableColumns];
+ NSMutableArray *revisedDatatypes = [[NSMutableArray alloc]initWithArray:tableDatatypes];
+ NSInteger ROWIDIndex = [self NSFP_ROWIDPresenceLocation:tableColumns datatypes:tableDatatypes];
+ NSString *ROWIDDatatype = [[NSString alloc]initWithFormat:@"%@ PRIMARY KEY", NSFStringFromNanoDataType(NSFNanoTypeRowUID)];
+
+ if (NSNotFound != ROWIDIndex) {
+ // Even though the ROWID has been specified by the user, we make sure the datatype is correct
+ [revisedDatatypes replaceObjectAtIndex:ROWIDIndex withObject:ROWIDDatatype];
+ } else {
+ // ROWID not found:add it manually
+ [revisedColumns insertObject:NSFRowIDColumnName atIndex:0];
+ [revisedDatatypes insertObject:ROWIDDatatype atIndex:0];
+ }
+
+ [ROWIDDatatype release];
+
+ BOOL transactionSetHere = NO;
+ if (NO == [self isTransactionActive])
+ transactionSetHere = [self beginTransaction];
+
+ BOOL everythingIsFine = YES;
+
+ NSMutableString* theSQLStatement;
+ if (YES == isTemporaryFlag)
+ theSQLStatement = [[NSMutableString alloc]initWithString:[NSString stringWithFormat:@"CREATE TEMPORARY TABLE %@(", table]];
+ else
+ theSQLStatement = [[NSMutableString alloc]initWithString:[NSString stringWithFormat:@"CREATE TABLE %@(", table]];
+
+ NSMutableArray *tableCreationDatatypes = [NSMutableArray arrayWithArray:revisedDatatypes];
+
+ if (YES == [self NSFP_sqlString:theSQLStatement forTable:table withColumns:revisedColumns datatypes:tableCreationDatatypes]) {
+ [theSQLStatement appendString:@");"];
+
+ everythingIsFine = (nil == [[self executeSQL:theSQLStatement]error]);
+
+ if (everythingIsFine) {
+ // Now add the entries to NSFP_SchemaTable
+ NSMutableArray *tableAndColumns = [[NSMutableArray alloc]init];
+ NSInteger i, count = [revisedDatatypes count];
+
+ for (i = 0; i < count; i++) {
+ if (NO == [self NSFP_insertStringValues:[NSArray arrayWithObjects:table, [revisedColumns objectAtIndex:i], [revisedDatatypes objectAtIndex:i], nil] forColumns:[NSArray arrayWithObjects:NSFP_TableIdentifier, NSFP_ColumnIdentifier, NSFP_DatatypeIdentifier, nil]table:NSFP_SchemaTable]) {
+ everythingIsFine = NO;
+ break;
+ }
+ }
+
+ // Cleanup
+ [tableAndColumns release];
+ }
+ } else {
+ everythingIsFine = NO;
+ }
+
+ if (transactionSetHere) {
+ if (everythingIsFine)
+ [self commitTransaction];
+ else
+ [self rollbackTransaction];
+ }
+
+ if (everythingIsFine)
+ [self NSFP_rebuildDatatypeCache];
+
+ // Cleanup
+ [revisedColumns release];
+ [revisedDatatypes release];
+ [theSQLStatement release];
+
+ return everythingIsFine;
+}
+
+- (BOOL)NSFP_removeColumn:(NSString *)column fromTable:(NSString *)table
+{
+ // Obtain all current columns and datatypes for table
+ NSArray *tableInfoDatatypes = [self datatypesForTable:table];
+
+ if (nil == column)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: column is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSArray *tableInfoColumns = [self columnsForTable:table];
+
+ NSInteger index = [tableInfoColumns indexOfObject:column];
+
+ if (index == NSNotFound)
+ return NO;
+
+ // Add the new column and data type to the list
+ NSMutableArray *tableColumns = [[NSMutableArray alloc]initWithArray:tableInfoColumns];
+ NSMutableArray *tableDatatypes = [[NSMutableArray alloc]initWithArray:tableInfoDatatypes];
+ [tableColumns removeObjectAtIndex:index];
+ [tableDatatypes removeObjectAtIndex:index];
+
+ BOOL transactionSetHere = NO;
+ if (NO == [self isTransactionActive])
+ transactionSetHere = [self beginTransaction];
+
+ // Create a backup table with the columns and datatypes
+ NSUInteger numberOfIssues = 0;
+
+ BOOL isTableTemporary = [[self temporaryTables]containsObject:table];
+ if ([self NSFP_createTable:[NSString stringWithFormat:@"%@_backup", table] withColumns:tableColumns datatypes:tableDatatypes isTemporary:isTableTemporary]) {
+ // Insert all existing data
+ NSMutableString* query = [NSMutableString stringWithString:[NSString stringWithFormat:@"INSERT INTO %@_backup(", table]];
+
+ [self NSFP_sqlString:query appendingTags:tableColumns];
+ [query appendString:@") SELECT "];
+ [self NSFP_sqlString:query appendingTags:tableColumns];
+
+ if (nil == [[self executeSQL:[NSString stringWithFormat:@"%@ FROM %@;", query, table]]error])
+ numberOfIssues++;
+
+ // Delete the old table
+ if ([self dropTable:table] == NO)
+ numberOfIssues++;
+
+ // Create the new table with the columns and datatypes
+ isTableTemporary = [[self temporaryTables]containsObject:table];
+ if ([self NSFP_createTable:table withColumns:tableColumns datatypes:tableDatatypes isTemporary:isTableTemporary] == NO)
+ numberOfIssues++;
+
+ // Copy the data from the backup table
+ query = [NSMutableString stringWithString:[NSString stringWithFormat:@"INSERT INTO %@(", table]];
+
+ [self NSFP_sqlString:query appendingTags:tableColumns];
+ [query appendString:@") SELECT "];
+ [self NSFP_sqlString:query appendingTags:tableColumns];
+
+ if (nil == [[self executeSQL:[NSString stringWithFormat:@"%@ FROM %@_backup;", query, table]]error])
+ numberOfIssues++;
+
+ // Delete the backup table
+ if ([self dropTable:[NSString stringWithFormat:@"%@_backup", table]] == NO)
+ numberOfIssues++;
+ } else {
+ numberOfIssues++;
+ }
+
+ // Cleanup
+ [tableColumns release];
+ [tableDatatypes release];
+
+ if (transactionSetHere) {
+ if (0 == numberOfIssues) {
+ [self commitTransaction];
+ } else {
+ [self rollbackTransaction];
+ }
+ }
+
+ if (0 == numberOfIssues)
+ [self NSFP_rebuildDatatypeCache];
+
+ return (0 == numberOfIssues);
+}
+
+- (void)NSFP_rebuildDatatypeCache
+{
+ // Cleanup
+ [schema release];
+ schema = nil;
+ schema = [[NSMutableDictionary alloc]init];
+
+ NSArray *tables = [self NSFP_flattenAllTables];
+ if ([tables count] == 0)
+ return;
+
+ for (NSString *table in tables) {
+ NSArray *columns = [self columnsForTable:table];
+ NSArray *datatypes = [self datatypesForTable:table];
+ if ((nil == table) || (nil != columns) || (nil != datatypes)) {
+ break;
+ }
+
+ // Build the dictionary
+ NSMutableDictionary *tableDictionary = [[NSMutableDictionary alloc]init];
+ NSInteger j, columnCount = [columns count];
+
+ for (j = 0; j < columnCount; j++) {
+ [tableDictionary setObject:[datatypes objectAtIndex:j] forKey:[columns objectAtIndex:j]];
+ }
+
+ [schema setObject:tableDictionary forKey:table];
+
+ // Cleanup...
+ [tableDictionary release];
+ }
+}
+
+- (BOOL)NSFP_insertStringValues:(NSArray *)values forColumns:(NSArray *)columns table:(NSString *)table
+{
+ if (nil == values)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: values is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == columns)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: columns is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ // Make sure we have specified ROWID in the group of columns
+ NSMutableArray *revisedColumns = (NSMutableArray*)columns;
+
+ // Escape all values except the one with type NSFNanoTypeRowUID
+ NSMutableArray *escapedValues = [[NSMutableArray alloc]init];
+ NSInteger i, count = [revisedColumns count];
+
+ for (i = 0; i < count; i++) {
+ NSString *column = [revisedColumns objectAtIndex:i];
+ NSString *value = [values objectAtIndex:i];
+ NSString *escapedValue = nil;
+ if (NO == [self NSFP_isColumnROWIDAlias:column forTable:table])
+ escapedValue = [[NSString alloc]initWithFormat:@"'%@'", value];
+ else
+ escapedValue = [[NSString alloc]initWithFormat:@"%@", value];
+ [escapedValues addObject:escapedValue];
+ // Cleanup
+ [escapedValue release];
+ }
+
+ NSMutableString* theSQLStatement = [[NSMutableString alloc]initWithString:[NSString stringWithFormat:@"INSERT INTO %@(", table]];
+
+ [self NSFP_sqlString:theSQLStatement appendingTags:revisedColumns];
+ [theSQLStatement appendString:@") VALUES("];
+ [self NSFP_sqlString:theSQLStatement appendingTags:escapedValues];
+ [theSQLStatement appendString:@");"];
+ BOOL insertWasOK = (nil == [[self executeSQL:theSQLStatement]error]);
+
+ // Cleanup
+ [escapedValues release];
+ [theSQLStatement release];
+
+ return insertWasOK;
+}
+
+- (void)NSFP_sqlString:(NSMutableString*)theSQLStatement appendingTags:(NSArray *)tags quoteTags:(BOOL)flag
+{
+ if (nil == theSQLStatement)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == tags)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tags is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSInteger i, count = [tags count];
+
+ if (flag) {
+ for (i = 0; i < count; i++) {
+ NSString *tagName = [tags objectAtIndex:i];
+ NSString *escapedValue = [[NSString alloc]initWithFormat:@"'%@'", tagName];
+
+ [theSQLStatement appendString:escapedValue];
+
+ // Cleanup
+ [escapedValue release];
+
+ if (i != count - 1)
+ [theSQLStatement appendString:@","];
+ }
+ } else {
+ [theSQLStatement appendString:[tags componentsJoinedByString:@","]];
+ }
+}
+
+- (void)NSFP_sqlString:(NSMutableString*)theSQLStatement appendingTags:(NSArray *)tags
+{
+ if (nil == theSQLStatement)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == tags)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tags is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ [self NSFP_sqlString:theSQLStatement appendingTags:tags quoteTags:NO];
+}
+
+- (BOOL)NSFP_sqlString:(NSMutableString*)theSQLStatement forTable:(NSString *)table withColumns:(NSArray *)columns datatypes:(NSArray *)datatypes
+{
+ if (nil == theSQLStatement)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: theSQLStatement is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == columns)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: columns is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == datatypes)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: datatypes is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ BOOL constructionSucceeded = YES;
+ NSInteger i, count = [columns count];
+
+ for (i = 0; i < count; i++) {
+ NSString *column = [columns objectAtIndex:i];
+ NSString *datatype = [datatypes objectAtIndex:i];
+
+ if (nil != datatype) {
+ // Some datatypes may be empty strings.
+ // See NSFNanoEngine's header file for more info on datatypesForTable:.
+ NSString *columnAndDatatype = nil;
+
+ if ([datatype isEqualToString:@""])
+ columnAndDatatype = [[NSString alloc]initWithFormat:@"%@", column];
+ else
+ columnAndDatatype = [[NSString alloc]initWithFormat:@"%@ %@", column, datatype];
+
+ [theSQLStatement appendString:columnAndDatatype];
+
+ // Cleanup
+ [columnAndDatatype release];
+
+ if (i != count - 1)
+ [theSQLStatement appendString:@","];
+ } else {
+ constructionSucceeded = NO;
+ }
+ }
+
+ return constructionSucceeded;
+}
+
+- (NSInteger)NSFP_ROWIDPresenceLocation:(NSArray *)tableColumns datatypes:(NSArray *)datatypes
+{
+ if (nil == tableColumns)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: tableColumns is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == datatypes)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: datatypes is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ // First check if we have a datatype of type NSFNanoTypeRowUID
+ NSInteger ROWIDIndex = NSNotFound;
+
+ if (nil != datatypes) {
+ NSInteger i, count = [datatypes count];
+ NSString *rowUIDDatatype = NSFStringFromNanoDataType(NSFNanoTypeRowUID);
+
+ for (i = 0; i < count; i++) {
+ if ([[[datatypes objectAtIndex:i] uppercaseString]isEqualToString:rowUIDDatatype]) {
+ ROWIDIndex = i;
+ break;
+ }
+ }
+ }
+
+ if (NSNotFound == ROWIDIndex) {
+ // Make sure we have specified ROWID in the group of columns
+ NSArray *reservedKeywords = [NSFNanoEngine NSFP_sharedROWIDKeywords];
+
+ for (NSString *tableColumn in tableColumns) {
+ NSInteger index = [reservedKeywords indexOfObject:tableColumn];
+
+ if (NSNotFound != index) {
+ ROWIDIndex = index;
+ break;
+ }
+ }
+ }
+
+ return ROWIDIndex;
+}
+
+- (BOOL)NSFP_isColumnROWIDAlias:(NSString *)column forTable:(NSString *)table
+{
+ if (nil == column)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: column is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ if (nil == table)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException
+ reason:[NSString stringWithFormat:@"*** -[%@ %s]: table is nil.", [self class], _cmd]
+ userInfo:nil]raise];
+
+ NSString *rowUIDDatatype = NSFStringFromNanoDataType(NSFNanoTypeRowUID);
+
+ if (nil != schema)
+ return [[[schema objectForKey:table]objectForKey:column]isEqualToString:rowUIDDatatype];
+
+ NSString *theSQLStatement = [NSString stringWithFormat:@"SELECT %@ FROM %@ WHERE %@ = '%@' AND %@ = '%@';", NSFP_DatatypeIdentifier, NSFP_SchemaTable, NSFP_TableIdentifier, table, NSFP_ColumnIdentifier, column];
+ NSFNanoResult* result = [self executeSQL:theSQLStatement];
+
+ NSString *columnFound = [[result valuesForColumn:NSFP_FullDatatypeIdentifier]lastObject];
+ BOOL isROWIDAlias = [columnFound isEqualToString:rowUIDDatatype];
+
+ return isROWIDAlias;
+}
+
+- (NSString *)NSFP_prefixWithDotDelimiter:(NSString *)tableAndColumn
+{
+ if (nil == tableAndColumn)
+ [[NSException exceptionWithName:NSFUnexpectedParameterException