├── .gitignore ├── .travis.yml ├── Example ├── Podfile ├── Podfile.lock ├── Tests │ ├── CipherTests.m │ ├── ExtraTests.m │ ├── Tests-Info.plist │ ├── Tests-Prefix.pch │ ├── Tests.m │ ├── VVExtraClasses.h │ ├── VVExtraClasses.m │ ├── VVTestClasses.h │ ├── VVTestClasses.m │ ├── en.lproj │ │ └── InfoPlist.strings │ └── mobiles.sqlite ├── VVSequelize.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── VVSequelize.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── VVSequelize │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Main.storyboard │ ├── Model │ ├── VVItem.h │ ├── VVItem.m │ ├── VVMessage.h │ ├── VVMessage.m │ ├── VVMock.h │ └── VVMock.m │ ├── VVAppDelegate.h │ ├── VVAppDelegate.m │ ├── VVSequelize-Info.plist │ ├── VVSequelize-Prefix.pch │ ├── VVTableViewController.h │ ├── VVTableViewController.m │ ├── en.lproj │ └── InfoPlist.strings │ ├── main.m │ └── 神话纪元.txt ├── LICENSE ├── README.md ├── VVSequelize.png ├── VVSequelize.podspec ├── VVSequelize.xmind ├── VVSequelize ├── Assets │ ├── .gitkeep │ └── VVPinYin.bundle │ │ ├── hanzi2pinyin.plist │ │ ├── pinyin.plist │ │ ├── syllables.txt │ │ └── transform.txt ├── Core │ ├── .gitkeep │ ├── Database │ │ ├── VVDBStatement.h │ │ ├── VVDBStatement.m │ │ ├── VVDatabase+Additions.h │ │ ├── VVDatabase+Additions.m │ │ ├── VVDatabase.h │ │ └── VVDatabase.m │ ├── KeyValue │ │ ├── NSObject+VVKeyValue.h │ │ ├── NSObject+VVKeyValue.m │ │ ├── VVClassInfo.h │ │ └── VVClassInfo.m │ └── Orm │ │ ├── NSObject+VVOrm.h │ │ ├── NSObject+VVOrm.m │ │ ├── VVForeignKey.h │ │ ├── VVForeignKey.m │ │ ├── VVOrm+Create.h │ │ ├── VVOrm+Create.m │ │ ├── VVOrm+Delete.h │ │ ├── VVOrm+Delete.m │ │ ├── VVOrm+Retrieve.h │ │ ├── VVOrm+Retrieve.m │ │ ├── VVOrm+Update.h │ │ ├── VVOrm+Update.m │ │ ├── VVOrm.h │ │ ├── VVOrm.m │ │ ├── VVOrmConfig.h │ │ ├── VVOrmConfig.m │ │ ├── VVOrmDefs.h │ │ ├── VVOrmView.h │ │ ├── VVOrmView.m │ │ ├── VVOrmable.h │ │ ├── VVOrmable.m │ │ ├── VVSelect.h │ │ └── VVSelect.m ├── FTS │ ├── Tokenizer │ │ ├── NSString+Tokenizer.h │ │ ├── NSString+Tokenizer.m │ │ ├── VVPinYinSegmentor.h │ │ ├── VVPinYinSegmentor.m │ │ ├── VVResultMatch.h │ │ ├── VVResultMatch.m │ │ ├── VVTokenEnumerator.h │ │ └── VVTokenEnumerator.m │ ├── VVDatabase+FTS.h │ ├── VVDatabase+FTS.m │ ├── VVFtsable.h │ ├── VVFtsable.m │ ├── VVOrm+FTS.h │ └── VVOrm+FTS.m ├── Util │ ├── VVDBCipher.h │ ├── VVDBCipher.m │ ├── VVDBUpgrader.h │ ├── VVDBUpgrader.m │ ├── VVOrmRoute.h │ └── VVOrmRoute.m └── VVSequelize.h └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 26 | # Carthage/Checkouts 27 | 28 | Carthage/Build 29 | 30 | # We recommend against adding the Pods directory to your .gitignore. However 31 | # you should judge for yourself, the pros and cons are mentioned at: 32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 33 | # 34 | # Note: if you ignore the Pods directory, make sure to uncomment 35 | # `pod install` in .travis.yml 36 | # 37 | # Pods/ 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/VVSequelize.xcworkspace -scheme VVSequelize-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | platform :ios, '10.0' 4 | install!'cocoapods', :warn_for_unused_master_specs_repo => false 5 | 6 | target 'VVSequelize_Example' do 7 | pod 'VVSequelize', :path => '../' 8 | 9 | target 'VVSequelize_Tests' do 10 | inherit! :search_paths 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - SQLCipher (4.4.0): 3 | - SQLCipher/standard (= 4.4.0) 4 | - SQLCipher/common (4.4.0) 5 | - SQLCipher/standard (4.4.0): 6 | - SQLCipher/common 7 | - VVSequelize (0.4.7): 8 | - VVSequelize/cipher (= 0.4.7) 9 | - VVSequelize/cipher (0.4.7): 10 | - SQLCipher 11 | - VVSequelize/core 12 | - VVSequelize/fts 13 | - VVSequelize/util 14 | - VVSequelize/core (0.4.7): 15 | - VVSequelize/header 16 | - VVSequelize/fts (0.4.7): 17 | - VVSequelize/core 18 | - VVSequelize/header 19 | - VVSequelize/header (0.4.7) 20 | - VVSequelize/util (0.4.7): 21 | - VVSequelize/header 22 | 23 | DEPENDENCIES: 24 | - VVSequelize (from `../`) 25 | 26 | SPEC REPOS: 27 | trunk: 28 | - SQLCipher 29 | 30 | EXTERNAL SOURCES: 31 | VVSequelize: 32 | :path: "../" 33 | 34 | SPEC CHECKSUMS: 35 | SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072 36 | VVSequelize: 68760aaacdfd94c370101e9820295898856426c2 37 | 38 | PODFILE CHECKSUM: c043495dabb40cfb5fd44ad1663377da0cd3e384 39 | 40 | COCOAPODS: 1.10.1 41 | -------------------------------------------------------------------------------- /Example/Tests/CipherTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // CipherTests.m 3 | // VVSequelize_Tests 4 | // 5 | // Created by Valo on 2020/9/3. 6 | // Copyright © 2020 Valo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface CipherTests : XCTestCase 13 | @property (nonatomic, copy) NSString *plaindb; 14 | @property (nonatomic, copy) NSString *encryptdb; 15 | @end 16 | 17 | @implementation CipherTests 18 | 19 | - (void)setUp { 20 | NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 21 | _plaindb = [path stringByAppendingPathComponent:@"0-plain.db"]; 22 | _encryptdb = [path stringByAppendingPathComponent:@"0-encrypt.db"]; 23 | NSLog(@"dir: %@", path); 24 | } 25 | 26 | - (void)tearDown { 27 | // Put teardown code here. This method is called after the invocation of each test method in the class. 28 | } 29 | 30 | - (void)testResetFile 31 | { 32 | NSFileManager *fm = [NSFileManager defaultManager]; 33 | NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"mobiles.sqlite" ofType:nil]; 34 | [fm removeItemAtPath:_plaindb error:nil]; 35 | [fm removeItemAtPath:_encryptdb error:nil]; 36 | [fm copyItemAtPath:sourcePath toPath:_plaindb error:nil]; 37 | } 38 | 39 | - (void)testEncrypt { 40 | XCTAssert([VVDBCipher encrypt:self.plaindb target:self.encryptdb key:@"123456" options:nil]); 41 | } 42 | 43 | - (void)testDecrypt { 44 | [[NSFileManager defaultManager] removeItemAtPath:_plaindb error:nil]; 45 | XCTAssert([VVDBCipher decrypt:self.encryptdb target:self.plaindb key:@"123456" options:nil]); 46 | } 47 | 48 | 49 | - (void)testEncryptWithOptions { 50 | NSArray *options = @[ 51 | @"PRAGMA cipher_plaintext_header_size = 32;", 52 | @"PRAGMA cipher_salt = \"x'01010101010101010101010101010101'\";", 53 | ]; 54 | XCTAssert([VVDBCipher encrypt:self.plaindb target:self.encryptdb key:@"123456" options:options]); 55 | } 56 | 57 | - (void)testDecryptWithOptions { 58 | NSArray *options = @[ 59 | @"PRAGMA cipher_plaintext_header_size = 32;", 60 | @"PRAGMA cipher_salt = \"x'01010101010101010101010101010101'\";", 61 | ]; 62 | [[NSFileManager defaultManager] removeItemAtPath:_plaindb error:nil]; 63 | XCTAssert([VVDBCipher decrypt:self.encryptdb target:self.plaindb key:@"123456" options:options]); 64 | } 65 | 66 | - (void)testChangeKey{ 67 | NSArray *srcOpts = @[ 68 | @"pragma cipher_plaintext_header_size = 0;", 69 | @"pragma cipher_page_size = 4096;", 70 | @"pragma kdf_iter = 64000;", 71 | @"pragma cipher_hmac_algorithm = HMAC_SHA1;", 72 | @"pragma cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;", 73 | ]; 74 | NSArray *tarOpts = @[ 75 | @"PRAGMA cipher_plaintext_header_size = 32;", 76 | @"PRAGMA cipher_salt = \"x'01010101010101010101010101010101'\";", 77 | ]; 78 | [VVDBCipher encrypt:_plaindb target:_encryptdb key:@"123456" options:srcOpts]; 79 | [[NSFileManager defaultManager] removeItemAtPath:_plaindb error:nil]; 80 | [VVDBCipher change:_encryptdb srcKey:@"123456" srcOpts:srcOpts tarKey:@"654321" tarOpts:tarOpts]; 81 | [VVDBCipher decrypt:_encryptdb target:_plaindb key:@"654321" options:tarOpts]; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /Example/Tests/ExtraTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExtraTests.m 3 | // VVSequelize_Tests 4 | // 5 | // Created by Valo on 2020/12/28. 6 | // Copyright © 2020 Valo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "VVExtraClasses.h" 12 | 13 | @interface ExtraTests : XCTestCase 14 | @property (nonatomic, strong) VVDatabase *vvdb; 15 | @end 16 | 17 | @implementation ExtraTests 18 | 19 | - (void)setUp { 20 | NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 21 | NSString *dbPath = [path stringByAppendingPathComponent:@"extra.sqlite"]; 22 | NSLog(@"[VVDB][DEBUG] db path: %@", dbPath); 23 | self.vvdb = [[VVDatabase alloc] initWithPath:dbPath]; 24 | [self.vvdb setTraceHook:^int (unsigned int mask, void *_Nonnull stmt, void *_Nonnull sql) { 25 | NSLog(@"[VVDB][DEBUG] sql: %s", (char *)sql); 26 | return 0; 27 | }]; 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | } 33 | 34 | - (void)testExample { 35 | VVOrm *classOrm = [VVOrm ormWithClass:VVTestClass.class name:@"classes" database:self.vvdb setup:VVOrmSetupRebuild]; 36 | VVOrm *studentOrm = [VVOrm ormWithClass:VVTestStudent.class name:@"students" database:self.vvdb setup:VVOrmSetupRebuild]; 37 | if (classOrm && studentOrm) { } 38 | } 39 | 40 | - (void)testPerformanceExample { 41 | // This is an example of a performance test case. 42 | [self measureBlock:^{ 43 | // Put the code you want to measure the time of here. 44 | }]; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // The contents of this file are implicitly included at the beginning of every test case source file. 2 | 3 | #ifdef __OBJC__ 4 | 5 | 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /Example/Tests/VVExtraClasses.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVExtraClasses.h 3 | // VVSequelize_Tests 4 | // 5 | // Created by Valo on 2020/12/28. 6 | // Copyright © 2020 Valo. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface VVTestClass : NSObject 15 | @property (nonatomic, assign) uint64_t cid; 16 | @property (nonatomic, copy) NSString *name; 17 | 18 | @end 19 | 20 | @interface VVTestStudent : NSObject 21 | @property (nonatomic, assign) uint64_t sid; 22 | @property (nonatomic, assign) uint64_t cid; 23 | @property (nonatomic, copy) NSString *sno; 24 | @property (nonatomic, copy) NSString *name; 25 | @property (nonatomic, assign) NSUInteger age; 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /Example/Tests/VVExtraClasses.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVExtraClasses.m 3 | // VVSequelize_Tests 4 | // 5 | // Created by Valo on 2020/12/28. 6 | // Copyright © 2020 Valo. All rights reserved. 7 | // 8 | 9 | #import "VVExtraClasses.h" 10 | 11 | @implementation VVTestClass 12 | 13 | + (NSArray *)primaries 14 | { 15 | return @[@"cid"]; 16 | } 17 | 18 | + (BOOL)pkAutoInc 19 | { 20 | return YES; 21 | } 22 | 23 | @end 24 | 25 | 26 | @implementation VVTestStudent 27 | 28 | + (NSArray *)primaries 29 | { 30 | return @[@"sid"]; 31 | } 32 | 33 | + (BOOL)pkAutoInc 34 | { 35 | return YES; 36 | } 37 | 38 | + (NSArray *)notnulls 39 | { 40 | return @[@"sno"]; 41 | } 42 | 43 | + (NSArray *)uniques 44 | { 45 | return @[@"sno"]; 46 | } 47 | 48 | + (NSArray *)foreignKeys 49 | { 50 | VVForeignKey *fk = [VVForeignKey foreignKeyWithTable:@"classes" from:@"cid" to:@"cid" on_update:VVForeignKeyActionCascade on_delete:VVForeignKeyActionRestrict]; 51 | return @[fk]; 52 | } 53 | 54 | + (NSArray *)checks 55 | { 56 | return @[@"age > 12"]; 57 | } 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /Example/Tests/VVTestClasses.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVTestClasses.h 3 | // VVSequelize_Tests 4 | // 5 | // Created by Valo on 2018/6/12. 6 | // Copyright © 2018年 Valo Lee. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface VVTestMobile : NSObject 13 | @property (nonatomic, copy) NSString *mobile; 14 | @property (nonatomic, copy) NSString *province; 15 | @property (nonatomic, copy) NSString *city; 16 | @property (nonatomic, copy) NSString *carrier; 17 | @property (nonatomic, copy) NSString *industry; 18 | @property (nonatomic, assign) CGFloat relative; 19 | @property (nonatomic, assign) NSInteger times; 20 | @property (nonatomic, assign) NSInteger frequency; 21 | @end 22 | 23 | @interface VVTestPerson : NSObject 24 | @property (nonatomic, copy) NSString *idcard; 25 | @property (nonatomic, copy) NSString *name; 26 | @property (nonatomic, assign) NSInteger age; 27 | @property (nonatomic, assign) NSDate *birth; 28 | @property (nonatomic, copy) NSString *mobile; 29 | @property (nonatomic, strong) NSData *data; 30 | 31 | /* 32 | @property (nonatomic, assign) BOOL male; 33 | @property (nonatomic, strong) NSDictionary *dic; 34 | @property (nonatomic, strong) NSMutableSet *mdic; 35 | @property (nonatomic, strong) NSArray *arr; 36 | @property (nonatomic, strong) NSMutableArray *marr; 37 | @property (nonatomic, copy ) NSMutableString *mstr; 38 | @property (nonatomic, strong) NSSet *set; 39 | @property (nonatomic, strong) NSMutableSet *mset; 40 | @property (nonatomic, strong) NSData *data; 41 | @property (nonatomic, strong) NSMutableData *mdata; 42 | */ 43 | 44 | @end 45 | 46 | @interface VVTestOne : NSObject 47 | @property (nonatomic, assign) NSInteger oneId; ///< id 48 | @property (nonatomic, strong) VVTestPerson *person; ///< person 49 | @property (nonatomic, strong) NSArray *mobiles; ///< mobile 50 | @property (nonatomic, strong) NSSet *friends; ///< friends 51 | @property (nonatomic, copy) NSString *flag; ///< flag 52 | @property (nonatomic, strong) NSDictionary *dic; ///< flag 53 | @property (nonatomic, strong) NSArray *arr; ///< flag 54 | 55 | @end 56 | 57 | typedef union TestUnion { 58 | uint32_t num; 59 | char ch; 60 | } VVTestUnion; 61 | 62 | typedef struct TestStruct { 63 | uint8_t num; 64 | char ch; 65 | } VVTestStruct; 66 | 67 | @interface VVTestMix : NSObject 68 | @property (nonatomic, assign) NSInteger cnum; 69 | @property (nonatomic, strong) NSValue *val; 70 | @property (nonatomic, strong) NSNumber *num; 71 | @property (nonatomic, strong) NSDecimalNumber *decNum; 72 | @property (nonatomic, assign) SEL selector; 73 | @property (nonatomic, assign) CGSize size; 74 | @property (nonatomic, assign) CGPoint point; 75 | @property (nonatomic, assign) VVTestUnion un; 76 | @property (nonatomic, assign) VVTestStruct stru; 77 | @property (nonatomic, assign) char *str; 78 | @property (nonatomic, assign) void *unknown; 79 | @property (nonatomic, assign) char sa; 80 | @property (nonatomic, strong) NSData *data; 81 | 82 | @end 83 | 84 | @interface VVTestEnumerator : NSObject 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /Example/Tests/VVTestClasses.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVTestClasses.m 3 | // VVSequelize_Tests 4 | // 5 | // Created by Valo on 2018/6/12. 6 | // Copyright © 2018年 Valo Lee. All rights reserved. 7 | // 8 | 9 | #import "VVTestClasses.h" 10 | #import 11 | 12 | @implementation VVTestMobile 13 | 14 | - (NSString *)description { 15 | return [NSString stringWithFormat:@"%@ | %@ | %@ | %@ | %@ | %2.f | %@", _mobile, _province, _city, _carrier, _industry, _relative, @(_times)]; 16 | } 17 | 18 | @end 19 | 20 | @implementation VVTestPerson 21 | 22 | - (NSString *)description { 23 | return [NSString stringWithFormat:@"%@ | %@ | %@ | %@ | %@", _name, _idcard, @(_age), _birth, _mobile]; 24 | } 25 | 26 | @end 27 | 28 | @implementation VVTestOne 29 | 30 | + (NSDictionary *)mj_objectClassInArray 31 | { 32 | return @{ @"mobiles": VVTestMobile.class, @"friends": @"VVTestPerson" }; 33 | } 34 | 35 | + (nullable NSArray *)vv_ignoreProperties 36 | { 37 | return @[@"dic"]; 38 | } 39 | 40 | @end 41 | 42 | @implementation VVTestMix 43 | 44 | @end 45 | 46 | @implementation VVTestEnumerator 47 | + (NSArray *)enumerate:(const char *)input mask:(VVTokenMask)mask 48 | { 49 | NSString *string = [NSString stringWithUTF8String:input]; 50 | NSUInteger count = string.length; 51 | NSMutableArray *results = [NSMutableArray arrayWithCapacity:count]; 52 | for (NSUInteger i = 0; i < count; i++) { 53 | const char *prefix = [string substringToIndex:i].cLangString; 54 | NSString *cur = [string substringWithRange:NSMakeRange(i, 1)]; 55 | int start = (int)strlen(prefix); 56 | int len = (int)strlen(cur.cLangString); 57 | VVToken *token = [VVToken token:cur.UTF8String len:len start:start end:(start + len)]; 58 | [results addObject:token]; 59 | } 60 | return results; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/Tests/mobiles.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pozi119/VVSequelize/a575648ec93ab8fc68e7f9b769ea9d64698b126a/Example/Tests/mobiles.sqlite -------------------------------------------------------------------------------- /Example/VVSequelize.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/VVSequelize.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/VVSequelize.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/VVSequelize/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Example/VVSequelize/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Example/VVSequelize/Model/VVItem.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | NS_ASSUME_NONNULL_BEGIN 5 | 6 | @class VVDatabase,VVOrm; 7 | 8 | @interface VVItem : NSObject 9 | @property (nonatomic, copy ) NSString *tableName; 10 | @property (nonatomic, assign) unsigned long long count; 11 | @property (nonatomic, assign) unsigned long long maxCount; 12 | @property (nonatomic, weak ) UILabel *label; 13 | 14 | @property (nonatomic, copy ) NSString *dbName; 15 | @property (nonatomic, copy ) NSString *dbPath; 16 | @property (nonatomic, strong) VVOrm *orm; 17 | @property (nonatomic, strong) VVDatabase *db; 18 | @property (nonatomic, assign) unsigned long long fileSize; 19 | 20 | @property (nonatomic, copy ) NSString *ftsDbName; 21 | @property (nonatomic, copy ) NSString *ftsDbPath; 22 | @property (nonatomic, strong) VVOrm *ftsOrm; 23 | @property (nonatomic, strong) VVDatabase *ftsDb; 24 | @property (nonatomic, assign) unsigned long long ftsFileSize; 25 | 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /Example/VVSequelize/Model/VVItem.m: -------------------------------------------------------------------------------- 1 | 2 | #import "VVItem.h" 3 | 4 | @implementation VVItem 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /Example/VVSequelize/Model/VVMessage.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | #import 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | 7 | @interface VVMessage : NSObject 8 | 9 | @property (nonatomic, copy) NSString *dialog_id; 10 | @property (nonatomic, assign) long long message_id; 11 | @property (nonatomic, assign) long long client_message_id; 12 | @property (nonatomic, assign) long long send_time; 13 | @property (nonatomic, assign) NSInteger type; 14 | @property (nonatomic, copy) NSString *info; 15 | 16 | + (NSArray *)mockThousandModels:(long long)startMessageId; 17 | 18 | + (NSArray *)mockThousandModels:(NSArray *)infos start:(long long)startMessageId; 19 | 20 | @end 21 | 22 | @interface VVMessage (VVOrmable) 23 | 24 | @end 25 | 26 | @interface VVMessage (VVFtsable) 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /Example/VVSequelize/Model/VVMessage.m: -------------------------------------------------------------------------------- 1 | 2 | #import "VVMessage.h" 3 | #import "VVMock.h" 4 | 5 | @implementation VVMessage 6 | 7 | + (NSArray *)mockThousandModels:(long long)startMessageId { 8 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:1000]; 9 | for (long long i = 0; i < 1000; i++) { 10 | VVMessage *message = [VVMessage new]; 11 | message.dialog_id = @"S-10086"; 12 | message.message_id = startMessageId + i; 13 | message.client_message_id = startMessageId + i; 14 | message.send_time = [[NSDate date] timeIntervalSince1970]; 15 | message.type = arc4random_uniform(5); 16 | message.info = [VVMock.shared shortText]; 17 | [array addObject:message]; 18 | } 19 | return array; 20 | } 21 | 22 | + (NSArray *)mockThousandModels:(NSArray *)infos start:(long long)startMessageId { 23 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:1000]; 24 | long long count = infos.count; 25 | for (long long i = 0; i < 1000; i++) { 26 | long long messageId = startMessageId + i; 27 | VVMessage *message = [VVMessage new]; 28 | message.dialog_id = @"S-10086"; 29 | message.message_id = messageId; 30 | message.client_message_id = startMessageId + i; 31 | message.send_time = [[NSDate date] timeIntervalSince1970]; 32 | message.type = arc4random_uniform(5); 33 | message.info = infos[(NSUInteger)messageId % count]; 34 | [array addObject:message]; 35 | } 36 | return array; 37 | } 38 | 39 | - (NSString *)description 40 | { 41 | return [NSString stringWithFormat:@"%@,%@,%@,%@: %@", _dialog_id, @(_message_id), @(_send_time), @(_type), _info]; 42 | } 43 | 44 | @end 45 | 46 | @implementation VVMessage (VVOrmable) 47 | 48 | + (NSArray *)primaries 49 | { 50 | return @[@"message_id"]; 51 | } 52 | 53 | @end 54 | 55 | @implementation VVMessage (VVFtsable) 56 | 57 | + (NSArray *)fts_indexes 58 | { 59 | return @[@"info"]; 60 | } 61 | 62 | + (NSString *)fts_tokenizer 63 | { 64 | return @"sequelize 3"; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Example/VVSequelize/Model/VVMock.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | @interface VVMock : NSObject 5 | 6 | + (instancetype)shared; 7 | 8 | - (NSString *)nick; 9 | 10 | - (NSString *)name; 11 | 12 | - (NSString *)shortText; 13 | 14 | - (NSString *)longText; 15 | 16 | - (NSString *)longLongText; 17 | 18 | - (NSString *)textEn; 19 | 20 | - (NSString *)mobile; 21 | 22 | - (NSString *)email; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Example/VVSequelize/Model/VVMock.m: -------------------------------------------------------------------------------- 1 | 2 | #import "VVMock.h" 3 | 4 | NSString * const chars = @"a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P K R S T U V W X Y Z 1 2 3 4 5 6 7 8 9 0"; 5 | 6 | NSString * const lastNames = @"王 李 张 刘 陈 杨 赵 黄 周 吴 徐 孙 胡 朱 高 林 何 郭 马 罗 梁 宋 郑 谢 韩 唐 冯 于 董 萧 程 曹 袁 邓 许 傅 沈 曾 彭 吕 苏 卢 蒋 蔡 贾 丁 魏 薛 叶 阎 余 潘 杜 戴 夏 锺 汪 田 任 姜 范 方 石 姚 谭 廖 邹 熊 金 陆 郝 孔 白 崔 康 毛 邱 秦 江 史 顾 侯 邵 孟 龙 万 段 雷 钱 汤 尹 黎 易 常 武 乔 贺 赖 龚 文"; 7 | 8 | NSString * const firstNames = @"怡君 雅婷 欣怡 雅雯 家豪 怡婷 宗翰 雅惠 志豪 心怡 建宏 佳蓉 佩珊 靜怡 志偉 雅玲 佩君 俊宏 佳穎 怡伶 婉婷 俊傑 郁婷 怡如 鈺婷 靜宜 彥廷 冠宇 佳玲 詩婷 家瑋 承翰 詩涵 佳慧 惠雯 宜君 雅琪 雅文 柏翰 韻如 思穎 俊賢 玉婷 淑芬 琬婷 家銘 怡靜 冠廷 雅萍 怡萱 信宏 婷婷 惠婷 淑娟 馨儀 威廷 雅慧 淑惠 佩蓉 哲瑋 智偉 淑婷 宜芳 佳樺 珮瑜 嘉玲 依婷 雅芳 欣儀 慧君 芳瑜 俊豪 宗憲 哲維 志宏 家瑜 雅涵 宜靜 筱婷 佳琪 怡文 淑君 郁雯 冠宏 士豪 惠君 家榮 嘉宏 偉倫 雅筑 怡潔 慧玲 佩玲 欣穎 建志 惠如 雅君 明哲 怡安 孟儒 于婷 俊宇 美玲 欣宜 俊廷 志鴻 彥君 宗霖 芳儀 俊毅 怡慧 瑋婷 佩璇 美君 珮君 建良 政宏 建銘 柏宏 志強 雅琳 佳雯 惠玲 仁傑 書豪 志銘 淑玲 智凱 盈君 思妤 佳霖 士傑 智翔 建宇 婉如 淑萍 子豪 偉哲 凱翔 文傑 建勳 博文 筱涵 淑華 彥宏 郁涵 佳欣 志遠 怡璇 嘉慧 佳伶 宇軒 嘉文 玉玲 俊凱 思婷 千惠 雅芬 建豪 莉婷 立偉 志明 怡菁 淑貞 靜雯 家宏 淑慧 明宏 怡芳 舒婷 雅茹 書瑋 俊佑 冠霖 怡雯 俊銘 建智 雅如 哲宇 佩穎 宜蓁 姿吟 宜珊 家維 柏勳 凱文 佩芬 建文 明翰 怡秀 惠文 佳容 人豪 宗穎 筱雯 婉君 雅鈴 智傑 佳怡 凱婷 文彥 世偉 俊良 俊彥 欣樺 彥儒 育誠 蕙如 文豪 瓊文 伊婷 俊瑋 思涵 哲豪 嘉琪 芳如 姿君 家偉 姿穎 佩樺 慧如 聖文 文馨 郁文 慧雯 秀玲 怡欣 嘉偉 怡均 馨慧 婉瑜 英傑 佳君 怡萍 靜儀 美惠 彥霖 振宇 政翰 家慧 佳芳 文彬 佳瑩 宜臻 俊男 明憲 柏毅 家慶 韋廷 姿瑩 建廷 姿伶 美慧 佩怡 安琪 佳靜 慧娟 欣潔 鎮宇 柏廷 千慧 盈如 珮如 慧婷 志瑋 子翔 昱廷 淑芳 建成 柏宇 宜庭 佳惠 靖雯 慧珊 威志 彥良 志成 曉雯 佳宏 建中 維倫 子軒 承恩 俊德 凱傑 宗賢 宜婷 彥伶 惠茹 建安 俊霖 哲銘 文君 育如 家齊 志文 珮綺 怡臻 建霖 佩芳 孟君 宜真 聖傑 庭瑋 乃文 君豪 玉如 孟穎 淑敏 威宇 秋萍 俊達 宜樺 美芳 佳儀 偉誠 可欣 嘉鴻 文凱 家華 世豪 志傑 世傑 舒涵 文婷 志忠 姵君 雯婷 雅嵐 秀娟 淑媛 家弘 佩宜 政達 政勳 佩真 嘉惠 意婷 世昌 育賢 宗儒 慧敏 怡蓁 宜潔 郁芬 冠伶 佩娟 佳燕 麗君 孟潔 珮雯 惠萍 姿蓉 淑雯 宛蓉 雅菁 瓊慧 惠敏 世杰 珮珊 冠穎 嘉雯 靜芳 國豪 怡廷 志賢 孟哲 靜如 正偉 佩琪 毓婷 明勳 佩瑜 曉君 依潔 佳琳 育瑋 明慧 俊偉 怡樺 美娟 子傑 志龍 宛儒 玉芳 冠儒 孟翰 偉翔 怡芬 逸群 奕廷 家祥 柏豪 博仁 怡真 念慈 孟勳 柏翔 惠娟 玫君 國維 竹君 美如 于庭 彥翔 雅淳 偉豪 曉薇 佳芬 家賢 韋伶 宛真 佩儀 佳芸 曉婷 瑋倫 湘婷 鈺雯 孟軒 麗娟 珮甄 維哲 佳儒 偉傑 于珊 莉雯 宗佑 淑真 智仁 偉銘 佩如 佳宜 嘉豪 思潔 志維 仁豪 柏均 玉珊 怡華 瑋玲 慧萍 柏鈞 麗雯 政憲 俊諺 耀文 家綺 建華 孟璇 姿儀 欣蓉 欣瑜 彥豪 佩雯 巧玲 佳勳 智文 韻婷 柏霖 佳純 聖凱 建佑 國華 珮琪 美伶 蕙君 佳真 婉菁 佳蓁 雅欣 宇翔 彥文 明峰 婉玲 玉芬 育德 雅馨 欣慧 俊杰 政霖 思翰 倩如 佩芸 彥志 韋志 建興 志宇 明達 宗勳 子揚 士賢 馨文 凱鈞 柏凱 盈潔 彥甫 曉玲 雅晴 佩瑩 彥宇 美華 如君 俊翰 志誠 文賢 明倫 怡蓉 慧茹 子涵 裕翔 慧真 冠傑 思賢 宏偉 曉菁 怡珊 婉茹 姿婷 思瑩 明志 鴻文 盈秀 俊榮 弘毅 欣儒 智豪 郁珊 東霖 佩儒 于萱 育民 文琪 明賢 育菁 瓊儀 婕妤 耀仁 郁茹 育銘 彥均 依璇 政緯 孟樺 育慈 靜芬 姿妤 俊穎 淑如 思樺 俊緯 育瑄 文宏 玟君 筱薇 文正 柏諺 健銘 依玲 文 俊翔 俊仁 宜玲 詩怡 俐君 家琪 立婷 彥勳 士哲 宗哲 建瑋 俊維 冠文 智鈞 俊嘉 郁翔 書銘 俊安 志軒 冠華 明儒 俊明 志杰 佳瑜 秀慧 家欣 國瑋 景翔 智堯 怡穎 柏瑋 育萱 健豪 秉勳 建德 峻瑋 玉娟 柏元 明軒 宗達 家瑩 佳璇 宜萱 致遠 筱芸 淑卿 明芳 曉萍 子瑜 香君 思吟 鈺涵 育霖 耀德 彥銘 冠中 怡瑩 詩雅 宜欣 冠豪 亭君 峻豪 怡瑄 志祥 志峰 家緯 怡嘉 筱君 逸軒 依萍 岳霖 逸凡 玉萍 慧芳 貞儀 宛臻 哲緯 書維 俊逸 淑珍 明潔 婉甄 雨潔 慧珍 思慧 偉智 瑋 曉琪 慧貞 心瑜 冠樺 麗如 振豪 昱宏 立群 郁芳 雅云 政哲 哲偉 靖雅 國榮 佩伶 彥如 怡禎 秀雯 俊吉 欣妤 宏達 佳倫 雅鈞 大為 玟伶 孟珊 宜芬 志榮 婷 俊成 柏青 文怡 佳臻 曉慧 佳吟 政賢 庭瑜 宜璇 玉雯 文欣 文斌 佳珍 嘉琳 彥鈞 郁欣 珮慈 馨瑩 柏安 彥婷 永昌 宗樺 哲民 育豪 曉芬 怡妏 智超 明德 敏華 耀中 明輝 啟宏 建邦 建明 佩欣 雅莉 易霖 宜穎 國峰 淑怡 秀如 靜慧 尚儒 惠琪 秋燕 雅娟 嘉瑩 志翔 秉翰 俊儒 文祥 國偉 寧 俊憲 凱元 慧文 晏如 志仁 靜茹 美琪 嘉祥 安妮 惠珍 博凱 冠儀 鳳儀 家興 博元 明杰 俊雄 家禎 維真 雅雲 偉志 瑋珊 淑美 詩穎 珮瑄 婕 宗祐 志揚 彥博 政諺 裕仁 信豪 銘鴻 文華 俊龍 昱翔 建璋 凱琳 士弘 欣如 力瑋 詠翔 奕君 偉成 博鈞 建榮 宏仁 金龍 哲安 惠菁 瑞鴻 冠甫 怡玲 佩吟 詩雯 大維 嘉駿 惠珊 政儒 柏志 智強 永祥 昆霖 漢威 文龍 嘉倫 勝傑 育正 俐伶 建彰 育廷 佳琦 怡貞 家誠 凱雯 冠良 家寧 彥伯 于真 淑雅 晉嘉 婉柔 佩萱 景文 嘉芸 思齊 虹君 孟修 玉菁 怡儒 秀芬 宜儒 懿萱 宏銘 奕安 怡青 偉民 冠瑋 秀婷 家儀 承勳 依珊 建忠 書瑜 建男 婉琳 妍伶 心儀 宏文 瑜珊 信良 盈盈 鼎鈞 孟婷 佩容 世明 宜珍 致豪 淑儀 子芸 美吟 孟蓉 智勇 淑鈴 玉潔 奕辰 育儒 佳鴻 巧雯 彥傑 明宗 嘉芳 欣蓓 士銘 嘉仁 詩芸 怡珍 佩茹 婉萍 宗毅 世賢 俞君 彥佑 柏仁 柏儒 麗婷 明璋 建樺 宜軒 郁仁 建緯 銘宏 孟娟 志堅 珊珊 宜慧 佳盈 筱筠 佳翰 育嘉 仁宏 逸婷 信安 士軒 思嘉 珮茹 惠芳 文政 文心 宛君 雅琴 英哲 瑞祥 志雄 慧怡 嘉慶 建甫 奕翔 政穎 書賢 明君 怡寧 孟芳 傑 凱倫 筱筑 宛玲 允中 玉琳 啟銘 美儀 育婷 佳音 佳恩 明修 震宇 宜蓉 威任 昱辰 宏儒 政偉 育維 自強 明穎 麗華 文志 婉真 竣傑 柏辰 哲嘉 偉婷 嘉容 宛庭 仲豪 家源 廷宇 玉華 聖翔 品潔 淑菁 子維 盈瑩 曉嵐 君儀 子婷 柏成 宛婷 鈺翔 婉琪 昱豪 惠美 志鵬 聖哲 亞璇 明鴻 維仁 柏村 銘哲 珮玲 大鈞 薇如 智瑋 依靜 耀賢 美秀 曉涵 志平 育仁 韋翔 秀芳 柏賢 冠銘 晉瑋 文瑜 家駿 彥蓉 俊儀 振瑋 雅琦 姿慧 宛諭 宜娟 柏任 鈺茹 鈺珊 冠群 書婷 佳瑋 信傑 宇婷 慧慈 韻竹 伟 芳 娜 秀英 敏 静 丽 强 磊 军 洋 勇 艳 杰 娟 涛 明 超 秀兰 霞 平 刚 桂英"; 9 | 10 | // article.length = 3885; 11 | NSString * const article = @"2002年,一家名为亿迅的客服外包公司向媒体算了笔账:平均一个Call Center(呼叫中心)人工座席的建设成本为10万元,而一个初具规模的Call Center则需要有50个座席。这样,以50个座席计算,一个Call Center的建设费用就高达500万元。总裁李冰影判断,呼叫中心外包是趋势。彼时,硬件软件成本高企,而呼叫中心集成了程控交换机、排队机、CTI网关和语音记录系统等多种设备和系统,于是成为高科技高投入行业。十几年后,软硬件成本下降,但外包趋势不可逆。呼叫中心从高科技行业转为劳动密集型行业,电话客服代表,及其背后的培训、管理、流程协调成为成本的大头,并且越来越贵。行业人员不再提坐席建设成本,他们更愿意将“单呼成本”(每一通电话的成本)视为关键。“呼叫中心的成本是要抠到每一秒钟的,每一秒都是钱。”一家企业的客服负责人李伟(化名)告诉我,这家企业有着业内公认的成熟客服体系,其每秒成本为3分钱,1分钟成本为一块八。也就是说,一个客户打了5分钟的投诉电话,这家企业就花出9块钱。客服成本高不是问题,但问题是客服中心不是盈利部门而是成本中心。“一个客服人才解决纠纷的能力再强,他也不赚钱。中国企业喜欢看人效比(平均每个人给公司创造的利润),不赚钱的话,我为什么要养那么多客服?特别是高级客服?”李伟说道。于是企业自然将客服外包出去。在经营有压力的企业中,客服成为其缩减开支的重要板块。“这么多年客服一直都是这样子的,每家都说客服很重要,但是没人愿意给客服掏钱。”回到文章题目,为什么接你电话的客服总是解决不了问题?答案:钱。一、外包不是问题8月25日中午,浙江省乐清市公安局官方微博@乐清公安发布信息,破获一起滴滴顺风车司机强奸杀害女乘客的案件。在这起事件中,滴滴客服的反应备受诟病。根据被害人好友吴某在微博上披露,他曾在15:42到16:42期间,先后7次与滴滴平台联系,询问事件进展,客服回应称,“一线客服没有权限,请您耐心等待,您的反馈我们会为您加急标红”、“将有相关安全专家介入处理此事,会在1小时内回复。”但数小时过后仍然没有反馈。警方也表示,在接到受害人亲友报警后,先后3次与滴滴联系请求提供犯罪嫌疑人车牌号以及其他个人信息。但从开始联系到真正获取到有效信息,前后耗时92分钟。出事后不久,媒体曝出滴滴客服为一家总部在上海的外包公司。于是,客服的迟钝反应似乎也有了解释。在固有的观点中,外包通常与“偷工减料”“不专业”划等号,将其改为自营能解决大半问题。但在客服领域有些不同,外包客服尤其是早期搭建起来的呼叫中心算得上专业。如文章开头所述,因为搭建呼叫中心,需要付出高额的软件、硬件成本,以及面临人员管理、招聘、培训等问题,所以能搭建呼叫中心的企业通常拥有较为雄厚的资金和技术实力。1995年,美国客户服务领域权威咨询机构COPC制定了一套服务标准,后来这一标准成为国际上公认的呼叫中心服务标准。由于COPC认证标准过于严格,并且需要付出百万级别认证费用和近一年的认证时间,因此国内通过COPC认证的呼叫中心并不多。2004年联想的呼叫中心成为第一个通过认证的呼叫中心,为此联想在当年曾特意召开过一次发布会,足见重视。同一年,又一个企业紧跟联想获得认证,这是一个总部位于上海的专门提供外包服务的呼叫中心。相比较国内企业多采用“自营+外包”的模式,大部分欧美企业更倾向于选择外包。一个被广为引用的数据是,在世界500强企业中,将一项或多项重要业务流程交给商业呼叫中心来完成的企业超过90%。汇丰银行是少数坚持自营客服的金融企业,曾在印度搭建自营呼叫中心,但在2008年金融危机期间,也选择将其出售。汇丰与收购方签订一个十年的服务合同,来保持服务的正常进行,但成本下降。王强(化名)就职于国内某大型呼叫中心,“一些企业把客服外包出来不单纯是出于成本考虑,还有一个原因是学习借鉴,像我们这种体量,不是只做一个客户,而是一堆客户,有些客户服务是互通的,我们可以跟他们提建议。”他告诉我。身在甲方的李伟也有同样的表述:“不要觉得外包,质量就下来了。这个行业的一些规范是由外包建立起来的,企业得去取经。”“当然,我说的是正规的外包公司。”王强补充道。二、问题是没钱既然外包公司具备专业技能,那为什么在滴滴事件中表现得如此业余?答案在权限上。依据界面报道,滴滴的外包客服均为一线客服,其权限仅有“接电话”和“提交”,即便是组长,权限也仅限于向用户提供代金券补偿,金额为5元。一线客服可以将投诉加急处理给上一级客服,但也有比例限制,加急的比例不能超过10%。这涉及到客服最常用的两个权限:赔偿权限、投诉升级权限。滴滴一线客服的赔偿权限约等于零。互联网公司一线客服的赔偿权限普遍不高,即便自营模式的京东,也只有20元。在知乎上,京东一线客服透露,其赔偿的最大权限是2000京东(100京豆可替代1元),有业内人士告诉我,这在互联网企业里已经算是一线客服的较高赔偿权限。此前锤子手机出现系统升级导致微信聊天记录清空的问题,客服回复是赔偿一个充电宝。一线客服解决不了,就升级到高级客服去解决,这涉及到“投诉升级权限”。但王强告诉我,并不是所有一线解决不了的问题都能升级到高级客服去,甲方会设定一些限制条件,比如滴滴有比例限制。“我们做的是整个流程的外包,相当把他们的招聘、培训、业务流程梳理,甚至流程优化,我们完全复制过来。区别就是他们的人员权限高于我们。所以这就造成了外包行业客服在处理投诉的时候,很多东西都做不了决定。”王强说。为什么不能开放权限?因为关系到成本。赔偿成本。身在甲方,李伟的表述比较直白:“外包会问你会给我多少资源。那你是往多里给,还是往少里给?如果给得多了,外包肯定会卡着上限用;如果给得少了,外包会说不够用。不管多少,外包肯定会把你的资源用到最后一滴的。”处在外包公司的王强坦承,提高赔偿权限,会存在员工乱用的风险。“这个就看甲方对于用户体验的理解,如果这个事情给用户造成损失五百块钱,这五百块钱,一分钟解决赔付和半个月赔付,完全就是两种体验。”人力成本。高级客服无论在薪资水平还是培养成本上都远高于一线客服。“配置1000个高级客服跟配置100个高级客服成本不一样。所以要控制住高级投诉的数量。”培训成本。给到的资源越多,意味着培训难度越大,提高培训成本。成本不仅限制一线客服的权限,也限制了优质服务模式的推广。在客服行业,有一个模式叫“首问负责制”也叫“一站到底”(One Stop Service),由一个人或一个小团队将一个投诉从头跟到尾,并且这个人/团队能协调内外部资源解决问题。这避免了同一个人的投诉被不同的客服接听,要不断重复阐述问题,极大提升用户体验。但这一模式只有少部分企业在应用。“这个成本非常高。呼入和呼出电话的成本是不一样的。客服主动把电话打出去比用户把电话打过来,花费的时间更长,所以呼出成本更高。更关键的是,首问负责制比较占用人力,举例本来上午排班有20个人,但有两个人要去负责专门的投诉,这样影响接起率等指标。”李伟说。三、最终还是回到价值观上呼叫中心垂直网站CTI,曾披露一个行业数据:在呼叫中心的成本结构中,人力资源是最大项目,占有全部成本比例均超过二分之一,(占比)最高(的呼叫中心)能达到80%。巨大的成本压力之下,呼叫中心外迁成为大势。在国际上,欧美国家偏爱菲律宾。菲律宾人口有1.049亿,位于全球第13位,且官方语言为英语。所以近两年,地处东南亚的菲律宾在近些年超过印度成为最大的外包服务提供国。依据菲律宾《商业镜报》报道,到2016年,菲服务外包产业收入将达到250亿美元,从业人员将达到130万人。Convergys,全球最大的服务外包商,在菲律宾的分公司有30多家,它在全球的雇员有12.5万人,其中一半都在菲律宾。更早时候,摩根大通将14000座席的呼叫中心也从美国迁移到了菲律宾。而国内,由于与“互联网+”挂钩,且进入门槛低,能解决大量劳动力就业问题,因此呼叫中心成为各三四线政府争抢的引入项目。合肥喊出“中国呼叫中心之都”的口号,要建设10万座席,创造30万就业岗位。《贵阳日报》则称,贵阳呼叫中心规模已经成为全国第五大,仅次于北上广苏。一时间,各地呼叫中心产业基地扎堆儿出现。但致富不忘乡亲。2009年,刘强东将京东全国客户服务中心放在老家宿迁,据说带动了2万人就业,贡献20亿的税收。2017年11月,江西上饶人程维出现在首届赣商大会,宣布将在老家上饶建立一个大型互联网客服中心,座席将达3000个。既致富乡亲又降低人力成本,一举两得。不过这还不够,为降低成本,客服外包企业们会与学校合作,以实习名义招用未毕业的学生。凤凰科技援引滴滴外包商的话:“滴滴为了尽量压低成本,甚至会暗示我们,大专以上学历都不需要,有的供应商会招聘十五六岁的实习生。”这似乎是行业公开的秘密,甚至有老师撰写诸如《客服外包公司与高职学校合作的利弊研究》的研究文章,来证实其可行性。但对于血汗工厂的形容,外包商们觉得有些委屈,“像国内某最大电商平台,他们要求是起码本科起的。这个就是甲方客户以及业务的不同来要求的。不是我们想招什么样的,而是他们要招什么样的。”王强说。在他看来,甲方对于客服的投入,归结为这个企业对客户的重视程度,而这归根结底源于其价值观。客服行业从业者愿意用亚马逊来举例什么是“客户至上”:每次客服沟通是没有时长限制的、一线客服有直接免单权限……于是,本文标题的答案最后又回归到企业价值观上"; 12 | 13 | // articleEn.length = 757; 14 | NSString * const articleEn = @"China has published a report on the human rights situation in the US, which exposed Washington's human rights violations in such areas as the prevalence of money politics, rising income inequality and worsening racial discrimination. The report, titled \"Human Rights Record of the United States in 2018\" was released by the Information Office of the State Council on Thursday. \n It said the US government, a self-styled \"human rights defender\", has a human rights record which is flawed and lackluster, and the double standards of human rights it pursues are obvious.According to the report, the US 2018 midterm elections cost a huge quantity of money as elections in the country became games of money, with much involvement of \"dark money\" and corruption."; 15 | 16 | @interface VVMock () 17 | @property (nonatomic, strong) NSArray *charArray; 18 | @property (nonatomic, strong) NSArray *firstNameArray; 19 | @property (nonatomic, strong) NSArray *lastNameArray; 20 | @end 21 | 22 | @implementation VVMock 23 | 24 | + (instancetype)shared{ 25 | static VVMock *_mock; 26 | static dispatch_once_t onceToken; 27 | dispatch_once(&onceToken, ^{ 28 | _mock = [[self alloc] init]; 29 | }); 30 | return _mock; 31 | } 32 | 33 | - (instancetype)init{ 34 | self = [super init]; 35 | if (self) { 36 | _charArray = [chars componentsSeparatedByString:@" "]; 37 | _firstNameArray = [firstNames componentsSeparatedByString:@" "]; 38 | _lastNameArray = [lastNames componentsSeparatedByString:@" "]; 39 | } 40 | return self; 41 | } 42 | 43 | - (NSString *)nick{ 44 | return [self charTextWithMinLen:4 maxLen:16]; 45 | } 46 | 47 | - (NSString *)name{ 48 | NSString *first = _firstNameArray[arc4random_uniform((uint32_t)_firstNameArray.count)]; 49 | NSString *last = _firstNameArray[arc4random_uniform((uint32_t)_lastNameArray.count)]; 50 | return [NSString stringWithFormat:@"%@%@",last,first]; 51 | } 52 | 53 | - (NSString *)shortText{ 54 | NSInteger loc = arc4random_uniform(3885 - 20); 55 | NSInteger len = 2 + arc4random_uniform(18); 56 | return [article substringWithRange:NSMakeRange(loc, len)]; 57 | } 58 | 59 | - (NSString *)longText{ 60 | NSInteger loc = arc4random_uniform(3885 - 100); 61 | NSInteger len = 20 + arc4random_uniform(80); 62 | return [article substringWithRange:NSMakeRange(loc, len)]; 63 | } 64 | 65 | - (NSString *)longLongText{ 66 | NSInteger loc = arc4random_uniform(3885 - 1000); 67 | NSInteger len = 100 + arc4random_uniform(900); 68 | return [article substringWithRange:NSMakeRange(loc, len)]; 69 | } 70 | 71 | - (NSString *)textEn{ 72 | NSInteger loc = arc4random_uniform(757 - 100); 73 | NSInteger len = 20 + arc4random_uniform(80); 74 | return [articleEn substringWithRange:NSMakeRange(loc, len)]; 75 | } 76 | 77 | - (NSString *)mobile{ 78 | return [NSString stringWithFormat:@"+86%@%@%@", 79 | @(130 + arc4random_uniform(60)), 80 | @(1000 +arc4random_uniform(8999)), 81 | @(1000 +arc4random_uniform(8999))]; 82 | } 83 | 84 | - (NSString *)email{ 85 | return [NSString stringWithFormat:@"%@@%@.com",[self charTextWithMinLen:4 maxLen:16],[self charTextWithMinLen:3 maxLen:8]]; 86 | } 87 | 88 | //MARK: - Private 89 | - (NSString *)charTextWithMinLen:(NSUInteger)minLen maxLen:(NSUInteger)maxLen{ 90 | NSInteger len = minLen + arc4random_uniform((uint32_t)(maxLen - minLen)); 91 | NSMutableString *text = [NSMutableString stringWithCapacity:0]; 92 | uint32_t count = (uint32_t)_charArray.count; 93 | for (NSInteger i = 0; i < len; i ++) { 94 | NSInteger idx = arc4random_uniform(count); 95 | [text appendString:_charArray[idx]]; 96 | } 97 | return text; 98 | } 99 | 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /Example/VVSequelize/VVAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVAppDelegate.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 03/13/2019. 6 | // Copyright (c) 2019 Valo. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | 11 | @interface VVAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/VVSequelize/VVAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVAppDelegate.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 03/13/2019. 6 | // Copyright (c) 2019 Valo. All rights reserved. 7 | // 8 | 9 | #import "VVAppDelegate.h" 10 | 11 | @implementation VVAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | // Override point for customization after application launch. 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // 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. 22 | // 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. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // 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. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // 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. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Example/VVSequelize/VVSequelize-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Example/VVSequelize/VVSequelize-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | @import UIKit; 15 | @import Foundation; 16 | #endif 17 | -------------------------------------------------------------------------------- /Example/VVSequelize/VVTableViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/3/1. 6 | // Copyright © 2019 valo. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VVTableViewController : UITableViewController 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Example/VVSequelize/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Example/VVSequelize/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 03/13/2019. 6 | // Copyright (c) 2019 Valo. All rights reserved. 7 | // 8 | 9 | @import UIKit; 10 | #import "VVAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) 13 | { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([VVAppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Valo Lee 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VVSequelize 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/VVSequelize.svg?style=flat)](https://cocoapods.org/pods/VVSequelize) 4 | [![License](https://img.shields.io/cocoapods/l/VVSequelize.svg?style=flat)](https://cocoapods.org/pods/VVSequelize) 5 | [![Platform](https://img.shields.io/cocoapods/p/VVSequelize.svg?style=flat)](https://cocoapods.org/pods/VVSequelize) 6 | 7 | ## 改动(0.4.7) 8 | 1. 移除accessible. 不建议访问appgroup中的数据库, 所以去掉此功能. 9 | 2. 移除cache. 因为`sqlite3_update_hook()`不能完全hook所有delete, drop操作, cache将不能被有效更新,会导致查询到错误数据, 所以去掉此功能, 由用户自行管理缓存. 10 | 11 | ## 功能 12 | * [x] 根据Class生成数据表 13 | * [x] 增删改查,insert,update,upsert,delele,drop... 14 | * [x] Where语句生成,可满足大部分常规场景 15 | * [x] 数据库加解密(SQLCipher) 16 | * [x] 原生SQL语句支持 17 | * [x] 常规查询函数支持,max(),min(),sum(),count()... 18 | * [x] 支持主键,可多主键,单主键可自增. 19 | * [x] 支持唯一性约束 20 | * [x] Transaction支持 21 | * [x] Object直接处理 22 | * [x] 数据存储,OC类型支持: NSData, NSURL, NSSelector, NSValue, NSDate, NSArray, NSDictionary, NSSet,... 23 | * [x] 数据存储,C类型支持: char *, struct, union 24 | * [x] 子对象存储为Json字符串 25 | * [x] OrmModel查询缓存 26 | * [x] FTS5全文搜索(不支持FTS3/4) 27 | * [x] 自定义FTS分词器 28 | * [x] 支持拼音分词,简繁互搜 29 | * [x] 支持外键,约束 30 | * [x] 支持View 31 | 32 | ## 结构 33 | ![](VVSequelize.png) 34 | 35 | ## 安装 36 | ```ruby 37 | pod 'VVSequelize', '~> 0.4.7' 38 | ``` 39 | 使用测试版本: 40 | ```ruby 41 | pod 'VVSequelize', :git => 'https://github.com/pozi119/VVSequelize.git' 42 | ``` 43 | ## 注意 44 | 1. 子对象会保存成为Json字符串,子对象内的NSData也会保存为16进制字符串. 45 | 2. 含有子对象时,请确保不会循环引用,否则`Dictionary/Object`互转会死循环,请将相应的循环引用加入互转黑名单. 46 | 3. VVKeyValue仅用于本工具,不适用常规的Json转对象. 47 | 48 | ## 用法 49 | 此处主要列出一些基本用法,详细用法请阅读代码注释. 50 | 51 | ### 打开/创建数据库文件 52 | ```objc 53 | self.vvdb = [[VVDatabase alloc] initWithPath:dbPath]; 54 | ``` 55 | 56 | ### 定义ORM配置 57 | 1. 手动创建`VVOrmConfig`. 58 | 2. 普通表适配协议`VVOrmable`, fts表适配协议`VVFtsable` 59 | 60 | **注意:** 已移除fts3/4的支持,仅使用fts5 61 | 62 | ### 定义ORM模型 63 | 可自定义表名和存放的数据库文件. 64 | 生成的模型将不在保存在ModelPool中,防止表过多导致内存占用大,需要请自行实现. 65 | 66 | 示例如下: 67 | 68 | ```objc 69 | item.orm = [VVOrm ormWithClass:VVMessage.class name:item.tableName database:item.db]; 70 | 71 | item.ftsOrm = [VVOrm ormWithFtsClass:VVMessage.class name:item.tableName database:item.ftsDb]; 72 | ``` 73 | 74 | ### 增删改查 75 | 使用ORM模型进行增删改查等操作. 76 | 77 | 示例如下: 78 | 79 | ```objc 80 | NSInteger count = [self.mobileModel count:nil]; 81 | 82 | BOOL ret = [self.mobileModel increase:nil field:@"times" value:-1]; 83 | 84 | NSArray *array = [self.mobileModel findAll:nil orderBy:nil limit:10 offset:0]; 85 | 86 | ... 87 | ``` 88 | 89 | ### 生成SQL子句 90 | 现在仅支持非套嵌的字典或字典数组,转换方式如下: 91 | ``` 92 | //where/having : 93 | {field1:val1,field2:val2} --> field1 = "val1" AND field2 = "val2" 94 | [{field1:val1,field2:val2},{field3:val3}] --> (field1 = "val1" AND field2 = "val2") OR (field3 = "val3") 95 | //group by: 96 | [filed1,field2] --> "field1","field2" 97 | //order by 98 | [filed1,field2] --> "field1","field2" ASC 99 | [filed1,field2].desc --> "field1","field2" DESC 100 | ``` 101 | 示例: 102 | ```objc 103 | - (void)testClause 104 | { 105 | VVSelect *select = [VVSelect new]; 106 | select.table(@"mobiles"); 107 | select.where(@"relative".lt(@(0.3)) 108 | .and(@"mobile".gte(@(1600000000))) 109 | .and(@"times".gte(@(0)))); 110 | NSLog(@"%@", select.sql); 111 | select.where(@{ @"city": @"西安", @"relative": @(0.3) }); 112 | NSLog(@"%@", select.sql); 113 | select.where(@[@{ @"city": @"西安", @"relative": @(0.3) }, @{ @"relative": @(0.7) }]); 114 | NSLog(@"%@", select.sql); 115 | select.where(@"relative".lt(@(0.3))); 116 | NSLog(@"%@", select.sql); 117 | select.where(@" where relative < 0.3"); 118 | NSLog(@"%@", select.sql); 119 | select.groupBy(@"city"); 120 | NSLog(@"%@", select.sql); 121 | select.groupBy(@[@"city", @"carrier"]); 122 | NSLog(@"%@", select.sql); 123 | select.groupBy(@" group by city carrier"); 124 | NSLog(@"%@", select.sql); 125 | select.having(@"relative".lt(@(0.2))); 126 | NSLog(@"%@", select.sql); 127 | select.groupBy(nil); 128 | NSLog(@"%@", select.sql); 129 | select.orderBy(@[@"city", @"carrier"]); 130 | NSLog(@"%@", select.sql); 131 | select.orderBy(@" order by relative"); 132 | NSLog(@"%@", select.sql); 133 | select.limit(10); 134 | NSLog(@"%@", select.sql); 135 | select.distinct(YES); 136 | NSLog(@"%@", select.sql); 137 | } 138 | ``` 139 | ### 原生语句查询 140 | ``` 141 | - (NSArray *)query:(NSString *)sql; 142 | 143 | - (NSArray *)query:(NSString *)sql clazz:(Class)clazz; 144 | ``` 145 | 146 | ## 加密数据数据转换(sqlcipher 3.x->4.x) 147 | ```objc 148 | VVDatabase *database = [VVDatabase databaseWithPath:path flags:0 encrypt:@"XXXXX"]; 149 | database.cipherOptions = @[ 150 | @"pragma cipher_page_size = 4096;", ///<3.x的cipher_page_size,默认为1024 151 | @"pragma kdf_iter = 64000;", 152 | @"pragma cipher_hmac_algorithm = HMAC_SHA1;", 153 | @"pragma cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;" 154 | ]; 155 | ``` 156 | 157 | ## Author 158 | 159 | Valo Lee, pozi119@163.com 160 | 161 | ## License 162 | 163 | VVSequelize is available under the MIT license. See the LICENSE file for more info. 164 | -------------------------------------------------------------------------------- /VVSequelize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pozi119/VVSequelize/a575648ec93ab8fc68e7f9b769ea9d64698b126a/VVSequelize.png -------------------------------------------------------------------------------- /VVSequelize.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = 'VVSequelize' 4 | s.version = '0.4.7' 5 | s.summary = 'ORM model based on SQLite3.' 6 | s.description = <<-DESC 7 | ORM model based on SQLite3. 8 | DESC 9 | 10 | s.homepage = 'https://github.com/pozi119/VVSequelize' 11 | s.license = { :type => 'MIT', :file => 'LICENSE' } 12 | s.author = { 'Valo Lee' => 'pozi119@163.com' } 13 | s.source = { :git => 'https://github.com/pozi119/VVSequelize.git', :tag => s.version.to_s } 14 | 15 | s.ios.deployment_target = '10.0' 16 | s.default_subspec = 'cipher' 17 | 18 | s.subspec 'system' do |ss| 19 | ss.dependency 'VVSequelize/core' 20 | ss.dependency 'VVSequelize/fts' 21 | ss.dependency 'VVSequelize/util' 22 | ss.libraries = 'sqlite3' 23 | end 24 | 25 | s.subspec 'cipher' do |ss| 26 | ss.dependency 'VVSequelize/core' 27 | ss.dependency 'VVSequelize/fts' 28 | ss.dependency 'VVSequelize/util' 29 | ss.dependency 'SQLCipher' 30 | ss.xcconfig = { 31 | 'OTHER_CFLAGS' => '-DSQLITE_HAS_CODEC', 32 | 'HEADER_SEARCH_PATHS' => "{PODS_ROOT}/SQLCipher" 33 | } 34 | end 35 | 36 | # child specs 37 | s.subspec 'core' do |ss| 38 | ss.source_files = 'VVSequelize/Core/**/*' 39 | ss.public_header_files = 'VVSequelize/Core/**/*.h' 40 | ss.dependency 'VVSequelize/header' 41 | ss.xcconfig = { 'OTHER_CFLAGS' => '-DVVSEQUELIZE_CORE' } 42 | end 43 | 44 | s.subspec 'fts' do |ss| 45 | ss.source_files = 'VVSequelize/FTS/**/*' 46 | ss.public_header_files = 'VVSequelize/FTS/**/*.h' 47 | ss.resource = ['VVSequelize/Assets/VVPinYin.bundle'] 48 | ss.dependency 'VVSequelize/core' 49 | ss.dependency 'VVSequelize/header' 50 | ss.xcconfig = { 'OTHER_CFLAGS' => '-DVVSEQUELIZE_FTS' } 51 | end 52 | 53 | s.subspec 'util' do |ss| 54 | ss.source_files = 'VVSequelize/Util/**/*' 55 | ss.public_header_files = 'VVSequelize/Util/**/*.h' 56 | ss.dependency 'VVSequelize/header' 57 | ss.xcconfig = { 'OTHER_CFLAGS' => '-DVVSEQUELIZE_UTIL' } 58 | end 59 | 60 | s.subspec 'header' do |ss| 61 | ss.source_files = 'VVSequelize/VVSequelize.h' 62 | end 63 | 64 | end 65 | -------------------------------------------------------------------------------- /VVSequelize.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pozi119/VVSequelize/a575648ec93ab8fc68e7f9b769ea9d64698b126a/VVSequelize.xmind -------------------------------------------------------------------------------- /VVSequelize/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pozi119/VVSequelize/a575648ec93ab8fc68e7f9b769ea9d64698b126a/VVSequelize/Assets/.gitkeep -------------------------------------------------------------------------------- /VVSequelize/Assets/VVPinYin.bundle/syllables.txt: -------------------------------------------------------------------------------- 1 | shi,35225 2 | de,22242 3 | yi,19404 4 | wo,17532 5 | you,15403 6 | jiu,14424 7 | dui,13558 8 | bu,12287 9 | ta,11129 10 | ni,9883 11 | hou,8643 12 | zai,8507 13 | na,8409 14 | zhe,7635 15 | ge,7303 16 | men,6890 17 | dao,6772 18 | mei,6324 19 | xiang,6319 20 | hui,6163 21 | zhi,5952 22 | ke,5870 23 | shuo,5820 24 | qu,5751 25 | ran,5436 26 | wei,5283 27 | hen,5252 28 | hao,5241 29 | guo,5186 30 | yao,5182 31 | ji,4991 32 | ye,4930 33 | zhong,4847 34 | yang,4644 35 | zi,4517 36 | hai,4502 37 | le,4456 38 | dou,4451 39 | da,4398 40 | yin,4360 41 | lai,4151 42 | ren,4120 43 | qi,4115 44 | me,3897 45 | jiao,3874 46 | jue,3606 47 | li,3564 48 | jia,3537 49 | bian,3527 50 | xian,3456 51 | kan,3450 52 | zuo,3212 53 | lu,3156 54 | jie,3142 55 | tai,3119 56 | xiao,3092 57 | bi,3066 58 | xi,3053 59 | hua,2912 60 | shang,2866 61 | suo,2863 62 | she,2792 63 | gong,2568 64 | duo,2559 65 | dian,2485 66 | qian,2462 67 | ma,2389 68 | jing,2298 69 | jiang,2292 70 | xue,2254 71 | gen,2242 72 | chu,2227 73 | wan,2193 74 | xie,2193 75 | fang,2171 76 | nian,2163 77 | di,2124 78 | dong,2107 79 | er,2096 80 | xin,2067 81 | neng,2041 82 | chang,2037 83 | mian,2012 84 | ying,1996 85 | zou,1960 86 | sheng,1937 87 | jin,1913 88 | jian,1883 89 | gai,1863 90 | qing,1832 91 | ban,1804 92 | yuan,1775 93 | bei,1767 94 | wu,1740 95 | wen,1707 96 | zheng,1686 97 | man,1666 98 | zhen,1659 99 | xia,1632 100 | si,1615 101 | nan,1563 102 | fu,1555 103 | yu,1546 104 | che,1531 105 | tong,1412 106 | tian,1402 107 | guan,1387 108 | fa,1378 109 | cheng,1363 110 | huo,1351 111 | zhu,1350 112 | ru,1349 113 | xing,1343 114 | ba,1337 115 | wang,1238 116 | dang,1210 117 | dan,1201 118 | cai,1200 119 | zen,1196 120 | shu,1190 121 | yong,1156 122 | yan,1134 123 | liang,1123 124 | kai,1120 125 | ben,1119 126 | bao,1096 127 | ting,1077 128 | gan,1024 129 | ti,1017 130 | san,1006 131 | gao,1005 132 | fan,1004 133 | shou,973 134 | fen,962 135 | huan,954 136 | zhuan,954 137 | ci,939 138 | lao,931 139 | cong,930 140 | zui,930 141 | ding,928 142 | kao,928 143 | zhan,912 144 | zhao,911 145 | gang,884 146 | cha,876 147 | fei,832 148 | wai,817 149 | du,800 150 | mai,794 151 | dai,781 152 | shao,774 153 | chi,764 154 | bai,748 155 | yue,744 156 | kuai,718 157 | ju,708 158 | suan,707 159 | liu,698 160 | xu,688 161 | shen,685 162 | zhang,669 163 | deng,664 164 | quan,657 165 | qie,653 166 | lian,651 167 | shui,639 168 | shan,629 169 | tiao,629 170 | cuo,625 171 | duan,618 172 | mu,618 173 | ming,608 174 | hu,606 175 | ping,606 176 | gei,583 177 | peng,551 178 | nv,542 179 | tou,533 180 | he,530 181 | liao,518 182 | yun,516 183 | ri,507 184 | ling,505 185 | guang,495 186 | chong,493 187 | gui,488 188 | lv,487 189 | bing,483 190 | kou,479 191 | zao,463 192 | sui,460 193 | gou,456 194 | lei,451 195 | min,447 196 | xiu,445 197 | gu,433 198 | tu,428 199 | pao,414 200 | zong,412 201 | pai,411 202 | rang,400 203 | kong,398 204 | pian,396 205 | ai,388 206 | han,384 207 | diao,375 208 | feng,370 209 | an,364 210 | bang,361 211 | zhuang,361 212 | ya,352 213 | bo,344 214 | bie,340 215 | tao,338 216 | su,318 217 | hang,315 218 | mo,315 219 | biao,313 220 | xuan,309 221 | chuan,303 222 | can,300 223 | guai,289 224 | pang,286 225 | qiu,281 226 | hun,279 227 | chao,269 228 | zu,266 229 | nei,265 230 | pi,265 231 | tan,264 232 | nao,262 233 | tuo,260 234 | zhou,260 235 | geng,259 236 | hong,257 237 | tui,243 238 | pa,241 239 | rong,241 240 | ku,240 241 | lan,235 242 | heng,232 243 | qiao,230 244 | lin,228 245 | te,223 246 | qiang,222 247 | pin,221 248 | se,218 249 | qin,212 250 | piao,209 251 | jun,202 252 | a,198 253 | ne,198 254 | tang,190 255 | la,189 256 | song,183 257 | kuang,176 258 | que,165 259 | nong,161 260 | shei,161 261 | ze,159 262 | ceng,154 263 | lun,153 264 | mao,150 265 | xun,149 266 | lou,148 267 | chan,147 268 | sai,147 269 | cun,143 270 | pu,143 271 | huang,142 272 | tuan,141 273 | zhun,141 274 | ce,139 275 | sha,138 276 | zha,137 277 | huai,135 278 | luo,130 279 | ou,130 280 | chen,129 281 | pei,129 282 | po,127 283 | gua,126 284 | kang,124 285 | chun,122 286 | re,119 287 | mi,117 288 | ka,115 289 | chuang,109 290 | long,108 291 | lie,106 292 | mou,105 293 | ken,104 294 | miao,104 295 | bin,103 296 | mang,102 297 | xiong,101 298 | shun,98 299 | kun,96 300 | zhua,96 301 | luan,95 302 | chou,93 303 | ao,92 304 | shuang,84 305 | nin,83 306 | niu,83 307 | rao,82 308 | tie,80 309 | lang,79 310 | e,75 311 | kua,74 312 | za,70 313 | cui,68 314 | hei,68 315 | wa,68 316 | juan,67 317 | dun,66 318 | sen,66 319 | cao,65 320 | kuan,60 321 | die,59 322 | meng,57 323 | kui,56 324 | leng,55 325 | diu,53 326 | ning,48 327 | pan,48 328 | zhuo,47 329 | ruo,46 330 | zang,44 331 | sun,42 332 | zeng,40 333 | nai,39 334 | zan,37 335 | qun,36 336 | sao,35 337 | cu,33 338 | zhui,33 339 | cang,32 340 | shuai,32 341 | nu,30 342 | chui,29 343 | rou,29 344 | ruan,29 345 | shua,28 346 | chai,27 347 | ha,27 348 | zun,27 349 | sou,25 350 | en,24 351 | niao,20 352 | zhai,20 353 | fou,19 354 | niang,19 355 | ga,18 356 | shai,18 357 | kuo,17 358 | qia,17 359 | qiong,16 360 | sa,16 361 | rui,15 362 | teng,15 363 | fo,13 364 | keng,13 365 | sang,13 366 | lue,12 367 | cou,10 368 | mie,10 369 | zuan,10 370 | lia,9 371 | nuan,9 372 | beng,8 373 | nie,8 374 | weng,8 375 | ca,7 376 | chuo,7 377 | gun,7 378 | nuo,7 379 | pen,7 380 | run,6 381 | k,4 382 | o,4 383 | ang,3 384 | pou,3 385 | tun,3 386 | cuan,2 387 | miu,2 388 | cen,1 389 | nang,1 390 | nue,1 391 | pie,1 392 | zei,1 393 | -------------------------------------------------------------------------------- /VVSequelize/Assets/VVPinYin.bundle/transform.txt: -------------------------------------------------------------------------------- 1 | 锕皑蔼碍爱嗳嫒瑷暧霭谙铵鹌肮袄奥媪骜鳌坝罢钯摆败呗颁办绊钣帮绑镑谤剥饱宝报鲍鸨龅辈贝钡狈备惫鹎贲锛绷笔毕毙币闭荜哔滗铋筚跸边编贬变辩辫苄缏笾标骠飑飙镖镳鳔鳖别瘪濒滨宾摈傧缤槟殡膑镔髌鬓饼禀拨钵铂驳饽钹鹁补钸财参蚕残惭惨灿骖黪苍舱仓沧厕侧册测恻层诧锸侪钗搀掺蝉馋谗缠铲产阐颤冁谄谶蒇忏婵骣觇禅镡场尝长偿肠厂畅伥苌怅阊鲳钞车彻砗尘陈衬伧谌榇碜龀撑称惩诚骋枨柽铖铛痴迟驰耻齿炽饬鸱冲冲虫宠铳畴踌筹绸俦帱雠橱厨锄雏础储触处刍绌蹰传钏疮闯创怆锤缍纯鹑绰辍龊辞词赐鹚聪葱囱从丛苁骢枞凑辏蹿窜撺错锉鹾达哒鞑带贷骀绐担单郸掸胆惮诞弹殚赕瘅箪当挡党荡档谠砀裆捣岛祷导盗焘灯邓镫敌涤递缔籴诋谛绨觌镝颠点垫电巅钿癫钓调铫鲷谍叠鲽钉顶锭订铤丢铥东动栋冻岽鸫窦犊独读赌镀渎椟牍笃黩锻断缎簖兑队对怼镦吨顿钝炖趸夺堕铎鹅额讹恶饿谔垩阏轭锇锷鹗颚颛鳄诶儿尔饵贰迩铒鸸鲕发罚阀珐矾钒烦贩饭访纺钫鲂飞诽废费绯镄鲱纷坟奋愤粪偾丰枫锋风疯冯缝讽凤沣肤辐抚辅赋复负讣妇缚凫驸绂绋赙麸鲋鳆钆该钙盖赅杆赶秆赣尴擀绀冈刚钢纲岗戆镐睾诰缟锆搁鸽阁铬个纥镉颍给亘赓绠鲠龚宫巩贡钩沟苟构购够诟缑觏蛊顾诂毂钴锢鸪鹄鹘剐挂鸹掴关观馆惯贯诖掼鹳鳏广犷规归龟闺轨诡贵刽匦刿妫桧鲑鳜辊滚衮绲鲧锅国过埚呙帼椁蝈铪骇韩汉阚绗颉号灏颢阂鹤贺诃阖蛎横轰鸿红黉讧荭闳鲎壶护沪户浒鹕哗华画划话骅桦铧怀坏欢环还缓换唤痪焕涣奂缳锾鲩黄谎鳇挥辉毁贿秽会烩汇讳诲绘诙荟哕浍缋珲晖荤浑诨馄阍获货祸钬镬击机积饥迹讥鸡绩缉极辑级挤几蓟剂济计记际继纪讦诘荠叽哜骥玑觊齑矶羁虿跻霁鲚鲫夹荚颊贾钾价驾郏浃铗镓蛲歼监坚笺间艰缄茧检碱硷拣捡简俭减荐槛鉴践贱见键舰剑饯渐溅涧谏缣戋戬睑鹣笕鲣鞯将浆蒋桨奖讲酱绛缰胶浇骄娇搅铰矫侥脚饺缴绞轿较挢峤鹪鲛阶节洁结诫届疖颌鲒紧锦仅谨进晋烬尽劲荆茎卺荩馑缙赆觐鲸惊经颈静镜径痉竞净刭泾迳弪胫靓纠厩旧阄鸠鹫驹举据锯惧剧讵屦榉飓钜锔窭龃鹃绢锩镌隽觉决绝谲珏钧军骏皲开凯剀垲忾恺铠锴龛闶钪铐颗壳课骒缂轲钶锞颔垦恳龈铿抠库裤喾块侩郐哙脍宽狯髋矿旷况诓诳邝圹纩贶亏岿窥馈溃匮蒉愦聩篑阃锟鲲扩阔蛴蜡腊莱来赖崃徕涞濑赉睐铼癞籁蓝栏拦篮阑兰澜谰揽览懒缆烂滥岚榄斓镧褴琅阆锒捞劳涝唠崂铑铹痨乐鳓镭垒类泪诔缧篱狸离鲤礼丽厉励砾历沥隶俪郦坜苈位蓠呖逦骊缡枥栎轹砺锂鹂疠粝跞雳鲡鳢俩联莲连镰怜涟帘敛脸链恋炼练蔹奁潋琏殓裢裣鲢粮凉两辆谅魉疗辽镣缭钌鹩猎临邻鳞凛赁蔺廪檩辚躏龄铃灵岭领绫棂蛏鲮馏刘浏骝绺镏鹨龙聋咙笼垄拢陇茏泷珑栊胧砻楼娄搂篓偻蒌喽嵝镂瘘耧蝼髅芦卢颅庐炉掳卤虏鲁赂禄录陆垆撸噜闾泸渌栌橹轳辂辘氇胪鸬鹭舻鲈峦挛孪滦乱脔娈栾鸾銮抡轮伦仑沦纶论囵萝罗逻锣箩骡骆络荦猡泺椤脶镙驴吕铝侣屡缕虑滤绿榈褛锊呒妈玛码蚂马骂吗唛嬷杩买麦卖迈脉劢瞒馒蛮满谩缦镘颡鳗猫锚铆贸麽没镁门闷们扪焖懑钔锰梦眯谜弥觅幂芈谧猕祢绵缅渑腼黾庙缈缪灭悯闽闵缗鸣铭谬谟蓦馍殁镆谋亩钼呐钠纳难挠脑恼闹铙讷馁内拟腻铌鲵撵辇鲶酿鸟茑袅聂啮镊镍陧蘖嗫颟蹑柠狞宁拧泞苎咛聍钮纽脓浓农侬哝驽钕诺傩疟欧鸥殴呕沤讴怄瓯盘蹒庞抛疱赔辔喷鹏纰罴铍骗谝骈飘缥频贫嫔苹凭评泼颇钋扑铺朴谱镤镨栖脐齐骑岂启气弃讫蕲骐绮桤碛颀颃鳍牵钎铅迁签谦钱钳潜浅谴堑佥荨悭骞缱椠钤枪呛墙蔷强抢嫱樯戗炝锖锵镪羟跄锹桥乔侨翘窍诮谯荞缲硗跷窃惬锲箧钦亲寝锓轻氢倾顷请庆揿鲭琼穷茕蛱巯赇虮鳅趋区躯驱龋诎岖阒觑鸲颧权劝诠绻辁铨却鹊确阕阙悫让饶扰绕荛娆桡热韧认纫饪轫荣绒嵘蝾缛铷颦软锐蚬闰润洒萨飒鳃赛伞毵糁丧骚扫缫涩啬铯穑杀刹纱铩鲨筛晒酾删闪陕赡缮讪姗骟钐鳝墒伤赏垧殇觞烧绍赊摄慑设厍滠畲绅审婶肾渗诜谂渖声绳胜师狮湿诗时蚀实识驶势适释饰视试谥埘莳弑轼贳铈鲥寿兽绶枢输书赎属术树竖数摅纾帅闩双谁税顺说硕烁铄丝饲厮驷缌锶鸶耸怂颂讼诵擞薮馊飕锼苏诉肃谡稣虽随绥岁谇孙损笋荪狲缩琐锁唢睃獭挞闼铊鳎台态钛鲐摊贪瘫滩坛谭谈叹昙钽锬顸汤烫傥饧铴镗涛绦讨韬铽腾誊锑题体屉缇鹈阗条粜龆鲦贴铁厅听烃铜统恸头钭秃图钍团抟颓蜕饨脱鸵驮驼椭箨鼍袜娲腽弯湾顽万纨绾网辋韦违围为潍维苇伟伪纬谓卫诿帏闱沩涠玮韪炜鲔温闻纹稳问阌瓮挝蜗涡窝卧莴龌呜钨乌诬无芜吴坞雾务误邬庑怃妩骛鹉鹜锡牺袭习铣戏细饩阋玺觋虾辖峡侠狭厦吓硖鲜纤贤衔闲显险现献县馅羡宪线苋莶藓岘猃娴鹇痫蚝籼跹厢镶乡详响项芗饷骧缃飨萧嚣销晓啸哓潇骁绡枭箫协挟携胁谐写泻谢亵撷绁缬锌衅兴陉荥凶汹锈绣馐鸺虚嘘须许叙绪续诩顼轩悬选癣绚谖铉镟学谑泶鳕勋询寻驯训讯逊埙浔鲟压鸦鸭哑亚讶垭娅桠氩阉烟盐严岩颜阎艳厌砚彦谚验厣赝俨兖谳恹闫酽魇餍鼹鸯杨扬疡阳痒养样炀瑶摇尧遥窑谣药轺鹞鳐爷页业叶靥谒邺晔烨医铱颐遗仪蚁艺亿忆义诣议谊译异绎诒呓峄饴怿驿缢轶贻钇镒镱瘗舣荫阴银饮隐铟瘾樱婴鹰应缨莹萤营荧蝇赢颖茔莺萦蓥撄嘤滢潆璎鹦瘿颏罂哟拥佣痈踊咏镛优忧邮铀犹诱莸铕鱿舆鱼渔娱与屿语狱誉预驭伛俣谀谕蓣嵛饫阈妪纡觎欤钰鹆鹬龉鸳渊辕园员圆缘远橼鸢鼋约跃钥粤悦阅钺郧匀陨运蕴酝晕韵郓芸恽愠纭韫殒氲杂灾载攒暂赞瓒趱錾赃脏驵凿枣责择则泽赜啧帻箦贼谮赠综缯轧铡闸栅诈斋债毡盏斩辗崭栈战绽谵张涨帐账胀赵诏钊蛰辙锗这谪辄鹧贞针侦诊镇阵浈缜桢轸赈祯鸩挣睁狰争帧症郑证诤峥钲铮筝织职执纸挚掷帜质滞骘栉栀轵轾贽鸷蛳絷踬踯觯钟终种肿众锺诌轴皱昼骤纣绉猪诸诛烛瞩嘱贮铸驻伫槠铢专砖转赚啭馔颞桩庄装妆壮状锥赘坠缀骓缒谆准着浊诼镯兹资渍谘缁辎赀眦锱龇鲻踪总纵偬邹诹驺鲰诅组镞钻缵躜鳟翱并卜沉丑淀迭斗范干皋硅柜后伙秸杰诀夸里凌么霉捻凄扦圣尸抬涂洼喂污锨咸蝎彝涌游吁御愿岳云灶扎札筑于志注凋讠谫郄勐凼坂垅垴埯埝苘荬荮莜莼菰藁揸吒吣咔咝咴噘噼嚯幞岙嵴彷徼犸狍馀馇馓馕愣憷懔丬溆滟溷漤潴澹甯纟绔绱珉枧桊桉槔橥轱轷赍肷胨飚煳煅熘愍淼砜磙眍钚钷铘铞锃锍锎锏锘锝锪锫锿镅镎镢镥镩镲稆鹋鹛鹱疬疴痖癯裥襁耢颥螨麴鲅鲆鲇鲞鲴鲺鲼鳊鳋鳘鳙鞒鞴齄丢并乱亘亚汲夫伫布占徊并来仑侣局俣系侠伥俩仓个们幸仿伦伟侧侦咱伪杰伧伞备家佣偬传伛债伤倾偻仅戮佥侨仆侥偾雇价仪侬亿当侩俭傧俦侪尽偿优储俪罗傩傥俨凶兑儿兖内两册胄幂净冻凛凯别删刭则克刹刚剥剐剀创铲划剧刘刽刿剑剂匡劲动勖务勋胜劳势积剿劢励劝匀匦汇匮区协恤却厍厌厉厣参丛寸吴呐吕尺呙员呗念问哑启衔唤丧吃乔单哟呛啬吗呜唢哔叹喽呕啧尝唛哗唠啸叽哓呒恶嘘哒哝哕嗳哙喷吨当咛吓哜噜啮咽呖咙向喾严嘤啭嗫嚣冁呓罗苏嘱囱囵国围园圆图团垧垭执坚垩埚尧报场碱块茔垲埘涂坞埙尘堑垫墒坠堕坟墙垦坛埙压垒圹垆坏垄坜坝壮壶寿够梦夹奂奥奁夺奋妆你姗奸侄娱娄妇娅娲妫媪妈妪妩娴娆婵娇嫱袅嫒嬷嫔婴婶娘娈孙学孪宫寝实宁审写宽宠宝将专寻对导尴届屉扉屡层屦属冈迢岘岛峡崃岗仑岽峥嵛岚岩嵝崭岖崂峤峄嵘岭屿岳岿漓峦巅岩巯卺帅师帐带帧帏帼帻帜币帮帱干几仄库厕厢厩厦厨厮庙厂庑废广廪庐痈厅弑吊弪张强别弹强弥弯汇彦雕佛径从徕复旁彻恒耻悦怅闷凄恶恼恽恻爱惬悫怆恺忾栗殷态愠惨惭恸惯怄怂虑悭庆戚忧惫怜凭愦惮愤悯怃宪忆恳应怿檩蒙怼懑恹惩懒怀悬忏惧慑恋戆钺戋戗戬战戏户仂叉扦抵拚擦殒曳抛局挟扞舍扪卷扫抡挣挂采拣扬换挥背构损摇捣擀抢掴掼搂挚抠抟掺捞撑挠捻挢掸掸拨抚扑揿挞挝捡拥掳择击挡担据挤捣拟摈拧搁掷扩撷摆擞撸扰摅撵拢拦撄搀撺携摄攒挛摊搅揽考败叙敌数敛毙斓斩断旗时晋昼曦晕晖阳畅暂昵了晔历昙晓暧旷晒书会胧术圬东栀拐栅杆栀条枭弃枨枣栋栈栖桠杨枫桢业极干杩荣桤盘构枪杠椠椁桨桩乐枞梁楼标枢样朴树桦桡桥机椭横檩柽档桧检樯台槟柠槛柜橹榈栉椟橼栎橱槠栌枥榇栊榉棂樱栏权椤栾榄棂钦叹欧欤欢岁历归殁残殒殇殚殓殡歼杀壳毁殴毋球毵毡氇气氢氩氲泛丸泛污决没冲况泄汹里浃泾凉凄泪渌净沦渊涞浅涣减涡测浑凑浈涌汤沩准沟温湿沧灭涤荥沪滞渗卤浒滚满渔沤汉涟渍涨渐浆颍泼洁潜泻润浔溃滗涠涩浇涝涧渑泽泶浍淀浊浓湿泞蒙济涛滥潍滨溅泺滤滢渎泻渖浏濒泸沥潇潆泷濑弥潋澜沣滠洒漓滩灏湾滦灾为乌烃无炼炜烟茕焕烦炀荧炝热炽烨焰灯炖磷烧烫焖营灿毁烛烩烬焘烁炉烂争爷尔墙牍瘪抵牵荦犁犊牺状狭狈狰犹狲狱狮奖独狯猃猕狞获猎犷兽獭献猕猡兹珏佩现珐珲玮琐瑶莹玛琅琏玑瑷环玺琼珑璎镶瓒瓯瓮产苏亩毕画畲异当畴叠痱痉酸麻麻痹疯疡痪瘗疮疟瘘疗痨痫瘅疠瘪痴痒疖症癞癣瘿瘾痈瘫癫发皑疱皲皱隳盗盏尽监盘卢荡眦众困睁睐睾眯瞒了睑蒙胧瞩矫炮朱硖砗砚硕砀确码砖碜碛矶硗础碍矿砺砾矾砻祆只佑秘禄祸祯御禅礼祢祷秃籼税秆棱禀种称谷稣积颖穑秽稳获窝洼穷窑窭窥窜窍窦窃竞筇笔笋笕箅个笺筝节范筑箧筱笃筛筚箦篓蓑箪简篑箫檐签帘篮筹藤箨籁笼签龠笾簖篱箩吁粤糁粪馍粮团粝籴粜纠纪纣约红纡纥纨纫纹纳纽纾纯纰纱纸级纷纭纺细绂绁绅绍绀绋绐绌终弦组绊绗结绝绦绞络绚给绒统丝绦绢绑绡绠绨绥经综缍绿绸绻绶维绾纲网缀彩纶绺绮绽绰绫绵绲缁紧绯绪缃缄缂线缉缎缔缗缘缌编缓缅纬缑缈练缏缇致萦缙缢缒绉缣绦缚缜缟缛县缝缡缩演纵缧缚纤缦絷缕缥总绩绷缫缪襁缯织缮缭绕绣缋绳绘系茧缳缲缴绎继缤缱缬纩续累缠缨才纤缵缆钵坛罂罚骂罢罗罴羁芈羟羡义习翘端耧圣闻联聪声耸聩聂职聍听聋肃胁脉胫唇睃修脱胀肾脶脑肿脚肠腽肤胶腻胆脍脓脸脐膑腊胪脏脔卧临台与兴举旧舱橹舣舰舻艰艳艹苄刍苎苟兹荆庄茎荚苋华庵苌莱万莴叶荭着苇药荤莳莅苍荪盖莲苁荜菱卜蒌蒋葱茑荫荨蒇荞芸莸荛蒉荡芜萧蓣荟蓟芗姜蔷莶荐槁萨荠蓝荩艺药薮苈薯蔼蔺蕲芦苏蕴苹蘖藓蔹茏兰蓠萝处虚虏号亏蛱蜕蚬蚀虾蜗蛳蚂萤蝼蛰蝈虮蝉蛲虫蛏蚁蝇虿蛴蝾蚝蜡蛎蛊蚕蛮蔑术卫冲只衮袅里补装里制复裤裢褛亵袄裣裆褴袜摆衬袭核见规觅视觇觋觎亲觊觏觐觑觉览觌观筋觞觯触订讣计讯讧讨讦训讪讫托记讹讶讼欣诀讷访设许诉诃诊注证诂诋讵诈诒诏评诎诅词咏诩询诣试诗诧诟诡诠诘话该详诜诙诖诔诛诓夸志认诳诶诞诱诮语诚诫诬误诰诵诲说谁课谇诽谊调谄谆谈诿请诤诹诼谅论谂谀谍谝诨谔谛谐谏谕谘讳谙谌讽诸谚谖诺谋谒谓誊诌谎谜谧谑谡谤谦谥讲谢谣谟谪谬讴谨谩证谲讥谮识谯谭谱噪谵译议谴护誉读变雠谗让谰谶赞谠谳岂竖丰艳猪狸猫贝贞负财贡贫货贩贪贯责贮贳赀贰贵贬买贷贶费贴贻贸贺贲赂赁贿赅资贾贼赈赊宾赇赉赐赏赔赓贤卖贱赋赕质账赌赖赚赙购赛赜贽赘赠赞赡赢赆赃赎赝赣赶赵趋趱迹局践蜷碰踊跄跸跖蹒踪跷趸踌跻跃踯跞踬蹰跹蹑蹿躜躏躯车轧轨军轩轫轭软轸轴轵轺轲轶轼较辂辁载轾辄挽辅轻辆辎辉辋辍辊辇辈轮辑辏输辐辗舆毂辖辕辘转辙轿辚轰辔轹轳办辞辫辩农迤回乃迳这连周进游运过达违遥逊递远适迟迁选遗辽迈还迩边逻逦合郏邮郓乡邹邬郧邓郑邻郸邺郐邝郦腌丑酝医酱酿衅酾酽释厘钆钇钌钊钉钋针钓钐扣钏钒钎钗钍钕钯钫钭钠钝钤钣钞钮钧钙钬钛钪铌铈钶铃钴钹铍钰钸铀钿钾钜钻铊铉刨铋铂钳铆铅钺钩钲钼钽铰铒铬铪银铳铜铣铨铢铭铫衔铑铷铱铟铵铥铕铯铐焊锐销锈锑锉铝锒锌钡铤铗锋锊锓锄锔锇铺铖锆锂铽锯钢锞录锖锩锥锕锟锤锱铮锛锬锭钱锦锚锡锢错锰表铼钔锴链锅镀锷铡锻锸锲锹锾键锶锗锺镁镑熔锁枪镉钨蓥镏铠铩锼镐镇镒镍镓镞镟链镆镙镝铿锵镗镘镛铲镜镖镂錾铧镤镪锈铙铣铴镣铹镦镡钟镫镨镄镌镰镯镭铁铎铛钽镱铸镬镔监鉴铄镳刨镧钥镶镊锣钻銮凿长门闩闪闫闭开闶闳闰闲闲间闵闸阂阁合阀闺闽阃阆闾阅阊阉阎阏阍阈阌阒板闱阔阕阑阗阖阙闯关阚阐辟闼厄址陉陕陕阵阴陈陆阳堤陧队阶陨际随险隐陇隶只隽虽双雏杂鸡离难云电沾溜雾霁雳霭灵靓静腼靥巩秋缰鞑千鞯韦韧韩韪韬韫韵响页顶顷项顺顸须顼颂颀颃预顽颁顿颇领颌颉颐颏头颊颔颈颓频颗题额颚颜颛愿颡颠类颟颢顾颤显颦颅颞颧风飑飒台刮飓扬飕飘飙飞饥饨饪饫饬饭饮饴饲饱饰饺饼饷养饵饽馁饿余肴馄饯馅馆饧喂饩馈馏馊馍馒馐馑馈馔饥饶飨餍馋马驭冯驮驰驯驳驻驽驹驵驾骀驸驶驼驷骈骇骆骏骋骓骒骑骐骛骗骞骘骝腾驺骚骟骡蓦骜骖骠骢驱骅骁骣骄验惊驿骤驴骧骥骊肮髅脏体髌髋发松胡须鬓斗闹哄阋阄郁魉魇鱼鲁鲂鱿鲐鲍鲋鲒鲕鲔鲛鲑鲜鲧鲠鲩鲤鲨鲻鲭鲷鲱鲵鲲鲳鲸鲮鲰鲶鲫鲽鳇鳅鳆鳃鲥鳏鳎鳐鳍鲢鳌鳓鲦鲣鳗鳔鳕鳖鳟鳝鳜鳞鲟鲎鳢鲚鳄鲈鲡鸟凫鸠凤鸣鸢鸩鸨鸦鸵鸳鸲鸱鸪鸯鸭鸸鸹鸿鸽鸺鹃鹆鹁鹈鹅鹄鹉鹌鹏鹎鹊鸫鹑鹕鹗鹜莺骞鹤鹘鹣鹚鹞鹧鸥鸷鹨鸶鹪鹩鹫鹇鹇鹬鹰鹭鸬鹦鹳鹂鸾卤咸鹾硷盐丽麦麸面麽黄黉点党黪霉黩黾鼋鳌鼍冬鼹齐斋齑齿龀龅龇龃龆龄出龈啮龊龉龋龌龙庞龚龛龟 2 | 锕皚藹礙愛嗳嫒瑷暧霭谙铵鹌肮襖奧媪骜鳌壩罷钯擺敗呗頒辦絆钣幫綁鎊謗剝飽寶報鮑鸨龅輩貝鋇狽備憊鹎贲锛繃筆畢斃幣閉荜哔滗铋筚跸邊編貶變辯辮苄缏邊標骠飑飙镖镳鳔鼈別癟瀕濱賓擯傧缤槟殡膑镔髌鬓餅禀撥缽鉑駁饽钹鹁補钸財參蠶殘慚慘燦骖黪蒼艙倉滄廁側冊測測層詫锸侪钗攙摻蟬饞讒纏鏟産闡顫冁谄谶蒇忏婵骣觇禅镡場嘗長償腸廠暢伥苌怅阊鲳鈔車徹砗塵陳襯伧谌榇碜龀撐稱懲誠騁枨柽铖铛癡遲馳恥齒熾饬鸱沖沖蟲寵铳疇躊籌綢俦疇雠櫥廚鋤雛礎儲觸處刍礎蹰傳钏瘡闖創怆錘缍純鹑綽辍龊辭詞賜鹚聰蔥囪從叢從骢縱湊辏躥竄撺錯锉鹾達哒鞑帶貸骀绐擔單鄲撣膽憚誕彈殚赕瘅箪當擋黨蕩檔谠砀裆搗島禱導盜焘燈鄧镫敵滌遞締籴诋谛绨觌镝顛點墊電巅钿癫釣調铫鲷諜疊鲽釘頂錠訂铤丟铥東動棟凍凍鸫窦犢獨讀賭鍍犢椟牍笃黩鍛斷緞簖兌隊對對镦噸頓鈍炖趸奪墮铎鵝額訛惡餓谔垩阏轭锇锷鹗颚颛鳄诶兒爾餌貳迩铒鸸鲕發罰閥琺礬釩煩販飯訪紡钫鲂飛誹廢費绯镄鲱紛墳奮憤糞偾豐楓鋒風瘋馮縫諷鳳沣膚輻撫輔賦複負訃婦縛凫驸绂绋赙麸鲋鳆钆該鈣蓋赅杆趕稈贛尴擀绀岡剛鋼綱崗戆鎬睾诰缟锆擱鴿閣鉻個纥镉颍給亘赓绠鲠龔宮鞏貢鈎溝苟構購夠诟缑觏蠱顧估毂沽锢鸪鹄鹘剮挂鸹掴關觀館慣貫诖掼鹳鳏廣犷規歸龜閨軌詭貴劊軌刿妫桧鲑鳜輥滾衮绲鲧鍋國過埚呙帼椁蝈哈駭韓漢阚绗颉號灏颢閡鶴賀诃阖蛎橫轟鴻紅黉讧荭闳鲎壺護滬戶浒鹕嘩華畫劃話骅桦铧懷壞歡環還緩換喚瘓煥渙奂缳锾鲩黃謊鳇揮輝毀賄穢會燴彙諱誨繪诙荟哕會缋珲晖葷渾诨馄阍獲貨禍夥獲擊機積饑迹譏雞績緝極輯級擠幾薊劑濟計記際繼紀讦诘荠叽擠骥玑觊齑矶羁虿跻霁鲚鲫夾莢頰賈鉀價駕郏浃铗镓蛲殲監堅箋間艱緘繭檢堿鹼揀撿簡儉減薦檻鑒踐賤見鍵艦劍餞漸濺澗谏缣戋戬睑鹣笕鲣鞯將漿蔣槳獎講醬绛缰膠澆驕嬌攪鉸矯僥腳餃繳絞轎較挢峤鹪鲛階節潔結誡屆疖颌鲒緊錦僅謹進晉燼盡勁荊莖卺荩馑缙赆觐鯨驚經頸靜鏡徑痙競淨刭泾迳弪胫靓糾廄舊阄鸠鹫駒舉據鋸懼劇讵屦榉飓钜锔窭龃鵑絹锩镌隽覺決絕谲珏鈞軍駿皲開凱凱垲忾恺铠锴龛闶钪铐顆殼課骒缂轲钶锞颔墾懇龈铿摳庫褲喾塊儈郐哙脍寬狯髋礦曠況诓诳邝圹礦贶虧巋窺饋潰匮蒉愦聩篑阃锟鲲擴闊蛴蠟臘萊來賴崃徕涞濑赉睐铼癞籁藍欄攔籃闌蘭瀾讕攬覽懶纜爛濫岚榄斓镧褴琅阆锒撈勞澇唠撈铑铹痨樂鳓鐳壘類淚诔缧籬狸離鯉禮麗厲勵礫曆瀝隸俪郦坜苈位離曆逦骊缡枥栎轹砺锂鹂厲粝躍雳鲡鳢倆聯蓮連鐮憐漣簾斂臉鏈戀煉練蔹奁斂琏殓裢裣鲢糧涼兩輛諒魉療遼鐐缭钌鹩獵臨鄰鱗凜賃蔺廪檩辚躏齡鈴靈嶺領绫棂蛏鲮餾劉浏骝绺镏鹨龍聾嚨籠壟攏隴嚨壟珑栊胧砻樓婁摟簍偻蒌喽摟镂瘘耧蝼髅蘆盧顱廬爐擄鹵虜魯賂祿錄陸垆撸魯闾泸渌栌橹轳辂辘氇胪鸬鹭舻鲈巒攣孿灤亂脔娈栾鸾銮掄輪倫侖淪綸論掄蘿羅邏鑼籮騾駱絡荦猡樂椤脶镙驢呂鋁侶屢縷慮濾綠榈褛锊呒媽瑪碼螞馬罵嗎唛嬷杩買麥賣邁脈劢瞞饅蠻滿謾缦镘颡鳗貓錨鉚貿麽沒鎂門悶們扪焖懑钔錳夢眯謎彌覓冪芈谧猕祢綿緬渑腼黾廟缈缪滅憫閩闵缗鳴銘謬谟蓦馍殁镆謀畝钼呐鈉納難撓腦惱鬧铙讷餒內擬膩铌鲵攆辇鲶釀鳥鳥袅聶齧鑷鎳陧蘖聶颟蹑檸獰甯擰濘苎咛聍鈕紐膿濃農侬膿驽钕諾傩瘧歐鷗毆嘔漚讴怄歐盤蹒龐抛疱賠辔噴鵬批罴铍騙谝骈飄缥頻貧嫔蘋憑評潑頗钋撲鋪樸譜镤镨棲臍齊騎豈啓氣棄訖薪骐绮桤碛颀颃鳍牽釺鉛遷簽謙錢鉗潛淺譴塹佥荨悭骞缱椠钤槍嗆牆薔強搶牆樯戗炝锖锵镪羟跄鍬橋喬僑翹竅诮谯荞缲硗跷竊惬锲箧欽親寢浸輕氫傾頃請慶揿鲭瓊窮茕蛱巯赇虮鳅趨區軀驅齲诎區阒觑鸲顴權勸诠绻辁铨卻鵲確阕阙悫讓饒擾繞荛娆桡熱韌認紉饪轫榮絨嵘蝾缛铷颦軟銳蚬閏潤灑薩飒鰓賽傘毵糁喪騷掃缫澀啬铯穑殺刹紗铩鲨篩曬酾刪閃陝贍繕讪姗骟钐鳝墒傷賞垧殇觞燒紹賒攝懾設厍滠畲紳審嬸腎滲诜谂渖聲繩勝師獅濕詩時蝕實識駛勢適釋飾視試諡埘莳弑轼贳铈鲥壽獸绶樞輸書贖屬術樹豎數摅纾帥闩雙誰稅順說碩爍铄絲飼厮驷缌锶鸶聳慫頌訟誦擻數馊飕锼蘇訴肅谡稣雖隨綏歲谇孫損筍荪狲縮瑣鎖唢睃獺撻闼铊鳎台態钛鲐攤貪癱灘壇譚談歎昙坦锬顸湯燙傥湯铴镗濤縧討韬铽騰謄銻題體屜缇鹈阗條粜龆鲦貼鐵廳聽烴銅統恸頭钭禿圖钍團專頹蛻饨脫鴕馱駝橢箨鼍襪娲腽彎灣頑萬纨绾網辋韋違圍爲濰維葦偉僞緯謂衛诿帏闱僞涠玮韪炜鲔溫聞紋穩問阌甕撾蝸渦窩臥莴龌嗚鎢烏誣無蕪吳塢霧務誤邬庑怃妩骛鹉鹜錫犧襲習銑戲細饩阋玺觋蝦轄峽俠狹廈嚇硖鮮纖賢銜閑顯險現獻縣餡羨憲線苋莶藓岘猃閑鹇痫蚝籼跹廂鑲鄉詳響項鄉饷骧缃飨蕭囂銷曉嘯曉潇骁绡枭箫協挾攜脅諧寫瀉謝亵撷泄缬鋅釁興陉荥凶洶鏽繡馐鸺虛噓須許敘緒續诩顼軒懸選癬絢谖铉镟學谑泶鳕勳詢尋馴訓訊遜埙尋鲟壓鴉鴨啞亞訝垭娅桠氩閹煙鹽嚴岩顔閻豔厭硯彥諺驗厣赝俨兖谳恹闫酽魇餍鼹鴦楊揚瘍陽癢養樣炀瑤搖堯遙窯謠藥轺鹞鳐爺頁業葉靥谒邺晔烨醫銥頤遺儀蟻藝億憶義詣議誼譯異繹诒呓峄饴怿驿缢轶贻钇镒镱瘗舣蔭陰銀飲隱铟瘾櫻嬰鷹應纓瑩螢營熒蠅贏穎茔莺萦蓥撄嘤滢潆櫻鹦瘿颏罂喲擁傭癰踴詠镛優憂郵鈾猶誘莸铕鱿輿魚漁娛與嶼語獄譽預馭伛俣谀谕蓣嵛饫阈妪于觎欤钰鹆鹬龉鴛淵轅園員圓緣遠橼鸢鼋約躍鑰粵悅閱钺鄖勻隕運蘊醞暈韻郓芸恽愠纭韫隕氲雜災載攢暫贊攢趱錾贓髒驵鑿棗責擇則澤赜啧帻箦賊谮贈綜缯軋鍘閘柵詐齋債氈盞斬輾嶄棧戰綻谵張漲帳賬脹趙诏钊蟄轍鍺這谪辄鹧貞針偵診鎮陣貞缜桢轸赈祯鸩掙睜猙爭幀症鄭證诤峥钲铮筝織職執紙摯擲幟質滯骘栉栀職轾贽鸷蛳絷踬踯觯鍾終種腫衆锺謅軸皺晝驟纣绉豬諸誅燭矚囑貯鑄駐伫槠铢專磚轉賺轉馔颞樁莊裝妝壯狀錐贅墜綴骓缒諄准著濁诼镯茲資漬谘缁辎赀眦锱龇鲻蹤總縱偬鄒诹驺鲰詛組镞鑽缵躜鳟翺並蔔沈醜澱叠鬥範幹臯矽櫃後夥稭傑訣誇裏淩麽黴撚淒扡聖屍擡塗窪喂汙鍁鹹蠍彜湧遊籲禦願嶽雲竈紮劄築于志注凋讠谫郄勐凼坂垅垴埯埝苘買荮莜莼菰藁揸吒吣咔咝咴噘霹嚯幞岙嵴彷徼瑪狍馀馇馓馕愣憷懔丬敘豔溷婪潴澹甯纟绔绱珉枧桊桉槔橥轱轷赍肷胨飚葫煅熘愍淼砜磙眍钚钷铘吊锃锍锎锏锘锝锪锫锿镅镎镢镥镩察稆鹋鹛鹱疬疴痖癯裥襁耢颥螨麴鲅鲆鲇鲞鲴鲺鲼鳊鳋鳘鳙鞒鞴齄丟並亂亙亞伋伕佇佈佔佪併來侖侶侷俁係俠倀倆倉個們倖倣倫偉側偵偺偽傑傖傘備傢傭傯傳傴債傷傾僂僅僇僉僑僕僥僨僱價儀儂億儅儈儉儐儔儕儘償優儲儷儸儺儻儼兇兌兒兗內兩冊冑冪凈凍凜凱別刪剄則剋剎剛剝剮剴創剷劃劇劉劊劌劍劑劻勁動勗務勛勝勞勢勣勦勱勵勸勻匭匯匱區協卹卻厙厭厲厴參叢吋吳吶呂呎咼員唄唸問啞啟啣喚喪喫喬單喲嗆嗇嗎嗚嗩嗶嘆嘍嘔嘖嘗嘜嘩嘮嘯嘰嘵嘸噁噓噠噥噦噯噲噴噸噹嚀嚇嚌嚕嚙嚥嚦嚨嚮嚳嚴嚶囀囁囂囅囈囉囌囑囪圇國圍園圓圖團坰埡執堅堊堝堯報場堿塊塋塏塒塗塢塤塵塹墊墑墜墮墳墻墾壇壎壓壘壙壚壞壟壢壩壯壺壽夠夢夾奐奧奩奪奮妝妳姍姦姪娛婁婦婭媧媯媼媽嫗嫵嫻嬈嬋嬌嬙嬝嬡嬤嬪嬰嬸孃孌孫學孿宮寢實寧審寫寬寵寶將專尋對導尷屆屜屝屢層屨屬岡岧峴島峽崍崗崙崠崢崳嵐嵒嶁嶄嶇嶗嶠嶧嶸嶺嶼嶽巋巑巒巔巖巰巹帥師帳帶幀幃幗幘幟幣幫幬幹幾庂庫廁廂廄廈廚廝廟廠廡廢廣廩廬廱廳弒弔弳張強彆彈彊彌彎彙彥彫彿徑從徠復徬徹恆恥悅悵悶悽惡惱惲惻愛愜愨愴愷愾慄慇態慍慘慚慟慣慪慫慮慳慶慼憂憊憐憑憒憚憤憫憮憲憶懇應懌懍懞懟懣懨懲懶懷懸懺懼懾戀戇戉戔戧戩戰戲戶扐扠扢扺抃抆抎抴拋挶挾捍捨捫捲掃掄掙掛採揀揚換揮揹搆損搖搗搟搶摑摜摟摯摳摶摻撈撐撓撚撟撢撣撥撫撲撳撻撾撿擁擄擇擊擋擔據擠擣擬擯擰擱擲擴擷擺擻擼擾攄攆攏攔攖攙攛攜攝攢攣攤攪攬攷敗敘敵數斂斃斕斬斷旂時晉晝晞暈暉暘暢暫暱暸曄曆曇曉曖曠曬書會朧朮杇東枙枴柵桿梔條梟棄棖棗棟棧棲椏楊楓楨業極榦榪榮榿槃構槍槓槧槨槳樁樂樅樑樓標樞樣樸樹樺橈橋機橢橫檁檉檔檜檢檣檯檳檸檻櫃櫓櫚櫛櫝櫞櫟櫥櫧櫨櫪櫬櫳櫸櫺櫻欄權欏欒欖欞欽歎歐歟歡歲歷歸歿殘殞殤殫殮殯殲殺殼毀毆毌毬毿氈氌氣氫氬氳氾汍汎汙決沒沖況洩洶浬浹涇涼淒淚淥淨淪淵淶淺渙減渦測渾湊湞湧湯溈準溝溫溼滄滅滌滎滬滯滲滷滸滾滿漁漚漢漣漬漲漸漿潁潑潔潛潟潤潯潰潷潿澀澆澇澗澠澤澩澮澱濁濃濕濘濛濟濤濫濰濱濺濼濾瀅瀆瀉瀋瀏瀕瀘瀝瀟瀠瀧瀨瀰瀲瀾灃灄灑灕灘灝灣灤災為烏烴無煉煒煙煢煥煩煬熒熗熱熾燁燄燈燉燐燒燙燜營燦燬燭燴燼燾爍爐爛爭爺爾牆牘牪牴牽犖犛犢犧狀狹狽猙猶猻獄獅獎獨獪獫獮獰獲獵獷獸獺獻獼玀玆玨珮現琺琿瑋瑣瑤瑩瑪瑯璉璣璦環璽瓊瓏瓔瓖瓚甌甕產甦畝畢畫畬異當疇疊疿痙痠痲痳痺瘋瘍瘓瘞瘡瘧瘺療癆癇癉癘癟癡癢癤癥癩癬癭癮癰癱癲發皚皰皸皺皻盜盞盡監盤盧盪眥眾睏睜睞睪瞇瞞瞭瞼矇矓矚矯砲硃硤硨硯碩碭確碼磚磣磧磯磽礎礙礦礪礫礬礱祅祇祐祕祿禍禎禦禪禮禰禱禿秈稅稈稜稟種稱穀穌積穎穡穢穩穫窩窪窮窯窶窺竄竅竇竊競笻筆筍筧箄箇箋箏節範築篋篠篤篩篳簀簍簑簞簡簣簫簷簽簾籃籌籐籜籟籠籤籥籩籪籬籮籲粵糝糞糢糧糰糲糴糶糾紀紂約紅紆紇紈紉紋納紐紓純紕紗紙級紛紜紡細紱紲紳紹紺紼紿絀終絃組絆絎結絕絛絞絡絢給絨統絲絳絹綁綃綆綈綏經綜綞綠綢綣綬維綰綱網綴綵綸綹綺綻綽綾綿緄緇緊緋緒緗緘緙線緝緞締緡緣緦編緩緬緯緱緲練緶緹緻縈縉縊縋縐縑縚縛縝縞縟縣縫縭縮縯縱縲縳縴縵縶縷縹總績繃繅繆繈繒織繕繚繞繡繢繩繪繫繭繯繰繳繹繼繽繾纈纊續纍纏纓纔纖纘纜缽罈罌罰罵罷羅羆羈羋羥羨義習翹耑耬聖聞聯聰聲聳聵聶職聹聽聾肅脅脈脛脣脧脩脫脹腎腡腦腫腳腸膃膚膠膩膽膾膿臉臍臏臘臚臟臠臥臨臺與興舉舊艙艣艤艦艫艱艷艸芐芻苧茍茲荊莊莖莢莧華菴萇萊萬萵葉葒著葦葯葷蒔蒞蒼蓀蓋蓮蓯蓽蔆蔔蔞蔣蔥蔦蔭蕁蕆蕎蕓蕕蕘蕢蕩蕪蕭蕷薈薊薌薑薔薟薦薧薩薺藍藎藝藥藪藶藷藹藺蘄蘆蘇蘊蘋蘗蘚蘞蘢蘭蘺蘿處虛虜號虧蛺蛻蜆蝕蝦蝸螄螞螢螻蟄蟈蟣蟬蟯蟲蟶蟻蠅蠆蠐蠑蠔蠟蠣蠱蠶蠻衊術衛衝衹袞裊裏補裝裡製複褲褳褸褻襖襝襠襤襪襬襯襲覈見規覓視覘覡覦親覬覯覲覷覺覽覿觀觔觴觶觸訂訃計訊訌討訐訓訕訖託記訛訝訟訢訣訥訪設許訴訶診註証詁詆詎詐詒詔評詘詛詞詠詡詢詣試詩詫詬詭詮詰話該詳詵詼詿誄誅誆誇誌認誑誒誕誘誚語誠誡誣誤誥誦誨說誰課誶誹誼調諂諄談諉請諍諏諑諒論諗諛諜諞諢諤諦諧諫諭諮諱諳諶諷諸諺諼諾謀謁謂謄謅謊謎謐謔謖謗謙謚講謝謠謨謫謬謳謹謾證譎譏譖識譙譚譜譟譫譯議譴護譽讀變讎讒讓讕讖讚讜讞豈豎豐豔豬貍貓貝貞負財貢貧貨販貪貫責貯貰貲貳貴貶買貸貺費貼貽貿賀賁賂賃賄賅資賈賊賑賒賓賕賚賜賞賠賡賢賣賤賦賧質賬賭賴賺賻購賽賾贄贅贈贊贍贏贐贓贖贗贛趕趙趨趲跡跼踐踡踫踴蹌蹕蹠蹣蹤蹺躉躊躋躍躑躒躓躕躚躡躥躦躪軀車軋軌軍軒軔軛軟軫軸軹軺軻軼軾較輅輇載輊輒輓輔輕輛輜輝輞輟輥輦輩輪輯輳輸輻輾輿轂轄轅轆轉轍轎轔轟轡轢轤辦辭辮辯農迆迴迺逕這連週進遊運過達違遙遜遞遠適遲遷選遺遼邁還邇邊邏邐郃郟郵鄆鄉鄒鄔鄖鄧鄭鄰鄲鄴鄶鄺酈醃醜醞醫醬釀釁釃釅釋釐釓釔釕釗釘釙針釣釤釦釧釩釬釵釷釹鈀鈁鈄鈉鈍鈐鈑鈔鈕鈞鈣鈥鈦鈧鈮鈰鈳鈴鈷鈸鈹鈺鈽鈾鈿鉀鉅鉆鉈鉉鉋鉍鉑鉗鉚鉛鉞鉤鉦鉬鉭鉸鉺鉻鉿銀銃銅銑銓銖銘銚銜銠銣銥銦銨銩銪銫銬銲銳銷銹銻銼鋁鋃鋅鋇鋌鋏鋒鋝鋟鋤鋦鋨鋪鋮鋯鋰鋱鋸鋼錁錄錆錈錐錒錕錘錙錚錛錟錠錢錦錨錫錮錯錳錶錸鍆鍇鍊鍋鍍鍔鍘鍛鍤鍥鍬鍰鍵鍶鍺鍾鎂鎊鎔鎖鎗鎘鎢鎣鎦鎧鎩鎪鎬鎮鎰鎳鎵鏃鏇鏈鏌鏍鏑鏗鏘鏜鏝鏞鏟鏡鏢鏤鏨鏵鏷鏹鏽鐃鐉鐋鐐鐒鐓鐔鐘鐙鐠鐨鐫鐮鐲鐳鐵鐸鐺鐽鐿鑄鑊鑌鑑鑒鑠鑣鑤鑭鑰鑲鑷鑼鑽鑾鑿長門閂閃閆閉開閌閎閏閑閒間閔閘閡閣閤閥閨閩閫閬閭閱閶閹閻閼閽閾閿闃闆闈闊闋闌闐闔闕闖關闞闡闢闥阨阯陘陜陝陣陰陳陸陽隄隉隊階隕際隨險隱隴隸隻雋雖雙雛雜雞離難雲電霑霤霧霽靂靄靈靚靜靦靨鞏鞦韁韃韆韉韋韌韓韙韜韞韻響頁頂頃項順頇須頊頌頎頏預頑頒頓頗領頜頡頤頦頭頰頷頸頹頻顆題額顎顏顓願顙顛類顢顥顧顫顯顰顱顳顴風颮颯颱颳颶颺颼飄飆飛飢飩飪飫飭飯飲飴飼飽飾餃餅餉養餌餑餒餓餘餚餛餞餡館餳餵餼餽餾餿饃饅饈饉饋饌饑饒饗饜饞馬馭馮馱馳馴駁駐駑駒駔駕駘駙駛駝駟駢駭駱駿騁騅騍騎騏騖騙騫騭騮騰騶騷騸騾驀驁驂驃驄驅驊驍驏驕驗驚驛驟驢驤驥驪骯髏髒體髕髖髮鬆鬍鬚鬢鬥鬧鬨鬩鬮鬱魎魘魚魯魴魷鮐鮑鮒鮚鮞鮪鮫鮭鮮鯀鯁鯇鯉鯊鯔鯖鯛鯡鯢鯤鯧鯨鯪鯫鯰鯽鰈鰉鰍鰒鰓鰣鰥鰨鰩鰭鰱鰲鰳鰷鰹鰻鰾鱈鱉鱒鱔鱖鱗鱘鱟鱧鱭鱷鱸鱺鳥鳧鳩鳳鳴鳶鴆鴇鴉鴕鴛鴝鴟鴣鴦鴨鴯鴰鴻鴿鵂鵑鵒鵓鵜鵝鵠鵡鵪鵬鵯鵲鶇鶉鶘鶚鶩鶯鶱鶴鶻鶼鶿鷂鷓鷗鷙鷚鷥鷦鷯鷲鷳鷴鷸鷹鷺鸕鸚鸛鸝鸞鹵鹹鹺鹼鹽麗麥麩麵麼黃黌點黨黲黴黷黽黿鼇鼉鼕鼴齊齋齏齒齔齙齜齟齠齡齣齦齧齪齬齲齷龍龐龔龕龜 3 | -------------------------------------------------------------------------------- /VVSequelize/Core/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pozi119/VVSequelize/a575648ec93ab8fc68e7f9b769ea9d64698b126a/VVSequelize/Core/.gitkeep -------------------------------------------------------------------------------- /VVSequelize/Core/Database/VVDBStatement.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVDBStatement.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/3/19. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @class VVDatabase, VVDBStatement; 13 | 14 | /// sqlit3_stmt cursor, use to bind/read data 15 | @interface VVDBCursor : NSObject 16 | - (instancetype)initWithStatement:(VVDBStatement *)statement; 17 | 18 | - (id)objectAtIndexedSubscript:(NSUInteger)idx; 19 | - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx; 20 | 21 | - (id)objectForKeyedSubscript:(NSString *)key; 22 | - (void)setObject:(id)obj forKeyedSubscript:(NSString *)key; 23 | 24 | @end 25 | 26 | /// packaged sqlite3_stmt 27 | @interface VVDBStatement : NSObject 28 | /// query/bind lines 29 | @property (nonatomic, assign, readonly) int columnCount; 30 | 31 | /// query/bind column names 32 | @property (nonatomic, strong, readonly) NSArray *columnNames; 33 | 34 | /// cursor 35 | @property (nonatomic, strong, readonly) VVDBCursor *cursor; 36 | 37 | /// create/get VVDBStatement object 38 | /// @param vvdb db object 39 | /// @param sql native sql 40 | /// @note has cache 41 | + (nullable instancetype)statementWithDatabase:(VVDatabase *)vvdb sql:(NSString *)sql; 42 | 43 | /// Initialize VVDBStatement 44 | /// @param vvdb db object 45 | /// @param sql native sql 46 | - (nullable instancetype)initWithDatabase:(VVDatabase *)vvdb sql:(NSString *)sql; 47 | 48 | /// bind data 49 | /// @param values data array, corresponding to 'columnnames' one by one 50 | - (VVDBStatement *)bind:(nullable NSArray *)values; 51 | 52 | - (id)scalar:(nullable NSArray *)values; 53 | 54 | /// execute sqlite3_stmt 55 | - (BOOL)run; 56 | 57 | /// execute sqlite3_stmt query 58 | - (nullable NSArray *)query; 59 | 60 | /// execute sqlite3_step() 61 | - (BOOL)step; 62 | 63 | /// reset sqlite3_stmt, do not clean bind data 64 | - (void)reset; 65 | 66 | /// reset sqlite3_stmt, clean bind data 67 | - (void)resetAndClear; 68 | 69 | @end 70 | 71 | NS_ASSUME_NONNULL_END 72 | -------------------------------------------------------------------------------- /VVSequelize/Core/Database/VVDBStatement.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVDBStatement.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/3/19. 6 | // 7 | 8 | #import "VVDBStatement.h" 9 | #import "VVDatabase.h" 10 | #import "VVOrm.h" 11 | 12 | #ifdef SQLITE_HAS_CODEC 13 | #import "sqlite3.h" 14 | #else 15 | #import 16 | #endif 17 | 18 | @interface VVDBStatement () 19 | @property (nonatomic, weak) VVDatabase *vvdb; 20 | @property (nonatomic, copy) NSString *sql; 21 | @property (nonatomic, strong) NSArray *values; 22 | @property (nonatomic, assign) sqlite3_stmt *stmt; 23 | @property (nonatomic, assign) int columnCount; 24 | @property (nonatomic, strong) NSArray *columnNames; 25 | @property (nonatomic, strong) VVDBCursor *cursor; 26 | @end 27 | 28 | @implementation VVDBStatement 29 | 30 | + (instancetype)statementWithDatabase:(VVDatabase *)vvdb sql:(NSString *)sql 31 | { 32 | return [[VVDBStatement alloc] initWithDatabase:vvdb sql:sql]; 33 | } 34 | 35 | - (instancetype)initWithDatabase:(VVDatabase *)vvdb sql:(NSString *)sql 36 | { 37 | self = [super init]; 38 | if (self) { 39 | _vvdb = vvdb; 40 | _sql = sql; 41 | int rc = sqlite3_prepare_v2(vvdb.db, sql.UTF8String, -1, &_stmt, nil); 42 | BOOL ret = [self.vvdb check:rc sql:sql]; 43 | //NSAssert(ret && _stmt != NULL, @"prepare sqlite3_stmt failure: %@, vvdb : %@", sql, vvdb); 44 | if (!ret) return nil; 45 | } 46 | return self; 47 | } 48 | 49 | - (void)dealloc 50 | { 51 | sqlite3_finalize(_stmt); 52 | _stmt = NULL; 53 | } 54 | 55 | //MARK: - 56 | - (int)columnCount 57 | { 58 | if (!_columnCount) { 59 | _columnCount = sqlite3_column_count(_stmt); 60 | } 61 | return _columnCount; 62 | } 63 | 64 | - (NSArray *)columnNames 65 | { 66 | if (!_columnNames) { 67 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.columnCount]; 68 | for (int i = 0; i < self.columnCount; i++) { 69 | const char *col = sqlite3_column_name(_stmt, i); 70 | [array addObject:[NSString stringWithUTF8String:col]]; 71 | } 72 | _columnNames = [array copy]; 73 | } 74 | return _columnNames; 75 | } 76 | 77 | - (VVDBCursor *)cursor 78 | { 79 | if (!_cursor) { 80 | _cursor = [[VVDBCursor alloc] initWithStatement:self]; 81 | } 82 | return _cursor; 83 | } 84 | 85 | - (VVDBStatement *)bind:(nullable NSArray *)values 86 | { 87 | int count = (int)values.count; 88 | if (count == 0) return self; 89 | _values = values; 90 | sqlite3_reset(_stmt); 91 | sqlite3_clear_bindings(_stmt); 92 | int paramsCount = sqlite3_bind_parameter_count(_stmt); 93 | NSAssert(count == paramsCount, @"%d values expected, %d passed", paramsCount, count); 94 | for (int i = 0; i < paramsCount; i++) { 95 | self.cursor[i] = values[i]; 96 | } 97 | return self; 98 | } 99 | 100 | - (id)scalar:(nullable NSArray *)values 101 | { 102 | [[self bind:values] step]; 103 | return self.cursor[0]; 104 | } 105 | 106 | - (BOOL)run 107 | { 108 | sqlite3_reset(_stmt); 109 | __block int rc; 110 | do { 111 | [self.vvdb sync:^{ 112 | rc = sqlite3_step(self.stmt); 113 | }]; 114 | } while (rc == SQLITE_ROW); 115 | return [self.vvdb check:rc sql:_sql]; 116 | } 117 | 118 | - (NSArray *)query 119 | { 120 | int columnCount = self.columnCount; 121 | if (columnCount == 0) return @[]; 122 | 123 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:0]; 124 | BOOL rc; 125 | do { 126 | rc = [self step]; 127 | if (rc) { 128 | NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:columnCount]; 129 | for (int i = 0; i < columnCount; i++) { 130 | dic[self.columnNames[i]] = self.cursor[i]; 131 | } 132 | [array addObject:dic]; 133 | } 134 | } while (rc); 135 | return array; 136 | } 137 | 138 | - (BOOL)step 139 | { 140 | return sqlite3_step(_stmt) == SQLITE_ROW; 141 | } 142 | 143 | - (void)reset 144 | { 145 | sqlite3_reset(_stmt); 146 | } 147 | 148 | - (void)resetAndClear 149 | { 150 | sqlite3_reset(_stmt); 151 | sqlite3_clear_bindings(_stmt); 152 | } 153 | 154 | @end 155 | 156 | //MARK: - 157 | @interface VVDBCursor () 158 | @property (nonatomic, assign) sqlite3_stmt *stmt; 159 | @property (nonatomic, assign) int columnCount; 160 | @end 161 | 162 | @implementation VVDBCursor 163 | 164 | - (instancetype)initWithStatement:(VVDBStatement *)statement 165 | { 166 | self = [super init]; 167 | if (self) { 168 | self.stmt = statement.stmt; 169 | self.columnCount = statement.columnCount; 170 | } 171 | return self; 172 | } 173 | 174 | - (id)objectAtIndexedSubscript:(NSUInteger)idx 175 | { 176 | int index = (int)idx; 177 | int type = sqlite3_column_type(_stmt, index); 178 | switch (type) { 179 | case SQLITE_INTEGER: { 180 | return @(sqlite3_column_int64(_stmt, index)); 181 | } 182 | case SQLITE_FLOAT: { 183 | return @(sqlite3_column_double(_stmt, index)); 184 | } 185 | case SQLITE_TEXT: { 186 | const unsigned char *bytes = sqlite3_column_text(_stmt, index); 187 | NSString *text = [NSString stringWithUTF8String:(const char *)bytes ? : ""]; 188 | return text; 189 | } 190 | case SQLITE_BLOB: { 191 | const unsigned char *bytes = sqlite3_column_blob(_stmt, index); 192 | int64_t len = sqlite3_column_bytes(_stmt, index); 193 | NSData *data = [NSData dataWithBytes:bytes length:(NSUInteger)len]; 194 | return data; 195 | } 196 | case SQLITE_NULL: { 197 | return [NSNull null]; 198 | } 199 | default: { 200 | NSAssert(NO, @"unsupported column type: %d", type); 201 | return [NSNull null]; 202 | } 203 | } 204 | } 205 | 206 | - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx 207 | { 208 | int index = (int)idx + 1; 209 | if (obj == nil || [obj isKindOfClass:[NSNull class]]) { 210 | sqlite3_bind_null(_stmt, index); 211 | } else if ([obj isKindOfClass:[NSData class]]) { 212 | NSData *data = (NSData *)obj; 213 | if (data.length > INT_MAX) { 214 | sqlite3_bind_blob64(_stmt, index, data.bytes, data.length, SQLITE_TRANSIENT); 215 | } else { 216 | sqlite3_bind_blob(_stmt, index, data.bytes, (int)data.length, SQLITE_TRANSIENT); 217 | } 218 | } else if ([obj isKindOfClass:[NSNumber class]]) { 219 | NSNumber *number = (NSNumber *)obj; 220 | switch (number.objCType[0]) { 221 | case 'B': 222 | sqlite3_bind_int(_stmt, index, number.boolValue); 223 | break; 224 | 225 | case 'c': 226 | case 'C': 227 | case 's': 228 | case 'S': 229 | case 'i': 230 | case 'I': 231 | sqlite3_bind_int(_stmt, index, number.intValue); 232 | break; 233 | 234 | case 'l': 235 | case 'L': 236 | case 'q': 237 | case 'Q': 238 | sqlite3_bind_int64(_stmt, index, number.longValue); 239 | break; 240 | 241 | case 'f': 242 | sqlite3_bind_double(_stmt, index, number.floatValue); 243 | break; 244 | 245 | case 'd': 246 | case 'D': 247 | sqlite3_bind_double(_stmt, index, number.doubleValue); 248 | break; 249 | 250 | default: 251 | break; 252 | } 253 | } else if ([obj isKindOfClass:[NSString class]]) { 254 | const char *string = [(NSString *)obj UTF8String]; 255 | sqlite3_bind_text(_stmt, index, string, -1, SQLITE_TRANSIENT); 256 | } else { 257 | NSAssert(NO, @"tried to bind unexpected value %@", obj); 258 | } 259 | } 260 | 261 | - (id)objectForKeyedSubscript:(NSString *)key 262 | { 263 | int idx = sqlite3_bind_parameter_index(_stmt, key.UTF8String); 264 | return self[idx]; 265 | } 266 | 267 | - (void)setObject:(id)obj forKeyedSubscript:(NSString *)key 268 | { 269 | int idx = sqlite3_bind_parameter_index(_stmt, key.UTF8String); 270 | self[idx] = obj; 271 | } 272 | 273 | @end 274 | -------------------------------------------------------------------------------- /VVSequelize/Core/Database/VVDatabase+Additions.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVDatabase+Additions.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/3/27. 6 | // 7 | 8 | #import "VVDatabase.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface VVDatabase (Additions) 13 | // MARK: - pool 14 | 15 | /// open / create db 16 | /// @param path db file full path 17 | /// @note get from db poll first 18 | + (instancetype)databaseInPoolWithPath:(nullable NSString *)path; 19 | 20 | /// open / create db 21 | /// @param path db file full path 22 | /// @param flags third parameter of sqlite3_open_v2, (flags | VVDBEssentialFlags) 23 | /// @note get from db poll first 24 | + (instancetype)databaseInPoolWithPath:(nullable NSString *)path 25 | flags:(int)flags; 26 | 27 | /// open / create db 28 | /// @param path db file full path 29 | /// @param flags third parameter of sqlite3_open_v2, (flags | VVDBEssentialFlags) 30 | /// @param key encrypt key 31 | /// @note get from db poll first 32 | + (instancetype)databaseInPoolWithPath:(nullable NSString *)path 33 | flags:(int)flags 34 | encrypt:(nullable NSString *)key; 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /VVSequelize/Core/Database/VVDatabase+Additions.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVDatabase+Additions.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/3/27. 6 | // 7 | 8 | #import "VVDatabase+Additions.h" 9 | 10 | @implementation VVDatabase (Additions) 11 | // MARK: - pool 12 | + (instancetype)databaseInPoolWithPath:(nullable NSString *)path 13 | { 14 | return [self databaseInPoolWithPath:path flags:0 encrypt:nil]; 15 | } 16 | 17 | + (instancetype)databaseInPoolWithPath:(nullable NSString *)path 18 | flags:(int)flags 19 | { 20 | return [self databaseInPoolWithPath:path flags:flags encrypt:nil]; 21 | } 22 | 23 | + (instancetype)databaseInPoolWithPath:(nullable NSString *)path 24 | flags:(int)flags 25 | encrypt:(nullable NSString *)key 26 | { 27 | static NSMapTable *_pool; 28 | static dispatch_once_t onceToken; 29 | dispatch_once(&onceToken, ^{ 30 | _pool = [NSMapTable strongToWeakObjectsMapTable]; 31 | }); 32 | NSString *udid = [self udidWithPath:path flags:flags encryptKey:key]; 33 | VVDatabase *db = [_pool objectForKey:udid]; 34 | if (!db) { 35 | db = [self databaseWithPath:path flags:flags encrypt:key]; 36 | [_pool setObject:db forKey:udid]; 37 | } 38 | return db; 39 | } 40 | 41 | + (NSString *)udidWithPath:(NSString *)path flags:(int)flags encryptKey:(NSString *)key 42 | { 43 | NSString *aPath = path ? : VVDBPathTemporary; 44 | int aFlags = flags | VVDBEssentialFlags; 45 | NSString *aKey = key ? : @""; 46 | return [NSString stringWithFormat:@"%@|%@|%@", aPath, @(aFlags), aKey]; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /VVSequelize/Core/Database/VVDatabase.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVDatabase.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/3/19. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | typedef struct sqlite3 sqlite3; 13 | 14 | ///memory database path 15 | FOUNDATION_EXPORT NSString *const VVDBPathInMemory; 16 | /// temporary database path 17 | FOUNDATION_EXPORT NSString *const VVDBPathTemporary; 18 | /// SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX 19 | FOUNDATION_EXPORT int VVDBEssentialFlags; 20 | 21 | /// sqlite3 transaction type 22 | /// 23 | /// VVDBTransactionDeferred: `BEGIN DEFERRED` 24 | /// VVDBTransactionImmediate: `BEGIN IMMEDIATE` 25 | /// VVDBTransactionExclusive: `BEGIN EXCLUSIVE` 26 | typedef NS_ENUM (NSUInteger, VVDBTransaction) { 27 | VVDBTransactionDeferred, 28 | VVDBTransactionImmediate, 29 | VVDBTransactionExclusive, 30 | }; 31 | 32 | /// retry handler 33 | /// @param times retry times 34 | /// @return 0 stop retry; >0 retry. 35 | typedef int (^VVDBBusyHandler)(int times); 36 | 37 | /// trace sql 38 | /// @param mask SQLITE_TRACE_STMT, SQLITE_TRACE_PROFILE, SQLITE_TRACE_ROW, SQLITE_TRACE_CLOSE 39 | /// @param stmt sqlite3_stmt structure 40 | /// @return SQLITE_OK,SQLITE_DONE, etc.. 41 | typedef int (^VVDBTraceHook)(unsigned mask, void *stmt, void *sql); 42 | 43 | /// hook update 44 | /// @param op type: such as SQLITE_INSERT, SQLITE_DELETE, SQLITE_UPDATE 45 | /// @param db sqlite3_db structure 46 | typedef void (^VVDBUpdateHook)(int op, char const *db, char const *table, int64_t rowid); 47 | 48 | /// hook transaction 49 | /// @return 0-commit, else - rollback 50 | typedef int (^VVDBCommitHook)(void); 51 | 52 | ///hook rollback 53 | typedef void (^VVDBRollbackHook)(void); 54 | 55 | /// trace error 56 | typedef void (^VVDBTraceError)(int rc, NSString *sql, NSString *errmsg); 57 | 58 | @class VVDBStatement; 59 | 60 | /** 61 | Sequelize database 62 | */ 63 | @interface VVDatabase : NSObject 64 | 65 | /// db file full path 66 | @property (nonatomic, copy, readonly) NSString *path; 67 | 68 | ///encrypt key, nil means no encryption 69 | @property (nonatomic, copy, nullable) NSString *encryptKey; 70 | 71 | ///third parameter of sqlite3_open_v2(), (flags | VVDBEssentialFlags) 72 | @property (nonatomic, assign) int flags; 73 | 74 | /// execute between sqlite3_open_v2() and sqlite3_key() 75 | /// 76 | /// example: 77 | /// 78 | /// "pragma cipher_default_plaintext_header_size = 32;" 79 | /// 80 | @property (nonatomic, strong) NSArray *cipherDefaultOptions; 81 | 82 | /// execute after sqlite3_key_v2() 83 | /// 84 | /// example: open 3.x ciphered database 85 | /// 86 | /// "pragma kdf_iter = 64000;" 87 | /// 88 | /// "pragma cipher_hmac_algorithm = HMAC_SHA1;" 89 | /// 90 | /// "pragma cipher_kdf_algorithm = PBKDF2_HMAC_SHA1;" 91 | /// 92 | @property (nonatomic, strong) NSArray *cipherOptions; 93 | 94 | /// execute after cipherOptions 95 | /// 96 | /// example: 97 | /// 98 | /// "PRAGMA synchronous = NORMAL" 99 | /// 100 | /// "PRAGMA journal_mode = WAL" 101 | /// 102 | @property (nonatomic, strong) NSArray *normalOptions; 103 | 104 | /// remove file when SQLITE_NOTADB, default is NO 105 | @property (nonatomic, assign) BOOL removeWhenNotADB; 106 | 107 | ///serial write queue 108 | @property (nonatomic, strong, readonly) dispatch_queue_t writeQueue; 109 | 110 | ///concurrent read queue 111 | @property (nonatomic, strong, readonly) dispatch_queue_t readQueue; 112 | 113 | ///db is open or not 114 | @property (nonatomic, assign, readonly) BOOL isOpen; 115 | 116 | ///last changes for sqlite3_exec() 117 | @property (nonatomic, assign, readonly) int changes; 118 | 119 | ///total changes after db open 120 | @property (nonatomic, assign, readonly) int totalChanges; 121 | 122 | ///last insert rowid 123 | @property (nonatomic, assign, readonly) int64_t lastInsertRowid; 124 | 125 | + (instancetype)new __attribute__((unavailable("use initWithPath: instead."))); 126 | - (instancetype)init __attribute__((unavailable("use initWithPath: instead."))); 127 | 128 | /// open/create db 129 | /// @param path db file full path 130 | - (instancetype)initWithPath:(nullable NSString *)path; 131 | 132 | /// open/create db, no encryption, use `VVDBEssentialFlags` by default 133 | /// @param path db file full path 134 | + (instancetype)databaseWithPath:(nullable NSString *)path; 135 | 136 | /// open/create db, no encryption 137 | /// @param path db file full path 138 | /// @param flags third parameter of sqlite3_open_v2, (flags | VVDBEssentialFlags) 139 | + (instancetype)databaseWithPath:(nullable NSString *)path flags:(BOOL)flags; 140 | 141 | /// open/create db 142 | /// @param path db file full path 143 | /// @param flags third parameter of sqlite3_open_v2, (flags | VVDBEssentialFlags) 144 | /// @param key encrypt key 145 | + (instancetype)databaseWithPath:(nullable NSString *)path flags:(int)flags encrypt:(nullable NSString *)key; 146 | 147 | //MARK: - open and close 148 | /// open db 149 | /// @note add lazy loading, can no longer be executed 150 | - (BOOL)open; 151 | 152 | /// close db 153 | - (BOOL)close; 154 | 155 | // MARK: - queue 156 | /// synchronous operation, in writeQueue 157 | /// @param block operation 158 | - (void)sync:(void (^)(void))block; 159 | 160 | // MARK: - Execute 161 | 162 | /// use sqlite3_exec() execute native sql statement 163 | /// @param sql native sql 164 | - (BOOL)execute:(NSString *)sql; 165 | 166 | // MARK: - Prepare 167 | - (VVDBStatement *)prepare:(NSString *)sql; 168 | 169 | - (VVDBStatement *)prepare:(NSString *)sql bind:(nullable NSArray *)values; 170 | 171 | // MARK: - Run 172 | 173 | /// execute native sql query 174 | /// @param sql native sql 175 | /// @return query results 176 | /// @attention cache results. clear cache after update/insert/delete/commit. 177 | - (NSArray *)query:(NSString *)sql; 178 | 179 | /// execute native sql query 180 | /// @param values corresponding to `?` In sql 181 | - (NSArray *)query:(NSString *)sql bind:(NSArray *)values; 182 | 183 | /// execute native sql query 184 | /// @param sql native sqls 185 | /// @param clazz results class 186 | /// @return query results 187 | /// @attention cache results. clear cache after update/insert/delete/commit. 188 | - (NSArray *)query:(NSString *)sql clazz:(Class)clazz; 189 | 190 | /// execute native sql query 191 | /// @param values corresponding to `?` In sql 192 | - (NSArray *)query:(NSString *)sql bind:(NSArray *)values clazz:(Class)clazz; 193 | 194 | /// check if table exists 195 | /// @param table name 196 | - (BOOL)isExist:(NSString *)table; 197 | 198 | /// use sqlite3_step() execute native sql statement 199 | /// @param sql native sql 200 | - (BOOL)run:(NSString *)sql; 201 | 202 | /// use sqlite3_step() execute native sql statement 203 | /// @param sql native sql 204 | /// @param values bind values 205 | - (BOOL)run:(NSString *)sql bind:(nullable NSArray *)values; 206 | 207 | // MARK: - Scalar 208 | - (id)scalar:(NSString *)sql bind:(nullable NSArray *)values; 209 | 210 | // MARK: - Transactions 211 | 212 | /// begin transaction 213 | /// @param mode transaction mode 214 | - (BOOL)begin:(VVDBTransaction)mode; 215 | 216 | /// commit transaction 217 | - (BOOL)commit; 218 | 219 | /// rollback transaction 220 | - (BOOL)rollback; 221 | 222 | /// save point, use to rollback some transaction.such as: 223 | /// 1.set savepoint a 224 | /// 2.rollback to a or rollback all 225 | /// @note commit will delete all save point 226 | /// @param name save point name 227 | /// @param block operation 228 | - (BOOL)savepoint:(NSString *)name block:(BOOL (^)(void))block; 229 | 230 | /// immediate transaction 231 | - (BOOL)transaction:(BOOL (^)(void))block; 232 | 233 | /// transaction 234 | /// @param mode transaction mode 235 | /// @param block operation 236 | - (BOOL)transaction:(VVDBTransaction)mode block:(BOOL (^)(void))block; 237 | 238 | /// interrupt some operation manually 239 | - (void)interrupt; 240 | 241 | // MARK: - Handlers 242 | ///db busy time out 243 | @property (nonatomic, assign) NSTimeInterval timeout; 244 | 245 | ///callback when timeout 246 | @property (nonatomic, copy) VVDBBusyHandler busyHandler; 247 | 248 | ///trace sql 249 | @property (nonatomic, copy) VVDBTraceHook traceHook; 250 | 251 | ///hook update 252 | @property (nonatomic, copy) VVDBUpdateHook updateHook; 253 | 254 | ///hook commit 255 | @property (nonatomic, copy) VVDBCommitHook commitHook; 256 | 257 | ///hook rollback 258 | @property (nonatomic, copy) VVDBRollbackHook rollbackHook; 259 | 260 | ///error handler 261 | @property (nonatomic, copy) VVDBTraceError traceError; 262 | 263 | // MARK: - Error Handling 264 | /// check sqlite3 return value 265 | /// @param resultCode sqlite3 return value 266 | /// @param sql success or not 267 | - (BOOL)check:(int)resultCode sql:(NSString *)sql; 268 | 269 | /// last error code 270 | - (int)lastErrorCode; 271 | 272 | /// last error infomation 273 | - (NSError *)lastError; 274 | 275 | // MARK: - Utils 276 | 277 | /// migrating data to a new table 278 | /// @param columns columns to migrate 279 | /// @param drop drop source table 280 | - (BOOL)migrating:(NSArray *)columns 281 | from:(NSString *)fromTable 282 | to:(NSString *)toTable 283 | drop:(BOOL)drop; 284 | 285 | - (void)metadata:(nullable NSString *)dbname 286 | table:(NSString *)table 287 | column:(NSString *)column 288 | dataType:(NSString *_Nullable *_Nullable)dataType 289 | notnull:(nullable int *)notnull 290 | pk:(nullable int *)pk 291 | pkAutoInc:(nullable int *)pkAutoInc; 292 | 293 | // MARK: - cipher 294 | #ifdef SQLITE_HAS_CODEC 295 | /// sqlite3 cipher verision 296 | @property (nonatomic, copy) NSString *cipherVersion; 297 | 298 | /// set encrypt key for db 299 | /// @param key encrypt key 300 | - (BOOL)key:(NSString *)key db:(nullable NSString *)db; 301 | 302 | /// modify encrypt key 303 | /// @param key encrypt key 304 | - (BOOL)rekey:(NSString *)key db:(nullable NSString *)db; 305 | 306 | /// check for encryption, usually called when the database is opened but the encryption key is not se 307 | - (BOOL)cipherKeyCheck; 308 | #endif 309 | 310 | //MARK: - private 311 | /// sqlite3 structure 312 | @property (nonatomic, assign, readonly) sqlite3 *db; 313 | 314 | /// orm cache 315 | @property (nonatomic, strong, readonly) NSMapTable *orms; 316 | 317 | #ifdef VVSEQUELIZE_FTS 318 | @property (nonatomic, strong) NSMutableDictionary *enumerators; 319 | #endif 320 | 321 | @end 322 | 323 | NS_ASSUME_NONNULL_END 324 | -------------------------------------------------------------------------------- /VVSequelize/Core/KeyValue/NSObject+VVKeyValue.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+VVKeyValue.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/7/13. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /// coordinate -> string 14 | FOUNDATION_EXPORT NSString * NSStringFromCoordinate2D(CLLocationCoordinate2D coordinate2D); 15 | 16 | /// string -> coordinate 17 | FOUNDATION_EXPORT CLLocationCoordinate2D Coordinate2DFromString(NSString *string); 18 | 19 | @interface NSData (VVKeyValue) 20 | 21 | /// NSValue -> NSData 22 | + (instancetype)dataWithValue:(NSValue *)value; 23 | 24 | /// NSData -> NSValue 25 | + (instancetype)dataWithNumber:(NSNumber *)number; 26 | 27 | /// hex data string -> NSData 28 | + (nullable instancetype)vv_dataWithHexString:(NSString *)hexString; 29 | 30 | /// data -> hex string 31 | - (NSString *)hexString; 32 | @end 33 | 34 | @interface NSValue (VVKeyValue) 35 | 36 | /// encode NSValue as || 37 | - (NSString *)vv_encodedString; 38 | 39 | /// decode || into NSValue 40 | + (nullable instancetype)vv_decodedWithString:(NSString *)encodedString; 41 | 42 | /// NSValue -> coordinate 43 | - (CLLocationCoordinate2D)coordinate2DValue; 44 | 45 | /// coordinate -> NSValue 46 | + (instancetype)valueWithCoordinate2D:(CLLocationCoordinate2D)coordinate2D; 47 | 48 | @end 49 | 50 | @interface NSDate (VVKeyValue) 51 | 52 | /// NSDate -> "yyyy-MM-dd HH:mm:ss.SSS" 53 | - (NSString *)vv_dateString; 54 | 55 | /// "yyyy-MM-dd HH:mm:ss.SSS" -> NSDate 56 | + (instancetype)vv_dateWithString:(NSString *)dateString; 57 | 58 | @end 59 | 60 | @protocol VVKeyValue 61 | @optional 62 | /// class in Array/Set, key: array property name, value: class or name 63 | + (nullable NSDictionary *)vv_collectionMapper; 64 | 65 | /// black list 66 | + (nullable NSArray *)vv_blackProperties; 67 | 68 | /// white list 69 | + (nullable NSArray *)vv_whiteProperties; 70 | 71 | @end 72 | 73 | /// NSObject <-> NSDictionary/NSArray, used for VVDB 74 | /// @note basic converision, field mapping and white/black list is not supported, do not consider efficiency 75 | @interface NSObject (VVKeyValue) 76 | 77 | /// generate store value for db 78 | /// @return NSData/NSString/NSNumber 79 | - (nullable id)vv_dbStoreValue; 80 | 81 | /// object -> dictionary 82 | /// @note support NSSelector and C types: char, string, struct, union 83 | - (NSDictionary *)vv_keyValues; 84 | 85 | /// dictionary -> object 86 | + (instancetype)vv_objectWithKeyValues:(NSDictionary *)keyValues; 87 | 88 | /// objects -> dictionary array 89 | + (NSArray *)vv_keyValuesArrayWithObjects:(NSArray *)objects; 90 | 91 | /// dictionary array -> objects 92 | + (NSArray *)vv_objectsWithKeyValuesArray:(id)keyValuesArray; 93 | 94 | @end 95 | 96 | NS_ASSUME_NONNULL_END 97 | -------------------------------------------------------------------------------- /VVSequelize/Core/KeyValue/VVClassInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVClassInfo.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/7/17. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef NS_ENUM (NSUInteger, VVEncodingType) { 14 | VVEncodingTypeUnknown = 0, ///< unknown 15 | VVEncodingTypeVoid, ///< void 16 | VVEncodingTypeCNumber, ///< bool /char / unsigned char .... 17 | VVEncodingTypeCRealNumber, ///< float / double .. 18 | VVEncodingTypeObject, ///< id 19 | VVEncodingTypeClass, ///< Class 20 | VVEncodingTypeSEL, ///< SEL 21 | VVEncodingTypeBlock, ///< block 22 | VVEncodingTypePointer, ///< void* 23 | VVEncodingTypeStruct, ///< struct 24 | VVEncodingTypeUnion, ///< union 25 | VVEncodingTypeCString, ///< char* 26 | VVEncodingTypeCArray, ///< char[10] (for example) 27 | }; 28 | 29 | typedef NS_ENUM (NSUInteger, VVEncodingNSType) { 30 | VVEncodingTypeNSUndefined = 0, 31 | VVEncodingTypeNSString, 32 | VVEncodingTypeNSMutableString, 33 | VVEncodingTypeNSValue, 34 | VVEncodingTypeNSNumber, 35 | VVEncodingTypeNSDecimalNumber, 36 | VVEncodingTypeNSData, 37 | VVEncodingTypeNSMutableData, 38 | VVEncodingTypeNSDate, 39 | VVEncodingTypeNSURL, 40 | VVEncodingTypeNSArray, 41 | VVEncodingTypeNSMutableArray, 42 | VVEncodingTypeNSDictionary, 43 | VVEncodingTypeNSMutableDictionary, 44 | VVEncodingTypeNSSet, 45 | VVEncodingTypeNSMutableSet, 46 | VVEncodingTypeNSUnknown = 999, 47 | }; 48 | 49 | typedef NS_ENUM (NSUInteger, VVStructType) { 50 | VVStructTypeUnknown = 0, 51 | VVStructTypeNSRange, 52 | VVStructTypeCGPoint, 53 | VVStructTypeCGVector, 54 | VVStructTypeCGSize, 55 | VVStructTypeCGRect, 56 | VVStructTypeCGAffineTransform, 57 | VVStructTypeUIEdgeInsets, 58 | VVStructTypeUIOffset, 59 | VVStructTypeCLLocationCoordinate2D, 60 | VVStructTypeNSDirectionalEdgeInsets, 61 | }; 62 | 63 | FOUNDATION_EXPORT VVEncodingNSType VVClassGetNSType(Class cls); 64 | FOUNDATION_EXPORT VVStructType VVStructGetType(NSString *typeEncodeing); 65 | 66 | typedef NS_OPTIONS (NSUInteger, VVPropertyKeyword) { 67 | VVPropertyKeywordReadonly = 1 << 0, ///< readonly 68 | VVPropertyKeywordCopy = 1 << 1, ///< copy 69 | VVPropertyKeywordRetain = 1 << 2, ///< retain 70 | VVPropertyKeywordNonatomic = 1 << 3, ///< nonatomic 71 | VVPropertyKeywordWeak = 1 << 4, ///< weak 72 | VVPropertyKeywordCustomGetter = 1 << 5, ///< getter= 73 | VVPropertyKeywordCustomSetter = 1 << 6, ///< setter= 74 | VVPropertyKeywordDynamic = 1 << 7, ///< @dynamic 75 | }; 76 | 77 | typedef NS_OPTIONS (NSUInteger, VVPropertyQualifier) { 78 | VVPropertyQualifierConst = 1 << 0, ///< const 79 | VVPropertyQualifierIn = 1 << 1, ///< in 80 | VVPropertyQualifierInout = 1 << 2, ///< inout 81 | VVPropertyQualifierOut = 1 << 3, ///< out 82 | VVPropertyQualifierBycopy = 1 << 4, ///< bycopy 83 | VVPropertyQualifierByref = 1 << 5, ///< byref 84 | VVPropertyQualifierOneway = 1 << 6, ///< oneway 85 | }; 86 | 87 | @interface VVPropertyInfo : NSObject 88 | @property (nonatomic, assign, readonly) objc_property_t property; ///< property's opaque struct 89 | @property (nonatomic, strong, readonly) NSString *name; ///< property's name 90 | @property (nonatomic, strong, readonly) NSString *typeEncoding; ///< property's encoding value 91 | @property (nonatomic, strong, readonly) NSString *ivarName; ///< property's ivar name 92 | @property (nonatomic, assign, readonly) VVPropertyKeyword keyword; ///< property's keywords 93 | @property (nonatomic, assign, readonly) VVPropertyQualifier qualifier; ///< property's qualifier 94 | @property (nonatomic, assign, readonly) SEL getter; ///< getter (nonnull) 95 | @property (nonatomic, assign, readonly) SEL setter; ///< setter (nonnull) 96 | @property (nonatomic, assign, readonly) VVEncodingType type; ///< property's type 97 | @property (nonatomic, assign, readonly) VVEncodingNSType nsType; ///< property's NSType 98 | @property (nullable, nonatomic, assign, readonly) Class cls; ///< may be nil 99 | @property (nullable, nonatomic, strong, readonly) NSString *structUnionName; ///< property's struct or union name, maybe null 100 | 101 | /** 102 | Creates and returns a property info object. 103 | 104 | @param property property opaque struct 105 | @return A new object, or nil if an error occurs. 106 | */ 107 | - (instancetype)initWithProperty:(objc_property_t)property; 108 | @end 109 | 110 | @interface VVClassInfo : NSObject 111 | @property (nonatomic, copy, readonly) NSString *name; 112 | @property (nonatomic, assign, readonly) Class cls; 113 | @property (nonatomic, assign, readonly) Class metaCls; 114 | @property (nonatomic, readonly) BOOL isMeta; 115 | @property (nullable, nonatomic, strong, readonly) NSArray *properties; 116 | 117 | + (nullable instancetype)classInfoWithClass:(Class)cls; 118 | @end 119 | 120 | NS_ASSUME_NONNULL_END 121 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/NSObject+VVOrm.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | #import "VVOrmDefs.h" 4 | 5 | NS_ASSUME_NONNULL_BEGIN 6 | 7 | @interface NSObject (VVOrm) 8 | 9 | /// where clause 10 | - (NSString *)sqlWhere; 11 | 12 | /// array -> "item1","item2",... 13 | /// string: no change 14 | - (NSString *)sqlJoin; 15 | 16 | @end 17 | 18 | @interface NSDictionary (VVOrm) 19 | 20 | /// remove some keys 21 | - (NSDictionary *)vv_removeObjectsForKeys:(NSArray *)keys; 22 | 23 | @end 24 | 25 | /// generate sql clause 26 | @interface NSArray (VVOrm) 27 | 28 | /// order by clause, exluding `order by` 29 | /// array -> item1,item2,... asc 30 | - (NSString *)asc; 31 | 32 | /// order by clause, exluding `order by` 33 | /// array -> item1,item2,... desc 34 | - (NSString *)desc; 35 | 36 | /// array -> "item1","item2",... 37 | - (NSString *)sqlJoin; 38 | 39 | /// clear duplicate items 40 | - (NSArray *)vv_distinctUnionOfObjects; 41 | 42 | /// remove some items 43 | - (NSArray *)vv_removeObjectsInArray:(NSArray *)otherArray; 44 | 45 | @end 46 | 47 | /// generate sql clause 48 | @interface NSString (VVOrm) 49 | 50 | // MARK: - where 51 | /// self AND value, 52 | /// @note self: field1 = val1 53 | /// @note value: field2 = val2 54 | /// @return (field1 = val1) AND (field2 = val2) 55 | - (NSString *(^)(id value))and; 56 | 57 | /// self OR value, 58 | /// @note self: field1 = val1 59 | /// @note value: field2 = val2 60 | /// @return (field1 = val1) OR (field2 = val2) 61 | - (NSString *(^)(id value))or; 62 | 63 | /// self ON value 64 | /// @note self: table1 INNER JOIN table2 65 | /// @note value: table1.field == table2.field 66 | /// @return table1 INNER JOIN table2 ON table1.field == table2.field 67 | - (NSString *(^)(id value))on; 68 | 69 | /// self = value 70 | - (NSString *(^)(id value))eq; 71 | 72 | /// self != value 73 | - (NSString *(^)(id value))ne; 74 | 75 | /// self > value 76 | - (NSString *(^)(id value))gt; 77 | 78 | /// self >= value 79 | - (NSString *(^)(id value))gte; 80 | 81 | /// self < value 82 | - (NSString *(^)(id value))lt; 83 | 84 | /// self <= value 85 | - (NSString *(^)(id value))lte; 86 | 87 | /// IS NULL 88 | - (NSString *(^)(void))isNull; 89 | 90 | /// IS NOT NULL 91 | - (NSString *(^)(void))isNotNull; 92 | 93 | /// self BETWEEN value1,value2 94 | - (NSString *(^)(id value, id value2))between; 95 | 96 | /// self NOT BETWEEN value1,value2 97 | - (NSString *(^)(id value1, id value2))notBetween; 98 | 99 | /// self IN (val1,val2,...) 100 | - (NSString *(^)(NSArray *array))in; 101 | 102 | /// self NOT IN (val1,val2,...) 103 | - (NSString *(^)(NSArray *array))notIn; 104 | 105 | /// self LIKE value 106 | /// @note value support % and _ 107 | - (NSString *(^)(id value))like; 108 | 109 | /// self NOT LIKE value 110 | /// @note value support % and _ 111 | - (NSString *(^)(id value))notLike; 112 | 113 | /// self GLOB value 114 | /// @note value support * and ? 115 | - (NSString *(^)(id value))glob; 116 | 117 | /// self NOT GLOB value 118 | /// @note value support * and ? 119 | - (NSString *(^)(id value))notGlob; 120 | 121 | /// self MATCH value, such as: tableName match "value" 122 | - (NSString *(^)(id value))match; 123 | 124 | /// self JOIN right 125 | - (NSString *(^)(NSString *right))innerJoin; 126 | 127 | /// self LEFT OUTER JOIN right 128 | - (NSString *(^)(NSString *right))outerJoin; 129 | 130 | /// self CROSS JOIN right 131 | - (NSString *(^)(NSString *right))crossJoin; 132 | 133 | /// self.cloumn 134 | - (NSString *(^)(NSString *column))column; 135 | 136 | /// A.concat(@"=", B) -> A = B 137 | - (NSString *(^)(NSString *concat, id value))concat; 138 | 139 | /// self ASC 140 | - (NSString *)asc; 141 | 142 | /// self DESC 143 | - (NSString *)desc; 144 | 145 | // MARK: - other 146 | /// quote " : self -> "self", quote ' : self -> 'self' 147 | - (NSString *)quote:(NSString *)quote; 148 | 149 | /// self -> "self" 150 | - (NSString *)quoted; 151 | 152 | /// self -> 'self' 153 | - (NSString *)singleQuoted; 154 | 155 | /// remove quote 156 | - (NSString *)removeQuote; 157 | 158 | /// remove spaces and returns at the beginning and end of a string 159 | - (NSString *)vv_trim; 160 | 161 | /// remove duplicate spaces 162 | - (NSString *)vv_strip; 163 | 164 | /// match regular expression or not 165 | - (BOOL)isMatch:(NSString *)regex; 166 | 167 | /// pemove extra spaces, quotes, prepare for sql statement 168 | - (NSString *)prepareForParseSQL; 169 | 170 | /// generate html tag ``, use for offset() in fts3, highlight() in fts5 171 | + (NSString *)leftSpanForAttributes:(NSDictionary *)attributes; 172 | 173 | /// generate css code, use for offset() in fts3, highlight() in fts5 174 | + (NSString *)cssForAttributes:(NSDictionary *)attributes; 175 | 176 | @end 177 | 178 | NS_ASSUME_NONNULL_END 179 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/NSObject+VVOrm.m: -------------------------------------------------------------------------------- 1 | 2 | #import "NSObject+VVOrm.h" 3 | #import 4 | 5 | @interface NSNumber (VVOrm) 6 | 7 | @end 8 | 9 | @implementation NSObject (VVOrm) 10 | 11 | - (NSString *)sqlWhere 12 | { 13 | return @""; 14 | } 15 | 16 | - (NSString *)sqlJoin 17 | { 18 | return @""; 19 | } 20 | 21 | - (NSString *)sqlValue 22 | { 23 | return [[NSString stringWithFormat:@"%@", self] quote:@"\""]; 24 | } 25 | 26 | @end 27 | 28 | @implementation NSDictionary (VVOrm) 29 | - (NSString *)sqlWhere 30 | { 31 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:0]; 32 | for (NSString *key in self) { 33 | [array addObject:key.quoted.eq(self[key])]; 34 | } 35 | return [array componentsJoinedByString:@" AND "]; 36 | } 37 | 38 | - (NSDictionary *)vv_removeObjectsForKeys:(NSArray *)keys 39 | { 40 | NSMutableDictionary *dic = [self mutableCopy]; 41 | [dic removeObjectsForKeys:keys]; 42 | return dic; 43 | } 44 | 45 | @end 46 | 47 | @implementation NSArray (VVOrm) 48 | - (NSString *)sqlWhere 49 | { 50 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:0]; 51 | for (id val in self) { 52 | NSString *str = [val sqlWhere]; 53 | if (str.length > 0) { 54 | [array addObject:str]; 55 | } 56 | } 57 | return [array componentsJoinedByString:@" OR "]; 58 | } 59 | 60 | - (NSString *)asc 61 | { 62 | return [[self sqlJoin] stringByAppendingString:@" ASC"]; 63 | } 64 | 65 | - (NSString *)desc 66 | { 67 | return [[self sqlJoin] stringByAppendingString:@" DESC"]; 68 | } 69 | 70 | - (NSString *)sqlJoin 71 | { 72 | NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count]; 73 | for (NSObject *obj in self) { 74 | [array addObject:obj.sqlValue]; 75 | } 76 | return [array componentsJoinedByString:@","]; 77 | } 78 | 79 | - (NSArray *)vv_distinctUnionOfObjects 80 | { 81 | return [self valueForKeyPath:@"@distinctUnionOfObjects.self"]; 82 | } 83 | 84 | - (NSArray *)vv_removeObjectsInArray:(NSArray *)otherArray 85 | { 86 | NSMutableArray *array = [self mutableCopy]; 87 | [array removeObjectsInArray:otherArray]; 88 | return array; 89 | } 90 | 91 | @end 92 | 93 | @implementation NSNumber (VVOrm) 94 | 95 | - (NSString *)sqlValue 96 | { 97 | return self.stringValue; 98 | } 99 | 100 | @end 101 | 102 | @implementation NSString (VVOrm) 103 | 104 | - (NSString *)sqlValue 105 | { 106 | return self.quoted; 107 | } 108 | 109 | // MARK: - where 110 | - (NSString *(^)(id))and 111 | { 112 | return ^(id value) { return self.length == 0 ? [value sqlWhere] : [NSString stringWithFormat:@"%@ AND %@", self, [value sqlWhere]]; }; 113 | } 114 | 115 | - (NSString *(^)(id))or 116 | { 117 | return ^(id value) { return self.length == 0 ? [value sqlWhere] : [NSString stringWithFormat:@"(%@) OR (%@)", self, [value sqlWhere]]; }; 118 | } 119 | 120 | - (NSString *(^)(id))on 121 | { 122 | return ^(id value) { return [NSString stringWithFormat:@"%@ ON %@", self, [value sqlWhere]]; }; 123 | } 124 | 125 | - (NSString *(^)(id))eq 126 | { 127 | return ^(id value) { return [NSString stringWithFormat:@"%@ = %@", self, [value sqlValue]]; }; 128 | } 129 | 130 | - (NSString *(^)(id))ne 131 | { 132 | return ^(id value) { return [NSString stringWithFormat:@"%@ != %@", self, [value sqlValue]]; }; 133 | } 134 | 135 | - (NSString *(^)(id))gt 136 | { 137 | return ^(id value) { return [NSString stringWithFormat:@"%@ > %@", self, [value sqlValue]]; }; 138 | } 139 | 140 | - (NSString *(^)(id))gte 141 | { 142 | return ^(id value) { return [NSString stringWithFormat:@"%@ >= %@", self, [value sqlValue]]; }; 143 | } 144 | 145 | - (NSString *(^)(id))lt 146 | { 147 | return ^(id value) { return [NSString stringWithFormat:@"%@ < %@", self, [value sqlValue]]; }; 148 | } 149 | 150 | - (NSString *(^)(id))lte 151 | { 152 | return ^(id value) { return [NSString stringWithFormat:@"%@ <= %@", self, [value sqlValue]]; }; 153 | } 154 | 155 | - (NSString *(^)(void))isNull 156 | { 157 | return ^() { return [NSString stringWithFormat:@"%@ IS NULL", self]; }; 158 | } 159 | 160 | - (NSString *(^)(void))isNotNull 161 | { 162 | return ^() { return [NSString stringWithFormat:@"%@ IS NOT NULL", self]; }; 163 | } 164 | 165 | - (NSString *(^)(id, id))between 166 | { 167 | return ^(id value1, id value2) { return [NSString stringWithFormat:@"%@ BETWEEN %@ AND %@", self, [value1 sqlValue], [value2 sqlValue]]; }; 168 | } 169 | 170 | - (NSString *(^)(id, id))notBetween 171 | { 172 | return ^(id value1, id value2) { return [NSString stringWithFormat:@"%@ NOT BETWEEN %@ AND %@", self, [value1 sqlValue], [value2 sqlValue]]; }; 173 | } 174 | 175 | - (NSString *(^)(NSArray *))in 176 | { 177 | return ^(NSArray *array) { return [NSString stringWithFormat:@"%@ IN (%@)", self, [array sqlJoin]]; }; 178 | } 179 | 180 | - (NSString *(^)(NSArray *))notIn 181 | { 182 | return ^(NSArray *array) { return [NSString stringWithFormat:@"%@ NOT IN (%@)", self, [array sqlJoin]]; }; 183 | } 184 | 185 | - (NSString *(^)(id))like 186 | { 187 | return ^(id value) { return [NSString stringWithFormat:@"%@ LIKE %@", self, [value sqlValue]]; }; 188 | } 189 | 190 | - (NSString *(^)(id))notLike 191 | { 192 | return ^(id value) { return [NSString stringWithFormat:@"%@ NOT LIKE %@", self, [value sqlValue]]; }; 193 | } 194 | 195 | - (NSString *(^)(id))glob 196 | { 197 | return ^(id value) { return [NSString stringWithFormat:@"%@ GLOB %@", self, [value sqlValue]]; }; 198 | } 199 | 200 | - (NSString *(^)(id))notGlob 201 | { 202 | return ^(id value) { return [NSString stringWithFormat:@"%@ NOT GLOB %@", self, [value sqlValue]]; }; 203 | } 204 | 205 | - (NSString *(^)(id))match 206 | { 207 | return ^(id value) { return [NSString stringWithFormat:@"%@ MATCH %@", self, [value description].singleQuoted]; }; 208 | } 209 | 210 | - (NSString *(^)(NSString *))innerJoin 211 | { 212 | return ^(NSString *right) { return [NSString stringWithFormat:@"%@ JOIN %@", self, right]; }; 213 | } 214 | 215 | - (NSString *(^)(NSString *))outerJoin 216 | { 217 | return ^(NSString *right) { return [NSString stringWithFormat:@"%@ LEFT OUTER JOIN %@", self, right]; }; 218 | } 219 | 220 | - (NSString *(^)(NSString *))crossJoin 221 | { 222 | return ^(NSString *right) { return [NSString stringWithFormat:@"%@ CROSS JOIN %@", self, right]; }; 223 | } 224 | 225 | - (NSString *(^)(NSString *))column 226 | { 227 | return ^(NSString *column) { return [NSString stringWithFormat:@"%@.%@", self, column]; }; 228 | } 229 | 230 | - (NSString *(^)(NSString *, id))concat 231 | { 232 | return ^(NSString *concat, id value) { return [NSString stringWithFormat:@"%@ %@ %@", self, concat, value]; }; 233 | } 234 | 235 | - (NSString *)asc 236 | { 237 | return [self stringByAppendingString:@" ASC"]; 238 | } 239 | 240 | - (NSString *)desc 241 | { 242 | return [self stringByAppendingString:@" DESC"]; 243 | } 244 | 245 | - (NSString *)sqlWhere 246 | { 247 | return self; 248 | } 249 | 250 | - (NSString *)sqlJoin 251 | { 252 | return self; 253 | } 254 | 255 | // MARK: - other 256 | - (NSString *)quote:(NSString *)quote 257 | { 258 | if (quote.length == 0) return self; 259 | NSString *lquote = [self hasPrefix:quote] ? @"" : quote; 260 | NSString *rquote = [self hasSuffix:quote] ? @"" : quote; 261 | return [NSString stringWithFormat:@"%@%@%@", lquote, self, rquote]; 262 | } 263 | 264 | - (NSString *)quoted 265 | { 266 | return [self quote:@"\""]; 267 | } 268 | 269 | - (NSString *)singleQuoted 270 | { 271 | return [self quote:@"'"]; 272 | } 273 | 274 | - (NSString *)removeQuote 275 | { 276 | NSMutableString *result = [self mutableCopy]; 277 | [result replaceOccurrencesOfString:@"\"" withString:@"" options:0 range:NSMakeRange(0, result.length)]; 278 | [result replaceOccurrencesOfString:@"'" withString:@"" options:0 range:NSMakeRange(0, result.length)]; 279 | return result; 280 | } 281 | 282 | - (NSString *)vv_trim 283 | { 284 | return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 285 | } 286 | 287 | - (NSString *)vv_strip 288 | { 289 | static NSRegularExpression *_regex; 290 | static dispatch_once_t onceToken; 291 | dispatch_once(&onceToken, ^{ 292 | _regex = [NSRegularExpression regularExpressionWithPattern:@" +" options:0 error:nil]; 293 | }); 294 | return [_regex stringByReplacingMatchesInString:self options:0 range:NSMakeRange(0, self.length) withTemplate:@" "]; 295 | } 296 | 297 | - (BOOL)isMatch:(NSString *)regex 298 | { 299 | NSStringCompareOptions options = NSRegularExpressionSearch | NSCaseInsensitiveSearch; 300 | NSRange range = [self rangeOfString:regex options:options]; 301 | return range.location != NSNotFound; 302 | } 303 | 304 | - (NSString *)prepareForParseSQL 305 | { 306 | NSString *tmp = self.vv_trim.vv_strip; 307 | tmp = [tmp stringByReplacingOccurrencesOfString:@"'|\"" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, tmp.length)]; 308 | return tmp; 309 | } 310 | 311 | + (NSString *)leftSpanForAttributes:(NSDictionary *)attributes 312 | { 313 | NSString *css = [NSString cssForAttributes:attributes]; 314 | return [NSString stringWithFormat:@"", css.quoted]; 315 | } 316 | 317 | + (NSString *)cssForAttributes:(NSDictionary *)attributes 318 | { 319 | NSMutableAttributedString *attrText = [[NSMutableAttributedString alloc] initWithString:@"X" attributes:attributes]; 320 | NSDictionary *documentAttributes = @{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType }; 321 | NSData *htmlData = [attrText dataFromRange:NSMakeRange(0, attrText.length) documentAttributes:documentAttributes error:NULL]; 322 | NSString *htmlString = [[NSString alloc] initWithData:htmlData encoding:NSUTF8StringEncoding]; 323 | NSStringCompareOptions options = NSRegularExpressionSearch | NSCaseInsensitiveSearch; 324 | NSRange range = [htmlString rangeOfString:@"span\\.s1 *\\{.*\\}" options:options]; 325 | if (range.location == NSNotFound) { 326 | return @""; 327 | } 328 | NSString *css = [htmlString substringWithRange:range]; 329 | css = [css stringByReplacingOccurrencesOfString:@"span\\.s1 *\\{" withString:@"" options:options range:NSMakeRange(0, css.length)]; 330 | css = [css stringByReplacingOccurrencesOfString:@"\\}.*" withString:@"" options:options range:NSMakeRange(0, css.length)]; 331 | css = [css stringByReplacingOccurrencesOfString:@"'" withString:@""]; 332 | return css; 333 | } 334 | 335 | @end 336 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVForeignKey.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVForeignKey.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2020/12/25. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | typedef NS_ENUM (NSUInteger, VVForeignKeyAction) { 13 | VVForeignKeyActionNone, 14 | VVForeignKeyActionCascade, 15 | VVForeignKeyActionSetNull, 16 | VVForeignKeyActionNoAction, 17 | VVForeignKeyActionRestrict, 18 | VVForeignKeyActionSetDefault, 19 | }; 20 | 21 | @interface VVForeignKey : NSObject 22 | @property (nonatomic, copy) NSString *table; 23 | @property (nonatomic, copy) NSString *from; 24 | @property (nonatomic, copy) NSString *to; 25 | @property (nonatomic, assign) VVForeignKeyAction on_update; 26 | @property (nonatomic, assign) VVForeignKeyAction on_delete; 27 | 28 | + (instancetype)foreignKeyWithTable:(NSString *)table 29 | from:(NSString *)from 30 | to:(NSString *)to 31 | on_update:(VVForeignKeyAction)on_update 32 | on_delete:(VVForeignKeyAction)on_delete; 33 | 34 | @end 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVForeignKey.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVForeignKey.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2020/12/25. 6 | // 7 | 8 | #import "VVForeignKey.h" 9 | 10 | @implementation VVForeignKey 11 | 12 | + (instancetype)foreignKeyWithTable:(NSString *)table 13 | from:(NSString *)from 14 | to:(NSString *)to 15 | on_update:(VVForeignKeyAction)on_update 16 | on_delete:(VVForeignKeyAction)on_delete 17 | { 18 | VVForeignKey *foreignKey = [[VVForeignKey alloc] init]; 19 | foreignKey.table = table; 20 | foreignKey.from = from; 21 | foreignKey.to = to; 22 | foreignKey.on_update = on_update; 23 | foreignKey.on_delete = on_delete; 24 | return foreignKey; 25 | } 26 | 27 | - (NSString *)table 28 | { 29 | NSAssert(_table.length > 0, @"please set reference table first!"); 30 | return _table; 31 | } 32 | 33 | - (NSString *)from 34 | { 35 | NSAssert(_from.length > 0, @"please set local fields first!"); 36 | return _from; 37 | } 38 | 39 | - (NSString *)to 40 | { 41 | NSAssert(_to.length > 0, @"please set reference fields first!"); 42 | return _to; 43 | } 44 | 45 | - (BOOL)isEqual:(VVForeignKey *)other 46 | { 47 | if (other == self) { 48 | return YES; 49 | } else { 50 | return [self.table isEqualToString:other.table] 51 | && [self.from isEqualToString:other.from] 52 | && [self.to isEqualToString:other.to] 53 | && self.on_update == other.on_update 54 | && self.on_delete == other.on_delete; 55 | } 56 | } 57 | 58 | - (NSUInteger)hash 59 | { 60 | return self.table.hash ^ self.from.hash ^self.to.hash ^ @(self.on_update).hash ^ @(self.on_delete).hash; 61 | } 62 | 63 | - (NSString *)description 64 | { 65 | return [NSString stringWithFormat:@"%p: table: %@, from: %@, to: %@, on_update: %@, on_delete: %@", self, self.table, self.from, self.to, @(self.on_update), @(self.on_delete)]; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrm+Create.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm+Create.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/12. 6 | // 7 | 8 | #import "VVOrm.h" 9 | 10 | @interface VVOrm (Create) 11 | /// insert a record 12 | /// @param object object or dictionary 13 | - (BOOL)insertOne:(nonnull id)object; 14 | 15 | /// insert many records 16 | /// @param objects objects/dictionaries/mixed 17 | /// @note execute in transaction 18 | - (NSUInteger)insertMulti:(nullable NSArray *)objects; 19 | 20 | /// insert or replace a record 21 | /// @param object object or dictionary 22 | /// @note will update vv_createAt 23 | - (BOOL)upsertOne:(nonnull id)object; 24 | 25 | /// insert or replace many records 26 | /// @param objects objects/dictionaries/mixed 27 | /// @note execute in transaction, will update vv_createAt 28 | - (NSUInteger)upsertMulti:(nullable NSArray *)objects; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrm+Create.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm+Create.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/12. 6 | // 7 | 8 | #import "VVOrm+Create.h" 9 | #import "NSObject+VVKeyValue.h" 10 | #import "NSObject+VVOrm.h" 11 | #import "VVOrmView.h" 12 | 13 | @implementation VVOrm (Create) 14 | 15 | - (BOOL)_insertOne:(nonnull id)object upsert:(BOOL)upsert 16 | { 17 | [self createTableAndIndexes]; 18 | 19 | NSDictionary *dic = [object isKindOfClass:[NSDictionary class]] ? object : [object vv_keyValues]; 20 | if (!upsert && self.config.primaries.count == 1 && self.config.pkAutoInc) { 21 | dic = [dic vv_removeObjectsForKeys:self.config.primaries]; 22 | } 23 | 24 | NSMutableArray *keys = [NSMutableArray arrayWithCapacity:0]; 25 | NSMutableArray *placeholders = [NSMutableArray arrayWithCapacity:0]; 26 | NSMutableArray *values = [NSMutableArray arrayWithCapacity:0]; 27 | 28 | [dic enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { 29 | if (key && obj && [self.config.columns containsObject:key]) { 30 | [keys addObject:key.quoted]; 31 | [placeholders addObject:@"?"]; 32 | [values addObject:[obj vv_dbStoreValue]]; 33 | } 34 | }]; 35 | 36 | if (keys.count == 0) return NO; 37 | 38 | if (self.config.logAt) { 39 | NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; 40 | [keys addObject:kVVCreateAt.quoted]; 41 | [placeholders addObject:@"?"]; 42 | [values addObject:@(now)]; 43 | 44 | [keys addObject:kVVUpdateAt.quoted]; 45 | [placeholders addObject:@"?"]; 46 | [values addObject:@(now)]; 47 | } 48 | 49 | NSString *keyString = [keys componentsJoinedByString:@","]; 50 | NSString *placeholderString = [placeholders componentsJoinedByString:@","]; 51 | NSString *sql = [NSString stringWithFormat:@"%@ INTO %@ (%@) VALUES (%@)", 52 | (upsert ? @"INSERT OR REPLACE" : @"INSERT"), 53 | self.name.quoted, keyString, placeholderString]; 54 | return [self.vvdb run:sql bind:values]; 55 | } 56 | 57 | - (BOOL)insertOne:(nonnull id)object 58 | { 59 | return [self _insertOne:object upsert:NO]; 60 | } 61 | 62 | - (NSUInteger)insertMulti:(nullable NSArray *)objects 63 | { 64 | __block NSUInteger count = 0; 65 | [self.vvdb transaction:VVDBTransactionImmediate block:^BOOL { 66 | for (id obj in objects) { 67 | if ([self _insertOne:obj upsert:NO]) { count++; } 68 | } 69 | return count > 0; 70 | }]; 71 | return count; 72 | } 73 | 74 | - (BOOL)upsertOne:(nonnull id)object 75 | { 76 | return [self _insertOne:object upsert:YES]; 77 | } 78 | 79 | - (NSUInteger)upsertMulti:(NSArray *)objects 80 | { 81 | __block NSUInteger count = 0; 82 | [self.vvdb transaction:VVDBTransactionImmediate block:^BOOL { 83 | for (id obj in objects) { 84 | if ([self _insertOne:obj upsert:YES]) { count++; } 85 | } 86 | return count > 0; 87 | }]; 88 | return count; 89 | } 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrm+Delete.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm+Delete.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/12. 6 | // 7 | 8 | #import "VVOrm.h" 9 | 10 | @interface VVOrm (Delete) 11 | /// delete table 12 | /// @warning Must set orm to nil after delete. Generally, please do not delete the table!!! 13 | - (BOOL)drop; 14 | 15 | /// delete a record 16 | - (BOOL)deleteOne:(nonnull id)object; 17 | 18 | /// delete many records 19 | - (NSUInteger)deleteMulti:(nullable NSArray *)objects; 20 | 21 | /// Delete records according to conditions 22 | /// @param condition support native sql, dictionary, array 23 | /// @note native sql: all subsequent statements after `where` 24 | - (BOOL)deleteWhere:(nullable VVExpr *)condition; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrm+Delete.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm+Delete.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/12. 6 | // 7 | 8 | #import "VVOrm+Delete.h" 9 | #import "NSObject+VVOrm.h" 10 | #import "VVOrmView.h" 11 | 12 | @implementation VVOrm (Delete) 13 | - (BOOL)drop 14 | { 15 | NSString *sql = [NSString stringWithFormat:@"DROP TABLE IF EXISTS %@", self.name.quoted]; 16 | BOOL ret = [self.vvdb run:sql]; 17 | if (ret) [self markTableDropped]; 18 | return ret; 19 | } 20 | 21 | - (BOOL)deleteOne:(nonnull id)object 22 | { 23 | NSDictionary *condition = [self uniqueConditionForObject:object]; 24 | if (condition.count == 0) return NO; 25 | return [self deleteWhere:condition]; 26 | } 27 | 28 | - (NSUInteger)deleteMulti:(nullable NSArray *)objects 29 | { 30 | __block NSUInteger count = 0; 31 | [self.vvdb transaction:VVDBTransactionImmediate block:^BOOL { 32 | for (id object in objects) { 33 | BOOL ret = [self deleteOne:object]; 34 | if (ret) count++; 35 | } 36 | return count > 0; 37 | }]; 38 | return count; 39 | } 40 | 41 | - (BOOL)deleteWhere:(nullable VVExpr *)condition 42 | { 43 | NSString *where = [condition sqlWhere]; 44 | where = where.length == 0 ? @"" : [NSString stringWithFormat:@" WHERE %@", where]; 45 | 46 | NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ %@", self.name.quoted, where]; 47 | return [self.vvdb run:sql]; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrm+Retrieve.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm+Retrieve.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/12. 6 | // 7 | 8 | #import "VVOrm.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface VVOrm (Retrieve) 13 | 14 | /// query a record 15 | /// @return object 16 | - (nullable id)findOne:(nullable VVExpr *)condition; 17 | 18 | /// query a record, sorted 19 | - (nullable id)findOne:(nullable VVExpr *)condition 20 | orderBy:(nullable VVOrderBy *)orderBy; 21 | 22 | /// query records by condition 23 | /// @return [object] 24 | - (NSArray *)findAll:(nullable VVExpr *)condition; 25 | 26 | /// query records by condition, sorted 27 | - (NSArray *)findAll:(nullable VVExpr *)condition 28 | orderBy:(nullable VVOrderBy *)orderBy; 29 | 30 | /// query records by condition, sorted, in a range 31 | - (NSArray *)findAll:(nullable VVExpr *)condition 32 | orderBy:(nullable VVOrderBy *)orderBy 33 | limit:(NSUInteger)limit 34 | offset:(NSUInteger)offset; 35 | 36 | /// query grouped records 37 | - (NSArray *)findAll:(nullable VVExpr *)condition 38 | groupBy:(nullable VVGroupBy *)groupBy; 39 | 40 | /// query grouped records, in a range 41 | - (NSArray *)findAll:(nullable VVExpr *)condition 42 | groupBy:(nullable VVGroupBy *)groupBy 43 | limit:(NSUInteger)limit 44 | offset:(NSUInteger)offset; 45 | 46 | /// query records by condition, 47 | /// @param condition query condtiion. 48 | /// 1.NSString: native sql, all subsequent statements after `where`; 49 | /// 2.NSDictionary: key and value are connected with '=', different key values are connected with 'and'; 50 | /// 3.NSArray: [dictionary], Each dictionary is connected with 'or' 51 | /// @param distinct clear duplicate records or not 52 | /// @param fields specifies the query fields 53 | /// 1. string: `"field1","field2",...`, `count(*) as count`, ... 54 | /// 2. array: ["field1","field2",...] 55 | /// @param groupBy group method 56 | /// 1. string: "field1","field2",... 57 | /// 2. array: ["field1","field2",...] 58 | /// @param having filter group, same as condition 59 | /// @param orderBy sort method 60 | /// 1. string: "field1 asc","field1,field2 desc","field1 asc,field2,field3 desc",... 61 | /// 2. array: ["field1 asc","field2,field3 desc",...] 62 | /// @param limit limit of results, 0 without limit 63 | /// @param offset start position 64 | /// @return [object] or [dictionary] 65 | - (NSArray *)findAll:(nullable VVExpr *)condition 66 | distinct:(BOOL)distinct 67 | fields:(nullable VVFields *)fields 68 | groupBy:(nullable VVGroupBy *)groupBy 69 | having:(nullable VVExpr *)having 70 | orderBy:(nullable VVOrderBy *)orderBy 71 | limit:(NSUInteger)limit 72 | offset:(NSUInteger)offset; 73 | 74 | /// records number by condition 75 | - (NSInteger)count:(nullable VVExpr *)condition; 76 | 77 | /// chech if object exists 78 | - (BOOL)isExist:(nonnull id)object; 79 | 80 | /// query records by condition 81 | /// @return {"count":100,list:[object]} 82 | - (NSDictionary *)findAndCount:(nullable VVExpr *)condition 83 | orderBy:(nullable VVOrderBy *)orderBy 84 | limit:(NSUInteger)limit 85 | offset:(NSUInteger)offset; 86 | 87 | /// query `max(rowid)` 88 | /// @discussion `max(rowid)` is uniqued, use `max(rowid) + 1` as the primary key of the next record. 89 | - (NSUInteger)maxRowid; 90 | 91 | /// get the maximum value of a field 92 | - (id)max:(nonnull NSString *)field condition:(nullable VVExpr *)condition; 93 | 94 | /// get the minimum value of a field 95 | - (id)min:(nonnull NSString *)field condition:(nullable VVExpr *)condition; 96 | 97 | /// get the summary value of a field 98 | - (id)sum:(nonnull NSString *)field condition:(nullable VVExpr *)condition; 99 | 100 | @end 101 | 102 | NS_ASSUME_NONNULL_END 103 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrm+Retrieve.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm+Retrieve.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/12. 6 | // 7 | 8 | #import "VVOrm+Retrieve.h" 9 | #import "NSObject+VVOrm.h" 10 | #import "VVSelect.h" 11 | 12 | @implementation VVOrm (Retrieve) 13 | 14 | - (id)findOne:(nullable VVExpr *)condition 15 | { 16 | VVSelect *select = VVSelect.new.orm(self).where(condition).limit(1); 17 | return [select allObjects].firstObject; 18 | } 19 | 20 | - (id)findOne:(nullable VVExpr *)condition 21 | orderBy:(nullable VVOrderBy *)orderBy 22 | { 23 | VVSelect *select = VVSelect.new.orm(self).where(condition).orderBy(orderBy).limit(1); 24 | return [select allObjects].firstObject; 25 | } 26 | 27 | - (NSArray *)findAll:(nullable VVExpr *)condition 28 | { 29 | VVSelect *select = VVSelect.new.orm(self).where(condition); 30 | return [select allObjects]; 31 | } 32 | 33 | - (NSArray *)findAll:(nullable VVExpr *)condition 34 | orderBy:(nullable VVOrderBy *)orderBy 35 | { 36 | VVSelect *select = VVSelect.new.orm(self).where(condition).orderBy(orderBy); 37 | return [select allObjects]; 38 | } 39 | 40 | - (NSArray *)findAll:(nullable VVExpr *)condition 41 | orderBy:(nullable VVOrderBy *)orderBy 42 | limit:(NSUInteger)limit 43 | offset:(NSUInteger)offset 44 | { 45 | VVSelect *select = VVSelect.new.orm(self).where(condition).orderBy(orderBy).offset(offset).limit(limit); 46 | return [select allObjects]; 47 | } 48 | 49 | - (NSArray *)findAll:(nullable VVExpr *)condition 50 | groupBy:(nullable VVGroupBy *)groupBy 51 | { 52 | VVSelect *select = VVSelect.new.orm(self).where(condition).groupBy(groupBy); 53 | return [select allObjects]; 54 | } 55 | 56 | - (NSArray *)findAll:(nullable VVExpr *)condition 57 | groupBy:(nullable VVGroupBy *)groupBy 58 | limit:(NSUInteger)limit 59 | offset:(NSUInteger)offset 60 | { 61 | VVSelect *select = VVSelect.new.orm(self).where(condition).groupBy(groupBy).offset(offset).limit(limit); 62 | return [select allObjects]; 63 | } 64 | 65 | - (NSArray *)findAll:(nullable VVExpr *)condition 66 | distinct:(BOOL)distinct 67 | fields:(nullable VVFields *)fields 68 | groupBy:(nullable VVGroupBy *)groupBy 69 | having:(nullable VVExpr *)having 70 | orderBy:(nullable VVOrderBy *)orderBy 71 | limit:(NSUInteger)limit 72 | offset:(NSUInteger)offset 73 | { 74 | VVSelect *select = VVSelect.new.orm(self).where(condition).distinct(distinct).fields(fields) 75 | .groupBy(groupBy).having(having).orderBy(orderBy) 76 | .offset(offset).limit(limit); 77 | return [select allObjects]; 78 | } 79 | 80 | - (NSInteger)count:(nullable VVExpr *)condition 81 | { 82 | return [[self calc:@"*" method:@"count" condition:condition] unsignedIntegerValue]; 83 | } 84 | 85 | - (BOOL)isExist:(id)object 86 | { 87 | NSDictionary *condition = [self uniqueConditionForObject:object]; 88 | if (condition.count == 0) return NO; 89 | return [self count:condition] > 0; 90 | } 91 | 92 | - (NSDictionary *)findAndCount:(nullable VVExpr *)condition 93 | orderBy:(nullable VVOrderBy *)orderBy 94 | limit:(NSUInteger)limit 95 | offset:(NSUInteger)offset 96 | { 97 | NSUInteger count = [self count:condition]; 98 | NSArray *array = [self findAll:condition orderBy:orderBy limit:limit offset:offset]; 99 | return @{ @"count": @(count), @"list": array }; 100 | } 101 | 102 | - (NSUInteger)maxRowid 103 | { 104 | return [[self max:@"rowid" condition:nil] unsignedIntegerValue]; 105 | } 106 | 107 | - (id)max:(NSString *)field condition:(nullable VVExpr *)condition 108 | { 109 | return [self calc:field method:@"max" condition:condition]; 110 | } 111 | 112 | - (id)min:(NSString *)field condition:(nullable VVExpr *)condition 113 | { 114 | return [self calc:field method:@"min" condition:condition]; 115 | } 116 | 117 | - (id)sum:(NSString *)field condition:(nullable VVExpr *)condition 118 | { 119 | return [self calc:field method:@"sum" condition:condition]; 120 | } 121 | 122 | - (id)calc:(NSString *)field method:(NSString *)method condition:(nullable VVExpr *)condition 123 | { 124 | if (!([method isEqualToString:@"max"] 125 | || [method isEqualToString:@"min"] 126 | || [method isEqualToString:@"sum"] 127 | || [method isEqualToString:@"count"])) return nil; 128 | NSString *fields = [NSString stringWithFormat:@"%@(%@) AS %@", method, field.quoted, method]; 129 | VVSelect *select = VVSelect.new.orm(self).where(condition).fields(fields); 130 | NSArray *array = [select allKeyValues]; 131 | NSDictionary *dic = array.firstObject; 132 | id result = dic[method]; 133 | return [result isKindOfClass:NSNull.class] ? nil : result; 134 | } 135 | 136 | @end 137 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrm+Update.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm+Update.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/12. 6 | // 7 | 8 | #import "VVOrm.h" 9 | NS_ASSUME_NONNULL_BEGIN 10 | 11 | @interface VVOrm (Update) 12 | 13 | /// update records by condition 14 | /// @param condition update condtiion. 15 | /// 1.NSString: native sql, all subsequent statements after `where`; 16 | /// 2.NSDictionary: key and value are connected with '=', different key values are connected with 'and'; 17 | /// 3.NSArray: [dictionary], Each dictionary is connected with 'or' 18 | /// @param keyValues {field1:value1, field2:value2,...} 19 | - (BOOL)update:(nullable VVExpr *)condition keyValues:(NSDictionary *)keyValues; 20 | 21 | /// updat a record, failure will not insert new record 22 | - (BOOL)updateOne:(nonnull id)object; 23 | 24 | /// updat a record, failure will not insert new record,limit update fields 25 | - (BOOL)updateOne:(nonnull id)object fields:(nullable NSArray *)fields; 26 | 27 | /// updat many records, failure will not insert new record,limit update fields, use transaction 28 | - (NSUInteger)updateMulti:(nullable NSArray *)objects fields:(nullable NSArray *)fields; 29 | 30 | /// updat many records, failure will not insert new record, use transaction 31 | - (NSUInteger)updateMulti:(nullable NSArray *)objects; 32 | 33 | /// Add a value to a field 34 | - (BOOL)increase:(nullable VVExpr *)condition 35 | field:(nonnull NSString *)field 36 | value:(NSInteger)value; 37 | 38 | @end 39 | 40 | NS_ASSUME_NONNULL_END 41 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrm+Update.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm+Update.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/12. 6 | // 7 | 8 | #import "VVOrm+Update.h" 9 | #import "NSObject+VVKeyValue.h" 10 | #import "NSObject+VVOrm.h" 11 | #import "VVOrmView.h" 12 | 13 | @implementation VVOrm (Update) 14 | 15 | - (BOOL)_update:(nullable VVExpr *)condition keyValues:(NSDictionary *)keyValues 16 | { 17 | [self createTableAndIndexes]; 18 | 19 | NSString *where = [condition sqlWhere]; 20 | where = where.length == 0 ? @"" : [NSString stringWithFormat:@" WHERE %@", where]; 21 | 22 | NSMutableArray *sets = [NSMutableArray arrayWithCapacity:0]; 23 | NSMutableArray *vals = [NSMutableArray arrayWithCapacity:0]; 24 | 25 | [keyValues enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { 26 | if (key && obj && [self.config.columns containsObject:key]) { 27 | NSString *tmp = [NSString stringWithFormat:@"%@ = ?", key.quoted]; 28 | [sets addObject:tmp]; 29 | [vals addObject:[obj vv_dbStoreValue]]; 30 | } 31 | }]; 32 | 33 | if (sets.count == 0) return NO; 34 | 35 | if (self.config.logAt) { 36 | NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; 37 | NSString *tmp = [NSString stringWithFormat:@"%@ = ?", kVVUpdateAt.quoted]; 38 | [sets addObject:tmp]; 39 | [vals addObject:@(now)]; 40 | } 41 | 42 | NSString *setString = [sets componentsJoinedByString:@","]; 43 | NSString *sql = [NSString stringWithFormat:@"UPDATE %@ SET %@ %@", self.name.quoted, setString, where]; 44 | return [self.vvdb run:sql bind:vals]; 45 | } 46 | 47 | - (BOOL)_updateOne:(id)object fields:(nullable NSArray *)fields 48 | { 49 | NSDictionary *condition = [self uniqueConditionForObject:object]; 50 | if (condition.count == 0) return NO; 51 | NSDictionary *dic = [object isKindOfClass:[NSDictionary class]] ? object : [object vv_keyValues]; 52 | NSMutableDictionary *keyValues = nil; 53 | if (fields.count == 0) { 54 | keyValues = dic.mutableCopy; 55 | } else { 56 | keyValues = [NSMutableDictionary dictionaryWithCapacity:fields.count]; 57 | for (NSString *field in fields) { 58 | keyValues[field] = dic[field]; 59 | } 60 | } 61 | if (keyValues.count == 0) return NO; 62 | return [self _update:condition keyValues:keyValues]; 63 | } 64 | 65 | - (BOOL)update:(nullable VVExpr *)condition keyValues:(NSDictionary *)keyValues 66 | { 67 | return [self _update:condition keyValues:keyValues]; 68 | } 69 | 70 | - (BOOL)updateOne:(id)object 71 | { 72 | return [self _updateOne:object fields:nil]; 73 | } 74 | 75 | - (BOOL)updateOne:(id)object fields:(nullable NSArray *)fields 76 | { 77 | return [self _updateOne:object fields:fields]; 78 | } 79 | 80 | - (NSUInteger)updateMulti:(NSArray *)objects 81 | { 82 | return [self updateMulti:objects fields:nil]; 83 | } 84 | 85 | - (NSUInteger)updateMulti:(NSArray *)objects fields:(nullable NSArray *)fields 86 | { 87 | __block NSUInteger count = 0; 88 | [self.vvdb transaction:VVDBTransactionImmediate block:^BOOL { 89 | for (id object in objects) { 90 | if ([self _updateOne:object fields:fields]) { count++; } 91 | } 92 | return count > 0; 93 | }]; 94 | return count; 95 | } 96 | 97 | - (BOOL)increase:(nullable VVExpr *)condition 98 | field:(NSString *)field 99 | value:(NSInteger)value 100 | { 101 | if (value == 0) { 102 | return YES; 103 | } 104 | NSMutableString *setString = [NSMutableString stringWithFormat:@"%@ = %@%@%@", 105 | field.quoted, field.quoted, value > 0 ? @"+" : @"-", @(ABS(value))]; 106 | if (self.config.logAt) { 107 | NSTimeInterval now = [[NSDate date] timeIntervalSince1970]; 108 | [setString appendFormat:@",%@ = %@", kVVUpdateAt.quoted, @(now).stringValue.quoted]; 109 | } 110 | 111 | NSString *where = [condition sqlWhere]; 112 | where = where.length == 0 ? @"" : [NSString stringWithFormat:@" WHERE %@", where]; 113 | 114 | NSString *sql = [NSString stringWithFormat:@"UPDATE %@ SET %@ %@", self.name.quoted, setString, where]; 115 | return [self.vvdb run:sql]; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrm.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/6/6. 6 | // 7 | 8 | #import 9 | #import "VVOrmDefs.h" 10 | #import "VVDatabase.h" 11 | #import "VVOrmConfig.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | typedef NS_ENUM (NSUInteger, VVOrmSetup) { 16 | VVOrmSetupNoCreation = 0, 17 | VVOrmSetupCreate, 18 | VVOrmSetupRebuild, 19 | }; 20 | 21 | /// Object Relational Mapping 22 | @interface VVOrm : NSObject 23 | /// orm configration 24 | @property (nonatomic, strong, readonly) VVOrmConfig *config; 25 | /// databse 26 | @property (nonatomic, strong, readonly) VVDatabase *vvdb; 27 | /// table name in VVOrm , view name in VVOrmView 28 | @property (nonatomic, copy, readonly) NSString *name; 29 | /// the table has been created 30 | @property (nonatomic, assign, readonly) BOOL created; 31 | /// class of queried objects 32 | @property (nonatomic, strong) Class metaClass; 33 | 34 | - (instancetype)init __attribute__((unavailable("use initWithConfig:name:database: instead."))); 35 | + (instancetype)new __attribute__((unavailable("use initWithConfig:name:database: instead."))); 36 | 37 | /// Initialize orm, auto create/modify defalut table, use temporary db. 38 | /// @param config orm configuration 39 | + (nullable instancetype)ormWithConfig:(VVOrmConfig *)config; 40 | 41 | /// Initialize orm, auto create/modify table and indexes, check and create table immediately 42 | /// @param config orm configuration 43 | /// @param name table name, nil means to use class name 44 | /// @param vvdb db, nil means to use temporary db 45 | + (nullable instancetype)ormWithConfig:(VVOrmConfig *)config 46 | name:(nullable NSString *)name 47 | database:(nullable VVDatabase *)vvdb; 48 | 49 | /// Initialize orm, auto create/modify table and indexes 50 | /// @param config orm configuration 51 | /// @param name table name, nil means to use class name 52 | /// @param vvdb db, nil means to use temporary db 53 | /// @param setup check and create table or not 54 | + (nullable instancetype)ormWithConfig:(VVOrmConfig *)config 55 | name:(nullable NSString *)name 56 | database:(nullable VVDatabase *)vvdb 57 | setup:(VVOrmSetup)setup; 58 | 59 | /// Initialize fts orm 60 | /// @param config orm configuration 61 | /// @param name table name, nil means to use class name 62 | /// @param vvdb db, nil means to use temporary db 63 | /// @param content_table extenal content table 64 | /// @param content_rowid relative content_rowid 65 | /// @param setup check and create table or not 66 | + (nullable instancetype)ormWithConfig:(VVOrmConfig *)config 67 | name:(nullable NSString *)name 68 | database:(nullable VVDatabase *)vvdb 69 | content_table:(nullable NSString *)content_table 70 | content_rowid:(nullable NSString *)content_rowid 71 | setup:(VVOrmSetup)setup; 72 | 73 | /// Initialize fts orm 74 | /// @param config orm configuration 75 | /// @param relativeORM relative universal orm 76 | /// @param content_rowid relative content_rowid 77 | + (nullable instancetype)ormWithConfig:(VVOrmConfig *)config 78 | relative:(VVOrm *)relativeORM 79 | content_rowid:(nullable NSString *)content_rowid; 80 | 81 | /// Initialize orm, do not create/modify table and indexes 82 | /// @param config orm configuration 83 | /// @param name table name, nil means to use class name 84 | /// @param vvdb db, nil means to use temporary db 85 | /// @attention call `inspectExistingTable` and `setupTableWith:` in turns to create/modify table and indexes. 86 | - (nullable instancetype)initWithConfig:(VVOrmConfig *)config 87 | name:(nullable NSString *)name 88 | database:(nullable VVDatabase *)vvdb NS_DESIGNATED_INITIALIZER; 89 | 90 | /// create table manually 91 | - (BOOL)createTableAndIndexes; 92 | 93 | ///compare and update table structures 94 | - (void)rebuildTableAndIndexes; 95 | 96 | /// rebuild indexes 97 | - (BOOL)rebuildIndexes; 98 | 99 | /// mark this table has been dropped 100 | - (void)markTableDropped; 101 | 102 | /// get unique condition, use to update/delete 103 | - (nullable NSDictionary *)uniqueConditionForObject:(id)object; 104 | @end 105 | 106 | NS_ASSUME_NONNULL_END 107 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrmConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrmConfig.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/10. 6 | // 7 | 8 | #import 9 | /// record create time 10 | FOUNDATION_EXPORT NSString *const kVVCreateAt; 11 | /// record update time 12 | FOUNDATION_EXPORT NSString *const kVVUpdateAt; 13 | 14 | @class VVDatabase,VVForeignKey; 15 | /// ORM configuration 16 | @interface VVOrmConfig : NSObject 17 | /// corresponding model, objective-c class 18 | @property (nonatomic, strong) Class cls; 19 | /// record time or not, default is NO 20 | @property (nonatomic, assign) BOOL logAt; 21 | 22 | //MARK: fts table configuration 23 | /// fts table or not, default is NO 24 | @property (nonatomic, assign) BOOL fts; 25 | /// fts tokenize module, fts3,fts4,fts5; default is fts5 26 | @property (nonatomic, copy) NSString *ftsModule; 27 | /// fts tokenizer, such as: porter, unicode61, icu,... 28 | @property (nonatomic, copy) NSString *ftsTokenizer; 29 | 30 | //MARK: table parameters 31 | /// self-increasing primary key or not 32 | @property (nonatomic, assign) BOOL pkAutoInc; 33 | /// all field names 34 | @property (nonatomic, strong) NSArray *columns; 35 | /// fields of primary keys 36 | @property (nonatomic, strong) NSArray *primaries; 37 | /// white list 38 | @property (nonatomic, strong) NSArray *whiteList; 39 | /// black list, if white list exists, use white list 40 | @property (nonatomic, strong) NSArray *blackList; 41 | /// non null constraints 42 | @property (nonatomic, strong) NSArray *notnulls; 43 | /// unique constraints 44 | @property (nonatomic, strong) NSArray *uniques; 45 | /// index fields 46 | @property (nonatomic, strong) NSArray *indexes; 47 | /// field type mapping,{field:type} 48 | @property (nonatomic, strong) NSDictionary *types; 49 | /// field default values; {field: value}: value can be NSString,NSNumber,NSData 50 | @property (nonatomic, strong) NSDictionary *defaultValues; 51 | 52 | #ifdef VVSEQUELIZE_CONSTRAINTS 53 | /// foreign keys 54 | @property (nonatomic, strong) NSArray *foreignKeys; 55 | /// check constraint; [expression]: expression is similar to `field > 0` 56 | @property (nonatomic, strong) NSArray *checks; 57 | #endif 58 | 59 | //MARK: read-only properties 60 | /// fts version 61 | @property (nonatomic, assign, readonly) NSUInteger ftsVersion; 62 | /// generated by table or not 63 | @property (nonatomic, assign, readonly) BOOL fromTable; 64 | 65 | //MARK: - Public 66 | /// create configuration from an existing table 67 | + (instancetype)configFromTable:(NSString *)tableName 68 | database:(VVDatabase *)vvdb; 69 | 70 | /// create configuration from a objective-c class 71 | + (instancetype)configWithClass:(Class)cls; 72 | 73 | /// create fts configuration from a objective-c class 74 | /// @param cls objective-c class 75 | /// @param indexes fields requiring full-text indexing, valid only in versions above fts4 76 | + (instancetype)ftsConfigWithClass:(Class)cls 77 | module:(NSString *)module 78 | tokenizer:(NSString *)tokenizer 79 | indexes:(NSArray *)indexes; 80 | 81 | /// duplication, deal with black and white list, etc. 82 | - (void)treate; 83 | 84 | /// compare with an other configuratioin 85 | - (BOOL)isEqualToConfig:(VVOrmConfig *)config; 86 | 87 | /// compare indexes with an other configuration 88 | - (BOOL)isInedexesEqual:(VVOrmConfig *)config; 89 | 90 | /// alert new column 91 | - (NSString *)alertSQLOfColumn:(NSString *)column table:(NSString *)tableName; 92 | 93 | /// generate common table SQL statement 94 | - (NSString *)createSQLWith:(NSString *)tableName; 95 | 96 | /// generate fts table SQL statement 97 | - (NSString *)createFtsSQLWith:(NSString *)tableName 98 | content_table:(NSString *)content_table 99 | content_rowid:(NSString *)content_rowid; 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrmDefs.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrmDefs.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/4/2. 6 | // 7 | 8 | #import 9 | 10 | #ifndef $ 11 | #define $(field) NSStringFromSelector(@selector(field)) 12 | #endif 13 | 14 | /// query expression, where/having sub statement 15 | /// 1.NSString: native sql, all subsequent statements after `where`; 16 | /// 2.NSDictionary: key and value are connected with '=', different key values are connected with 'and'; 17 | /// 3.NSArray: [dictionary], Each dictionary is connected with 'or' 18 | typedef NSObject VVExpr; 19 | 20 | // specify the fields 21 | // NSString: `"field1","field2",...`, `count(*) as count`, ... 22 | // NSArray: ["field1","field2",...] 23 | typedef NSObject VVFields; 24 | 25 | // sort expression 26 | // NSString: "field1 asc", "field1,field2 desc", "field1 asc,field2,field3 desc", ... 27 | // NSArray: ["field1 asc","field2,field3 desc",...] 28 | typedef NSObject VVOrderBy; 29 | 30 | // group expression 31 | // NSString: "field1","field2",... 32 | // NSArray: ["field1","field2",...] 33 | typedef NSObject VVGroupBy; 34 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrmView.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrmView.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2020/4/23. 6 | // 7 | 8 | #import "VVOrm.h" 9 | #import "VVOrm+Create.h" 10 | #import "VVOrm+Delete.h" 11 | #import "VVOrm+Update.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface VVOrmView : VVOrm 16 | @property (nonatomic, strong) VVExpr *condition; 17 | @property (nonatomic, assign) BOOL temporary; 18 | @property (nonatomic, strong) NSArray *columns; 19 | 20 | @property (nonatomic, assign, readonly) BOOL exist; 21 | 22 | /// init view 23 | /// @param viewName view name 24 | /// @param orm source table orm 25 | /// @param condition view condition 26 | /// @param temporary temporary view or not 27 | /// @param columns specify columns for view 28 | - (instancetype)initWithName:(NSString *)viewName 29 | orm:(VVOrm *)orm 30 | condition:(VVExpr *)condition 31 | temporary:(BOOL)temporary 32 | columns:(nullable NSArray *)columns; 33 | 34 | /// create view 35 | - (BOOL)createView; 36 | 37 | /// drop view 38 | - (BOOL)dropView; 39 | 40 | /// drop old view and create new view 41 | - (BOOL)recreateView; 42 | 43 | //MARK: - UNAVAILABLE 44 | 45 | #define VVORMVIEW_UNAVAILABLE __attribute__((unavailable("This method is not supported by VVOrmView."))) 46 | 47 | //MARK: Setup 48 | 49 | - (void)createTable VVORMVIEW_UNAVAILABLE; 50 | 51 | - (void)rebuildTable VVORMVIEW_UNAVAILABLE; 52 | 53 | //MARK: Create 54 | - (BOOL)insertOne:(nonnull id)object VVORMVIEW_UNAVAILABLE; 55 | 56 | - (NSUInteger)insertMulti:(nullable NSArray *)objects VVORMVIEW_UNAVAILABLE; 57 | 58 | - (BOOL)upsertOne:(nonnull id)object VVORMVIEW_UNAVAILABLE; 59 | 60 | - (NSUInteger)upsertMulti:(nullable NSArray *)objects VVORMVIEW_UNAVAILABLE; 61 | 62 | //MARK: Delete 63 | - (BOOL)drop VVORMVIEW_UNAVAILABLE; 64 | 65 | - (BOOL)deleteOne:(nonnull id)object VVORMVIEW_UNAVAILABLE; 66 | 67 | - (NSUInteger)deleteMulti:(nullable NSArray *)objects VVORMVIEW_UNAVAILABLE; 68 | 69 | - (BOOL)deleteWhere:(nullable VVExpr *)condition VVORMVIEW_UNAVAILABLE; 70 | 71 | //MARK: Update 72 | - (BOOL)update:(nullable VVExpr *)condition keyValues:(NSDictionary *)keyValues VVORMVIEW_UNAVAILABLE; 73 | 74 | - (BOOL)updateOne:(nonnull id)object VVORMVIEW_UNAVAILABLE; 75 | 76 | - (BOOL)updateOne:(nonnull id)object fields:(nullable NSArray *)fields VVORMVIEW_UNAVAILABLE; 77 | 78 | - (NSUInteger)updateMulti:(nullable NSArray *)objects fields:(nullable NSArray *)fields VVORMVIEW_UNAVAILABLE; 79 | 80 | - (NSUInteger)updateMulti:(nullable NSArray *)objects VVORMVIEW_UNAVAILABLE; 81 | 82 | - (BOOL)increase:(nullable VVExpr *)condition 83 | field:(nonnull NSString *)field 84 | value:(NSInteger)value VVORMVIEW_UNAVAILABLE; 85 | 86 | @end 87 | 88 | NS_ASSUME_NONNULL_END 89 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrmView.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrmView.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2020/4/23. 6 | // 7 | 8 | #import "VVOrmView.h" 9 | #import "NSObject+VVOrm.h" 10 | 11 | @interface VVOrmView () 12 | /// source table name 13 | @property (nonatomic, copy) NSString *sourceTable; 14 | @end 15 | 16 | @implementation VVOrmView 17 | 18 | @synthesize name = _name; 19 | 20 | + (nullable instancetype)ormWithConfig:(VVOrmConfig *)config { 21 | return [self ormWithConfig:config tableName:nil dataBase:nil]; 22 | } 23 | 24 | + (nullable instancetype)ormWithConfig:(VVOrmConfig *)config 25 | tableName:(nullable NSString *)tableName 26 | dataBase:(nullable VVDatabase *)vvdb 27 | { 28 | return [[VVOrmView alloc] initWithConfig:config name:tableName database:vvdb]; 29 | } 30 | 31 | - (instancetype)initWithName:(NSString *)name 32 | orm:(VVOrm *)orm 33 | condition:(VVExpr *)condition 34 | temporary:(BOOL)temporary 35 | columns:(nullable NSArray *)columns 36 | { 37 | self = [super initWithConfig:orm.config name:orm.name database:orm.vvdb]; 38 | if (self) { 39 | _sourceTable = orm.name; 40 | _name = name; 41 | _condition = condition; 42 | _temporary = temporary; 43 | _columns = columns; 44 | } 45 | return self; 46 | } 47 | 48 | - (instancetype)initWithConfig:(VVOrmConfig *)config name:(NSString *)name database:(VVDatabase *)database 49 | { 50 | self = [super initWithConfig:config name:name database:database]; 51 | if (self) { 52 | _sourceTable = name; 53 | _name = @""; 54 | } 55 | return self; 56 | } 57 | 58 | //MARK: - public 59 | - (BOOL)exist 60 | { 61 | NSAssert(_name.length > 0, @"Please set view name first!"); 62 | NSString *sql = [NSString stringWithFormat:@"SELECT count(*) as 'count' FROM sqlite_master WHERE type ='view' and tbl_name = %@", _name.quoted]; 63 | return [[self.vvdb scalar:sql bind:nil] boolValue]; 64 | } 65 | 66 | - (BOOL)createView 67 | { 68 | NSString *where = [_condition sqlWhere]; 69 | NSAssert(_name.length > 0 && where.length > 0, @"Please set view name and condition first!"); 70 | 71 | NSArray *cols = nil; 72 | if (_columns.count > 0) { 73 | NSSet *all = [NSSet setWithArray:self.config.columns]; 74 | NSMutableSet *set = [NSMutableSet setWithArray:_columns]; 75 | [set intersectSet:all]; 76 | cols = set.allObjects; 77 | } 78 | 79 | NSString *sql = [NSString stringWithFormat: 80 | @"CREATE %@ VIEW %@ AS " 81 | "SELECT %@ " 82 | "FROM %@" 83 | "WHERE %@", 84 | (_temporary ? @"TEMP" : @""), _name.quoted, 85 | (cols.count > 0 ? cols.sqlJoin : @"*"), 86 | _sourceTable.quoted, 87 | where]; 88 | 89 | return [self.vvdb run:sql]; 90 | } 91 | 92 | - (BOOL)dropView 93 | { 94 | NSString *sql = [NSString stringWithFormat:@"DROP VIEW %@", _name.quoted]; 95 | return [self.vvdb run:sql]; 96 | } 97 | 98 | - (BOOL)recreateView 99 | { 100 | BOOL ret = YES; 101 | if (self.exist) { 102 | ret = [self dropView]; 103 | } 104 | if (ret) { 105 | ret = [self createView]; 106 | } 107 | return ret; 108 | } 109 | 110 | //MAKR: - UNAVAILABLE 111 | - (void)createTable 112 | { 113 | } 114 | 115 | - (void)rebuildTable 116 | { 117 | } 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrmable.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrmable.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2020/7/29. 6 | // 7 | 8 | #import 9 | #import "VVOrm.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class VVForeignKey; 14 | @protocol VVOrmable 15 | @optional 16 | + (NSArray *)primaries; 17 | + (NSArray *)whites; 18 | + (NSArray *)blacks; 19 | + (NSArray *)indexes; 20 | + (NSArray *)notnulls; 21 | + (NSArray *)uniques; 22 | + (BOOL)logAt; 23 | + (BOOL)pkAutoInc; 24 | 25 | /// {field: value}: value can be NSString,NSNumber,NSData 26 | + (NSDictionary *)defaultValues; 27 | 28 | #ifdef VVSEQUELIZE_CONSTRAINTS 29 | /// foreign keys 30 | + (NSArray *)foreignKeys; 31 | 32 | /// [expression]: expression is similar to `field > 0` 33 | + (NSArray *)checks; 34 | #endif 35 | 36 | @end 37 | 38 | @interface VVOrmConfig (VVOrmable) 39 | 40 | + (instancetype)configWithOrmable:(Class)cls; 41 | 42 | @end 43 | 44 | @interface VVOrm (VVOrmable) 45 | /// Initialize orm, auto create/modify defalut table, use temporary db. 46 | /// @param clazz class confirm to VVOrmable 47 | + (nullable instancetype)ormWithClass:(Class)clazz; 48 | 49 | /// Initialize orm, auto create/modify table and indexes, check and create table immediately 50 | /// @param clazz class confirm to VVOrmable 51 | /// @param name table name, nil means to use class name 52 | /// @param vvdb db, nil means to use temporary db 53 | + (nullable instancetype)ormWithClass:(Class)clazz 54 | name:(nullable NSString *)name 55 | database:(nullable VVDatabase *)vvdb; 56 | 57 | /// Initialize orm, auto create/modify table and indexes 58 | /// @param clazz class confirm to VVOrmable 59 | /// @param name table name, nil means to use class name 60 | /// @param vvdb db, nil means to use temporary db 61 | /// @param setup check and create table or not 62 | + (nullable instancetype)ormWithClass:(Class)clazz 63 | name:(nullable NSString *)name 64 | database:(nullable VVDatabase *)vvdb 65 | setup:(VVOrmSetup)setup; 66 | 67 | @end 68 | 69 | NS_ASSUME_NONNULL_END 70 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVOrmable.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrmable.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2020/7/29. 6 | // 7 | 8 | #import "VVOrmable.h" 9 | #import "VVForeignKey.h" 10 | 11 | @implementation VVOrmConfig (VVOrmable) 12 | 13 | + (instancetype)configWithOrmable:(Class)cls 14 | { 15 | NSAssert([cls conformsToProtocol:@protocol(VVOrmable)], @"class must confroms to VVOrmable"); 16 | VVOrmConfig *config = [VVOrmConfig configWithClass:cls]; 17 | if ([cls respondsToSelector:@selector(primaries)]) { 18 | config.primaries = [cls primaries] ? : @[]; 19 | } 20 | if ([cls respondsToSelector:@selector(whites)]) { 21 | config.whiteList = [cls whites] ? : @[]; 22 | } 23 | if ([cls respondsToSelector:@selector(blacks)]) { 24 | config.blackList = [cls blacks] ? : @[]; 25 | } 26 | if ([cls respondsToSelector:@selector(indexes)]) { 27 | config.indexes = [cls indexes] ? : @[]; 28 | } 29 | if ([cls respondsToSelector:@selector(notnulls)]) { 30 | config.notnulls = [cls notnulls] ? : @[]; 31 | } 32 | if ([cls respondsToSelector:@selector(uniques)]) { 33 | config.uniques = [cls uniques] ? : @[]; 34 | } 35 | if ([cls respondsToSelector:@selector(logAt)]) { 36 | config.logAt = [cls logAt]; 37 | } 38 | if ([cls respondsToSelector:@selector(pkAutoInc)]) { 39 | config.pkAutoInc = [cls pkAutoInc]; 40 | } 41 | if ([cls respondsToSelector:@selector(defaultValues)]) { 42 | config.defaultValues = [cls defaultValues] ? : @{}; 43 | } 44 | #ifdef VVSEQUELIZE_CONSTRAINTS 45 | if ([cls respondsToSelector:@selector(foreignKeys)]) { 46 | config.foreignKeys = [cls foreignKeys] ? : @[]; 47 | } 48 | if ([cls respondsToSelector:@selector(checks)]) { 49 | config.checks = [cls checks] ? : @{}; 50 | } 51 | #endif 52 | return config; 53 | } 54 | 55 | @end 56 | 57 | @implementation VVOrm (VVOrmable) 58 | 59 | + (instancetype)ormWithClass:(Class)clazz 60 | { 61 | return [VVOrm ormWithClass:clazz name:nil database:nil]; 62 | } 63 | 64 | + (instancetype)ormWithClass:(Class)clazz name:(NSString *)name database:(VVDatabase *)vvdb 65 | { 66 | return [VVOrm ormWithClass:clazz name:name database:vvdb setup:VVOrmSetupCreate]; 67 | } 68 | 69 | + (instancetype)ormWithClass:(Class)clazz name:(NSString *)name database:(VVDatabase *)vvdb setup:(VVOrmSetup)setup 70 | { 71 | VVOrmConfig *config = [VVOrmConfig configWithOrmable:clazz]; 72 | return [VVOrm ormWithConfig:config name:name database:vvdb setup:setup]; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVSelect.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVSelect.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/14. 6 | // 7 | 8 | #import "VVOrm.h" 9 | 10 | @interface VVSelect : NSObject 11 | /// generate sql statement 12 | @property (nonatomic, copy, readonly) NSString *sql; 13 | 14 | /// query results 15 | /// @note must set orm 16 | - (NSArray *)allObjects; 17 | 18 | /// query results 19 | /// @note must set orm 20 | - (NSArray *)allKeyValues; 21 | 22 | /// query the specified column 23 | /// @note must set orm 24 | - (NSArray *)allValues:(NSString *)field; 25 | 26 | //MARK: - chain 27 | /// create chain 28 | + (instancetype)makeSelect:(void (^)(VVSelect *make))block; 29 | 30 | /// select.orm == select.vvdb(orm.vvdb).table(orm.table).clazz(orm.metaClass ? : orm.config.cls) 31 | - (VVSelect *(^)(VVOrm *orm))orm; 32 | 33 | - (VVSelect *(^)(VVDatabase *vvdb))vvdb; 34 | 35 | - (VVSelect *(^)(NSString *table))table; 36 | 37 | - (VVSelect *(^)(Class clazz))clazz; 38 | 39 | - (VVSelect *(^)(BOOL distinct))distinct; 40 | 41 | - (VVSelect *(^)(VVFields *fields))fields; 42 | 43 | - (VVSelect *(^)(VVExpr *where))where; 44 | 45 | - (VVSelect *(^)(VVOrderBy *orderBy))orderBy; 46 | 47 | - (VVSelect *(^)(VVGroupBy *groupBy))groupBy; 48 | 49 | - (VVSelect *(^)(VVExpr *having))having; 50 | 51 | - (VVSelect *(^)(NSUInteger offset))offset; 52 | 53 | - (VVSelect *(^)(NSUInteger limit))limit; 54 | 55 | - (VVSelect *(^)(NSArray *values))values; 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /VVSequelize/Core/Orm/VVSelect.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVSelect.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/14. 6 | // 7 | 8 | #import "VVSelect.h" 9 | #import "NSObject+VVKeyValue.h" 10 | #import "NSObject+VVOrm.h" 11 | 12 | @interface VVSelect () 13 | @property (nonatomic, copy) NSString *fieldsString; 14 | @end 15 | 16 | @implementation VVSelect 17 | { 18 | VVDatabase *_vvdb; ///< database 19 | NSString *_table; ///< table name 20 | Class _clazz; ///< results class 21 | 22 | BOOL _distinct; ///< clear duplicate records or not 23 | VVFields *_fields; ///< query fields: NSString, NSArray 24 | VVExpr *_where; ///< query condition: NSString, NSDictionary, NSArray 25 | VVOrderBy *_orderBy; ///< sort: NSString, NSArray 26 | VVGroupBy *_groupBy; ///< group: NSString, NSArray 27 | VVExpr *_having; ///< group filter: NSString, NSDictionary, NSArray 28 | NSUInteger _offset; ///< offset 29 | NSUInteger _limit; ///< limit 30 | NSArray *_values; ///< bind values 31 | } 32 | 33 | - (NSArray *)allObjects 34 | { 35 | return [self allResults:YES]; 36 | } 37 | 38 | - (NSArray *)allKeyValues 39 | { 40 | return [self allResults:NO]; 41 | } 42 | 43 | - (NSArray *)allResults:(BOOL)useObjects 44 | { 45 | NSAssert(_vvdb, @"set database or orm first!"); 46 | NSArray *keyValuesArray = [_vvdb query:self.sql bind:_values]; 47 | if (useObjects) { 48 | NSAssert(_clazz, @"set class or orm first!"); 49 | return [_clazz vv_objectsWithKeyValuesArray:keyValuesArray]; 50 | } 51 | return keyValuesArray; 52 | } 53 | 54 | - (NSArray *)allValues:(NSString *)field { 55 | if (field.length == 0) return @[]; 56 | NSArray *allKeyValues = [self allKeyValues]; 57 | NSMutableArray *results = [NSMutableArray arrayWithCapacity:allKeyValues.count]; 58 | for (NSDictionary *keyValues in allKeyValues) { 59 | [results addObject:keyValues[field] ? : NSNull.null]; 60 | } 61 | return results; 62 | } 63 | 64 | //MARK: - chain 65 | + (instancetype)makeSelect:(void (^)(VVSelect *make))block 66 | { 67 | VVSelect *select = [[VVSelect alloc] init]; 68 | if (block) block(select); 69 | return select; 70 | } 71 | 72 | - (VVSelect *(^)(VVOrm *orm))orm 73 | { 74 | return ^(VVOrm *orm) { 75 | self->_clazz = orm.metaClass ? : orm.config.cls; 76 | self->_vvdb = orm.vvdb; 77 | self->_table = orm.name; 78 | return self; 79 | }; 80 | } 81 | 82 | - (VVSelect *(^)(VVDatabase *vvdb))vvdb 83 | { 84 | return ^(VVDatabase *vvdb) { 85 | self->_vvdb = vvdb; 86 | return self; 87 | }; 88 | } 89 | 90 | - (VVSelect *(^)(NSString *table))table 91 | { 92 | return ^(NSString *table) { 93 | self->_table = table; 94 | return self; 95 | }; 96 | } 97 | 98 | - (VVSelect *(^)(Class clazz))clazz 99 | { 100 | return ^(Class clazz) { 101 | self->_clazz = clazz; 102 | return self; 103 | }; 104 | } 105 | 106 | - (VVSelect *(^)(BOOL distinct))distinct 107 | { 108 | return ^(BOOL distinct) { 109 | self->_distinct = distinct; 110 | return self; 111 | }; 112 | } 113 | 114 | - (VVSelect *(^)(VVFields *fields))fields 115 | { 116 | return ^(VVFields *fields) { 117 | self->_fields = fields; 118 | return self; 119 | }; 120 | } 121 | 122 | - (VVSelect *(^)(VVExpr *where))where 123 | { 124 | return ^(VVExpr *where) { 125 | self->_where = where; 126 | return self; 127 | }; 128 | } 129 | 130 | - (VVSelect *(^)(VVOrderBy *orderBy))orderBy 131 | { 132 | return ^(VVOrderBy *orderBy) { 133 | self->_orderBy = orderBy; 134 | return self; 135 | }; 136 | } 137 | 138 | - (VVSelect *(^)(VVGroupBy *groupBy))groupBy 139 | { 140 | return ^(VVGroupBy *groupBy) { 141 | self->_groupBy = groupBy; 142 | return self; 143 | }; 144 | } 145 | 146 | - (VVSelect *(^)(VVExpr *having))having 147 | { 148 | return ^(VVExpr *having) { 149 | self->_having = having; 150 | return self; 151 | }; 152 | } 153 | 154 | - (VVSelect *(^)(NSUInteger offset))offset 155 | { 156 | return ^(NSUInteger offset) { 157 | self->_offset = offset; 158 | return self; 159 | }; 160 | } 161 | 162 | - (VVSelect *(^)(NSUInteger limit))limit 163 | { 164 | return ^(NSUInteger limit) { 165 | self->_limit = limit; 166 | return self; 167 | }; 168 | } 169 | 170 | - (VVSelect *(^)(NSArray *values))values 171 | { 172 | return ^(NSArray *values) { 173 | self->_values = values; 174 | return self; 175 | }; 176 | } 177 | 178 | - (NSString *)fieldsString 179 | { 180 | if (!_fieldsString) { 181 | if ([_fields isKindOfClass:NSString.class]) { 182 | _fieldsString = (NSString *)_fields; 183 | } else if ([_fields isKindOfClass:NSArray.class] && [(NSArray *)_fields count] > 0) { 184 | _fieldsString = [(NSArray *)_fields sqlJoin]; 185 | } else { 186 | _fieldsString = @"*"; 187 | } 188 | } 189 | return _fieldsString; 190 | } 191 | 192 | //MARK: - generate query sql 193 | - (NSString *)sql 194 | { 195 | NSAssert(_table.length > 0, @"set table or orm first!"); 196 | _fieldsString = nil; // reset fieldsString 197 | 198 | NSString *where = [_where sqlWhere] ? : @""; 199 | if (where.length > 0) where = [NSString stringWithFormat:@" WHERE %@", where]; 200 | 201 | NSString *groupBy = [_groupBy sqlJoin] ? : @""; 202 | if (groupBy.length > 0) groupBy = [NSString stringWithFormat:@" GROUP BY %@", groupBy]; 203 | 204 | NSString *having = groupBy.length > 0 ? ([_having sqlWhere] ? : @"") : @""; 205 | if (having.length > 0) having = [NSString stringWithFormat:@" HAVING %@", having]; 206 | 207 | NSString *orderBy = [_orderBy sqlJoin] ? : @""; 208 | if (orderBy.length > 0) { 209 | if (![orderBy isMatch:@"( +ASC *$)|( +DESC *$)"]) orderBy = orderBy.asc; 210 | orderBy = [NSString stringWithFormat:@" ORDER BY %@", orderBy]; 211 | } 212 | 213 | if (_offset > 0 && _limit <= 0) _limit = NSUIntegerMax; 214 | 215 | NSString *limit = _limit > 0 ? [NSString stringWithFormat:@" LIMIT %@", @(_limit)] : @""; 216 | 217 | NSString *offset = _offset > 0 ? [NSString stringWithFormat:@" OFFSET %@", @(_offset)] : @""; 218 | 219 | NSString *sql = [NSMutableString stringWithFormat:@"SELECT %@ %@ FROM %@ %@ %@ %@ %@ %@ %@", 220 | _distinct ? @"DISTINCT" : @"", self.fieldsString, _table, 221 | where, groupBy, having, orderBy, limit, offset].vv_strip; 222 | return sql; 223 | } 224 | 225 | @end 226 | -------------------------------------------------------------------------------- /VVSequelize/FTS/Tokenizer/NSString+Tokenizer.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+Tokenizer.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/3/22. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface VVPinYin : NSObject 13 | @property (nonatomic, strong, readonly) NSCharacterSet *trimmingSet; 14 | @property (nonatomic, strong, readonly) NSCharacterSet *cleanSet; 15 | @property (nonatomic, strong, readonly) NSCharacterSet *symbolSet; 16 | @property (nonatomic, strong, readonly) NSDictionary *hanzi2pinyins; 17 | @property (nonatomic, strong, readonly) NSDictionary *pinyins; 18 | @property (nonatomic, strong, readonly) NSDictionary *gb2big5Map; 19 | @property (nonatomic, strong, readonly) NSDictionary *big52gbMap; 20 | @property (nonatomic, strong, readonly) NSDictionary *syllables; 21 | 22 | + (instancetype)shared; 23 | 24 | @end 25 | 26 | @interface NSString (Tokenizer) 27 | 28 | /// pinyin token resource preloading 29 | + (void)preloadingForPinyin; 30 | 31 | /// using utf8 or ascii encoding to generate objc string 32 | + (instancetype)ocStringWithCString:(const char *)cString; 33 | 34 | /// using utf8 or ascii encoding to generate c string 35 | - (const char *)cLangString; 36 | 37 | /// convert to simplified chinese string 38 | - (NSString *)simplifiedChineseString; 39 | 40 | /// convert to traditional chinese string 41 | - (NSString *)traditionalChineseString; 42 | 43 | /// check whether the string contains chinese 44 | - (BOOL)hasChinese; 45 | 46 | /// get chinese pinyin 47 | - (NSString *)pinyin; 48 | 49 | /// get number without separator 50 | - (NSString *)numberWithoutSeparator; 51 | 52 | /// clean string after removing special characters 53 | - (NSString *)cleanString; 54 | 55 | /// convert white space characters (\t \n \f \r \p{Z}) to whtie space 56 | - (NSString *)singleLine; 57 | 58 | /// string use to match 59 | - (NSString *)matchingPattern; 60 | 61 | /// regular expression of keyword 62 | - (NSString *)regexPattern; 63 | 64 | /// pinyin segmentation search 65 | - (NSString *)fts5MatchPattern; 66 | 67 | /// full width pattern 68 | - (NSString *)halfWidthPattern; 69 | 70 | /// full width pattern 71 | - (NSString *)fullWidthPattern; 72 | 73 | + (NSString *)fts5HighlightOfFields:(NSArray *)fields 74 | tableName:(NSString *)tableName 75 | tableColumns:(NSArray *)tableColumns 76 | resultColumns:(nullable NSArray *)resultColumns; 77 | 78 | + (NSString *)fts5HighlightOfFields:(NSArray *)fields 79 | tableName:(NSString *)tableName 80 | tableColumns:(NSArray *)tableColumns 81 | resultColumns:(nullable NSArray *)resultColumns 82 | leftMark:(NSString *)leftMark 83 | rightMark:(NSString *)rightMark; 84 | 85 | /// fast pinyin segmentation 86 | - (NSArray *)fastPinyinSegmentation; 87 | 88 | /// all pinyin segmentation 89 | - (NSArray *> *)pinyinSegmentation; 90 | 91 | @end 92 | 93 | @interface NSAttributedString (Highlighter) 94 | 95 | /// trim the text to the specified length, and use ellipsis to replace the excess part 96 | - (NSAttributedString *)attributedStringByTrimmingToLength:(NSUInteger)maxLen withAttributes:(NSDictionary *)attributes; 97 | 98 | /// trim the text to the specified length, and use ellipsis to replace the excess part 99 | - (NSAttributedString *)attributedStringByTrimmingToLength:(NSUInteger)maxLen withFirstHighlightRange:(NSRange)range; 100 | 101 | // convert highlighted text to attributed text, use '' '' 102 | + (NSAttributedString *)attributedStringWithHighlightString:(NSString *)string 103 | attributes:(NSDictionary *)attributes; 104 | // convert highlighted text to attributed text 105 | + (NSAttributedString *)attributedStringWithHighlightString:(NSString *)string 106 | attributes:(NSDictionary *)attributes 107 | leftMark:(NSString *)leftMark 108 | rightMark:(NSString *)rightMark; 109 | 110 | @end 111 | 112 | NS_ASSUME_NONNULL_END 113 | -------------------------------------------------------------------------------- /VVSequelize/FTS/Tokenizer/VVPinYinSegmentor.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVPinyinSegmentor.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2020/4/3. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface VVPinYinSegmentor : NSObject 13 | 14 | + (NSArray *)segment:(NSString *)pinyinString; 15 | 16 | + (NSArray *> *)recursionSegment:(NSString *)pinyinString; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /VVSequelize/FTS/Tokenizer/VVPinYinSegmentor.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVPinyinSegmentor.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2020/4/3. 6 | // 7 | 8 | #import "VVPinYinSegmentor.h" 9 | #import "NSString+Tokenizer.h" 10 | 11 | typedef struct CG_BOXABLE VVPinYinTrie { 12 | long freq; 13 | bool next; 14 | char ch; 15 | struct VVPinYinTrie *childs[26]; 16 | } VVPinYinTrie; 17 | 18 | @interface VVPinYinPhone : NSObject 19 | @property (nonatomic, copy) NSString *pinyin; 20 | @property (nonatomic, assign) long frequency; 21 | @end 22 | 23 | @implementation VVPinYinPhone 24 | @end 25 | 26 | @implementation VVPinYinSegmentor 27 | 28 | //MARK: - Trie 29 | + (VVPinYinTrie *)rootPinYinTrie 30 | { 31 | static dispatch_once_t onceToken; 32 | static VVPinYinTrie *_trie = nil; 33 | dispatch_once(&onceToken, ^{ 34 | _trie = [self newTrieNode]; 35 | NSDictionary *syllables = VVPinYin.shared.syllables; 36 | [syllables enumerateKeysAndObjectsUsingBlock:^(NSString *pinyin, NSNumber *freq, BOOL *stop) { 37 | [self addPinyin:pinyin.UTF8String frequency:freq.longValue root:_trie]; 38 | }]; 39 | }); 40 | return _trie; 41 | } 42 | 43 | + (VVPinYinTrie *)newTrieNode 44 | { 45 | size_t size = sizeof(VVPinYinTrie); 46 | VVPinYinTrie *node = (VVPinYinTrie *)malloc(size); 47 | memset(node, 0x0, size); 48 | return node; 49 | } 50 | 51 | + (void)addChar:(char)ch to:(VVPinYinTrie *)node frequency:(long)frequency 52 | { 53 | if (ch < 'a' || ch > 'z') return; 54 | int idx = ch - 97; 55 | node->next = true; 56 | VVPinYinTrie *child = node->childs[idx]; 57 | if (!child) { 58 | child = [self newTrieNode]; 59 | node->childs[idx] = child; 60 | } 61 | child->ch = ch; 62 | if (frequency > child->freq) child->freq = frequency; 63 | } 64 | 65 | + (void)addPinyin:(const char *)pinyin frequency:(long)frequency root:(VVPinYinTrie *)root 66 | { 67 | int i = 0; 68 | int len = (int)strlen(pinyin); 69 | char ch = pinyin[i]; 70 | VVPinYinTrie *node = root; 71 | while (ch) { 72 | if (ch < 'a' || ch > 'z') continue; 73 | [[self class] addChar:ch to:node frequency:i < len - 1 ? 0 : frequency]; 74 | node = node->childs[ch - 97]; 75 | ch = pinyin[++i]; 76 | } 77 | } 78 | 79 | + (NSArray *)retrieve:(const char *)pinyin 80 | { 81 | NSMutableArray *results = [NSMutableArray array]; 82 | VVPinYinTrie *root = [self rootPinYinTrie]; 83 | VVPinYinTrie *node = root; 84 | u_long len = strlen(pinyin); 85 | u_long last = len - 1; 86 | for (u_long i = 0; i < len; i++) { 87 | char ch = pinyin[i]; 88 | if (ch < 'a' || ch > 'z') break; 89 | VVPinYinTrie *child = node->childs[ch - 97]; 90 | if (!child) break; 91 | if (child->freq > 0 || i == last) { 92 | VVPinYinPhone *phone = [VVPinYinPhone new]; 93 | phone.pinyin = [[NSString alloc] initWithBytes:pinyin length:i + 1 encoding:NSASCIIStringEncoding]; 94 | phone.frequency = i == last ? 65535 : child->freq; 95 | [results addObject:phone]; 96 | } 97 | node = child; 98 | } 99 | [results sortUsingComparator:^NSComparisonResult (VVPinYinPhone *phone1, VVPinYinPhone *phone2) { 100 | return phone1.frequency > phone2.frequency ? NSOrderedAscending : NSOrderedDescending; 101 | }]; 102 | return results; 103 | } 104 | 105 | + (NSArray *)split:(const char *)pinyin 106 | { 107 | NSArray *fronts = [self retrieve:pinyin]; 108 | u_long len = strlen(pinyin); 109 | for (VVPinYinPhone *item in fronts) { 110 | u_long iLen = item.pinyin.length; 111 | if (iLen == len) { 112 | return @[item.pinyin]; 113 | } else { 114 | NSArray *temp = [self retrieve:(pinyin + iLen)]; 115 | if (temp.count > 0) { 116 | NSArray *rights = [self split:(pinyin + iLen)]; 117 | return [@[item.pinyin] arrayByAddingObjectsFromArray:rights]; 118 | } 119 | } 120 | } 121 | return @[]; 122 | } 123 | 124 | //MARK: public 125 | + (NSArray *)segment:(NSString *)pinyinString 126 | { 127 | return [self split:pinyinString.lowercaseString.UTF8String]; 128 | } 129 | 130 | //MARK: - recursion 131 | + (NSArray *)firstPinyinsOf:(NSString *)pinyinString 132 | { 133 | const char *str = pinyinString.UTF8String; 134 | u_long length = strlen(str); 135 | if (length <= 0) return @[]; 136 | 137 | NSString *firstLetter = [pinyinString substringToIndex:1]; 138 | NSArray *array = [[VVPinYin shared].pinyins objectForKey:firstLetter]; 139 | 140 | NSMutableArray *results = [NSMutableArray array]; 141 | for (NSString *pinyin in array) { 142 | const char *py = pinyin.cLangString; 143 | u_long len = strlen(py); 144 | if (len < length && strncmp(py, str, len) == 0) { 145 | [results addObject:pinyin]; 146 | } 147 | } 148 | return results; 149 | } 150 | 151 | + (NSArray *> *)recursionSplit:(NSString *)pinyinString 152 | { 153 | NSMutableArray *> *results = [NSMutableArray array]; 154 | @autoreleasepool { 155 | NSArray *array = [self firstPinyinsOf:pinyinString]; 156 | if (array.count == 0) return @[@[self]]; 157 | for (NSString *first in array) { 158 | NSString *tail = [pinyinString substringFromIndex:first.length]; 159 | NSArray *> *components = [self recursionSplit:tail]; 160 | for (NSArray *pinyins in components) { 161 | NSArray *result = [@[first] arrayByAddingObjectsFromArray:pinyins]; 162 | [results addObject:result]; 163 | } 164 | } 165 | } 166 | return results; 167 | } 168 | 169 | //MARK: public 170 | + (NSArray *> *)recursionSegment:(NSString *)pinyinString 171 | { 172 | return [self recursionSplit:pinyinString.lowercaseString]; 173 | } 174 | 175 | @end 176 | -------------------------------------------------------------------------------- /VVSequelize/FTS/Tokenizer/VVResultMatch.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVResultMatch.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/8/20. 6 | // 7 | 8 | #import 9 | #import "VVOrm.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef NS_ENUM (NSUInteger, VVMatchLV1) { 14 | VVMatchLV1_None = 0, 15 | VVMatchLV1_Fuzzy, 16 | VVMatchLV1_Firsts, 17 | VVMatchLV1_Fulls, 18 | VVMatchLV1_Origin, 19 | }; 20 | 21 | typedef NS_ENUM (NSUInteger, VVMatchLV2) { 22 | VVMatchLV2_None = 0, 23 | VVMatchLV2_Other, 24 | VVMatchLV2_NonPrefix, 25 | VVMatchLV2_Prefix, 26 | VVMatchLV2_Full, 27 | }; 28 | 29 | typedef NS_ENUM (NSUInteger, VVMatchLV3) { 30 | VVMatchLV3_Low = 0, 31 | VVMatchLV3_Medium, 32 | VVMatchLV3_High, 33 | }; 34 | 35 | @interface VVResultMatch : NSObject 36 | @property (nonatomic, copy) NSString *source; 37 | @property (nonatomic, copy) NSAttributedString *attrText; 38 | @property (nonatomic, strong, readonly) NSArray *ranges; 39 | @property (nonatomic, assign, readonly) UInt64 lowerWeight; 40 | @property (nonatomic, assign, readonly) UInt64 upperWeight; 41 | @property (nonatomic, assign, readonly) UInt64 weight; 42 | 43 | //used to calculate weights 44 | @property (nonatomic, assign, readonly) VVMatchLV1 lv1; 45 | @property (nonatomic, assign, readonly) VVMatchLV2 lv2; 46 | @property (nonatomic, assign, readonly) VVMatchLV3 lv3; 47 | 48 | + (instancetype)matchWithAttributedString:(NSAttributedString *)attrText 49 | attributes:(NSDictionary *)attributes 50 | keyword:(NSString *)keyword; 51 | 52 | - (NSComparisonResult)compare:(VVResultMatch *)other; 53 | 54 | @end 55 | 56 | NS_ASSUME_NONNULL_END 57 | -------------------------------------------------------------------------------- /VVSequelize/FTS/Tokenizer/VVResultMatch.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVResultMatch.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/8/20. 6 | // 7 | 8 | #import "VVResultMatch.h" 9 | #import "VVDatabase+FTS.h" 10 | #import "NSString+Tokenizer.h" 11 | 12 | @interface VVResultMatch () 13 | @property (nonatomic, assign) VVMatchLV1 lv1; 14 | @property (nonatomic, assign) VVMatchLV2 lv2; 15 | @property (nonatomic, assign) VVMatchLV3 lv3; 16 | @property (nonatomic, strong) NSArray *ranges; 17 | @end 18 | 19 | @implementation VVResultMatch 20 | @synthesize lowerWeight = _lowerWeight; 21 | @synthesize upperWeight = _upperWeight; 22 | @synthesize weight = _weight; 23 | 24 | + (instancetype)matchWithAttributedString:(NSAttributedString *)attrText 25 | attributes:(NSDictionary *)attributes 26 | keyword:(NSString *)keyword 27 | { 28 | NSString *source = attrText.string.lowercaseString; 29 | NSString *lowerkw = keyword.lowercaseString; 30 | NSMutableArray *ranges = [NSMutableArray array]; 31 | __block NSString *firstString = nil; 32 | __block NSRange firstRange = NSMakeRange(NSNotFound, 0); 33 | [attrText enumerateAttributesInRange:NSMakeRange(0, attrText.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) { 34 | if ([attrs isEqualToDictionary:attributes]) { 35 | [ranges addObject:[NSValue valueWithRange:range]]; 36 | if (!firstString) { 37 | firstString = [source substringWithRange:range]; 38 | firstRange = range; 39 | } 40 | } 41 | }]; 42 | VVResultMatch *match = [VVResultMatch new]; 43 | match.source = source; 44 | match.attrText = attrText; 45 | match.ranges = ranges; 46 | 47 | if (ranges.count == 0) return match; 48 | 49 | unichar rch = [firstString characterAtIndex:0]; 50 | unichar kch = [lowerkw characterAtIndex:0]; 51 | 52 | // lv1 53 | if (rch == kch) { 54 | match.lv1 = VVMatchLV1_Origin; 55 | } else if (rch >= 0x3000 && kch >= 0x3000) { 56 | NSDictionary *map = VVPinYin.shared.big52gbMap; 57 | unichar runi = (unichar)[(map[@(rch)] ? : @(rch)) unsignedShortValue]; 58 | unichar kuni = (unichar)[(map[@(kch)] ? : @(kch)) unsignedShortValue]; 59 | match.lv1 = runi == kuni ? VVMatchLV1_Origin : VVMatchLV1_Fuzzy; 60 | } else if (rch >= 0x3000 && kch < 0xC0) { 61 | if (lowerkw.length == firstString.length) { 62 | match.lv1 = VVMatchLV1_Firsts; 63 | } else { 64 | NSDictionary *map = VVPinYin.shared.big52gbMap; 65 | unichar runi = (unichar)[(map[@(rch)] ? : @(rch)) unsignedShortValue]; 66 | NSArray *pinyins = VVPinYin.shared.hanzi2pinyins[@(runi)]; 67 | BOOL isfull = NO; 68 | for (NSString *pinyin in pinyins) { 69 | if ((lowerkw.length >= pinyin.length && [lowerkw hasPrefix:pinyin]) 70 | || (lowerkw.length < pinyin.length && [pinyin hasPrefix:lowerkw])) { 71 | isfull = YES; 72 | break; 73 | } 74 | } 75 | match.lv1 = isfull ? VVMatchLV1_Fulls : VVMatchLV1_Firsts; 76 | } 77 | } 78 | 79 | //lv2 80 | VVMatchLV2 lv2 = firstRange.location > 0 ? VVMatchLV2_NonPrefix : (firstRange.length == attrText.length ? VVMatchLV2_Full : VVMatchLV2_Prefix); 81 | if (lv2 == VVMatchLV2_Full && match.lv1 == VVMatchLV1_Fulls && rch >= 0x3000 && kch < 0xC0) { 82 | unichar ech = [firstString characterAtIndex:firstString.length - 1]; 83 | NSDictionary *map = VVPinYin.shared.big52gbMap; 84 | unichar euni = (unichar)[(map[@(ech)] ? : @(ech)) unsignedShortValue]; 85 | NSArray *pinyins = VVPinYin.shared.hanzi2pinyins[@(euni)]; 86 | NSString *lastkw = [lowerkw substringFromIndex:lowerkw.length - 1]; 87 | lv2 = VVMatchLV2_Prefix; 88 | for (NSString *pinyin in pinyins) { 89 | if ((lowerkw.length >= pinyin.length && [lowerkw hasSuffix:pinyin]) || [pinyin hasPrefix:lastkw]) { 90 | lv2 = VVMatchLV2_Full; 91 | break; 92 | } 93 | } 94 | } 95 | match.lv2 = lv2; 96 | 97 | //lv3 98 | match.lv3 = match.lv1 == VVMatchLV1_Origin ? VVMatchLV3_High : match.lv1 == VVMatchLV1_Fulls ? VVMatchLV3_Medium : VVMatchLV3_Low; 99 | return match; 100 | } 101 | 102 | - (UInt64)upperWeight 103 | { 104 | if (_upperWeight == 0 && _ranges.count > 0) { 105 | _upperWeight = (UInt64)(_lv1 & 0xF) << 28 | (UInt64)(_lv2 & 0xF) << 24 | (UInt64)(_lv3 & 0xF) << 20; 106 | } 107 | return _upperWeight; 108 | } 109 | 110 | - (UInt64)lowerWeight 111 | { 112 | if (_upperWeight == 0 && _ranges.count > 0) { 113 | NSRange range = [_ranges.firstObject rangeValue]; 114 | UInt64 loc = ~range.location & 0xFFFF; 115 | UInt64 rate = ((UInt64)range.length << 32) / _source.length; 116 | _lowerWeight = (UInt64)(loc & 0xFFFF) << 16 | (UInt64)(rate & 0xFFFF) << 0; 117 | } 118 | return _upperWeight; 119 | } 120 | 121 | - (UInt64)weight 122 | { 123 | if (_weight == 0 && _ranges.count > 0) { 124 | _weight = self.upperWeight << 32 | self.lowerWeight; 125 | } 126 | return _weight; 127 | } 128 | 129 | - (NSComparisonResult)compare:(VVResultMatch *)other 130 | { 131 | return self.weight == other.weight ? NSOrderedSame : self.weight > other.weight ? NSOrderedAscending : NSOrderedDescending; 132 | } 133 | 134 | - (NSString *)description 135 | { 136 | return [NSString stringWithFormat:@"[%@|%@|%@|%@|0x%llx]: %@", @(_lv1), @(_lv2), @(_lv3), _ranges.firstObject, self.weight, [_attrText.description stringByReplacingOccurrencesOfString:@"\n" withString:@""]]; 137 | } 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /VVSequelize/FTS/Tokenizer/VVTokenEnumerator.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | //MARK: - defines 5 | #ifndef UNUSED_PARAM 6 | #define UNUSED_PARAM(v) (void)(v) 7 | #endif 8 | 9 | #ifndef TOKEN_PINYIN_MAX_LENGTH 10 | #define TOKEN_PINYIN_MAX_LENGTH 15 11 | #endif 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | typedef NS_OPTIONS (NSUInteger, VVTokenMask) { 16 | VVTokenMaskPinyin = 1 << 0, ///< placeholder, it will be executed without setting 17 | VVTokenMaskAbbreviation = 1 << 1, ///< pinyin abbreviation. not recommended, many invalid results will be found 18 | 19 | VVTokenMaskAll = 0xFFFFFF, 20 | VVTokenMaskQuery = 1 << 24, ///< FTS5_TOKENIZE_QUERY, only for query 21 | }; 22 | 23 | //MARK: - VVTokenizerName 24 | typedef NSString *VVTokenizerName NS_EXTENSIBLE_STRING_ENUM; 25 | 26 | FOUNDATION_EXPORT VVTokenizerName const VVTokenTokenizerSequelize; 27 | FOUNDATION_EXPORT VVTokenizerName const VVTokenTokenizerApple; 28 | FOUNDATION_EXPORT VVTokenizerName const VVTokenTokenizerNatual; 29 | 30 | //MARK: - VVToken 31 | 32 | @interface VVToken : NSObject 33 | @property (nonatomic, assign) char *word; 34 | @property (nonatomic, assign) int len; 35 | @property (nonatomic, assign) int start; 36 | @property (nonatomic, assign) int end; 37 | @property (nonatomic, assign) int colocated; ///< -1:full width, 0:original, 1:full pinyin, 2:abbreviation, 3:syllable 38 | 39 | @property (nonatomic, copy, readonly) NSString *token; 40 | 41 | + (instancetype)token:(const char *)word len:(int)len start:(int)start end:(int)end; 42 | 43 | + (NSArray *)sortedTokens:(NSArray *)tokens; 44 | @end 45 | 46 | @protocol VVTokenEnumerator 47 | 48 | + (NSArray *)enumerate:(const char *)input mask:(VVTokenMask)mask; 49 | 50 | @end 51 | 52 | @interface VVTokenAppleEnumerator : NSObject 53 | 54 | @end 55 | 56 | @interface VVTokenNatualEnumerator : NSObject 57 | 58 | @end 59 | 60 | @interface VVTokenSequelizeEnumerator : NSObject 61 | 62 | @end 63 | 64 | NS_ASSUME_NONNULL_END 65 | -------------------------------------------------------------------------------- /VVSequelize/FTS/VVDatabase+FTS.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVDataBase+FTS.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/3/20. 6 | // 7 | 8 | #import "VVDatabase.h" 9 | #import "VVTokenEnumerator.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface VVDatabase (FTS) 14 | 15 | /// register tokenizer, only fts5 is supported 16 | - (void)registerEnumerator:(Class)enumerator forTokenizer:(NSString *)name; 17 | 18 | - (nullable Class)enumeratorForTokenizer:(NSString *)name; 19 | 20 | - (void)registerEnumerators:(sqlite3 *)db; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /VVSequelize/FTS/VVDatabase+FTS.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVDatabase+FTS.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2019/3/20. 6 | // 7 | 8 | #import "VVDatabase+FTS.h" 9 | #import "NSString+Tokenizer.h" 10 | 11 | #ifdef SQLITE_HAS_CODEC 12 | #import "sqlite3.h" 13 | #else 14 | #import 15 | #endif 16 | 17 | //MARK: - FTS5 18 | 19 | static fts5_api * fts5_api_from_db(sqlite3 *db) 20 | { 21 | fts5_api *pRet = 0; 22 | sqlite3_stmt *pStmt = 0; 23 | 24 | if (SQLITE_OK == sqlite3_prepare(db, "SELECT fts5(?1)", -1, &pStmt, 0) ) { 25 | #ifdef SQLITE_HAS_CODEC 26 | sqlite3_bind_pointer(pStmt, 1, (void *)&pRet, "fts5_api_ptr", NULL); 27 | sqlite3_step(pStmt); 28 | #else 29 | if (@available(iOS 12.0, *)) { 30 | sqlite3_bind_pointer(pStmt, 1, (void *)&pRet, "fts5_api_ptr", NULL); 31 | sqlite3_step(pStmt); 32 | } 33 | #endif 34 | } 35 | sqlite3_finalize(pStmt); 36 | return pRet; 37 | } 38 | 39 | typedef struct Fts5VVTokenizer Fts5VVTokenizer; 40 | struct Fts5VVTokenizer { 41 | char locale[16]; 42 | uint64_t mask; 43 | void *clazz; 44 | }; 45 | 46 | static void vv_fts5_xDelete(Fts5Tokenizer *p) 47 | { 48 | sqlite3_free(p); 49 | } 50 | 51 | static int vv_fts5_xCreate( 52 | void *pUnused, 53 | const char **azArg, int nArg, 54 | Fts5Tokenizer **ppOut 55 | ) 56 | { 57 | Fts5VVTokenizer *tok = sqlite3_malloc(sizeof(Fts5VVTokenizer)); 58 | if (!tok) return SQLITE_NOMEM; 59 | 60 | memset(tok->locale, 0x0, 16); 61 | tok->mask = 0; 62 | 63 | for (int i = 0; i < MIN(2, nArg); i++) { 64 | const char *arg = azArg[i]; 65 | uint32_t mask = (uint32_t)atoll(arg); 66 | if (mask > 0) { 67 | tok->mask = mask; 68 | } else { 69 | strncpy(tok->locale, arg, 15); 70 | } 71 | } 72 | 73 | tok->clazz = pUnused; 74 | *ppOut = (Fts5Tokenizer *)tok; 75 | return SQLITE_OK; 76 | } 77 | 78 | static int vv_fts5_xTokenize( 79 | Fts5Tokenizer *pTokenizer, 80 | void *pCtx, 81 | int iUnused, 82 | const char *pText, int nText, 83 | int (*xToken)(void *, int, const char *, int nToken, int iStart, int iEnd) 84 | ) 85 | { 86 | UNUSED_PARAM(iUnused); 87 | UNUSED_PARAM(pText); 88 | if (pText == 0) return SQLITE_OK; 89 | 90 | int rc = SQLITE_OK; 91 | Fts5VVTokenizer *tok = (Fts5VVTokenizer *)pTokenizer; 92 | Class clazz = (__bridge Class)(tok->clazz); 93 | if (!clazz || ![clazz conformsToProtocol:@protocol(VVTokenEnumerator)]) { 94 | return SQLITE_ERROR; 95 | } 96 | uint64_t mask = tok->mask; 97 | if (iUnused & FTS5_TOKENIZE_QUERY) { 98 | mask = mask | VVTokenMaskQuery; 99 | } else if (iUnused & FTS5_TOKENIZE_DOCUMENT) { 100 | mask = mask & ~VVTokenMaskQuery; 101 | } 102 | NSArray *array = [clazz enumerate:pText mask:(VVTokenMask)mask]; 103 | 104 | for (VVToken *tk in array) { 105 | rc = xToken(pCtx, tk.colocated, tk.word, tk.len, tk.start, tk.end); 106 | if (rc != SQLITE_OK) break; 107 | } 108 | 109 | if (rc == SQLITE_DONE) rc = SQLITE_OK; 110 | return rc; 111 | } 112 | 113 | @implementation VVDatabase (FTS) 114 | 115 | - (void)registerEnumerator:(Class)enumerator forTokenizer:(NSString *)name 116 | { 117 | [self.enumerators setObject:enumerator forKey:name]; 118 | if (self.isOpen) [self registerEnumerators:self.db]; 119 | } 120 | 121 | - (Class)enumeratorForTokenizer:(NSString *)name 122 | { 123 | return [self.enumerators objectForKey:name]; 124 | } 125 | 126 | - (void)registerEnumerators:(sqlite3 *)db 127 | { 128 | if (self.enumerators.count == 0) return; 129 | fts5_api *pApi = fts5_api_from_db(db); 130 | if (!pApi) { 131 | #if DEBUG 132 | printf("[VVDB][DEBUG] fts5 is not supported\n"); 133 | #endif 134 | return; 135 | } 136 | 137 | [self.enumerators enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class enumerator, BOOL *stop) { 138 | fts5_tokenizer *tokenizer; 139 | tokenizer = (fts5_tokenizer *)sqlite3_malloc(sizeof(*tokenizer)); 140 | tokenizer->xCreate = vv_fts5_xCreate; 141 | tokenizer->xDelete = vv_fts5_xDelete; 142 | tokenizer->xTokenize = vv_fts5_xTokenize; 143 | 144 | int rc = pApi->xCreateTokenizer(pApi, name.cLangString, (__bridge void *)enumerator, tokenizer, NULL); 145 | NSString *errorsql = [NSString stringWithFormat:@"register tokenizer: %@", name]; 146 | [self check:rc sql:errorsql]; 147 | }]; 148 | } 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /VVSequelize/FTS/VVFtsable.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVFtsable.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2020/7/29. 6 | // 7 | 8 | #import 9 | #import "VVOrm.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @protocol VVFtsable 14 | @optional 15 | + (NSArray *)fts_whites; 16 | + (NSArray *)fts_blacks; 17 | + (NSArray *)fts_indexes; 18 | + (NSString *)fts_module; 19 | + (NSString *)fts_tokenizer; 20 | @end 21 | 22 | @interface VVOrmConfig (VVFtsable) 23 | 24 | + (instancetype)configWithFtsable:(Class)cls; 25 | 26 | @end 27 | 28 | @interface VVOrm (VVFtsable) 29 | /// Initialize orm, auto create/modify defalut table, use temporary db. 30 | /// @param clazz class confirm to VVFtsable 31 | + (nullable instancetype)ormWithFtsClass:(Class)clazz; 32 | 33 | /// Initialize orm, auto create/modify table and indexes, check and create table immediately 34 | /// @param clazz class confirm to VVFtsable 35 | /// @param name table name, nil means to use class name 36 | /// @param vvdb db, nil means to use temporary db 37 | + (nullable instancetype)ormWithFtsClass:(Class)clazz 38 | name:(nullable NSString *)name 39 | database:(nullable VVDatabase *)vvdb; 40 | 41 | /// Initialize orm, auto create/modify table and indexes 42 | /// @param clazz class confirm to VVFtsable 43 | /// @param name table name, nil means to use class name 44 | /// @param vvdb db, nil means to use temporary db 45 | /// @param setup check and create table or not 46 | + (nullable instancetype)ormWithFtsClass:(Class)clazz 47 | name:(nullable NSString *)name 48 | database:(nullable VVDatabase *)vvdb 49 | setup:(VVOrmSetup)setup; 50 | 51 | @end 52 | 53 | NS_ASSUME_NONNULL_END 54 | -------------------------------------------------------------------------------- /VVSequelize/FTS/VVFtsable.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVFtsable.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2020/7/29. 6 | // 7 | 8 | #import "VVFtsable.h" 9 | #import "VVTokenEnumerator.h" 10 | 11 | @implementation VVOrmConfig (VVFtsable) 12 | 13 | + (instancetype)configWithFtsable:(Class)cls 14 | { 15 | NSAssert([cls conformsToProtocol:@protocol(VVFtsable)], @"class must confroms to VVFtsable"); 16 | VVOrmConfig *config = [VVOrmConfig configWithClass:cls]; 17 | config.fts = YES; 18 | if ([cls respondsToSelector:@selector(fts_whites)]) { 19 | config.whiteList = [cls fts_whites] ? : @[]; 20 | } 21 | if ([cls respondsToSelector:@selector(fts_blacks)]) { 22 | config.blackList = [cls fts_blacks] ? : @[]; 23 | } 24 | if ([cls respondsToSelector:@selector(fts_indexes)]) { 25 | config.indexes = [cls fts_indexes] ? : @[]; 26 | } 27 | if ([cls respondsToSelector:@selector(fts_module)]) { 28 | config.ftsModule = [cls fts_module] ? : @"fts5"; 29 | } 30 | if ([cls respondsToSelector:@selector(fts_tokenizer)]) { 31 | config.ftsTokenizer = [cls fts_tokenizer] ? : @"sequelize"; 32 | } 33 | return config; 34 | } 35 | 36 | @end 37 | 38 | @implementation VVOrm (VVFtsable) 39 | 40 | + (instancetype)ormWithFtsClass:(Class)clazz 41 | { 42 | return [VVOrm ormWithFtsClass:clazz name:nil database:nil]; 43 | } 44 | 45 | + (instancetype)ormWithFtsClass:(Class)clazz name:(NSString *)name database:(VVDatabase *)vvdb 46 | { 47 | return [VVOrm ormWithFtsClass:clazz name:name database:vvdb setup:VVOrmSetupCreate]; 48 | } 49 | 50 | + (instancetype)ormWithFtsClass:(Class)clazz name:(NSString *)name database:(VVDatabase *)vvdb setup:(VVOrmSetup)setup 51 | { 52 | VVOrmConfig *config = [VVOrmConfig configWithFtsable:clazz]; 53 | return [VVOrm ormWithConfig:config name:name database:vvdb setup:setup]; 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /VVSequelize/FTS/VVOrm+FTS.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm+FTS.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/15. 6 | // 7 | 8 | #import "VVOrm.h" 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | FOUNDATION_EXPORT NSString *const VVOrmFtsCount; 13 | 14 | @interface VVOrm (FTS) 15 | 16 | /// generating clause with fts5 highlight(), use default marks `,` 17 | /// @param fields fields to highlight 18 | - (NSString *)fts5HighlightOfFields:(NSArray *)fields; 19 | 20 | /// generating clause with fts5 highlight() 21 | /// @param fields fields to highlight 22 | /// @param resultColumns search result fields 23 | /// @param leftMark left highlight mark 24 | /// @param rightMark right highlight mark 25 | - (NSString *)fts5HighlightOfFields:(NSArray *)fields 26 | resultColumns:(nullable NSArray *)resultColumns 27 | leftMark:(NSString *)leftMark 28 | rightMark:(NSString *)rightMark; 29 | 30 | /// full text search 31 | /// @param condition match expression, such as: "name:zhan*","zhan*", refer to: https://sqlite.org/fts5.html 32 | /// @param orderBy sort method 33 | /// @param limit limit of results, 0 without limit 34 | /// @param offset start position 35 | /// @return [object] 36 | - (NSArray *)match:(nullable VVExpr *)condition 37 | orderBy:(nullable VVOrderBy *)orderBy 38 | limit:(NSUInteger)limit 39 | offset:(NSUInteger)offset; 40 | 41 | /// full text search 42 | /// @note simple highlight 43 | - (NSArray *)match:(nullable VVExpr *)condition 44 | highlight:(NSArray *)fields 45 | orderBy:(nullable VVOrderBy *)orderBy 46 | limit:(NSUInteger)limit 47 | offset:(NSUInteger)offset; 48 | 49 | /// grouped full text search 50 | /// @param groupBy group method 51 | /// @return [{field1:value1, field2: value2, ..., vvdb_fts_count: group_count}] 52 | - (NSArray *)match:(nullable VVExpr *)condition 53 | groupBy:(nullable VVGroupBy *)groupBy 54 | limit:(NSUInteger)limit 55 | offset:(NSUInteger)offset; 56 | 57 | /// get match count 58 | - (NSUInteger)matchCount:(nullable VVExpr *)condition; 59 | 60 | /// full text search 61 | /// @return {"count":100,list:[object]} 62 | - (NSDictionary *)matchAndCount:(nullable VVExpr *)condition 63 | orderBy:(nullable VVOrderBy *)orderBy 64 | limit:(NSUInteger)limit 65 | offset:(NSUInteger)offset; 66 | 67 | @end 68 | 69 | NS_ASSUME_NONNULL_END 70 | -------------------------------------------------------------------------------- /VVSequelize/FTS/VVOrm+FTS.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrm+FTS.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/15. 6 | // 7 | 8 | #import "VVOrm+FTS.h" 9 | #import "VVSelect.h" 10 | #import "NSObject+VVOrm.h" 11 | #import "VVDatabase+FTS.h" 12 | #import "VVDBStatement.h" 13 | #import "VVFtsable.h" 14 | #import "NSString+Tokenizer.h" 15 | 16 | NSString *const VVOrmFtsCount = @"vvdb_fts_count"; 17 | 18 | @implementation VVOrm (FTS) 19 | 20 | //MARK: - Public 21 | 22 | - (NSString *)fts5HighlightOfFields:(NSArray *)fields 23 | { 24 | return [self fts5HighlightOfFields:fields resultColumns:nil leftMark:@"" rightMark:@""]; 25 | } 26 | 27 | - (NSString *)fts5HighlightOfFields:(NSArray *)fields 28 | resultColumns:(nullable NSArray *)resultColumns 29 | leftMark:(NSString *)leftMark 30 | rightMark:(NSString *)rightMark 31 | { 32 | NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@ LIMIT 0;", self.name.quoted]; 33 | VVDBStatement *statement = [VVDBStatement statementWithDatabase:self.vvdb sql:sql]; 34 | NSArray *tableColumns = [[statement columnNames] copy]; 35 | statement = nil; 36 | 37 | NSString *result = [NSString fts5HighlightOfFields:fields tableName:self.name tableColumns:tableColumns resultColumns:resultColumns leftMark:leftMark rightMark:rightMark]; 38 | return result; 39 | } 40 | 41 | - (NSArray *)match:(nullable VVExpr *)condition 42 | orderBy:(nullable VVOrderBy *)orderBy 43 | limit:(NSUInteger)limit 44 | offset:(NSUInteger)offset 45 | { 46 | VVSelect *select = VVSelect.new.orm(self).where(condition).orderBy(orderBy).offset(offset).limit(limit); 47 | return [select allObjects]; 48 | } 49 | 50 | - (NSArray *)match:(nullable VVExpr *)condition 51 | highlight:(NSArray *)fields 52 | orderBy:(nullable VVOrderBy *)orderBy 53 | limit:(NSUInteger)limit 54 | offset:(NSUInteger)offset 55 | { 56 | VVSelect *select = VVSelect.new.orm(self).where(condition).orderBy(orderBy).offset(offset).limit(limit); 57 | NSString *fieldsString = [self fts5HighlightOfFields:fields]; 58 | select.fields(fieldsString); 59 | return [select allObjects]; 60 | } 61 | 62 | - (NSArray *)match:(nullable VVExpr *)condition 63 | groupBy:(nullable VVGroupBy *)groupBy 64 | limit:(NSUInteger)limit 65 | offset:(NSUInteger)offset 66 | { 67 | NSString *fields = [NSString stringWithFormat:@"*,count(*) as %@", VVOrmFtsCount]; 68 | NSString *orderBy = @"rowid".desc; 69 | VVSelect *select = VVSelect.new.orm(self).where(condition).fields(fields) 70 | .groupBy(groupBy).orderBy(orderBy).offset(offset).limit(limit); 71 | return [select allKeyValues]; 72 | } 73 | 74 | - (NSUInteger)matchCount:(nullable VVExpr *)condition 75 | { 76 | NSString *fields = @"count(*) as count"; 77 | VVSelect *select = VVSelect.new.orm(self).where(condition).fields(fields); 78 | NSDictionary *dic = [select allKeyValues].firstObject; 79 | return [dic[@"count"] integerValue]; 80 | } 81 | 82 | - (NSDictionary *)matchAndCount:(nullable VVExpr *)condition 83 | orderBy:(nullable VVOrderBy *)orderBy 84 | limit:(NSUInteger)limit 85 | offset:(NSUInteger)offset 86 | { 87 | NSUInteger count = [self matchCount:condition]; 88 | NSArray *array = [self match:condition orderBy:orderBy limit:limit offset:offset]; 89 | return @{ @"count": @(count), @"list": array }; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /VVSequelize/Util/VVDBCipher.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVDBCipher.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/6/19. 6 | // 7 | 8 | #ifdef SQLITE_HAS_CODEC 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface VVDBCipher : NSObject 15 | 16 | /// encrypt database 17 | /// @param path database file path 18 | /// @param key encrypt key 19 | /// @param options cipher options, such as `pragma cipher_plaintext_header_size = 32;` 20 | + (BOOL)encrypt:(NSString *)path 21 | key:(NSString *)key 22 | options:(nullable NSArray *)options; 23 | 24 | /// decrypt database 25 | + (BOOL)decrypt:(NSString *)path 26 | key:(NSString *)key 27 | options:(nullable NSArray *)options; 28 | 29 | /// encrypt database 30 | + (BOOL)encrypt:(NSString *)source 31 | target:(NSString *)target 32 | key:(NSString *)key 33 | options:(nullable NSArray *)options; 34 | 35 | /// decrypt databse 36 | + (BOOL)decrypt:(NSString *)source 37 | target:(NSString *)target 38 | key:(NSString *)key 39 | options:(nullable NSArray *)options; 40 | 41 | /// change databse encrypt key 42 | + (BOOL)change:(NSString *)source 43 | srcKey:(nullable NSString *)srcKey 44 | srcOpts:(nullable NSArray *)srcOpts 45 | tarKey:(nullable NSString *)tarKey 46 | tarOpts:(nullable NSArray *)tarOpts; 47 | 48 | /// change databse encrypt key 49 | + (BOOL)change:(NSString *)source 50 | target:(NSString *)target 51 | srcKey:(nullable NSString *)srcKey 52 | srcOpts:(nullable NSArray *)srcOpts 53 | tarKey:(nullable NSString *)tarKey 54 | tarOpts:(nullable NSArray *)tarOpts; 55 | 56 | + (void)removeDatabaseFile:(NSString *)path; 57 | 58 | + (void)moveDatabaseFile:(NSString *)srcPath 59 | toPath:(NSString *)dstPath 60 | force:(BOOL)force; 61 | 62 | + (void)copyDatabaseFile:(NSString *)srcPath 63 | toPath:(NSString *)dstPath 64 | force:(BOOL)force; 65 | 66 | @end 67 | 68 | NS_ASSUME_NONNULL_END 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /VVSequelize/Util/VVDBCipher.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVDBCipher.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/6/19. 6 | // 7 | 8 | #ifdef SQLITE_HAS_CODEC 9 | 10 | #import "VVDBCipher.h" 11 | #import "sqlite3.h" 12 | 13 | @implementation VVDBCipher 14 | 15 | + (BOOL)encrypt:(NSString *)path 16 | key:(NSString *)key 17 | options:(NSArray *)options 18 | { 19 | NSString *target = [NSString stringWithFormat:@"%@-tmpcipher", path]; 20 | if ([self encrypt:path target:target key:key options:options]) { 21 | [self removeDatabaseFile:path]; 22 | [self moveDatabaseFile:target toPath:path force:YES]; 23 | return YES; 24 | } 25 | return NO; 26 | } 27 | 28 | + (BOOL)decrypt:(NSString *)path 29 | key:(NSString *)key 30 | options:(NSArray *)options 31 | { 32 | NSString *target = [NSString stringWithFormat:@"%@-tmpcipher", path]; 33 | if ([self decrypt:path target:target key:key options:options]) { 34 | [self removeDatabaseFile:path]; 35 | [self moveDatabaseFile:target toPath:path force:YES]; 36 | return YES; 37 | } 38 | return NO; 39 | } 40 | 41 | + (BOOL)encrypt:(NSString *)source 42 | target:(NSString *)target 43 | key:(NSString *)key 44 | options:(NSArray *)options 45 | { 46 | return [self change:source target:target srcKey:nil srcOpts:nil tarKey:key tarOpts:options]; 47 | } 48 | 49 | + (BOOL)decrypt:(NSString *)source 50 | target:(NSString *)target 51 | key:(NSString *)key 52 | options:(NSArray *)options 53 | { 54 | return [self change:source target:target srcKey:key srcOpts:options tarKey:nil tarOpts:nil]; 55 | } 56 | 57 | + (BOOL)change:(NSString *)source 58 | srcKey:(nullable NSString *)srcKey 59 | srcOpts:(nullable NSArray *)srcOpts 60 | tarKey:(nullable NSString *)tarKey 61 | tarOpts:(nullable NSArray *)tarOpts 62 | { 63 | NSString *target = [NSString stringWithFormat:@"%@-tmpcipher", source]; 64 | if ([self change:source target:target srcKey:srcKey srcOpts:srcOpts tarKey:tarKey tarOpts:tarOpts]) { 65 | [self removeDatabaseFile:source]; 66 | [self moveDatabaseFile:target toPath:source force:YES]; 67 | return YES; 68 | } 69 | return NO; 70 | } 71 | 72 | + (BOOL)change:(NSString *)source 73 | target:(NSString *)target 74 | srcKey:(NSString *)srcKey 75 | srcOpts:(NSArray *)srcOpts 76 | tarKey:(NSString *)tarKey 77 | tarOpts:(NSArray *)tarOpts 78 | { 79 | sqlite3 *db; 80 | int rc = sqlite3_open(source.UTF8String, &db); 81 | if (rc != SQLITE_OK) return NO; 82 | 83 | if (srcKey.length > 0) { 84 | const char *xKey = srcKey.UTF8String ? : ""; 85 | int nKey = (int)strlen(xKey); 86 | if (nKey == 0) return NO; 87 | rc = sqlite3_key(db, xKey, nKey); 88 | if (rc != SQLITE_OK) return NO; 89 | } 90 | NSString *attach = [NSString stringWithFormat:@"ATTACH DATABASE '%@' AS tardb KEY '%@';", target, tarKey.length > 0 ? tarKey : @""]; 91 | NSString *export = @"BEGIN IMMEDIATE;SELECT sqlcipher_export('tardb');COMMIT;"; 92 | NSString *detach = @"DETACH DATABASE tardb;"; 93 | NSArray *xSrcOpts = srcKey.length > 0 ? [self pretreat:srcOpts db:@"main"] : @[]; 94 | NSArray *xTarOpts = tarKey.length > 0 ? [self pretreat:tarOpts db:@"tardb"] : @[]; 95 | 96 | NSMutableArray *array = [NSMutableArray array]; 97 | [array addObjectsFromArray:xSrcOpts]; 98 | [array addObject:attach]; 99 | [array addObjectsFromArray:xTarOpts]; 100 | [array addObject:export]; 101 | [array addObject:detach]; 102 | 103 | for (NSString *sql in array) { 104 | int rc = sqlite3_exec(db, sql.UTF8String, NULL, NULL, NULL); 105 | #if DEBUG 106 | if (rc != SQLITE_OK) { 107 | printf("[VVDBCipher][ERROR] code: %i, error: %s, sql: %s\n", rc, sqlite3_errmsg(db), sql.UTF8String); 108 | } else { 109 | printf("[VVDBCipher][DEBUG] code: %i, sql: %s\n", rc, sql.UTF8String); 110 | } 111 | #endif 112 | if (rc != SQLITE_OK) break; 113 | } 114 | sqlite3_close(db); 115 | return rc == SQLITE_OK; 116 | } 117 | 118 | + (void)removeDatabaseFile:(NSString *)path 119 | { 120 | if (path.length == 0) return; 121 | 122 | NSFileManager *fm = [NSFileManager defaultManager]; 123 | NSString *shm = [path stringByAppendingString:@"-shm"]; 124 | NSString *wal = [path stringByAppendingString:@"-wal"]; 125 | [fm removeItemAtPath:path error:nil]; 126 | [fm removeItemAtPath:shm error:nil]; 127 | [fm removeItemAtPath:wal error:nil]; 128 | } 129 | 130 | + (void)moveDatabaseFile:(NSString *)srcPath 131 | toPath:(NSString *)dstPath 132 | force:(BOOL)force 133 | { 134 | if (srcPath.length == 0 || dstPath.length == 0 || [srcPath isEqualToString:dstPath]) return; 135 | 136 | NSFileManager *fm = [NSFileManager defaultManager]; 137 | NSString *dstdir = [dstPath stringByDeletingLastPathComponent]; 138 | BOOL isdir = NO; 139 | BOOL exist = [fm fileExistsAtPath:dstdir isDirectory:&isdir]; 140 | if (!exist || !isdir) { 141 | [fm createDirectoryAtPath:dstdir withIntermediateDirectories:YES attributes:nil error:nil]; 142 | } 143 | 144 | NSString *shm = [srcPath stringByAppendingString:@"-shm"]; 145 | NSString *wal = [srcPath stringByAppendingString:@"-wal"]; 146 | NSString *dstshm = [dstPath stringByAppendingString:@"-shm"]; 147 | NSString *dstwal = [dstPath stringByAppendingString:@"-wal"]; 148 | if (force) { 149 | [fm removeItemAtPath:dstPath error:nil]; 150 | [fm removeItemAtPath:dstshm error:nil]; 151 | [fm removeItemAtPath:dstwal error:nil]; 152 | } 153 | [fm moveItemAtPath:srcPath toPath:dstPath error:nil]; 154 | [fm moveItemAtPath:shm toPath:dstshm error:nil]; 155 | [fm moveItemAtPath:wal toPath:dstwal error:nil]; 156 | } 157 | 158 | + (void)copyDatabaseFile:(NSString *)srcPath 159 | toPath:(NSString *)dstPath 160 | force:(BOOL)force 161 | { 162 | if (srcPath.length == 0 || dstPath.length == 0 || [srcPath isEqualToString:dstPath]) return; 163 | 164 | NSFileManager *fm = [NSFileManager defaultManager]; 165 | NSString *dstdir = [dstPath stringByDeletingLastPathComponent]; 166 | BOOL isdir = NO; 167 | BOOL exist = [fm fileExistsAtPath:dstdir isDirectory:&isdir]; 168 | if (!exist || !isdir) { 169 | [fm createDirectoryAtPath:dstdir withIntermediateDirectories:YES attributes:nil error:nil]; 170 | } 171 | 172 | NSString *shm = [srcPath stringByAppendingString:@"-shm"]; 173 | NSString *wal = [srcPath stringByAppendingString:@"-wal"]; 174 | NSString *dstshm = [dstPath stringByAppendingString:@"-shm"]; 175 | NSString *dstwal = [dstPath stringByAppendingString:@"-wal"]; 176 | if (force) { 177 | [fm removeItemAtPath:dstPath error:nil]; 178 | [fm removeItemAtPath:dstshm error:nil]; 179 | [fm removeItemAtPath:dstshm error:nil]; 180 | } 181 | [fm copyItemAtPath:srcPath toPath:dstPath error:nil]; 182 | [fm copyItemAtPath:shm toPath:dstshm error:nil]; 183 | [fm copyItemAtPath:wal toPath:dstwal error:nil]; 184 | } 185 | 186 | + (NSArray *)pretreat:(NSArray *)options db:(NSString *)db 187 | { 188 | if (db.length == 0) return options ? : @[]; 189 | 190 | NSRegularExpression *exp = [NSRegularExpression regularExpressionWithPattern:@"[a-z]|[A-Z]" options:0 error:nil]; 191 | NSString *dbPrefix = [db stringByAppendingString:@"."]; 192 | NSMutableArray *results = [NSMutableArray arrayWithCapacity:options.count]; 193 | for (NSString *option in options) { 194 | NSArray *subOptions = [option componentsSeparatedByString:@";"]; 195 | for (NSString *pragma in subOptions) { 196 | NSRange r = [pragma.lowercaseString rangeOfString:@"pragma "]; 197 | if (r.location == NSNotFound) { 198 | #if DEBUG 199 | NSRange range = [pragma rangeOfString:@" *" options:NSRegularExpressionSearch]; 200 | if (range.location == NSNotFound) printf("[VVDBCipher][DEBUG] invalid option: %s\n", pragma.UTF8String); 201 | #endif 202 | continue; 203 | } 204 | NSUInteger loc = NSMaxRange(r); 205 | NSTextCheckingResult *first = [exp firstMatchInString:pragma options:0 range:NSMakeRange(loc, pragma.length - loc)]; 206 | if (!first) continue; 207 | NSMutableString *string = [pragma mutableCopy]; 208 | [string insertString:dbPrefix atIndex:first.range.location]; 209 | [string appendString:@";"]; 210 | [results addObject:string]; 211 | } 212 | } 213 | return results; 214 | } 215 | 216 | @end 217 | 218 | #endif 219 | -------------------------------------------------------------------------------- /VVSequelize/Util/VVDBUpgrader.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVDBUpgrader.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/8/11. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface NSString (VVDBUpgrader) 13 | + (NSComparisonResult)vv_compareVersion:(NSString *)version1 with:(NSString *)version2; 14 | @end 15 | 16 | typedef NS_ENUM(NSUInteger, VVDBUpgradeStrategy) { 17 | VVDBUpgradeStrategyDefault = 0, 18 | VVDBUpgradeStrategyForceNoRecord, 19 | VVDBUpgradeStrategyForceAnyCase, 20 | }; 21 | 22 | /// upgrade item 23 | @interface VVDBUpgradeItem : NSObject 24 | @property (nonatomic, weak, nullable) id target; 25 | @property (nonatomic, assign, nullable) SEL action; ///< - (BOOL)dosomething:(VVDBUpgradeItem*)item; 26 | @property (nonatomic, copy, nullable) BOOL (^ handler)(VVDBUpgradeItem *); 27 | 28 | @property (nonatomic, copy, nonnull) NSString *identifier; ///< set as unique 29 | @property (nonatomic, copy, nonnull) NSString *version; 30 | @property (nonatomic, assign) NSUInteger stage; 31 | @property (nonatomic, assign) CGFloat priority; ///< 0.0 ~ 1.0, default is 0.5 32 | @property (nonatomic, assign) CGFloat weight; ///< 1.0 ~ ∞, default is 1.0 33 | @property (nonatomic, assign) CGFloat progress; ///< 0.0 ~ 100.0 34 | @property (nonatomic, assign) VVDBUpgradeStrategy strategy; ///< default is VVDBUpgradeStrategyDefault 35 | 36 | @property (nonatomic, strong) id reserved; ///< for additional data 37 | 38 | + (instancetype)itemWithIdentifier:(NSString *)identifier 39 | version:(NSString *)version 40 | stage:(NSUInteger)stage 41 | target:(id)target 42 | action:(SEL)action; 43 | 44 | + (instancetype)itemWithIdentifier:(NSString *)identifier 45 | version:(NSString *)version 46 | stage:(NSUInteger)stage 47 | handler:(BOOL (^)(VVDBUpgradeItem *))handler; 48 | 49 | - (instancetype)initWithIdentifier:(NSString *)identifier 50 | version:(NSString *)version 51 | stage:(NSUInteger)stage; 52 | 53 | /// compare with other item 54 | - (NSComparisonResult)compare:(VVDBUpgradeItem *)other; 55 | 56 | @end 57 | 58 | @interface VVDBUpgrader : NSObject 59 | 60 | /// Key to save the last upgraded version in NSUserDefaults 61 | @property (nonatomic, copy) NSString *versionKey; 62 | 63 | /// upgrade progress 64 | @property (nonatomic, strong) NSProgress *progress; 65 | 66 | /// upgrading or not 67 | @property (nonatomic, assign, readonly, getter = isUpgrading) BOOL upgrading; 68 | 69 | /// add upgrade item 70 | - (void)addItem:(VVDBUpgradeItem *)item; 71 | 72 | /// add upgrade items 73 | - (void)addItems:(NSArray *)items; 74 | 75 | /// cancel upgrade 76 | - (void)cancel; 77 | 78 | /// check if the updater needs to be executed 79 | - (BOOL)needUpgrade; 80 | 81 | /// check if the item needs to be upgraded 82 | - (BOOL)isNeedToUpgrade:(VVDBUpgradeItem *)item; 83 | 84 | /// upgrade all stages 85 | - (void)upgradeAll; 86 | 87 | /// upgrade one stage 88 | - (void)upgradeStage:(NSUInteger)stage; 89 | 90 | /// debug upgrade items 91 | - (void)debugUpgradeItems:(NSArray *)items progress:(NSProgress *)progress; 92 | 93 | @end 94 | 95 | NS_ASSUME_NONNULL_END 96 | -------------------------------------------------------------------------------- /VVSequelize/Util/VVOrmRoute.h: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrmRoute.h 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/20. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | /// database and table to write to 13 | @interface VVOrmRoute : NSObject 14 | @property (nonatomic, copy) NSString *dbPath; 15 | @property (nonatomic, copy) NSString *tableName; 16 | @end 17 | 18 | /// database and table to write to 19 | @protocol VVOrmRoute 20 | 21 | + (VVOrmRoute *)routeOfObject:(id)object; 22 | 23 | + (NSDictionary *)routesOfObjects:(NSArray *)objects; 24 | 25 | /// @return {route:[sub_start,sub_end]} 26 | + (NSDictionary *)routesOfRange:(NSUInteger)type 27 | start:(id)start 28 | end:(id)end; 29 | 30 | @end 31 | 32 | NS_ASSUME_NONNULL_END 33 | -------------------------------------------------------------------------------- /VVSequelize/Util/VVOrmRoute.m: -------------------------------------------------------------------------------- 1 | // 2 | // VVOrmRoute.m 3 | // VVSequelize 4 | // 5 | // Created by Valo on 2018/9/20. 6 | // 7 | 8 | #import "VVOrmRoute.h" 9 | 10 | @implementation VVOrmRoute 11 | 12 | - (id)copyWithZone:(NSZone *)zone 13 | { 14 | VVOrmRoute *route = [[VVOrmRoute alloc] init]; 15 | route.dbPath = [self.dbPath copy]; 16 | route.tableName = [self.tableName copy]; 17 | return route; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /VVSequelize/VVSequelize.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | #ifdef VVSEQUELIZE_CORE 14 | #import "NSObject+VVKeyValue.h" 15 | #import "NSObject+VVOrm.h" 16 | #import "VVClassInfo.h" 17 | #import "VVDatabase.h" 18 | #import "VVDBStatement.h" 19 | #import "VVDatabase+Additions.h" 20 | #import "VVForeignKey.h" 21 | #import "VVSelect.h" 22 | #import "VVOrm.h" 23 | #import "VVOrmable.h" 24 | #import "VVOrmConfig.h" 25 | #import "VVOrmDefs.h" 26 | #import "VVOrmView.h" 27 | #import "VVOrm+Create.h" 28 | #import "VVOrm+Delete.h" 29 | #import "VVOrm+Retrieve.h" 30 | #import "VVOrm+Update.h" 31 | #endif 32 | 33 | #ifdef VVSEQUELIZE_FTS 34 | #import "NSString+Tokenizer.h" 35 | #import "VVResultMatch.h" 36 | #import "VVTokenEnumerator.h" 37 | #import "VVPinYinSegmentor.h" 38 | #import "VVFtsable.h" 39 | #import "VVDatabase+FTS.h" 40 | #import "VVOrm+FTS.h" 41 | #endif 42 | 43 | #ifdef VVSEQUELIZE_UTIL 44 | #import "VVDBCipher.h" 45 | #import "VVDBUpgrader.h" 46 | #import "VVOrmRoute.h" 47 | #endif 48 | 49 | FOUNDATION_EXPORT double VVSequelizeVersionNumber; 50 | FOUNDATION_EXPORT const unsigned char VVSequelizeVersionString[]; 51 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------