├── .gitignore ├── FmdbTest.xcodeproj └── project.pbxproj ├── FmdbTest ├── AppDelegate.h ├── AppDelegate.m ├── FmdbTest-Info.plist ├── FmdbTest-Prefix.pch ├── MacroUtils.h ├── ViewController.h ├── ViewController.m ├── en.lproj │ ├── InfoPlist.strings │ └── ViewController.xib ├── fmdb │ ├── FMDatabase.h │ ├── FMDatabase.m │ ├── FMDatabaseAdditions.h │ ├── FMDatabaseAdditions.m │ ├── FMDatabasePool.h │ ├── FMDatabasePool.m │ ├── FMDatabaseQueue.h │ ├── FMDatabaseQueue.m │ ├── FMResultSet.h │ └── FMResultSet.m └── main.m └── FmdbTestTests ├── FmdbTestTests-Info.plist ├── FmdbTestTests.h ├── FmdbTestTests.m └── en.lproj └── InfoPlist.strings /.gitignore: -------------------------------------------------------------------------------- 1 | FmdbTest.xcodeproj/project.xcworkspace/ 2 | FmdbTest.xcodeproj/xcuserdata/ 3 | -------------------------------------------------------------------------------- /FmdbTest.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 456E44D91543E44C002A05E3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 456E44D81543E44C002A05E3 /* UIKit.framework */; }; 11 | 456E44DB1543E44C002A05E3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 456E44DA1543E44C002A05E3 /* Foundation.framework */; }; 12 | 456E44DD1543E44C002A05E3 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 456E44DC1543E44C002A05E3 /* CoreGraphics.framework */; }; 13 | 456E44E31543E44C002A05E3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 456E44E11543E44C002A05E3 /* InfoPlist.strings */; }; 14 | 456E44E51543E44C002A05E3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 456E44E41543E44C002A05E3 /* main.m */; }; 15 | 456E44E91543E44C002A05E3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 456E44E81543E44C002A05E3 /* AppDelegate.m */; }; 16 | 456E44EC1543E44C002A05E3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 456E44EB1543E44C002A05E3 /* ViewController.m */; }; 17 | 456E44EF1543E44C002A05E3 /* ViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 456E44ED1543E44C002A05E3 /* ViewController.xib */; }; 18 | 456E44F71543E44C002A05E3 /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 456E44F61543E44C002A05E3 /* SenTestingKit.framework */; }; 19 | 456E44F81543E44C002A05E3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 456E44D81543E44C002A05E3 /* UIKit.framework */; }; 20 | 456E44F91543E44C002A05E3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 456E44DA1543E44C002A05E3 /* Foundation.framework */; }; 21 | 456E45011543E44C002A05E3 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 456E44FF1543E44C002A05E3 /* InfoPlist.strings */; }; 22 | 456E45041543E44C002A05E3 /* FmdbTestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 456E45031543E44C002A05E3 /* FmdbTestTests.m */; }; 23 | 456E451B1543E481002A05E3 /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = 456E45101543E481002A05E3 /* FMDatabase.m */; }; 24 | 456E451C1543E481002A05E3 /* FMDatabaseAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 456E45121543E481002A05E3 /* FMDatabaseAdditions.m */; }; 25 | 456E451D1543E481002A05E3 /* FMDatabasePool.m in Sources */ = {isa = PBXBuildFile; fileRef = 456E45141543E481002A05E3 /* FMDatabasePool.m */; }; 26 | 456E451E1543E481002A05E3 /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 456E45161543E481002A05E3 /* FMDatabaseQueue.m */; }; 27 | 456E45201543E481002A05E3 /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = 456E45191543E481002A05E3 /* FMResultSet.m */; }; 28 | 456E45221543E4E8002A05E3 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 456E45211543E4E8002A05E3 /* libsqlite3.dylib */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXContainerItemProxy section */ 32 | 456E44FA1543E44C002A05E3 /* PBXContainerItemProxy */ = { 33 | isa = PBXContainerItemProxy; 34 | containerPortal = 456E44CB1543E44C002A05E3 /* Project object */; 35 | proxyType = 1; 36 | remoteGlobalIDString = 456E44D31543E44C002A05E3; 37 | remoteInfo = FmdbTest; 38 | }; 39 | /* End PBXContainerItemProxy section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 456E44D41543E44C002A05E3 /* FmdbTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FmdbTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 456E44D81543E44C002A05E3 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 44 | 456E44DA1543E44C002A05E3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 45 | 456E44DC1543E44C002A05E3 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 46 | 456E44E01543E44C002A05E3 /* FmdbTest-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FmdbTest-Info.plist"; sourceTree = ""; }; 47 | 456E44E21543E44C002A05E3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 48 | 456E44E41543E44C002A05E3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 49 | 456E44E61543E44C002A05E3 /* FmdbTest-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FmdbTest-Prefix.pch"; sourceTree = ""; }; 50 | 456E44E71543E44C002A05E3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 51 | 456E44E81543E44C002A05E3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 52 | 456E44EA1543E44C002A05E3 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 53 | 456E44EB1543E44C002A05E3 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 54 | 456E44EE1543E44C002A05E3 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/ViewController.xib; sourceTree = ""; }; 55 | 456E44F51543E44C002A05E3 /* FmdbTestTests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FmdbTestTests.octest; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 456E44F61543E44C002A05E3 /* SenTestingKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SenTestingKit.framework; path = Library/Frameworks/SenTestingKit.framework; sourceTree = DEVELOPER_DIR; }; 57 | 456E44FE1543E44C002A05E3 /* FmdbTestTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FmdbTestTests-Info.plist"; sourceTree = ""; }; 58 | 456E45001543E44C002A05E3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 59 | 456E45021543E44C002A05E3 /* FmdbTestTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FmdbTestTests.h; sourceTree = ""; }; 60 | 456E45031543E44C002A05E3 /* FmdbTestTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FmdbTestTests.m; sourceTree = ""; }; 61 | 456E450F1543E481002A05E3 /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = ""; }; 62 | 456E45101543E481002A05E3 /* FMDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabase.m; sourceTree = ""; }; 63 | 456E45111543E481002A05E3 /* FMDatabaseAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabaseAdditions.h; sourceTree = ""; }; 64 | 456E45121543E481002A05E3 /* FMDatabaseAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseAdditions.m; sourceTree = ""; }; 65 | 456E45131543E481002A05E3 /* FMDatabasePool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabasePool.h; sourceTree = ""; }; 66 | 456E45141543E481002A05E3 /* FMDatabasePool.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabasePool.m; sourceTree = ""; }; 67 | 456E45151543E481002A05E3 /* FMDatabaseQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabaseQueue.h; sourceTree = ""; }; 68 | 456E45161543E481002A05E3 /* FMDatabaseQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabaseQueue.m; sourceTree = ""; }; 69 | 456E45181543E481002A05E3 /* FMResultSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMResultSet.h; sourceTree = ""; }; 70 | 456E45191543E481002A05E3 /* FMResultSet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMResultSet.m; sourceTree = ""; }; 71 | 456E45211543E4E8002A05E3 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; 72 | 456E45231543F99A002A05E3 /* MacroUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MacroUtils.h; sourceTree = ""; }; 73 | /* End PBXFileReference section */ 74 | 75 | /* Begin PBXFrameworksBuildPhase section */ 76 | 456E44D11543E44C002A05E3 /* Frameworks */ = { 77 | isa = PBXFrameworksBuildPhase; 78 | buildActionMask = 2147483647; 79 | files = ( 80 | 456E45221543E4E8002A05E3 /* libsqlite3.dylib in Frameworks */, 81 | 456E44D91543E44C002A05E3 /* UIKit.framework in Frameworks */, 82 | 456E44DB1543E44C002A05E3 /* Foundation.framework in Frameworks */, 83 | 456E44DD1543E44C002A05E3 /* CoreGraphics.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | 456E44F11543E44C002A05E3 /* Frameworks */ = { 88 | isa = PBXFrameworksBuildPhase; 89 | buildActionMask = 2147483647; 90 | files = ( 91 | 456E44F71543E44C002A05E3 /* SenTestingKit.framework in Frameworks */, 92 | 456E44F81543E44C002A05E3 /* UIKit.framework in Frameworks */, 93 | 456E44F91543E44C002A05E3 /* Foundation.framework in Frameworks */, 94 | ); 95 | runOnlyForDeploymentPostprocessing = 0; 96 | }; 97 | /* End PBXFrameworksBuildPhase section */ 98 | 99 | /* Begin PBXGroup section */ 100 | 456E44C91543E44C002A05E3 = { 101 | isa = PBXGroup; 102 | children = ( 103 | 456E44DE1543E44C002A05E3 /* FmdbTest */, 104 | 456E44FC1543E44C002A05E3 /* FmdbTestTests */, 105 | 456E44D71543E44C002A05E3 /* Frameworks */, 106 | 456E44D51543E44C002A05E3 /* Products */, 107 | ); 108 | sourceTree = ""; 109 | }; 110 | 456E44D51543E44C002A05E3 /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 456E44D41543E44C002A05E3 /* FmdbTest.app */, 114 | 456E44F51543E44C002A05E3 /* FmdbTestTests.octest */, 115 | ); 116 | name = Products; 117 | sourceTree = ""; 118 | }; 119 | 456E44D71543E44C002A05E3 /* Frameworks */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 456E45211543E4E8002A05E3 /* libsqlite3.dylib */, 123 | 456E44D81543E44C002A05E3 /* UIKit.framework */, 124 | 456E44DA1543E44C002A05E3 /* Foundation.framework */, 125 | 456E44DC1543E44C002A05E3 /* CoreGraphics.framework */, 126 | 456E44F61543E44C002A05E3 /* SenTestingKit.framework */, 127 | ); 128 | name = Frameworks; 129 | sourceTree = ""; 130 | }; 131 | 456E44DE1543E44C002A05E3 /* FmdbTest */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | 456E450D1543E481002A05E3 /* FMDB */, 135 | 456E44E71543E44C002A05E3 /* AppDelegate.h */, 136 | 456E44E81543E44C002A05E3 /* AppDelegate.m */, 137 | 456E44EA1543E44C002A05E3 /* ViewController.h */, 138 | 456E44EB1543E44C002A05E3 /* ViewController.m */, 139 | 456E44ED1543E44C002A05E3 /* ViewController.xib */, 140 | 456E44DF1543E44C002A05E3 /* Supporting Files */, 141 | ); 142 | path = FmdbTest; 143 | sourceTree = ""; 144 | }; 145 | 456E44DF1543E44C002A05E3 /* Supporting Files */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 456E45231543F99A002A05E3 /* MacroUtils.h */, 149 | 456E44E01543E44C002A05E3 /* FmdbTest-Info.plist */, 150 | 456E44E11543E44C002A05E3 /* InfoPlist.strings */, 151 | 456E44E41543E44C002A05E3 /* main.m */, 152 | 456E44E61543E44C002A05E3 /* FmdbTest-Prefix.pch */, 153 | ); 154 | name = "Supporting Files"; 155 | sourceTree = ""; 156 | }; 157 | 456E44FC1543E44C002A05E3 /* FmdbTestTests */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | 456E45021543E44C002A05E3 /* FmdbTestTests.h */, 161 | 456E45031543E44C002A05E3 /* FmdbTestTests.m */, 162 | 456E44FD1543E44C002A05E3 /* Supporting Files */, 163 | ); 164 | path = FmdbTestTests; 165 | sourceTree = ""; 166 | }; 167 | 456E44FD1543E44C002A05E3 /* Supporting Files */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | 456E44FE1543E44C002A05E3 /* FmdbTestTests-Info.plist */, 171 | 456E44FF1543E44C002A05E3 /* InfoPlist.strings */, 172 | ); 173 | name = "Supporting Files"; 174 | sourceTree = ""; 175 | }; 176 | 456E450D1543E481002A05E3 /* FMDB */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 456E450F1543E481002A05E3 /* FMDatabase.h */, 180 | 456E45101543E481002A05E3 /* FMDatabase.m */, 181 | 456E45111543E481002A05E3 /* FMDatabaseAdditions.h */, 182 | 456E45121543E481002A05E3 /* FMDatabaseAdditions.m */, 183 | 456E45131543E481002A05E3 /* FMDatabasePool.h */, 184 | 456E45141543E481002A05E3 /* FMDatabasePool.m */, 185 | 456E45151543E481002A05E3 /* FMDatabaseQueue.h */, 186 | 456E45161543E481002A05E3 /* FMDatabaseQueue.m */, 187 | 456E45181543E481002A05E3 /* FMResultSet.h */, 188 | 456E45191543E481002A05E3 /* FMResultSet.m */, 189 | ); 190 | name = FMDB; 191 | path = fmdb; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXGroup section */ 195 | 196 | /* Begin PBXNativeTarget section */ 197 | 456E44D31543E44C002A05E3 /* FmdbTest */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = 456E45071543E44C002A05E3 /* Build configuration list for PBXNativeTarget "FmdbTest" */; 200 | buildPhases = ( 201 | 456E44D01543E44C002A05E3 /* Sources */, 202 | 456E44D11543E44C002A05E3 /* Frameworks */, 203 | 456E44D21543E44C002A05E3 /* Resources */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | ); 209 | name = FmdbTest; 210 | productName = FmdbTest; 211 | productReference = 456E44D41543E44C002A05E3 /* FmdbTest.app */; 212 | productType = "com.apple.product-type.application"; 213 | }; 214 | 456E44F41543E44C002A05E3 /* FmdbTestTests */ = { 215 | isa = PBXNativeTarget; 216 | buildConfigurationList = 456E450A1543E44C002A05E3 /* Build configuration list for PBXNativeTarget "FmdbTestTests" */; 217 | buildPhases = ( 218 | 456E44F01543E44C002A05E3 /* Sources */, 219 | 456E44F11543E44C002A05E3 /* Frameworks */, 220 | 456E44F21543E44C002A05E3 /* Resources */, 221 | 456E44F31543E44C002A05E3 /* ShellScript */, 222 | ); 223 | buildRules = ( 224 | ); 225 | dependencies = ( 226 | 456E44FB1543E44C002A05E3 /* PBXTargetDependency */, 227 | ); 228 | name = FmdbTestTests; 229 | productName = FmdbTestTests; 230 | productReference = 456E44F51543E44C002A05E3 /* FmdbTestTests.octest */; 231 | productType = "com.apple.product-type.bundle"; 232 | }; 233 | /* End PBXNativeTarget section */ 234 | 235 | /* Begin PBXProject section */ 236 | 456E44CB1543E44C002A05E3 /* Project object */ = { 237 | isa = PBXProject; 238 | attributes = { 239 | LastUpgradeCheck = 0420; 240 | ORGANIZATIONNAME = blog.devtang.com; 241 | }; 242 | buildConfigurationList = 456E44CE1543E44C002A05E3 /* Build configuration list for PBXProject "FmdbTest" */; 243 | compatibilityVersion = "Xcode 3.2"; 244 | developmentRegion = English; 245 | hasScannedForEncodings = 0; 246 | knownRegions = ( 247 | en, 248 | ); 249 | mainGroup = 456E44C91543E44C002A05E3; 250 | productRefGroup = 456E44D51543E44C002A05E3 /* Products */; 251 | projectDirPath = ""; 252 | projectRoot = ""; 253 | targets = ( 254 | 456E44D31543E44C002A05E3 /* FmdbTest */, 255 | 456E44F41543E44C002A05E3 /* FmdbTestTests */, 256 | ); 257 | }; 258 | /* End PBXProject section */ 259 | 260 | /* Begin PBXResourcesBuildPhase section */ 261 | 456E44D21543E44C002A05E3 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | 456E44E31543E44C002A05E3 /* InfoPlist.strings in Resources */, 266 | 456E44EF1543E44C002A05E3 /* ViewController.xib in Resources */, 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | }; 270 | 456E44F21543E44C002A05E3 /* Resources */ = { 271 | isa = PBXResourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | 456E45011543E44C002A05E3 /* InfoPlist.strings in Resources */, 275 | ); 276 | runOnlyForDeploymentPostprocessing = 0; 277 | }; 278 | /* End PBXResourcesBuildPhase section */ 279 | 280 | /* Begin PBXShellScriptBuildPhase section */ 281 | 456E44F31543E44C002A05E3 /* ShellScript */ = { 282 | isa = PBXShellScriptBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | ); 286 | inputPaths = ( 287 | ); 288 | outputPaths = ( 289 | ); 290 | runOnlyForDeploymentPostprocessing = 0; 291 | shellPath = /bin/sh; 292 | shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; 293 | }; 294 | /* End PBXShellScriptBuildPhase section */ 295 | 296 | /* Begin PBXSourcesBuildPhase section */ 297 | 456E44D01543E44C002A05E3 /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 456E44E51543E44C002A05E3 /* main.m in Sources */, 302 | 456E44E91543E44C002A05E3 /* AppDelegate.m in Sources */, 303 | 456E44EC1543E44C002A05E3 /* ViewController.m in Sources */, 304 | 456E451B1543E481002A05E3 /* FMDatabase.m in Sources */, 305 | 456E451C1543E481002A05E3 /* FMDatabaseAdditions.m in Sources */, 306 | 456E451D1543E481002A05E3 /* FMDatabasePool.m in Sources */, 307 | 456E451E1543E481002A05E3 /* FMDatabaseQueue.m in Sources */, 308 | 456E45201543E481002A05E3 /* FMResultSet.m in Sources */, 309 | ); 310 | runOnlyForDeploymentPostprocessing = 0; 311 | }; 312 | 456E44F01543E44C002A05E3 /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | 456E45041543E44C002A05E3 /* FmdbTestTests.m in Sources */, 317 | ); 318 | runOnlyForDeploymentPostprocessing = 0; 319 | }; 320 | /* End PBXSourcesBuildPhase section */ 321 | 322 | /* Begin PBXTargetDependency section */ 323 | 456E44FB1543E44C002A05E3 /* PBXTargetDependency */ = { 324 | isa = PBXTargetDependency; 325 | target = 456E44D31543E44C002A05E3 /* FmdbTest */; 326 | targetProxy = 456E44FA1543E44C002A05E3 /* PBXContainerItemProxy */; 327 | }; 328 | /* End PBXTargetDependency section */ 329 | 330 | /* Begin PBXVariantGroup section */ 331 | 456E44E11543E44C002A05E3 /* InfoPlist.strings */ = { 332 | isa = PBXVariantGroup; 333 | children = ( 334 | 456E44E21543E44C002A05E3 /* en */, 335 | ); 336 | name = InfoPlist.strings; 337 | sourceTree = ""; 338 | }; 339 | 456E44ED1543E44C002A05E3 /* ViewController.xib */ = { 340 | isa = PBXVariantGroup; 341 | children = ( 342 | 456E44EE1543E44C002A05E3 /* en */, 343 | ); 344 | name = ViewController.xib; 345 | sourceTree = ""; 346 | }; 347 | 456E44FF1543E44C002A05E3 /* InfoPlist.strings */ = { 348 | isa = PBXVariantGroup; 349 | children = ( 350 | 456E45001543E44C002A05E3 /* en */, 351 | ); 352 | name = InfoPlist.strings; 353 | sourceTree = ""; 354 | }; 355 | /* End PBXVariantGroup section */ 356 | 357 | /* Begin XCBuildConfiguration section */ 358 | 456E45051543E44C002A05E3 /* Debug */ = { 359 | isa = XCBuildConfiguration; 360 | buildSettings = { 361 | ALWAYS_SEARCH_USER_PATHS = NO; 362 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 363 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 364 | COPY_PHASE_STRIP = NO; 365 | GCC_C_LANGUAGE_STANDARD = gnu99; 366 | GCC_DYNAMIC_NO_PIC = NO; 367 | GCC_OPTIMIZATION_LEVEL = 0; 368 | GCC_PREPROCESSOR_DEFINITIONS = ( 369 | "DEBUG=1", 370 | "$(inherited)", 371 | ); 372 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 373 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 374 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 375 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 376 | GCC_WARN_UNUSED_VARIABLE = YES; 377 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 378 | SDKROOT = iphoneos; 379 | }; 380 | name = Debug; 381 | }; 382 | 456E45061543E44C002A05E3 /* Release */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | ALWAYS_SEARCH_USER_PATHS = NO; 386 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 387 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 388 | COPY_PHASE_STRIP = YES; 389 | GCC_C_LANGUAGE_STANDARD = gnu99; 390 | GCC_VERSION = com.apple.compilers.llvm.clang.1_0; 391 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; 392 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 393 | GCC_WARN_UNUSED_VARIABLE = YES; 394 | IPHONEOS_DEPLOYMENT_TARGET = 5.0; 395 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 396 | SDKROOT = iphoneos; 397 | VALIDATE_PRODUCT = YES; 398 | }; 399 | name = Release; 400 | }; 401 | 456E45081543E44C002A05E3 /* Debug */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 405 | GCC_PREFIX_HEADER = "FmdbTest/FmdbTest-Prefix.pch"; 406 | INFOPLIST_FILE = "FmdbTest/FmdbTest-Info.plist"; 407 | PRODUCT_NAME = "$(TARGET_NAME)"; 408 | WRAPPER_EXTENSION = app; 409 | }; 410 | name = Debug; 411 | }; 412 | 456E45091543E44C002A05E3 /* Release */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 416 | GCC_PREFIX_HEADER = "FmdbTest/FmdbTest-Prefix.pch"; 417 | INFOPLIST_FILE = "FmdbTest/FmdbTest-Info.plist"; 418 | PRODUCT_NAME = "$(TARGET_NAME)"; 419 | WRAPPER_EXTENSION = app; 420 | }; 421 | name = Release; 422 | }; 423 | 456E450B1543E44C002A05E3 /* Debug */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/FmdbTest.app/FmdbTest"; 427 | FRAMEWORK_SEARCH_PATHS = ( 428 | "$(SDKROOT)/Developer/Library/Frameworks", 429 | "$(DEVELOPER_LIBRARY_DIR)/Frameworks", 430 | ); 431 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 432 | GCC_PREFIX_HEADER = "FmdbTest/FmdbTest-Prefix.pch"; 433 | INFOPLIST_FILE = "FmdbTestTests/FmdbTestTests-Info.plist"; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | TEST_HOST = "$(BUNDLE_LOADER)"; 436 | WRAPPER_EXTENSION = octest; 437 | }; 438 | name = Debug; 439 | }; 440 | 456E450C1543E44C002A05E3 /* Release */ = { 441 | isa = XCBuildConfiguration; 442 | buildSettings = { 443 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/FmdbTest.app/FmdbTest"; 444 | FRAMEWORK_SEARCH_PATHS = ( 445 | "$(SDKROOT)/Developer/Library/Frameworks", 446 | "$(DEVELOPER_LIBRARY_DIR)/Frameworks", 447 | ); 448 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 449 | GCC_PREFIX_HEADER = "FmdbTest/FmdbTest-Prefix.pch"; 450 | INFOPLIST_FILE = "FmdbTestTests/FmdbTestTests-Info.plist"; 451 | PRODUCT_NAME = "$(TARGET_NAME)"; 452 | TEST_HOST = "$(BUNDLE_LOADER)"; 453 | WRAPPER_EXTENSION = octest; 454 | }; 455 | name = Release; 456 | }; 457 | /* End XCBuildConfiguration section */ 458 | 459 | /* Begin XCConfigurationList section */ 460 | 456E44CE1543E44C002A05E3 /* Build configuration list for PBXProject "FmdbTest" */ = { 461 | isa = XCConfigurationList; 462 | buildConfigurations = ( 463 | 456E45051543E44C002A05E3 /* Debug */, 464 | 456E45061543E44C002A05E3 /* Release */, 465 | ); 466 | defaultConfigurationIsVisible = 0; 467 | defaultConfigurationName = Release; 468 | }; 469 | 456E45071543E44C002A05E3 /* Build configuration list for PBXNativeTarget "FmdbTest" */ = { 470 | isa = XCConfigurationList; 471 | buildConfigurations = ( 472 | 456E45081543E44C002A05E3 /* Debug */, 473 | 456E45091543E44C002A05E3 /* Release */, 474 | ); 475 | defaultConfigurationIsVisible = 0; 476 | }; 477 | 456E450A1543E44C002A05E3 /* Build configuration list for PBXNativeTarget "FmdbTestTests" */ = { 478 | isa = XCConfigurationList; 479 | buildConfigurations = ( 480 | 456E450B1543E44C002A05E3 /* Debug */, 481 | 456E450C1543E44C002A05E3 /* Release */, 482 | ); 483 | defaultConfigurationIsVisible = 0; 484 | }; 485 | /* End XCConfigurationList section */ 486 | }; 487 | rootObject = 456E44CB1543E44C002A05E3 /* Project object */; 488 | } 489 | -------------------------------------------------------------------------------- /FmdbTest/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // FmdbTest 4 | // 5 | // Created by Tang Qiao on 12-4-22. 6 | // Copyright (c) 2012年 blog.devtang.com All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class ViewController; 12 | 13 | @interface AppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | 17 | @property (strong, nonatomic) ViewController *viewController; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /FmdbTest/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // FmdbTest 4 | // 5 | // Created by Tang Qiao on 12-4-22. 6 | // Copyright (c) 2012年 blog.devtang.com All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import "ViewController.h" 12 | 13 | @implementation AppDelegate 14 | 15 | @synthesize window = _window; 16 | @synthesize viewController = _viewController; 17 | 18 | - (void)dealloc 19 | { 20 | [_window release]; 21 | [_viewController release]; 22 | [super dealloc]; 23 | } 24 | 25 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 26 | { 27 | self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; 28 | // Override point for customization after application launch. 29 | self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController" bundle:nil] autorelease]; 30 | self.window.rootViewController = self.viewController; 31 | [self.window makeKeyAndVisible]; 32 | return YES; 33 | } 34 | 35 | - (void)applicationWillResignActive:(UIApplication *)application 36 | { 37 | /* 38 | 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. 39 | 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. 40 | */ 41 | } 42 | 43 | - (void)applicationDidEnterBackground:(UIApplication *)application 44 | { 45 | /* 46 | 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. 47 | If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 48 | */ 49 | } 50 | 51 | - (void)applicationWillEnterForeground:(UIApplication *)application 52 | { 53 | /* 54 | 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. 55 | */ 56 | } 57 | 58 | - (void)applicationDidBecomeActive:(UIApplication *)application 59 | { 60 | /* 61 | 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. 62 | */ 63 | } 64 | 65 | - (void)applicationWillTerminate:(UIApplication *)application 66 | { 67 | /* 68 | Called when the application is about to terminate. 69 | Save data if appropriate. 70 | See also applicationDidEnterBackground:. 71 | */ 72 | } 73 | 74 | @end 75 | -------------------------------------------------------------------------------- /FmdbTest/FmdbTest-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFiles 12 | 13 | CFBundleIdentifier 14 | com.youdao.${PRODUCT_NAME:rfc1034identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | ${PRODUCT_NAME} 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 1.0 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | 1.0 27 | LSRequiresIPhoneOS 28 | 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /FmdbTest/FmdbTest-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'FmdbTest' target in the 'FmdbTest' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_4_0 8 | #warning "This project uses features only available in iOS SDK 4.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #import "MacroUtils.h" 15 | #endif 16 | 17 | -------------------------------------------------------------------------------- /FmdbTest/MacroUtils.h: -------------------------------------------------------------------------------- 1 | // Author: Tang Qiao 2 | // Date: 2012-3-2 3 | // 4 | // The macro is inspired from: 5 | // http://www.yifeiyang.net/iphone-development-skills-of-the-debugging-chapter-2-save-the-log/ 6 | 7 | #ifdef DEBUG 8 | #define debugLog(...) NSLog(__VA_ARGS__) 9 | #define debugMethod() NSLog(@"%s", __func__) 10 | #else 11 | #define debugLog(...) 12 | #define debugMethod() 13 | #endif 14 | 15 | #define EMPTY_STRING @"" 16 | 17 | #define STR(key) NSLocalizedString(key, nil) 18 | 19 | #define PATH_OF_APP_HOME NSHomeDirectory() 20 | #define PATH_OF_TEMP NSTemporaryDirectory() 21 | #define PATH_OF_DOCUMENT [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] 22 | -------------------------------------------------------------------------------- /FmdbTest/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // FmdbTest 4 | // 5 | // Created by Tang Qiao on 12-4-22. 6 | // Copyright (c) 2012年 blog.devtang.com All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FmdbTest/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // FmdbTest 4 | // 5 | // Created by Tang Qiao on 12-4-22. 6 | // Copyright (c) 2012年 blog.devtang.com All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "FMDatabase.h" 11 | #import "FMDatabaseQueue.h" 12 | 13 | @interface ViewController() 14 | 15 | @property (nonatomic, retain) NSString * dbPath; 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | @synthesize dbPath; 22 | 23 | #pragma mark - SQL Operations 24 | 25 | - (IBAction)createTable:(id)sender { 26 | debugMethod(); 27 | NSFileManager * fileManager = [NSFileManager defaultManager]; 28 | if ([fileManager fileExistsAtPath:self.dbPath] == NO) { 29 | // create it 30 | FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath]; 31 | if ([db open]) { 32 | NSString * sql = @"CREATE TABLE 'User' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , 'name' VARCHAR(30), 'password' VARCHAR(30))"; 33 | BOOL res = [db executeUpdate:sql]; 34 | if (!res) { 35 | debugLog(@"error when creating db table"); 36 | } else { 37 | debugLog(@"succ to creating db table"); 38 | } 39 | [db close]; 40 | } else { 41 | debugLog(@"error when open db"); 42 | } 43 | } 44 | } 45 | 46 | 47 | - (IBAction)insertData:(id)sender { 48 | static int idx = 1; 49 | FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath]; 50 | if ([db open]) { 51 | NSString * sql = @"insert into user (name, password) values(?, ?) "; 52 | NSString * name = [NSString stringWithFormat:@"tangqiao%d", idx++]; 53 | BOOL res = [db executeUpdate:sql, name, @"boy"]; 54 | if (!res) { 55 | debugLog(@"error to insert data"); 56 | } else { 57 | debugLog(@"succ to insert data"); 58 | } 59 | [db close]; 60 | } 61 | } 62 | 63 | 64 | - (IBAction)queryData:(id)sender { 65 | debugMethod(); 66 | FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath]; 67 | if ([db open]) { 68 | NSString * sql = @"select * from user"; 69 | FMResultSet * rs = [db executeQuery:sql]; 70 | while ([rs next]) { 71 | int userId = [rs intForColumn:@"id"]; 72 | NSString * name = [rs stringForColumn:@"name"]; 73 | NSString * pass = [rs stringForColumn:@"password"]; 74 | debugLog(@"user id = %d, name = %@, pass = %@", userId, name, pass); 75 | } 76 | [db close]; 77 | } 78 | } 79 | 80 | - (IBAction)clearAll:(id)sender { 81 | FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath]; 82 | if ([db open]) { 83 | NSString * sql = @"delete from user"; 84 | BOOL res = [db executeUpdate:sql]; 85 | if (!res) { 86 | debugLog(@"error to delete db data"); 87 | } else { 88 | debugLog(@"succ to deleta db data"); 89 | } 90 | [db close]; 91 | } 92 | } 93 | 94 | - (IBAction)multithread:(id)sender { 95 | FMDatabaseQueue * queue = [FMDatabaseQueue databaseQueueWithPath:self.dbPath]; 96 | dispatch_queue_t q1 = dispatch_queue_create("queue1", NULL); 97 | dispatch_queue_t q2 = dispatch_queue_create("queue2", NULL); 98 | 99 | dispatch_async(q1, ^{ 100 | for (int i = 0; i < 100; ++i) { 101 | [queue inDatabase:^(FMDatabase *db) { 102 | NSString * sql = @"insert into user (name, password) values(?, ?) "; 103 | NSString * name = [NSString stringWithFormat:@"queue111 %d", i]; 104 | BOOL res = [db executeUpdate:sql, name, @"boy"]; 105 | if (!res) { 106 | debugLog(@"error to add db data: %@", name); 107 | } else { 108 | debugLog(@"succ to add db data: %@", name); 109 | } 110 | }]; 111 | } 112 | }); 113 | 114 | dispatch_async(q2, ^{ 115 | for (int i = 0; i < 100; ++i) { 116 | [queue inDatabase:^(FMDatabase *db) { 117 | NSString * sql = @"insert into user (name, password) values(?, ?) "; 118 | NSString * name = [NSString stringWithFormat:@"queue222 %d", i]; 119 | BOOL res = [db executeUpdate:sql, name, @"boy"]; 120 | if (!res) { 121 | debugLog(@"error to add db data: %@", name); 122 | } else { 123 | debugLog(@"succ to add db data: %@", name); 124 | } 125 | }]; 126 | } 127 | }); 128 | } 129 | 130 | - (void)didReceiveMemoryWarning 131 | { 132 | [super didReceiveMemoryWarning]; 133 | // Release any cached data, images, etc that aren't in use. 134 | } 135 | 136 | #pragma mark - View lifecycle 137 | 138 | - (void)viewDidLoad 139 | { 140 | [super viewDidLoad]; 141 | // Do any additional setup after loading the view, typically from a nib. 142 | NSString * doc = PATH_OF_DOCUMENT; 143 | NSString * path = [doc stringByAppendingPathComponent:@"user.sqlite"]; 144 | self.dbPath = path; 145 | } 146 | 147 | - (void)viewDidUnload 148 | { 149 | [super viewDidUnload]; 150 | // Release any retained subviews of the main view. 151 | // e.g. self.myOutlet = nil; 152 | } 153 | 154 | - (void)viewWillAppear:(BOOL)animated 155 | { 156 | [super viewWillAppear:animated]; 157 | } 158 | 159 | - (void)viewDidAppear:(BOOL)animated 160 | { 161 | [super viewDidAppear:animated]; 162 | } 163 | 164 | - (void)viewWillDisappear:(BOOL)animated 165 | { 166 | [super viewWillDisappear:animated]; 167 | } 168 | 169 | - (void)viewDidDisappear:(BOOL)animated 170 | { 171 | [super viewDidDisappear:animated]; 172 | } 173 | 174 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 175 | { 176 | // Return YES for supported orientations 177 | return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown); 178 | } 179 | 180 | @end 181 | -------------------------------------------------------------------------------- /FmdbTest/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /FmdbTest/en.lproj/ViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1280 5 | 11D50b 6 | 1938 7 | 1138.32 8 | 568.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 933 12 | 13 | 14 | IBProxyObject 15 | IBUIView 16 | IBUIButton 17 | 18 | 19 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 20 | 21 | 22 | PluginDependencyRecalculationVersion 23 | 24 | 25 | 26 | 27 | IBFilesOwner 28 | IBCocoaTouchFramework 29 | 30 | 31 | IBFirstResponder 32 | IBCocoaTouchFramework 33 | 34 | 35 | 36 | 274 37 | 38 | 39 | 40 | 292 41 | {{103, 101}, {116, 37}} 42 | 43 | 44 | 45 | _NS:225 46 | NO 47 | IBCocoaTouchFramework 48 | 0 49 | 0 50 | 1 51 | Create Table 52 | 53 | 3 54 | MQA 55 | 56 | 57 | 1 58 | MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA 59 | 60 | 61 | 3 62 | MC41AA 63 | 64 | 65 | 2 66 | 15 67 | 68 | 69 | Helvetica-Bold 70 | 15 71 | 16 72 | 73 | 74 | 75 | 76 | 292 77 | {{104, 163}, {115, 37}} 78 | 79 | 80 | 81 | _NS:225 82 | NO 83 | IBCocoaTouchFramework 84 | 0 85 | 0 86 | 1 87 | Insert 88 | 89 | 90 | 1 91 | MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 292 100 | {{104, 230}, {115, 37}} 101 | 102 | 103 | 104 | _NS:225 105 | NO 106 | IBCocoaTouchFramework 107 | 0 108 | 0 109 | 1 110 | Query 111 | 112 | 113 | 1 114 | MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 292 123 | {{103, 369}, {116, 37}} 124 | 125 | 126 | 127 | _NS:225 128 | NO 129 | IBCocoaTouchFramework 130 | 0 131 | 0 132 | 1 133 | multi-thread 134 | 135 | 136 | 1 137 | MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 292 146 | {{103, 294}, {116, 37}} 147 | 148 | 149 | 150 | _NS:225 151 | NO 152 | IBCocoaTouchFramework 153 | 0 154 | 0 155 | 1 156 | Clear All 157 | 158 | 159 | 1 160 | MC4xOTYwNzg0MzQ2IDAuMzA5ODAzOTMyOSAwLjUyMTU2ODY1NgA 161 | 162 | 163 | 164 | 165 | 166 | 167 | {{0, 20}, {320, 460}} 168 | 169 | 170 | 171 | 172 | 3 173 | MC43NQA 174 | 175 | 2 176 | 177 | 178 | NO 179 | 180 | IBCocoaTouchFramework 181 | 182 | 183 | 184 | 185 | 186 | 187 | view 188 | 189 | 190 | 191 | 7 192 | 193 | 194 | 195 | createTable: 196 | 197 | 198 | 7 199 | 200 | 11 201 | 202 | 203 | 204 | insertData: 205 | 206 | 207 | 7 208 | 209 | 12 210 | 211 | 212 | 213 | queryData: 214 | 215 | 216 | 7 217 | 218 | 13 219 | 220 | 221 | 222 | multithread: 223 | 224 | 225 | 7 226 | 227 | 15 228 | 229 | 230 | 231 | clearAll: 232 | 233 | 234 | 7 235 | 236 | 17 237 | 238 | 239 | 240 | 241 | 242 | 0 243 | 244 | 245 | 246 | 247 | 248 | -1 249 | 250 | 251 | File's Owner 252 | 253 | 254 | -2 255 | 256 | 257 | 258 | 259 | 6 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 8 272 | 273 | 274 | 275 | 276 | 9 277 | 278 | 279 | 280 | 281 | 10 282 | 283 | 284 | 285 | 286 | 14 287 | 288 | 289 | 290 | 291 | 16 292 | 293 | 294 | 295 | 296 | 297 | 298 | ViewController 299 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 300 | UIResponder 301 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 302 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 303 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 304 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 305 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 306 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 307 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 308 | 309 | 310 | 311 | 312 | 313 | 17 314 | 315 | 316 | 317 | 318 | ViewController 319 | UIViewController 320 | 321 | IBProjectSource 322 | ./Classes/ViewController.h 323 | 324 | 325 | 326 | 327 | 0 328 | IBCocoaTouchFramework 329 | YES 330 | 3 331 | 933 332 | 333 | 334 | -------------------------------------------------------------------------------- /FmdbTest/fmdb/FMDatabase.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "sqlite3.h" 3 | #import "FMResultSet.h" 4 | #import "FMDatabasePool.h" 5 | 6 | 7 | #if ! __has_feature(objc_arc) 8 | #define FMDBAutorelease(__v) ([__v autorelease]); 9 | #define FMDBReturnAutoreleased FMDBAutorelease 10 | 11 | #define FMDBRetain(__v) ([__v retain]); 12 | #define FMDBReturnRetained FMDBRetain 13 | 14 | #define FMDBRelease(__v) ([__v release]); 15 | #else 16 | // -fobjc-arc 17 | #define FMDBAutorelease(__v) 18 | #define FMDBReturnAutoreleased(__v) (__v) 19 | 20 | #define FMDBRetain(__v) 21 | #define FMDBReturnRetained(__v) (__v) 22 | 23 | #define FMDBRelease(__v) 24 | #endif 25 | 26 | 27 | @interface FMDatabase : NSObject { 28 | 29 | sqlite3* _db; 30 | NSString* _databasePath; 31 | BOOL _logsErrors; 32 | BOOL _crashOnErrors; 33 | BOOL _traceExecution; 34 | BOOL _checkedOut; 35 | BOOL _shouldCacheStatements; 36 | BOOL _isExecutingStatement; 37 | BOOL _inTransaction; 38 | int _busyRetryTimeout; 39 | 40 | NSMutableDictionary *_cachedStatements; 41 | NSMutableSet *_openResultSets; 42 | NSMutableSet *_openFunctions; 43 | 44 | } 45 | 46 | 47 | @property (assign) BOOL traceExecution; 48 | @property (assign) BOOL checkedOut; 49 | @property (assign) int busyRetryTimeout; 50 | @property (assign) BOOL crashOnErrors; 51 | @property (assign) BOOL logsErrors; 52 | @property (retain) NSMutableDictionary *cachedStatements; 53 | 54 | 55 | + (id)databaseWithPath:(NSString*)inPath; 56 | - (id)initWithPath:(NSString*)inPath; 57 | 58 | - (BOOL)open; 59 | #if SQLITE_VERSION_NUMBER >= 3005000 60 | - (BOOL)openWithFlags:(int)flags; 61 | #endif 62 | - (BOOL)close; 63 | - (BOOL)goodConnection; 64 | - (void)clearCachedStatements; 65 | - (void)closeOpenResultSets; 66 | - (BOOL)hasOpenResultSets; 67 | 68 | // encryption methods. You need to have purchased the sqlite encryption extensions for these to work. 69 | - (BOOL)setKey:(NSString*)key; 70 | - (BOOL)rekey:(NSString*)key; 71 | 72 | - (NSString *)databasePath; 73 | 74 | - (NSString*)lastErrorMessage; 75 | 76 | - (int)lastErrorCode; 77 | - (BOOL)hadError; 78 | - (NSError*)lastError; 79 | 80 | - (sqlite_int64)lastInsertRowId; 81 | 82 | - (sqlite3*)sqliteHandle; 83 | 84 | - (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ...; 85 | - (BOOL)executeUpdate:(NSString*)sql, ...; 86 | - (BOOL)executeUpdateWithFormat:(NSString *)format, ...; 87 | - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments; 88 | - (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments; 89 | 90 | - (FMResultSet *)executeQuery:(NSString*)sql, ...; 91 | - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...; 92 | - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments; 93 | - (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments; 94 | 95 | - (BOOL)rollback; 96 | - (BOOL)commit; 97 | - (BOOL)beginTransaction; 98 | - (BOOL)beginDeferredTransaction; 99 | - (BOOL)inTransaction; 100 | - (BOOL)shouldCacheStatements; 101 | - (void)setShouldCacheStatements:(BOOL)value; 102 | 103 | #if SQLITE_VERSION_NUMBER >= 3007000 104 | - (BOOL)startSavePointWithName:(NSString*)name error:(NSError**)outErr; 105 | - (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError**)outErr; 106 | - (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError**)outErr; 107 | - (NSError*)inSavePoint:(void (^)(BOOL *rollback))block; 108 | #endif 109 | 110 | + (BOOL)isSQLiteThreadSafe; 111 | + (NSString*)sqliteLibVersion; 112 | 113 | - (int)changes; 114 | 115 | - (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block; 116 | 117 | @end 118 | 119 | @interface FMStatement : NSObject { 120 | sqlite3_stmt *_statement; 121 | NSString *_query; 122 | long _useCount; 123 | } 124 | 125 | @property (assign) long useCount; 126 | @property (retain) NSString *query; 127 | @property (assign) sqlite3_stmt *statement; 128 | 129 | - (void)close; 130 | - (void)reset; 131 | 132 | @end 133 | 134 | -------------------------------------------------------------------------------- /FmdbTest/fmdb/FMDatabase.m: -------------------------------------------------------------------------------- 1 | #import "FMDatabase.h" 2 | #import "unistd.h" 3 | #import 4 | 5 | @interface FMDatabase () 6 | 7 | - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args; 8 | - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args; 9 | @end 10 | 11 | @implementation FMDatabase 12 | @synthesize cachedStatements=_cachedStatements; 13 | @synthesize logsErrors=_logsErrors; 14 | @synthesize crashOnErrors=_crashOnErrors; 15 | @synthesize busyRetryTimeout=_busyRetryTimeout; 16 | @synthesize checkedOut=_checkedOut; 17 | @synthesize traceExecution=_traceExecution; 18 | 19 | + (id)databaseWithPath:(NSString*)aPath { 20 | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]); 21 | } 22 | 23 | + (NSString*)sqliteLibVersion { 24 | return [NSString stringWithFormat:@"%s", sqlite3_libversion()]; 25 | } 26 | 27 | + (BOOL)isSQLiteThreadSafe { 28 | // make sure to read the sqlite headers on this guy! 29 | return sqlite3_threadsafe(); 30 | } 31 | 32 | - (id)initWithPath:(NSString*)aPath { 33 | 34 | assert(sqlite3_threadsafe()); // whoa there big boy- gotta make sure sqlite it happy with what we're going to do. 35 | 36 | self = [super init]; 37 | 38 | if (self) { 39 | _databasePath = [aPath copy]; 40 | _openResultSets = [[NSMutableSet alloc] init]; 41 | _db = 0x00; 42 | _logsErrors = 0x00; 43 | _crashOnErrors = 0x00; 44 | _busyRetryTimeout = 0x00; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (void)finalize { 51 | [self close]; 52 | [super finalize]; 53 | } 54 | 55 | - (void)dealloc { 56 | [self close]; 57 | FMDBRelease(_openResultSets); 58 | FMDBRelease(_cachedStatements); 59 | FMDBRelease(_databasePath); 60 | FMDBRelease(_openFunctions); 61 | 62 | #if ! __has_feature(objc_arc) 63 | [super dealloc]; 64 | #endif 65 | } 66 | 67 | - (NSString *)databasePath { 68 | return _databasePath; 69 | } 70 | 71 | - (sqlite3*)sqliteHandle { 72 | return _db; 73 | } 74 | 75 | - (BOOL)open { 76 | if (_db) { 77 | return YES; 78 | } 79 | 80 | int err = sqlite3_open((_databasePath ? [_databasePath fileSystemRepresentation] : ":memory:"), &_db ); 81 | if(err != SQLITE_OK) { 82 | NSLog(@"error opening!: %d", err); 83 | return NO; 84 | } 85 | 86 | return YES; 87 | } 88 | 89 | #if SQLITE_VERSION_NUMBER >= 3005000 90 | - (BOOL)openWithFlags:(int)flags { 91 | int err = sqlite3_open_v2((_databasePath ? [_databasePath fileSystemRepresentation] : ":memory:"), &_db, flags, NULL /* Name of VFS module to use */); 92 | if(err != SQLITE_OK) { 93 | NSLog(@"error opening!: %d", err); 94 | return NO; 95 | } 96 | return YES; 97 | } 98 | #endif 99 | 100 | 101 | - (BOOL)close { 102 | 103 | [self clearCachedStatements]; 104 | [self closeOpenResultSets]; 105 | 106 | if (!_db) { 107 | return YES; 108 | } 109 | 110 | int rc; 111 | BOOL retry; 112 | int numberOfRetries = 0; 113 | BOOL triedFinalizingOpenStatements = NO; 114 | 115 | do { 116 | retry = NO; 117 | rc = sqlite3_close(_db); 118 | 119 | if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { 120 | 121 | retry = YES; 122 | usleep(20); 123 | 124 | if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { 125 | NSLog(@"%s:%d", __FUNCTION__, __LINE__); 126 | NSLog(@"Database busy, unable to close"); 127 | return NO; 128 | } 129 | 130 | if (!triedFinalizingOpenStatements) { 131 | triedFinalizingOpenStatements = YES; 132 | sqlite3_stmt *pStmt; 133 | while ((pStmt = sqlite3_next_stmt(_db, 0x00)) !=0) { 134 | NSLog(@"Closing leaked statement"); 135 | sqlite3_finalize(pStmt); 136 | } 137 | } 138 | } 139 | else if (SQLITE_OK != rc) { 140 | NSLog(@"error closing!: %d", rc); 141 | } 142 | } 143 | while (retry); 144 | 145 | _db = nil; 146 | return YES; 147 | } 148 | 149 | - (void)clearCachedStatements { 150 | 151 | for (FMStatement *cachedStmt in [_cachedStatements objectEnumerator]) { 152 | [cachedStmt close]; 153 | } 154 | 155 | [_cachedStatements removeAllObjects]; 156 | } 157 | 158 | - (BOOL)hasOpenResultSets { 159 | return [_openResultSets count] > 0; 160 | } 161 | 162 | - (void)closeOpenResultSets { 163 | 164 | //Copy the set so we don't get mutation errors 165 | NSMutableSet *openSetCopy = FMDBReturnAutoreleased([_openResultSets copy]); 166 | for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) { 167 | FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue]; 168 | 169 | [rs setParentDB:nil]; 170 | [rs close]; 171 | 172 | [_openResultSets removeObject:rsInWrappedInATastyValueMeal]; 173 | } 174 | } 175 | 176 | - (void)resultSetDidClose:(FMResultSet *)resultSet { 177 | NSValue *setValue = [NSValue valueWithNonretainedObject:resultSet]; 178 | 179 | [_openResultSets removeObject:setValue]; 180 | } 181 | 182 | - (FMStatement*)cachedStatementForQuery:(NSString*)query { 183 | return [_cachedStatements objectForKey:query]; 184 | } 185 | 186 | - (void)setCachedStatement:(FMStatement*)statement forQuery:(NSString*)query { 187 | 188 | query = [query copy]; // in case we got handed in a mutable string... 189 | 190 | [statement setQuery:query]; 191 | 192 | [_cachedStatements setObject:statement forKey:query]; 193 | 194 | FMDBRelease(query); 195 | } 196 | 197 | 198 | - (BOOL)rekey:(NSString*)key { 199 | #ifdef SQLITE_HAS_CODEC 200 | if (!key) { 201 | return NO; 202 | } 203 | 204 | int rc = sqlite3_rekey(_db, [key UTF8String], (int)strlen([key UTF8String])); 205 | 206 | if (rc != SQLITE_OK) { 207 | NSLog(@"error on rekey: %d", rc); 208 | NSLog(@"%@", [self lastErrorMessage]); 209 | } 210 | 211 | return (rc == SQLITE_OK); 212 | #else 213 | return NO; 214 | #endif 215 | } 216 | 217 | - (BOOL)setKey:(NSString*)key { 218 | #ifdef SQLITE_HAS_CODEC 219 | if (!key) { 220 | return NO; 221 | } 222 | 223 | int rc = sqlite3_key(_db, [key UTF8String], (int)strlen([key UTF8String])); 224 | 225 | return (rc == SQLITE_OK); 226 | #else 227 | return NO; 228 | #endif 229 | } 230 | 231 | - (BOOL)goodConnection { 232 | 233 | if (!_db) { 234 | return NO; 235 | } 236 | 237 | FMResultSet *rs = [self executeQuery:@"select name from sqlite_master where type='table'"]; 238 | 239 | if (rs) { 240 | [rs close]; 241 | return YES; 242 | } 243 | 244 | return NO; 245 | } 246 | 247 | - (void)warnInUse { 248 | NSLog(@"The FMDatabase %@ is currently in use.", self); 249 | 250 | #ifndef NS_BLOCK_ASSERTIONS 251 | if (_crashOnErrors) { 252 | abort(); 253 | NSAssert1(false, @"The FMDatabase %@ is currently in use.", self); 254 | } 255 | #endif 256 | } 257 | 258 | - (BOOL)databaseExists { 259 | 260 | if (!_db) { 261 | 262 | NSLog(@"The FMDatabase %@ is not open.", self); 263 | 264 | #ifndef NS_BLOCK_ASSERTIONS 265 | if (_crashOnErrors) { 266 | abort(); 267 | NSAssert1(false, @"The FMDatabase %@ is not open.", self); 268 | } 269 | #endif 270 | 271 | return NO; 272 | } 273 | 274 | return YES; 275 | } 276 | 277 | - (NSString*)lastErrorMessage { 278 | return [NSString stringWithUTF8String:sqlite3_errmsg(_db)]; 279 | } 280 | 281 | - (BOOL)hadError { 282 | int lastErrCode = [self lastErrorCode]; 283 | 284 | return (lastErrCode > SQLITE_OK && lastErrCode < SQLITE_ROW); 285 | } 286 | 287 | - (int)lastErrorCode { 288 | return sqlite3_errcode(_db); 289 | } 290 | 291 | - (NSError*)lastError { 292 | return [NSError errorWithDomain:@"FMDatabase" code:sqlite3_errcode(_db) userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage] forKey:NSLocalizedDescriptionKey]]; 293 | } 294 | 295 | - (sqlite_int64)lastInsertRowId { 296 | 297 | if (_isExecutingStatement) { 298 | [self warnInUse]; 299 | return NO; 300 | } 301 | 302 | _isExecutingStatement = YES; 303 | 304 | sqlite_int64 ret = sqlite3_last_insert_rowid(_db); 305 | 306 | _isExecutingStatement = NO; 307 | 308 | return ret; 309 | } 310 | 311 | - (int)changes { 312 | if (_isExecutingStatement) { 313 | [self warnInUse]; 314 | return 0; 315 | } 316 | 317 | _isExecutingStatement = YES; 318 | 319 | int ret = sqlite3_changes(_db); 320 | 321 | _isExecutingStatement = NO; 322 | 323 | return ret; 324 | } 325 | 326 | - (void)bindObject:(id)obj toColumn:(int)idx inStatement:(sqlite3_stmt*)pStmt { 327 | 328 | if ((!obj) || ((NSNull *)obj == [NSNull null])) { 329 | sqlite3_bind_null(pStmt, idx); 330 | } 331 | 332 | // FIXME - someday check the return codes on these binds. 333 | else if ([obj isKindOfClass:[NSData class]]) { 334 | sqlite3_bind_blob(pStmt, idx, [obj bytes], (int)[obj length], SQLITE_STATIC); 335 | } 336 | else if ([obj isKindOfClass:[NSDate class]]) { 337 | sqlite3_bind_double(pStmt, idx, [obj timeIntervalSince1970]); 338 | } 339 | else if ([obj isKindOfClass:[NSNumber class]]) { 340 | 341 | if (strcmp([obj objCType], @encode(BOOL)) == 0) { 342 | sqlite3_bind_int(pStmt, idx, ([obj boolValue] ? 1 : 0)); 343 | } 344 | else if (strcmp([obj objCType], @encode(int)) == 0) { 345 | sqlite3_bind_int64(pStmt, idx, [obj longValue]); 346 | } 347 | else if (strcmp([obj objCType], @encode(long)) == 0) { 348 | sqlite3_bind_int64(pStmt, idx, [obj longValue]); 349 | } 350 | else if (strcmp([obj objCType], @encode(long long)) == 0) { 351 | sqlite3_bind_int64(pStmt, idx, [obj longLongValue]); 352 | } 353 | else if (strcmp([obj objCType], @encode(unsigned long long)) == 0) { 354 | sqlite3_bind_int64(pStmt, idx, [obj unsignedLongLongValue]); 355 | } 356 | else if (strcmp([obj objCType], @encode(float)) == 0) { 357 | sqlite3_bind_double(pStmt, idx, [obj floatValue]); 358 | } 359 | else if (strcmp([obj objCType], @encode(double)) == 0) { 360 | sqlite3_bind_double(pStmt, idx, [obj doubleValue]); 361 | } 362 | else { 363 | sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC); 364 | } 365 | } 366 | else { 367 | sqlite3_bind_text(pStmt, idx, [[obj description] UTF8String], -1, SQLITE_STATIC); 368 | } 369 | } 370 | 371 | - (void)extractSQL:(NSString *)sql argumentsList:(va_list)args intoString:(NSMutableString *)cleanedSQL arguments:(NSMutableArray *)arguments { 372 | 373 | NSUInteger length = [sql length]; 374 | unichar last = '\0'; 375 | for (NSUInteger i = 0; i < length; ++i) { 376 | id arg = nil; 377 | unichar current = [sql characterAtIndex:i]; 378 | unichar add = current; 379 | if (last == '%') { 380 | switch (current) { 381 | case '@': 382 | arg = va_arg(args, id); break; 383 | case 'c': 384 | // warning: second argument to 'va_arg' is of promotable type 'char'; this va_arg has undefined behavior because arguments will be promoted to 'int' 385 | arg = [NSString stringWithFormat:@"%c", va_arg(args, int)]; break; 386 | case 's': 387 | arg = [NSString stringWithUTF8String:va_arg(args, char*)]; break; 388 | case 'd': 389 | case 'D': 390 | case 'i': 391 | arg = [NSNumber numberWithInt:va_arg(args, int)]; break; 392 | case 'u': 393 | case 'U': 394 | arg = [NSNumber numberWithUnsignedInt:va_arg(args, unsigned int)]; break; 395 | case 'h': 396 | i++; 397 | if (i < length && [sql characterAtIndex:i] == 'i') { 398 | // warning: second argument to 'va_arg' is of promotable type 'short'; this va_arg has undefined behavior because arguments will be promoted to 'int' 399 | arg = [NSNumber numberWithShort:va_arg(args, int)]; 400 | } 401 | else if (i < length && [sql characterAtIndex:i] == 'u') { 402 | // warning: second argument to 'va_arg' is of promotable type 'unsigned short'; this va_arg has undefined behavior because arguments will be promoted to 'int' 403 | arg = [NSNumber numberWithUnsignedShort:va_arg(args, uint)]; 404 | } 405 | else { 406 | i--; 407 | } 408 | break; 409 | case 'q': 410 | i++; 411 | if (i < length && [sql characterAtIndex:i] == 'i') { 412 | arg = [NSNumber numberWithLongLong:va_arg(args, long long)]; 413 | } 414 | else if (i < length && [sql characterAtIndex:i] == 'u') { 415 | arg = [NSNumber numberWithUnsignedLongLong:va_arg(args, unsigned long long)]; 416 | } 417 | else { 418 | i--; 419 | } 420 | break; 421 | case 'f': 422 | arg = [NSNumber numberWithDouble:va_arg(args, double)]; break; 423 | case 'g': 424 | // warning: second argument to 'va_arg' is of promotable type 'float'; this va_arg has undefined behavior because arguments will be promoted to 'double' 425 | arg = [NSNumber numberWithFloat:va_arg(args, double)]; break; 426 | case 'l': 427 | i++; 428 | if (i < length) { 429 | unichar next = [sql characterAtIndex:i]; 430 | if (next == 'l') { 431 | i++; 432 | if (i < length && [sql characterAtIndex:i] == 'd') { 433 | //%lld 434 | arg = [NSNumber numberWithLongLong:va_arg(args, long long)]; 435 | } 436 | else if (i < length && [sql characterAtIndex:i] == 'u') { 437 | //%llu 438 | arg = [NSNumber numberWithUnsignedLongLong:va_arg(args, unsigned long long)]; 439 | } 440 | else { 441 | i--; 442 | } 443 | } 444 | else if (next == 'd') { 445 | //%ld 446 | arg = [NSNumber numberWithLong:va_arg(args, long)]; 447 | } 448 | else if (next == 'u') { 449 | //%lu 450 | arg = [NSNumber numberWithUnsignedLong:va_arg(args, unsigned long)]; 451 | } 452 | else { 453 | i--; 454 | } 455 | } 456 | else { 457 | i--; 458 | } 459 | break; 460 | default: 461 | // something else that we can't interpret. just pass it on through like normal 462 | break; 463 | } 464 | } 465 | else if (current == '%') { 466 | // percent sign; skip this character 467 | add = '\0'; 468 | } 469 | 470 | if (arg != nil) { 471 | [cleanedSQL appendString:@"?"]; 472 | [arguments addObject:arg]; 473 | } 474 | else if (add != '\0') { 475 | [cleanedSQL appendFormat:@"%C", add]; 476 | } 477 | last = current; 478 | } 479 | } 480 | 481 | - (FMResultSet *)executeQuery:(NSString *)sql withParameterDictionary:(NSDictionary *)arguments { 482 | return [self executeQuery:sql withArgumentsInArray:nil orDictionary:arguments orVAList:nil]; 483 | } 484 | 485 | - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args { 486 | 487 | if (![self databaseExists]) { 488 | return 0x00; 489 | } 490 | 491 | if (_isExecutingStatement) { 492 | [self warnInUse]; 493 | return 0x00; 494 | } 495 | 496 | _isExecutingStatement = YES; 497 | 498 | int rc = 0x00; 499 | sqlite3_stmt *pStmt = 0x00; 500 | FMStatement *statement = 0x00; 501 | FMResultSet *rs = 0x00; 502 | 503 | if (_traceExecution && sql) { 504 | NSLog(@"%@ executeQuery: %@", self, sql); 505 | } 506 | 507 | if (_shouldCacheStatements) { 508 | statement = [self cachedStatementForQuery:sql]; 509 | pStmt = statement ? [statement statement] : 0x00; 510 | } 511 | 512 | int numberOfRetries = 0; 513 | BOOL retry = NO; 514 | 515 | if (!pStmt) { 516 | do { 517 | retry = NO; 518 | rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); 519 | 520 | if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { 521 | retry = YES; 522 | usleep(20); 523 | 524 | if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { 525 | NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); 526 | NSLog(@"Database busy"); 527 | sqlite3_finalize(pStmt); 528 | _isExecutingStatement = NO; 529 | return nil; 530 | } 531 | } 532 | else if (SQLITE_OK != rc) { 533 | 534 | if (_logsErrors) { 535 | NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); 536 | NSLog(@"DB Query: %@", sql); 537 | NSLog(@"DB Path: %@", _databasePath); 538 | #ifndef NS_BLOCK_ASSERTIONS 539 | if (_crashOnErrors) { 540 | abort(); 541 | NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); 542 | } 543 | #endif 544 | } 545 | 546 | sqlite3_finalize(pStmt); 547 | _isExecutingStatement = NO; 548 | return nil; 549 | } 550 | } 551 | while (retry); 552 | } 553 | 554 | id obj; 555 | int idx = 0; 556 | int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!) 557 | 558 | // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support 559 | if (dictionaryArgs) { 560 | 561 | for (NSString *dictionaryKey in [dictionaryArgs allKeys]) { 562 | 563 | // Prefix the key with a colon. 564 | NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey]; 565 | 566 | // Get the index for the parameter name. 567 | int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]); 568 | 569 | FMDBRelease(parameterName); 570 | 571 | if (namedIdx > 0) { 572 | // Standard binding from here. 573 | [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt]; 574 | } 575 | else { 576 | NSLog(@"Could not find index for %@", dictionaryKey); 577 | } 578 | } 579 | 580 | // we need the count of params to avoid an error below. 581 | idx = (int) [[dictionaryArgs allKeys] count]; 582 | } 583 | else { 584 | 585 | while (idx < queryCount) { 586 | 587 | if (arrayArgs) { 588 | obj = [arrayArgs objectAtIndex:idx]; 589 | } 590 | else { 591 | obj = va_arg(args, id); 592 | } 593 | 594 | if (_traceExecution) { 595 | NSLog(@"obj: %@", obj); 596 | } 597 | 598 | idx++; 599 | 600 | [self bindObject:obj toColumn:idx inStatement:pStmt]; 601 | } 602 | } 603 | 604 | if (idx != queryCount) { 605 | NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)"); 606 | sqlite3_finalize(pStmt); 607 | _isExecutingStatement = NO; 608 | return nil; 609 | } 610 | 611 | FMDBRetain(statement); // to balance the release below 612 | 613 | if (!statement) { 614 | statement = [[FMStatement alloc] init]; 615 | [statement setStatement:pStmt]; 616 | 617 | if (_shouldCacheStatements) { 618 | [self setCachedStatement:statement forQuery:sql]; 619 | } 620 | } 621 | 622 | // the statement gets closed in rs's dealloc or [rs close]; 623 | rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self]; 624 | [rs setQuery:sql]; 625 | 626 | NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs]; 627 | [_openResultSets addObject:openResultSet]; 628 | 629 | [statement setUseCount:[statement useCount] + 1]; 630 | 631 | FMDBRelease(statement); 632 | 633 | _isExecutingStatement = NO; 634 | 635 | return rs; 636 | } 637 | 638 | - (FMResultSet *)executeQuery:(NSString*)sql, ... { 639 | va_list args; 640 | va_start(args, sql); 641 | 642 | id result = [self executeQuery:sql withArgumentsInArray:nil orDictionary:nil orVAList:args]; 643 | 644 | va_end(args); 645 | return result; 646 | } 647 | 648 | - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ... { 649 | va_list args; 650 | va_start(args, format); 651 | 652 | NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]]; 653 | NSMutableArray *arguments = [NSMutableArray array]; 654 | [self extractSQL:format argumentsList:args intoString:sql arguments:arguments]; 655 | 656 | va_end(args); 657 | 658 | return [self executeQuery:sql withArgumentsInArray:arguments]; 659 | } 660 | 661 | - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments { 662 | return [self executeQuery:sql withArgumentsInArray:arguments orDictionary:nil orVAList:nil]; 663 | } 664 | 665 | - (BOOL)executeUpdate:(NSString*)sql error:(NSError**)outErr withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args { 666 | 667 | if (![self databaseExists]) { 668 | return NO; 669 | } 670 | 671 | if (_isExecutingStatement) { 672 | [self warnInUse]; 673 | return NO; 674 | } 675 | 676 | _isExecutingStatement = YES; 677 | 678 | int rc = 0x00; 679 | sqlite3_stmt *pStmt = 0x00; 680 | FMStatement *cachedStmt = 0x00; 681 | 682 | if (_traceExecution && sql) { 683 | NSLog(@"%@ executeUpdate: %@", self, sql); 684 | } 685 | 686 | if (_shouldCacheStatements) { 687 | cachedStmt = [self cachedStatementForQuery:sql]; 688 | pStmt = cachedStmt ? [cachedStmt statement] : 0x00; 689 | } 690 | 691 | int numberOfRetries = 0; 692 | BOOL retry = NO; 693 | 694 | if (!pStmt) { 695 | 696 | do { 697 | retry = NO; 698 | rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); 699 | if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { 700 | retry = YES; 701 | usleep(20); 702 | 703 | if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { 704 | NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); 705 | NSLog(@"Database busy"); 706 | sqlite3_finalize(pStmt); 707 | _isExecutingStatement = NO; 708 | return NO; 709 | } 710 | } 711 | else if (SQLITE_OK != rc) { 712 | 713 | if (_logsErrors) { 714 | NSLog(@"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); 715 | NSLog(@"DB Query: %@", sql); 716 | NSLog(@"DB Path: %@", _databasePath); 717 | #ifndef NS_BLOCK_ASSERTIONS 718 | if (_crashOnErrors) { 719 | abort(); 720 | NSAssert2(false, @"DB Error: %d \"%@\"", [self lastErrorCode], [self lastErrorMessage]); 721 | } 722 | #endif 723 | } 724 | 725 | sqlite3_finalize(pStmt); 726 | 727 | if (outErr) { 728 | *outErr = [NSError errorWithDomain:[NSString stringWithUTF8String:sqlite3_errmsg(_db)] code:rc userInfo:nil]; 729 | } 730 | 731 | _isExecutingStatement = NO; 732 | return NO; 733 | } 734 | } 735 | while (retry); 736 | } 737 | 738 | id obj; 739 | int idx = 0; 740 | int queryCount = sqlite3_bind_parameter_count(pStmt); 741 | 742 | // If dictionaryArgs is passed in, that means we are using sqlite's named parameter support 743 | if (dictionaryArgs) { 744 | 745 | for (NSString *dictionaryKey in [dictionaryArgs allKeys]) { 746 | 747 | // Prefix the key with a colon. 748 | NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey]; 749 | 750 | // Get the index for the parameter name. 751 | int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]); 752 | 753 | FMDBRelease(parameterName); 754 | 755 | if (namedIdx > 0) { 756 | // Standard binding from here. 757 | [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt]; 758 | } 759 | else { 760 | NSLog(@"Could not find index for %@", dictionaryKey); 761 | } 762 | } 763 | 764 | // we need the count of params to avoid an error below. 765 | idx = (int) [[dictionaryArgs allKeys] count]; 766 | } 767 | else { 768 | 769 | while (idx < queryCount) { 770 | 771 | if (arrayArgs) { 772 | obj = [arrayArgs objectAtIndex:idx]; 773 | } 774 | else { 775 | obj = va_arg(args, id); 776 | } 777 | 778 | if (_traceExecution) { 779 | NSLog(@"obj: %@", obj); 780 | } 781 | 782 | idx++; 783 | 784 | [self bindObject:obj toColumn:idx inStatement:pStmt]; 785 | } 786 | } 787 | 788 | 789 | if (idx != queryCount) { 790 | NSLog(@"Error: the bind count is not correct for the # of variables (%@) (executeUpdate)", sql); 791 | sqlite3_finalize(pStmt); 792 | _isExecutingStatement = NO; 793 | return NO; 794 | } 795 | 796 | /* Call sqlite3_step() to run the virtual machine. Since the SQL being 797 | ** executed is not a SELECT statement, we assume no data will be returned. 798 | */ 799 | numberOfRetries = 0; 800 | 801 | do { 802 | rc = sqlite3_step(pStmt); 803 | retry = NO; 804 | 805 | if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { 806 | // this will happen if the db is locked, like if we are doing an update or insert. 807 | // in that case, retry the step... and maybe wait just 10 milliseconds. 808 | retry = YES; 809 | if (SQLITE_LOCKED == rc) { 810 | rc = sqlite3_reset(pStmt); 811 | if (rc != SQLITE_LOCKED) { 812 | NSLog(@"Unexpected result from sqlite3_reset (%d) eu", rc); 813 | } 814 | } 815 | usleep(20); 816 | 817 | if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { 818 | NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); 819 | NSLog(@"Database busy"); 820 | retry = NO; 821 | } 822 | } 823 | else if (SQLITE_DONE == rc) { 824 | // all is well, let's return. 825 | } 826 | else if (SQLITE_ERROR == rc) { 827 | NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_ERROR", rc, sqlite3_errmsg(_db)); 828 | NSLog(@"DB Query: %@", sql); 829 | } 830 | else if (SQLITE_MISUSE == rc) { 831 | // uh oh. 832 | NSLog(@"Error calling sqlite3_step (%d: %s) SQLITE_MISUSE", rc, sqlite3_errmsg(_db)); 833 | NSLog(@"DB Query: %@", sql); 834 | } 835 | else { 836 | // wtf? 837 | NSLog(@"Unknown error calling sqlite3_step (%d: %s) eu", rc, sqlite3_errmsg(_db)); 838 | NSLog(@"DB Query: %@", sql); 839 | } 840 | 841 | } while (retry); 842 | 843 | if (rc == SQLITE_ROW) { 844 | NSAssert1(NO, @"A executeUpdate is being called with a query string '%@'", sql); 845 | } 846 | 847 | if (_shouldCacheStatements && !cachedStmt) { 848 | cachedStmt = [[FMStatement alloc] init]; 849 | 850 | [cachedStmt setStatement:pStmt]; 851 | 852 | [self setCachedStatement:cachedStmt forQuery:sql]; 853 | 854 | FMDBRelease(cachedStmt); 855 | } 856 | 857 | int closeErrorCode; 858 | 859 | if (cachedStmt) { 860 | [cachedStmt setUseCount:[cachedStmt useCount] + 1]; 861 | closeErrorCode = sqlite3_reset(pStmt); 862 | } 863 | else { 864 | /* Finalize the virtual machine. This releases all memory and other 865 | ** resources allocated by the sqlite3_prepare() call above. 866 | */ 867 | closeErrorCode = sqlite3_finalize(pStmt); 868 | } 869 | 870 | if (closeErrorCode != SQLITE_OK) { 871 | NSLog(@"Unknown error finalizing or resetting statement (%d: %s)", closeErrorCode, sqlite3_errmsg(_db)); 872 | NSLog(@"DB Query: %@", sql); 873 | } 874 | 875 | _isExecutingStatement = NO; 876 | return (rc == SQLITE_DONE || rc == SQLITE_OK); 877 | } 878 | 879 | 880 | - (BOOL)executeUpdate:(NSString*)sql, ... { 881 | va_list args; 882 | va_start(args, sql); 883 | 884 | BOOL result = [self executeUpdate:sql error:nil withArgumentsInArray:nil orDictionary:nil orVAList:args]; 885 | 886 | va_end(args); 887 | return result; 888 | } 889 | 890 | - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments { 891 | return [self executeUpdate:sql error:nil withArgumentsInArray:arguments orDictionary:nil orVAList:nil]; 892 | } 893 | 894 | - (BOOL)executeUpdate:(NSString*)sql withParameterDictionary:(NSDictionary *)arguments { 895 | return [self executeUpdate:sql error:nil withArgumentsInArray:nil orDictionary:arguments orVAList:nil]; 896 | } 897 | 898 | - (BOOL)executeUpdateWithFormat:(NSString*)format, ... { 899 | va_list args; 900 | va_start(args, format); 901 | 902 | NSMutableString *sql = [NSMutableString stringWithCapacity:[format length]]; 903 | NSMutableArray *arguments = [NSMutableArray array]; 904 | 905 | [self extractSQL:format argumentsList:args intoString:sql arguments:arguments]; 906 | 907 | va_end(args); 908 | 909 | return [self executeUpdate:sql withArgumentsInArray:arguments]; 910 | } 911 | 912 | - (BOOL)update:(NSString*)sql withErrorAndBindings:(NSError**)outErr, ... { 913 | va_list args; 914 | va_start(args, outErr); 915 | 916 | BOOL result = [self executeUpdate:sql error:outErr withArgumentsInArray:nil orDictionary:nil orVAList:args]; 917 | 918 | va_end(args); 919 | return result; 920 | } 921 | 922 | - (BOOL)rollback { 923 | BOOL b = [self executeUpdate:@"rollback transaction"]; 924 | 925 | if (b) { 926 | _inTransaction = NO; 927 | } 928 | 929 | return b; 930 | } 931 | 932 | - (BOOL)commit { 933 | BOOL b = [self executeUpdate:@"commit transaction"]; 934 | 935 | if (b) { 936 | _inTransaction = NO; 937 | } 938 | 939 | return b; 940 | } 941 | 942 | - (BOOL)beginDeferredTransaction { 943 | 944 | BOOL b = [self executeUpdate:@"begin deferred transaction"]; 945 | if (b) { 946 | _inTransaction = YES; 947 | } 948 | 949 | return b; 950 | } 951 | 952 | - (BOOL)beginTransaction { 953 | 954 | BOOL b = [self executeUpdate:@"begin exclusive transaction"]; 955 | if (b) { 956 | _inTransaction = YES; 957 | } 958 | 959 | return b; 960 | } 961 | 962 | - (BOOL)inTransaction { 963 | return _inTransaction; 964 | } 965 | 966 | #if SQLITE_VERSION_NUMBER >= 3007000 967 | 968 | - (BOOL)startSavePointWithName:(NSString*)name error:(NSError**)outErr { 969 | 970 | // FIXME: make sure the savepoint name doesn't have a ' in it. 971 | 972 | NSParameterAssert(name); 973 | 974 | if (![self executeUpdate:[NSString stringWithFormat:@"savepoint '%@';", name]]) { 975 | 976 | if (*outErr) { 977 | *outErr = [self lastError]; 978 | } 979 | 980 | return NO; 981 | } 982 | 983 | return YES; 984 | } 985 | 986 | - (BOOL)releaseSavePointWithName:(NSString*)name error:(NSError**)outErr { 987 | 988 | NSParameterAssert(name); 989 | 990 | BOOL worked = [self executeUpdate:[NSString stringWithFormat:@"release savepoint '%@';", name]]; 991 | 992 | if (!worked && *outErr) { 993 | *outErr = [self lastError]; 994 | } 995 | 996 | return worked; 997 | } 998 | 999 | - (BOOL)rollbackToSavePointWithName:(NSString*)name error:(NSError**)outErr { 1000 | 1001 | NSParameterAssert(name); 1002 | 1003 | BOOL worked = [self executeUpdate:[NSString stringWithFormat:@"rollback transaction to savepoint '%@';", name]]; 1004 | 1005 | if (!worked && *outErr) { 1006 | *outErr = [self lastError]; 1007 | } 1008 | 1009 | return worked; 1010 | } 1011 | 1012 | - (NSError*)inSavePoint:(void (^)(BOOL *rollback))block { 1013 | static unsigned long savePointIdx = 0; 1014 | 1015 | NSString *name = [NSString stringWithFormat:@"dbSavePoint%ld", savePointIdx++]; 1016 | 1017 | BOOL shouldRollback = NO; 1018 | 1019 | NSError *err = 0x00; 1020 | 1021 | if (![self startSavePointWithName:name error:&err]) { 1022 | return err; 1023 | } 1024 | 1025 | block(&shouldRollback); 1026 | 1027 | if (shouldRollback) { 1028 | [self rollbackToSavePointWithName:name error:&err]; 1029 | } 1030 | else { 1031 | [self releaseSavePointWithName:name error:&err]; 1032 | } 1033 | 1034 | return err; 1035 | } 1036 | 1037 | #endif 1038 | 1039 | 1040 | - (BOOL)shouldCacheStatements { 1041 | return _shouldCacheStatements; 1042 | } 1043 | 1044 | - (void)setShouldCacheStatements:(BOOL)value { 1045 | 1046 | _shouldCacheStatements = value; 1047 | 1048 | if (_shouldCacheStatements && !_cachedStatements) { 1049 | [self setCachedStatements:[NSMutableDictionary dictionary]]; 1050 | } 1051 | 1052 | if (!_shouldCacheStatements) { 1053 | [self setCachedStatements:nil]; 1054 | } 1055 | } 1056 | 1057 | void FMDBBlockSQLiteCallBackFunction(sqlite3_context *context, int argc, sqlite3_value **argv); 1058 | void FMDBBlockSQLiteCallBackFunction(sqlite3_context *context, int argc, sqlite3_value **argv) { 1059 | #if ! __has_feature(objc_arc) 1060 | void (^block)(sqlite3_context *context, int argc, sqlite3_value **argv) = (id)sqlite3_user_data(context); 1061 | #else 1062 | void (^block)(sqlite3_context *context, int argc, sqlite3_value **argv) = (__bridge id)sqlite3_user_data(context); 1063 | #endif 1064 | block(context, argc, argv); 1065 | } 1066 | 1067 | 1068 | - (void)makeFunctionNamed:(NSString*)name maximumArguments:(int)count withBlock:(void (^)(sqlite3_context *context, int argc, sqlite3_value **argv))block { 1069 | 1070 | if (!_openFunctions) { 1071 | _openFunctions = [NSMutableSet new]; 1072 | } 1073 | 1074 | id b = FMDBReturnAutoreleased([block copy]); 1075 | 1076 | [_openFunctions addObject:b]; 1077 | 1078 | /* I tried adding custom functions to release the block when the connection is destroyed- but they seemed to never be called, so we use _openFunctions to store the values instead. */ 1079 | #if ! __has_feature(objc_arc) 1080 | sqlite3_create_function([self sqliteHandle], [name UTF8String], count, SQLITE_UTF8, (void*)b, &FMDBBlockSQLiteCallBackFunction, 0x00, 0x00); 1081 | #else 1082 | sqlite3_create_function([self sqliteHandle], [name UTF8String], count, SQLITE_UTF8, (__bridge void*)b, &FMDBBlockSQLiteCallBackFunction, 0x00, 0x00); 1083 | #endif 1084 | } 1085 | 1086 | @end 1087 | 1088 | 1089 | 1090 | @implementation FMStatement 1091 | @synthesize statement=_statement; 1092 | @synthesize query=_query; 1093 | @synthesize useCount=_useCount; 1094 | 1095 | - (void)finalize { 1096 | [self close]; 1097 | [super finalize]; 1098 | } 1099 | 1100 | - (void)dealloc { 1101 | [self close]; 1102 | FMDBRelease(_query); 1103 | #if ! __has_feature(objc_arc) 1104 | [super dealloc]; 1105 | #endif 1106 | } 1107 | 1108 | - (void)close { 1109 | if (_statement) { 1110 | sqlite3_finalize(_statement); 1111 | _statement = 0x00; 1112 | } 1113 | } 1114 | 1115 | - (void)reset { 1116 | if (_statement) { 1117 | sqlite3_reset(_statement); 1118 | } 1119 | } 1120 | 1121 | - (NSString*)description { 1122 | return [NSString stringWithFormat:@"%@ %ld hit(s) for query %@", [super description], _useCount, _query]; 1123 | NSString* docsdir = [NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 1124 | NSString* dbpath = [docsdir stringByAppendingPathComponent:@"user.sqlite"]; 1125 | FMDatabase* db = [FMDatabase databaseWithPath:dbpath]; 1126 | [db open]; 1127 | FMResultSet *rs = [db executeQuery:@"select * from people"]; 1128 | while ([rs next]) { 1129 | NSLog(@"%@ %@", 1130 | [rs stringForColumn:@"firstname"], 1131 | [rs stringForColumn:@"lastname"]); 1132 | } 1133 | [db close]; 1134 | } 1135 | 1136 | 1137 | 1138 | @end 1139 | 1140 | -------------------------------------------------------------------------------- /FmdbTest/fmdb/FMDatabaseAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditions.h 3 | // fmkit 4 | // 5 | // Created by August Mueller on 10/30/05. 6 | // Copyright 2005 Flying Meat Inc.. All rights reserved. 7 | // 8 | 9 | #import 10 | @interface FMDatabase (FMDatabaseAdditions) 11 | 12 | 13 | - (int)intForQuery:(NSString*)objs, ...; 14 | - (long)longForQuery:(NSString*)objs, ...; 15 | - (BOOL)boolForQuery:(NSString*)objs, ...; 16 | - (double)doubleForQuery:(NSString*)objs, ...; 17 | - (NSString*)stringForQuery:(NSString*)objs, ...; 18 | - (NSData*)dataForQuery:(NSString*)objs, ...; 19 | - (NSDate*)dateForQuery:(NSString*)objs, ...; 20 | 21 | // Notice that there's no dataNoCopyForQuery:. 22 | // That would be a bad idea, because we close out the result set, and then what 23 | // happens to the data that we just didn't copy? Who knows, not I. 24 | 25 | 26 | - (BOOL)tableExists:(NSString*)tableName; 27 | - (FMResultSet*)getSchema; 28 | - (FMResultSet*)getTableSchema:(NSString*)tableName; 29 | - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName; 30 | 31 | - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /FmdbTest/fmdb/FMDatabaseAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditions.m 3 | // fmkit 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 | 12 | @interface FMDatabase (PrivateStuff) 13 | - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args; 14 | @end 15 | 16 | @implementation FMDatabase (FMDatabaseAdditions) 17 | 18 | #define RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(type, sel) \ 19 | va_list args; \ 20 | va_start(args, query); \ 21 | FMResultSet *resultSet = [self executeQuery:query withArgumentsInArray:0x00 orDictionary:0x00 orVAList:args]; \ 22 | va_end(args); \ 23 | if (![resultSet next]) { return (type)0; } \ 24 | type ret = [resultSet sel:0]; \ 25 | [resultSet close]; \ 26 | [resultSet setParentDB:nil]; \ 27 | return ret; 28 | 29 | 30 | - (NSString*)stringForQuery:(NSString*)query, ... { 31 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSString *, stringForColumnIndex); 32 | } 33 | 34 | - (int)intForQuery:(NSString*)query, ... { 35 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(int, intForColumnIndex); 36 | } 37 | 38 | - (long)longForQuery:(NSString*)query, ... { 39 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(long, longForColumnIndex); 40 | } 41 | 42 | - (BOOL)boolForQuery:(NSString*)query, ... { 43 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(BOOL, boolForColumnIndex); 44 | } 45 | 46 | - (double)doubleForQuery:(NSString*)query, ... { 47 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(double, doubleForColumnIndex); 48 | } 49 | 50 | - (NSData*)dataForQuery:(NSString*)query, ... { 51 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSData *, dataForColumnIndex); 52 | } 53 | 54 | - (NSDate*)dateForQuery:(NSString*)query, ... { 55 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSDate *, dateForColumnIndex); 56 | } 57 | 58 | 59 | - (BOOL)tableExists:(NSString*)tableName { 60 | 61 | tableName = [tableName lowercaseString]; 62 | 63 | FMResultSet *rs = [self executeQuery:@"select [sql] from sqlite_master where [type] = 'table' and lower(name) = ?", tableName]; 64 | 65 | //if at least one next exists, table exists 66 | BOOL returnBool = [rs next]; 67 | 68 | //close and free object 69 | [rs close]; 70 | 71 | return returnBool; 72 | } 73 | 74 | /* 75 | get table with list of tables: result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] 76 | check if table exist in database (patch from OZLB) 77 | */ 78 | - (FMResultSet*)getSchema { 79 | 80 | //result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] 81 | 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"]; 82 | 83 | return rs; 84 | } 85 | 86 | /* 87 | get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] 88 | */ 89 | - (FMResultSet*)getTableSchema:(NSString*)tableName { 90 | 91 | //result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] 92 | FMResultSet *rs = [self executeQuery:[NSString stringWithFormat: @"PRAGMA table_info('%@')", tableName]]; 93 | 94 | return rs; 95 | } 96 | 97 | 98 | - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName { 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 | - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error { 122 | sqlite3_stmt *pStmt = NULL; 123 | BOOL validationSucceeded = YES; 124 | BOOL keepTrying = YES; 125 | int numberOfRetries = 0; 126 | 127 | while (keepTrying == YES) { 128 | keepTrying = NO; 129 | int rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); 130 | if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { 131 | keepTrying = YES; 132 | usleep(20); 133 | 134 | if (_busyRetryTimeout && (numberOfRetries++ > _busyRetryTimeout)) { 135 | NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [self databasePath]); 136 | NSLog(@"Database busy"); 137 | } 138 | } 139 | else if (rc != SQLITE_OK) { 140 | validationSucceeded = NO; 141 | if (error) { 142 | *error = [NSError errorWithDomain:NSCocoaErrorDomain 143 | code:[self lastErrorCode] 144 | userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage] 145 | forKey:NSLocalizedDescriptionKey]]; 146 | } 147 | } 148 | } 149 | 150 | sqlite3_finalize(pStmt); 151 | 152 | return validationSucceeded; 153 | } 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /FmdbTest/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 | /* 13 | 14 | ***README OR SUFFER*** 15 | Before using FMDatabasePool, please consider using FMDatabaseQueue instead. 16 | 17 | If you really really really know what you're doing and FMDatabasePool is what 18 | you really really need (ie, you're using a read only database), OK you can use 19 | it. But just be careful not to deadlock! 20 | 21 | For an example on deadlocking, search for: 22 | ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD 23 | in the main.m file. 24 | 25 | */ 26 | 27 | 28 | 29 | @class FMDatabase; 30 | 31 | @interface FMDatabasePool : NSObject { 32 | NSString *_path; 33 | 34 | dispatch_queue_t _lockQueue; 35 | 36 | NSMutableArray *_databaseInPool; 37 | NSMutableArray *_databaseOutPool; 38 | 39 | __unsafe_unretained id _delegate; 40 | 41 | NSUInteger _maximumNumberOfDatabasesToCreate; 42 | } 43 | 44 | @property (retain) NSString *path; 45 | @property (assign) id delegate; 46 | @property (assign) NSUInteger maximumNumberOfDatabasesToCreate; 47 | 48 | + (id)databasePoolWithPath:(NSString*)aPath; 49 | - (id)initWithPath:(NSString*)aPath; 50 | 51 | - (NSUInteger)countOfCheckedInDatabases; 52 | - (NSUInteger)countOfCheckedOutDatabases; 53 | - (NSUInteger)countOfOpenDatabases; 54 | - (void)releaseAllDatabases; 55 | 56 | - (void)inDatabase:(void (^)(FMDatabase *db))block; 57 | 58 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 59 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 60 | 61 | #if SQLITE_VERSION_NUMBER >= 3007000 62 | // NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. 63 | // If you need to nest, use FMDatabase's startSavePointWithName:error: instead. 64 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; 65 | #endif 66 | 67 | @end 68 | 69 | 70 | @interface NSObject (FMDatabasePoolDelegate) 71 | 72 | - (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database; 73 | 74 | @end 75 | 76 | -------------------------------------------------------------------------------- /FmdbTest/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 | 25 | 26 | + (id)databasePoolWithPath:(NSString*)aPath { 27 | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]); 28 | } 29 | 30 | - (id)initWithPath:(NSString*)aPath { 31 | 32 | self = [super init]; 33 | 34 | if (self != nil) { 35 | _path = [aPath copy]; 36 | _lockQueue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); 37 | _databaseInPool = FMDBReturnRetained([NSMutableArray array]); 38 | _databaseOutPool = FMDBReturnRetained([NSMutableArray array]); 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (void)dealloc { 45 | 46 | _delegate = 0x00; 47 | FMDBRelease(_path); 48 | FMDBRelease(_databaseInPool); 49 | FMDBRelease(_databaseOutPool); 50 | 51 | if (_lockQueue) { 52 | dispatch_release(_lockQueue); 53 | _lockQueue = 0x00; 54 | } 55 | #if ! __has_feature(objc_arc) 56 | [super dealloc]; 57 | #endif 58 | } 59 | 60 | 61 | - (void)executeLocked:(void (^)(void))aBlock { 62 | dispatch_sync(_lockQueue, aBlock); 63 | } 64 | 65 | - (void)pushDatabaseBackInPool:(FMDatabase*)db { 66 | 67 | if (!db) { // db can be null if we set an upper bound on the # of databases to create. 68 | return; 69 | } 70 | 71 | [self executeLocked:^() { 72 | 73 | if ([_databaseInPool containsObject:db]) { 74 | [[NSException exceptionWithName:@"Database already in pool" reason:@"The FMDatabase being put back into the pool is already present in the pool" userInfo:nil] raise]; 75 | } 76 | 77 | [_databaseInPool addObject:db]; 78 | [_databaseOutPool removeObject:db]; 79 | 80 | }]; 81 | } 82 | 83 | - (FMDatabase*)db { 84 | 85 | __block FMDatabase *db; 86 | 87 | [self executeLocked:^() { 88 | db = [_databaseInPool lastObject]; 89 | 90 | if (db) { 91 | [_databaseOutPool addObject:db]; 92 | [_databaseInPool removeLastObject]; 93 | } 94 | else { 95 | 96 | if (_maximumNumberOfDatabasesToCreate) { 97 | NSUInteger currentCount = [_databaseOutPool count] + [_databaseInPool count]; 98 | 99 | if (currentCount >= _maximumNumberOfDatabasesToCreate) { 100 | NSLog(@"Maximum number of databases (%ld) has already been reached!", (long)currentCount); 101 | return; 102 | } 103 | } 104 | 105 | db = [FMDatabase databaseWithPath:_path]; 106 | } 107 | 108 | //This ensures that the db is opened before returning 109 | if ([db open]) { 110 | if ([_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![_delegate databasePool:self shouldAddDatabaseToPool:db]) { 111 | [db close]; 112 | db = 0x00; 113 | } 114 | else { 115 | //It should not get added in the pool twice if lastObject was found 116 | if (![_databaseOutPool containsObject:db]) { 117 | [_databaseOutPool addObject:db]; 118 | } 119 | } 120 | } 121 | else { 122 | NSLog(@"Could not open up the database at path %@", _path); 123 | db = 0x00; 124 | } 125 | }]; 126 | 127 | return db; 128 | } 129 | 130 | - (NSUInteger)countOfCheckedInDatabases { 131 | 132 | __block NSInteger count; 133 | 134 | [self executeLocked:^() { 135 | count = [_databaseInPool count]; 136 | }]; 137 | 138 | return count; 139 | } 140 | 141 | - (NSUInteger)countOfCheckedOutDatabases { 142 | 143 | __block NSInteger count; 144 | 145 | [self executeLocked:^() { 146 | count = [_databaseOutPool count]; 147 | }]; 148 | 149 | return count; 150 | } 151 | 152 | - (NSUInteger)countOfOpenDatabases { 153 | __block NSInteger count; 154 | 155 | [self executeLocked:^() { 156 | count = [_databaseOutPool count] + [_databaseInPool count]; 157 | }]; 158 | 159 | return count; 160 | } 161 | 162 | - (void)releaseAllDatabases { 163 | [self executeLocked:^() { 164 | [_databaseOutPool removeAllObjects]; 165 | [_databaseInPool removeAllObjects]; 166 | }]; 167 | } 168 | 169 | - (void)inDatabase:(void (^)(FMDatabase *db))block { 170 | 171 | FMDatabase *db = [self db]; 172 | 173 | block(db); 174 | 175 | [self pushDatabaseBackInPool:db]; 176 | } 177 | 178 | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { 179 | 180 | BOOL shouldRollback = NO; 181 | 182 | FMDatabase *db = [self db]; 183 | 184 | if (useDeferred) { 185 | [db beginDeferredTransaction]; 186 | } 187 | else { 188 | [db beginTransaction]; 189 | } 190 | 191 | 192 | block(db, &shouldRollback); 193 | 194 | if (shouldRollback) { 195 | [db rollback]; 196 | } 197 | else { 198 | [db commit]; 199 | } 200 | 201 | [self pushDatabaseBackInPool:db]; 202 | } 203 | 204 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 205 | [self beginTransaction:YES withBlock:block]; 206 | } 207 | 208 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 209 | [self beginTransaction:NO withBlock:block]; 210 | } 211 | #if SQLITE_VERSION_NUMBER >= 3007000 212 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { 213 | 214 | static unsigned long savePointIdx = 0; 215 | 216 | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; 217 | 218 | BOOL shouldRollback = NO; 219 | 220 | FMDatabase *db = [self db]; 221 | 222 | NSError *err = 0x00; 223 | 224 | if (![db startSavePointWithName:name error:&err]) { 225 | [self pushDatabaseBackInPool:db]; 226 | return err; 227 | } 228 | 229 | block(db, &shouldRollback); 230 | 231 | if (shouldRollback) { 232 | [db rollbackToSavePointWithName:name error:&err]; 233 | } 234 | else { 235 | [db releaseSavePointWithName:name error:&err]; 236 | } 237 | 238 | [self pushDatabaseBackInPool:db]; 239 | 240 | return err; 241 | } 242 | #endif 243 | 244 | @end 245 | -------------------------------------------------------------------------------- /FmdbTest/fmdb/FMDatabaseQueue.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 | @interface FMDatabaseQueue : NSObject { 15 | NSString *_path; 16 | dispatch_queue_t _queue; 17 | FMDatabase *_db; 18 | } 19 | 20 | @property (retain) NSString *path; 21 | 22 | + (id)databaseQueueWithPath:(NSString*)aPath; 23 | - (id)initWithPath:(NSString*)aPath; 24 | - (void)close; 25 | 26 | - (void)inDatabase:(void (^)(FMDatabase *db))block; 27 | 28 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 29 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 30 | 31 | #if SQLITE_VERSION_NUMBER >= 3007000 32 | // NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. 33 | // If you need to nest, use FMDatabase's startSavePointWithName:error: instead. 34 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; 35 | #endif 36 | 37 | @end 38 | 39 | -------------------------------------------------------------------------------- /FmdbTest/fmdb/FMDatabaseQueue.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 "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 | @implementation FMDatabaseQueue 21 | 22 | @synthesize path = _path; 23 | 24 | + (id)databaseQueueWithPath:(NSString*)aPath { 25 | 26 | FMDatabaseQueue *q = [[self alloc] initWithPath:aPath]; 27 | 28 | FMDBAutorelease(q); 29 | 30 | return q; 31 | } 32 | 33 | - (id)initWithPath:(NSString*)aPath { 34 | 35 | self = [super init]; 36 | 37 | if (self != nil) { 38 | 39 | _db = [FMDatabase databaseWithPath:aPath]; 40 | FMDBRetain(_db); 41 | 42 | if (![_db open]) { 43 | NSLog(@"Could not create database queue for path %@", aPath); 44 | FMDBRelease(self); 45 | return 0x00; 46 | } 47 | 48 | _path = FMDBReturnRetained(aPath); 49 | 50 | _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); 51 | } 52 | 53 | return self; 54 | } 55 | 56 | - (void)dealloc { 57 | 58 | FMDBRelease(_db); 59 | FMDBRelease(_path); 60 | 61 | if (_queue) { 62 | dispatch_release(_queue); 63 | _queue = 0x00; 64 | } 65 | #if ! __has_feature(objc_arc) 66 | [super dealloc]; 67 | #endif 68 | } 69 | 70 | - (void)close { 71 | FMDBRetain(self); 72 | dispatch_sync(_queue, ^() { 73 | [_db close]; 74 | FMDBRelease(_db); 75 | _db = 0x00; 76 | }); 77 | FMDBRelease(self); 78 | } 79 | 80 | - (FMDatabase*)database { 81 | if (!_db) { 82 | _db = FMDBReturnRetained([FMDatabase databaseWithPath:_path]); 83 | 84 | if (![_db open]) { 85 | NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path); 86 | FMDBRelease(_db); 87 | _db = 0x00; 88 | return 0x00; 89 | } 90 | } 91 | 92 | return _db; 93 | } 94 | 95 | - (void)inDatabase:(void (^)(FMDatabase *db))block { 96 | FMDBRetain(self); 97 | 98 | dispatch_sync(_queue, ^() { 99 | 100 | FMDatabase *db = [self database]; 101 | block(db); 102 | 103 | if ([db hasOpenResultSets]) { 104 | NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]"); 105 | } 106 | }); 107 | 108 | FMDBRelease(self); 109 | } 110 | 111 | 112 | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { 113 | FMDBRetain(self); 114 | dispatch_sync(_queue, ^() { 115 | 116 | BOOL shouldRollback = NO; 117 | 118 | if (useDeferred) { 119 | [[self database] beginDeferredTransaction]; 120 | } 121 | else { 122 | [[self database] beginTransaction]; 123 | } 124 | 125 | block([self database], &shouldRollback); 126 | 127 | if (shouldRollback) { 128 | [[self database] rollback]; 129 | } 130 | else { 131 | [[self database] commit]; 132 | } 133 | }); 134 | 135 | FMDBRelease(self); 136 | } 137 | 138 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 139 | [self beginTransaction:YES withBlock:block]; 140 | } 141 | 142 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 143 | [self beginTransaction:NO withBlock:block]; 144 | } 145 | 146 | #if SQLITE_VERSION_NUMBER >= 3007000 147 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { 148 | 149 | static unsigned long savePointIdx = 0; 150 | __block NSError *err = 0x00; 151 | FMDBRetain(self); 152 | dispatch_sync(_queue, ^() { 153 | 154 | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; 155 | 156 | BOOL shouldRollback = NO; 157 | 158 | if ([[self database] startSavePointWithName:name error:&err]) { 159 | 160 | block([self database], &shouldRollback); 161 | 162 | if (shouldRollback) { 163 | [[self database] rollbackToSavePointWithName:name error:&err]; 164 | } 165 | else { 166 | [[self database] releaseSavePointWithName:name error:&err]; 167 | } 168 | 169 | } 170 | }); 171 | FMDBRelease(self); 172 | return err; 173 | } 174 | #endif 175 | 176 | @end 177 | -------------------------------------------------------------------------------- /FmdbTest/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 | @interface FMResultSet : NSObject { 20 | FMDatabase *_parentDB; 21 | FMStatement *_statement; 22 | 23 | NSString *_query; 24 | NSMutableDictionary *_columnNameToIndexMap; 25 | BOOL _columnNamesSetup; 26 | } 27 | 28 | @property (retain) NSString *query; 29 | @property (retain) NSMutableDictionary *columnNameToIndexMap; 30 | @property (retain) FMStatement *statement; 31 | 32 | + (id)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB; 33 | 34 | - (void)close; 35 | 36 | - (void)setParentDB:(FMDatabase *)newDb; 37 | 38 | - (BOOL)next; 39 | - (BOOL)hasAnotherRow; 40 | 41 | - (int)columnCount; 42 | 43 | - (int)columnIndexForName:(NSString*)columnName; 44 | - (NSString*)columnNameForIndex:(int)columnIdx; 45 | 46 | - (int)intForColumn:(NSString*)columnName; 47 | - (int)intForColumnIndex:(int)columnIdx; 48 | 49 | - (long)longForColumn:(NSString*)columnName; 50 | - (long)longForColumnIndex:(int)columnIdx; 51 | 52 | - (long long int)longLongIntForColumn:(NSString*)columnName; 53 | - (long long int)longLongIntForColumnIndex:(int)columnIdx; 54 | 55 | - (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName; 56 | - (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx; 57 | 58 | - (BOOL)boolForColumn:(NSString*)columnName; 59 | - (BOOL)boolForColumnIndex:(int)columnIdx; 60 | 61 | - (double)doubleForColumn:(NSString*)columnName; 62 | - (double)doubleForColumnIndex:(int)columnIdx; 63 | 64 | - (NSString*)stringForColumn:(NSString*)columnName; 65 | - (NSString*)stringForColumnIndex:(int)columnIdx; 66 | 67 | - (NSDate*)dateForColumn:(NSString*)columnName; 68 | - (NSDate*)dateForColumnIndex:(int)columnIdx; 69 | 70 | - (NSData*)dataForColumn:(NSString*)columnName; 71 | - (NSData*)dataForColumnIndex:(int)columnIdx; 72 | 73 | - (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx; 74 | - (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName; 75 | 76 | // returns one of NSNumber, NSString, NSData, or NSNull 77 | - (id)objectForColumnName:(NSString*)columnName; 78 | - (id)objectForColumnIndex:(int)columnIdx; 79 | 80 | /* 81 | If you are going to use this data after you iterate over the next row, or after you close the 82 | result set, make sure to make a copy of the data first (or just use dataForColumn:/dataForColumnIndex:) 83 | If you don't, you're going to be in a world of hurt when you try and use the data. 84 | */ 85 | - (NSData*)dataNoCopyForColumn:(NSString*)columnName NS_RETURNS_NOT_RETAINED; 86 | - (NSData*)dataNoCopyForColumnIndex:(int)columnIdx NS_RETURNS_NOT_RETAINED; 87 | 88 | - (BOOL)columnIndexIsNull:(int)columnIdx; 89 | - (BOOL)columnIsNull:(NSString*)columnName; 90 | 91 | 92 | /* Returns a dictionary of the row results mapped to case sensitive keys of the column names. */ 93 | - (NSDictionary*)resultDictionary; 94 | 95 | /* Please use resultDictionary instead. Also, beware that resultDictionary is case sensitive! */ 96 | - (NSDictionary*)resultDict __attribute__ ((deprecated)); 97 | 98 | - (void)kvcMagic:(id)object; 99 | 100 | 101 | @end 102 | 103 | -------------------------------------------------------------------------------- /FmdbTest/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 | @interface FMResultSet (Private) 11 | - (NSMutableDictionary *)columnNameToIndexMap; 12 | - (void)setColumnNameToIndexMap:(NSMutableDictionary *)value; 13 | @end 14 | 15 | @implementation FMResultSet 16 | @synthesize query=_query; 17 | @synthesize columnNameToIndexMap=_columnNameToIndexMap; 18 | @synthesize statement=_statement; 19 | 20 | + (id)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB { 21 | 22 | FMResultSet *rs = [[FMResultSet alloc] init]; 23 | 24 | [rs setStatement:statement]; 25 | [rs setParentDB:aDB]; 26 | 27 | return FMDBReturnAutoreleased(rs); 28 | } 29 | 30 | - (void)finalize { 31 | [self close]; 32 | [super finalize]; 33 | } 34 | 35 | - (void)dealloc { 36 | [self close]; 37 | 38 | FMDBRelease(_query); 39 | _query = nil; 40 | 41 | FMDBRelease(_columnNameToIndexMap); 42 | _columnNameToIndexMap = nil; 43 | 44 | #if ! __has_feature(objc_arc) 45 | [super dealloc]; 46 | #endif 47 | } 48 | 49 | - (void)close { 50 | [_statement reset]; 51 | FMDBRelease(_statement); 52 | _statement = nil; 53 | 54 | // we don't need this anymore... (i think) 55 | //[_parentDB setInUse:NO]; 56 | [_parentDB resultSetDidClose:self]; 57 | _parentDB = nil; 58 | } 59 | 60 | - (int)columnCount { 61 | return sqlite3_column_count([_statement statement]); 62 | } 63 | 64 | - (void)setupColumnNames { 65 | 66 | if (!_columnNameToIndexMap) { 67 | [self setColumnNameToIndexMap:[NSMutableDictionary dictionary]]; 68 | } 69 | 70 | int columnCount = sqlite3_column_count([_statement statement]); 71 | 72 | int columnIdx = 0; 73 | for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { 74 | [_columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx] 75 | forKey:[[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)] lowercaseString]]; 76 | } 77 | _columnNamesSetup = YES; 78 | } 79 | 80 | - (void)kvcMagic:(id)object { 81 | 82 | int columnCount = sqlite3_column_count([_statement statement]); 83 | 84 | int columnIdx = 0; 85 | for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { 86 | 87 | const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx); 88 | 89 | // check for a null row 90 | if (c) { 91 | NSString *s = [NSString stringWithUTF8String:c]; 92 | 93 | [object setValue:s forKey:[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]]; 94 | } 95 | } 96 | } 97 | 98 | #pragma clang diagnostic push 99 | #pragma clang diagnostic ignored "-Wdeprecated-implementations" 100 | 101 | - (NSDictionary*)resultDict { 102 | 103 | int num_cols = sqlite3_data_count([_statement statement]); 104 | 105 | if (num_cols > 0) { 106 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; 107 | 108 | if (!_columnNamesSetup) { 109 | [self setupColumnNames]; 110 | } 111 | 112 | NSEnumerator *columnNames = [_columnNameToIndexMap keyEnumerator]; 113 | NSString *columnName = nil; 114 | while ((columnName = [columnNames nextObject])) { 115 | id objectValue = [self objectForColumnName:columnName]; 116 | [dict setObject:objectValue forKey:columnName]; 117 | } 118 | 119 | return FMDBReturnAutoreleased([dict copy]); 120 | } 121 | else { 122 | NSLog(@"Warning: There seem to be no columns in this set."); 123 | } 124 | 125 | return nil; 126 | } 127 | 128 | #pragma clang diagnostic pop 129 | 130 | - (NSDictionary*)resultDictionary { 131 | 132 | int num_cols = sqlite3_data_count([_statement statement]); 133 | 134 | if (num_cols > 0) { 135 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; 136 | 137 | int columnCount = sqlite3_column_count([_statement statement]); 138 | 139 | int columnIdx = 0; 140 | for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { 141 | 142 | NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]; 143 | id objectValue = [self objectForColumnIndex:columnIdx]; 144 | [dict setObject:objectValue forKey:columnName]; 145 | } 146 | 147 | return dict; 148 | } 149 | else { 150 | NSLog(@"Warning: There seem to be no columns in this set."); 151 | } 152 | 153 | return nil; 154 | } 155 | 156 | 157 | 158 | 159 | 160 | - (BOOL)next { 161 | 162 | int rc; 163 | BOOL retry; 164 | int numberOfRetries = 0; 165 | do { 166 | retry = NO; 167 | 168 | rc = sqlite3_step([_statement statement]); 169 | 170 | if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { 171 | // this will happen if the db is locked, like if we are doing an update or insert. 172 | // in that case, retry the step... and maybe wait just 10 milliseconds. 173 | retry = YES; 174 | if (SQLITE_LOCKED == rc) { 175 | rc = sqlite3_reset([_statement statement]); 176 | if (rc != SQLITE_LOCKED) { 177 | NSLog(@"Unexpected result from sqlite3_reset (%d) rs", rc); 178 | } 179 | } 180 | usleep(20); 181 | 182 | if ([_parentDB busyRetryTimeout] && (numberOfRetries++ > [_parentDB busyRetryTimeout])) { 183 | 184 | NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]); 185 | NSLog(@"Database busy"); 186 | break; 187 | } 188 | } 189 | else if (SQLITE_DONE == rc || SQLITE_ROW == rc) { 190 | // all is well, let's return. 191 | } 192 | else if (SQLITE_ERROR == rc) { 193 | NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); 194 | break; 195 | } 196 | else if (SQLITE_MISUSE == rc) { 197 | // uh oh. 198 | NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); 199 | break; 200 | } 201 | else { 202 | // wtf? 203 | NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); 204 | break; 205 | } 206 | 207 | } while (retry); 208 | 209 | 210 | if (rc != SQLITE_ROW) { 211 | [self close]; 212 | } 213 | 214 | return (rc == SQLITE_ROW); 215 | } 216 | 217 | - (BOOL)hasAnotherRow { 218 | return sqlite3_errcode([_parentDB sqliteHandle]) == SQLITE_ROW; 219 | } 220 | 221 | - (int)columnIndexForName:(NSString*)columnName { 222 | 223 | if (!_columnNamesSetup) { 224 | [self setupColumnNames]; 225 | } 226 | 227 | columnName = [columnName lowercaseString]; 228 | 229 | NSNumber *n = [_columnNameToIndexMap objectForKey:columnName]; 230 | 231 | if (n) { 232 | return [n intValue]; 233 | } 234 | 235 | NSLog(@"Warning: I could not find the column named '%@'.", columnName); 236 | 237 | return -1; 238 | } 239 | 240 | 241 | 242 | - (int)intForColumn:(NSString*)columnName { 243 | return [self intForColumnIndex:[self columnIndexForName:columnName]]; 244 | } 245 | 246 | - (int)intForColumnIndex:(int)columnIdx { 247 | return sqlite3_column_int([_statement statement], columnIdx); 248 | } 249 | 250 | - (long)longForColumn:(NSString*)columnName { 251 | return [self longForColumnIndex:[self columnIndexForName:columnName]]; 252 | } 253 | 254 | - (long)longForColumnIndex:(int)columnIdx { 255 | return (long)sqlite3_column_int64([_statement statement], columnIdx); 256 | } 257 | 258 | - (long long int)longLongIntForColumn:(NSString*)columnName { 259 | return [self longLongIntForColumnIndex:[self columnIndexForName:columnName]]; 260 | } 261 | 262 | - (long long int)longLongIntForColumnIndex:(int)columnIdx { 263 | return sqlite3_column_int64([_statement statement], columnIdx); 264 | } 265 | 266 | - (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName { 267 | return [self unsignedLongLongIntForColumnIndex:[self columnIndexForName:columnName]]; 268 | } 269 | 270 | - (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx { 271 | return (unsigned long long int)[self longLongIntForColumnIndex:columnIdx]; 272 | } 273 | 274 | - (BOOL)boolForColumn:(NSString*)columnName { 275 | return [self boolForColumnIndex:[self columnIndexForName:columnName]]; 276 | } 277 | 278 | - (BOOL)boolForColumnIndex:(int)columnIdx { 279 | return ([self intForColumnIndex:columnIdx] != 0); 280 | } 281 | 282 | - (double)doubleForColumn:(NSString*)columnName { 283 | return [self doubleForColumnIndex:[self columnIndexForName:columnName]]; 284 | } 285 | 286 | - (double)doubleForColumnIndex:(int)columnIdx { 287 | return sqlite3_column_double([_statement statement], columnIdx); 288 | } 289 | 290 | - (NSString*)stringForColumnIndex:(int)columnIdx { 291 | 292 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 293 | return nil; 294 | } 295 | 296 | const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx); 297 | 298 | if (!c) { 299 | // null row. 300 | return nil; 301 | } 302 | 303 | return [NSString stringWithUTF8String:c]; 304 | } 305 | 306 | - (NSString*)stringForColumn:(NSString*)columnName { 307 | return [self stringForColumnIndex:[self columnIndexForName:columnName]]; 308 | } 309 | 310 | - (NSDate*)dateForColumn:(NSString*)columnName { 311 | return [self dateForColumnIndex:[self columnIndexForName:columnName]]; 312 | } 313 | 314 | - (NSDate*)dateForColumnIndex:(int)columnIdx { 315 | 316 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 317 | return nil; 318 | } 319 | 320 | return [NSDate dateWithTimeIntervalSince1970:[self doubleForColumnIndex:columnIdx]]; 321 | } 322 | 323 | 324 | - (NSData*)dataForColumn:(NSString*)columnName { 325 | return [self dataForColumnIndex:[self columnIndexForName:columnName]]; 326 | } 327 | 328 | - (NSData*)dataForColumnIndex:(int)columnIdx { 329 | 330 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 331 | return nil; 332 | } 333 | 334 | int dataSize = sqlite3_column_bytes([_statement statement], columnIdx); 335 | 336 | NSMutableData *data = [NSMutableData dataWithLength:dataSize]; 337 | 338 | memcpy([data mutableBytes], sqlite3_column_blob([_statement statement], columnIdx), dataSize); 339 | 340 | return data; 341 | } 342 | 343 | 344 | - (NSData*)dataNoCopyForColumn:(NSString*)columnName { 345 | return [self dataNoCopyForColumnIndex:[self columnIndexForName:columnName]]; 346 | } 347 | 348 | - (NSData*)dataNoCopyForColumnIndex:(int)columnIdx { 349 | 350 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 351 | return nil; 352 | } 353 | 354 | int dataSize = sqlite3_column_bytes([_statement statement], columnIdx); 355 | 356 | NSData *data = [NSData dataWithBytesNoCopy:(void *)sqlite3_column_blob([_statement statement], columnIdx) length:dataSize freeWhenDone:NO]; 357 | 358 | return data; 359 | } 360 | 361 | 362 | - (BOOL)columnIndexIsNull:(int)columnIdx { 363 | return sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL; 364 | } 365 | 366 | - (BOOL)columnIsNull:(NSString*)columnName { 367 | return [self columnIndexIsNull:[self columnIndexForName:columnName]]; 368 | } 369 | 370 | - (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx { 371 | 372 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 373 | return nil; 374 | } 375 | 376 | return sqlite3_column_text([_statement statement], columnIdx); 377 | } 378 | 379 | - (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName { 380 | return [self UTF8StringForColumnIndex:[self columnIndexForName:columnName]]; 381 | } 382 | 383 | - (id)objectForColumnIndex:(int)columnIdx { 384 | int columnType = sqlite3_column_type([_statement statement], columnIdx); 385 | 386 | id returnValue = nil; 387 | 388 | if (columnType == SQLITE_INTEGER) { 389 | returnValue = [NSNumber numberWithLongLong:[self longLongIntForColumnIndex:columnIdx]]; 390 | } 391 | else if (columnType == SQLITE_FLOAT) { 392 | returnValue = [NSNumber numberWithDouble:[self doubleForColumnIndex:columnIdx]]; 393 | } 394 | else if (columnType == SQLITE_BLOB) { 395 | returnValue = [self dataForColumnIndex:columnIdx]; 396 | } 397 | else { 398 | //default to a string for everything else 399 | returnValue = [self stringForColumnIndex:columnIdx]; 400 | } 401 | 402 | if (returnValue == nil) { 403 | returnValue = [NSNull null]; 404 | } 405 | 406 | return returnValue; 407 | } 408 | 409 | - (id)objectForColumnName:(NSString*)columnName { 410 | return [self objectForColumnIndex:[self columnIndexForName:columnName]]; 411 | } 412 | 413 | // returns autoreleased NSString containing the name of the column in the result set 414 | - (NSString*)columnNameForIndex:(int)columnIdx { 415 | return [NSString stringWithUTF8String: sqlite3_column_name([_statement statement], columnIdx)]; 416 | } 417 | 418 | - (void)setParentDB:(FMDatabase *)newDb { 419 | _parentDB = newDb; 420 | } 421 | 422 | 423 | @end 424 | -------------------------------------------------------------------------------- /FmdbTest/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // FmdbTest 4 | // 5 | // Created by Tang Qiao on 12-4-22. 6 | // Copyright (c) 2012年 blog.devtang.com All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FmdbTestTests/FmdbTestTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.youdao.${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 | -------------------------------------------------------------------------------- /FmdbTestTests/FmdbTestTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // FmdbTestTests.h 3 | // FmdbTestTests 4 | // 5 | // Created by Tang Qiao on 12-4-22. 6 | // Copyright (c) 2012年 blog.devtang.com All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FmdbTestTests : SenTestCase 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /FmdbTestTests/FmdbTestTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FmdbTestTests.m 3 | // FmdbTestTests 4 | // 5 | // Created by Tang Qiao on 12-4-22. 6 | // Copyright (c) 2012年 blog.devtang.com All rights reserved. 7 | // 8 | 9 | #import "FmdbTestTests.h" 10 | 11 | @implementation FmdbTestTests 12 | 13 | - (void)setUp 14 | { 15 | [super setUp]; 16 | 17 | // Set-up code here. 18 | } 19 | 20 | - (void)tearDown 21 | { 22 | // Tear-down code here. 23 | 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample 28 | { 29 | STFail(@"Unit tests are not implemented yet in FmdbTestTests"); 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /FmdbTestTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | --------------------------------------------------------------------------------