├── ARDBConfigDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── jun.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── WorkspaceSettings.xcsettings └── xcuserdata │ └── jun.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ ├── ARDBConfigDemo.xcscheme │ └── xcschememanagement.plist ├── ARDBConfigDemo ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Info.plist ├── Logic │ ├── DBConfigLogic.h │ └── DBConfigLogic.m ├── Resources │ └── Images.xcassets │ │ └── AppIcon.appiconset │ │ └── Contents.json ├── ViewController │ ├── ViewController.h │ └── ViewController.m └── main.m ├── ARDBConfigDemoTests ├── ARDBConfigDemoTests.m └── Info.plist ├── README.md ├── Screenshop01.png └── ThridParty ├── ARDBConfig ├── ARDBConfigBase.h └── ARDBConfigBase.m └── FMDB ├── CHANGES_AND_TODO_LIST.txt ├── FMDB.h ├── FMDatabase.h ├── FMDatabase.m ├── FMDatabaseAdditions.h ├── FMDatabaseAdditions.m ├── FMDatabasePool.h ├── FMDatabasePool.m ├── FMDatabaseQueue.h ├── FMDatabaseQueue.m ├── FMResultSet.h ├── FMResultSet.m ├── README.markdown ├── Tests ├── FMDBTempDBTests.h ├── FMDBTempDBTests.m ├── FMDatabaseAdditionsTests.m ├── FMDatabasePoolTests.m ├── FMDatabaseQueueTests.m ├── FMDatabaseTests.m ├── Schemes │ └── Tests.xcscheme ├── Tests-Info.plist ├── Tests-Prefix.pch └── en.lproj │ └── InfoPlist.strings ├── extra ├── FMDatabase+InMemoryOnDiskIO.h └── FMDatabase+InMemoryOnDiskIO.m └── main (sample).m /ARDBConfigDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | BF6D34C31ACAA3BB0088FAA2 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D34C21ACAA3BB0088FAA2 /* main.m */; }; 11 | BF6D34C61ACAA3BB0088FAA2 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D34C51ACAA3BB0088FAA2 /* AppDelegate.m */; }; 12 | BF6D34DD1ACAA3BB0088FAA2 /* ARDBConfigDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D34DC1ACAA3BB0088FAA2 /* ARDBConfigDemoTests.m */; }; 13 | BF6D34EA1ACAA56C0088FAA2 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D34E91ACAA56C0088FAA2 /* ViewController.m */; }; 14 | BF6D34ED1ACAA5750088FAA2 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF6D34EC1ACAA5750088FAA2 /* Images.xcassets */; }; 15 | BF6D350F1ACAA5B10088FAA2 /* FMDatabase+InMemoryOnDiskIO.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D34F31ACAA5B10088FAA2 /* FMDatabase+InMemoryOnDiskIO.m */; }; 16 | BF6D35101ACAA5B10088FAA2 /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D34F51ACAA5B10088FAA2 /* FMDatabase.m */; }; 17 | BF6D35111ACAA5B10088FAA2 /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D34F71ACAA5B10088FAA2 /* FMDatabaseAdditions.m */; }; 18 | BF6D35121ACAA5B10088FAA2 /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D34F91ACAA5B10088FAA2 /* FMDatabasePool.m */; }; 19 | BF6D35131ACAA5B10088FAA2 /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D34FB1ACAA5B10088FAA2 /* FMDatabaseQueue.m */; }; 20 | BF6D35141ACAA5B10088FAA2 /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D34FE1ACAA5B10088FAA2 /* FMResultSet.m */; }; 21 | BF6D35161ACAA5B10088FAA2 /* README.markdown in Sources */ = {isa = PBXBuildFile; fileRef = BF6D35001ACAA5B10088FAA2 /* README.markdown */; }; 22 | BF6D35241ACAA68F0088FAA2 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = BF6D35231ACAA68F0088FAA2 /* libsqlite3.dylib */; }; 23 | BF6D35281ACAA6D10088FAA2 /* DBConfigLogic.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D35271ACAA6D10088FAA2 /* DBConfigLogic.m */; }; 24 | BF6D352C1ACB9F0D0088FAA2 /* ARDBConfigBase.m in Sources */ = {isa = PBXBuildFile; fileRef = BF6D352B1ACB9F0D0088FAA2 /* ARDBConfigBase.m */; }; 25 | BF6D35311ACBA8500088FAA2 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = BF6D352D1ACBA8500088FAA2 /* LaunchScreen.xib */; }; 26 | BF6D35321ACBA8500088FAA2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BF6D352F1ACBA8500088FAA2 /* Main.storyboard */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | BF6D34D71ACAA3BB0088FAA2 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = BF6D34B51ACAA3BB0088FAA2 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = BF6D34BC1ACAA3BB0088FAA2; 35 | remoteInfo = ARDBConfigDemo; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | BF6D34BD1ACAA3BB0088FAA2 /* ARDBConfigDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ARDBConfigDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | BF6D34C11ACAA3BB0088FAA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | BF6D34C21ACAA3BB0088FAA2 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 43 | BF6D34C41ACAA3BB0088FAA2 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 44 | BF6D34C51ACAA3BB0088FAA2 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 45 | BF6D34D61ACAA3BB0088FAA2 /* ARDBConfigDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ARDBConfigDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | BF6D34DB1ACAA3BB0088FAA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | BF6D34DC1ACAA3BB0088FAA2 /* ARDBConfigDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARDBConfigDemoTests.m; sourceTree = ""; }; 48 | BF6D34E81ACAA56C0088FAA2 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 49 | BF6D34E91ACAA56C0088FAA2 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 50 | BF6D34EC1ACAA5750088FAA2 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 51 | BF6D34F21ACAA5B10088FAA2 /* FMDatabase+InMemoryOnDiskIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FMDatabase+InMemoryOnDiskIO.h"; sourceTree = ""; }; 52 | BF6D34F31ACAA5B10088FAA2 /* FMDatabase+InMemoryOnDiskIO.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "FMDatabase+InMemoryOnDiskIO.m"; sourceTree = ""; }; 53 | BF6D34F41ACAA5B10088FAA2 /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = ""; }; 54 | BF6D34F51ACAA5B10088FAA2 /* FMDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabase.m; sourceTree = ""; }; 55 | BF6D34F61ACAA5B10088FAA2 /* FMDatabaseAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabaseAdditions.h; sourceTree = ""; }; 56 | BF6D34F71ACAA5B10088FAA2 /* FMDatabaseAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseAdditions.m; sourceTree = ""; }; 57 | BF6D34F81ACAA5B10088FAA2 /* FMDatabasePool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabasePool.h; sourceTree = ""; }; 58 | BF6D34F91ACAA5B10088FAA2 /* FMDatabasePool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabasePool.m; sourceTree = ""; }; 59 | BF6D34FA1ACAA5B10088FAA2 /* FMDatabaseQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabaseQueue.h; sourceTree = ""; }; 60 | BF6D34FB1ACAA5B10088FAA2 /* FMDatabaseQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseQueue.m; sourceTree = ""; }; 61 | BF6D34FC1ACAA5B10088FAA2 /* FMDB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDB.h; sourceTree = ""; }; 62 | BF6D34FD1ACAA5B10088FAA2 /* FMResultSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMResultSet.h; sourceTree = ""; }; 63 | BF6D34FE1ACAA5B10088FAA2 /* FMResultSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMResultSet.m; sourceTree = ""; }; 64 | BF6D35001ACAA5B10088FAA2 /* README.markdown */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.markdown; sourceTree = ""; }; 65 | BF6D35231ACAA68F0088FAA2 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; 66 | BF6D35261ACAA6D10088FAA2 /* DBConfigLogic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DBConfigLogic.h; path = Logic/DBConfigLogic.h; sourceTree = ""; }; 67 | BF6D35271ACAA6D10088FAA2 /* DBConfigLogic.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DBConfigLogic.m; path = Logic/DBConfigLogic.m; sourceTree = ""; }; 68 | BF6D352A1ACB9F0D0088FAA2 /* ARDBConfigBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARDBConfigBase.h; sourceTree = ""; }; 69 | BF6D352B1ACB9F0D0088FAA2 /* ARDBConfigBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARDBConfigBase.m; sourceTree = ""; }; 70 | BF6D352E1ACBA8500088FAA2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = ARDBConfigDemo/Base.lproj/LaunchScreen.xib; sourceTree = SOURCE_ROOT; }; 71 | BF6D35301ACBA8500088FAA2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = ARDBConfigDemo/Base.lproj/Main.storyboard; sourceTree = SOURCE_ROOT; }; 72 | /* End PBXFileReference section */ 73 | 74 | /* Begin PBXFrameworksBuildPhase section */ 75 | BF6D34BA1ACAA3BB0088FAA2 /* Frameworks */ = { 76 | isa = PBXFrameworksBuildPhase; 77 | buildActionMask = 2147483647; 78 | files = ( 79 | BF6D35241ACAA68F0088FAA2 /* libsqlite3.dylib in Frameworks */, 80 | ); 81 | runOnlyForDeploymentPostprocessing = 0; 82 | }; 83 | BF6D34D31ACAA3BB0088FAA2 /* Frameworks */ = { 84 | isa = PBXFrameworksBuildPhase; 85 | buildActionMask = 2147483647; 86 | files = ( 87 | ); 88 | runOnlyForDeploymentPostprocessing = 0; 89 | }; 90 | /* End PBXFrameworksBuildPhase section */ 91 | 92 | /* Begin PBXGroup section */ 93 | BF6D34B41ACAA3BB0088FAA2 = { 94 | isa = PBXGroup; 95 | children = ( 96 | BF6D35231ACAA68F0088FAA2 /* libsqlite3.dylib */, 97 | BF6D34BF1ACAA3BB0088FAA2 /* ARDBConfigDemo */, 98 | BF6D34D91ACAA3BB0088FAA2 /* ARDBConfigDemoTests */, 99 | BF6D34EE1ACAA5B10088FAA2 /* ThridParty */, 100 | BF6D34BE1ACAA3BB0088FAA2 /* Products */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | BF6D34BE1ACAA3BB0088FAA2 /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | BF6D34BD1ACAA3BB0088FAA2 /* ARDBConfigDemo.app */, 108 | BF6D34D61ACAA3BB0088FAA2 /* ARDBConfigDemoTests.xctest */, 109 | ); 110 | name = Products; 111 | sourceTree = ""; 112 | }; 113 | BF6D34BF1ACAA3BB0088FAA2 /* ARDBConfigDemo */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | BF6D34C41ACAA3BB0088FAA2 /* AppDelegate.h */, 117 | BF6D34C51ACAA3BB0088FAA2 /* AppDelegate.m */, 118 | BF6D34E71ACAA56C0088FAA2 /* ViewController */, 119 | BF6D35251ACAA6A80088FAA2 /* Logic */, 120 | BF6D34EB1ACAA5750088FAA2 /* Resources */, 121 | BF6D34C01ACAA3BB0088FAA2 /* Supporting Files */, 122 | ); 123 | path = ARDBConfigDemo; 124 | sourceTree = ""; 125 | }; 126 | BF6D34C01ACAA3BB0088FAA2 /* Supporting Files */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | BF6D34C11ACAA3BB0088FAA2 /* Info.plist */, 130 | BF6D34C21ACAA3BB0088FAA2 /* main.m */, 131 | ); 132 | name = "Supporting Files"; 133 | sourceTree = ""; 134 | }; 135 | BF6D34D91ACAA3BB0088FAA2 /* ARDBConfigDemoTests */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | BF6D34DC1ACAA3BB0088FAA2 /* ARDBConfigDemoTests.m */, 139 | BF6D34DA1ACAA3BB0088FAA2 /* Supporting Files */, 140 | ); 141 | path = ARDBConfigDemoTests; 142 | sourceTree = ""; 143 | }; 144 | BF6D34DA1ACAA3BB0088FAA2 /* Supporting Files */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | BF6D34DB1ACAA3BB0088FAA2 /* Info.plist */, 148 | ); 149 | name = "Supporting Files"; 150 | sourceTree = ""; 151 | }; 152 | BF6D34E71ACAA56C0088FAA2 /* ViewController */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | BF6D34E81ACAA56C0088FAA2 /* ViewController.h */, 156 | BF6D34E91ACAA56C0088FAA2 /* ViewController.m */, 157 | ); 158 | path = ViewController; 159 | sourceTree = ""; 160 | }; 161 | BF6D34EB1ACAA5750088FAA2 /* Resources */ = { 162 | isa = PBXGroup; 163 | children = ( 164 | BF6D352D1ACBA8500088FAA2 /* LaunchScreen.xib */, 165 | BF6D352F1ACBA8500088FAA2 /* Main.storyboard */, 166 | BF6D34EC1ACAA5750088FAA2 /* Images.xcassets */, 167 | ); 168 | path = Resources; 169 | sourceTree = ""; 170 | }; 171 | BF6D34EE1ACAA5B10088FAA2 /* ThridParty */ = { 172 | isa = PBXGroup; 173 | children = ( 174 | BF6D35291ACB9F0D0088FAA2 /* ARDBConfig */, 175 | BF6D34EF1ACAA5B10088FAA2 /* FMDB */, 176 | ); 177 | path = ThridParty; 178 | sourceTree = ""; 179 | }; 180 | BF6D34EF1ACAA5B10088FAA2 /* FMDB */ = { 181 | isa = PBXGroup; 182 | children = ( 183 | BF6D34F11ACAA5B10088FAA2 /* extra */, 184 | BF6D34F41ACAA5B10088FAA2 /* FMDatabase.h */, 185 | BF6D34F51ACAA5B10088FAA2 /* FMDatabase.m */, 186 | BF6D34F61ACAA5B10088FAA2 /* FMDatabaseAdditions.h */, 187 | BF6D34F71ACAA5B10088FAA2 /* FMDatabaseAdditions.m */, 188 | BF6D34F81ACAA5B10088FAA2 /* FMDatabasePool.h */, 189 | BF6D34F91ACAA5B10088FAA2 /* FMDatabasePool.m */, 190 | BF6D34FA1ACAA5B10088FAA2 /* FMDatabaseQueue.h */, 191 | BF6D34FB1ACAA5B10088FAA2 /* FMDatabaseQueue.m */, 192 | BF6D34FC1ACAA5B10088FAA2 /* FMDB.h */, 193 | BF6D34FD1ACAA5B10088FAA2 /* FMResultSet.h */, 194 | BF6D34FE1ACAA5B10088FAA2 /* FMResultSet.m */, 195 | BF6D35001ACAA5B10088FAA2 /* README.markdown */, 196 | ); 197 | path = FMDB; 198 | sourceTree = ""; 199 | }; 200 | BF6D34F11ACAA5B10088FAA2 /* extra */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | BF6D34F21ACAA5B10088FAA2 /* FMDatabase+InMemoryOnDiskIO.h */, 204 | BF6D34F31ACAA5B10088FAA2 /* FMDatabase+InMemoryOnDiskIO.m */, 205 | ); 206 | path = extra; 207 | sourceTree = ""; 208 | }; 209 | BF6D35251ACAA6A80088FAA2 /* Logic */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | BF6D35261ACAA6D10088FAA2 /* DBConfigLogic.h */, 213 | BF6D35271ACAA6D10088FAA2 /* DBConfigLogic.m */, 214 | ); 215 | name = Logic; 216 | sourceTree = ""; 217 | }; 218 | BF6D35291ACB9F0D0088FAA2 /* ARDBConfig */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | BF6D352A1ACB9F0D0088FAA2 /* ARDBConfigBase.h */, 222 | BF6D352B1ACB9F0D0088FAA2 /* ARDBConfigBase.m */, 223 | ); 224 | path = ARDBConfig; 225 | sourceTree = ""; 226 | }; 227 | /* End PBXGroup section */ 228 | 229 | /* Begin PBXNativeTarget section */ 230 | BF6D34BC1ACAA3BB0088FAA2 /* ARDBConfigDemo */ = { 231 | isa = PBXNativeTarget; 232 | buildConfigurationList = BF6D34E01ACAA3BB0088FAA2 /* Build configuration list for PBXNativeTarget "ARDBConfigDemo" */; 233 | buildPhases = ( 234 | BF6D34B91ACAA3BB0088FAA2 /* Sources */, 235 | BF6D34BA1ACAA3BB0088FAA2 /* Frameworks */, 236 | BF6D34BB1ACAA3BB0088FAA2 /* Resources */, 237 | ); 238 | buildRules = ( 239 | ); 240 | dependencies = ( 241 | ); 242 | name = ARDBConfigDemo; 243 | productName = ARDBConfigDemo; 244 | productReference = BF6D34BD1ACAA3BB0088FAA2 /* ARDBConfigDemo.app */; 245 | productType = "com.apple.product-type.application"; 246 | }; 247 | BF6D34D51ACAA3BB0088FAA2 /* ARDBConfigDemoTests */ = { 248 | isa = PBXNativeTarget; 249 | buildConfigurationList = BF6D34E31ACAA3BB0088FAA2 /* Build configuration list for PBXNativeTarget "ARDBConfigDemoTests" */; 250 | buildPhases = ( 251 | BF6D34D21ACAA3BB0088FAA2 /* Sources */, 252 | BF6D34D31ACAA3BB0088FAA2 /* Frameworks */, 253 | BF6D34D41ACAA3BB0088FAA2 /* Resources */, 254 | ); 255 | buildRules = ( 256 | ); 257 | dependencies = ( 258 | BF6D34D81ACAA3BB0088FAA2 /* PBXTargetDependency */, 259 | ); 260 | name = ARDBConfigDemoTests; 261 | productName = ARDBConfigDemoTests; 262 | productReference = BF6D34D61ACAA3BB0088FAA2 /* ARDBConfigDemoTests.xctest */; 263 | productType = "com.apple.product-type.bundle.unit-test"; 264 | }; 265 | /* End PBXNativeTarget section */ 266 | 267 | /* Begin PBXProject section */ 268 | BF6D34B51ACAA3BB0088FAA2 /* Project object */ = { 269 | isa = PBXProject; 270 | attributes = { 271 | LastUpgradeCheck = 0610; 272 | ORGANIZATIONNAME = Arwer; 273 | TargetAttributes = { 274 | BF6D34BC1ACAA3BB0088FAA2 = { 275 | CreatedOnToolsVersion = 6.1.1; 276 | }; 277 | BF6D34D51ACAA3BB0088FAA2 = { 278 | CreatedOnToolsVersion = 6.1.1; 279 | TestTargetID = BF6D34BC1ACAA3BB0088FAA2; 280 | }; 281 | }; 282 | }; 283 | buildConfigurationList = BF6D34B81ACAA3BB0088FAA2 /* Build configuration list for PBXProject "ARDBConfigDemo" */; 284 | compatibilityVersion = "Xcode 3.2"; 285 | developmentRegion = English; 286 | hasScannedForEncodings = 0; 287 | knownRegions = ( 288 | en, 289 | Base, 290 | ); 291 | mainGroup = BF6D34B41ACAA3BB0088FAA2; 292 | productRefGroup = BF6D34BE1ACAA3BB0088FAA2 /* Products */; 293 | projectDirPath = ""; 294 | projectRoot = ""; 295 | targets = ( 296 | BF6D34BC1ACAA3BB0088FAA2 /* ARDBConfigDemo */, 297 | BF6D34D51ACAA3BB0088FAA2 /* ARDBConfigDemoTests */, 298 | ); 299 | }; 300 | /* End PBXProject section */ 301 | 302 | /* Begin PBXResourcesBuildPhase section */ 303 | BF6D34BB1ACAA3BB0088FAA2 /* Resources */ = { 304 | isa = PBXResourcesBuildPhase; 305 | buildActionMask = 2147483647; 306 | files = ( 307 | BF6D35321ACBA8500088FAA2 /* Main.storyboard in Resources */, 308 | BF6D35311ACBA8500088FAA2 /* LaunchScreen.xib in Resources */, 309 | BF6D34ED1ACAA5750088FAA2 /* Images.xcassets in Resources */, 310 | ); 311 | runOnlyForDeploymentPostprocessing = 0; 312 | }; 313 | BF6D34D41ACAA3BB0088FAA2 /* Resources */ = { 314 | isa = PBXResourcesBuildPhase; 315 | buildActionMask = 2147483647; 316 | files = ( 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | /* End PBXResourcesBuildPhase section */ 321 | 322 | /* Begin PBXSourcesBuildPhase section */ 323 | BF6D34B91ACAA3BB0088FAA2 /* Sources */ = { 324 | isa = PBXSourcesBuildPhase; 325 | buildActionMask = 2147483647; 326 | files = ( 327 | BF6D35281ACAA6D10088FAA2 /* DBConfigLogic.m in Sources */, 328 | BF6D35161ACAA5B10088FAA2 /* README.markdown in Sources */, 329 | BF6D34EA1ACAA56C0088FAA2 /* ViewController.m in Sources */, 330 | BF6D35131ACAA5B10088FAA2 /* FMDatabaseQueue.m in Sources */, 331 | BF6D34C61ACAA3BB0088FAA2 /* AppDelegate.m in Sources */, 332 | BF6D34C31ACAA3BB0088FAA2 /* main.m in Sources */, 333 | BF6D35101ACAA5B10088FAA2 /* FMDatabase.m in Sources */, 334 | BF6D35121ACAA5B10088FAA2 /* FMDatabasePool.m in Sources */, 335 | BF6D35141ACAA5B10088FAA2 /* FMResultSet.m in Sources */, 336 | BF6D352C1ACB9F0D0088FAA2 /* ARDBConfigBase.m in Sources */, 337 | BF6D350F1ACAA5B10088FAA2 /* FMDatabase+InMemoryOnDiskIO.m in Sources */, 338 | BF6D35111ACAA5B10088FAA2 /* FMDatabaseAdditions.m in Sources */, 339 | ); 340 | runOnlyForDeploymentPostprocessing = 0; 341 | }; 342 | BF6D34D21ACAA3BB0088FAA2 /* Sources */ = { 343 | isa = PBXSourcesBuildPhase; 344 | buildActionMask = 2147483647; 345 | files = ( 346 | BF6D34DD1ACAA3BB0088FAA2 /* ARDBConfigDemoTests.m in Sources */, 347 | ); 348 | runOnlyForDeploymentPostprocessing = 0; 349 | }; 350 | /* End PBXSourcesBuildPhase section */ 351 | 352 | /* Begin PBXTargetDependency section */ 353 | BF6D34D81ACAA3BB0088FAA2 /* PBXTargetDependency */ = { 354 | isa = PBXTargetDependency; 355 | target = BF6D34BC1ACAA3BB0088FAA2 /* ARDBConfigDemo */; 356 | targetProxy = BF6D34D71ACAA3BB0088FAA2 /* PBXContainerItemProxy */; 357 | }; 358 | /* End PBXTargetDependency section */ 359 | 360 | /* Begin PBXVariantGroup section */ 361 | BF6D352D1ACBA8500088FAA2 /* LaunchScreen.xib */ = { 362 | isa = PBXVariantGroup; 363 | children = ( 364 | BF6D352E1ACBA8500088FAA2 /* Base */, 365 | ); 366 | name = LaunchScreen.xib; 367 | sourceTree = ""; 368 | }; 369 | BF6D352F1ACBA8500088FAA2 /* Main.storyboard */ = { 370 | isa = PBXVariantGroup; 371 | children = ( 372 | BF6D35301ACBA8500088FAA2 /* Base */, 373 | ); 374 | name = Main.storyboard; 375 | sourceTree = ""; 376 | }; 377 | /* End PBXVariantGroup section */ 378 | 379 | /* Begin XCBuildConfiguration section */ 380 | BF6D34DE1ACAA3BB0088FAA2 /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ALWAYS_SEARCH_USER_PATHS = NO; 384 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 385 | CLANG_CXX_LIBRARY = "libc++"; 386 | CLANG_ENABLE_MODULES = YES; 387 | CLANG_ENABLE_OBJC_ARC = YES; 388 | CLANG_WARN_BOOL_CONVERSION = YES; 389 | CLANG_WARN_CONSTANT_CONVERSION = YES; 390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 391 | CLANG_WARN_EMPTY_BODY = YES; 392 | CLANG_WARN_ENUM_CONVERSION = YES; 393 | CLANG_WARN_INT_CONVERSION = YES; 394 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 395 | CLANG_WARN_UNREACHABLE_CODE = YES; 396 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 397 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 398 | COPY_PHASE_STRIP = NO; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | GCC_C_LANGUAGE_STANDARD = gnu99; 401 | GCC_DYNAMIC_NO_PIC = NO; 402 | GCC_OPTIMIZATION_LEVEL = 0; 403 | GCC_PREPROCESSOR_DEFINITIONS = ( 404 | "DEBUG=1", 405 | "$(inherited)", 406 | ); 407 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 408 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 409 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 410 | GCC_WARN_UNDECLARED_SELECTOR = YES; 411 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 412 | GCC_WARN_UNUSED_FUNCTION = YES; 413 | GCC_WARN_UNUSED_VARIABLE = YES; 414 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 415 | MTL_ENABLE_DEBUG_INFO = YES; 416 | ONLY_ACTIVE_ARCH = YES; 417 | SDKROOT = iphoneos; 418 | TARGETED_DEVICE_FAMILY = "1,2"; 419 | }; 420 | name = Debug; 421 | }; 422 | BF6D34DF1ACAA3BB0088FAA2 /* Release */ = { 423 | isa = XCBuildConfiguration; 424 | buildSettings = { 425 | ALWAYS_SEARCH_USER_PATHS = NO; 426 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 427 | CLANG_CXX_LIBRARY = "libc++"; 428 | CLANG_ENABLE_MODULES = YES; 429 | CLANG_ENABLE_OBJC_ARC = YES; 430 | CLANG_WARN_BOOL_CONVERSION = YES; 431 | CLANG_WARN_CONSTANT_CONVERSION = YES; 432 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 433 | CLANG_WARN_EMPTY_BODY = YES; 434 | CLANG_WARN_ENUM_CONVERSION = YES; 435 | CLANG_WARN_INT_CONVERSION = YES; 436 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 437 | CLANG_WARN_UNREACHABLE_CODE = YES; 438 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 439 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 440 | COPY_PHASE_STRIP = YES; 441 | ENABLE_NS_ASSERTIONS = NO; 442 | ENABLE_STRICT_OBJC_MSGSEND = YES; 443 | GCC_C_LANGUAGE_STANDARD = gnu99; 444 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 445 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 446 | GCC_WARN_UNDECLARED_SELECTOR = YES; 447 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 448 | GCC_WARN_UNUSED_FUNCTION = YES; 449 | GCC_WARN_UNUSED_VARIABLE = YES; 450 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 451 | MTL_ENABLE_DEBUG_INFO = NO; 452 | SDKROOT = iphoneos; 453 | TARGETED_DEVICE_FAMILY = "1,2"; 454 | VALIDATE_PRODUCT = YES; 455 | }; 456 | name = Release; 457 | }; 458 | BF6D34E11ACAA3BB0088FAA2 /* Debug */ = { 459 | isa = XCBuildConfiguration; 460 | buildSettings = { 461 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 462 | INFOPLIST_FILE = ARDBConfigDemo/Info.plist; 463 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 464 | PRODUCT_NAME = "$(TARGET_NAME)"; 465 | }; 466 | name = Debug; 467 | }; 468 | BF6D34E21ACAA3BB0088FAA2 /* Release */ = { 469 | isa = XCBuildConfiguration; 470 | buildSettings = { 471 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 472 | INFOPLIST_FILE = ARDBConfigDemo/Info.plist; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 474 | PRODUCT_NAME = "$(TARGET_NAME)"; 475 | }; 476 | name = Release; 477 | }; 478 | BF6D34E41ACAA3BB0088FAA2 /* Debug */ = { 479 | isa = XCBuildConfiguration; 480 | buildSettings = { 481 | BUNDLE_LOADER = "$(TEST_HOST)"; 482 | FRAMEWORK_SEARCH_PATHS = ( 483 | "$(SDKROOT)/Developer/Library/Frameworks", 484 | "$(inherited)", 485 | ); 486 | GCC_PREPROCESSOR_DEFINITIONS = ( 487 | "DEBUG=1", 488 | "$(inherited)", 489 | ); 490 | INFOPLIST_FILE = ARDBConfigDemoTests/Info.plist; 491 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 492 | PRODUCT_NAME = "$(TARGET_NAME)"; 493 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ARDBConfigDemo.app/ARDBConfigDemo"; 494 | }; 495 | name = Debug; 496 | }; 497 | BF6D34E51ACAA3BB0088FAA2 /* Release */ = { 498 | isa = XCBuildConfiguration; 499 | buildSettings = { 500 | BUNDLE_LOADER = "$(TEST_HOST)"; 501 | FRAMEWORK_SEARCH_PATHS = ( 502 | "$(SDKROOT)/Developer/Library/Frameworks", 503 | "$(inherited)", 504 | ); 505 | INFOPLIST_FILE = ARDBConfigDemoTests/Info.plist; 506 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 507 | PRODUCT_NAME = "$(TARGET_NAME)"; 508 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ARDBConfigDemo.app/ARDBConfigDemo"; 509 | }; 510 | name = Release; 511 | }; 512 | /* End XCBuildConfiguration section */ 513 | 514 | /* Begin XCConfigurationList section */ 515 | BF6D34B81ACAA3BB0088FAA2 /* Build configuration list for PBXProject "ARDBConfigDemo" */ = { 516 | isa = XCConfigurationList; 517 | buildConfigurations = ( 518 | BF6D34DE1ACAA3BB0088FAA2 /* Debug */, 519 | BF6D34DF1ACAA3BB0088FAA2 /* Release */, 520 | ); 521 | defaultConfigurationIsVisible = 0; 522 | defaultConfigurationName = Release; 523 | }; 524 | BF6D34E01ACAA3BB0088FAA2 /* Build configuration list for PBXNativeTarget "ARDBConfigDemo" */ = { 525 | isa = XCConfigurationList; 526 | buildConfigurations = ( 527 | BF6D34E11ACAA3BB0088FAA2 /* Debug */, 528 | BF6D34E21ACAA3BB0088FAA2 /* Release */, 529 | ); 530 | defaultConfigurationIsVisible = 0; 531 | defaultConfigurationName = Release; 532 | }; 533 | BF6D34E31ACAA3BB0088FAA2 /* Build configuration list for PBXNativeTarget "ARDBConfigDemoTests" */ = { 534 | isa = XCConfigurationList; 535 | buildConfigurations = ( 536 | BF6D34E41ACAA3BB0088FAA2 /* Debug */, 537 | BF6D34E51ACAA3BB0088FAA2 /* Release */, 538 | ); 539 | defaultConfigurationIsVisible = 0; 540 | defaultConfigurationName = Release; 541 | }; 542 | /* End XCConfigurationList section */ 543 | }; 544 | rootObject = BF6D34B51ACAA3BB0088FAA2 /* Project object */; 545 | } 546 | -------------------------------------------------------------------------------- /ARDBConfigDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ARDBConfigDemo.xcodeproj/project.xcworkspace/xcuserdata/jun.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/longjun3000/ARDBConfig/6291906cb4da2a58a1ccee6c5a1bd9e13375272e/ARDBConfigDemo.xcodeproj/project.xcworkspace/xcuserdata/jun.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ARDBConfigDemo.xcodeproj/project.xcworkspace/xcuserdata/jun.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges 6 | 7 | SnapshotAutomaticallyBeforeSignificantChanges 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ARDBConfigDemo.xcodeproj/xcuserdata/jun.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 20 | 21 | 22 | 24 | 36 | 37 | 38 | 40 | 52 | 53 | 54 | 56 | 68 | 69 | 70 | 72 | 84 | 85 | 86 | 88 | 100 | 101 | 102 | 104 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /ARDBConfigDemo.xcodeproj/xcuserdata/jun.xcuserdatad/xcschemes/ARDBConfigDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 89 | 90 | 91 | 92 | 98 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /ARDBConfigDemo.xcodeproj/xcuserdata/jun.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ARDBConfigDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | BF6D34BC1ACAA3BB0088FAA2 16 | 17 | primary 18 | 19 | 20 | BF6D34D51ACAA3BB0088FAA2 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ARDBConfigDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ARDBConfigDemo 4 | // 5 | // Created by LongJun on 15/3/31. 6 | // Copyright (c) 2015年 Arwer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /ARDBConfigDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ARDBConfigDemo 4 | // 5 | // Created by LongJun on 15/3/31. 6 | // Copyright (c) 2015年 Arwer. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "DBConfigLogic.h" 11 | 12 | /// Document dir 13 | #define APP_PATH_DOCUMENT [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] 14 | /// 本地主数据库名称 15 | #define LOCAL_MAIN_DB_NAME @"db.sqlite" 16 | /// 本地主数据库完整路径 17 | #define LOCAL_MAIN_DB_PATH [APP_PATH_DOCUMENT stringByAppendingPathComponent:LOCAL_MAIN_DB_NAME] 18 | #define PRINT_APP_PATH NSLog(@"\n******** [App Path] *******\n%@\n***************************", NSHomeDirectory()); 19 | 20 | 21 | @interface AppDelegate () 22 | 23 | @end 24 | 25 | @implementation AppDelegate 26 | 27 | 28 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 29 | // Override point for customization after application launch. 30 | 31 | //打印App的根路径 32 | PRINT_APP_PATH 33 | 34 | /////////////////// Init local database //////////////////////////////////// 35 | DBConfigLogic *dbConfigLogic = [[DBConfigLogic alloc] init]; 36 | // dbConfigLogic.allowDowngrade = YES; //是否允许数据库降级,默认不允许。 37 | BOOL checkResult = [dbConfigLogic checkDatabase:LOCAL_MAIN_DB_PATH newVersion:dbConfigLogic.dbVersion]; 38 | if (!checkResult) { 39 | NSLog(@"check db fail."); 40 | 41 | UIAlertView *alertview = [[UIAlertView alloc] initWithTitle:@"提示" 42 | message:@"数据库初始化失败,不能继续加载,请彻底关闭程序后再次尝试,或者联系系统管理员。" 43 | delegate:nil 44 | cancelButtonTitle:@"确定" 45 | otherButtonTitles:nil, nil]; 46 | [alertview show]; 47 | return NO; 48 | } 49 | else { 50 | NSLog(@"check db success."); 51 | } 52 | ////////////////////////// END ///////////////////////////////////////////// 53 | 54 | return YES; 55 | } 56 | 57 | - (void)applicationWillResignActive:(UIApplication *)application { 58 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 59 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 60 | } 61 | 62 | - (void)applicationDidEnterBackground:(UIApplication *)application { 63 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 64 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 65 | } 66 | 67 | - (void)applicationWillEnterForeground:(UIApplication *)application { 68 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 69 | } 70 | 71 | - (void)applicationDidBecomeActive:(UIApplication *)application { 72 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 73 | } 74 | 75 | - (void)applicationWillTerminate:(UIApplication *)application { 76 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /ARDBConfigDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ARDBConfigDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ARDBConfigDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.arwer.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ARDBConfigDemo/Logic/DBConfigLogic.h: -------------------------------------------------------------------------------- 1 | // 2 | // DBConfigLogic.h 3 | // ARDBConfigDemo 4 | // 5 | // 功能说明: 6 | // 提供一个数据库表结构更新的机制,保证用户无论从哪个版本安装程序,数据库结构保证适配。 7 | // 如:用户A数据库版本是v1,用户B是v2,用户C没装过App这次新装;所有用户安装运行数据库版本是v3的App后,用户A数据库会v1->v2->v3依次升级,用户B会v2->v3依次升级,用户C会v1->v2->v3依次升级数据库。 8 | // 9 | // 使用说明: 10 | // 1、第一次创建工程,新建数据库的情况(数据库版本为1): 11 | // (1)新建一个继承于RLDBConfigBase的类,如DBConfigLogic。 12 | // (2)添加int类型只读属性dbVersion,实现get方法并return 1; 13 | // (3)添加覆盖父类方法onCreate,并在方法内写下第一次创建数据表结构的SQL及代码。 14 | // (4)在程序启动时(如AppDelegate.m)实例化DBConfigLogic类并调用checkDatabase方法,即可完成数据库的初始化动作。 15 | // 16 | // 2、App在某一版本数据库结构需要改动时(数据库版本升为2): 17 | // (1)在步骤1的基础上,修改dbVersion属性方法的返回值为return 2。 18 | // (2)在步骤1的基础上,添加覆盖父类方法onUpgrade,使用本文onUpgrade内示范代码,只需修改switch内的代码。 19 | // (3)如果在数据库结构升级完成后需要做一些后续数据处理,可以添加覆盖父类的方法didChecked,写入数据库操作的代码。 20 | // (4)在程序启动时(如AppDelegate.m)实例化DBConfigLogic类并调用checkDatabase方法,即可完成数据库的初始化和升级动作。 21 | // 22 | // Created by LongJun on 15/3/23. 23 | // Copyright (c) 2015年 Arwer. All rights reserved. 24 | // 25 | 26 | #import "ARDBConfigBase.h" 27 | 28 | @interface DBConfigLogic : ARDBConfigBase 29 | 30 | 31 | /** 32 | * 当前项目的数据库版本号,如果下一次数据库表结构或数据要更改,请在原数字上加1. 33 | * 34 | * 如:第一次工程创建时dbVersion=1;软件迭代升级了几次后要修改数据库表结构或数据要更改,则修改dbVersion=2。 35 | */ 36 | @property (nonatomic, readonly, assign) int dbVersion; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /ARDBConfigDemo/Logic/DBConfigLogic.m: -------------------------------------------------------------------------------- 1 | // 2 | // DBConfigLogic.m 3 | // ARDBConfigDemo 4 | // 5 | // 功能说明: 6 | // 提供一个数据库表结构更新的机制,保证用户无论从哪个版本安装程序,数据库结构保证适配。 7 | // 如:用户A数据库版本是v1,用户B是v2,用户C没装过App这次新装;所有用户安装运行数据库版本是v3的App后,用户A数据库会v1->v2->v3依次升级,用户B会v2->v3依次升级,用户C会v1->v2->v3依次升级数据库。 8 | // 9 | // 使用说明: 10 | // 1、第一次创建工程,新建数据库的情况(数据库版本为1): 11 | // (1)新建一个继承于RLDBConfigBase的类,如DBConfigLogic。 12 | // (2)添加int类型只读属性dbVersion,实现get方法并return 1; 13 | // (3)添加覆盖父类方法onCreate,并在方法内写下第一次创建数据表结构的SQL及代码。 14 | // (4)在程序启动时(如AppDelegate.m)实例化DBConfigLogic类并调用checkDatabase方法,即可完成数据库的初始化动作。 15 | // 16 | // 2、App在某一版本数据库结构需要改动时(数据库版本升为2): 17 | // (1)在步骤1的基础上,修改dbVersion属性方法的返回值为return 2。 18 | // (2)在步骤1的基础上,添加覆盖父类方法onUpgrade,使用本文onUpgrade内示范代码,只需修改switch内的代码。 19 | // (3)如果在数据库结构升级完成后需要做一些后续数据处理,可以添加覆盖父类的方法didChecked,写入数据库操作的代码。 20 | // (4)在程序启动时(如AppDelegate.m)实例化DBConfigLogic类并调用checkDatabase方法,即可完成数据库的初始化和升级动作。 21 | // 22 | // Created by LongJun on 15/3/23. 23 | // Copyright (c) 2015年 Arwer. All rights reserved. 24 | // 25 | 26 | #import "DBConfigLogic.h" 27 | 28 | @implementation DBConfigLogic 29 | 30 | #pragma mark - Custom Property 31 | 32 | /** 33 | * 只读属性,当前项目的数据库版本号,如果下一次数据库表结构或数据要更改,请在原数字上加1. 34 | * 35 | * 如:第一次工程创建时dbVersion请设为1;软件迭代升级了几次后要修改数据库表结构或数据要更改,则修改dbVersion=2;每次升级数据库请把版本号累加。 36 | */ 37 | - (int)dbVersion 38 | { 39 | /* 40 | 备注: 41 | DB走DB的版本号,App走App的版本号,互不冲突,互不影响,这里备注只是记录而已。 42 | dbVersion=1,appVersion=1.0:创建第一版数据库。 43 | dbVersion=2,appVersion=2.3:表t_Users增加了字段MobilePhone。 44 | dbVersion=3,appVersion=2.4:XXX。 45 | */ 46 | return 1; 47 | } 48 | 49 | #pragma mark - Override the parent class's methods 50 | 51 | /** 52 | * 第一次创建数据库时的sql。注意不需要写事务,父类已经启动事务 53 | * 54 | * @param db FMDB的数据库对象 55 | */ 56 | - (BOOL)onCreate:(FMDatabase *)db 57 | { 58 | NSLog(@">>> onCreate"); 59 | 60 | if (!db) { 61 | NSAssert(0, @"db can't be null"); 62 | return false; 63 | } 64 | 65 | @try { 66 | 67 | ////////////////////////// 在此处添加第一次创建表和初始化的SQL /////////////////////////////// 68 | BOOL result = NO; 69 | 70 | // 2 执行表创建工作 71 | // 2.1 用户表 72 | result = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_Users (UserId TEXT NOT NULL, LoginId TEXT NOT NULL, loginPassword TEXT, UserName TEXT, Age INTEGER, Title TEXT, PRIMARY KEY (UserId));"]; 73 | if (!result) { 74 | NSLog(@"create table Users Failed"); 75 | return false; 76 | } 77 | 78 | // 2.2 工作日志表 79 | result = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_Worklog (WorklogId TEXT NOT NULL, Title TEXT NOT NULL, Desc TEXT, Owner TEXT NOT NULL, CreatedTime TEXT NOT NULL, ModifiedTime TEXT NOT NULL, IsDeleted INTEGER, PRIMARY KEY (WorklogId));"]; 80 | if (!result) { 81 | NSLog(@"create table t_Worklog Failed"); 82 | return false; 83 | } 84 | /////////////////////////////////////// END //////////////////////////////////////////// 85 | 86 | 87 | //第一次创建数据库即self.dbVersion=1时,可以不用实现覆盖方法onUpgrade,此处可以直接return true; 88 | //self.dbVersion>1时,实现覆盖方法onUpgrade并调用它,是为了保证用户从不管从哪个版本新安装,都保证数据库版本更新到最新版。 89 | //如:用户A数据库版本是v1,用户B是v2,用户C没装过App这次新装;当前数据库版本是v3,安装运行App后,用户A会v1->v2->v3,用户B会v2->v3,用户C会v1->v2->v3依次升级数据库。 90 | return [self onUpgrade:db oldVersion:DBCONFIG_FIRST_VER newVersion:self.dbVersion]; 91 | 92 | } 93 | @catch (NSException *exception) { 94 | NSAssert1(0, @"Exception: %@", exception.reason); 95 | return false; 96 | } 97 | @finally { 98 | 99 | } 100 | 101 | } 102 | 103 | /** 104 | * 数据库版本相等时需要做的事情可以在该方法实现。 105 | * 106 | * @param db FMDB的数据库对象 107 | * @param oldVersion 当期数据库的版本 108 | * @param newVersion 要更新的新的数据库的版本 109 | */ 110 | - (BOOL)onEqual:(FMDatabase *)db oldVersion:(int)oldVersion newVersion:(int)newVersion { 111 | 112 | NSLog(@">>> onEqual, oldVersion=%d, newVersion=%d", oldVersion, newVersion); 113 | 114 | //Such as: 115 | //Clear table t_Worklog (demo need) 116 | BOOL result = [db executeUpdate:@"DELETE FROM t_Worklog"]; 117 | if (!result) { 118 | NSLog(@"remove table t_Worklog all rows Failed"); 119 | return false; 120 | } 121 | return true; 122 | } 123 | 124 | /** 125 | * 数据库版本增加时的方法,比如数据库表结构发生变化,要从版本v1升级到版本v2 126 | * 127 | * @param db FMDB的数据库对象 128 | * @param oldVersion 当期数据库的版本 129 | * @param newVersion 要更新的新的数据库的版本 130 | */ 131 | - (BOOL)onUpgrade:(FMDatabase *)db oldVersion:(int)oldVersion newVersion:(int)newVersion 132 | { 133 | NSLog(@">>> onUpgrade, oldVersion=%d, newVersion=%d", oldVersion, newVersion); 134 | 135 | if (!db) { 136 | NSAssert(0, @"db can't be null"); 137 | return false; 138 | } 139 | 140 | @try { 141 | // 升级数据库 142 | // 使用for实现跨版本升级数据库,代码逻辑始终会保证顺序递增升级。 143 | BOOL rev = NO; 144 | for(int ver = oldVersion; ver < newVersion; ver++) { 145 | rev = NO; 146 | switch(ver) { 147 | case 1: //v1-->v2 148 | rev = [self upgradeVersion1To2:db]; 149 | break ; 150 | case 2: //v2-->v3 151 | rev = [self upgradeVersion2To3:db]; 152 | break ; 153 | //有新的版本在此处添加case 3、case 4等等。 154 | default : 155 | break ; 156 | } 157 | if (!rev) return false; 158 | } 159 | 160 | // 161 | return true; 162 | } 163 | @catch (NSException *exception) { 164 | NSAssert1(0, @"Exception: %@", exception.reason); 165 | return false; 166 | } 167 | @finally { 168 | 169 | } 170 | } 171 | 172 | ///** 173 | // * 数据库版本降级时的方法。实现数据库版本降级时的代码。 174 | // * 注:默认降级是禁止的,如果要使用,在本类实例化后,设置allowDowngrade = YES;然后再调用checkDatabase方法。 175 | // * 176 | // * @param db FMDB的数据库对象 177 | // * @param oldVersion 当期数据库的版本 178 | // * @param newVersion 要更新的新的数据库的版本 179 | // * @return YES=成功,NO=失败 180 | // */ 181 | //- (BOOL)onDowngrade:(FMDatabase *)db oldVersion:(int)oldVersion newVersion:(int)newVersion { 182 | // 183 | // NSLog(@">>> onDowngrade, oldVersion=%d, newVersion=%d", oldVersion, newVersion); 184 | // 185 | // if (!db) { 186 | // NSAssert(0, @"db can't be null"); 187 | // return false; 188 | // } 189 | // 190 | // @try { 191 | // // 降级数据库 192 | // // 使用for实现跨版本降级数据库,代码逻辑始终会保证顺序递减。 193 | // BOOL rev = NO; 194 | // for(int ver = oldVersion; ver > newVersion; ver--) { 195 | // rev = NO; 196 | // switch(ver) { 197 | // case 3: //v3-->v2 198 | //// rev = [self downgradeVersion3To2:db]; 199 | // break ; 200 | // case 2: //v2-->v1 201 | //// rev = [self downgradeVersion2To1:db]; 202 | // break ; 203 | // default : 204 | // break ; 205 | // } 206 | // if (!rev) return false; 207 | // } 208 | // 209 | // // 210 | // return true; 211 | // } 212 | // @catch (NSException *exception) { 213 | // NSAssert1(0, @"Exception: %@", exception.reason); 214 | // return false; 215 | // } 216 | // @finally { 217 | // 218 | // } 219 | //} 220 | 221 | /** 222 | * 数据库配置检查完成后会调用的方法。可以实现数据库版本升级后的一些后续数据处理。 223 | * 224 | * @param db FMDB的数据库对象 225 | * @param dbCheckIsSuccess 数据库配置检查是否成功了 226 | */ 227 | - (void)didChecked:(FMDatabase *)db dbCheckIsSuccess:(BOOL)dbCheckIsSuccess 228 | { 229 | if (!dbCheckIsSuccess) return; 230 | 231 | //do db something 232 | //... 233 | 234 | } 235 | 236 | #pragma mark - Custom Method 237 | 238 | /** 239 | * 数据库版本从v1升级到v2。 240 | * 241 | * 主要功能有: 242 | * 给t_Users表增加字段MobilePhone 243 | */ 244 | - (BOOL)upgradeVersion1To2:(FMDatabase *)db 245 | { 246 | 247 | //1 判断表是否存在,取出t_Users表创建语句 248 | FMResultSet *rs = [db executeQuery:@"SELECT sql FROM sqlite_master WHERE type = 'table' AND tbl_name = 't_Users' "]; 249 | NSString *tabCreateSql = nil; 250 | BOOL tableIsExistus = NO; 251 | while([rs next]) { 252 | tableIsExistus = YES; 253 | tabCreateSql = [rs stringForColumnIndex:0]; 254 | break; 255 | } 256 | [rs close]; 257 | if (tableIsExistus && tabCreateSql) { 258 | //1.2 判断要新增的列MobilePhone是否存,不存在则添加 259 | NSString *column_FileName = @"MobilePhone"; 260 | NSRange range1 = [tabCreateSql rangeOfString: column_FileName]; 261 | if (range1.length < 1) { 262 | NSString *sql = [NSString stringWithFormat:@"ALTER TABLE t_Users ADD %@ TEXT NULL; ", column_FileName]; 263 | BOOL rev = [db executeUpdate:sql]; 264 | if (!rev) { 265 | [db rollback]; 266 | NSLog(@"执行以下sql时失败:\n%@\n失败原因是:%@", sql, [db lastErrorMessage]); 267 | NSAssert2(0, @"执行以下sql时失败:\n%@\n失败原因是:%@", sql, [db lastErrorMessage]); 268 | return false; 269 | } 270 | } 271 | } 272 | return true; 273 | } 274 | 275 | /** 276 | * 数据库版本从v2升级到v3。 277 | * 278 | * 主要功能有: 279 | * xxxxx 280 | */ 281 | - (BOOL)upgradeVersion2To3:(FMDatabase *)db 282 | { 283 | //do something... 284 | return true; 285 | } 286 | 287 | 288 | 289 | @end 290 | -------------------------------------------------------------------------------- /ARDBConfigDemo/Resources/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /ARDBConfigDemo/ViewController/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // ARDBConfigDemo 4 | // 5 | // Created by LongJun on 15/3/31. 6 | // Copyright (c) 2015年 Arwer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /ARDBConfigDemo/ViewController/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // ARDBConfigDemo 4 | // 5 | // Created by LongJun on 15/3/31. 6 | // Copyright (c) 2015年 Arwer. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | @end 14 | 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad { 18 | [super viewDidLoad]; 19 | // Do any additional setup after loading the view, typically from a nib. 20 | } 21 | 22 | - (void)didReceiveMemoryWarning { 23 | [super didReceiveMemoryWarning]; 24 | // Dispose of any resources that can be recreated. 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /ARDBConfigDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ARDBConfigDemo 4 | // 5 | // Created by LongJun on 15/3/31. 6 | // Copyright (c) 2015年 Arwer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ARDBConfigDemoTests/ARDBConfigDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ARDBConfigDemoTests.m 3 | // ARDBConfigDemoTests 4 | // 5 | // Created by LongJun on 15/3/31. 6 | // Copyright (c) 2015年 Arwer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface ARDBConfigDemoTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation ARDBConfigDemoTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /ARDBConfigDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.arwer.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ARDBConfig 2 | =========== 3 | On the iOS, provide a database table structure update mechanism, ensure that the user in any version of the installer, the database structure to ensure adapter. 4 | 5 | Such as: user A's database version is v1, user B is v2, user C never installed App; Now, all users to install and run the latest App (database version is v3) after the user A's database will "v1 --> v2 --> v3" order upgrades, user B 's database will "v2 --> v3" in order to upgrade, the user C's database will "v1 --> v2 --> v3" order to upgrade. 6 | 7 | 8 | 在iOS上,提供一个数据库表结构更新的机制,保证用户无论从哪个版本安装程序,数据库结构保证适配。 9 | 10 | 如:用户A的数据库版本是v1,用户B是v2,用户C没装过App;现在,所有用户安装并运行最新App(数据库版本是v3)后,用户A的数据库将会“v1->v2->v3”顺序升级,用户B的数据库将会“v2->v3”顺序升级,用户C的数据库将会“v1->v2->v3”顺序升级。 11 | 12 | ![image](https://github.com/longjun3000/ARDBConfig/blob/master/Screenshop01.png) 13 | 14 | How to use ? 15 | ============ 16 | 1. create a project for the first time, the situation of the new database (database version 1) : 17 | (1) to create a new inheritance in "RLDBConfigBase" classes, such as "DBConfigLogic". 18 | (2) to add an int type read-only property "dbVersion", realizing the get method and return 1; 19 | (3) added to cover the superclass method "onCreate", and write down around a method a SQL and create the data table structure of the code. 20 | (4) the application starts (e.g., "AppDelegate. M"), instantiate "DBConfigLogic" class and call "checkDatabase" method, can complete the initialization of the database. 21 | 22 | 2. the App in a version of the database structure needs to be altered (database version to 2) : 23 | (1) in step 1, on the basis of modified dbVersion properties method in the return value is the return of 2. 24 | (2) in step 1, on the basis of "onUpgrade" add cover the superclass method, using demonstration in the article "onUpgrade" code, only need to modify the code within the switch. 25 | (3) if the database structure upgrade after the completion of the need to do some follow-up data processing, can be added to cover the superclass method "didChecked", code written to the database operation. 26 | (4) the application starts (e.g., "AppDelegate. M"), instantiate "DBConfigLogic" class and call "checkDatabase" method, can complete the initialization of the database and update action. 27 | 28 | 29 | 如何使用? 30 | ======== 31 | 1、第一次创建工程,新建数据库的情况(数据库版本为1): 32 | (1)新建一个继承于“RLDBConfigBase”的类,如“DBConfigLogic”。 33 | (2)添加int类型只读属性“dbVersion”,实现get方法并return 1; 34 | (3)添加覆盖父类方法“onCreate”,并在方法内写下第一次创建数据表结构的SQL及代码。 35 | (4)在程序启动时(如“AppDelegate.m”),实例化“DBConfigLogic”类并调用“checkDatabase”方法,即可完成数据库的初始化动作。 36 | 37 | 2、App在某一版本数据库结构需要改动时(数据库版本升为2): 38 | (1)在步骤1的基础上,修改“dbVersion”属性方法的返回值为return 2。 39 | (2)在步骤1的基础上,添加覆盖父类方法“onUpgrade”,使用本文“onUpgrade”内示范代码,只需修改switch内的代码。 40 | (3)如果在数据库结构升级完成后需要做一些后续数据处理,可以添加覆盖父类的方法“didChecked”,写入数据库操作的代码。 41 | (4)在程序启动时(如“AppDelegate.m”),实例化“DBConfigLogic”类并调用“checkDatabase”方法,即可完成数据库的初始化和升级动作。 42 | 43 | 44 | Contact 45 | ======= 46 | ArwerSoftware@gmail.com 47 | 48 | 联系方式 49 | ======= 50 | ArwerSoftware@gmail.com 51 | 52 | 53 | License 54 | ======= 55 | The MIT License (MIT) 56 | 57 | Copyright © 2014 LongJun 58 | 59 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 60 | 61 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 62 | 63 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 64 | 65 | -------------------------------------------------------------------------------- /Screenshop01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/longjun3000/ARDBConfig/6291906cb4da2a58a1ccee6c5a1bd9e13375272e/Screenshop01.png -------------------------------------------------------------------------------- /ThridParty/ARDBConfig/ARDBConfigBase.h: -------------------------------------------------------------------------------- 1 | // 2 | // RLDBConfigBase.h 3 | // ARDBConfigDemo 4 | // 5 | // 功能说明: 6 | // 提供一个数据库表结构更新的机制,保证用户无论从哪个版本安装程序,数据库结构保证适配。 7 | // 8 | // 使用说明: 9 | // 1、新增一个类,如DBConfigLogic,继承于RLDBConfigBase。 10 | // 2、重载方法onCreate:在方法内写入第一次创建数据库时的SQL。 11 | // 3、重载方法onUpgrade:在方法内写入每个数据库版本升级时的代码,比如v1->v2,v2->v3。 12 | // 4、在程序启动时(如AppDelegate.m)实例化DBConfigLogic类并调用checkDatabase方法,即可完成数据库的初始化和升级动作。 13 | // 14 | // Created by LongJun on 15/3/9. 15 | // Copyright (c) 2015年 Arwer. All rights reserved. 16 | // 17 | 18 | #import 19 | #import "FMDatabase.h" 20 | 21 | ///Database的第一个版本号为1。 22 | #define DBCONFIG_FIRST_VER 1 23 | 24 | @interface ARDBConfigBase : NSObject 25 | 26 | #pragma mark - Custom Property 27 | /** 28 | * 是否允许数据库降级,默认不允许。 29 | * 注:默认降级是禁止的,如果要使用,在本类的子类实例化后,设置allowDowngrade = YES;然后再调用checkDatabase方法。 30 | * 31 | */ 32 | @property (nonatomic, assign) BOOL allowDowngrade; 33 | 34 | #pragma mark - base class's methods 35 | 36 | /** 37 | * 检查数据库(初始化数据库或更新数据库) 38 | * 39 | * @param dbFullName 完整的数据库路径+数据库文件名 40 | * @param newVersion 新版本的版本号 41 | * @return YES=成功,NO=失败 42 | */ 43 | - (BOOL)checkDatabase:(NSString*)dbFullName newVersion:(int)newVersion; 44 | 45 | 46 | /** 47 | * 子类必须覆盖该方法,实现第一次创建数据库时的SQL 48 | * 49 | * @param db FMDB的数据库对象 50 | * @return YES=成功,NO=失败 51 | */ 52 | - (BOOL)onCreate:(FMDatabase *)db; 53 | 54 | /** 55 | * 数据库版本相等时的方法。子类可以覆盖该方法,实现数据库 版本相等时的SQL 56 | * 57 | * @param db FMDB的数据库对象 58 | * @param oldVersion 当期数据库的版本 59 | * @param newVersion 要更新的新的数据库的版本 60 | * @return YES=成功,NO=失败 61 | */ 62 | - (BOOL)onEqual:(FMDatabase *)db oldVersion:(int)oldVersion newVersion:(int)newVersion; 63 | 64 | /** 65 | * 数据库版本增加时的方法。子类需要覆盖该方法,实现数据库版本增加时的代码 66 | * 67 | * @param db FMDB的数据库对象 68 | * @param oldVersion 当期数据库的版本 69 | * @param newVersion 要更新的新的数据库的版本 70 | * @return YES=成功,NO=失败 71 | */ 72 | - (BOOL)onUpgrade:(FMDatabase *)db oldVersion:(int)oldVersion newVersion:(int)newVersion; 73 | 74 | /** 75 | * 数据库版本降级时的方法。子类可以覆盖该方法,实现数据库版本降级时的代码 76 | * 77 | * @param db FMDB的数据库对象 78 | * @param oldVersion 当期数据库的版本 79 | * @param newVersion 要更新的新的数据库的版本 80 | * @return YES=成功,NO=失败 81 | */ 82 | - (BOOL)onDowngrade:(FMDatabase *)db oldVersion:(int)oldVersion newVersion:(int)newVersion; 83 | 84 | /** 85 | * 数据库配置检查完成后会调用的方法。子类可以覆盖该方法,实现数据库版本升级后的一些后续数据处理。 86 | * 87 | * @param db FMDB的数据库对象 88 | * @param dbCheckIsSuccess 数据库配置检查是否成功了 89 | */ 90 | - (void)didChecked:(FMDatabase *)db dbCheckIsSuccess:(BOOL)dbCheckIsSuccess; 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /ThridParty/ARDBConfig/ARDBConfigBase.m: -------------------------------------------------------------------------------- 1 | // 2 | // RLDBConfigBase.m 3 | // ARDBConfigDemo 4 | // 5 | // Created by LongJun on 15/3/9. 6 | // Copyright (c) 2015年 Arwer. All rights reserved. 7 | // 8 | /* 9 | 备注: 10 | 在数据库中,我们可以使用这样写 sql语句来查询它: 11 | PRAGMA user_version 12 | 或者来设置它的值 13 | PRAGMA user_version = 1 14 | 更多内容参考sqlite的官方描述:http://www.sqlite.org/pragma.html 15 | */ 16 | 17 | #import "ARDBConfigBase.h" 18 | 19 | @interface ARDBConfigBase() 20 | 21 | @property (nonatomic, strong) FMDatabase *db; 22 | 23 | @end 24 | 25 | @implementation ARDBConfigBase 26 | 27 | 28 | 29 | //- (instancetype)init 30 | //{ 31 | // if ((self = [super init])) { 32 | // 33 | // } 34 | // return self; 35 | //} 36 | 37 | /** 38 | * 检查数据库(初始化数据库或更新数据库) 39 | * 40 | * @param dbFullName 完整的数据库路径+数据库文件名 41 | * @param newVersion 新版本的版本号 42 | */ 43 | - (BOOL)checkDatabase:(NSString*)dbFullName newVersion:(int)newVersion 44 | { 45 | if (!dbFullName) { 46 | NSAssert1(0, @"db path and name can't be empty (%@)", dbFullName); 47 | @throw [NSException exceptionWithName:@"Database name error" reason:@"Database name and path can not be empty." userInfo:nil]; 48 | return NO; 49 | } 50 | if (newVersion < 1) { 51 | NSAssert1(0, @"The database version number cannot be less than 1. (%d)", newVersion); 52 | @throw [NSException exceptionWithName:@"Database version error" reason:@"The database version number cannot be less than 1." userInfo:nil]; 53 | return NO; 54 | } 55 | 56 | self.db = [FMDatabase databaseWithPath:dbFullName]; 57 | @try { 58 | if (![_db open]) { 59 | // [db release]; 60 | //NSLog(@"db open fail"); 61 | NSAssert1(0, @"db open fail (%@)", dbFullName); 62 | return NO; 63 | } 64 | 65 | //查出当前数据库版本 66 | FMResultSet *rs = [_db executeQuery:@"PRAGMA user_version;"]; 67 | int oldVersion = -1; 68 | if ([rs next]) 69 | { 70 | oldVersion = [rs intForColumnIndex:0]; 71 | } 72 | [rs close]; 73 | 74 | // 75 | if (oldVersion <= 0) { //表示第一次创建数据库 76 | 77 | [_db beginTransaction]; 78 | BOOL rev = [self onCreate:_db]; 79 | if (rev) { 80 | rev = [_db executeUpdate:[NSString stringWithFormat:@"PRAGMA user_version = %d", newVersion]]; 81 | if (rev) 82 | [_db commit]; 83 | else { 84 | NSLog(@">>> db exec fail: %@", [_db lastError]); 85 | [_db rollback]; 86 | } 87 | } 88 | else { 89 | [_db rollback]; 90 | NSLog(@">>> db exec fail: %@", [_db lastError]); 91 | } 92 | // 93 | [self didChecked:_db dbCheckIsSuccess:rev]; 94 | return rev; 95 | } 96 | else { //表示已经创建了库表,接下来走onUpgrade等,由开发者在子类中决定如何升级或降级库表结构 97 | 98 | if (newVersion < oldVersion) { //新版本号小于旧版本号 99 | 100 | if (self.allowDowngrade) { 101 | //执行用户的降级代码 102 | [_db beginTransaction]; 103 | BOOL rev = [self onDowngrade:self.db oldVersion:oldVersion newVersion:newVersion]; 104 | if (rev) { 105 | rev = [_db executeUpdate:[NSString stringWithFormat:@"PRAGMA user_version = %d", newVersion]]; 106 | if (rev) 107 | [_db commit]; 108 | else { 109 | [_db rollback]; 110 | NSLog(@">>> db exec fail: %@", [_db lastError]); 111 | } 112 | } 113 | else { 114 | [_db rollback]; 115 | NSLog(@">>> db exec fail: %@", [_db lastError]); 116 | } 117 | // 118 | [self didChecked:_db dbCheckIsSuccess:rev]; 119 | return rev; 120 | } 121 | else { //禁止降级 122 | NSString *errStr = [NSString stringWithFormat:@"The database new version(%d) cannot be less than the old version(%d)", newVersion, oldVersion]; 123 | NSAssert2(0, errStr, newVersion, oldVersion); 124 | // @throw [NSException exceptionWithName:@"Database version error" reason:errStr userInfo:nil]; 125 | // 126 | [self didChecked:_db dbCheckIsSuccess:NO]; 127 | return NO; 128 | } 129 | } 130 | else if (newVersion == oldVersion) { //新旧版本号相同 131 | 132 | //执行用户的代码 133 | [_db beginTransaction]; 134 | BOOL rev = [self onEqual:self.db oldVersion:oldVersion newVersion:newVersion]; 135 | if (rev) 136 | [_db commit]; 137 | else { 138 | [_db rollback]; 139 | NSLog(@">>> db exec fail: %@", [_db lastError]); 140 | } 141 | // 142 | [self didChecked:_db dbCheckIsSuccess:rev]; 143 | return rev; 144 | } 145 | else { //新版本号大于旧版本号则执行onUpgrade里的方法 146 | 147 | //执行用户的更新代码 148 | [_db beginTransaction]; 149 | BOOL rev = [self onUpgrade:self.db oldVersion:oldVersion newVersion:newVersion]; 150 | if (rev) { 151 | rev = [_db executeUpdate:[NSString stringWithFormat:@"PRAGMA user_version = %d", newVersion]]; 152 | if (rev) 153 | [_db commit]; 154 | else { 155 | [_db rollback]; 156 | NSLog(@">>> db exec fail: %@", [_db lastError]); 157 | } 158 | } 159 | else { 160 | [_db rollback]; 161 | NSLog(@">>> db exec fail: %@", [_db lastError]); 162 | } 163 | // 164 | [self didChecked:_db dbCheckIsSuccess:rev]; 165 | return rev; 166 | } 167 | } 168 | } 169 | @catch (NSException *ex) { 170 | 171 | NSAssert1(0, @"Exception: %@", ex.reason); 172 | 173 | } 174 | @finally { 175 | [self.db close]; 176 | 177 | } 178 | return NO; 179 | } 180 | 181 | /** 182 | * 第一次创建数据库时的方法。子类需要覆盖该方法,实现第一次创建数据库时的代码 183 | * 184 | * @param db FMDB的数据库对象 185 | * @return YES=成功,NO=失败 186 | */ 187 | - (BOOL)onCreate:(FMDatabase *)db { 188 | return YES; 189 | } 190 | 191 | /** 192 | * 数据库版本相等时的方法。子类可以覆盖该方法,实现数据库版本相等时的代码 193 | * 194 | * @param db FMDB的数据库对象 195 | * @param oldVersion 当期数据库的版本 196 | * @param newVersion 要更新的新的数据库的版本 197 | * @return YES=成功,NO=失败 198 | */ 199 | - (BOOL)onEqual:(FMDatabase *)db oldVersion:(int)oldVersion newVersion:(int)newVersion { 200 | return YES; 201 | } 202 | 203 | /** 204 | * 数据库版本增加时的方法。子类需要覆盖该方法,实现数据库版本增加时的代码 205 | * 206 | * @param db FMDB的数据库对象 207 | * @param oldVersion 当期数据库的版本 208 | * @param newVersion 要更新的新的数据库的版本 209 | * @return YES=成功,NO=失败 210 | */ 211 | - (BOOL)onUpgrade:(FMDatabase *)db oldVersion:(int)oldVersion newVersion:(int)newVersion { 212 | return YES; 213 | } 214 | 215 | /** 216 | * 数据库版本降级时的方法。子类可以覆盖该方法,实现数据库版本降级时的代码 217 | * 218 | * @param db FMDB的数据库对象 219 | * @param oldVersion 当期数据库的版本 220 | * @param newVersion 要更新的新的数据库的版本 221 | * @return YES=成功,NO=失败 222 | */ 223 | - (BOOL)onDowngrade:(FMDatabase *)db oldVersion:(int)oldVersion newVersion:(int)newVersion { 224 | return YES; 225 | } 226 | 227 | /** 228 | * 数据库配置检查完成后会调用的方法。子类可以覆盖该方法,实现数据库版本升级后的一些后续数据处理。 229 | * 230 | * @param db FMDB的数据库对象 231 | * @param dbCheckIsSuccess 数据库配置检查是否成功了 232 | */ 233 | - (void)didChecked:(FMDatabase *)db dbCheckIsSuccess:(BOOL)dbCheckIsSuccess 234 | { 235 | 236 | } 237 | 238 | @end 239 | -------------------------------------------------------------------------------- /ThridParty/FMDB/CHANGES_AND_TODO_LIST.txt: -------------------------------------------------------------------------------- 1 | TODO: 2 | Zip, nada, zilch. Got any ideas? 3 | 4 | If you would like to contribute some code- awesome! I just ask that you make it conform to the coding conventions already set in here, and to add a couple of tests for your new code to fmdb.m. And of course, the code should be of general use to more than just a couple of folks. Send your patches to gus@flyingmeat.com. 5 | 6 | 2014.04.23 7 | New executeStatements: method, which will take a single UTF-8 string with multiple statements in it. This is great for batch updates. There is also a executeStatements:withResultBlock: version which takes a callback block which will be used for any statements which return rows in the bulk statement. Thanks to Rob Ryan for contributing code for this. 8 | 9 | Deprecated update:withErrorAndBindings: in favor of executeUpdate:withErrorAndBindings: 10 | 11 | 2014.04.09 12 | Added back in busy handler code after a brief hiatus (check out the 2013.12.10 notes). But now doing so with sqlite3_busy_handler instead of while loops in the various execution places. 13 | Added some new optional classes that will help with doing batch updates - check out FMSQLStatementSplitter.h for more info. Thanks to Julius Scott for the patches. 14 | 15 | 2014.03.08 16 | A few administrative changes: 17 | 18 | - Move FMDB source files into three subdirectories, either src/fmdb, src/sample, or src/extras. 19 | - Renamed fmdb.m to main.m and moved it into src/sample so that it's clear its a sample and it won't be included in project for those users who manually drag fmdb source into their projects. 20 | - Created FMDB.m for those users who would prefer to do a single #import and get all of the key headers. 21 | 22 | 2014.01.17 23 | It's never been safe to reentrantly call -[FMDatabaseQueue inDatabase:], as it would block. Which can be kind of annoying - so now FMDB will crash instead (thanks to Mike Ash for the patch). 24 | 25 | 2013.12.10 26 | Lots of little updates - new test targets, ARC simplification, and open flags to FMDatabaseQueue (thanks Graham Dennis), FMDatabaseQueue now has + (Class)databaseClass; which can return a new subclass of FMDatabase for custom stuff (thanks Timur Islamgulov), 27 | 28 | ## IMPORTANT## 29 | int busyRetryTimeout has been change to NSTimeInterval busyTimeout in FMDatabase. Instead of using it's homegrown "try again every so often if it's locked", we use sqlite's built in support for this. Why wasn't FMDatabase using it before? Because Gus didn't know about it. Thanks to Jens Alfke for pointing this out and providing some code to work with. 30 | 31 | 2013.10.21 32 | Fixed a problem where having statement caching turned on would cause issues when trying to use two result sets with the same query but different binding parameters. Thanks to Nick Hodapp for the original patch. 33 | Fixed a problem where save points weren't being created with the correct names, and were not cleaned up properly. Thanks to Graham Dennis for the patches. 34 | 35 | 2013.10.16 36 | Added methods that expose va_list arguments. Thanks to Ibrahim Ennafaa for the patch. 37 | 38 | 2013.09.26 39 | Logs errors is now turned on by default. It's easy to turn off, and if you're seeing errors then you should probably fix those. 40 | 41 | 2013.06.26 42 | Added `NS_FORMAT_FUNCTION` qualifier to `executeQueryWithFormat` and `executeUpdateWithFormat`. These methods take format strings and variable number of arguments and you will now receive compiler warnings if the types of your parameters dont match the format string. 43 | 44 | 2013.06.04 45 | Merged in Robert Ryan's comments in .h header files. These comments hopefully make the .h more readable, but just as importantly, can be parsed by [`appledoc`](http://gentlebytes.com/appledoc/) to create HTML documentation or Xcode docsets. 46 | 47 | - To build that HTML documentation, once you've installed `appledoc`, you issue the command: 48 | 49 | appledoc --project-name FMDB --project-company ccgus --explicit-crossref --no-merge-categories --output ../Documentation --ignore *.m . 50 | 51 | - If you want online help integrated right into Xcode, you can issue the command: 52 | 53 | appledoc --project-name FMDB --project-company ccgus --explicit-crossref --merge-categories --install-docset --output --ignore *.m ../Documentation . 54 | 55 | 2013.05.24 56 | Merged in Chris Wright's date format additions to FMDatabase. 57 | Fixed a problem where executeUpdateWithFormat: + %@ as a placeholder and the value was nil would cause a bad value to be inserted. Thanks to rustybox on github for the fix. 58 | Fixed a variable argument crash if an incorrect number of arguments are passed in - patch by Joshua Tessier. 59 | Baked in support for the new application_id pragma in SQLite version 3.7.17: 60 | - (uint32_t)applicationID; 61 | - (void)setApplicationID:(uint32_t)appID; 62 | - (NSString*)applicationIDString; 63 | - (void)setApplicationIDString:(NSString*)s; 64 | 65 | 66 | 2013.04.17 67 | Added two new methods to FMDatabase for setting crypto keys, which take NSData objects. Thanks to Phillip Kast for the patch! 68 | 69 | 2013.02.19 70 | Fixed potential dereference of NULL outErr argument in -startSavePointWithName:error: and -releaseSavePointWithName:error:. Thanks to Jim Correia for the patch! 71 | 72 | 2013.02.05 73 | Added an "extra" folder which contains additional things which might be useful for programmers, but which I don't think should be part of the standard classes. At this time- there's only a single category in there which will load and save an on disk database to an in memory database. Thanks to Peter Carr who provided these additions! 74 | 75 | 2013.01.31 76 | Lazy init of columnNameToIndexMap in FMResultSet, and you are now able to use it before asking for any rows first. Thanks to Daniel Dickison for the patch! 77 | 78 | 2012.12.17 79 | Now resetting cached statements before executing a query on them (as well as resetting them at the close of a result set). There was an issue where if you used the same query twice without closing the result set of the first one, you would get results back from the previous query, or maybe an exhausted result set. Thanks to note173 on github for pointing out the problem. 80 | 81 | 2012.12.13 82 | Changed up how the binding count is calculated when passing a dictionary for named parameter support. Thanks to Samuel Chen for pointing out the problem. 83 | 84 | 2012.11.23 85 | Added keyed and indexed subscript support to FMResultSet- so you can do use the fancy boxed syntax against it (rs[@"foo"] & rs[0]). Thanks to Robert Ryan for the patches! 86 | 87 | 2012.08.08 88 | Fixed some problems when compiling with ARC against the 10.8 SDK (patch from Geoffrey Foster!). 89 | 90 | 2012.05.29: 91 | Changed up the behavior of binding empty NSData objects ([NSData data]). It will now insert an empty value, rather than a null value- which is consistent with [NSMutableData data] and empty strings (see https://github.com/ccgus/fmdb/issues/73 for a discussion on this). Thanks to Jens Alfke for pointing this out! 92 | 93 | 2012.05.25: 94 | Deprecated columnExists:columnName: in favor of columnExists:inTableWithName: 95 | Remembered to update the changes notes. I've been forgetting to do this recently. 96 | 97 | 2012.03.22: 98 | Deprecated resultDict and replaced it with resultDictionary on FMResultSet. Slight change in behavior as well- resultDictionary will return case sensitive keys. 99 | Fixed a problem with getTableSchema: not working with table names that start with a number. 100 | 101 | 102 | 2012.02.10: 103 | Changed up FMDatabasePool so that you can't "pop" it from a pool anymore. I just consider this too risky- use the block based functions instead. 104 | Also provided a good reason in main.m for why you should use FMDatabaseQueue instead. Search for "ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS". 105 | I consider this branch 2.0 at this point- I'll let it bake for a couple of days, then push it to the main repo. 106 | 107 | 108 | 2012.01.06: 109 | Added a new method to FMDatabase to make custom functions out of a block: 110 | - (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block 111 | 112 | Check out the function "testSQLiteFunction" in main.m for an example. 113 | 114 | 2011.07.14: 115 | Added methods for named parameters, using keys from an NSDictionary (Thanks to Drarok Ithaqua for the patches!) 116 | Changed FMDatabase's "- (BOOL)update:(NSString*)sql error:(NSError**)outErr bind:(id)bindArgs, ... " to "- (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ..." as the previous method didn't actually work as advertised in the way it was written. Thanks to @jaekwon for pointing this out. 117 | 118 | 2011.06.22 119 | Changed some methods to properties. Hello 2011. 120 | Added a warning when you try and use a database that wasn't opened. Hacked together based on patches from Drarok Ithaqua. 121 | Fixed a problem under GC where leaked statments were keeping a database from closing. Patch from Chris Dolan. 122 | Added + (BOOL)isThreadSafe to FMDatabase. It'll let you know if the version of SQLite you are running is compiled with it's thread safe options. THIS DOES NOT MEAN FMDATABASE IS THREAD SAFE. I haven't done a review of it for this case, so I'm just saying. 123 | 124 | 2011.04.09 125 | Added a method to validate a SQL statement. 126 | Added a method to retrieve the number of columns in a result set. 127 | Added two methods to execute queries and updates with NSString-style format specifiers. 128 | Thanks to Dave DeLong for the patches! 129 | 130 | 2011.03.12 131 | Added compatibility with garbage collection. 132 | When an FMDatabase is closed, all open FMResultSets pertaining to that database are also closed. 133 | Added: 134 | - (id) objectForColumnIndex:(int)columnIdx; 135 | - (id) objectForColumnName:(NSString*)columnName; 136 | Changes by Dave DeLong. 137 | 138 | 2011.02.05 139 | The -(int)changes; method on FMDatabase is a bit more robust now, and there's a new static library target. And if a database path is nil, we now open up a :memory: database. Patch from Pascal Pfiffner! 140 | 141 | 2011.01.13 142 | Happy New Year! 143 | Now checking for SQLITE_LOCKED along with SQLITE_BUSY when trying to perform a query. Patch from Jeff Meininger! 144 | 145 | 2010.12.28 146 | Fixed some compiler warnings (thanks to Casey Fleser!) 147 | 148 | 2010.11.30 149 | Added and updated some new methods to take NSError** params. 150 | Only execute the assertion macros if NS_BLOCK_ASSERTIONS is not set. Patch from David E. Wheeler! 151 | 152 | 2010.09.19 153 | The signature for FMDatabase's executeQuery* methods now return FMResultSet instead if id. Patch from Augie Fackler! 154 | 155 | 2010.08.24: 156 | Added resultDict to FMResultSet, which returns a dictionary of column values. Thanks to Pascal Pfiffner for the patch! 157 | Cleaned up some formatting. 158 | 159 | 2010.06.21: 160 | 161 | Changed up FMDatabase's close method to return a boolean, and fixed a compiler warning when compiling in 64bit land. Thanks to Jens Alfke for the patches! 162 | 163 | 164 | 2010.04.04: 165 | Added: 166 | dateForQuery which works like the other fooForQuery methods. Thanks to Matt Stevens for the patch! 167 | 168 | 2009.10.18: 169 | 170 | Added: 171 | FMDB now checks for longLongValue in NSNumbers passed for selects or updates, and binds that value to an sqlite int64 172 | 173 | Thanks for the patch from Brian Stern! 174 | 175 | Changed: 176 | renamed getDataBaseSchema: to getSchema. It didn't actually use the param. whooops. 177 | 178 | 179 | 180 | 181 | 2009.10.14: 182 | 183 | Reworked: 184 | - (id)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments; 185 | - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments; 186 | 187 | These two methods now point to: 188 | - (id) executeQuery:(NSString*)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args; 189 | - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray*)arrayArgs orVAList:(va_list)args; 190 | 191 | because the vargs were causing headaches in 64bit land, and on the iphone, and it's fragile n' stuff. 192 | 193 | 194 | Added: 195 | - (FMResultSet*) getTableSchema:(NSString*)tableName; 196 | - (BOOL) columnExists:(NSString*)tableName columnName:(NSString*)columnName; 197 | 198 | to FMDatabaseAdditions. Patch from OZLB 199 | 200 | 2009.09.22 201 | Disabled the following FMDatabaseAdditions when compiled as 64 bit: 202 | - (id)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments; 203 | and 204 | - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments; 205 | 206 | Since they crash, I just print out a warning now. Got a patch to fix it? Send it to gus@flyingmeat.com 207 | 208 | 209 | Added: 210 | - (BOOL) tableExists:(NSString*)tableName; 211 | - (FMResultSet*) getDataBaseSchema:(NSString*)tableName; 212 | 213 | to FMDatabaseAdditions. Patch from OZLB 214 | 215 | 216 | 2009.09.1 217 | Added: 218 | - (BOOL) openWithFlags:(int)flags; 219 | To FMDatabase, which allows you to open up the database with certain flags for sqlite 3.5+ 220 | 221 | Thanks to Dan Wright for the addition. 222 | 223 | 2009.07.17 224 | Added: 225 | - (const unsigned char *) UTF8StringForColumnIndex:(int)columnIdx; 226 | - (const unsigned char *) UTF8StringForColumnName:(NSString*)columnName; 227 | to FMResultSet, patch from Nathan Stitt. 228 | 229 | 2009.05.23 230 | Added: 231 | - (id)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments; 232 | - (BOOL) executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments; 233 | thanks to code from Phong Long. 234 | 235 | 236 | Fix to FMResultSet's - (BOOL) hadError, as it was returning true for SQLITE_ROW & SQLITE_DONE, which aren't actually errors. 237 | Added: 238 | - (BOOL) hasAnotherRow; which lets you know if there is another row waiting in the result set. 239 | 240 | Thanks to code from Dave DeLong. 241 | 242 | 243 | 2009.05.10 244 | replaced sqlite3_prepare calls with sqlite3_prepare_v2, since it's shiny and new. 245 | Now making sure not to call any assembly if on the iphone (asm{ trap }). 246 | Tested on 10.6. It works. Not that I didn't expect it not to- but you never know… 247 | 248 | 2009.05.05 249 | stringForColumnIndex, stringForColumn, dataForColumnIndex, dataForColumn, dataNoCopyForColumnIndex, and dataNoCopyForColumn now return nil if a null value was inserted into its table row. dateForColumnIndex already did this. 250 | 251 | Also added the following methods to test if a column is null or not: 252 | - (BOOL) columnIndexIsNull:(int)columnIdx 253 | - (BOOL) columnIsNull:(NSString*)columnName 254 | 255 | And finally, just general code cleanup + refactoring. Happy Cinco de Mayo! 256 | 257 | 2009.04.27 258 | added columnNameForIndex: to FMResultSet which returns the column name for the given index. 259 | 260 | 2009.04.12 261 | dateForColumnIndex: now returns null, if a null value was inserted into its table row. Patch by Robbie Hanson. 262 | 263 | 2009.03.11 264 | Now importing unistd.h, which the absence of was causing some problems on the iPhone. Reported by multiple people, but Hal Mueller actually got me to make the change. 265 | 266 | 2009.03.03 267 | Added (int)changes; to FMDatabase. From the sqlite docs: "This function returns the number of database rows that were changed (or inserted or deleted) by the most recent SQL statement." 268 | Patch contributed by Tracy Harton 269 | 270 | 2009.01.02 271 | HAPPY NEW YEAR WOOOO! 272 | Added dataNoCopyForColumn: and dataNoCopyForColumnIndex: to FMResultSet. 273 | If you are going to use this data after you iterate over the next row, or after you close the 274 | result set, make sure to make a copy of the data first (or just use dataForColumn/dataForColumnIndex) 275 | If you don't, you're going to be in a world of hurt when you try and use the data. 276 | 277 | 2008.12.29 278 | Some changes to make Clang's static analysis happy (http://clang.llvm.org/StaticAnalysis.html). (patch provided by Matt Comi) 279 | 280 | 2008.12.14 281 | Added longLongIntForColumn: and longLongIntForColumnIndex: to FMResultSet. (patch provided by Will Cosgrove) 282 | 283 | 2008.11.20 284 | Added a check for NSNull in bindObject, which works just like passing nil does (patch provided by Robert Tolar Haining) 285 | 286 | 2008.11.02 287 | Removed the block keeping you from performing updates or selects while going through a result set. 288 | 289 | 2008.10.30 290 | Some bug fixes + warning fixes from Brian Stern (thanks again Brian!) 291 | 292 | 2008.10.03 293 | Fixed a crasher in FMResultSet's close where if the parent DB was already released, the result set would be talking to a bad address and fmdb went boom. (thanks to Brian Stern for the patch) 294 | 295 | 2008.06.06 296 | Thanks to Zach Wily for the return of FMDatabaseAdditions! 297 | 298 | 2008.06.03: 299 | Removed all exceptions, now you should use [db hadError] to check for an error. I hate (ns)exceptions, I'm not sure why I put them in. 300 | Moved to google code. 301 | Various little cleanup thingies. 302 | 303 | 2008.07.03 304 | Thanks to Kris Markel for some extra trace outputs, and a fix where the database would be locked if it was too busy. 305 | 306 | 2008.07.10 307 | Thanks to Daniel Pasco and Bil Moorehead for catching a problem where the column names lookup table was created on every row iteration. Doh! 308 | 309 | 2008.07.18 310 | FMDatabase will now cache statements if you turn it on using shouldCacheStatements:YES. In theory, this should speed things up. Test it out, let me know if it makes things good for ya. 311 | Note: This is pretty new code, so it hasn't gone through a lot of testing... you've been warned. (seems to work though!) 312 | 313 | 2008.00.01 314 | Fixed a problemo that kept it from compiling for the iPhone (Thanks to Sam Steele for the patch) 315 | 316 | 317 | 318 | questions? comments? patches? Send them to gus@flyingmeat.com 319 | -------------------------------------------------------------------------------- /ThridParty/FMDB/FMDB.h: -------------------------------------------------------------------------------- 1 | #import "FMDatabase.h" 2 | #import "FMResultSet.h" 3 | #import "FMDatabaseAdditions.h" 4 | #import "FMDatabaseQueue.h" 5 | #import "FMDatabasePool.h" 6 | -------------------------------------------------------------------------------- /ThridParty/FMDB/FMDatabaseAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditions.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 10/30/05. 6 | // Copyright 2005 Flying Meat Inc.. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FMDatabase.h" 11 | 12 | 13 | /** Category of additions for `` class. 14 | 15 | ### See also 16 | 17 | - `` 18 | */ 19 | 20 | @interface FMDatabase (FMDatabaseAdditions) 21 | 22 | ///---------------------------------------- 23 | /// @name Return results of SQL to variable 24 | ///---------------------------------------- 25 | 26 | /** Return `int` value for query 27 | 28 | @param query The SQL query to be performed. 29 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 30 | 31 | @return `int` value. 32 | */ 33 | 34 | - (int)intForQuery:(NSString*)query, ...; 35 | 36 | /** Return `long` value for query 37 | 38 | @param query The SQL query to be performed. 39 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 40 | 41 | @return `long` value. 42 | */ 43 | 44 | - (long)longForQuery:(NSString*)query, ...; 45 | 46 | /** Return `BOOL` value for query 47 | 48 | @param query The SQL query to be performed. 49 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 50 | 51 | @return `BOOL` value. 52 | */ 53 | 54 | - (BOOL)boolForQuery:(NSString*)query, ...; 55 | 56 | /** Return `double` value for query 57 | 58 | @param query The SQL query to be performed. 59 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 60 | 61 | @return `double` value. 62 | */ 63 | 64 | - (double)doubleForQuery:(NSString*)query, ...; 65 | 66 | /** Return `NSString` value for query 67 | 68 | @param query The SQL query to be performed. 69 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 70 | 71 | @return `NSString` value. 72 | */ 73 | 74 | - (NSString*)stringForQuery:(NSString*)query, ...; 75 | 76 | /** Return `NSData` value for query 77 | 78 | @param query The SQL query to be performed. 79 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 80 | 81 | @return `NSData` value. 82 | */ 83 | 84 | - (NSData*)dataForQuery:(NSString*)query, ...; 85 | 86 | /** Return `NSDate` value for query 87 | 88 | @param query The SQL query to be performed. 89 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 90 | 91 | @return `NSDate` value. 92 | */ 93 | 94 | - (NSDate*)dateForQuery:(NSString*)query, ...; 95 | 96 | 97 | // Notice that there's no dataNoCopyForQuery:. 98 | // That would be a bad idea, because we close out the result set, and then what 99 | // happens to the data that we just didn't copy? Who knows, not I. 100 | 101 | 102 | ///-------------------------------- 103 | /// @name Schema related operations 104 | ///-------------------------------- 105 | 106 | /** Does table exist in database? 107 | 108 | @param tableName The name of the table being looked for. 109 | 110 | @return `YES` if table found; `NO` if not found. 111 | */ 112 | 113 | - (BOOL)tableExists:(NSString*)tableName; 114 | 115 | /** The schema of the database. 116 | 117 | This will be the schema for the entire database. For each entity, each row of the result set will include the following fields: 118 | 119 | - `type` - The type of entity (e.g. table, index, view, or trigger) 120 | - `name` - The name of the object 121 | - `tbl_name` - The name of the table to which the object references 122 | - `rootpage` - The page number of the root b-tree page for tables and indices 123 | - `sql` - The SQL that created the entity 124 | 125 | @return `FMResultSet` of schema; `nil` on error. 126 | 127 | @see [SQLite File Format](http://www.sqlite.org/fileformat.html) 128 | */ 129 | 130 | - (FMResultSet*)getSchema; 131 | 132 | /** The schema of the database. 133 | 134 | This will be the schema for a particular table as report by SQLite `PRAGMA`, for example: 135 | 136 | PRAGMA table_info('employees') 137 | 138 | This will report: 139 | 140 | - `cid` - The column ID number 141 | - `name` - The name of the column 142 | - `type` - The data type specified for the column 143 | - `notnull` - whether the field is defined as NOT NULL (i.e. values required) 144 | - `dflt_value` - The default value for the column 145 | - `pk` - Whether the field is part of the primary key of the table 146 | 147 | @param tableName The name of the table for whom the schema will be returned. 148 | 149 | @return `FMResultSet` of schema; `nil` on error. 150 | 151 | @see [table_info](http://www.sqlite.org/pragma.html#pragma_table_info) 152 | */ 153 | 154 | - (FMResultSet*)getTableSchema:(NSString*)tableName; 155 | 156 | /** Test to see if particular column exists for particular table in database 157 | 158 | @param columnName The name of the column. 159 | 160 | @param tableName The name of the table. 161 | 162 | @return `YES` if column exists in table in question; `NO` otherwise. 163 | */ 164 | 165 | - (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName; 166 | 167 | /** Test to see if particular column exists for particular table in database 168 | 169 | @param columnName The name of the column. 170 | 171 | @param tableName The name of the table. 172 | 173 | @return `YES` if column exists in table in question; `NO` otherwise. 174 | 175 | @see columnExists:inTableWithName: 176 | 177 | @warning Deprecated - use `` instead. 178 | */ 179 | 180 | - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated)); 181 | 182 | 183 | /** Validate SQL statement 184 | 185 | This validates SQL statement by performing `sqlite3_prepare_v2`, but not returning the results, but instead immediately calling `sqlite3_finalize`. 186 | 187 | @param sql The SQL statement being validated. 188 | 189 | @param error This is a pointer to a `NSError` object that will receive the autoreleased `NSError` object if there was any error. If this is `nil`, no `NSError` result will be returned. 190 | 191 | @return `YES` if validation succeeded without incident; `NO` otherwise. 192 | 193 | */ 194 | 195 | - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error; 196 | 197 | 198 | #if SQLITE_VERSION_NUMBER >= 3007017 199 | 200 | ///----------------------------------- 201 | /// @name Application identifier tasks 202 | ///----------------------------------- 203 | 204 | /** Retrieve application ID 205 | 206 | @return The `uint32_t` numeric value of the application ID. 207 | 208 | @see setApplicationID: 209 | */ 210 | 211 | - (uint32_t)applicationID; 212 | 213 | /** Set the application ID 214 | 215 | @param appID The `uint32_t` numeric value of the application ID. 216 | 217 | @see applicationID 218 | */ 219 | 220 | - (void)setApplicationID:(uint32_t)appID; 221 | 222 | #if TARGET_OS_MAC && !TARGET_OS_IPHONE 223 | /** Retrieve application ID string 224 | 225 | @return The `NSString` value of the application ID. 226 | 227 | @see setApplicationIDString: 228 | */ 229 | 230 | 231 | - (NSString*)applicationIDString; 232 | 233 | /** Set the application ID string 234 | 235 | @param string The `NSString` value of the application ID. 236 | 237 | @see applicationIDString 238 | */ 239 | 240 | - (void)setApplicationIDString:(NSString*)string; 241 | #endif 242 | 243 | #endif 244 | 245 | ///----------------------------------- 246 | /// @name user version identifier tasks 247 | ///----------------------------------- 248 | 249 | /** Retrieve user version 250 | 251 | @return The `uint32_t` numeric value of the user version. 252 | 253 | @see setUserVersion: 254 | */ 255 | 256 | - (uint32_t)userVersion; 257 | 258 | /** Set the user-version 259 | 260 | @param version The `uint32_t` numeric value of the user version. 261 | 262 | @see userVersion 263 | */ 264 | 265 | - (void)setUserVersion:(uint32_t)version; 266 | 267 | @end 268 | -------------------------------------------------------------------------------- /ThridParty/FMDB/FMDatabaseAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditions.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 10/30/05. 6 | // Copyright 2005 Flying Meat Inc.. All rights reserved. 7 | // 8 | 9 | #import "FMDatabase.h" 10 | #import "FMDatabaseAdditions.h" 11 | #import "TargetConditionals.h" 12 | 13 | @interface FMDatabase (PrivateStuff) 14 | - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args; 15 | @end 16 | 17 | @implementation FMDatabase (FMDatabaseAdditions) 18 | 19 | #define RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(type, sel) \ 20 | va_list args; \ 21 | va_start(args, query); \ 22 | FMResultSet *resultSet = [self executeQuery:query withArgumentsInArray:0x00 orDictionary:0x00 orVAList:args]; \ 23 | va_end(args); \ 24 | if (![resultSet next]) { return (type)0; } \ 25 | type ret = [resultSet sel:0]; \ 26 | [resultSet close]; \ 27 | [resultSet setParentDB:nil]; \ 28 | return ret; 29 | 30 | 31 | - (NSString*)stringForQuery:(NSString*)query, ... { 32 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSString *, stringForColumnIndex); 33 | } 34 | 35 | - (int)intForQuery:(NSString*)query, ... { 36 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(int, intForColumnIndex); 37 | } 38 | 39 | - (long)longForQuery:(NSString*)query, ... { 40 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(long, longForColumnIndex); 41 | } 42 | 43 | - (BOOL)boolForQuery:(NSString*)query, ... { 44 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(BOOL, boolForColumnIndex); 45 | } 46 | 47 | - (double)doubleForQuery:(NSString*)query, ... { 48 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(double, doubleForColumnIndex); 49 | } 50 | 51 | - (NSData*)dataForQuery:(NSString*)query, ... { 52 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSData *, dataForColumnIndex); 53 | } 54 | 55 | - (NSDate*)dateForQuery:(NSString*)query, ... { 56 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSDate *, dateForColumnIndex); 57 | } 58 | 59 | 60 | - (BOOL)tableExists:(NSString*)tableName { 61 | 62 | tableName = [tableName lowercaseString]; 63 | 64 | FMResultSet *rs = [self executeQuery:@"select [sql] from sqlite_master where [type] = 'table' and lower(name) = ?", tableName]; 65 | 66 | //if at least one next exists, table exists 67 | BOOL returnBool = [rs next]; 68 | 69 | //close and free object 70 | [rs close]; 71 | 72 | return returnBool; 73 | } 74 | 75 | /* 76 | get table with list of tables: result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] 77 | check if table exist in database (patch from OZLB) 78 | */ 79 | - (FMResultSet*)getSchema { 80 | 81 | //result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] 82 | FMResultSet *rs = [self executeQuery:@"SELECT type, name, tbl_name, rootpage, sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type != 'meta' AND name NOT LIKE 'sqlite_%' ORDER BY tbl_name, type DESC, name"]; 83 | 84 | return rs; 85 | } 86 | 87 | /* 88 | get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] 89 | */ 90 | - (FMResultSet*)getTableSchema:(NSString*)tableName { 91 | 92 | //result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] 93 | FMResultSet *rs = [self executeQuery:[NSString stringWithFormat: @"pragma table_info('%@')", tableName]]; 94 | 95 | return rs; 96 | } 97 | 98 | - (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName { 99 | 100 | BOOL returnBool = NO; 101 | 102 | tableName = [tableName lowercaseString]; 103 | columnName = [columnName lowercaseString]; 104 | 105 | FMResultSet *rs = [self getTableSchema:tableName]; 106 | 107 | //check if column is present in table schema 108 | while ([rs next]) { 109 | if ([[[rs stringForColumn:@"name"] lowercaseString] isEqualToString:columnName]) { 110 | returnBool = YES; 111 | break; 112 | } 113 | } 114 | 115 | //If this is not done FMDatabase instance stays out of pool 116 | [rs close]; 117 | 118 | return returnBool; 119 | } 120 | 121 | 122 | #if SQLITE_VERSION_NUMBER >= 3007017 123 | 124 | - (uint32_t)applicationID { 125 | 126 | uint32_t r = 0; 127 | 128 | FMResultSet *rs = [self executeQuery:@"pragma application_id"]; 129 | 130 | if ([rs next]) { 131 | r = (uint32_t)[rs longLongIntForColumnIndex:0]; 132 | } 133 | 134 | [rs close]; 135 | 136 | return r; 137 | } 138 | 139 | - (void)setApplicationID:(uint32_t)appID { 140 | NSString *query = [NSString stringWithFormat:@"pragma application_id=%d", appID]; 141 | FMResultSet *rs = [self executeQuery:query]; 142 | [rs next]; 143 | [rs close]; 144 | } 145 | 146 | 147 | #if TARGET_OS_MAC && !TARGET_OS_IPHONE 148 | - (NSString*)applicationIDString { 149 | NSString *s = NSFileTypeForHFSTypeCode([self applicationID]); 150 | 151 | assert([s length] == 6); 152 | 153 | s = [s substringWithRange:NSMakeRange(1, 4)]; 154 | 155 | 156 | return s; 157 | 158 | } 159 | 160 | - (void)setApplicationIDString:(NSString*)s { 161 | 162 | if ([s length] != 4) { 163 | NSLog(@"setApplicationIDString: string passed is not exactly 4 chars long. (was %ld)", [s length]); 164 | } 165 | 166 | [self setApplicationID:NSHFSTypeCodeFromFileType([NSString stringWithFormat:@"'%@'", s])]; 167 | } 168 | 169 | 170 | #endif 171 | 172 | #endif 173 | 174 | - (uint32_t)userVersion { 175 | uint32_t r = 0; 176 | 177 | FMResultSet *rs = [self executeQuery:@"pragma user_version"]; 178 | 179 | if ([rs next]) { 180 | r = (uint32_t)[rs longLongIntForColumnIndex:0]; 181 | } 182 | 183 | [rs close]; 184 | return r; 185 | } 186 | 187 | - (void)setUserVersion:(uint32_t)version { 188 | NSString *query = [NSString stringWithFormat:@"pragma user_version = %d", version]; 189 | FMResultSet *rs = [self executeQuery:query]; 190 | [rs next]; 191 | [rs close]; 192 | } 193 | 194 | #pragma clang diagnostic push 195 | #pragma clang diagnostic ignored "-Wdeprecated-implementations" 196 | 197 | - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated)) { 198 | return [self columnExists:columnName inTableWithName:tableName]; 199 | } 200 | 201 | #pragma clang diagnostic pop 202 | 203 | 204 | - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error { 205 | sqlite3_stmt *pStmt = NULL; 206 | BOOL validationSucceeded = YES; 207 | 208 | int rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); 209 | if (rc != SQLITE_OK) { 210 | validationSucceeded = NO; 211 | if (error) { 212 | *error = [NSError errorWithDomain:NSCocoaErrorDomain 213 | code:[self lastErrorCode] 214 | userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage] 215 | forKey:NSLocalizedDescriptionKey]]; 216 | } 217 | } 218 | 219 | sqlite3_finalize(pStmt); 220 | 221 | return validationSucceeded; 222 | } 223 | 224 | @end 225 | -------------------------------------------------------------------------------- /ThridParty/FMDB/FMDatabasePool.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePool.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "sqlite3.h" 11 | 12 | @class FMDatabase; 13 | 14 | /** Pool of `` objects. 15 | 16 | ### See also 17 | 18 | - `` 19 | - `` 20 | 21 | @warning Before using `FMDatabasePool`, please consider using `` instead. 22 | 23 | If you really really really know what you're doing and `FMDatabasePool` is what 24 | you really really need (ie, you're using a read only database), OK you can use 25 | it. But just be careful not to deadlock! 26 | 27 | For an example on deadlocking, search for: 28 | `ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD` 29 | in the main.m file. 30 | */ 31 | 32 | @interface FMDatabasePool : NSObject { 33 | NSString *_path; 34 | 35 | dispatch_queue_t _lockQueue; 36 | 37 | NSMutableArray *_databaseInPool; 38 | NSMutableArray *_databaseOutPool; 39 | 40 | __unsafe_unretained id _delegate; 41 | 42 | NSUInteger _maximumNumberOfDatabasesToCreate; 43 | int _openFlags; 44 | } 45 | 46 | /** Database path */ 47 | 48 | @property (atomic, retain) NSString *path; 49 | 50 | /** Delegate object */ 51 | 52 | @property (atomic, assign) id delegate; 53 | 54 | /** Maximum number of databases to create */ 55 | 56 | @property (atomic, assign) NSUInteger maximumNumberOfDatabasesToCreate; 57 | 58 | /** Open flags */ 59 | 60 | @property (atomic, readonly) int openFlags; 61 | 62 | 63 | ///--------------------- 64 | /// @name Initialization 65 | ///--------------------- 66 | 67 | /** Create pool using path. 68 | 69 | @param aPath The file path of the database. 70 | 71 | @return The `FMDatabasePool` object. `nil` on error. 72 | */ 73 | 74 | + (instancetype)databasePoolWithPath:(NSString*)aPath; 75 | 76 | /** Create pool using path and specified flags 77 | 78 | @param aPath The file path of the database. 79 | @param openFlags Flags passed to the openWithFlags method of the database 80 | 81 | @return The `FMDatabasePool` object. `nil` on error. 82 | */ 83 | 84 | + (instancetype)databasePoolWithPath:(NSString*)aPath flags:(int)openFlags; 85 | 86 | /** Create pool using path. 87 | 88 | @param aPath The file path of the database. 89 | 90 | @return The `FMDatabasePool` object. `nil` on error. 91 | */ 92 | 93 | - (instancetype)initWithPath:(NSString*)aPath; 94 | 95 | /** Create pool using path and specified flags. 96 | 97 | @param aPath The file path of the database. 98 | @param openFlags Flags passed to the openWithFlags method of the database 99 | 100 | @return The `FMDatabasePool` object. `nil` on error. 101 | */ 102 | 103 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags; 104 | 105 | ///------------------------------------------------ 106 | /// @name Keeping track of checked in/out databases 107 | ///------------------------------------------------ 108 | 109 | /** Number of checked-in databases in pool 110 | 111 | @returns Number of databases 112 | */ 113 | 114 | - (NSUInteger)countOfCheckedInDatabases; 115 | 116 | /** Number of checked-out databases in pool 117 | 118 | @returns Number of databases 119 | */ 120 | 121 | - (NSUInteger)countOfCheckedOutDatabases; 122 | 123 | /** Total number of databases in pool 124 | 125 | @returns Number of databases 126 | */ 127 | 128 | - (NSUInteger)countOfOpenDatabases; 129 | 130 | /** Release all databases in pool */ 131 | 132 | - (void)releaseAllDatabases; 133 | 134 | ///------------------------------------------ 135 | /// @name Perform database operations in pool 136 | ///------------------------------------------ 137 | 138 | /** Synchronously perform database operations in pool. 139 | 140 | @param block The code to be run on the `FMDatabasePool` pool. 141 | */ 142 | 143 | - (void)inDatabase:(void (^)(FMDatabase *db))block; 144 | 145 | /** Synchronously perform database operations in pool using transaction. 146 | 147 | @param block The code to be run on the `FMDatabasePool` pool. 148 | */ 149 | 150 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 151 | 152 | /** Synchronously perform database operations in pool using deferred transaction. 153 | 154 | @param block The code to be run on the `FMDatabasePool` pool. 155 | */ 156 | 157 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 158 | 159 | #if SQLITE_VERSION_NUMBER >= 3007000 160 | 161 | /** Synchronously perform database operations in pool using save point. 162 | 163 | @param block The code to be run on the `FMDatabasePool` pool. 164 | 165 | @return `NSError` object if error; `nil` if successful. 166 | 167 | @warning You can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. If you need to nest, use `<[FMDatabase startSavePointWithName:error:]>` instead. 168 | */ 169 | 170 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; 171 | #endif 172 | 173 | @end 174 | 175 | 176 | /** FMDatabasePool delegate category 177 | 178 | This is a category that defines the protocol for the FMDatabasePool delegate 179 | */ 180 | 181 | @interface NSObject (FMDatabasePoolDelegate) 182 | 183 | /** Asks the delegate whether database should be added to the pool. 184 | 185 | @param pool The `FMDatabasePool` object. 186 | @param database The `FMDatabase` object. 187 | 188 | @return `YES` if it should add database to pool; `NO` if not. 189 | 190 | */ 191 | 192 | - (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database; 193 | 194 | /** Tells the delegate that database was added to the pool. 195 | 196 | @param pool The `FMDatabasePool` object. 197 | @param database The `FMDatabase` object. 198 | 199 | */ 200 | 201 | - (void)databasePool:(FMDatabasePool*)pool didAddDatabase:(FMDatabase*)database; 202 | 203 | @end 204 | 205 | -------------------------------------------------------------------------------- /ThridParty/FMDB/FMDatabasePool.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePool.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import "FMDatabasePool.h" 10 | #import "FMDatabase.h" 11 | 12 | @interface FMDatabasePool() 13 | 14 | - (void)pushDatabaseBackInPool:(FMDatabase*)db; 15 | - (FMDatabase*)db; 16 | 17 | @end 18 | 19 | 20 | @implementation FMDatabasePool 21 | @synthesize path=_path; 22 | @synthesize delegate=_delegate; 23 | @synthesize maximumNumberOfDatabasesToCreate=_maximumNumberOfDatabasesToCreate; 24 | @synthesize openFlags=_openFlags; 25 | 26 | 27 | + (instancetype)databasePoolWithPath:(NSString*)aPath { 28 | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]); 29 | } 30 | 31 | + (instancetype)databasePoolWithPath:(NSString*)aPath flags:(int)openFlags { 32 | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath flags:openFlags]); 33 | } 34 | 35 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags { 36 | 37 | self = [super init]; 38 | 39 | if (self != nil) { 40 | _path = [aPath copy]; 41 | _lockQueue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); 42 | _databaseInPool = FMDBReturnRetained([NSMutableArray array]); 43 | _databaseOutPool = FMDBReturnRetained([NSMutableArray array]); 44 | _openFlags = openFlags; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (instancetype)initWithPath:(NSString*)aPath 51 | { 52 | // default flags for sqlite3_open 53 | return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE]; 54 | } 55 | 56 | - (instancetype)init { 57 | return [self initWithPath:nil]; 58 | } 59 | 60 | 61 | - (void)dealloc { 62 | 63 | _delegate = 0x00; 64 | FMDBRelease(_path); 65 | FMDBRelease(_databaseInPool); 66 | FMDBRelease(_databaseOutPool); 67 | 68 | if (_lockQueue) { 69 | FMDBDispatchQueueRelease(_lockQueue); 70 | _lockQueue = 0x00; 71 | } 72 | #if ! __has_feature(objc_arc) 73 | [super dealloc]; 74 | #endif 75 | } 76 | 77 | 78 | - (void)executeLocked:(void (^)(void))aBlock { 79 | dispatch_sync(_lockQueue, aBlock); 80 | } 81 | 82 | - (void)pushDatabaseBackInPool:(FMDatabase*)db { 83 | 84 | if (!db) { // db can be null if we set an upper bound on the # of databases to create. 85 | return; 86 | } 87 | 88 | [self executeLocked:^() { 89 | 90 | if ([_databaseInPool containsObject:db]) { 91 | [[NSException exceptionWithName:@"Database already in pool" reason:@"The FMDatabase being put back into the pool is already present in the pool" userInfo:nil] raise]; 92 | } 93 | 94 | [_databaseInPool addObject:db]; 95 | [_databaseOutPool removeObject:db]; 96 | 97 | }]; 98 | } 99 | 100 | - (FMDatabase*)db { 101 | 102 | __block FMDatabase *db; 103 | 104 | 105 | [self executeLocked:^() { 106 | db = [_databaseInPool lastObject]; 107 | 108 | BOOL shouldNotifyDelegate = NO; 109 | 110 | if (db) { 111 | [_databaseOutPool addObject:db]; 112 | [_databaseInPool removeLastObject]; 113 | } 114 | else { 115 | 116 | if (_maximumNumberOfDatabasesToCreate) { 117 | NSUInteger currentCount = [_databaseOutPool count] + [_databaseInPool count]; 118 | 119 | if (currentCount >= _maximumNumberOfDatabasesToCreate) { 120 | NSLog(@"Maximum number of databases (%ld) has already been reached!", (long)currentCount); 121 | return; 122 | } 123 | } 124 | 125 | db = [FMDatabase databaseWithPath:_path]; 126 | shouldNotifyDelegate = YES; 127 | } 128 | 129 | //This ensures that the db is opened before returning 130 | #if SQLITE_VERSION_NUMBER >= 3005000 131 | BOOL success = [db openWithFlags:_openFlags]; 132 | #else 133 | BOOL success = [db open]; 134 | #endif 135 | if (success) { 136 | if ([_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![_delegate databasePool:self shouldAddDatabaseToPool:db]) { 137 | [db close]; 138 | db = 0x00; 139 | } 140 | else { 141 | //It should not get added in the pool twice if lastObject was found 142 | if (![_databaseOutPool containsObject:db]) { 143 | [_databaseOutPool addObject:db]; 144 | 145 | if (shouldNotifyDelegate && [_delegate respondsToSelector:@selector(databasePool:didAddDatabase:)]) { 146 | [_delegate databasePool:self didAddDatabase:db]; 147 | } 148 | } 149 | } 150 | } 151 | else { 152 | NSLog(@"Could not open up the database at path %@", _path); 153 | db = 0x00; 154 | } 155 | }]; 156 | 157 | return db; 158 | } 159 | 160 | - (NSUInteger)countOfCheckedInDatabases { 161 | 162 | __block NSUInteger count; 163 | 164 | [self executeLocked:^() { 165 | count = [_databaseInPool count]; 166 | }]; 167 | 168 | return count; 169 | } 170 | 171 | - (NSUInteger)countOfCheckedOutDatabases { 172 | 173 | __block NSUInteger count; 174 | 175 | [self executeLocked:^() { 176 | count = [_databaseOutPool count]; 177 | }]; 178 | 179 | return count; 180 | } 181 | 182 | - (NSUInteger)countOfOpenDatabases { 183 | __block NSUInteger count; 184 | 185 | [self executeLocked:^() { 186 | count = [_databaseOutPool count] + [_databaseInPool count]; 187 | }]; 188 | 189 | return count; 190 | } 191 | 192 | - (void)releaseAllDatabases { 193 | [self executeLocked:^() { 194 | [_databaseOutPool removeAllObjects]; 195 | [_databaseInPool removeAllObjects]; 196 | }]; 197 | } 198 | 199 | - (void)inDatabase:(void (^)(FMDatabase *db))block { 200 | 201 | FMDatabase *db = [self db]; 202 | 203 | block(db); 204 | 205 | [self pushDatabaseBackInPool:db]; 206 | } 207 | 208 | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { 209 | 210 | BOOL shouldRollback = NO; 211 | 212 | FMDatabase *db = [self db]; 213 | 214 | if (useDeferred) { 215 | [db beginDeferredTransaction]; 216 | } 217 | else { 218 | [db beginTransaction]; 219 | } 220 | 221 | 222 | block(db, &shouldRollback); 223 | 224 | if (shouldRollback) { 225 | [db rollback]; 226 | } 227 | else { 228 | [db commit]; 229 | } 230 | 231 | [self pushDatabaseBackInPool:db]; 232 | } 233 | 234 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 235 | [self beginTransaction:YES withBlock:block]; 236 | } 237 | 238 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 239 | [self beginTransaction:NO withBlock:block]; 240 | } 241 | #if SQLITE_VERSION_NUMBER >= 3007000 242 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { 243 | 244 | static unsigned long savePointIdx = 0; 245 | 246 | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; 247 | 248 | BOOL shouldRollback = NO; 249 | 250 | FMDatabase *db = [self db]; 251 | 252 | NSError *err = 0x00; 253 | 254 | if (![db startSavePointWithName:name error:&err]) { 255 | [self pushDatabaseBackInPool:db]; 256 | return err; 257 | } 258 | 259 | block(db, &shouldRollback); 260 | 261 | if (shouldRollback) { 262 | // We need to rollback and release this savepoint to remove it 263 | [db rollbackToSavePointWithName:name error:&err]; 264 | } 265 | [db releaseSavePointWithName:name error:&err]; 266 | 267 | [self pushDatabaseBackInPool:db]; 268 | 269 | return err; 270 | } 271 | #endif 272 | 273 | @end 274 | -------------------------------------------------------------------------------- /ThridParty/FMDB/FMDatabaseQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseQueue.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "sqlite3.h" 11 | 12 | @class FMDatabase; 13 | 14 | /** To perform queries and updates on multiple threads, you'll want to use `FMDatabaseQueue`. 15 | 16 | Using a single instance of `` from multiple threads at once is a bad idea. It has always been OK to make a `` object *per thread*. Just don't share a single instance across threads, and definitely not across multiple threads at the same time. 17 | 18 | Instead, use `FMDatabaseQueue`. Here's how to use it: 19 | 20 | First, make your queue. 21 | 22 | FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath]; 23 | 24 | Then use it like so: 25 | 26 | [queue inDatabase:^(FMDatabase *db) { 27 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; 28 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; 29 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; 30 | 31 | FMResultSet *rs = [db executeQuery:@"select * from foo"]; 32 | while ([rs next]) { 33 | //… 34 | } 35 | }]; 36 | 37 | An easy way to wrap things up in a transaction can be done like this: 38 | 39 | [queue inTransaction:^(FMDatabase *db, BOOL *rollback) { 40 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; 41 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; 42 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; 43 | 44 | if (whoopsSomethingWrongHappened) { 45 | *rollback = YES; 46 | return; 47 | } 48 | // etc… 49 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]]; 50 | }]; 51 | 52 | `FMDatabaseQueue` will run the blocks on a serialized queue (hence the name of the class). So if you call `FMDatabaseQueue`'s methods from multiple threads at the same time, they will be executed in the order they are received. This way queries and updates won't step on each other's toes, and every one is happy. 53 | 54 | ### See also 55 | 56 | - `` 57 | 58 | @warning Do not instantiate a single `` object and use it across multiple threads. Use `FMDatabaseQueue` instead. 59 | 60 | @warning The calls to `FMDatabaseQueue`'s methods are blocking. So even though you are passing along blocks, they will **not** be run on another thread. 61 | 62 | */ 63 | 64 | @interface FMDatabaseQueue : NSObject { 65 | NSString *_path; 66 | dispatch_queue_t _queue; 67 | FMDatabase *_db; 68 | int _openFlags; 69 | } 70 | 71 | /** Path of database */ 72 | 73 | @property (atomic, retain) NSString *path; 74 | 75 | /** Open flags */ 76 | 77 | @property (atomic, readonly) int openFlags; 78 | 79 | ///---------------------------------------------------- 80 | /// @name Initialization, opening, and closing of queue 81 | ///---------------------------------------------------- 82 | 83 | /** Create queue using path. 84 | 85 | @param aPath The file path of the database. 86 | 87 | @return The `FMDatabaseQueue` object. `nil` on error. 88 | */ 89 | 90 | + (instancetype)databaseQueueWithPath:(NSString*)aPath; 91 | 92 | /** Create queue using path and specified flags. 93 | 94 | @param aPath The file path of the database. 95 | @param openFlags Flags passed to the openWithFlags method of the database 96 | 97 | @return The `FMDatabaseQueue` object. `nil` on error. 98 | */ 99 | + (instancetype)databaseQueueWithPath:(NSString*)aPath flags:(int)openFlags; 100 | 101 | /** Create queue using path. 102 | 103 | @param aPath The file path of the database. 104 | 105 | @return The `FMDatabaseQueue` object. `nil` on error. 106 | */ 107 | 108 | - (instancetype)initWithPath:(NSString*)aPath; 109 | 110 | /** Create queue using path and specified flags. 111 | 112 | @param aPath The file path of the database. 113 | @param openFlags Flags passed to the openWithFlags method of the database 114 | 115 | @return The `FMDatabaseQueue` object. `nil` on error. 116 | */ 117 | 118 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags; 119 | 120 | /** Returns the Class of 'FMDatabase' subclass, that will be used to instantiate database object. 121 | 122 | Subclasses can override this method to return specified Class of 'FMDatabase' subclass. 123 | 124 | @return The Class of 'FMDatabase' subclass, that will be used to instantiate database object. 125 | */ 126 | 127 | + (Class)databaseClass; 128 | 129 | /** Close database used by queue. */ 130 | 131 | - (void)close; 132 | 133 | ///----------------------------------------------- 134 | /// @name Dispatching database operations to queue 135 | ///----------------------------------------------- 136 | 137 | /** Synchronously perform database operations on queue. 138 | 139 | @param block The code to be run on the queue of `FMDatabaseQueue` 140 | */ 141 | 142 | - (void)inDatabase:(void (^)(FMDatabase *db))block; 143 | 144 | /** Synchronously perform database operations on queue, using transactions. 145 | 146 | @param block The code to be run on the queue of `FMDatabaseQueue` 147 | */ 148 | 149 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 150 | 151 | /** Synchronously perform database operations on queue, using deferred transactions. 152 | 153 | @param block The code to be run on the queue of `FMDatabaseQueue` 154 | */ 155 | 156 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 157 | 158 | ///----------------------------------------------- 159 | /// @name Dispatching database operations to queue 160 | ///----------------------------------------------- 161 | 162 | /** Synchronously perform database operations using save point. 163 | 164 | @param block The code to be run on the queue of `FMDatabaseQueue` 165 | */ 166 | 167 | #if SQLITE_VERSION_NUMBER >= 3007000 168 | // NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. 169 | // If you need to nest, use FMDatabase's startSavePointWithName:error: instead. 170 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; 171 | #endif 172 | 173 | @end 174 | 175 | -------------------------------------------------------------------------------- /ThridParty/FMDB/FMDatabaseQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseQueue.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import "FMDatabaseQueue.h" 10 | #import "FMDatabase.h" 11 | 12 | /* 13 | 14 | Note: we call [self retain]; before using dispatch_sync, just incase 15 | FMDatabaseQueue is released on another thread and we're in the middle of doing 16 | something in dispatch_sync 17 | 18 | */ 19 | 20 | /* 21 | * A key used to associate the FMDatabaseQueue object with the dispatch_queue_t it uses. 22 | * This in turn is used for deadlock detection by seeing if inDatabase: is called on 23 | * the queue's dispatch queue, which should not happen and causes a deadlock. 24 | */ 25 | static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey; 26 | 27 | @implementation FMDatabaseQueue 28 | 29 | @synthesize path = _path; 30 | @synthesize openFlags = _openFlags; 31 | 32 | + (instancetype)databaseQueueWithPath:(NSString*)aPath { 33 | 34 | FMDatabaseQueue *q = [[self alloc] initWithPath:aPath]; 35 | 36 | FMDBAutorelease(q); 37 | 38 | return q; 39 | } 40 | 41 | + (instancetype)databaseQueueWithPath:(NSString*)aPath flags:(int)openFlags { 42 | 43 | FMDatabaseQueue *q = [[self alloc] initWithPath:aPath flags:openFlags]; 44 | 45 | FMDBAutorelease(q); 46 | 47 | return q; 48 | } 49 | 50 | + (Class)databaseClass { 51 | return [FMDatabase class]; 52 | } 53 | 54 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags { 55 | 56 | self = [super init]; 57 | 58 | if (self != nil) { 59 | 60 | _db = [[[self class] databaseClass] databaseWithPath:aPath]; 61 | FMDBRetain(_db); 62 | 63 | #if SQLITE_VERSION_NUMBER >= 3005000 64 | BOOL success = [_db openWithFlags:openFlags]; 65 | #else 66 | BOOL success = [_db open]; 67 | #endif 68 | if (!success) { 69 | NSLog(@"Could not create database queue for path %@", aPath); 70 | FMDBRelease(self); 71 | return 0x00; 72 | } 73 | 74 | _path = FMDBReturnRetained(aPath); 75 | 76 | _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); 77 | dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL); 78 | _openFlags = openFlags; 79 | } 80 | 81 | return self; 82 | } 83 | 84 | - (instancetype)initWithPath:(NSString*)aPath { 85 | 86 | // default flags for sqlite3_open 87 | return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE]; 88 | } 89 | 90 | - (instancetype)init { 91 | return [self initWithPath:nil]; 92 | } 93 | 94 | 95 | - (void)dealloc { 96 | 97 | FMDBRelease(_db); 98 | FMDBRelease(_path); 99 | 100 | if (_queue) { 101 | FMDBDispatchQueueRelease(_queue); 102 | _queue = 0x00; 103 | } 104 | #if ! __has_feature(objc_arc) 105 | [super dealloc]; 106 | #endif 107 | } 108 | 109 | - (void)close { 110 | FMDBRetain(self); 111 | dispatch_sync(_queue, ^() { 112 | [_db close]; 113 | FMDBRelease(_db); 114 | _db = 0x00; 115 | }); 116 | FMDBRelease(self); 117 | } 118 | 119 | - (FMDatabase*)database { 120 | if (!_db) { 121 | _db = FMDBReturnRetained([FMDatabase databaseWithPath:_path]); 122 | 123 | #if SQLITE_VERSION_NUMBER >= 3005000 124 | BOOL success = [_db openWithFlags:_openFlags]; 125 | #else 126 | BOOL success = [db open]; 127 | #endif 128 | if (!success) { 129 | NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path); 130 | FMDBRelease(_db); 131 | _db = 0x00; 132 | return 0x00; 133 | } 134 | } 135 | 136 | return _db; 137 | } 138 | 139 | - (void)inDatabase:(void (^)(FMDatabase *db))block { 140 | /* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue 141 | * and then check it against self to make sure we're not about to deadlock. */ 142 | FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); 143 | assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock"); 144 | 145 | FMDBRetain(self); 146 | 147 | dispatch_sync(_queue, ^() { 148 | 149 | FMDatabase *db = [self database]; 150 | block(db); 151 | 152 | if ([db hasOpenResultSets]) { 153 | NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]"); 154 | 155 | #ifdef DEBUG 156 | NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]); 157 | for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) { 158 | FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue]; 159 | NSLog(@"query: '%@'", [rs query]); 160 | } 161 | #endif 162 | } 163 | }); 164 | 165 | FMDBRelease(self); 166 | } 167 | 168 | 169 | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { 170 | FMDBRetain(self); 171 | dispatch_sync(_queue, ^() { 172 | 173 | BOOL shouldRollback = NO; 174 | 175 | if (useDeferred) { 176 | [[self database] beginDeferredTransaction]; 177 | } 178 | else { 179 | [[self database] beginTransaction]; 180 | } 181 | 182 | block([self database], &shouldRollback); 183 | 184 | if (shouldRollback) { 185 | [[self database] rollback]; 186 | } 187 | else { 188 | [[self database] commit]; 189 | } 190 | }); 191 | 192 | FMDBRelease(self); 193 | } 194 | 195 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 196 | [self beginTransaction:YES withBlock:block]; 197 | } 198 | 199 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 200 | [self beginTransaction:NO withBlock:block]; 201 | } 202 | 203 | #if SQLITE_VERSION_NUMBER >= 3007000 204 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { 205 | 206 | static unsigned long savePointIdx = 0; 207 | __block NSError *err = 0x00; 208 | FMDBRetain(self); 209 | dispatch_sync(_queue, ^() { 210 | 211 | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; 212 | 213 | BOOL shouldRollback = NO; 214 | 215 | if ([[self database] startSavePointWithName:name error:&err]) { 216 | 217 | block([self database], &shouldRollback); 218 | 219 | if (shouldRollback) { 220 | // We need to rollback and release this savepoint to remove it 221 | [[self database] rollbackToSavePointWithName:name error:&err]; 222 | } 223 | [[self database] releaseSavePointWithName:name error:&err]; 224 | 225 | } 226 | }); 227 | FMDBRelease(self); 228 | return err; 229 | } 230 | #endif 231 | 232 | @end 233 | -------------------------------------------------------------------------------- /ThridParty/FMDB/FMResultSet.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "sqlite3.h" 3 | 4 | #ifndef __has_feature // Optional. 5 | #define __has_feature(x) 0 // Compatibility with non-clang compilers. 6 | #endif 7 | 8 | #ifndef NS_RETURNS_NOT_RETAINED 9 | #if __has_feature(attribute_ns_returns_not_retained) 10 | #define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained)) 11 | #else 12 | #define NS_RETURNS_NOT_RETAINED 13 | #endif 14 | #endif 15 | 16 | @class FMDatabase; 17 | @class FMStatement; 18 | 19 | /** Represents the results of executing a query on an ``. 20 | 21 | ### See also 22 | 23 | - `` 24 | */ 25 | 26 | @interface FMResultSet : NSObject { 27 | FMDatabase *_parentDB; 28 | FMStatement *_statement; 29 | 30 | NSString *_query; 31 | NSMutableDictionary *_columnNameToIndexMap; 32 | } 33 | 34 | ///----------------- 35 | /// @name Properties 36 | ///----------------- 37 | 38 | /** Executed query */ 39 | 40 | @property (atomic, retain) NSString *query; 41 | 42 | /** `NSMutableDictionary` mapping column names to numeric index */ 43 | 44 | @property (readonly) NSMutableDictionary *columnNameToIndexMap; 45 | 46 | /** `FMStatement` used by result set. */ 47 | 48 | @property (atomic, retain) FMStatement *statement; 49 | 50 | ///------------------------------------ 51 | /// @name Creating and closing database 52 | ///------------------------------------ 53 | 54 | /** Create result set from `` 55 | 56 | @param statement A `` to be performed 57 | 58 | @param aDB A `` to be used 59 | 60 | @return A `FMResultSet` on success; `nil` on failure 61 | */ 62 | 63 | + (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB; 64 | 65 | /** Close result set */ 66 | 67 | - (void)close; 68 | 69 | - (void)setParentDB:(FMDatabase *)newDb; 70 | 71 | ///--------------------------------------- 72 | /// @name Iterating through the result set 73 | ///--------------------------------------- 74 | 75 | /** Retrieve next row for result set. 76 | 77 | You must always invoke `next` before attempting to access the values returned in a query, even if you're only expecting one. 78 | 79 | @return `YES` if row successfully retrieved; `NO` if end of result set reached 80 | 81 | @see hasAnotherRow 82 | */ 83 | 84 | - (BOOL)next; 85 | 86 | /** Did the last call to `` succeed in retrieving another row? 87 | 88 | @return `YES` if the last call to `` succeeded in retrieving another record; `NO` if not. 89 | 90 | @see next 91 | 92 | @warning The `hasAnotherRow` method must follow a call to ``. If the previous database interaction was something other than a call to `next`, then this method may return `NO`, whether there is another row of data or not. 93 | */ 94 | 95 | - (BOOL)hasAnotherRow; 96 | 97 | ///--------------------------------------------- 98 | /// @name Retrieving information from result set 99 | ///--------------------------------------------- 100 | 101 | /** How many columns in result set 102 | 103 | @return Integer value of the number of columns. 104 | */ 105 | 106 | - (int)columnCount; 107 | 108 | /** Column index for column name 109 | 110 | @param columnName `NSString` value of the name of the column. 111 | 112 | @return Zero-based index for column. 113 | */ 114 | 115 | - (int)columnIndexForName:(NSString*)columnName; 116 | 117 | /** Column name for column index 118 | 119 | @param columnIdx Zero-based index for column. 120 | 121 | @return columnName `NSString` value of the name of the column. 122 | */ 123 | 124 | - (NSString*)columnNameForIndex:(int)columnIdx; 125 | 126 | /** Result set integer value for column. 127 | 128 | @param columnName `NSString` value of the name of the column. 129 | 130 | @return `int` value of the result set's column. 131 | */ 132 | 133 | - (int)intForColumn:(NSString*)columnName; 134 | 135 | /** Result set integer value for column. 136 | 137 | @param columnIdx Zero-based index for column. 138 | 139 | @return `int` value of the result set's column. 140 | */ 141 | 142 | - (int)intForColumnIndex:(int)columnIdx; 143 | 144 | /** Result set `long` value for column. 145 | 146 | @param columnName `NSString` value of the name of the column. 147 | 148 | @return `long` value of the result set's column. 149 | */ 150 | 151 | - (long)longForColumn:(NSString*)columnName; 152 | 153 | /** Result set long value for column. 154 | 155 | @param columnIdx Zero-based index for column. 156 | 157 | @return `long` value of the result set's column. 158 | */ 159 | 160 | - (long)longForColumnIndex:(int)columnIdx; 161 | 162 | /** Result set `long long int` value for column. 163 | 164 | @param columnName `NSString` value of the name of the column. 165 | 166 | @return `long long int` value of the result set's column. 167 | */ 168 | 169 | - (long long int)longLongIntForColumn:(NSString*)columnName; 170 | 171 | /** Result set `long long int` value for column. 172 | 173 | @param columnIdx Zero-based index for column. 174 | 175 | @return `long long int` value of the result set's column. 176 | */ 177 | 178 | - (long long int)longLongIntForColumnIndex:(int)columnIdx; 179 | 180 | /** Result set `unsigned long long int` value for column. 181 | 182 | @param columnName `NSString` value of the name of the column. 183 | 184 | @return `unsigned long long int` value of the result set's column. 185 | */ 186 | 187 | - (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName; 188 | 189 | /** Result set `unsigned long long int` value for column. 190 | 191 | @param columnIdx Zero-based index for column. 192 | 193 | @return `unsigned long long int` value of the result set's column. 194 | */ 195 | 196 | - (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx; 197 | 198 | /** Result set `BOOL` value for column. 199 | 200 | @param columnName `NSString` value of the name of the column. 201 | 202 | @return `BOOL` value of the result set's column. 203 | */ 204 | 205 | - (BOOL)boolForColumn:(NSString*)columnName; 206 | 207 | /** Result set `BOOL` value for column. 208 | 209 | @param columnIdx Zero-based index for column. 210 | 211 | @return `BOOL` value of the result set's column. 212 | */ 213 | 214 | - (BOOL)boolForColumnIndex:(int)columnIdx; 215 | 216 | /** Result set `double` value for column. 217 | 218 | @param columnName `NSString` value of the name of the column. 219 | 220 | @return `double` value of the result set's column. 221 | 222 | */ 223 | 224 | - (double)doubleForColumn:(NSString*)columnName; 225 | 226 | /** Result set `double` value for column. 227 | 228 | @param columnIdx Zero-based index for column. 229 | 230 | @return `double` value of the result set's column. 231 | 232 | */ 233 | 234 | - (double)doubleForColumnIndex:(int)columnIdx; 235 | 236 | /** Result set `NSString` value for column. 237 | 238 | @param columnName `NSString` value of the name of the column. 239 | 240 | @return `NSString` value of the result set's column. 241 | 242 | */ 243 | 244 | - (NSString*)stringForColumn:(NSString*)columnName; 245 | 246 | /** Result set `NSString` value for column. 247 | 248 | @param columnIdx Zero-based index for column. 249 | 250 | @return `NSString` value of the result set's column. 251 | */ 252 | 253 | - (NSString*)stringForColumnIndex:(int)columnIdx; 254 | 255 | /** Result set `NSDate` value for column. 256 | 257 | @param columnName `NSString` value of the name of the column. 258 | 259 | @return `NSDate` value of the result set's column. 260 | */ 261 | 262 | - (NSDate*)dateForColumn:(NSString*)columnName; 263 | 264 | /** Result set `NSDate` value for column. 265 | 266 | @param columnIdx Zero-based index for column. 267 | 268 | @return `NSDate` value of the result set's column. 269 | 270 | */ 271 | 272 | - (NSDate*)dateForColumnIndex:(int)columnIdx; 273 | 274 | /** Result set `NSData` value for column. 275 | 276 | This is useful when storing binary data in table (such as image or the like). 277 | 278 | @param columnName `NSString` value of the name of the column. 279 | 280 | @return `NSData` value of the result set's column. 281 | 282 | */ 283 | 284 | - (NSData*)dataForColumn:(NSString*)columnName; 285 | 286 | /** Result set `NSData` value for column. 287 | 288 | @param columnIdx Zero-based index for column. 289 | 290 | @return `NSData` value of the result set's column. 291 | */ 292 | 293 | - (NSData*)dataForColumnIndex:(int)columnIdx; 294 | 295 | /** Result set `(const unsigned char *)` value for column. 296 | 297 | @param columnName `NSString` value of the name of the column. 298 | 299 | @return `(const unsigned char *)` value of the result set's column. 300 | */ 301 | 302 | - (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName; 303 | 304 | /** Result set `(const unsigned char *)` value for column. 305 | 306 | @param columnIdx Zero-based index for column. 307 | 308 | @return `(const unsigned char *)` value of the result set's column. 309 | */ 310 | 311 | - (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx; 312 | 313 | /** Result set object for column. 314 | 315 | @param columnName `NSString` value of the name of the column. 316 | 317 | @return Either `NSNumber`, `NSString`, `NSData`, or `NSNull`. If the column was `NULL`, this returns `[NSNull null]` object. 318 | 319 | @see objectForKeyedSubscript: 320 | */ 321 | 322 | - (id)objectForColumnName:(NSString*)columnName; 323 | 324 | /** Result set object for column. 325 | 326 | @param columnIdx Zero-based index for column. 327 | 328 | @return Either `NSNumber`, `NSString`, `NSData`, or `NSNull`. If the column was `NULL`, this returns `[NSNull null]` object. 329 | 330 | @see objectAtIndexedSubscript: 331 | */ 332 | 333 | - (id)objectForColumnIndex:(int)columnIdx; 334 | 335 | /** Result set object for column. 336 | 337 | This method allows the use of the "boxed" syntax supported in Modern Objective-C. For example, by defining this method, the following syntax is now supported: 338 | 339 | id result = rs[@"employee_name"]; 340 | 341 | This simplified syntax is equivalent to calling: 342 | 343 | id result = [rs objectForKeyedSubscript:@"employee_name"]; 344 | 345 | which is, it turns out, equivalent to calling: 346 | 347 | id result = [rs objectForColumnName:@"employee_name"]; 348 | 349 | @param columnName `NSString` value of the name of the column. 350 | 351 | @return Either `NSNumber`, `NSString`, `NSData`, or `NSNull`. If the column was `NULL`, this returns `[NSNull null]` object. 352 | */ 353 | 354 | - (id)objectForKeyedSubscript:(NSString *)columnName; 355 | 356 | /** Result set object for column. 357 | 358 | This method allows the use of the "boxed" syntax supported in Modern Objective-C. For example, by defining this method, the following syntax is now supported: 359 | 360 | id result = rs[0]; 361 | 362 | This simplified syntax is equivalent to calling: 363 | 364 | id result = [rs objectForKeyedSubscript:0]; 365 | 366 | which is, it turns out, equivalent to calling: 367 | 368 | id result = [rs objectForColumnName:0]; 369 | 370 | @param columnIdx Zero-based index for column. 371 | 372 | @return Either `NSNumber`, `NSString`, `NSData`, or `NSNull`. If the column was `NULL`, this returns `[NSNull null]` object. 373 | */ 374 | 375 | - (id)objectAtIndexedSubscript:(int)columnIdx; 376 | 377 | /** Result set `NSData` value for column. 378 | 379 | @param columnName `NSString` value of the name of the column. 380 | 381 | @return `NSData` value of the result set's column. 382 | 383 | @warning If you are going to use this data after you iterate over the next row, or after you close the 384 | result set, make sure to make a copy of the data first (or just use ``/``) 385 | If you don't, you're going to be in a world of hurt when you try and use the data. 386 | 387 | */ 388 | 389 | - (NSData*)dataNoCopyForColumn:(NSString*)columnName NS_RETURNS_NOT_RETAINED; 390 | 391 | /** Result set `NSData` value for column. 392 | 393 | @param columnIdx Zero-based index for column. 394 | 395 | @return `NSData` value of the result set's column. 396 | 397 | @warning If you are going to use this data after you iterate over the next row, or after you close the 398 | result set, make sure to make a copy of the data first (or just use ``/``) 399 | If you don't, you're going to be in a world of hurt when you try and use the data. 400 | 401 | */ 402 | 403 | - (NSData*)dataNoCopyForColumnIndex:(int)columnIdx NS_RETURNS_NOT_RETAINED; 404 | 405 | /** Is the column `NULL`? 406 | 407 | @param columnIdx Zero-based index for column. 408 | 409 | @return `YES` if column is `NULL`; `NO` if not `NULL`. 410 | */ 411 | 412 | - (BOOL)columnIndexIsNull:(int)columnIdx; 413 | 414 | /** Is the column `NULL`? 415 | 416 | @param columnName `NSString` value of the name of the column. 417 | 418 | @return `YES` if column is `NULL`; `NO` if not `NULL`. 419 | */ 420 | 421 | - (BOOL)columnIsNull:(NSString*)columnName; 422 | 423 | 424 | /** Returns a dictionary of the row results mapped to case sensitive keys of the column names. 425 | 426 | @returns `NSDictionary` of the row results. 427 | 428 | @warning The keys to the dictionary are case sensitive of the column names. 429 | */ 430 | 431 | - (NSDictionary*)resultDictionary; 432 | 433 | /** Returns a dictionary of the row results 434 | 435 | @see resultDictionary 436 | 437 | @warning **Deprecated**: Please use `` instead. Also, beware that `` is case sensitive! 438 | */ 439 | 440 | - (NSDictionary*)resultDict __attribute__ ((deprecated)); 441 | 442 | ///----------------------------- 443 | /// @name Key value coding magic 444 | ///----------------------------- 445 | 446 | /** Performs `setValue` to yield support for key value observing. 447 | 448 | @param object The object for which the values will be set. This is the key-value-coding compliant object that you might, for example, observe. 449 | 450 | */ 451 | 452 | - (void)kvcMagic:(id)object; 453 | 454 | 455 | @end 456 | 457 | -------------------------------------------------------------------------------- /ThridParty/FMDB/FMResultSet.m: -------------------------------------------------------------------------------- 1 | #import "FMResultSet.h" 2 | #import "FMDatabase.h" 3 | #import "unistd.h" 4 | 5 | @interface FMDatabase () 6 | - (void)resultSetDidClose:(FMResultSet *)resultSet; 7 | @end 8 | 9 | 10 | @implementation FMResultSet 11 | @synthesize query=_query; 12 | @synthesize statement=_statement; 13 | 14 | + (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB { 15 | 16 | FMResultSet *rs = [[FMResultSet alloc] init]; 17 | 18 | [rs setStatement:statement]; 19 | [rs setParentDB:aDB]; 20 | 21 | NSParameterAssert(![statement inUse]); 22 | [statement setInUse:YES]; // weak reference 23 | 24 | return FMDBReturnAutoreleased(rs); 25 | } 26 | 27 | - (void)finalize { 28 | [self close]; 29 | [super finalize]; 30 | } 31 | 32 | - (void)dealloc { 33 | [self close]; 34 | 35 | FMDBRelease(_query); 36 | _query = nil; 37 | 38 | FMDBRelease(_columnNameToIndexMap); 39 | _columnNameToIndexMap = nil; 40 | 41 | #if ! __has_feature(objc_arc) 42 | [super dealloc]; 43 | #endif 44 | } 45 | 46 | - (void)close { 47 | [_statement reset]; 48 | FMDBRelease(_statement); 49 | _statement = nil; 50 | 51 | // we don't need this anymore... (i think) 52 | //[_parentDB setInUse:NO]; 53 | [_parentDB resultSetDidClose:self]; 54 | _parentDB = nil; 55 | } 56 | 57 | - (int)columnCount { 58 | return sqlite3_column_count([_statement statement]); 59 | } 60 | 61 | - (NSMutableDictionary *)columnNameToIndexMap { 62 | if (!_columnNameToIndexMap) { 63 | int columnCount = sqlite3_column_count([_statement statement]); 64 | _columnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:(NSUInteger)columnCount]; 65 | int columnIdx = 0; 66 | for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { 67 | [_columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx] 68 | forKey:[[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)] lowercaseString]]; 69 | } 70 | } 71 | return _columnNameToIndexMap; 72 | } 73 | 74 | - (void)kvcMagic:(id)object { 75 | 76 | int columnCount = sqlite3_column_count([_statement statement]); 77 | 78 | int columnIdx = 0; 79 | for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { 80 | 81 | const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx); 82 | 83 | // check for a null row 84 | if (c) { 85 | NSString *s = [NSString stringWithUTF8String:c]; 86 | 87 | [object setValue:s forKey:[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]]; 88 | } 89 | } 90 | } 91 | 92 | #pragma clang diagnostic push 93 | #pragma clang diagnostic ignored "-Wdeprecated-implementations" 94 | 95 | - (NSDictionary*)resultDict { 96 | 97 | NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]); 98 | 99 | if (num_cols > 0) { 100 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; 101 | 102 | NSEnumerator *columnNames = [[self columnNameToIndexMap] keyEnumerator]; 103 | NSString *columnName = nil; 104 | while ((columnName = [columnNames nextObject])) { 105 | id objectValue = [self objectForColumnName:columnName]; 106 | [dict setObject:objectValue forKey:columnName]; 107 | } 108 | 109 | return FMDBReturnAutoreleased([dict copy]); 110 | } 111 | else { 112 | NSLog(@"Warning: There seem to be no columns in this set."); 113 | } 114 | 115 | return nil; 116 | } 117 | 118 | #pragma clang diagnostic pop 119 | 120 | - (NSDictionary*)resultDictionary { 121 | 122 | NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]); 123 | 124 | if (num_cols > 0) { 125 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; 126 | 127 | int columnCount = sqlite3_column_count([_statement statement]); 128 | 129 | int columnIdx = 0; 130 | for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { 131 | 132 | NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]; 133 | id objectValue = [self objectForColumnIndex:columnIdx]; 134 | [dict setObject:objectValue forKey:columnName]; 135 | } 136 | 137 | return dict; 138 | } 139 | else { 140 | NSLog(@"Warning: There seem to be no columns in this set."); 141 | } 142 | 143 | return nil; 144 | } 145 | 146 | 147 | 148 | 149 | - (BOOL)next { 150 | 151 | int rc = sqlite3_step([_statement statement]); 152 | 153 | if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { 154 | NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]); 155 | NSLog(@"Database busy"); 156 | } 157 | else if (SQLITE_DONE == rc || SQLITE_ROW == rc) { 158 | // all is well, let's return. 159 | } 160 | else if (SQLITE_ERROR == rc) { 161 | NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); 162 | } 163 | else if (SQLITE_MISUSE == rc) { 164 | // uh oh. 165 | NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); 166 | } 167 | else { 168 | // wtf? 169 | NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); 170 | } 171 | 172 | 173 | if (rc != SQLITE_ROW) { 174 | [self close]; 175 | } 176 | 177 | return (rc == SQLITE_ROW); 178 | } 179 | 180 | - (BOOL)hasAnotherRow { 181 | return sqlite3_errcode([_parentDB sqliteHandle]) == SQLITE_ROW; 182 | } 183 | 184 | - (int)columnIndexForName:(NSString*)columnName { 185 | columnName = [columnName lowercaseString]; 186 | 187 | NSNumber *n = [[self columnNameToIndexMap] objectForKey:columnName]; 188 | 189 | if (n) { 190 | return [n intValue]; 191 | } 192 | 193 | NSLog(@"Warning: I could not find the column named '%@'.", columnName); 194 | 195 | return -1; 196 | } 197 | 198 | 199 | 200 | - (int)intForColumn:(NSString*)columnName { 201 | return [self intForColumnIndex:[self columnIndexForName:columnName]]; 202 | } 203 | 204 | - (int)intForColumnIndex:(int)columnIdx { 205 | return sqlite3_column_int([_statement statement], columnIdx); 206 | } 207 | 208 | - (long)longForColumn:(NSString*)columnName { 209 | return [self longForColumnIndex:[self columnIndexForName:columnName]]; 210 | } 211 | 212 | - (long)longForColumnIndex:(int)columnIdx { 213 | return (long)sqlite3_column_int64([_statement statement], columnIdx); 214 | } 215 | 216 | - (long long int)longLongIntForColumn:(NSString*)columnName { 217 | return [self longLongIntForColumnIndex:[self columnIndexForName:columnName]]; 218 | } 219 | 220 | - (long long int)longLongIntForColumnIndex:(int)columnIdx { 221 | return sqlite3_column_int64([_statement statement], columnIdx); 222 | } 223 | 224 | - (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName { 225 | return [self unsignedLongLongIntForColumnIndex:[self columnIndexForName:columnName]]; 226 | } 227 | 228 | - (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx { 229 | return (unsigned long long int)[self longLongIntForColumnIndex:columnIdx]; 230 | } 231 | 232 | - (BOOL)boolForColumn:(NSString*)columnName { 233 | return [self boolForColumnIndex:[self columnIndexForName:columnName]]; 234 | } 235 | 236 | - (BOOL)boolForColumnIndex:(int)columnIdx { 237 | return ([self intForColumnIndex:columnIdx] != 0); 238 | } 239 | 240 | - (double)doubleForColumn:(NSString*)columnName { 241 | return [self doubleForColumnIndex:[self columnIndexForName:columnName]]; 242 | } 243 | 244 | - (double)doubleForColumnIndex:(int)columnIdx { 245 | return sqlite3_column_double([_statement statement], columnIdx); 246 | } 247 | 248 | - (NSString*)stringForColumnIndex:(int)columnIdx { 249 | 250 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 251 | return nil; 252 | } 253 | 254 | const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx); 255 | 256 | if (!c) { 257 | // null row. 258 | return nil; 259 | } 260 | 261 | return [NSString stringWithUTF8String:c]; 262 | } 263 | 264 | - (NSString*)stringForColumn:(NSString*)columnName { 265 | return [self stringForColumnIndex:[self columnIndexForName:columnName]]; 266 | } 267 | 268 | - (NSDate*)dateForColumn:(NSString*)columnName { 269 | return [self dateForColumnIndex:[self columnIndexForName:columnName]]; 270 | } 271 | 272 | - (NSDate*)dateForColumnIndex:(int)columnIdx { 273 | 274 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 275 | return nil; 276 | } 277 | 278 | return [_parentDB hasDateFormatter] ? [_parentDB dateFromString:[self stringForColumnIndex:columnIdx]] : [NSDate dateWithTimeIntervalSince1970:[self doubleForColumnIndex:columnIdx]]; 279 | } 280 | 281 | 282 | - (NSData*)dataForColumn:(NSString*)columnName { 283 | return [self dataForColumnIndex:[self columnIndexForName:columnName]]; 284 | } 285 | 286 | - (NSData*)dataForColumnIndex:(int)columnIdx { 287 | 288 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 289 | return nil; 290 | } 291 | 292 | int dataSize = sqlite3_column_bytes([_statement statement], columnIdx); 293 | const char *dataBuffer = sqlite3_column_blob([_statement statement], columnIdx); 294 | 295 | if (dataBuffer == NULL) { 296 | return nil; 297 | } 298 | 299 | return [NSData dataWithBytes:(const void *)dataBuffer length:(NSUInteger)dataSize]; 300 | } 301 | 302 | 303 | - (NSData*)dataNoCopyForColumn:(NSString*)columnName { 304 | return [self dataNoCopyForColumnIndex:[self columnIndexForName:columnName]]; 305 | } 306 | 307 | - (NSData*)dataNoCopyForColumnIndex:(int)columnIdx { 308 | 309 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 310 | return nil; 311 | } 312 | 313 | int dataSize = sqlite3_column_bytes([_statement statement], columnIdx); 314 | 315 | NSData *data = [NSData dataWithBytesNoCopy:(void *)sqlite3_column_blob([_statement statement], columnIdx) length:(NSUInteger)dataSize freeWhenDone:NO]; 316 | 317 | return data; 318 | } 319 | 320 | 321 | - (BOOL)columnIndexIsNull:(int)columnIdx { 322 | return sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL; 323 | } 324 | 325 | - (BOOL)columnIsNull:(NSString*)columnName { 326 | return [self columnIndexIsNull:[self columnIndexForName:columnName]]; 327 | } 328 | 329 | - (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx { 330 | 331 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 332 | return nil; 333 | } 334 | 335 | return sqlite3_column_text([_statement statement], columnIdx); 336 | } 337 | 338 | - (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName { 339 | return [self UTF8StringForColumnIndex:[self columnIndexForName:columnName]]; 340 | } 341 | 342 | - (id)objectForColumnIndex:(int)columnIdx { 343 | int columnType = sqlite3_column_type([_statement statement], columnIdx); 344 | 345 | id returnValue = nil; 346 | 347 | if (columnType == SQLITE_INTEGER) { 348 | returnValue = [NSNumber numberWithLongLong:[self longLongIntForColumnIndex:columnIdx]]; 349 | } 350 | else if (columnType == SQLITE_FLOAT) { 351 | returnValue = [NSNumber numberWithDouble:[self doubleForColumnIndex:columnIdx]]; 352 | } 353 | else if (columnType == SQLITE_BLOB) { 354 | returnValue = [self dataForColumnIndex:columnIdx]; 355 | } 356 | else { 357 | //default to a string for everything else 358 | returnValue = [self stringForColumnIndex:columnIdx]; 359 | } 360 | 361 | if (returnValue == nil) { 362 | returnValue = [NSNull null]; 363 | } 364 | 365 | return returnValue; 366 | } 367 | 368 | - (id)objectForColumnName:(NSString*)columnName { 369 | return [self objectForColumnIndex:[self columnIndexForName:columnName]]; 370 | } 371 | 372 | // returns autoreleased NSString containing the name of the column in the result set 373 | - (NSString*)columnNameForIndex:(int)columnIdx { 374 | return [NSString stringWithUTF8String: sqlite3_column_name([_statement statement], columnIdx)]; 375 | } 376 | 377 | - (void)setParentDB:(FMDatabase *)newDb { 378 | _parentDB = newDb; 379 | } 380 | 381 | - (id)objectAtIndexedSubscript:(int)columnIdx { 382 | return [self objectForColumnIndex:columnIdx]; 383 | } 384 | 385 | - (id)objectForKeyedSubscript:(NSString *)columnName { 386 | return [self objectForColumnName:columnName]; 387 | } 388 | 389 | 390 | @end 391 | -------------------------------------------------------------------------------- /ThridParty/FMDB/README.markdown: -------------------------------------------------------------------------------- 1 | # FMDB v2.3 2 | This is an Objective-C wrapper around SQLite: http://sqlite.org/ 3 | 4 | ## The FMDB Mailing List: 5 | http://groups.google.com/group/fmdb 6 | 7 | ## Read the SQLite FAQ: 8 | http://www.sqlite.org/faq.html 9 | 10 | Since FMDB is built on top of SQLite, you're going to want to read this page top to bottom at least once. And while you're there, make sure to bookmark the SQLite Documentation page: http://www.sqlite.org/docs.html 11 | 12 | ## Contributing 13 | Do you have an awesome idea that deserves to be in FMDB? You might consider pinging ccgus first to make sure he hasn't already ruled it out for some reason. Otherwise pull requests are great, and make sure you stick to the local coding conventions. However, please be patient and if you haven't heard anything from ccgus for a week or more, you might want to send a note asking what's up. 14 | 15 | ## CocoaPods 16 | 17 | FMDB can be installed using [CocoaPods](http://cocoapods.org/). 18 | 19 | ``` 20 | pod 'FMDB' 21 | # pod 'FMDB/SQLCipher' # If using FMDB with SQLCipher 22 | ``` 23 | 24 | **If using FMDB with [SQLCipher](http://sqlcipher.net/) you must use the FMDB/SQLCipher subspec. The FMDB/SQLCipher subspec declares SQLCipher as a dependency, allowing FMDB to be compiled with the `-DSQLITE_HAS_CODEC` flag.** 25 | 26 | ## FMDB Class Reference: 27 | http://ccgus.github.io/fmdb/html/index.html 28 | 29 | ## Automatic Reference Counting (ARC) or Manual Memory Management? 30 | You can use either style in your Cocoa project. FMDB Will figure out which you are using at compile time and do the right thing. 31 | 32 | ## Usage 33 | There are three main classes in FMDB: 34 | 35 | 1. `FMDatabase` - Represents a single SQLite database. Used for executing SQL statements. 36 | 2. `FMResultSet` - Represents the results of executing a query on an `FMDatabase`. 37 | 3. `FMDatabaseQueue` - If you're wanting to perform queries and updates on multiple threads, you'll want to use this class. It's described in the "Thread Safety" section below. 38 | 39 | ### Database Creation 40 | An `FMDatabase` is created with a path to a SQLite database file. This path can be one of these three: 41 | 42 | 1. A file system path. The file does not have to exist on disk. If it does not exist, it is created for you. 43 | 2. An empty string (`@""`). An empty database is created at a temporary location. This database is deleted with the `FMDatabase` connection is closed. 44 | 3. `NULL`. An in-memory database is created. This database will be destroyed with the `FMDatabase` connection is closed. 45 | 46 | (For more information on temporary and in-memory databases, read the sqlite documentation on the subject: http://www.sqlite.org/inmemorydb.html) 47 | 48 | FMDatabase *db = [FMDatabase databaseWithPath:@"/tmp/tmp.db"]; 49 | 50 | ### Opening 51 | 52 | Before you can interact with the database, it must be opened. Opening fails if there are insufficient resources or permissions to open and/or create the database. 53 | 54 | if (![db open]) { 55 | [db release]; 56 | return; 57 | } 58 | 59 | ### Executing Updates 60 | 61 | Any sort of SQL statement which is not a `SELECT` statement qualifies as an update. This includes `CREATE`, `UPDATE`, `INSERT`, `ALTER`, `COMMIT`, `BEGIN`, `DETACH`, `DELETE`, `DROP`, `END`, `EXPLAIN`, `VACUUM`, and `REPLACE` statements (plus many more). Basically, if your SQL statement does not begin with `SELECT`, it is an update statement. 62 | 63 | Executing updates returns a single value, a `BOOL`. A return value of `YES` means the update was successfully executed, and a return value of `NO` means that some error was encountered. You may invoke the `-lastErrorMessage` and `-lastErrorCode` methods to retrieve more information. 64 | 65 | ### Executing Queries 66 | 67 | A `SELECT` statement is a query and is executed via one of the `-executeQuery...` methods. 68 | 69 | Executing queries returns an `FMResultSet` object if successful, and `nil` upon failure. Like executing updates, there is a variant that accepts an `NSError **` parameter. Otherwise you should use the `-lastErrorMessage` and `-lastErrorCode` methods to determine why a query failed. 70 | 71 | In order to iterate through the results of your query, you use a `while()` loop. You also need to "step" from one record to the other. With FMDB, the easiest way to do that is like this: 72 | 73 | FMResultSet *s = [db executeQuery:@"SELECT * FROM myTable"]; 74 | while ([s next]) { 75 | //retrieve values for each record 76 | } 77 | 78 | You must always invoke `-[FMResultSet next]` before attempting to access the values returned in a query, even if you're only expecting one: 79 | 80 | FMResultSet *s = [db executeQuery:@"SELECT COUNT(*) FROM myTable"]; 81 | if ([s next]) { 82 | int totalCount = [s intForColumnIndex:0]; 83 | } 84 | 85 | `FMResultSet` has many methods to retrieve data in an appropriate format: 86 | 87 | - `intForColumn:` 88 | - `longForColumn:` 89 | - `longLongIntForColumn:` 90 | - `boolForColumn:` 91 | - `doubleForColumn:` 92 | - `stringForColumn:` 93 | - `dateForColumn:` 94 | - `dataForColumn:` 95 | - `dataNoCopyForColumn:` 96 | - `UTF8StringForColumnName:` 97 | - `objectForColumnName:` 98 | 99 | Each of these methods also has a `{type}ForColumnIndex:` variant that is used to retrieve the data based on the position of the column in the results, as opposed to the column's name. 100 | 101 | Typically, there's no need to `-close` an `FMResultSet` yourself, since that happens when either the result set is deallocated, or the parent database is closed. 102 | 103 | ### Closing 104 | 105 | When you have finished executing queries and updates on the database, you should `-close` the `FMDatabase` connection so that SQLite will relinquish any resources it has acquired during the course of its operation. 106 | 107 | [db close]; 108 | 109 | ### Transactions 110 | 111 | `FMDatabase` can begin and commit a transaction by invoking one of the appropriate methods or executing a begin/end transaction statement. 112 | 113 | ### Multiple Statements and Batch Stuff 114 | 115 | You can use `FMDatabase`'s executeStatements:withResultBlock: to do multiple statements in a string: 116 | 117 | ``` 118 | NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);" 119 | "create table bulktest2 (id integer primary key autoincrement, y text);" 120 | "create table bulktest3 (id integer primary key autoincrement, z text);" 121 | "insert into bulktest1 (x) values ('XXX');" 122 | "insert into bulktest2 (y) values ('YYY');" 123 | "insert into bulktest3 (z) values ('ZZZ');"; 124 | 125 | success = [db executeStatements:sql]; 126 | 127 | sql = @"select count(*) as count from bulktest1;" 128 | "select count(*) as count from bulktest2;" 129 | "select count(*) as count from bulktest3;"; 130 | 131 | success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) { 132 | NSInteger count = [dictionary[@"count"] integerValue]; 133 | XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary); 134 | return 0; 135 | }]; 136 | 137 | ``` 138 | 139 | ### Data Sanitization 140 | 141 | When providing a SQL statement to FMDB, you should not attempt to "sanitize" any values before insertion. Instead, you should use the standard SQLite binding syntax: 142 | 143 | INSERT INTO myTable VALUES (?, ?, ?) 144 | 145 | The `?` character is recognized by SQLite as a placeholder for a value to be inserted. The execution methods all accept a variable number of arguments (or a representation of those arguments, such as an `NSArray`, `NSDictionary`, or a `va_list`), which are properly escaped for you. 146 | 147 | Alternatively, you may use named parameters syntax: 148 | 149 | INSERT INTO myTable VALUES (:id, :name, :value) 150 | 151 | The parameters *must* start with a colon. SQLite itself supports other characters, but internally the Dictionary keys are prefixed with a colon, do **not** include the colon in your dictionary keys. 152 | 153 | NSDictionary *argsDict = [NSDictionary dictionaryWithObjectsAndKeys:@"My Name", @"name", nil]; 154 | [db executeUpdate:@"INSERT INTO myTable (name) VALUES (:name)" withParameterDictionary:argsDict]; 155 | 156 | Thus, you SHOULD NOT do this (or anything like this): 157 | 158 | [db executeUpdate:[NSString stringWithFormat:@"INSERT INTO myTable VALUES (%@)", @"this has \" lots of ' bizarre \" quotes '"]]; 159 | 160 | Instead, you SHOULD do: 161 | 162 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", @"this has \" lots of ' bizarre \" quotes '"]; 163 | 164 | All arguments provided to the `-executeUpdate:` method (or any of the variants that accept a `va_list` as a parameter) must be objects. The following will not work (and will result in a crash): 165 | 166 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", 42]; 167 | 168 | The proper way to insert a number is to box it in an `NSNumber` object: 169 | 170 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:42]]; 171 | 172 | Alternatively, you can use the `-execute*WithFormat:` variant to use `NSString`-style substitution: 173 | 174 | [db executeUpdateWithFormat:@"INSERT INTO myTable VALUES (%d)", 42]; 175 | 176 | Internally, the `-execute*WithFormat:` methods are properly boxing things for you. The following percent modifiers are recognized: `%@`, `%c`, `%s`, `%d`, `%D`, `%i`, `%u`, `%U`, `%hi`, `%hu`, `%qi`, `%qu`, `%f`, `%g`, `%ld`, `%lu`, `%lld`, and `%llu`. Using a modifier other than those will have unpredictable results. If, for some reason, you need the `%` character to appear in your SQL statement, you should use `%%`. 177 | 178 | 179 |

Using FMDatabaseQueue and Thread Safety.

180 | 181 | Using a single instance of FMDatabase from multiple threads at once is a bad idea. It has always been OK to make a FMDatabase object *per thread*. Just don't share a single instance across threads, and definitely not across multiple threads at the same time. Bad things will eventually happen and you'll eventually get something to crash, or maybe get an exception, or maybe meteorites will fall out of the sky and hit your Mac Pro. *This would suck*. 182 | 183 | **So don't instantiate a single FMDatabase object and use it across multiple threads.** 184 | 185 | Instead, use FMDatabaseQueue. It's your friend and it's here to help. Here's how to use it: 186 | 187 | First, make your queue. 188 | 189 | FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath]; 190 | 191 | Then use it like so: 192 | 193 | [queue inDatabase:^(FMDatabase *db) { 194 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; 195 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; 196 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; 197 | 198 | FMResultSet *rs = [db executeQuery:@"select * from foo"]; 199 | while ([rs next]) { 200 | … 201 | } 202 | }]; 203 | 204 | An easy way to wrap things up in a transaction can be done like this: 205 | 206 | [queue inTransaction:^(FMDatabase *db, BOOL *rollback) { 207 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; 208 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; 209 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; 210 | 211 | if (whoopsSomethingWrongHappened) { 212 | *rollback = YES; 213 | return; 214 | } 215 | // etc… 216 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]]; 217 | }]; 218 | 219 | 220 | FMDatabaseQueue will run the blocks on a serialized queue (hence the name of the class). So if you call FMDatabaseQueue's methods from multiple threads at the same time, they will be executed in the order they are received. This way queries and updates won't step on each other's toes, and every one is happy. 221 | 222 | **Note:** The calls to FMDatabaseQueue's methods are blocking. So even though you are passing along blocks, they will **not** be run on another thread. 223 | 224 | ## Making custom sqlite functions, based on blocks. 225 | 226 | You can do this! For an example, look for "makeFunctionNamed:" in main.m 227 | 228 | ## History 229 | 230 | The history and changes are availbe on its [GitHub page](https://github.com/ccgus/fmdb) and are summarized in the "CHANGES_AND_TODO_LIST.txt" file. 231 | 232 | ## Contributors 233 | 234 | The contributors to FMDB are contained in the "Contributors.txt" file. 235 | 236 | ## Quick notes on FMDB's coding style 237 | 238 | Spaces, not tabs. Square brackets, not dot notation. Look at what FMDB already does with curly brackets and such, and stick to that style. 239 | 240 | ## Reporting bugs 241 | 242 | Reduce your bug down to the smallest amount of code possible. You want to make it super easy for the developers to see and reproduce your bug. If it helps, pretend that the person who can fix your bug is active on shipping 3 major products, works on a handful of open source projects, has a newborn baby, and is generally very very busy. 243 | 244 | And we've even added a template function to main.m (FMDBReportABugFunction) in the FMDB distribution to help you out: 245 | 246 | * Open up fmdb project in Xcode. 247 | * Open up main.m and modify the FMDBReportABugFunction to reproduce your bug. 248 | * Setup your table(s) in the code. 249 | * Make your query or update(s). 250 | * Add some assertions which demonstrate the bug. 251 | 252 | Then you can bring it up on the FMDB mailing list by showing your nice and compact FMDBReportABugFunction, or you can report the bug via the github FMDB bug reporter. 253 | 254 | **Optional:** 255 | 256 | Figure out where the bug is, fix it, and send a patch in or bring that up on the mailing list. Make sure all the other tests run after your modifications. 257 | 258 | ## License 259 | 260 | The license for FMDB is contained in the "License.txt" file. 261 | -------------------------------------------------------------------------------- /ThridParty/FMDB/Tests/FMDBTempDBTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDBTempDBTests.h 3 | // fmdb 4 | // 5 | // Created by Graham Dennis on 24/11/2013. 6 | // 7 | // 8 | 9 | #import 10 | #import "FMDatabase.h" 11 | 12 | @protocol FMDBTempDBTests 13 | 14 | @optional 15 | + (void)populateDatabase:(FMDatabase *)database; 16 | 17 | @end 18 | 19 | @interface FMDBTempDBTests : XCTestCase 20 | 21 | @property FMDatabase *db; 22 | @property (readonly) NSString *databasePath; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /ThridParty/FMDB/Tests/FMDBTempDBTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDBTempDBTests.m 3 | // fmdb 4 | // 5 | // Created by Graham Dennis on 24/11/2013. 6 | // 7 | // 8 | 9 | #import "FMDBTempDBTests.h" 10 | 11 | static NSString *const testDatabasePath = @"/tmp/tmp.db"; 12 | static NSString *const populatedDatabasePath = @"/tmp/tmp-populated.db"; 13 | 14 | @implementation FMDBTempDBTests 15 | 16 | + (void)setUp 17 | { 18 | [super setUp]; 19 | 20 | // Delete old populated database 21 | NSFileManager *fileManager = [NSFileManager defaultManager]; 22 | [fileManager removeItemAtPath:populatedDatabasePath error:NULL]; 23 | 24 | if ([self respondsToSelector:@selector(populateDatabase:)]) { 25 | FMDatabase *db = [FMDatabase databaseWithPath:populatedDatabasePath]; 26 | 27 | [db open]; 28 | [self populateDatabase:db]; 29 | [db close]; 30 | } 31 | } 32 | 33 | - (void)setUp 34 | { 35 | [super setUp]; 36 | 37 | // Delete the old database 38 | NSFileManager *fileManager = [NSFileManager defaultManager]; 39 | [fileManager removeItemAtPath:testDatabasePath error:NULL]; 40 | 41 | if ([[self class] respondsToSelector:@selector(populateDatabase:)]) { 42 | [fileManager copyItemAtPath:populatedDatabasePath toPath:testDatabasePath error:NULL]; 43 | } 44 | 45 | self.db = [FMDatabase databaseWithPath:testDatabasePath]; 46 | 47 | XCTAssertTrue([self.db open], @"Wasn't able to open database"); 48 | [self.db setShouldCacheStatements:YES]; 49 | } 50 | 51 | - (void)tearDown 52 | { 53 | [super tearDown]; 54 | 55 | [self.db close]; 56 | } 57 | 58 | - (NSString *)databasePath 59 | { 60 | return testDatabasePath; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /ThridParty/FMDB/Tests/FMDatabaseAdditionsTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditionsTests.m 3 | // fmdb 4 | // 5 | // Created by Graham Dennis on 24/11/2013. 6 | // 7 | // 8 | 9 | #import 10 | #import "FMDatabaseAdditions.h" 11 | 12 | @interface FMDatabaseAdditionsTests : FMDBTempDBTests 13 | 14 | @end 15 | 16 | @implementation FMDatabaseAdditionsTests 17 | 18 | - (void)setUp 19 | { 20 | [super setUp]; 21 | // Put setup code here. This method is called before the invocation of each test method in the class. 22 | } 23 | 24 | - (void)tearDown 25 | { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testFunkyTableNames 31 | { 32 | [self.db executeUpdate:@"create table '234 fds' (foo text)"]; 33 | XCTAssertFalse([self.db hadError], @"table creation should have succeeded"); 34 | FMResultSet *rs = [self.db getTableSchema:@"234 fds"]; 35 | XCTAssertTrue([rs next], @"Schema should have succeded"); 36 | [rs close]; 37 | XCTAssertFalse([self.db hadError], @"There shouldn't be any errors"); 38 | } 39 | 40 | - (void)testBoolForQuery 41 | { 42 | BOOL result = [self.db boolForQuery:@"SELECT ? not null", @""]; 43 | XCTAssertTrue(result, @"Empty strings should be considered true"); 44 | 45 | result = [self.db boolForQuery:@"SELECT ? not null", [NSMutableData data]]; 46 | XCTAssertTrue(result, @"Empty mutable data should be considered true"); 47 | 48 | result = [self.db boolForQuery:@"SELECT ? not null", [NSData data]]; 49 | XCTAssertTrue(result, @"Empty data should be considered true"); 50 | } 51 | 52 | 53 | - (void)testIntForQuery 54 | { 55 | [self.db executeUpdate:@"create table t1 (a integer)"]; 56 | [self.db executeUpdate:@"insert into t1 values (?)", [NSNumber numberWithInt:5]]; 57 | 58 | XCTAssertEqual([self.db changes], 1, @"There should only be one change"); 59 | 60 | int ia = [self.db intForQuery:@"select a from t1 where a = ?", [NSNumber numberWithInt:5]]; 61 | XCTAssertEqual(ia, 5, @"foo"); 62 | } 63 | 64 | - (void)testDateForQuery 65 | { 66 | NSDate *date = [NSDate date]; 67 | [self.db executeUpdate:@"create table datetest (a double, b double, c double)"]; 68 | [self.db executeUpdate:@"insert into datetest (a, b, c) values (?, ?, 0)" , [NSNull null], date]; 69 | 70 | NSDate *foo = [self.db dateForQuery:@"select b from datetest where c = 0"]; 71 | XCTAssertEqualWithAccuracy([foo timeIntervalSinceDate:date], 0.0, 1.0, @"Dates should be the same to within a second"); 72 | } 73 | 74 | - (void)testTableExists 75 | { 76 | XCTAssertTrue([self.db executeUpdate:@"create table t4 (a text, b text)"]); 77 | 78 | XCTAssertTrue([self.db tableExists:@"t4"]); 79 | XCTAssertFalse([self.db tableExists:@"thisdoesntexist"]); 80 | 81 | FMResultSet *rs = [self.db getSchema]; 82 | while ([rs next]) { 83 | XCTAssertEqualObjects([rs stringForColumn:@"type"], @"table"); 84 | } 85 | 86 | } 87 | 88 | - (void)testColumnExists 89 | { 90 | [self.db executeUpdate:@"create table nulltest (a text, b text)"]; 91 | 92 | XCTAssertTrue([self.db columnExists:@"a" inTableWithName:@"nulltest"]); 93 | XCTAssertTrue([self.db columnExists:@"b" inTableWithName:@"nulltest"]); 94 | XCTAssertFalse([self.db columnExists:@"c" inTableWithName:@"nulltest"]); 95 | } 96 | 97 | - (void)testUserVersion { 98 | 99 | [[self db] setUserVersion:12]; 100 | 101 | XCTAssertTrue([[self db] userVersion] == 12); 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /ThridParty/FMDB/Tests/FMDatabasePoolTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePoolTests.m 3 | // fmdb 4 | // 5 | // Created by Graham Dennis on 24/11/2013. 6 | // 7 | // 8 | 9 | #import 10 | 11 | @interface FMDatabasePoolTests : FMDBTempDBTests 12 | 13 | @property FMDatabasePool *pool; 14 | 15 | @end 16 | 17 | @implementation FMDatabasePoolTests 18 | 19 | + (void)populateDatabase:(FMDatabase *)db 20 | { 21 | [db executeUpdate:@"create table easy (a text)"]; 22 | [db executeUpdate:@"create table easy2 (a text)"]; 23 | 24 | [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1001]]; 25 | [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1002]]; 26 | [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1003]]; 27 | 28 | [db executeUpdate:@"create table likefoo (foo text)"]; 29 | [db executeUpdate:@"insert into likefoo values ('hi')"]; 30 | [db executeUpdate:@"insert into likefoo values ('hello')"]; 31 | [db executeUpdate:@"insert into likefoo values ('not')"]; 32 | } 33 | 34 | - (void)setUp 35 | { 36 | [super setUp]; 37 | // Put setup code here. This method is called before the invocation of each test method in the class. 38 | 39 | [self setPool:[FMDatabasePool databasePoolWithPath:self.databasePath]]; 40 | 41 | [[self pool] setDelegate:self]; 42 | 43 | } 44 | 45 | - (void)tearDown 46 | { 47 | // Put teardown code here. This method is called after the invocation of each test method in the class. 48 | [super tearDown]; 49 | } 50 | 51 | - (void)testPoolIsInitiallyEmpty 52 | { 53 | XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)0, @"Pool should be empty on creation"); 54 | } 55 | 56 | - (void)testDatabaseCreation 57 | { 58 | __block FMDatabase *db1; 59 | 60 | [self.pool inDatabase:^(FMDatabase *db) { 61 | 62 | XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1, @"Should only have one database at this point"); 63 | 64 | db1 = db; 65 | 66 | }]; 67 | 68 | [self.pool inDatabase:^(FMDatabase *db) { 69 | XCTAssertEqualObjects(db, db1, @"We should get the same database back because there was no need to create a new one"); 70 | 71 | [self.pool inDatabase:^(FMDatabase *db2) { 72 | XCTAssertNotEqualObjects(db2, db, @"We should get a different database because the first was in use."); 73 | }]; 74 | 75 | }]; 76 | 77 | XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)2); 78 | 79 | [self.pool releaseAllDatabases]; 80 | 81 | XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)0, @"We should be back to zero databases again"); 82 | } 83 | 84 | - (void)testCheckedInCheckoutOutCount 85 | { 86 | [self.pool inDatabase:^(FMDatabase *aDb) { 87 | 88 | XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)0); 89 | XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)1); 90 | 91 | XCTAssertTrue(([aDb executeUpdate:@"insert into easy (a) values (?)", @"hi"])); 92 | 93 | // just for fun. 94 | FMResultSet *rs = [aDb executeQuery:@"select * from easy"]; 95 | XCTAssertNotNil(rs); 96 | XCTAssertTrue([rs next]); 97 | while ([rs next]) { ; } // whatevers. 98 | 99 | XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1); 100 | XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)0); 101 | XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)1); 102 | }]; 103 | 104 | XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1); 105 | } 106 | 107 | - (void)testMaximumDatabaseLimit 108 | { 109 | [self.pool setMaximumNumberOfDatabasesToCreate:2]; 110 | 111 | [self.pool inDatabase:^(FMDatabase *db) { 112 | [self.pool inDatabase:^(FMDatabase *db2) { 113 | [self.pool inDatabase:^(FMDatabase *db3) { 114 | XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)2); 115 | XCTAssertNil(db3, @"The third database must be nil because we have a maximum of 2 databases in the pool"); 116 | }]; 117 | 118 | }]; 119 | }]; 120 | } 121 | 122 | - (void)testTransaction 123 | { 124 | [self.pool inTransaction:^(FMDatabase *adb, BOOL *rollback) { 125 | [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1001]]; 126 | [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1002]]; 127 | [adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1003]]; 128 | 129 | XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1); 130 | XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)0); 131 | XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)1); 132 | }]; 133 | 134 | XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1); 135 | XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)1); 136 | XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)0); 137 | } 138 | 139 | - (void)testSelect 140 | { 141 | [self.pool inDatabase:^(FMDatabase *db) { 142 | FMResultSet *rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1001]]; 143 | XCTAssertNotNil(rs); 144 | XCTAssertTrue ([rs next]); 145 | XCTAssertFalse([rs next]); 146 | }]; 147 | } 148 | 149 | - (void)testTransactionRollback 150 | { 151 | [self.pool inDeferredTransaction:^(FMDatabase *adb, BOOL *rollback) { 152 | XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1004]])); 153 | XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1005]])); 154 | XCTAssertTrue([[adb executeQuery:@"select * from easy where a == '1004'"] next], @"1004 should be in database"); 155 | 156 | *rollback = YES; 157 | }]; 158 | 159 | [self.pool inDatabase:^(FMDatabase *db) { 160 | XCTAssertFalse([[db executeQuery:@"select * from easy where a == '1004'"] next], @"1004 should not be in database"); 161 | }]; 162 | 163 | XCTAssertEqual([self.pool countOfOpenDatabases], (NSUInteger)1); 164 | XCTAssertEqual([self.pool countOfCheckedInDatabases], (NSUInteger)1); 165 | XCTAssertEqual([self.pool countOfCheckedOutDatabases], (NSUInteger)0); 166 | } 167 | 168 | - (void)testSavepoint 169 | { 170 | NSError *err = [self.pool inSavePoint:^(FMDatabase *db, BOOL *rollback) { 171 | [db executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1006]]; 172 | }]; 173 | 174 | XCTAssertNil(err); 175 | } 176 | 177 | - (void)testNestedSavepointRollback 178 | { 179 | NSError *err = [self.pool inSavePoint:^(FMDatabase *adb, BOOL *rollback) { 180 | XCTAssertFalse([adb hadError]); 181 | XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1009]])); 182 | 183 | [adb inSavePoint:^(BOOL *arollback) { 184 | XCTAssertTrue(([adb executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:1010]])); 185 | *arollback = YES; 186 | }]; 187 | }]; 188 | 189 | 190 | XCTAssertNil(err); 191 | 192 | [self.pool inDatabase:^(FMDatabase *db) { 193 | FMResultSet *rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1009]]; 194 | XCTAssertTrue ([rs next]); 195 | XCTAssertFalse([rs next]); // close it out. 196 | 197 | rs = [db executeQuery:@"select * from easy where a = ?", [NSNumber numberWithInt:1010]]; 198 | XCTAssertFalse([rs next]); 199 | }]; 200 | } 201 | 202 | - (void)testLikeStringQuery 203 | { 204 | [self.pool inDatabase:^(FMDatabase *db) { 205 | int count = 0; 206 | FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"]; 207 | while ([rsl next]) { 208 | count++; 209 | } 210 | 211 | XCTAssertEqual(count, 2); 212 | 213 | count = 0; 214 | rsl = [db executeQuery:@"select * from likefoo where foo like ?", @"h%"]; 215 | while ([rsl next]) { 216 | count++; 217 | } 218 | 219 | XCTAssertEqual(count, 2); 220 | 221 | }]; 222 | } 223 | 224 | - (void)testStressTest 225 | { 226 | size_t ops = 128; 227 | 228 | dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 229 | 230 | dispatch_apply(ops, dqueue, ^(size_t nby) { 231 | 232 | // just mix things up a bit for demonstration purposes. 233 | if (nby % 2 == 1) { 234 | 235 | [NSThread sleepForTimeInterval:.001]; 236 | } 237 | 238 | [self.pool inDatabase:^(FMDatabase *db) { 239 | FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"]; 240 | XCTAssertNotNil(rsl); 241 | int i = 0; 242 | while ([rsl next]) { 243 | i++; 244 | if (nby % 3 == 1) { 245 | [NSThread sleepForTimeInterval:.0005]; 246 | } 247 | } 248 | XCTAssertEqual(i, 2); 249 | }]; 250 | }); 251 | 252 | XCTAssert([self.pool countOfOpenDatabases] < 64, @"There should be significantly less than 64 databases after that stress test"); 253 | } 254 | 255 | 256 | - (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database { 257 | [database setMaxBusyRetryTimeInterval:10]; 258 | // [database setCrashOnErrors:YES]; 259 | return YES; 260 | } 261 | 262 | - (void)testReadWriteStressTest 263 | { 264 | int ops = 16; 265 | 266 | dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 267 | 268 | dispatch_apply(ops, dqueue, ^(size_t nby) { 269 | 270 | // just mix things up a bit for demonstration purposes. 271 | if (nby % 2 == 1) { 272 | [NSThread sleepForTimeInterval:.01]; 273 | 274 | [self.pool inTransaction:^(FMDatabase *db, BOOL *rollback) { 275 | FMResultSet *rsl = [db executeQuery:@"select * from likefoo where foo like 'h%'"]; 276 | XCTAssertNotNil(rsl); 277 | while ([rsl next]) { 278 | ;// whatever. 279 | } 280 | 281 | }]; 282 | 283 | } 284 | 285 | if (nby % 3 == 1) { 286 | [NSThread sleepForTimeInterval:.01]; 287 | } 288 | 289 | [self.pool inTransaction:^(FMDatabase *db, BOOL *rollback) { 290 | XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('1')"]); 291 | XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('2')"]); 292 | XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('3')"]); 293 | }]; 294 | }); 295 | 296 | [self.pool releaseAllDatabases]; 297 | 298 | [self.pool inDatabase:^(FMDatabase *db) { 299 | XCTAssertTrue([db executeUpdate:@"insert into likefoo values ('1')"]); 300 | }]; 301 | } 302 | 303 | @end 304 | -------------------------------------------------------------------------------- /ThridParty/FMDB/Tests/FMDatabaseQueueTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseQueueTests.m 3 | // fmdb 4 | // 5 | // Created by Graham Dennis on 24/11/2013. 6 | // 7 | // 8 | 9 | #import 10 | #import "FMDatabaseQueue.h" 11 | 12 | @interface FMDatabaseQueueTests : FMDBTempDBTests 13 | 14 | @property FMDatabaseQueue *queue; 15 | 16 | @end 17 | 18 | @implementation FMDatabaseQueueTests 19 | 20 | + (void)populateDatabase:(FMDatabase *)db 21 | { 22 | [db executeUpdate:@"create table easy (a text)"]; 23 | 24 | [db executeUpdate:@"create table qfoo (foo text)"]; 25 | [db executeUpdate:@"insert into qfoo values ('hi')"]; 26 | [db executeUpdate:@"insert into qfoo values ('hello')"]; 27 | [db executeUpdate:@"insert into qfoo values ('not')"]; 28 | } 29 | 30 | - (void)setUp 31 | { 32 | [super setUp]; 33 | // Put setup code here. This method is called before the invocation of each test method in the class. 34 | 35 | self.queue = [FMDatabaseQueue databaseQueueWithPath:self.databasePath]; 36 | } 37 | 38 | - (void)tearDown 39 | { 40 | // Put teardown code here. This method is called after the invocation of each test method in the class. 41 | [super tearDown]; 42 | } 43 | 44 | - (void)testQueueSelect 45 | { 46 | [self.queue inDatabase:^(FMDatabase *adb) { 47 | int count = 0; 48 | FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"]; 49 | while ([rsl next]) { 50 | count++; 51 | } 52 | 53 | XCTAssertEqual(count, 2); 54 | 55 | count = 0; 56 | rsl = [adb executeQuery:@"select * from qfoo where foo like ?", @"h%"]; 57 | while ([rsl next]) { 58 | count++; 59 | } 60 | 61 | XCTAssertEqual(count, 2); 62 | }]; 63 | } 64 | 65 | - (void)testReadOnlyQueue 66 | { 67 | FMDatabaseQueue *queue2 = [FMDatabaseQueue databaseQueueWithPath:self.databasePath flags:SQLITE_OPEN_READONLY]; 68 | XCTAssertNotNil(queue2); 69 | 70 | { 71 | [queue2 inDatabase:^(FMDatabase *db2) { 72 | FMResultSet *rs1 = [db2 executeQuery:@"SELECT * FROM qfoo"]; 73 | XCTAssertNotNil(rs1); 74 | 75 | [rs1 close]; 76 | 77 | XCTAssertFalse(([db2 executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]]), @"Insert should fail because this is a read-only database"); 78 | }]; 79 | 80 | [queue2 close]; 81 | 82 | // Check that when we re-open the database, it's still read-only 83 | [queue2 inDatabase:^(FMDatabase *db2) { 84 | FMResultSet *rs1 = [db2 executeQuery:@"SELECT * FROM qfoo"]; 85 | XCTAssertNotNil(rs1); 86 | 87 | [rs1 close]; 88 | 89 | XCTAssertFalse(([db2 executeUpdate:@"insert into easy values (?)", [NSNumber numberWithInt:3]]), @"Insert should fail because this is a read-only database"); 90 | }]; 91 | } 92 | } 93 | 94 | - (void)testStressTest 95 | { 96 | size_t ops = 16; 97 | 98 | dispatch_queue_t dqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 99 | 100 | dispatch_apply(ops, dqueue, ^(size_t nby) { 101 | 102 | // just mix things up a bit for demonstration purposes. 103 | if (nby % 2 == 1) { 104 | [NSThread sleepForTimeInterval:.01]; 105 | 106 | [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) { 107 | FMResultSet *rsl = [adb executeQuery:@"select * from qfoo where foo like 'h%'"]; 108 | while ([rsl next]) { 109 | ;// whatever. 110 | } 111 | }]; 112 | 113 | } 114 | 115 | if (nby % 3 == 1) { 116 | [NSThread sleepForTimeInterval:.01]; 117 | } 118 | 119 | [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) { 120 | XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('1')"]); 121 | XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('2')"]); 122 | XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('3')"]); 123 | }]; 124 | }); 125 | 126 | [self.queue close]; 127 | 128 | [self.queue inDatabase:^(FMDatabase *adb) { 129 | XCTAssertTrue([adb executeUpdate:@"insert into qfoo values ('1')"]); 130 | }]; 131 | } 132 | 133 | - (void)testTransaction 134 | { 135 | [self.queue inDatabase:^(FMDatabase *adb) { 136 | [adb executeUpdate:@"create table transtest (a integer)"]; 137 | XCTAssertTrue([adb executeUpdate:@"insert into transtest values (1)"]); 138 | XCTAssertTrue([adb executeUpdate:@"insert into transtest values (2)"]); 139 | 140 | int rowCount = 0; 141 | FMResultSet *ars = [adb executeQuery:@"select * from transtest"]; 142 | while ([ars next]) { 143 | rowCount++; 144 | } 145 | 146 | XCTAssertEqual(rowCount, 2); 147 | }]; 148 | 149 | [self.queue inTransaction:^(FMDatabase *adb, BOOL *rollback) { 150 | XCTAssertTrue([adb executeUpdate:@"insert into transtest values (3)"]); 151 | 152 | if (YES) { 153 | // uh oh!, something went wrong (not really, this is just a test 154 | *rollback = YES; 155 | return; 156 | } 157 | 158 | XCTFail(@"This shouldn't be reached"); 159 | }]; 160 | 161 | [self.queue inDatabase:^(FMDatabase *adb) { 162 | 163 | int rowCount = 0; 164 | FMResultSet *ars = [adb executeQuery:@"select * from transtest"]; 165 | while ([ars next]) { 166 | rowCount++; 167 | } 168 | 169 | XCTAssertFalse([adb hasOpenResultSets]); 170 | 171 | XCTAssertEqual(rowCount, 2); 172 | }]; 173 | 174 | } 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /ThridParty/FMDB/Tests/Schemes/Tests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 66 | 67 | 68 | 69 | 75 | 76 | 78 | 79 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /ThridParty/FMDB/Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | me.grahamdennis.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ThridParty/FMDB/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #import "FMDBTempDBTests.h" 11 | #endif 12 | -------------------------------------------------------------------------------- /ThridParty/FMDB/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /ThridParty/FMDB/extra/FMDatabase+InMemoryOnDiskIO.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabase+InMemoryOnDiskIO.h 3 | // FMDB 4 | // 5 | // Created by Peter Carr on 6/12/12. 6 | // 7 | // I find there is a massive performance hit using an "on-disk" representation when 8 | // constantly reading from or writing to the DB. If your machine has sufficient memory, you 9 | // should get a significant performance boost using an "in-memory" representation. The FMDB 10 | // warpper does not contain methods to load an "on-disk" representation into memory and 11 | // similarly save an "in-memory" representation to disk. However, SQLite3 has built-in 12 | // support for this functionality via its "Backup" API. Here, we extend the FMBD wrapper 13 | // to include this functionality. 14 | // 15 | // http://www.sqlite.org/backup.html 16 | 17 | #import "FMDatabase.h" 18 | 19 | @interface FMDatabase (InMemoryOnDiskIO) 20 | 21 | // Loads an on-disk representation into memory. 22 | - (BOOL)readFromFile:(NSString*)filePath; 23 | 24 | // Saves an in-memory representation to disk 25 | - (BOOL)writeToFile:(NSString *)filePath; 26 | @end 27 | -------------------------------------------------------------------------------- /ThridParty/FMDB/extra/FMDatabase+InMemoryOnDiskIO.m: -------------------------------------------------------------------------------- 1 | #import "FMDatabase+InMemoryOnDiskIO.h" 2 | 3 | // http://www.sqlite.org/backup.html 4 | static 5 | int loadOrSaveDb(sqlite3 *pInMemory, const char *zFilename, int isSave) 6 | { 7 | int rc; /* Function return code */ 8 | sqlite3 *pFile; /* Database connection opened on zFilename */ 9 | sqlite3_backup *pBackup; /* Backup object used to copy data */ 10 | sqlite3 *pTo; /* Database to copy to (pFile or pInMemory) */ 11 | sqlite3 *pFrom; /* Database to copy from (pFile or pInMemory) */ 12 | 13 | /* Open the database file identified by zFilename. Exit early if this fails 14 | ** for any reason. */ 15 | rc = sqlite3_open(zFilename, &pFile); 16 | if( rc==SQLITE_OK ){ 17 | 18 | /* If this is a 'load' operation (isSave==0), then data is copied 19 | ** from the database file just opened to database pInMemory. 20 | ** Otherwise, if this is a 'save' operation (isSave==1), then data 21 | ** is copied from pInMemory to pFile. Set the variables pFrom and 22 | ** pTo accordingly. */ 23 | pFrom = (isSave ? pInMemory : pFile); 24 | pTo = (isSave ? pFile : pInMemory); 25 | 26 | /* Set up the backup procedure to copy from the "main" database of 27 | ** connection pFile to the main database of connection pInMemory. 28 | ** If something goes wrong, pBackup will be set to NULL and an error 29 | ** code and message left in connection pTo. 30 | ** 31 | ** If the backup object is successfully created, call backup_step() 32 | ** to copy data from pFile to pInMemory. Then call backup_finish() 33 | ** to release resources associated with the pBackup object. If an 34 | ** error occurred, then an error code and message will be left in 35 | ** connection pTo. If no error occurred, then the error code belonging 36 | ** to pTo is set to SQLITE_OK. 37 | */ 38 | pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main"); 39 | if( pBackup ){ 40 | (void)sqlite3_backup_step(pBackup, -1); 41 | (void)sqlite3_backup_finish(pBackup); 42 | } 43 | rc = sqlite3_errcode(pTo); 44 | } 45 | 46 | /* Close the database connection opened on database file zFilename 47 | ** and return the result of this function. */ 48 | (void)sqlite3_close(pFile); 49 | return rc; 50 | } 51 | 52 | 53 | 54 | @implementation FMDatabase (InMemoryOnDiskIO) 55 | 56 | - (BOOL)readFromFile:(NSString*)filePath 57 | { 58 | // only attempt to load an on-disk representation for an in-memory database 59 | if ( self->_databasePath != nil ) 60 | { 61 | NSLog(@"Database is not an in-memory representation." ); 62 | return NO; 63 | } 64 | 65 | // and only if the database is open 66 | if ( self->_db == nil ) 67 | { 68 | NSLog(@"Invalid database connection." ); 69 | return NO; 70 | } 71 | 72 | return ( SQLITE_OK == loadOrSaveDb( self->_db, [filePath fileSystemRepresentation], false ) ); 73 | 74 | } 75 | 76 | - (BOOL)writeToFile:(NSString *)filePath 77 | { 78 | // only attempt to save an on-disk representation for an in-memory database 79 | if ( self->_databasePath != nil ) 80 | { 81 | NSLog(@"Database is not an in-memory representation." ); 82 | return NO; 83 | } 84 | 85 | // and only if the database is open 86 | if ( self->_db == nil ) 87 | { 88 | NSLog(@"Invalid database connection." ); 89 | return NO; 90 | } 91 | 92 | // save the in-memory representation 93 | return ( SQLITE_OK == loadOrSaveDb( self->_db, [filePath fileSystemRepresentation], true ) ); 94 | } 95 | 96 | @end 97 | --------------------------------------------------------------------------------